Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
015c8f2
chore: move const content into fund specific files + fix tests
damian-molinski Sep 23, 2025
aedb7b7
empty f15 campaign
damian-molinski Sep 23, 2025
5f712b0
feat: campaign filters
damian-molinski Sep 23, 2025
cc78327
fix: filter type
damian-molinski Sep 23, 2025
5f7673b
feat: workspace multi campaign
damian-molinski Sep 23, 2025
4d751f9
fix: viewing older proposals
damian-molinski Sep 23, 2025
0380036
static all campaigns
damian-molinski Sep 23, 2025
71008a7
fix: more flexible create new proposal dialog
damian-molinski Sep 23, 2025
6001704
feat: simplify campaign filtering
damian-molinski Sep 23, 2025
bdb1d76
watch user proposals count for active campaign
damian-molinski Sep 23, 2025
a12fb86
campaign change documents clear
damian-molinski Sep 23, 2025
c06fa4f
fix: analyzer
damian-molinski Sep 23, 2025
05e7883
fix: tests
damian-molinski Sep 23, 2025
3667365
Merge f15/cat-app into feat/documents_campaign_isolation_316
damian-molinski Sep 23, 2025
b4cd96a
chore: cleanup
damian-molinski Sep 23, 2025
189806a
Merge branch 'f15/cat-app' into feat/documents_campaign_isolation_316
damian-molinski Sep 24, 2025
60a9eda
explicit categoryId query type
damian-molinski Sep 24, 2025
c31ec9f
chore: move active campaign pointers to one file
damian-molinski Sep 24, 2025
204711d
feat: use category.in filter parameter
damian-molinski Sep 24, 2025
fd98ef7
fix: failing tests
damian-molinski Sep 25, 2025
7ead1cc
feat: add ProposalsFilters.forActiveCampaign constructor
damian-molinski Sep 25, 2025
ecb6adb
final list of all campaigns
damian-molinski Sep 25, 2025
54b313b
chore: refactor creating money logic
damian-molinski Sep 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -470,14 +470,23 @@ final class Dependencies extends DependencyProvider {
},
dispose: (storage) async => storage.dispose(),
);
registerLazySingleton<AppMetaStorage>(
() {
return AppMetaStorageLocalStorage(
sharedPreferences: get<SharedPreferencesAsync>(),
);
},
);
}

void _registerUtils() {
registerLazySingleton<SyncManager>(
() {
return SyncManager(
get<AppMetaStorage>(),
get<SyncStatsStorage>(),
get<DocumentsService>(),
get<CampaignService>(),
);
},
dispose: (manager) async => manager.dispose(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class ProposalBuilderStatusAction extends StatelessWidget {
offstage: state.isLoading || state.error != null,
items: ProposalMenuItemAction.proposalBuilderAvailableOptions(
state.metadata.publish,
fromActiveCampaign: state.metadata.fromActiveCampaign,
),
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class ProposalMenuActionButton extends StatefulWidget {
final int version;
final String title;
final bool hasNewerLocalIteration;
final bool fromActiveCampaign;

const ProposalMenuActionButton({
super.key,
Expand All @@ -31,6 +32,7 @@ class ProposalMenuActionButton extends StatefulWidget {
required this.version,
required this.title,
required this.hasNewerLocalIteration,
required this.fromActiveCampaign,
});

@override
Expand Down Expand Up @@ -65,6 +67,7 @@ class _ProposalMenuActionButtonState extends State<ProposalMenuActionButton> {

List<ProposalMenuItemAction> get _items => ProposalMenuItemAction.workspaceAvailableOptions(
widget.proposalPublish,
fromActiveCampaign: widget.fromActiveCampaign,
);

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class UserProposalSection extends StatefulWidget {
final List<UsersProposalOverview> items;
final String title;
final String info;
final String learnMoreUrl;
final String? learnMoreUrl;
final String emptyTextMessage;

const UserProposalSection({
Expand All @@ -17,7 +17,7 @@ class UserProposalSection extends StatefulWidget {
required this.emptyTextMessage,
required this.title,
required this.info,
required this.learnMoreUrl,
this.learnMoreUrl,
});

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,16 @@ class _Header extends StatelessWidget {
}

class _UserProposalsState extends State<UserProposals> {
List<UsersProposalOverview> get _draft => widget.items.where((e) => e.publish.isDraft).toList();
List<UsersProposalOverview> get _local => widget.items.where((e) => e.publish.isLocal).toList();
Iterable<UsersProposalOverview> get _active => widget.items.where((e) => e.fromActiveCampaign);

List<UsersProposalOverview> get _draft => _active.where((e) => e.publish.isDraft).toList();

Iterable<UsersProposalOverview> get _inactive => widget.items.where((e) => !e.fromActiveCampaign);

List<UsersProposalOverview> get _local => _active.where((e) => e.publish.isLocal).toList();

List<UsersProposalOverview> get _submitted =>
widget.items.where((e) => e.publish.isPublished).toList();
_active.where((e) => e.publish.isPublished).toList();

@override
Widget build(BuildContext context) {
Expand Down Expand Up @@ -74,6 +80,13 @@ class _UserProposalsState extends State<UserProposals> {
info: context.l10n.notPublishedInfoMarkdown,
learnMoreUrl: VoicesConstants.proposalPublishingDocsUrl,
),
if (_inactive.isNotEmpty)
UserProposalSection(
items: _inactive.toList(),
emptyTextMessage: '',
title: context.l10n.notActiveCampaign,
info: context.l10n.notActiveCampaignInfoMarkdown,
),
],
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class _Body extends StatelessWidget {
title: proposal.title,
version: proposal.iteration,
hasNewerLocalIteration: proposal.hasNewerLocalIteration,
fromActiveCampaign: proposal.fromActiveCampaign,
),
],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import 'package:flutter/material.dart';
class SectionLearnMoreHeader extends StatelessWidget with LaunchUrlMixin {
final String title;
final String info;
final String learnMoreUrl;
final String? learnMoreUrl;
final bool isExpanded;
final ValueChanged<bool>? onExpandedChanged;

const SectionLearnMoreHeader({
super.key,
required this.title,
required this.info,
required this.learnMoreUrl,
this.learnMoreUrl,
this.isExpanded = false,
this.onExpandedChanged,
});
Expand Down Expand Up @@ -50,7 +50,7 @@ class SectionLearnMoreHeader extends StatelessWidget with LaunchUrlMixin {
),
),
const Spacer(),
VoicesLearnMoreTextButton.url(url: learnMoreUrl),
if (learnMoreUrl case final value?) VoicesLearnMoreTextButton.url(url: value),
],
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ void main() {
commentsCount: 0,
category: 'Cardano Use Cases: Concept',
categoryId: SignedDocumentRef.generateFirstRef(),
fromActiveCampaign: true,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class DiscoveryCubit extends Cubit<DiscoveryState> with BlocErrorEmitterMixin {
return _proposalService
.watchProposalsPage(
request: const PageRequest(page: 0, size: _maxRecentProposalsCount),
filters: const ProposalsFilters(),
filters: ProposalsFilters.forActiveCampaign(),
order: const UpdateDate(isAscending: false),
)
.map((event) => event.items)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,27 +79,28 @@ class NewProposalCubit extends Cubit<NewProposalState>
if (campaign == null) {
throw StateError('Cannot load proposal, active campaign not found');
}
final templateRef = await _proposalService.getProposalTemplate(
// TODO(LynxLynxx): when we have separate proposal template for generic questions use it here
// right now user can start creating proposal without selecting category.
// Right now every category have the same requirements for title so we can do a fallback for
// first category from the list.
ref: campaign.categories
.firstWhere(
(e) => e.selfRef == categoryRef,
orElse: () => campaign.categories.first,
)
.proposalTemplateRef,
);

final titlePropertySchema =
templateRef.schema.getPropertySchema(ProposalDocument.titleNodeId)!
as DocumentStringSchema;
final titleRange = titlePropertySchema.strLengthRange;
// TODO(LynxLynxx): when we have separate proposal template for generic questions use it here
// right now user can start creating proposal without selecting category.
// Right now every category have the same requirements for title so we can do a fallback for
// first category from the list.
final templateRef = campaign.categories
.cast<CampaignCategory?>()
.firstWhere(
(e) => e?.selfRef == categoryRef,
orElse: () => campaign.categories.firstOrNull,
)
?.proposalTemplateRef;

final template = templateRef != null
? await _proposalService.getProposalTemplate(ref: templateRef)
: null;
final titleRange = template?.title?.strLengthRange;

final categories = campaign.categories
.map(CampaignCategoryDetailsViewModel.fromModel)
.toList();

final newState = state.copyWith(
isLoading: false,
step: step,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,9 @@ final class ProposalBuilderBloc extends Bloc<ProposalBuilderEvent, ProposalBuild
}
final categoryRef = proposal.categoryRef;
final category = await _campaignService.getCategory(categoryRef);
final campaign = await _campaignService.getActiveCampaign();

final fromActiveCampaign = campaign?.hasCategory(categoryRef.id) ?? false;

return _cacheAndCreateState(
proposalDocument: proposalData.document.document,
Expand All @@ -549,6 +552,7 @@ final class ProposalBuilderBloc extends Bloc<ProposalBuilderEvent, ProposalBuild
templateRef: proposalData.document.metadata.templateRef,
categoryId: categoryRef,
versions: versions,
fromActiveCampaign: fromActiveCampaign,
),
category: category,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ final class ProposalBuilderMetadata extends Equatable {
final SignedDocumentRef? templateRef;
final SignedDocumentRef? categoryId;
final List<DocumentVersion> versions;
final bool fromActiveCampaign;

const ProposalBuilderMetadata({
this.publish = ProposalPublish.localDraft,
Expand All @@ -19,6 +20,7 @@ final class ProposalBuilderMetadata extends Equatable {
this.templateRef,
this.categoryId,
this.versions = const [],
this.fromActiveCampaign = true,
});

factory ProposalBuilderMetadata.newDraft({
Expand All @@ -43,6 +45,7 @@ final class ProposalBuilderMetadata extends Equatable {
templateRef,
categoryId,
versions,
fromActiveCampaign,
];

ProposalBuilderMetadata copyWith({
Expand All @@ -52,6 +55,7 @@ final class ProposalBuilderMetadata extends Equatable {
Optional<SignedDocumentRef>? templateRef,
Optional<SignedDocumentRef>? categoryId,
List<DocumentVersion>? versions,
bool? fromActiveCampaign,
}) {
return ProposalBuilderMetadata(
publish: publish ?? this.publish,
Expand All @@ -60,6 +64,7 @@ final class ProposalBuilderMetadata extends Equatable {
templateRef: templateRef.dataOr(this.templateRef),
categoryId: categoryId.dataOr(this.categoryId),
versions: versions ?? this.versions,
fromActiveCampaign: fromActiveCampaign ?? this.fromActiveCampaign,
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ final class ProposalsCubit extends Cubit<ProposalsState>

void _resetCache() {
final activeAccount = _userService.user.activeAccount;
final filters = ProposalsFilters(author: activeAccount?.catalystId);
final filters = ProposalsFilters.forActiveCampaign(author: activeAccount?.catalystId);
_cache = ProposalsCubitCache(filters: filters);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ final class VotingCubit extends Cubit<VotingState>

void _resetCache() {
final activeAccount = _userService.user.activeAccount;
final filters = ProposalsFilters(author: activeAccount?.catalystId);
final filters = ProposalsFilters.forActiveCampaign(author: activeAccount?.catalystId);

_cache = VotingCubitCache(
filters: filters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,17 +184,28 @@ final class WorkspaceBloc extends Bloc<WorkspaceEvent, WorkspaceState>

Future<List<UsersProposalOverview>> _mapProposalToViewModel(
List<DetailProposal> proposals,
int fundNumber,
) async {
final futures = proposals.map((proposal) async {
_cachedCampaign ??= await _campaignService.getActiveCampaign();
final category = _cachedCampaign?.categories.firstWhere(
(e) => e.selfRef.id == proposal.categoryRef.id,
);

// TODO(damian-molinski): proposal should have ref to campaign
final campaigns = Campaign.all;

final categories = campaigns.expand((element) => element.categories);
final category = categories.firstWhereOrNull((e) => e.selfRef.id == proposal.categoryRef.id);

// TODO(damian-molinski): refactor it
final fundNumber = category != null
? campaigns.firstWhere((campaign) => campaign.hasCategory(category.selfRef.id)).fundNumber
: 0;

final fromActiveCampaign = fundNumber == _cachedCampaign?.fundNumber;

return UsersProposalOverview.fromProposal(
proposal,
fundNumber,
category?.formattedCategoryName ?? '',
fromActiveCampaign: fromActiveCampaign,
);
}).toList();

Expand All @@ -212,7 +223,7 @@ final class WorkspaceBloc extends Bloc<WorkspaceEvent, WorkspaceState>
(proposals) async {
if (isClosed) return;
_logger.info('Stream received ${proposals.length} proposals');
final mappedProposals = await _mapProposalToViewModel(proposals, state.fundNumber);
final mappedProposals = await _mapProposalToViewModel(proposals);
add(LoadProposalsEvent(mappedProposals));
},
onError: (Object error, StackTrace stackTrace) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2026,6 +2026,14 @@
"@notPublishedInfoMarkdown": {
"description": "Info message shown in tooltip info in workspace page"
},
"notActiveCampaign": "Not active campaign",
"@notActiveCampaign": {
"description": "Label for proposals from not active campaign"
},
"notActiveCampaignInfoMarkdown": "**Read only**",
"@notActiveCampaignInfoMarkdown": {
"description": "Info message shown in tooltip info in workspace page"
},
"notPublishedProposals": "Not published proposals",
"@notPublishedProposals": {
"description": "Title for section to show not published proposals"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:equatable/equatable.dart';

final class AppMeta extends Equatable {
final DocumentRef? activeCampaign;

const AppMeta({
this.activeCampaign,
});

@override
List<Object?> get props => [
activeCampaign,
];

AppMeta copyWith({
Optional<DocumentRef>? activeCampaign,
}) {
return AppMeta(
activeCampaign: activeCampaign.dataOr(this.activeCampaign),
);
}
}
Loading