From d44630aa500078a19023db2e22f987151d712e47 Mon Sep 17 00:00:00 2001 From: Zied Dahmani Date: Fri, 20 Sep 2024 18:55:30 +0100 Subject: [PATCH] feat: implement filter proposals logic --- lib/core/di/bloc_module.dart | 10 +- lib/core/di/di_setup.dart | 3 + lib/core/di/usecases_module.dart | 4 + .../api/services/proposal_service.dart | 11 +- lib/core/network/models/dao_data_model.dart | 16 +- .../repository/proposal_repository.dart | 13 +- lib/design/dao_image.dart | 26 + lib/ui/profile/components/dao_widget.dart | 17 +- .../proposals/components/proposal_header.dart | 24 +- .../components/proposal_details_view.dart | 9 +- .../get_proposal_details_use_case.dart | 1 - .../components/filter_proposals_view.dart | 92 ++ .../filter/components/filter_view.dart | 83 -- .../filter/components/hypha_filter_card.dart | 94 +- lib/ui/proposals/filter/filter_page.dart | 11 - .../filter/filter_proposals_page.dart | 30 + .../interactor/dao_proposal_count_entity.dart | 8 + .../interactor/filter_proposals_bloc.dart | 84 ++ .../filter_proposals_bloc.freezed.dart | 867 ++++++++++++++++++ .../interactor/filter_proposals_event.dart | 8 + .../interactor/filter_proposals_state.dart | 10 + .../filter/interactor/filter_status.dart | 12 + .../filter/interactor/page_command.dart | 6 + ...ggregate_dao_proposal_counts_use_case.dart | 27 + ...et_daos_from_proposal_counts_use_case.dart | 8 + .../list/components/proposals_view.dart | 12 +- .../list/interactor/proposals_bloc.dart | 35 +- .../interactor/proposals_bloc.freezed.dart | 94 +- .../list/interactor/proposals_event.dart | 6 +- .../list/usecases/get_proposals_use_case.dart | 4 +- 30 files changed, 1403 insertions(+), 222 deletions(-) create mode 100644 lib/design/dao_image.dart create mode 100644 lib/ui/proposals/filter/components/filter_proposals_view.dart delete mode 100644 lib/ui/proposals/filter/components/filter_view.dart delete mode 100644 lib/ui/proposals/filter/filter_page.dart create mode 100644 lib/ui/proposals/filter/filter_proposals_page.dart create mode 100644 lib/ui/proposals/filter/interactor/dao_proposal_count_entity.dart create mode 100644 lib/ui/proposals/filter/interactor/filter_proposals_bloc.dart create mode 100644 lib/ui/proposals/filter/interactor/filter_proposals_bloc.freezed.dart create mode 100644 lib/ui/proposals/filter/interactor/filter_proposals_event.dart create mode 100644 lib/ui/proposals/filter/interactor/filter_proposals_state.dart create mode 100644 lib/ui/proposals/filter/interactor/filter_status.dart create mode 100644 lib/ui/proposals/filter/interactor/page_command.dart create mode 100644 lib/ui/proposals/filter/usecases/aggregate_dao_proposal_counts_use_case.dart create mode 100644 lib/ui/proposals/filter/usecases/get_daos_from_proposal_counts_use_case.dart diff --git a/lib/core/di/bloc_module.dart b/lib/core/di/bloc_module.dart index 46a37622..f05f7ab5 100644 --- a/lib/core/di/bloc_module.dart +++ b/lib/core/di/bloc_module.dart @@ -134,9 +134,17 @@ void _registerBlocsModule() { _getIt(), )); - _registerFactory(() => ProposalsBloc( + _registerLazySingleton(() => ProposalsBloc( _getIt(), _getIt(), _getIt(), )); + + _registerLazySingleton(() => FilterProposalsBloc( + _getIt(), + _getIt(), + _getIt(), + _getIt(), + _getIt(), + )); } \ No newline at end of file diff --git a/lib/core/di/di_setup.dart b/lib/core/di/di_setup.dart index 5b16b532..f10641d8 100644 --- a/lib/core/di/di_setup.dart +++ b/lib/core/di/di_setup.dart @@ -73,6 +73,9 @@ import 'package:hypha_wallet/ui/profile/usecases/set_bio_use_case.dart'; import 'package:hypha_wallet/ui/profile/usecases/set_image_use_case.dart'; import 'package:hypha_wallet/ui/profile/usecases/set_name_use_case.dart'; import 'package:hypha_wallet/ui/proposals/details/usecases/get_proposal_details_use_case.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_proposals_bloc.dart'; +import 'package:hypha_wallet/ui/proposals/filter/usecases/aggregate_dao_proposal_counts_use_case.dart'; +import 'package:hypha_wallet/ui/proposals/filter/usecases/get_daos_from_proposal_counts_use_case.dart'; import 'package:hypha_wallet/ui/proposals/list/interactor/proposals_bloc.dart'; import 'package:hypha_wallet/ui/proposals/list/usecases/get_proposals_use_case.dart'; import 'package:hypha_wallet/ui/search_user/interactor/search_user_bloc.dart'; diff --git a/lib/core/di/usecases_module.dart b/lib/core/di/usecases_module.dart index 74faf6ea..585debdd 100644 --- a/lib/core/di/usecases_module.dart +++ b/lib/core/di/usecases_module.dart @@ -86,4 +86,8 @@ void _registerUseCasesModule() { _registerFactory(() => GetProposalsUseCase(_getIt(), _getIt())); _registerFactory(() => GetProposalDetailsUseCase(_getIt(), _getIt())); + + _registerFactory(() => AggregateDaoProposalCountsUseCase()); + + _registerFactory(() => GetDaosFromProposalCountsUseCase()); } \ No newline at end of file diff --git a/lib/core/network/api/services/proposal_service.dart b/lib/core/network/api/services/proposal_service.dart index d0a9e3c2..9642b500 100644 --- a/lib/core/network/api/services/proposal_service.dart +++ b/lib/core/network/api/services/proposal_service.dart @@ -8,11 +8,18 @@ class ProposalService { const ProposalService(this._graphQLService); - Future, HyphaError>> getProposals(UserProfileData user, int daoId) async { - final String query = '{"query":"query Proposals(\$docId: String!) { queryDao(filter: { docId: { eq: \$docId } }) { proposal { docId ... on Poll { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Budget { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Queststart { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Questcomplet { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Policy { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Circle { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Payout { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Assignment { details_timeShareX100_i details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Assignbadge { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Role { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Badge { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Suspend { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Edit { details_timeShareX100_i original { ... on Assignbadge { details_title_s } ... on Assignment { details_title_s } } details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Votable { vote { ... on Vote { vote_voter_n vote_vote_s } } } } } }","variables":{"docId":"$daoId"}}'; + Future, HyphaError>> getActiveProposals(UserProfileData user, int daoId) async { + final String query = '{"query":"query ActiveProposals(\$docId: String!) { queryDao(filter: { docId: { eq: \$docId } }) { proposal { docId ... on Poll { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Budget { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Queststart { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Questcomplet { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Policy { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Circle { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Payout { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Assignment { details_timeShareX100_i details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Assignbadge { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Role { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Badge { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Suspend { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Edit { details_timeShareX100_i original { ... on Assignbadge { details_title_s } ... on Assignment { details_title_s } } details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Votable { vote { ... on Vote { vote_voter_n vote_vote_s } } } } } }","variables":{"docId":"$daoId"}}'; return _graphQLService.graphQLQuery(network: user.network, query: query); } + + Future, HyphaError>> getPastProposals(UserProfileData user, int daoId) async { + final String query = '{"query":"query PastProposals(\$docId: String!) { queryDao(filter: { docId: { eq: \$docId } }) { votable { docId ... on Poll { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Budget { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Queststart { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Questcomplet { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Policy { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Circle { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Payout { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Assignment { details_timeShareX100_i details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Assignbadge { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Role { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Badge { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Suspend { details_title_s details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Edit { details_timeShareX100_i original { ... on Assignbadge { details_title_s } ... on Assignment { details_title_s } } details_ballotAlignment_i details_ballotQuorum_i ballot_expiration_t creator } ... on Votable { vote { ... on Vote { vote_voter_n vote_vote_s } } } } } }","variables":{"docId":"$daoId"}}'; + + return _graphQLService.graphQLQuery(network: user.network, query: query); + } + Future, HyphaError>> getProposalDetails( String proposalId, UserProfileData user) async { final String query = diff --git a/lib/core/network/models/dao_data_model.dart b/lib/core/network/models/dao_data_model.dart index 00c619ea..c3fa551a 100644 --- a/lib/core/network/models/dao_data_model.dart +++ b/lib/core/network/models/dao_data_model.dart @@ -1,4 +1,6 @@ -class DaoData { +import 'package:equatable/equatable.dart'; + +class DaoData extends Equatable{ final int docId; final String detailsDaoName; final String settingsDaoTitle; @@ -6,7 +8,7 @@ class DaoData { final String logoType; final String settingsDaoUrl; - DaoData({ + const DaoData({ required this.docId, required this.detailsDaoName, required this.settingsDaoTitle, @@ -15,6 +17,16 @@ class DaoData { required this.settingsDaoUrl, }); + @override + List get props => [ + docId, + detailsDaoName, + settingsDaoTitle, + logoIPFSHash, + logoType, + settingsDaoUrl, + ]; + factory DaoData.fromJson(Map json) { final Map settings = json['settings'][0]; print('got settings result: $settings'); diff --git a/lib/core/network/repository/proposal_repository.dart b/lib/core/network/repository/proposal_repository.dart index c5f5c7a2..54aa2801 100644 --- a/lib/core/network/repository/proposal_repository.dart +++ b/lib/core/network/repository/proposal_repository.dart @@ -9,6 +9,7 @@ import 'package:hypha_wallet/core/network/models/user_profile_data.dart'; import 'package:hypha_wallet/core/network/repository/profile_repository.dart'; import 'package:hypha_wallet/ui/architecture/result/result.dart'; import 'package:hypha_wallet/ui/profile/interactor/profile_data.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_status.dart'; class ProposalRepository { final ProposalService _proposalService; @@ -16,9 +17,9 @@ class ProposalRepository { ProposalRepository(this._proposalService, this._profileService); - Future, HyphaError>> getProposals(UserProfileData user, List daos) async { - final List, HyphaError>>> futures = daos.map((dao) { - return _proposalService.getProposals(user, dao.docId); + Future, HyphaError>> getProposals(UserProfileData user, List daos, FilterStatus filterStatus) async { + final List, HyphaError>>> futures = daos.map((DaoData dao) { + return filterStatus == FilterStatus.active ? _proposalService.getActiveProposals(user, dao.docId) : _proposalService.getPastProposals(user, dao.docId); }).toList(); final List, HyphaError>> futureResults = await Future.wait(futures); @@ -37,7 +38,7 @@ class ProposalRepository { } try { - final List proposals = await _parseProposalsFromResponse(response, daos[i]); + final List proposals = await _parseProposalsFromResponse(response, daos[i], filterStatus); allProposals.addAll(proposals); } catch (e, stackTrace) { LogHelper.e('Error parsing data into proposal model', error: e, stacktrace: stackTrace); @@ -53,11 +54,11 @@ class ProposalRepository { return Result.value(allProposals); } - Future> _parseProposalsFromResponse(Map response, DaoData daoData) async { + Future> _parseProposalsFromResponse(Map response, DaoData daoData, FilterStatus filterStatus) async { final List proposalsData = response['data']['queryDao']; final List> proposalFutures = proposalsData.expand((dao) { - final List proposals = dao['proposal'] as List; + final List proposals = dao[filterStatus == FilterStatus.active ? 'proposal' : 'votable'] as List; return proposals.map((dynamic proposal) async { final Result creator = await _profileService.getProfile(proposal['creator']); proposal['creator'] = null; diff --git a/lib/design/dao_image.dart b/lib/design/dao_image.dart new file mode 100644 index 00000000..16578aab --- /dev/null +++ b/lib/design/dao_image.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; +import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; +import 'package:hypha_wallet/design/ipfs_image.dart'; + +class DaoImage extends StatelessWidget { + final DaoData? dao; + const DaoImage(this.dao, {super.key}); + + @override + Widget build(BuildContext context) { + return Container( + width: 48, + height: 48, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + ), + child: ClipOval( + child: IpfsImage( + ipfsHash: dao?.logoIPFSHash ?? '', + type: dao?.logoType ?? '', + ), + ), + ); + } +} diff --git a/lib/ui/profile/components/dao_widget.dart b/lib/ui/profile/components/dao_widget.dart index 867d3559..75e4c4a4 100644 --- a/lib/ui/profile/components/dao_widget.dart +++ b/lib/ui/profile/components/dao_widget.dart @@ -5,6 +5,7 @@ import 'package:hypha_wallet/design/hypha_card.dart'; import 'package:hypha_wallet/design/hypha_colors.dart'; import 'package:hypha_wallet/design/ipfs_image.dart'; import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; +import 'package:hypha_wallet/design/dao_image.dart'; import 'package:url_launcher/url_launcher.dart'; class DaoWidget extends StatelessWidget { @@ -43,21 +44,7 @@ class DaoWidget extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Container( - width: 48, - height: 48, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: - Colors.white, // Note: white bg is standard on the DAO website where people upload images - ), - child: ClipOval( - child: IpfsImage( - ipfsHash: dao.logoIPFSHash, - type: dao.logoType, - ), - ), - ), + DaoImage(dao), const SizedBox(width: 8), Expanded( child: Column( diff --git a/lib/ui/proposals/components/proposal_header.dart b/lib/ui/proposals/components/proposal_header.dart index e7e1321f..bbb38f53 100644 --- a/lib/ui/proposals/components/proposal_header.dart +++ b/lib/ui/proposals/components/proposal_header.dart @@ -1,35 +1,21 @@ import 'package:flutter/material.dart'; import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; -import 'package:hypha_wallet/design/ipfs_image.dart'; +import 'package:hypha_wallet/design/dao_image.dart'; import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; class ProposalHeader extends StatelessWidget { - final DaoData? _daoData; - const ProposalHeader(this._daoData, {super.key}); + final DaoData? _dao; + const ProposalHeader(this._dao, {super.key}); @override Widget build(BuildContext context) { return Row( children: [ - Container( - width: 48, - height: 48, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: - Colors.white, - ), - child: ClipOval( - child: IpfsImage( - ipfsHash: _daoData?.logoIPFSHash ?? '', - type: _daoData?.logoType ?? '', - ), - ), - ), + DaoImage(_dao), const SizedBox(width: 10), Flexible( child: Text( - _daoData?.settingsDaoTitle ?? '', + _dao?.settingsDaoTitle ?? '', style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis, ), diff --git a/lib/ui/proposals/details/components/proposal_details_view.dart b/lib/ui/proposals/details/components/proposal_details_view.dart index d51ad6cb..660068f2 100644 --- a/lib/ui/proposals/details/components/proposal_details_view.dart +++ b/lib/ui/proposals/details/components/proposal_details_view.dart @@ -9,6 +9,7 @@ import 'package:hypha_wallet/design/avatar_image/hypha_avatar_image.dart'; import 'package:hypha_wallet/design/background/hypha_page_background.dart'; import 'package:hypha_wallet/design/buttons/button_type.dart'; import 'package:hypha_wallet/design/buttons/hypha_app_button.dart'; +import 'package:hypha_wallet/design/dao_image.dart'; import 'package:hypha_wallet/design/dividers/hypha_divider.dart'; import 'package:hypha_wallet/design/hypha_colors.dart'; import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; @@ -158,8 +159,7 @@ class _ProposalDetailsViewState extends State { child: HyphaDivider(), ), /// Rewards Section - // TODO(Zied): implement the logic - if (!(_proposalDetailsModel.utilityAmount == null && _proposalDetailsModel.utilityAmountPerPeriod == null)) ... [ + if (_proposalDetailsModel.utilityAmount != null || _proposalDetailsModel.utilityAmountPerPeriod != null) ... [ ValueListenableBuilder( valueListenable: _isShownNotifier, builder: (BuildContext context, bool? isShown, Widget? child) { @@ -176,10 +176,7 @@ class _ProposalDetailsViewState extends State { padding: const EdgeInsets.only(top: 10), child: Row( children: [ - const HyphaAvatarImage( - imageRadius: 24, - imageFromUrl: 'https://etudestech.com/wp-content/uploads/2023/05/midjourney-scaled.jpeg', - ), + DaoImage(_proposalDetailsModel.dao), const SizedBox(width: 10), Expanded( child: Column( diff --git a/lib/ui/proposals/details/usecases/get_proposal_details_use_case.dart b/lib/ui/proposals/details/usecases/get_proposal_details_use_case.dart index ada6705f..650fe8e8 100644 --- a/lib/ui/proposals/details/usecases/get_proposal_details_use_case.dart +++ b/lib/ui/proposals/details/usecases/get_proposal_details_use_case.dart @@ -1,6 +1,5 @@ import 'package:hypha_wallet/core/error_handler/model/hypha_error.dart'; import 'package:hypha_wallet/core/network/models/proposal_details_model.dart'; -import 'package:hypha_wallet/core/network/models/proposal_model.dart'; import 'package:hypha_wallet/core/network/repository/auth_repository.dart'; import 'package:hypha_wallet/core/network/repository/proposal_repository.dart'; import 'package:hypha_wallet/ui/architecture/result/result.dart'; diff --git a/lib/ui/proposals/filter/components/filter_proposals_view.dart b/lib/ui/proposals/filter/components/filter_proposals_view.dart new file mode 100644 index 00000000..0e868344 --- /dev/null +++ b/lib/ui/proposals/filter/components/filter_proposals_view.dart @@ -0,0 +1,92 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get/get.dart'; +import 'package:hypha_wallet/design/buttons/hypha_app_button.dart'; +import 'package:hypha_wallet/design/hypha_colors.dart'; +import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; +import 'package:hypha_wallet/ui/proposals/filter/components/hypha_filter_card.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_proposals_bloc.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_status.dart'; +import 'package:hypha_wallet/ui/shared/hypha_body_widget.dart'; + +class FilterProposalsView extends StatelessWidget { + FilterProposalsView({super.key}); + + final List _statusFilters = ['Active Proposals', 'Past Proposals']; + + @override + Widget build(BuildContext context) { + final FilterProposalsBloc filterProposalsBloc = context.read(); + return Scaffold( + backgroundColor: context.isDarkMode ? HyphaColors.darkBlack : HyphaColors.offWhite, + appBar: AppBar( + title: const Text('Filter Proposals'), + ), + body: BlocBuilder( + builder: (context, state) { + return HyphaBodyWidget(pageState: state.pageState, success: (context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 26.0, vertical: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Filter by your DAOs', + style: context.hyphaTextTheme.ralMediumBody + .copyWith(color: HyphaColors.midGrey), + ), + const SizedBox( + height: 10, + ), + ...List.generate( + state.daoProposalCounts.length, + (index) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: HyphaFilterCard( + dao: state.daoProposalCounts[index].dao, + subTitle: '${state.daoProposalCounts[index].proposalCount} ${filterProposalsBloc.selectedStatusIndexNotifier.value == 0 ? 'Active' : 'Past'} Proposal${state.daoProposalCounts[index].proposalCount == 1 ? '' : 's'}', + filterProposalsBloc.selectedDaoIndexNotifier, + index + )); + }, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + 'Filter By Status', + style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), + ), + ), + ...List.generate( + 2, + (index) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 10), + child: HyphaFilterCard( + title: _statusFilters[index], + filterProposalsBloc.selectedStatusIndexNotifier, + index, + )); + }, + ), + const Spacer(), + HyphaAppButton( + title: 'SAVE FILTERS', + onPressed: () { + filterProposalsBloc.add(FilterProposalsEvent.saveFilters(filterProposalsBloc.selectedDaoIndexNotifier.value == null ? state.daoProposalCounts: [state.daoProposalCounts[filterProposalsBloc.selectedDaoIndexNotifier.value!]], filterProposalsBloc.selectedStatusIndexNotifier.value == 0 ? FilterStatus.active : FilterStatus.past)); + }, + ), + const SizedBox( + height: 20, + ) + ], + ), + ); + } + ); + }, + ), + ); + } +} diff --git a/lib/ui/proposals/filter/components/filter_view.dart b/lib/ui/proposals/filter/components/filter_view.dart deleted file mode 100644 index 830693b2..00000000 --- a/lib/ui/proposals/filter/components/filter_view.dart +++ /dev/null @@ -1,83 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import 'package:hypha_wallet/design/buttons/hypha_app_button.dart'; -import 'package:hypha_wallet/design/hypha_colors.dart'; -import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; -import 'package:hypha_wallet/ui/proposals/filter/components/hypha_filter_card.dart'; - -class FilterView extends StatelessWidget { - FilterView({super.key}); - - final List statusFilters = ['Active Proposals', 'Past Proposals']; - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: - context.isDarkMode ? HyphaColors.darkBlack : HyphaColors.offWhite, - appBar: AppBar( - title: const Text('Filter Proposals'), - ), - body: Padding( - padding: const EdgeInsets.symmetric(horizontal: 26.0, vertical: 20), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Filter by your DAOs', - style: context.hyphaTextTheme.ralMediumBody - .copyWith(color: HyphaColors.midGrey), - ), - const SizedBox( - height: 10, - ), - ...List.generate( - 3, - (index) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: HyphaFilterCard( - title: 'Hypha DAO', - imageUrl: - 'https://etudestech.com/wp-content/uploads/2023/05/midjourney-scaled.jpeg', - subTitle: '6 Active Proposals', - isSelected: index == 0, - )); - }, - ), - const SizedBox( - height: 10, - ), - Text( - 'Filter By Status', - style: context.hyphaTextTheme.ralMediumBody - .copyWith(color: HyphaColors.midGrey), - ), - const SizedBox( - height: 10, - ), - ...List.generate( - 2, - (index) { - return Container( - margin: const EdgeInsets.symmetric(vertical: 10), - child: HyphaFilterCard( - title: statusFilters[index], - isSelected: index == 0, - )); - }, - ), - const Spacer(), - HyphaAppButton( - title: 'SAVE FILTERS', - onPressed: () {}, - ), - const SizedBox( - height: 20, - ) - ], - ), - ), - ); - } -} diff --git a/lib/ui/proposals/filter/components/hypha_filter_card.dart b/lib/ui/proposals/filter/components/hypha_filter_card.dart index 20a6f248..53d9429d 100644 --- a/lib/ui/proposals/filter/components/hypha_filter_card.dart +++ b/lib/ui/proposals/filter/components/hypha_filter_card.dart @@ -1,60 +1,68 @@ import 'package:flutter/material.dart'; +import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; import 'package:hypha_wallet/design/avatar_image/hypha_avatar_image.dart'; import 'package:hypha_wallet/design/hypha_card.dart'; import 'package:hypha_wallet/design/hypha_colors.dart'; +import 'package:hypha_wallet/design/ipfs_image.dart'; import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; +import 'package:hypha_wallet/design/dao_image.dart'; class HyphaFilterCard extends StatelessWidget { - final String? imageUrl; - final String title; + final DaoData? dao; + final String? title; final String? subTitle; - final bool isSelected; + final dynamic valueNotifier; + final int index; - const HyphaFilterCard( - {required this.title, - this.isSelected = false, - this.subTitle, - this.imageUrl, - super.key}); + const HyphaFilterCard(this.valueNotifier, this.index, {this.dao, this.title, this.subTitle, super.key}); @override Widget build(BuildContext context) { - return HyphaCard( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20), - child: Row( - children: [ - if (imageUrl != null) - HyphaAvatarImage( - imageRadius: 24, - imageFromUrl: imageUrl, + // TODO(Saif): fix the card height (filter by status) + return GestureDetector( + onTap: () { + valueNotifier.value = index; + }, + child: HyphaCard( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20), + child: Row( + children: [ + if (dao != null) + DaoImage(dao), + const SizedBox( + width: 10, ), - const SizedBox( - width: 10, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: context.hyphaTextTheme.smallTitles, - ), - if (subTitle != null) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ Text( - subTitle!, - style: context.hyphaTextTheme.ralMediumBody - .copyWith(color: HyphaColors.midGrey), + title ?? dao!.settingsDaoTitle, + style: context.hyphaTextTheme.smallTitles, ), - ], - ), - const Spacer(), - if (isSelected) - const CircleAvatar( - radius: 12, - backgroundColor: HyphaColors.primaryBlu, - ) - ], - ), - )); + if (subTitle != null) + Text( + subTitle!, + style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), + ), + ], + ), + const Spacer(), + ValueListenableBuilder( + valueListenable: valueNotifier, + builder: (context, selectedIndex, child) { + return Visibility( + visible: selectedIndex == index, + child: const CircleAvatar( + radius: 12, + backgroundColor: HyphaColors.primaryBlu, + ), + ); + }, + ), + ], + ), + )), + ); } } diff --git a/lib/ui/proposals/filter/filter_page.dart b/lib/ui/proposals/filter/filter_page.dart deleted file mode 100644 index 71e67ea4..00000000 --- a/lib/ui/proposals/filter/filter_page.dart +++ /dev/null @@ -1,11 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hypha_wallet/ui/proposals/filter/components/filter_view.dart'; - -class FilterPage extends StatelessWidget { - const FilterPage({super.key}); - - @override - Widget build(BuildContext context) { - return FilterView(); - } -} diff --git a/lib/ui/proposals/filter/filter_proposals_page.dart b/lib/ui/proposals/filter/filter_proposals_page.dart new file mode 100644 index 00000000..ceb55d68 --- /dev/null +++ b/lib/ui/proposals/filter/filter_proposals_page.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get/get.dart'; +import 'package:get_it/get_it.dart'; +import 'package:hypha_wallet/ui/proposals/filter/components/filter_proposals_view.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_proposals_bloc.dart'; + +class FilterProposalsPage extends StatelessWidget { + const FilterProposalsPage({super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider.value( + value: GetIt.I.get(), + child: BlocListener( + listenWhen: (previous, current) => previous.command != current.command, + listener: (context, state) { + state.command?.when( + navigateToProposals: () { + Get.back(); + }, + ); + + context.read().add(const FilterProposalsEvent.clearPageCommand()); + }, + child: FilterProposalsView(), + ), + ); + } +} diff --git a/lib/ui/proposals/filter/interactor/dao_proposal_count_entity.dart b/lib/ui/proposals/filter/interactor/dao_proposal_count_entity.dart new file mode 100644 index 00000000..33b74500 --- /dev/null +++ b/lib/ui/proposals/filter/interactor/dao_proposal_count_entity.dart @@ -0,0 +1,8 @@ +import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; + +class DaoProposalCountEntity { + final DaoData dao; + final int proposalCount; + + DaoProposalCountEntity(this.dao, this.proposalCount); +} diff --git a/lib/ui/proposals/filter/interactor/filter_proposals_bloc.dart b/lib/ui/proposals/filter/interactor/filter_proposals_bloc.dart new file mode 100644 index 00000000..0b5b6715 --- /dev/null +++ b/lib/ui/proposals/filter/interactor/filter_proposals_bloc.dart @@ -0,0 +1,84 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:hypha_wallet/core/error_handler/error_handler_manager.dart'; +import 'package:hypha_wallet/core/error_handler/model/hypha_error.dart'; +import 'package:hypha_wallet/ui/architecture/interactor/page_states.dart'; +import 'package:hypha_wallet/ui/architecture/result/result.dart'; +import 'package:hypha_wallet/ui/profile/interactor/profile_data.dart'; +import 'package:hypha_wallet/ui/profile/usecases/fetch_profile_use_case.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/dao_proposal_count_entity.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_status.dart'; +import 'package:hypha_wallet/ui/proposals/filter/usecases/aggregate_dao_proposal_counts_use_case.dart'; +import 'package:hypha_wallet/ui/proposals/filter/usecases/get_daos_from_proposal_counts_use_case.dart'; +import 'package:hypha_wallet/ui/proposals/list/interactor/proposals_bloc.dart'; + +part 'page_command.dart'; +part 'filter_proposals_bloc.freezed.dart'; +part 'filter_proposals_event.dart'; +part 'filter_proposals_state.dart'; + +class FilterProposalsBloc extends Bloc { + final ProposalsBloc _proposalsBloc; + final FetchProfileUseCase _fetchProfileUseCase; + final AggregateDaoProposalCountsUseCase _aggregateDaoProposalCountsUseCase; + final GetDaosFromProposalCountsUseCase _getDaosFromProposalCountsUseCase; + final ErrorHandlerManager _errorHandlerManager; + late bool _isCalled; + + FilterProposalsBloc( + this._proposalsBloc, + this._fetchProfileUseCase, + this._aggregateDaoProposalCountsUseCase, + this._getDaosFromProposalCountsUseCase, + this._errorHandlerManager, + ) : super(const FilterProposalsState()) { + on<_Initial>(_initial); + on<_ClearPageCommand>((_, emit) => emit(state.copyWith(command: null))); + on<_SaveFilters>(_saveFilters); + + _isCalled = false; + add(const FilterProposalsEvent.initial()); + } + + final ValueNotifier _selectedDaoIndexNotifier = ValueNotifier(null); + final ValueNotifier _selectedStatusIndexNotifier = ValueNotifier(0); + + ValueNotifier get selectedDaoIndexNotifier => _selectedDaoIndexNotifier; + ValueNotifier get selectedStatusIndexNotifier => _selectedStatusIndexNotifier; + + Future _initial(_Initial event, Emitter emit) async { + emit(state.copyWith(pageState: PageState.loading)); + + final Result profileResult = await _fetchProfileUseCase.run(); + + if (profileResult.isValue) { + if (!_isCalled && _proposalsBloc.state.pageState == PageState.success) { + final List daoProposalCounts = _aggregateDaoProposalCountsUseCase.run(_proposalsBloc.state.proposals, profileResult.asValue!.value.daos); + emit(state.copyWith(pageState: PageState.success, daoProposalCounts: daoProposalCounts)); + } + + await emit.forEach(_proposalsBloc.stream, onData: (ProposalsState proposalsState) { + if (proposalsState.pageState == PageState.loading) { + emit(state.copyWith(pageState: PageState.loading)); + } else if (proposalsState.pageState == PageState.success) { + final List daoProposalCounts = _aggregateDaoProposalCountsUseCase.run(proposalsState.proposals, profileResult.asValue!.value.daos); + return state.copyWith(pageState: PageState.success, daoProposalCounts: daoProposalCounts); + } + return state; + }).catchError((error) async { + await _errorHandlerManager.handlerError(error); + emit(state.copyWith(pageState: PageState.failure)); + }); + } else { + await _errorHandlerManager.handlerError(profileResult.asError!.error); + emit(state.copyWith(pageState: PageState.failure)); + } + } + + Future _saveFilters(_SaveFilters event, Emitter emit) async { + _proposalsBloc.add(ProposalsEvent.initial(daos: _getDaosFromProposalCountsUseCase.run(event.daoProposalCounts), filterStatus: event.filterStatus)); + emit(state.copyWith(command: const PageCommand.navigateToProposals())); + } +} diff --git a/lib/ui/proposals/filter/interactor/filter_proposals_bloc.freezed.dart b/lib/ui/proposals/filter/interactor/filter_proposals_bloc.freezed.dart new file mode 100644 index 00000000..5eb3e443 --- /dev/null +++ b/lib/ui/proposals/filter/interactor/filter_proposals_bloc.freezed.dart @@ -0,0 +1,867 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'filter_proposals_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$PageCommand { + @optionalTypeArgs + TResult when({ + required TResult Function() navigateToProposals, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? navigateToProposals, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? navigateToProposals, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_NavigateToProposals value) navigateToProposals, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_NavigateToProposals value)? navigateToProposals, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_NavigateToProposals value)? navigateToProposals, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PageCommandCopyWith<$Res> { + factory $PageCommandCopyWith( + PageCommand value, $Res Function(PageCommand) then) = + _$PageCommandCopyWithImpl<$Res, PageCommand>; +} + +/// @nodoc +class _$PageCommandCopyWithImpl<$Res, $Val extends PageCommand> + implements $PageCommandCopyWith<$Res> { + _$PageCommandCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PageCommand + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$NavigateToProposalsImplCopyWith<$Res> { + factory _$$NavigateToProposalsImplCopyWith(_$NavigateToProposalsImpl value, + $Res Function(_$NavigateToProposalsImpl) then) = + __$$NavigateToProposalsImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$NavigateToProposalsImplCopyWithImpl<$Res> + extends _$PageCommandCopyWithImpl<$Res, _$NavigateToProposalsImpl> + implements _$$NavigateToProposalsImplCopyWith<$Res> { + __$$NavigateToProposalsImplCopyWithImpl(_$NavigateToProposalsImpl _value, + $Res Function(_$NavigateToProposalsImpl) _then) + : super(_value, _then); + + /// Create a copy of PageCommand + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$NavigateToProposalsImpl implements _NavigateToProposals { + const _$NavigateToProposalsImpl(); + + @override + String toString() { + return 'PageCommand.navigateToProposals()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$NavigateToProposalsImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() navigateToProposals, + }) { + return navigateToProposals(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? navigateToProposals, + }) { + return navigateToProposals?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? navigateToProposals, + required TResult orElse(), + }) { + if (navigateToProposals != null) { + return navigateToProposals(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_NavigateToProposals value) navigateToProposals, + }) { + return navigateToProposals(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_NavigateToProposals value)? navigateToProposals, + }) { + return navigateToProposals?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_NavigateToProposals value)? navigateToProposals, + required TResult orElse(), + }) { + if (navigateToProposals != null) { + return navigateToProposals(this); + } + return orElse(); + } +} + +abstract class _NavigateToProposals implements PageCommand { + const factory _NavigateToProposals() = _$NavigateToProposalsImpl; +} + +/// @nodoc +mixin _$FilterProposalsEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function(List daoProposalCounts, + FilterStatus filterStatus) + saveFilters, + required TResult Function() clearPageCommand, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function(List daoProposalCounts, + FilterStatus filterStatus)? + saveFilters, + TResult? Function()? clearPageCommand, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function(List daoProposalCounts, + FilterStatus filterStatus)? + saveFilters, + TResult Function()? clearPageCommand, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_SaveFilters value) saveFilters, + required TResult Function(_ClearPageCommand value) clearPageCommand, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_SaveFilters value)? saveFilters, + TResult? Function(_ClearPageCommand value)? clearPageCommand, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_SaveFilters value)? saveFilters, + TResult Function(_ClearPageCommand value)? clearPageCommand, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FilterProposalsEventCopyWith<$Res> { + factory $FilterProposalsEventCopyWith(FilterProposalsEvent value, + $Res Function(FilterProposalsEvent) then) = + _$FilterProposalsEventCopyWithImpl<$Res, FilterProposalsEvent>; +} + +/// @nodoc +class _$FilterProposalsEventCopyWithImpl<$Res, + $Val extends FilterProposalsEvent> + implements $FilterProposalsEventCopyWith<$Res> { + _$FilterProposalsEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of FilterProposalsEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$InitialImplCopyWith<$Res> { + factory _$$InitialImplCopyWith( + _$InitialImpl value, $Res Function(_$InitialImpl) then) = + __$$InitialImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$InitialImplCopyWithImpl<$Res> + extends _$FilterProposalsEventCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of FilterProposalsEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'FilterProposalsEvent.initial()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$InitialImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function(List daoProposalCounts, + FilterStatus filterStatus) + saveFilters, + required TResult Function() clearPageCommand, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function(List daoProposalCounts, + FilterStatus filterStatus)? + saveFilters, + TResult? Function()? clearPageCommand, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function(List daoProposalCounts, + FilterStatus filterStatus)? + saveFilters, + TResult Function()? clearPageCommand, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_SaveFilters value) saveFilters, + required TResult Function(_ClearPageCommand value) clearPageCommand, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_SaveFilters value)? saveFilters, + TResult? Function(_ClearPageCommand value)? clearPageCommand, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_SaveFilters value)? saveFilters, + TResult Function(_ClearPageCommand value)? clearPageCommand, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements FilterProposalsEvent { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$SaveFiltersImplCopyWith<$Res> { + factory _$$SaveFiltersImplCopyWith( + _$SaveFiltersImpl value, $Res Function(_$SaveFiltersImpl) then) = + __$$SaveFiltersImplCopyWithImpl<$Res>; + @useResult + $Res call( + {List daoProposalCounts, + FilterStatus filterStatus}); +} + +/// @nodoc +class __$$SaveFiltersImplCopyWithImpl<$Res> + extends _$FilterProposalsEventCopyWithImpl<$Res, _$SaveFiltersImpl> + implements _$$SaveFiltersImplCopyWith<$Res> { + __$$SaveFiltersImplCopyWithImpl( + _$SaveFiltersImpl _value, $Res Function(_$SaveFiltersImpl) _then) + : super(_value, _then); + + /// Create a copy of FilterProposalsEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? daoProposalCounts = null, + Object? filterStatus = null, + }) { + return _then(_$SaveFiltersImpl( + null == daoProposalCounts + ? _value._daoProposalCounts + : daoProposalCounts // ignore: cast_nullable_to_non_nullable + as List, + null == filterStatus + ? _value.filterStatus + : filterStatus // ignore: cast_nullable_to_non_nullable + as FilterStatus, + )); + } +} + +/// @nodoc + +class _$SaveFiltersImpl implements _SaveFilters { + const _$SaveFiltersImpl( + final List daoProposalCounts, this.filterStatus) + : _daoProposalCounts = daoProposalCounts; + + final List _daoProposalCounts; + @override + List get daoProposalCounts { + if (_daoProposalCounts is EqualUnmodifiableListView) + return _daoProposalCounts; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_daoProposalCounts); + } + + @override + final FilterStatus filterStatus; + + @override + String toString() { + return 'FilterProposalsEvent.saveFilters(daoProposalCounts: $daoProposalCounts, filterStatus: $filterStatus)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SaveFiltersImpl && + const DeepCollectionEquality() + .equals(other._daoProposalCounts, _daoProposalCounts) && + (identical(other.filterStatus, filterStatus) || + other.filterStatus == filterStatus)); + } + + @override + int get hashCode => Object.hash(runtimeType, + const DeepCollectionEquality().hash(_daoProposalCounts), filterStatus); + + /// Create a copy of FilterProposalsEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SaveFiltersImplCopyWith<_$SaveFiltersImpl> get copyWith => + __$$SaveFiltersImplCopyWithImpl<_$SaveFiltersImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function(List daoProposalCounts, + FilterStatus filterStatus) + saveFilters, + required TResult Function() clearPageCommand, + }) { + return saveFilters(daoProposalCounts, filterStatus); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function(List daoProposalCounts, + FilterStatus filterStatus)? + saveFilters, + TResult? Function()? clearPageCommand, + }) { + return saveFilters?.call(daoProposalCounts, filterStatus); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function(List daoProposalCounts, + FilterStatus filterStatus)? + saveFilters, + TResult Function()? clearPageCommand, + required TResult orElse(), + }) { + if (saveFilters != null) { + return saveFilters(daoProposalCounts, filterStatus); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_SaveFilters value) saveFilters, + required TResult Function(_ClearPageCommand value) clearPageCommand, + }) { + return saveFilters(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_SaveFilters value)? saveFilters, + TResult? Function(_ClearPageCommand value)? clearPageCommand, + }) { + return saveFilters?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_SaveFilters value)? saveFilters, + TResult Function(_ClearPageCommand value)? clearPageCommand, + required TResult orElse(), + }) { + if (saveFilters != null) { + return saveFilters(this); + } + return orElse(); + } +} + +abstract class _SaveFilters implements FilterProposalsEvent { + const factory _SaveFilters( + final List daoProposalCounts, + final FilterStatus filterStatus) = _$SaveFiltersImpl; + + List get daoProposalCounts; + FilterStatus get filterStatus; + + /// Create a copy of FilterProposalsEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SaveFiltersImplCopyWith<_$SaveFiltersImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ClearPageCommandImplCopyWith<$Res> { + factory _$$ClearPageCommandImplCopyWith(_$ClearPageCommandImpl value, + $Res Function(_$ClearPageCommandImpl) then) = + __$$ClearPageCommandImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ClearPageCommandImplCopyWithImpl<$Res> + extends _$FilterProposalsEventCopyWithImpl<$Res, _$ClearPageCommandImpl> + implements _$$ClearPageCommandImplCopyWith<$Res> { + __$$ClearPageCommandImplCopyWithImpl(_$ClearPageCommandImpl _value, + $Res Function(_$ClearPageCommandImpl) _then) + : super(_value, _then); + + /// Create a copy of FilterProposalsEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$ClearPageCommandImpl implements _ClearPageCommand { + const _$ClearPageCommandImpl(); + + @override + String toString() { + return 'FilterProposalsEvent.clearPageCommand()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$ClearPageCommandImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function(List daoProposalCounts, + FilterStatus filterStatus) + saveFilters, + required TResult Function() clearPageCommand, + }) { + return clearPageCommand(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function(List daoProposalCounts, + FilterStatus filterStatus)? + saveFilters, + TResult? Function()? clearPageCommand, + }) { + return clearPageCommand?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function(List daoProposalCounts, + FilterStatus filterStatus)? + saveFilters, + TResult Function()? clearPageCommand, + required TResult orElse(), + }) { + if (clearPageCommand != null) { + return clearPageCommand(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_SaveFilters value) saveFilters, + required TResult Function(_ClearPageCommand value) clearPageCommand, + }) { + return clearPageCommand(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_SaveFilters value)? saveFilters, + TResult? Function(_ClearPageCommand value)? clearPageCommand, + }) { + return clearPageCommand?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_SaveFilters value)? saveFilters, + TResult Function(_ClearPageCommand value)? clearPageCommand, + required TResult orElse(), + }) { + if (clearPageCommand != null) { + return clearPageCommand(this); + } + return orElse(); + } +} + +abstract class _ClearPageCommand implements FilterProposalsEvent { + const factory _ClearPageCommand() = _$ClearPageCommandImpl; +} + +/// @nodoc +mixin _$FilterProposalsState { + PageState get pageState => throw _privateConstructorUsedError; + List get daoProposalCounts => + throw _privateConstructorUsedError; + PageCommand? get command => throw _privateConstructorUsedError; + + /// Create a copy of FilterProposalsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $FilterProposalsStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $FilterProposalsStateCopyWith<$Res> { + factory $FilterProposalsStateCopyWith(FilterProposalsState value, + $Res Function(FilterProposalsState) then) = + _$FilterProposalsStateCopyWithImpl<$Res, FilterProposalsState>; + @useResult + $Res call( + {PageState pageState, + List daoProposalCounts, + PageCommand? command}); + + $PageCommandCopyWith<$Res>? get command; +} + +/// @nodoc +class _$FilterProposalsStateCopyWithImpl<$Res, + $Val extends FilterProposalsState> + implements $FilterProposalsStateCopyWith<$Res> { + _$FilterProposalsStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of FilterProposalsState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? pageState = null, + Object? daoProposalCounts = null, + Object? command = freezed, + }) { + return _then(_value.copyWith( + pageState: null == pageState + ? _value.pageState + : pageState // ignore: cast_nullable_to_non_nullable + as PageState, + daoProposalCounts: null == daoProposalCounts + ? _value.daoProposalCounts + : daoProposalCounts // ignore: cast_nullable_to_non_nullable + as List, + command: freezed == command + ? _value.command + : command // ignore: cast_nullable_to_non_nullable + as PageCommand?, + ) as $Val); + } + + /// Create a copy of FilterProposalsState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $PageCommandCopyWith<$Res>? get command { + if (_value.command == null) { + return null; + } + + return $PageCommandCopyWith<$Res>(_value.command!, (value) { + return _then(_value.copyWith(command: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$FilterProposalsStateImplCopyWith<$Res> + implements $FilterProposalsStateCopyWith<$Res> { + factory _$$FilterProposalsStateImplCopyWith(_$FilterProposalsStateImpl value, + $Res Function(_$FilterProposalsStateImpl) then) = + __$$FilterProposalsStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {PageState pageState, + List daoProposalCounts, + PageCommand? command}); + + @override + $PageCommandCopyWith<$Res>? get command; +} + +/// @nodoc +class __$$FilterProposalsStateImplCopyWithImpl<$Res> + extends _$FilterProposalsStateCopyWithImpl<$Res, _$FilterProposalsStateImpl> + implements _$$FilterProposalsStateImplCopyWith<$Res> { + __$$FilterProposalsStateImplCopyWithImpl(_$FilterProposalsStateImpl _value, + $Res Function(_$FilterProposalsStateImpl) _then) + : super(_value, _then); + + /// Create a copy of FilterProposalsState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? pageState = null, + Object? daoProposalCounts = null, + Object? command = freezed, + }) { + return _then(_$FilterProposalsStateImpl( + pageState: null == pageState + ? _value.pageState + : pageState // ignore: cast_nullable_to_non_nullable + as PageState, + daoProposalCounts: null == daoProposalCounts + ? _value._daoProposalCounts + : daoProposalCounts // ignore: cast_nullable_to_non_nullable + as List, + command: freezed == command + ? _value.command + : command // ignore: cast_nullable_to_non_nullable + as PageCommand?, + )); + } +} + +/// @nodoc + +class _$FilterProposalsStateImpl implements _FilterProposalsState { + const _$FilterProposalsStateImpl( + {this.pageState = PageState.initial, + final List daoProposalCounts = const [], + this.command}) + : _daoProposalCounts = daoProposalCounts; + + @override + @JsonKey() + final PageState pageState; + final List _daoProposalCounts; + @override + @JsonKey() + List get daoProposalCounts { + if (_daoProposalCounts is EqualUnmodifiableListView) + return _daoProposalCounts; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_daoProposalCounts); + } + + @override + final PageCommand? command; + + @override + String toString() { + return 'FilterProposalsState(pageState: $pageState, daoProposalCounts: $daoProposalCounts, command: $command)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FilterProposalsStateImpl && + (identical(other.pageState, pageState) || + other.pageState == pageState) && + const DeepCollectionEquality() + .equals(other._daoProposalCounts, _daoProposalCounts) && + (identical(other.command, command) || other.command == command)); + } + + @override + int get hashCode => Object.hash(runtimeType, pageState, + const DeepCollectionEquality().hash(_daoProposalCounts), command); + + /// Create a copy of FilterProposalsState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$FilterProposalsStateImplCopyWith<_$FilterProposalsStateImpl> + get copyWith => + __$$FilterProposalsStateImplCopyWithImpl<_$FilterProposalsStateImpl>( + this, _$identity); +} + +abstract class _FilterProposalsState implements FilterProposalsState { + const factory _FilterProposalsState( + {final PageState pageState, + final List daoProposalCounts, + final PageCommand? command}) = _$FilterProposalsStateImpl; + + @override + PageState get pageState; + @override + List get daoProposalCounts; + @override + PageCommand? get command; + + /// Create a copy of FilterProposalsState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$FilterProposalsStateImplCopyWith<_$FilterProposalsStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/ui/proposals/filter/interactor/filter_proposals_event.dart b/lib/ui/proposals/filter/interactor/filter_proposals_event.dart new file mode 100644 index 00000000..404923bd --- /dev/null +++ b/lib/ui/proposals/filter/interactor/filter_proposals_event.dart @@ -0,0 +1,8 @@ +part of 'filter_proposals_bloc.dart'; + +@freezed +class FilterProposalsEvent with _$FilterProposalsEvent { + const factory FilterProposalsEvent.initial() = _Initial; + const factory FilterProposalsEvent.saveFilters(List daoProposalCounts, FilterStatus filterStatus) = _SaveFilters; + const factory FilterProposalsEvent.clearPageCommand() = _ClearPageCommand; +} diff --git a/lib/ui/proposals/filter/interactor/filter_proposals_state.dart b/lib/ui/proposals/filter/interactor/filter_proposals_state.dart new file mode 100644 index 00000000..6fa64a00 --- /dev/null +++ b/lib/ui/proposals/filter/interactor/filter_proposals_state.dart @@ -0,0 +1,10 @@ +part of 'filter_proposals_bloc.dart'; + +@freezed +class FilterProposalsState with _$FilterProposalsState { + const factory FilterProposalsState({ + @Default(PageState.initial) PageState pageState, + @Default([]) List daoProposalCounts, + PageCommand? command, + }) = _FilterProposalsState; +} diff --git a/lib/ui/proposals/filter/interactor/filter_status.dart b/lib/ui/proposals/filter/interactor/filter_status.dart new file mode 100644 index 00000000..b615ccdd --- /dev/null +++ b/lib/ui/proposals/filter/interactor/filter_status.dart @@ -0,0 +1,12 @@ +enum FilterStatus { active, past } + +extension FilterStatusExtension on FilterStatus { + String get string { + switch (this) { + case FilterStatus.active: + return 'Active'; + case FilterStatus.past: + return 'Past'; + } + } +} diff --git a/lib/ui/proposals/filter/interactor/page_command.dart b/lib/ui/proposals/filter/interactor/page_command.dart new file mode 100644 index 00000000..b7b28023 --- /dev/null +++ b/lib/ui/proposals/filter/interactor/page_command.dart @@ -0,0 +1,6 @@ +part of 'filter_proposals_bloc.dart'; + +@freezed +class PageCommand with _$PageCommand { + const factory PageCommand.navigateToProposals() = _NavigateToProposals; +} diff --git a/lib/ui/proposals/filter/usecases/aggregate_dao_proposal_counts_use_case.dart b/lib/ui/proposals/filter/usecases/aggregate_dao_proposal_counts_use_case.dart new file mode 100644 index 00000000..61ad439b --- /dev/null +++ b/lib/ui/proposals/filter/usecases/aggregate_dao_proposal_counts_use_case.dart @@ -0,0 +1,27 @@ +import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; +import 'package:hypha_wallet/core/network/models/proposal_model.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/dao_proposal_count_entity.dart'; + +class AggregateDaoProposalCountsUseCase { + List run(List proposals, List daos) { + final Map daoProposalCounts = {}; + + for (final ProposalModel proposal in proposals) { + final DaoData? dao = proposal.dao; + + if (dao != null) { + daoProposalCounts[dao] = (daoProposalCounts[dao] ?? 0) + 1; + } + } + + for (final DaoData dao in daos) { + if (!daoProposalCounts.containsKey(dao)) { + daoProposalCounts[dao] = 0; + } + } + + return daoProposalCounts.entries + .map((entry) => DaoProposalCountEntity(entry.key, entry.value)) + .toList(); + } +} diff --git a/lib/ui/proposals/filter/usecases/get_daos_from_proposal_counts_use_case.dart b/lib/ui/proposals/filter/usecases/get_daos_from_proposal_counts_use_case.dart new file mode 100644 index 00000000..ffd5fa31 --- /dev/null +++ b/lib/ui/proposals/filter/usecases/get_daos_from_proposal_counts_use_case.dart @@ -0,0 +1,8 @@ +import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/dao_proposal_count_entity.dart'; + +class GetDaosFromProposalCountsUseCase { + List run(List daoProposalCounts) { + return daoProposalCounts.map((entity) => entity.dao).toList(); + } +} diff --git a/lib/ui/proposals/list/components/proposals_view.dart b/lib/ui/proposals/list/components/proposals_view.dart index 7fa2ba90..215fbc2b 100644 --- a/lib/ui/proposals/list/components/proposals_view.dart +++ b/lib/ui/proposals/list/components/proposals_view.dart @@ -7,7 +7,9 @@ import 'package:hypha_wallet/design/hypha_colors.dart'; import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart'; import 'package:hypha_wallet/ui/blocs/authentication/authentication_bloc.dart'; import 'package:hypha_wallet/ui/profile/profile_page.dart'; -import 'package:hypha_wallet/ui/proposals/filter/filter_page.dart'; +import 'package:hypha_wallet/ui/proposals/filter/filter_proposals_page.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_proposals_bloc.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_status.dart'; import 'package:hypha_wallet/ui/proposals/list/components/hypha_proposals_action_card.dart'; import 'package:hypha_wallet/ui/proposals/list/interactor/proposals_bloc.dart'; import 'package:hypha_wallet/ui/shared/hypha_body_widget.dart'; @@ -64,7 +66,7 @@ class ProposalsView extends StatelessWidget { onRefresh: () async { context .read() - .add(const ProposalsEvent.initial()); + .add(const ProposalsEvent.initial(refresh: true)); }, child: Container( margin: const EdgeInsets.only(top: 20), @@ -91,7 +93,7 @@ class ProposalsView extends StatelessWidget { height: 22, ), Text( - '${state.proposals.length} Active Proposals', + '${state.proposals.length} ${context.read().filterStatus.string} Proposal${state.proposals.length == 1 ? '' : 's'}', style: context.hyphaTextTheme.ralMediumBody .copyWith(color: HyphaColors.midGrey), ), @@ -117,8 +119,8 @@ class ProposalsView extends StatelessWidget { floatingActionButton: IconButton( onPressed: () { GetX.Get.to( - const FilterPage(), - transition: GetX.Transition.leftToRight, + () => const FilterProposalsPage(), + transition: GetX.Transition.leftToRight ); }, icon: const CircleAvatar( diff --git a/lib/ui/proposals/list/interactor/proposals_bloc.dart b/lib/ui/proposals/list/interactor/proposals_bloc.dart index 2ec2dcbc..ba9d7d76 100644 --- a/lib/ui/proposals/list/interactor/proposals_bloc.dart +++ b/lib/ui/proposals/list/interactor/proposals_bloc.dart @@ -2,11 +2,13 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hypha_wallet/core/error_handler/error_handler_manager.dart'; import 'package:hypha_wallet/core/error_handler/model/hypha_error.dart'; +import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; import 'package:hypha_wallet/core/network/models/proposal_model.dart'; import 'package:hypha_wallet/ui/architecture/interactor/page_states.dart'; import 'package:hypha_wallet/ui/architecture/result/result.dart'; import 'package:hypha_wallet/ui/profile/interactor/profile_data.dart'; import 'package:hypha_wallet/ui/profile/usecases/fetch_profile_use_case.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_status.dart'; import 'package:hypha_wallet/ui/proposals/list/usecases/get_proposals_use_case.dart'; part 'proposals_bloc.freezed.dart'; @@ -22,24 +24,37 @@ class ProposalsBloc extends Bloc { on<_Initial>(_initial); } + FilterStatus filterStatus = FilterStatus.active; + Future _initial(_Initial event, Emitter emit) async { - if(!event.refresh) { + if (!event.refresh) { emit(state.copyWith(pageState: PageState.loading)); + filterStatus = event.filterStatus; + } + + if (event.daos != null) { + await _fetchAndEmitProposals(emit, event.daos!, filterStatus); + return; } final Result profileResult = await _fetchProfileUseCase.run(); if (profileResult.isValue && profileResult.asValue!.value.daos.isNotEmpty) { - final Result, HyphaError> proposalsResult = await _getProposalsUseCase.run(profileResult.asValue!.value.daos); - - if (proposalsResult.isValue) { - emit(state.copyWith(pageState: PageState.success, proposals: proposalsResult.asValue!.value)); - } else { - await _errorHandlerManager.handlerError(proposalsResult.asError!.error); - emit(state.copyWith(pageState: PageState.failure)); - } + await _fetchAndEmitProposals(emit, profileResult.asValue!.value.daos, filterStatus); + } else { + final HyphaError error = profileResult.isError ? profileResult.asError!.error : HyphaError.api('Failed to retrieve DAOs'); + await _errorHandlerManager.handlerError(error); + emit(state.copyWith(pageState: PageState.failure)); + } + } + + Future _fetchAndEmitProposals(Emitter emit, List daos, FilterStatus filterStatus) async { + final Result, HyphaError> proposalsResult = await _getProposalsUseCase.run(daos, filterStatus); + + if (proposalsResult.isValue) { + emit(state.copyWith(pageState: PageState.success, proposals: proposalsResult.asValue!.value)); } else { - await _errorHandlerManager.handlerError(profileResult.isError ? profileResult.asError!.error : HyphaError.api('Failed to retrieve DAOs')); + await _errorHandlerManager.handlerError(proposalsResult.asError!.error); emit(state.copyWith(pageState: PageState.failure)); } } diff --git a/lib/ui/proposals/list/interactor/proposals_bloc.freezed.dart b/lib/ui/proposals/list/interactor/proposals_bloc.freezed.dart index 9ac0b8cf..d58bf8b9 100644 --- a/lib/ui/proposals/list/interactor/proposals_bloc.freezed.dart +++ b/lib/ui/proposals/list/interactor/proposals_bloc.freezed.dart @@ -17,19 +17,27 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$ProposalsEvent { bool get refresh => throw _privateConstructorUsedError; + List? get daos => throw _privateConstructorUsedError; + FilterStatus get filterStatus => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(bool refresh) initial, + required TResult Function( + bool refresh, List? daos, FilterStatus filterStatus) + initial, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(bool refresh)? initial, + TResult? Function( + bool refresh, List? daos, FilterStatus filterStatus)? + initial, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(bool refresh)? initial, + TResult Function( + bool refresh, List? daos, FilterStatus filterStatus)? + initial, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -63,7 +71,7 @@ abstract class $ProposalsEventCopyWith<$Res> { ProposalsEvent value, $Res Function(ProposalsEvent) then) = _$ProposalsEventCopyWithImpl<$Res, ProposalsEvent>; @useResult - $Res call({bool refresh}); + $Res call({bool refresh, List? daos, FilterStatus filterStatus}); } /// @nodoc @@ -82,12 +90,22 @@ class _$ProposalsEventCopyWithImpl<$Res, $Val extends ProposalsEvent> @override $Res call({ Object? refresh = null, + Object? daos = freezed, + Object? filterStatus = null, }) { return _then(_value.copyWith( refresh: null == refresh ? _value.refresh : refresh // ignore: cast_nullable_to_non_nullable as bool, + daos: freezed == daos + ? _value.daos + : daos // ignore: cast_nullable_to_non_nullable + as List?, + filterStatus: null == filterStatus + ? _value.filterStatus + : filterStatus // ignore: cast_nullable_to_non_nullable + as FilterStatus, ) as $Val); } } @@ -100,7 +118,7 @@ abstract class _$$InitialImplCopyWith<$Res> __$$InitialImplCopyWithImpl<$Res>; @override @useResult - $Res call({bool refresh}); + $Res call({bool refresh, List? daos, FilterStatus filterStatus}); } /// @nodoc @@ -117,12 +135,22 @@ class __$$InitialImplCopyWithImpl<$Res> @override $Res call({ Object? refresh = null, + Object? daos = freezed, + Object? filterStatus = null, }) { return _then(_$InitialImpl( refresh: null == refresh ? _value.refresh : refresh // ignore: cast_nullable_to_non_nullable as bool, + daos: freezed == daos + ? _value._daos + : daos // ignore: cast_nullable_to_non_nullable + as List?, + filterStatus: null == filterStatus + ? _value.filterStatus + : filterStatus // ignore: cast_nullable_to_non_nullable + as FilterStatus, )); } } @@ -130,15 +158,32 @@ class __$$InitialImplCopyWithImpl<$Res> /// @nodoc class _$InitialImpl implements _Initial { - const _$InitialImpl({this.refresh = false}); + const _$InitialImpl( + {this.refresh = false, + final List? daos, + this.filterStatus = FilterStatus.active}) + : _daos = daos; @override @JsonKey() final bool refresh; + final List? _daos; + @override + List? get daos { + final value = _daos; + if (value == null) return null; + if (_daos is EqualUnmodifiableListView) return _daos; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + @JsonKey() + final FilterStatus filterStatus; @override String toString() { - return 'ProposalsEvent.initial(refresh: $refresh)'; + return 'ProposalsEvent.initial(refresh: $refresh, daos: $daos, filterStatus: $filterStatus)'; } @override @@ -146,11 +191,15 @@ class _$InitialImpl implements _Initial { return identical(this, other) || (other.runtimeType == runtimeType && other is _$InitialImpl && - (identical(other.refresh, refresh) || other.refresh == refresh)); + (identical(other.refresh, refresh) || other.refresh == refresh) && + const DeepCollectionEquality().equals(other._daos, _daos) && + (identical(other.filterStatus, filterStatus) || + other.filterStatus == filterStatus)); } @override - int get hashCode => Object.hash(runtimeType, refresh); + int get hashCode => Object.hash(runtimeType, refresh, + const DeepCollectionEquality().hash(_daos), filterStatus); /// Create a copy of ProposalsEvent /// with the given fields replaced by the non-null parameter values. @@ -163,27 +212,33 @@ class _$InitialImpl implements _Initial { @override @optionalTypeArgs TResult when({ - required TResult Function(bool refresh) initial, + required TResult Function( + bool refresh, List? daos, FilterStatus filterStatus) + initial, }) { - return initial(refresh); + return initial(refresh, daos, filterStatus); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(bool refresh)? initial, + TResult? Function( + bool refresh, List? daos, FilterStatus filterStatus)? + initial, }) { - return initial?.call(refresh); + return initial?.call(refresh, daos, filterStatus); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(bool refresh)? initial, + TResult Function( + bool refresh, List? daos, FilterStatus filterStatus)? + initial, required TResult orElse(), }) { if (initial != null) { - return initial(refresh); + return initial(refresh, daos, filterStatus); } return orElse(); } @@ -218,10 +273,17 @@ class _$InitialImpl implements _Initial { } abstract class _Initial implements ProposalsEvent { - const factory _Initial({final bool refresh}) = _$InitialImpl; + const factory _Initial( + {final bool refresh, + final List? daos, + final FilterStatus filterStatus}) = _$InitialImpl; @override bool get refresh; + @override + List? get daos; + @override + FilterStatus get filterStatus; /// Create a copy of ProposalsEvent /// with the given fields replaced by the non-null parameter values. diff --git a/lib/ui/proposals/list/interactor/proposals_event.dart b/lib/ui/proposals/list/interactor/proposals_event.dart index b5ce4886..fa6ca754 100644 --- a/lib/ui/proposals/list/interactor/proposals_event.dart +++ b/lib/ui/proposals/list/interactor/proposals_event.dart @@ -2,5 +2,9 @@ part of 'proposals_bloc.dart'; @freezed class ProposalsEvent with _$ProposalsEvent { - const factory ProposalsEvent.initial({@Default(false) bool refresh}) = _Initial; + const factory ProposalsEvent.initial({ + @Default(false) bool refresh, + List? daos, + @Default(FilterStatus.active) FilterStatus filterStatus, + }) = _Initial; } diff --git a/lib/ui/proposals/list/usecases/get_proposals_use_case.dart b/lib/ui/proposals/list/usecases/get_proposals_use_case.dart index a0c8d5cb..588c6963 100644 --- a/lib/ui/proposals/list/usecases/get_proposals_use_case.dart +++ b/lib/ui/proposals/list/usecases/get_proposals_use_case.dart @@ -4,12 +4,14 @@ import 'package:hypha_wallet/core/network/models/proposal_model.dart'; import 'package:hypha_wallet/core/network/repository/auth_repository.dart'; import 'package:hypha_wallet/core/network/repository/proposal_repository.dart'; import 'package:hypha_wallet/ui/architecture/result/result.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_status.dart'; +// TODO(Zied): add 'extends' (check) class GetProposalsUseCase { final AuthRepository _authRepository; final ProposalRepository _proposalRepository; GetProposalsUseCase(this._authRepository, this._proposalRepository); - Future, HyphaError>> run(List daos) async => _proposalRepository.getProposals(_authRepository.authDataOrCrash.userProfileData, daos); + Future, HyphaError>> run(List daos, FilterStatus filterStatus) async => _proposalRepository.getProposals(_authRepository.authDataOrCrash.userProfileData, daos, filterStatus); }