Skip to content

Commit 1894823

Browse files
feat(cat-voices): integrate proposals list - Part 2 (#2227)
* proposals tabs + category selector widget * feat: update url when category changes * feat: proposals type as query param * fix: full category name * refactor: split widgets into separate files * wip: proposals pagination part 1 * wip: Proposals filters model * feat: integrate fav * chore: add filters param to get proposals page request * chore: proposals types count * chore: moving classes and models around * wip: counting proposals * feat: counting final proposals * feat: favorites count * feat: my proposals count * chore: final proposals action helper * chore: mapping ref ver * chore: cleanup code * feat: count author search * feat: finish proposals count query * fix: melos flutter_rust_bridge version * fix: empty category name * chore: spelling * chore: remove dummy page request delay * chore: spelling * chore: make analyser happy * feat: paging proposals * chore: make ProposalsSignal extend Equatable * refactor: little refactor of methods in ProposalDao * feat: emitting errors when fav change fails * refactor: separate build joined method * feat: filtering proposals types * chore: safe checking proposals + mapping * chore: pagination wrap alignment * chore: include categoryText * chore: proposal card formatting * chore: localized pagination status text * fix: limit unnecessary pagination resets * fix: _IdsFilter * chore: cleanup * fix: comparing category id only * fix: displaying non 0 pages * chore: marking documents as changed when fav is changing * chore: removing fav proposals * fix: my proposals filtering when not authenticated * chore: docs * fix: proposal dao tests * chore: loc plural * chore: explicit switch * refactor: use records syntax
1 parent 7a6721f commit 1894823

File tree

23 files changed

+973
-102
lines changed

23 files changed

+973
-102
lines changed

catalyst_voices/apps/voices/lib/pages/proposals/proposals_page.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ class _ProposalsPageState extends State<ProposalsPage>
123123
currentPage: page.page,
124124
maxResults: page.total,
125125
itemList: page.items,
126+
error: const Optional.empty(),
126127
isLoading: false,
127128
);
128129
}

catalyst_voices/apps/voices/lib/widgets/cards/pending_proposal_card.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class _FundsAndDuration extends StatelessWidget {
120120
@override
121121
Widget build(BuildContext context) {
122122
return Container(
123-
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
123+
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
124124
decoration: BoxDecoration(
125125
color: Theme.of(context).colorScheme.primary.withValues(alpha: 0.12),
126126
borderRadius: BorderRadius.circular(8),

catalyst_voices/apps/voices/lib/widgets/cards/proposal_card.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import 'package:flutter/material.dart';
77
/// A proposal card spanning proposals in different stages.
88
///
99
/// Designed to work with as many cases as [ProposalViewModel] will support.
10+
// TODO(damian-molinski): Refactor this widget to accept minimum data necessary
11+
// not entire ProposalViewModel.
1012
class ProposalCard extends StatelessWidget {
1113
final AssetGenImage? image;
1214
final ProposalViewModel proposal;

catalyst_voices/apps/voices/lib/widgets/pagination/layouts/paginated_grid_view.dart.dart

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ class PaginatedGridView<ItemType> extends StatelessWidget {
4040
valueListenable: _pagingController,
4141
builder: (context, pagingState, _) {
4242
Widget child;
43-
final itemList = _pagingController.itemList;
4443
switch (pagingState.status) {
4544
case PagingStatus.empty:
4645
child = builderDelegate.emptyIndicatorBuilder(context);
@@ -57,12 +56,12 @@ class PaginatedGridView<ItemType> extends StatelessWidget {
5756
key: const Key('PaginatedGridView'),
5857
spacing: 16,
5958
runSpacing: 16,
60-
children: [
61-
for (var i = pagingState.currentFrom;
62-
i <= pagingState.currentTo;
63-
i++)
64-
_itemBuilder(context, itemList[i]),
65-
],
59+
alignment: WrapAlignment.spaceEvenly,
60+
crossAxisAlignment: WrapCrossAlignment.start,
61+
runAlignment: WrapAlignment.start,
62+
children: pagingState.itemList
63+
.map((item) => _itemBuilder(context, item))
64+
.toList(),
6665
),
6766
);
6867
break;
@@ -137,7 +136,11 @@ class _Controls extends StatelessWidget {
137136
children: [
138137
Text(
139138
key: const Key('PaginationText'),
140-
'$fromNumber-$toNumber of $maxResults proposals',
139+
context.l10n.paginationProposalsCounter(
140+
fromNumber,
141+
toNumber,
142+
maxResults,
143+
),
141144
),
142145
VoicesIconButton(
143146
key: const Key('PrevPageBtn'),

catalyst_voices/apps/voices/lib/widgets/pagination/paging_state.dart

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import 'dart:math';
2-
31
import 'package:catalyst_voices/widgets/pagination/paging_status.dart';
42
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
53
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
@@ -54,13 +52,7 @@ class PagingState<ItemType> extends Equatable {
5452
int get currentLastPage => (_itemCount / itemsPerPage).ceil() - 1;
5553

5654
int get currentTo {
57-
if (itemList.isEmpty) {
58-
return 0;
59-
}
60-
return min(
61-
min(currentFrom + itemsPerPage - 1, itemList.length - 1),
62-
maxResults - 1,
63-
);
55+
return currentPage * itemsPerPage + itemsPerPage;
6456
}
6557

6658
int get fromValue => currentFrom + 1;

catalyst_voices/melos.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ command:
122122
url: https://github.com/input-output-hk/flutter-quill
123123
path: flutter_quill_extensions
124124
ref: 532172254c3cee957bbdbb08be922f2c870b7fff
125-
flutter_rust_bridge: 2.9.0
125+
flutter_rust_bridge: 2.5.1
126126
flutter_secure_storage: ^9.2.2
127127
formz: ^0.7.0
128128
intl: ^0.19.0

catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/discovery/discovery_cubit.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,11 @@ class DiscoveryCubit extends Cubit<DiscoveryState> with BlocErrorEmitterMixin {
185185
}
186186

187187
void _setupProposalsSubscription() {
188-
_logger.info('Setting up proposals subscription');
188+
_logger.fine('Setting up proposals subscription');
189189
_proposalsSubscription =
190190
_proposalService.watchLatestProposals(limit: 7).listen(
191191
(proposals) async {
192-
_logger.info('Got proposals: ${proposals.length}');
192+
_logger.finest('Got proposals: ${proposals.length}');
193193
_emitMostRecentProposals(proposals);
194194
final currentFavorites =
195195
await _proposalService.watchFavoritesProposalsIds().first;

catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposals/proposals_cubit.dart

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ final class ProposalsCubit extends Cubit<ProposalsState>
5252
Optional<SignedDocumentRef>? category,
5353
ProposalsFilterType? type,
5454
Optional<String>? searchQuery,
55+
bool sendResetSignal = true,
5556
}) {
5657
final filters = _cache.filters.copyWith(
5758
type: type,
@@ -61,13 +62,19 @@ final class ProposalsCubit extends Cubit<ProposalsState>
6162
searchQuery: searchQuery,
6263
);
6364

65+
if (_cache.filters == filters) {
66+
return;
67+
}
68+
6469
_cache = _cache.copyWith(filters: filters);
6570

6671
if (category != null) _rebuildCategories();
6772

6873
_watchProposalsCount(filters: filters.toCountFilters());
6974

70-
emitSignal(const ResetProposalsPaginationSignal());
75+
if (sendResetSignal) {
76+
emitSignal(const ResetProposalsPaginationSignal());
77+
}
7178
}
7279

7380
void changeSelectedCategory(SignedDocumentRef? categoryId) {
@@ -90,37 +97,19 @@ final class ProposalsCubit extends Cubit<ProposalsState>
9097

9198
Future<void> getProposals(PageRequest request) async {
9299
try {
93-
final campaign = await _campaignService.getActiveCampaign();
94-
if (campaign == null) {
95-
return;
100+
if (_cache.campaign == null) {
101+
final campaign = await _campaignService.getActiveCampaign();
102+
_cache = _cache.copyWith(campaign: Optional(campaign));
96103
}
97104

98-
final campaignStage = CampaignStage.fromCampaign(
99-
campaign,
100-
DateTimeExt.now(),
101-
);
102-
103-
final filters = _cache.filters;
104-
105105
final page = await _proposalService.getProposalsPage(
106106
request: request,
107-
filters: filters,
107+
filters: _cache.filters,
108108
);
109109

110110
_cache = _cache.copyWith(page: Optional(page));
111111

112-
final mappedPage = page.map(
113-
(proposal) {
114-
return ProposalViewModel.fromProposalAtStage(
115-
proposal: proposal,
116-
campaignName: campaign.name,
117-
campaignStage: campaignStage,
118-
);
119-
},
120-
);
121-
122-
final signal = ProposalsPageReadySignal(page: mappedPage);
123-
emitSignal(signal);
112+
_emitCachedProposalsPage();
124113
} catch (error, stackTrace) {
125114
_logger.severe('Failed loading page $request', error, stackTrace);
126115
}
@@ -139,6 +128,7 @@ final class ProposalsCubit extends Cubit<ProposalsState>
139128
onlyMy: Optional(onlyMyProposals),
140129
category: Optional(category),
141130
type: type,
131+
sendResetSignal: false,
142132
);
143133
}
144134

@@ -157,6 +147,21 @@ final class ProposalsCubit extends Cubit<ProposalsState>
157147

158148
emit(state.copyWith(favoritesIds: favoritesIds));
159149

150+
if (!isFavorite && _cache.filters.type == ProposalsFilterType.favorites) {
151+
final page = _cache.page;
152+
if (page != null) {
153+
final proposals = List.of(page.items)
154+
.where((element) => element.selfRef != ref)
155+
.toList();
156+
157+
final updatedPage = page.copyWithItems(proposals);
158+
159+
_cache = _cache.copyWith(page: Optional(updatedPage));
160+
161+
_emitCachedProposalsPage();
162+
}
163+
}
164+
160165
unawaited(_updateFavoriteProposal(ref, isFavorite: isFavorite));
161166
}
162167

@@ -173,6 +178,34 @@ final class ProposalsCubit extends Cubit<ProposalsState>
173178
emit(state.copyWith(hasSearchQuery: !asOptional.isEmpty));
174179
}
175180

181+
void _emitCachedProposalsPage() {
182+
final campaign = _cache.campaign;
183+
final page = _cache.page;
184+
185+
if (campaign == null || page == null) {
186+
return;
187+
}
188+
189+
final campaignStage = CampaignStage.fromCampaign(
190+
campaign,
191+
DateTimeExt.now(),
192+
);
193+
194+
final mappedPage = page.map(
195+
(proposal) {
196+
return ProposalViewModel.fromProposalAtStage(
197+
proposal: proposal,
198+
campaignName: campaign.name,
199+
campaignStage: campaignStage,
200+
);
201+
},
202+
);
203+
204+
final signal = ProposalsPageReadySignal(page: mappedPage);
205+
206+
emitSignal(signal);
207+
}
208+
176209
void _handleActiveAccountIdChange(CatalystId? id) {
177210
changeFilters(author: Optional(id));
178211
}

catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposals/proposals_cubit_cache.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@ import 'package:catalyst_voices_models/catalyst_voices_models.dart';
22
import 'package:equatable/equatable.dart';
33

44
final class ProposalsCubitCache extends Equatable {
5+
final Campaign? campaign;
56
final Page<Proposal>? page;
67
final ProposalsFilters filters;
78
final List<CampaignCategory>? categories;
89
final ProposalsCount count;
910

1011
const ProposalsCubitCache({
12+
this.campaign,
1113
this.page,
1214
this.filters = const ProposalsFilters(),
1315
this.categories,
@@ -16,19 +18,22 @@ final class ProposalsCubitCache extends Equatable {
1618

1719
@override
1820
List<Object?> get props => [
21+
campaign,
1922
page,
2023
filters,
2124
categories,
2225
count,
2326
];
2427

2528
ProposalsCubitCache copyWith({
29+
Optional<Campaign>? campaign,
2630
Optional<Page<Proposal>>? page,
2731
ProposalsFilters? filters,
2832
Optional<List<CampaignCategory>>? categories,
2933
ProposalsCount? count,
3034
}) {
3135
return ProposalsCubitCache(
36+
campaign: campaign.dataOr(this.campaign),
3237
page: page.dataOr(this.page),
3338
filters: filters ?? this.filters,
3439
categories: categories.dataOr(this.categories),

catalyst_voices/packages/internal/catalyst_voices_localization/lib/l10n/intl_en.arb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2504,5 +2504,20 @@
25042504
"viewProposalCommentsDescription": "Open My proposals in the Discovery Space",
25052505
"@viewProposalCommentsDescription": {
25062506
"description": "Text for the view proposal comments description in the card"
2507+
},
2508+
"paginationProposalsCounter": "{from}-{to} of {max, plural, =0{proposals} =1{proposal} other{proposals}}",
2509+
"@paginationProposalsCounter": {
2510+
"description": "Below pagination list, next to page switch controls",
2511+
"placeholders": {
2512+
"from": {
2513+
"type": "int"
2514+
},
2515+
"to": {
2516+
"type": "int"
2517+
},
2518+
"max": {
2519+
"type": "int"
2520+
}
2521+
}
25072522
}
25082523
}

0 commit comments

Comments
 (0)