Skip to content

Commit b826495

Browse files
feat(cat-voices): connect propsal action (#2144)
* feat: saving published document in local db * feat: stream with proper user proposal * feat: add query for fetching latest document version by ref and type in DocumentsDao * feat: remove unused back navigation from proposal builder action and cleanup imports in the proposal builder status action file * feat: integrate ProposalVersionInfoText for better display of proposal iteration details and streamline imports in the card widget * refactor: replace SignedDocumentRef with DocumentRef across multiple repositories and services for consistency and clarity * refactor: streamline proposal data handling by creating helper methods and improving data fetching logic in ProposalServiceImpl * test: enhance proposal service tests with additional mocks for proposal publish and comments count handling * refactor: improve document processing logic by consolidating async operations and adding a dedicated method for document handling * refactor: enhance document querying logic and streamline proposal data handling with improved filtering and error handling mechanisms * refactor: simplify proposal subscription setup and enhance document processing with better error handling and logging * test: adding tests --------- Co-authored-by: Damian Moliński <[email protected]>
1 parent 5f2da0a commit b826495

File tree

20 files changed

+786
-256
lines changed

20 files changed

+786
-256
lines changed

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

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import 'package:catalyst_voices/widgets/cards/proposal_card_widgets.dart';
55
import 'package:catalyst_voices/widgets/modals/proposals/share_proposal_dialog.dart';
66
import 'package:catalyst_voices/widgets/text/day_month_time_text.dart';
77
import 'package:catalyst_voices/widgets/widgets.dart';
8+
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
89
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
910
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
1011
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
1112
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
1213
import 'package:flutter/material.dart';
14+
import 'package:flutter_bloc/flutter_bloc.dart';
1315

1416
/// Displays a proposal in pending state on a card.
1517
class PendingProposalCard extends StatefulWidget {
@@ -153,61 +155,63 @@ class _PendingProposalCardState extends State<PendingProposalCard> {
153155

154156
@override
155157
Widget build(BuildContext context) {
156-
return Material(
157-
key: const Key('ProposalCard'),
158-
color: Colors.transparent,
159-
child: InkWell(
160-
statesController: _statesController,
161-
onTap: widget.onTap,
162-
child: ValueListenableBuilder(
163-
valueListenable: _statesController,
164-
builder: (context, value, child) => Container(
165-
constraints: const BoxConstraints(maxWidth: 326),
166-
decoration: BoxDecoration(
167-
color: context.colors.elevationsOnSurfaceNeutralLv1White,
168-
borderRadius: BorderRadius.circular(12),
169-
border: Border.all(
170-
color: _border.resolve(_statesController.value),
158+
return SizedBox(
159+
height: 454,
160+
child: Material(
161+
key: const Key('ProposalCard'),
162+
color: Colors.transparent,
163+
child: InkWell(
164+
statesController: _statesController,
165+
onTap: widget.onTap,
166+
child: ValueListenableBuilder(
167+
valueListenable: _statesController,
168+
builder: (context, value, child) => Container(
169+
constraints: const BoxConstraints(maxWidth: 326),
170+
decoration: BoxDecoration(
171+
color: context.colors.elevationsOnSurfaceNeutralLv1White,
172+
borderRadius: BorderRadius.circular(12),
173+
border: Border.all(
174+
color: _border.resolve(_statesController.value),
175+
),
171176
),
172-
),
173-
child: Column(
174-
crossAxisAlignment: CrossAxisAlignment.stretch,
175-
children: [
176-
Padding(
177-
padding: const EdgeInsets.all(16),
178-
child: Column(
179-
crossAxisAlignment: CrossAxisAlignment.start,
180-
children: [
181-
_Topbar(
182-
proposalRef: widget.proposal.ref,
183-
showStatus: widget.showStatus,
184-
isFavorite: widget.isFavorite,
185-
onFavoriteChanged: widget.onFavoriteChanged,
186-
),
187-
_Category(
188-
category: widget.proposal.category,
189-
),
190-
const SizedBox(height: 4),
191-
_Title(text: widget.proposal.title),
192-
_Author(author: widget.proposal.author),
193-
_FundsAndDuration(
194-
funds: widget.proposal.fundsRequested,
195-
duration: widget.proposal.duration,
196-
),
197-
const SizedBox(height: 12),
198-
_Description(text: widget.proposal.description),
199-
const SizedBox(height: 24),
200-
_ProposalInfo(
201-
proposalStage: widget.proposal.publishStage,
202-
version: widget.proposal.version,
203-
lastUpdate: widget.proposal.lastUpdateDate,
204-
commentsCount: widget.proposal.commentsCount,
205-
showLastUpdate: widget.showLastUpdate,
206-
),
207-
],
208-
),
177+
child: Padding(
178+
padding: const EdgeInsets.all(16),
179+
child: Column(
180+
crossAxisAlignment: CrossAxisAlignment.start,
181+
children: [
182+
_Topbar(
183+
proposalRef: widget.proposal.ref,
184+
showStatus: widget.showStatus,
185+
isFavorite: widget.isFavorite,
186+
onFavoriteChanged: widget.onFavoriteChanged,
187+
),
188+
_Category(
189+
category: widget.proposal.category,
190+
),
191+
const SizedBox(height: 4),
192+
Expanded(
193+
child: _Title(text: widget.proposal.title),
194+
),
195+
_Author(author: widget.proposal.author),
196+
_FundsAndDuration(
197+
funds: widget.proposal.fundsRequested,
198+
duration: widget.proposal.duration,
199+
),
200+
const SizedBox(height: 12),
201+
Expanded(
202+
child: _Description(text: widget.proposal.description),
203+
),
204+
const SizedBox(height: 12),
205+
_ProposalInfo(
206+
proposalStage: widget.proposal.publishStage,
207+
version: widget.proposal.version,
208+
lastUpdate: widget.proposal.lastUpdateDate,
209+
commentsCount: widget.proposal.commentsCount,
210+
showLastUpdate: widget.showLastUpdate,
211+
),
212+
],
209213
),
210-
],
214+
),
211215
),
212216
),
213217
),
@@ -359,8 +363,16 @@ class _ProposalInfo extends StatelessWidget {
359363
if (lastUpdate == null) {
360364
return '';
361365
}
366+
final timezone = context.select<SessionCubit?, TimezonePreferences>(
367+
(value) => value?.state.settings.timezone ?? TimezonePreferences.local,
368+
);
369+
370+
final effectiveData = switch (timezone) {
371+
TimezonePreferences.utc => lastUpdate!.toUtc(),
372+
TimezonePreferences.local => lastUpdate!.toLocal(),
373+
};
362374
final dt =
363-
DateFormatter.formatDateTimeParts(lastUpdate!, includeYear: true);
375+
DateFormatter.formatDateTimeParts(effectiveData, includeYear: true);
364376

365377
return context.l10n.publishedOn(dt.date, dt.time);
366378
}

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

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import 'dart:async';
22

33
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
44
import 'package:catalyst_voices/common/ext/proposal_publish_ext.dart';
5-
import 'package:catalyst_voices/common/formatters/date_formatter.dart';
65
import 'package:catalyst_voices/routes/routing/proposal_builder_route.dart';
76
import 'package:catalyst_voices/widgets/buttons/voices_text_button.dart';
87
import 'package:catalyst_voices/widgets/common/affix_decorator.dart';
98
import 'package:catalyst_voices/widgets/modals/proposals/proposal_builder_delete_confirmation_dialog.dart';
9+
import 'package:catalyst_voices/widgets/text/proposal_version_info_text.dart';
1010
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
1111
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
1212
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
@@ -135,7 +135,6 @@ class _IterationVersionList extends StatelessWidget {
135135
crossAxisAlignment: CrossAxisAlignment.stretch,
136136
spacing: 6,
137137
children: versions
138-
.skip(1)
139138
.map(
140139
(e) => _IterationVersion(
141140
version: e,
@@ -157,7 +156,8 @@ class _ProposalIterationHistoryState extends State<ProposalIterationHistory> {
157156

158157
bool get _hasNewerLocalIteration {
159158
if (widget.proposal.versions.isEmpty) return false;
160-
return widget.proposal.versions.first.isLatestVersion(
159+
final latestVersion = widget.proposal.versions.first;
160+
return latestVersion.isLatestVersion(
161161
widget.proposal.selfRef.version ?? '',
162162
);
163163
}
@@ -177,7 +177,7 @@ class _ProposalIterationHistoryState extends State<ProposalIterationHistory> {
177177
Padding(
178178
padding: const EdgeInsets.symmetric(
179179
vertical: 6,
180-
horizontal: 17,
180+
horizontal: 16,
181181
),
182182
child: Row(
183183
children: [
@@ -197,17 +197,22 @@ class _ProposalIterationHistoryState extends State<ProposalIterationHistory> {
197197
boldTitle: true,
198198
)
199199
else
200-
Text(
201-
context.l10n.publishingHistory,
202-
style: context.textTheme.labelMedium?.copyWith(
203-
color: context.colors.textOnPrimaryLevel1,
200+
Padding(
201+
padding: const EdgeInsets.symmetric(
202+
vertical: 14,
203+
),
204+
child: Text(
205+
context.l10n.publishingHistory,
206+
style: context.textTheme.labelMedium?.copyWith(
207+
color: context.colors.textOnPrimaryLevel1,
208+
),
204209
),
205210
),
206211
const Spacer(),
207212
Offstage(
208213
offstage: !_hasNewerLocalIteration,
209214
child: _Actions(
210-
ref: widget.proposal.selfRef,
215+
ref: widget.proposal.versions.first.selfRef,
211216
),
212217
),
213218
],
@@ -249,21 +254,15 @@ class _Title extends StatelessWidget {
249254

250255
@override
251256
Widget build(BuildContext context) {
252-
final datetime = DateFormatter.formatDayMonthTime(updateDate);
253257
final publishName = publish.localizedWorkspaceName(context.l10n);
254258
return AffixDecorator(
255259
prefix: VoicesAssets.icons.documentText.buildIcon(size: 18),
256-
child: Text(
257-
context.l10n.proposalIterationPublishUpdateAndTitle(
258-
iteration,
259-
publishName,
260-
datetime,
261-
title,
262-
),
263-
style: context.textTheme.labelMedium?.copyWith(
264-
color: context.colors.textOnPrimaryLevel1,
265-
fontWeight: boldTitle ? FontWeight.bold : FontWeight.w100,
266-
),
260+
child: ProposalVersionInfoText(
261+
iteration: iteration,
262+
publish: publishName,
263+
updateDate: updateDate,
264+
title: title,
265+
boldTitle: boldTitle,
267266
),
268267
);
269268
}

catalyst_voices/apps/voices/lib/widgets/text/last_edit_date.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ class LastEditDate extends StatelessWidget {
2020
Widget build(BuildContext context) {
2121
return TimezoneDateTimeText(
2222
dateTime,
23-
formatter: (context, dateTime) {
24-
final date = DateFormatter.formatDayMonthTime(
25-
dateTime,
23+
formatter: (context, date) {
24+
final dt = DateFormatter.formatDayMonthTime(
25+
date,
2626
);
27-
return context.l10n.lastEditDate(date);
27+
return context.l10n.lastEditDate(dt);
2828
},
2929
style: textStyle ??
3030
context.textTheme.labelMedium?.copyWith(
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import 'package:catalyst_voices/common/ext/build_context_ext.dart';
2+
import 'package:catalyst_voices/common/formatters/date_formatter.dart';
3+
import 'package:catalyst_voices/widgets/text/timezone_date_time_text.dart';
4+
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
5+
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
6+
import 'package:flutter/material.dart';
7+
8+
class ProposalVersionInfoText extends StatelessWidget {
9+
final String title;
10+
final String publish;
11+
final int iteration;
12+
final DateTime updateDate;
13+
final bool boldTitle;
14+
const ProposalVersionInfoText({
15+
super.key,
16+
required this.title,
17+
required this.publish,
18+
required this.iteration,
19+
required this.updateDate,
20+
required this.boldTitle,
21+
});
22+
23+
@override
24+
Widget build(BuildContext context) {
25+
return TimezoneDateTimeTextTheme(
26+
data: TimezoneDateTimeTextThemeData(
27+
timestampTextStyle: WidgetStatePropertyAll(
28+
context.textTheme.labelMedium?.copyWith(
29+
color: context.colors.textOnPrimaryLevel1,
30+
fontWeight: boldTitle ? FontWeight.bold : FontWeight.w100,
31+
),
32+
),
33+
),
34+
child: TimezoneDateTimeText(
35+
updateDate,
36+
showTimezone: false,
37+
formatter: (context, dateTime) {
38+
final datetime = DateFormatter.formatDayMonthTime(dateTime);
39+
40+
return context.l10n.proposalIterationPublishUpdateAndTitle(
41+
iteration,
42+
publish,
43+
datetime,
44+
title,
45+
);
46+
},
47+
),
48+
);
49+
}
50+
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@ import 'dart:async';
22

33
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
44
import 'package:catalyst_voices_services/catalyst_voices_services.dart';
5+
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
56
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
67
import 'package:equatable/equatable.dart';
78
import 'package:flutter_bloc/flutter_bloc.dart';
89

910
part 'discovery_state.dart';
1011

12+
final _logger = Logger('DiscoveryCubit');
13+
1114
class DiscoveryCubit extends Cubit<DiscoveryState> {
1215
// ignore: unused_field
1316
final CampaignService _campaignService;
@@ -90,10 +93,12 @@ class DiscoveryCubit extends Cubit<DiscoveryState> {
9093
}
9194

9295
void _setupProposalsSubscription() {
96+
_logger.info('Setting up proposals subscription');
9397
_proposalsSubscription =
9498
_proposalService.watchLatestProposals(limit: 7).listen(
9599
(proposals) {
96100
if (isClosed) return;
101+
_logger.info('Got proposals: ${proposals.length}');
97102
final proposalList = proposals
98103
.map(
99104
(e) => PendingProposal.fromProposal(

catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/proposal_builder/proposal_builder_bloc.dart

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -381,8 +381,6 @@ final class ProposalBuilderBloc
381381
final proposalData = await _proposalService.getProposal(
382382
ref: proposalRef,
383383
);
384-
// TODO(LynxLynxx): check if new local proposal is created
385-
// when SignedDocumentRef is used instead of DraftRef
386384

387385
final proposal = Proposal.fromData(proposalData);
388386

@@ -396,7 +394,6 @@ final class ProposalBuilderBloc
396394
isLatest: index == proposalData.versions.length - 1,
397395
);
398396
}).toList();
399-
400397
final categoryId = proposalData.categoryId;
401398
final category = await _campaignService.getCategory(categoryId);
402399

catalyst_voices/packages/internal/catalyst_voices_blocs/lib/src/workspace/workspace_bloc.dart

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,14 @@ final class WorkspaceBloc extends Bloc<WorkspaceEvent, WorkspaceState>
137137
if (proposal == null || proposal.selfRef is! SignedDocumentRef) {
138138
return emitError(const LocalizedUnknownException());
139139
}
140-
await _proposalService.unlockProposal(
141-
proposalRef: proposal.selfRef as SignedDocumentRef,
142-
categoryId: proposal.categoryId,
143-
);
140+
try {
141+
await _proposalService.forgetProposal(
142+
proposalRef: proposal.selfRef as SignedDocumentRef,
143+
categoryId: proposal.categoryId,
144+
);
145+
} catch (e, stackTrace) {
146+
_logger.severe('Error forgetting proposal', e, stackTrace);
147+
}
144148
}
145149

146150
Future<void> _getTimelineItems(
@@ -189,9 +193,9 @@ final class WorkspaceBloc extends Bloc<WorkspaceEvent, WorkspaceState>
189193

190194
add(LoadProposalsEvent(proposals));
191195
},
192-
onError: (Object error) {
196+
onError: (Object error, StackTrace stackTrace) {
193197
if (isClosed) return;
194-
_logger.info('Users proposals stream error', error);
198+
_logger.info('Users proposals stream error', error, stackTrace);
195199
add(ErrorLoadProposalsEvent(LocalizedException.create(error)));
196200
},
197201
);
@@ -227,5 +231,6 @@ final class WorkspaceBloc extends Bloc<WorkspaceEvent, WorkspaceState>
227231
await _proposalsSubscription?.cancel();
228232
_proposalsSubscription = null;
229233
_setupProposalsSubscription();
234+
emit(state.copyWith(isLoading: false));
230235
}
231236
}

0 commit comments

Comments
 (0)