From 7c0837d617d0fbd9f692a6ded7a9afe049485dd4 Mon Sep 17 00:00:00 2001 From: Zied Dahmani Date: Tue, 24 Sep 2024 14:56:31 +0100 Subject: [PATCH 01/10] refactor: remove s --- lib/core/extension/base_proposal_model_extension.dart | 1 + lib/ui/proposals/details/components/proposal_details_view.dart | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/core/extension/base_proposal_model_extension.dart b/lib/core/extension/base_proposal_model_extension.dart index fe8d5b64..de7248d6 100644 --- a/lib/core/extension/base_proposal_model_extension.dart +++ b/lib/core/extension/base_proposal_model_extension.dart @@ -2,6 +2,7 @@ import 'package:hypha_wallet/core/network/models/base_proposal_model.dart'; extension BaseProposalModelExtension on BaseProposalModel { String formatExpiration() { + // TODO(Zied-Saif): use another word instead of 'Expired' if (expiration == null) return 'Expired'; if (isExpired()) { diff --git a/lib/ui/proposals/details/components/proposal_details_view.dart b/lib/ui/proposals/details/components/proposal_details_view.dart index 660068f2..0fe96f80 100644 --- a/lib/ui/proposals/details/components/proposal_details_view.dart +++ b/lib/ui/proposals/details/components/proposal_details_view.dart @@ -141,7 +141,7 @@ class _ProposalDetailsViewState extends State { Padding( padding: const EdgeInsets.symmetric(vertical: 10), child: Text( - '${_proposalDetailsModel.cycleCount} Cycles', + '${_proposalDetailsModel.cycleCount} Cycle${_proposalDetailsModel.cycleCount == 1 ? '' : 's'}', style: context.hyphaTextTheme.reducedTitles, ), ), From d74922d12d5b08431e1b58984f03a666f982b644 Mon Sep 17 00:00:00 2001 From: Zied Dahmani Date: Tue, 24 Sep 2024 15:33:59 +0100 Subject: [PATCH 02/10] refactor: add extends InputUseCase --- lib/core/network/repository/proposal_repository.dart | 9 +++++---- .../list/interactor/get_proposals_use_case_input.dart | 9 +++++++++ lib/ui/proposals/list/interactor/proposals_bloc.dart | 3 ++- .../list/usecases/get_proposals_use_case.dart | 10 +++++----- 4 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 lib/ui/proposals/list/interactor/get_proposals_use_case_input.dart diff --git a/lib/core/network/repository/proposal_repository.dart b/lib/core/network/repository/proposal_repository.dart index 54aa2801..4cf17612 100644 --- a/lib/core/network/repository/proposal_repository.dart +++ b/lib/core/network/repository/proposal_repository.dart @@ -10,6 +10,7 @@ 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'; +import 'package:hypha_wallet/ui/proposals/list/interactor/get_proposals_use_case_input.dart'; class ProposalRepository { final ProposalService _proposalService; @@ -17,9 +18,9 @@ class ProposalRepository { ProposalRepository(this._proposalService, this._profileService); - 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); + Future, HyphaError>> getProposals(UserProfileData user, GetProposalsUseCaseInput input) async { + final List, HyphaError>>> futures = input.daos.map((DaoData dao) { + return input.filterStatus == FilterStatus.active ? _proposalService.getActiveProposals(user, dao.docId) : _proposalService.getPastProposals(user, dao.docId); }).toList(); final List, HyphaError>> futureResults = await Future.wait(futures); @@ -38,7 +39,7 @@ class ProposalRepository { } try { - final List proposals = await _parseProposalsFromResponse(response, daos[i], filterStatus); + final List proposals = await _parseProposalsFromResponse(response, input.daos[i], input.filterStatus); allProposals.addAll(proposals); } catch (e, stackTrace) { LogHelper.e('Error parsing data into proposal model', error: e, stacktrace: stackTrace); diff --git a/lib/ui/proposals/list/interactor/get_proposals_use_case_input.dart b/lib/ui/proposals/list/interactor/get_proposals_use_case_input.dart new file mode 100644 index 00000000..aa1886f1 --- /dev/null +++ b/lib/ui/proposals/list/interactor/get_proposals_use_case_input.dart @@ -0,0 +1,9 @@ +import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; +import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_status.dart'; + +class GetProposalsUseCaseInput { + final List daos; + final FilterStatus filterStatus; + + GetProposalsUseCaseInput(this.daos, this.filterStatus); +} diff --git a/lib/ui/proposals/list/interactor/proposals_bloc.dart b/lib/ui/proposals/list/interactor/proposals_bloc.dart index ba9d7d76..78028fe3 100644 --- a/lib/ui/proposals/list/interactor/proposals_bloc.dart +++ b/lib/ui/proposals/list/interactor/proposals_bloc.dart @@ -9,6 +9,7 @@ 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/interactor/get_proposals_use_case_input.dart'; import 'package:hypha_wallet/ui/proposals/list/usecases/get_proposals_use_case.dart'; part 'proposals_bloc.freezed.dart'; @@ -49,7 +50,7 @@ class ProposalsBloc extends Bloc { } Future _fetchAndEmitProposals(Emitter emit, List daos, FilterStatus filterStatus) async { - final Result, HyphaError> proposalsResult = await _getProposalsUseCase.run(daos, filterStatus); + final Result, HyphaError> proposalsResult = await _getProposalsUseCase.run(GetProposalsUseCaseInput(daos, filterStatus)); if (proposalsResult.isValue) { emit(state.copyWith(pageState: PageState.success, proposals: proposalsResult.asValue!.value)); 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 588c6963..67f7e40b 100644 --- a/lib/ui/proposals/list/usecases/get_proposals_use_case.dart +++ b/lib/ui/proposals/list/usecases/get_proposals_use_case.dart @@ -1,17 +1,17 @@ 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/core/network/repository/auth_repository.dart'; import 'package:hypha_wallet/core/network/repository/proposal_repository.dart'; +import 'package:hypha_wallet/ui/architecture/interactor/base_usecase.dart'; import 'package:hypha_wallet/ui/architecture/result/result.dart'; -import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_status.dart'; +import 'package:hypha_wallet/ui/proposals/list/interactor/get_proposals_use_case_input.dart'; -// TODO(Zied): add 'extends' (check) -class GetProposalsUseCase { +class GetProposalsUseCase extends InputUseCase, HyphaError>, GetProposalsUseCaseInput> { final AuthRepository _authRepository; final ProposalRepository _proposalRepository; GetProposalsUseCase(this._authRepository, this._proposalRepository); - Future, HyphaError>> run(List daos, FilterStatus filterStatus) async => _proposalRepository.getProposals(_authRepository.authDataOrCrash.userProfileData, daos, filterStatus); + @override + Future, HyphaError>> run(GetProposalsUseCaseInput input) async => _proposalRepository.getProposals(_authRepository.authDataOrCrash.userProfileData, input); } From eb63b270258e8e2f07358713214e923b3d804a9e Mon Sep 17 00:00:00 2001 From: Zied Dahmani Date: Tue, 24 Sep 2024 22:25:01 +0100 Subject: [PATCH 03/10] feat: implement proposals history --- lib/core/di/bloc_module.dart | 10 +- lib/core/di/di_setup.dart | 2 + .../proposals/components/proposals_list.dart | 20 + .../components/proposals_history_view.dart | 35 ++ .../interactor/proposals_history_bloc.dart | 39 ++ .../proposals_history_bloc.freezed.dart | 396 ++++++++++++++++++ .../interactor/proposals_history_event.dart | 6 + .../interactor/proposals_history_state.dart | 9 + .../history/proposals_history_page.dart | 19 + .../list/components/proposals_view.dart | 15 +- .../list/interactor/proposals_bloc.dart | 1 + 11 files changed, 538 insertions(+), 14 deletions(-) create mode 100644 lib/ui/proposals/components/proposals_list.dart create mode 100644 lib/ui/proposals/history/components/proposals_history_view.dart create mode 100644 lib/ui/proposals/history/interactor/proposals_history_bloc.dart create mode 100644 lib/ui/proposals/history/interactor/proposals_history_bloc.freezed.dart create mode 100644 lib/ui/proposals/history/interactor/proposals_history_event.dart create mode 100644 lib/ui/proposals/history/interactor/proposals_history_state.dart create mode 100644 lib/ui/proposals/history/proposals_history_page.dart diff --git a/lib/core/di/bloc_module.dart b/lib/core/di/bloc_module.dart index f05f7ab5..3276585c 100644 --- a/lib/core/di/bloc_module.dart +++ b/lib/core/di/bloc_module.dart @@ -147,4 +147,12 @@ void _registerBlocsModule() { _getIt(), _getIt(), )); -} \ No newline at end of file + + _registerFactoryWithParams( + (dao, _) => ProposalsHistoryBloc( + _getIt(), + _getIt(), + dao + ), + ); +} diff --git a/lib/core/di/di_setup.dart b/lib/core/di/di_setup.dart index f10641d8..b4dbca63 100644 --- a/lib/core/di/di_setup.dart +++ b/lib/core/di/di_setup.dart @@ -30,6 +30,7 @@ import 'package:hypha_wallet/core/network/api/services/token_service.dart'; import 'package:hypha_wallet/core/network/api/services/transaction_history_service.dart'; import 'package:hypha_wallet/core/network/api/services/user_account_service.dart'; import 'package:hypha_wallet/core/network/ipfs/ipfs_manager.dart'; +import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; import 'package:hypha_wallet/core/network/models/network.dart'; import 'package:hypha_wallet/core/network/models/user_profile_data.dart'; import 'package:hypha_wallet/core/network/networking_manager.dart'; @@ -76,6 +77,7 @@ import 'package:hypha_wallet/ui/proposals/details/usecases/get_proposal_details_ 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/history/interactor/proposals_history_bloc.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/ui/proposals/components/proposals_list.dart b/lib/ui/proposals/components/proposals_list.dart new file mode 100644 index 00000000..d85ed627 --- /dev/null +++ b/lib/ui/proposals/components/proposals_list.dart @@ -0,0 +1,20 @@ +import 'package:flutter/material.dart'; +import 'package:hypha_wallet/core/network/models/proposal_model.dart'; +import 'package:hypha_wallet/ui/proposals/list/components/hypha_proposals_action_card.dart'; + +class ProposalsList extends StatelessWidget { + final List proposals; + + const ProposalsList(this.proposals, {super.key}); + + @override + Widget build(BuildContext context) { + return ListView.separated( + padding: const EdgeInsets.only(bottom: 22), + itemBuilder: (BuildContext context, int index) => HyphaProposalsActionCard(proposals[index]), + separatorBuilder: (BuildContext context, int index) { + return const SizedBox(height: 16); + }, + itemCount: proposals.length); + } +} diff --git a/lib/ui/proposals/history/components/proposals_history_view.dart b/lib/ui/proposals/history/components/proposals_history_view.dart new file mode 100644 index 00000000..c58a4038 --- /dev/null +++ b/lib/ui/proposals/history/components/proposals_history_view.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get/get.dart'; +import 'package:hypha_wallet/design/hypha_colors.dart'; +import 'package:hypha_wallet/ui/proposals/components/proposals_list.dart'; +import 'package:hypha_wallet/ui/proposals/history/interactor/proposals_history_bloc.dart'; +import 'package:hypha_wallet/ui/shared/hypha_body_widget.dart'; + +class ProposalsHistoryView extends StatelessWidget { + const ProposalsHistoryView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: context.isDarkMode ? HyphaColors.darkBlack : HyphaColors.offWhite, + appBar: AppBar( + title: const Text('Proposals History'), + ), + body: RefreshIndicator( + onRefresh: () async { + context.read().add(const ProposalsHistoryEvent.initial(refresh: true)); + }, + child: BlocBuilder( + builder: (context, state) { + return HyphaBodyWidget(pageState: state.pageState, success: (context) { + return Container( + padding: const EdgeInsets.all(20), + child: ProposalsList(state.proposals), + ); + }); + }), + ) + ); + } +} diff --git a/lib/ui/proposals/history/interactor/proposals_history_bloc.dart b/lib/ui/proposals/history/interactor/proposals_history_bloc.dart new file mode 100644 index 00000000..b0c85339 --- /dev/null +++ b/lib/ui/proposals/history/interactor/proposals_history_bloc.dart @@ -0,0 +1,39 @@ +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/proposals/filter/interactor/filter_status.dart'; +import 'package:hypha_wallet/ui/proposals/list/usecases/get_proposals_use_case.dart'; + +part 'proposals_history_bloc.freezed.dart'; +part 'proposals_history_event.dart'; +part 'proposals_history_state.dart'; + +class ProposalsHistoryBloc extends Bloc { + final GetProposalsUseCase _getProposalsUseCase; + final ErrorHandlerManager _errorHandlerManager; + final DaoData _dao; + + ProposalsHistoryBloc(this._getProposalsUseCase, this._errorHandlerManager, this._dao) : super(const ProposalsHistoryState()) { + on<_Initial>(_initial); + } + + Future _initial(_Initial event, Emitter emit) async { + if (!event.refresh) { + emit(state.copyWith(pageState: PageState.loading)); + } + + final Result, HyphaError> proposalsResult = await _getProposalsUseCase.run([_dao], FilterStatus.past); + + 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)); + } + } +} diff --git a/lib/ui/proposals/history/interactor/proposals_history_bloc.freezed.dart b/lib/ui/proposals/history/interactor/proposals_history_bloc.freezed.dart new file mode 100644 index 00000000..f7f69edc --- /dev/null +++ b/lib/ui/proposals/history/interactor/proposals_history_bloc.freezed.dart @@ -0,0 +1,396 @@ +// 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 'proposals_history_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 _$ProposalsHistoryEvent { + bool get refresh => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(bool refresh) initial, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool refresh)? initial, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool refresh)? initial, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Create a copy of ProposalsHistoryEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProposalsHistoryEventCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProposalsHistoryEventCopyWith<$Res> { + factory $ProposalsHistoryEventCopyWith(ProposalsHistoryEvent value, + $Res Function(ProposalsHistoryEvent) then) = + _$ProposalsHistoryEventCopyWithImpl<$Res, ProposalsHistoryEvent>; + @useResult + $Res call({bool refresh}); +} + +/// @nodoc +class _$ProposalsHistoryEventCopyWithImpl<$Res, + $Val extends ProposalsHistoryEvent> + implements $ProposalsHistoryEventCopyWith<$Res> { + _$ProposalsHistoryEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProposalsHistoryEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? refresh = null, + }) { + return _then(_value.copyWith( + refresh: null == refresh + ? _value.refresh + : refresh // ignore: cast_nullable_to_non_nullable + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$InitialImplCopyWith<$Res> + implements $ProposalsHistoryEventCopyWith<$Res> { + factory _$$InitialImplCopyWith( + _$InitialImpl value, $Res Function(_$InitialImpl) then) = + __$$InitialImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({bool refresh}); +} + +/// @nodoc +class __$$InitialImplCopyWithImpl<$Res> + extends _$ProposalsHistoryEventCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of ProposalsHistoryEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? refresh = null, + }) { + return _then(_$InitialImpl( + refresh: null == refresh + ? _value.refresh + : refresh // ignore: cast_nullable_to_non_nullable + as bool, + )); + } +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl({this.refresh = false}); + + @override + @JsonKey() + final bool refresh; + + @override + String toString() { + return 'ProposalsHistoryEvent.initial(refresh: $refresh)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$InitialImpl && + (identical(other.refresh, refresh) || other.refresh == refresh)); + } + + @override + int get hashCode => Object.hash(runtimeType, refresh); + + /// Create a copy of ProposalsHistoryEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$InitialImplCopyWith<_$InitialImpl> get copyWith => + __$$InitialImplCopyWithImpl<_$InitialImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(bool refresh) initial, + }) { + return initial(refresh); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool refresh)? initial, + }) { + return initial?.call(refresh); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool refresh)? initial, + required TResult orElse(), + }) { + if (initial != null) { + return initial(refresh); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements ProposalsHistoryEvent { + const factory _Initial({final bool refresh}) = _$InitialImpl; + + @override + bool get refresh; + + /// Create a copy of ProposalsHistoryEvent + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$InitialImplCopyWith<_$InitialImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProposalsHistoryState { + PageState get pageState => throw _privateConstructorUsedError; + List get proposals => throw _privateConstructorUsedError; + + /// Create a copy of ProposalsHistoryState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProposalsHistoryStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProposalsHistoryStateCopyWith<$Res> { + factory $ProposalsHistoryStateCopyWith(ProposalsHistoryState value, + $Res Function(ProposalsHistoryState) then) = + _$ProposalsHistoryStateCopyWithImpl<$Res, ProposalsHistoryState>; + @useResult + $Res call({PageState pageState, List proposals}); +} + +/// @nodoc +class _$ProposalsHistoryStateCopyWithImpl<$Res, + $Val extends ProposalsHistoryState> + implements $ProposalsHistoryStateCopyWith<$Res> { + _$ProposalsHistoryStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProposalsHistoryState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? pageState = null, + Object? proposals = null, + }) { + return _then(_value.copyWith( + pageState: null == pageState + ? _value.pageState + : pageState // ignore: cast_nullable_to_non_nullable + as PageState, + proposals: null == proposals + ? _value.proposals + : proposals // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ProposalsHistoryStateImplCopyWith<$Res> + implements $ProposalsHistoryStateCopyWith<$Res> { + factory _$$ProposalsHistoryStateImplCopyWith( + _$ProposalsHistoryStateImpl value, + $Res Function(_$ProposalsHistoryStateImpl) then) = + __$$ProposalsHistoryStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({PageState pageState, List proposals}); +} + +/// @nodoc +class __$$ProposalsHistoryStateImplCopyWithImpl<$Res> + extends _$ProposalsHistoryStateCopyWithImpl<$Res, + _$ProposalsHistoryStateImpl> + implements _$$ProposalsHistoryStateImplCopyWith<$Res> { + __$$ProposalsHistoryStateImplCopyWithImpl(_$ProposalsHistoryStateImpl _value, + $Res Function(_$ProposalsHistoryStateImpl) _then) + : super(_value, _then); + + /// Create a copy of ProposalsHistoryState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? pageState = null, + Object? proposals = null, + }) { + return _then(_$ProposalsHistoryStateImpl( + pageState: null == pageState + ? _value.pageState + : pageState // ignore: cast_nullable_to_non_nullable + as PageState, + proposals: null == proposals + ? _value._proposals + : proposals // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$ProposalsHistoryStateImpl implements _ProposalsHistoryState { + const _$ProposalsHistoryStateImpl( + {this.pageState = PageState.initial, + final List proposals = const []}) + : _proposals = proposals; + + @override + @JsonKey() + final PageState pageState; + final List _proposals; + @override + @JsonKey() + List get proposals { + if (_proposals is EqualUnmodifiableListView) return _proposals; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_proposals); + } + + @override + String toString() { + return 'ProposalsHistoryState(pageState: $pageState, proposals: $proposals)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProposalsHistoryStateImpl && + (identical(other.pageState, pageState) || + other.pageState == pageState) && + const DeepCollectionEquality() + .equals(other._proposals, _proposals)); + } + + @override + int get hashCode => Object.hash( + runtimeType, pageState, const DeepCollectionEquality().hash(_proposals)); + + /// Create a copy of ProposalsHistoryState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProposalsHistoryStateImplCopyWith<_$ProposalsHistoryStateImpl> + get copyWith => __$$ProposalsHistoryStateImplCopyWithImpl< + _$ProposalsHistoryStateImpl>(this, _$identity); +} + +abstract class _ProposalsHistoryState implements ProposalsHistoryState { + const factory _ProposalsHistoryState( + {final PageState pageState, + final List proposals}) = _$ProposalsHistoryStateImpl; + + @override + PageState get pageState; + @override + List get proposals; + + /// Create a copy of ProposalsHistoryState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProposalsHistoryStateImplCopyWith<_$ProposalsHistoryStateImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/ui/proposals/history/interactor/proposals_history_event.dart b/lib/ui/proposals/history/interactor/proposals_history_event.dart new file mode 100644 index 00000000..bd9ea4a1 --- /dev/null +++ b/lib/ui/proposals/history/interactor/proposals_history_event.dart @@ -0,0 +1,6 @@ +part of 'proposals_history_bloc.dart'; + +@freezed +class ProposalsHistoryEvent with _$ProposalsHistoryEvent { + const factory ProposalsHistoryEvent.initial({@Default(false) bool refresh}) = _Initial; +} diff --git a/lib/ui/proposals/history/interactor/proposals_history_state.dart b/lib/ui/proposals/history/interactor/proposals_history_state.dart new file mode 100644 index 00000000..71180a10 --- /dev/null +++ b/lib/ui/proposals/history/interactor/proposals_history_state.dart @@ -0,0 +1,9 @@ +part of 'proposals_history_bloc.dart'; + +@freezed +class ProposalsHistoryState with _$ProposalsHistoryState { + const factory ProposalsHistoryState({ + @Default(PageState.initial) PageState pageState, + @Default([]) List proposals, + }) = _ProposalsHistoryState; +} diff --git a/lib/ui/proposals/history/proposals_history_page.dart b/lib/ui/proposals/history/proposals_history_page.dart new file mode 100644 index 00000000..b0cb4a2f --- /dev/null +++ b/lib/ui/proposals/history/proposals_history_page.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; +import 'package:hypha_wallet/ui/proposals/history/components/proposals_history_view.dart'; +import 'package:hypha_wallet/ui/proposals/history/interactor/proposals_history_bloc.dart'; + +class ProposalsHistoryPage extends StatelessWidget { + final DaoData _dao; + const ProposalsHistoryPage(this._dao, {super.key}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => GetIt.I.get(param1: _dao)..add(const ProposalsHistoryEvent.initial()), + child: const ProposalsHistoryView(), + ); + } +} diff --git a/lib/ui/proposals/list/components/proposals_view.dart b/lib/ui/proposals/list/components/proposals_view.dart index 215fbc2b..3e186cc3 100644 --- a/lib/ui/proposals/list/components/proposals_view.dart +++ b/lib/ui/proposals/list/components/proposals_view.dart @@ -7,10 +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/components/proposals_list.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'; @@ -100,17 +99,7 @@ class ProposalsView extends StatelessWidget { const SizedBox( height: 20, ), - Expanded( - child: ListView.separated( - padding: const EdgeInsets.only(bottom: 22), - itemBuilder: (BuildContext context, - int index) => - HyphaProposalsActionCard(state.proposals[index]), - separatorBuilder: - (BuildContext context, int index) { - return const SizedBox(height: 16); - }, - itemCount: state.proposals.length)), + Expanded(child: ProposalsList(state.proposals)), ], ), ), diff --git a/lib/ui/proposals/list/interactor/proposals_bloc.dart b/lib/ui/proposals/list/interactor/proposals_bloc.dart index ba9d7d76..7ef16974 100644 --- a/lib/ui/proposals/list/interactor/proposals_bloc.dart +++ b/lib/ui/proposals/list/interactor/proposals_bloc.dart @@ -39,6 +39,7 @@ class ProposalsBloc extends Bloc { final Result profileResult = await _fetchProfileUseCase.run(); + // TODO(Zied): DAOs may be empty list due to crash or user has no DAO, so refactor if (profileResult.isValue && profileResult.asValue!.value.daos.isNotEmpty) { await _fetchAndEmitProposals(emit, profileResult.asValue!.value.daos, filterStatus); } else { From 5bf5e325ca3267b5ac155a01191545946de29718 Mon Sep 17 00:00:00 2001 From: Zied Dahmani Date: Thu, 26 Sep 2024 19:57:48 +0100 Subject: [PATCH 04/10] fix: filter proposals --- lib/core/di/bloc_module.dart | 1 - .../extension/proposals_filter_extension.dart | 7 ++ .../components/filter_proposals_view.dart | 3 +- .../filter/components/hypha_filter_card.dart | 6 +- .../interactor/filter_proposals_bloc.dart | 10 +-- ...ggregate_dao_proposal_counts_use_case.dart | 7 +- ...et_daos_from_proposal_counts_use_case.dart | 8 --- .../list/components/proposals_view.dart | 67 +++++++++-------- .../list/interactor/proposals_bloc.dart | 25 +++---- .../interactor/proposals_bloc.freezed.dart | 72 ++++--------------- .../list/interactor/proposals_event.dart | 1 - 11 files changed, 85 insertions(+), 122 deletions(-) create mode 100644 lib/core/extension/proposals_filter_extension.dart delete 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 f05f7ab5..ec059eb7 100644 --- a/lib/core/di/bloc_module.dart +++ b/lib/core/di/bloc_module.dart @@ -144,7 +144,6 @@ void _registerBlocsModule() { _getIt(), _getIt(), _getIt(), - _getIt(), _getIt(), )); } \ No newline at end of file diff --git a/lib/core/extension/proposals_filter_extension.dart b/lib/core/extension/proposals_filter_extension.dart new file mode 100644 index 00000000..74b45b2b --- /dev/null +++ b/lib/core/extension/proposals_filter_extension.dart @@ -0,0 +1,7 @@ +import 'package:hypha_wallet/core/network/models/proposal_model.dart'; + +extension ProposalFilterExtension on List { + List filterByDao(List daoIds) { + return where((proposal) => daoIds.contains(proposal.dao?.docId)).toList(); + } +} diff --git a/lib/ui/proposals/filter/components/filter_proposals_view.dart b/lib/ui/proposals/filter/components/filter_proposals_view.dart index 0e868344..f9fdf372 100644 --- a/lib/ui/proposals/filter/components/filter_proposals_view.dart +++ b/lib/ui/proposals/filter/components/filter_proposals_view.dart @@ -74,7 +74,8 @@ class FilterProposalsView extends StatelessWidget { 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)); + final int? index = filterProposalsBloc.selectedDaoIndexNotifier.value; + filterProposalsBloc.add(FilterProposalsEvent.saveFilters(index == null ? state.daoProposalCounts : [state.daoProposalCounts[index]], filterProposalsBloc.selectedStatusIndexNotifier.value == 0 ? FilterStatus.active : FilterStatus.past)); }, ), const SizedBox( diff --git a/lib/ui/proposals/filter/components/hypha_filter_card.dart b/lib/ui/proposals/filter/components/hypha_filter_card.dart index 53d9429d..372373ef 100644 --- a/lib/ui/proposals/filter/components/hypha_filter_card.dart +++ b/lib/ui/proposals/filter/components/hypha_filter_card.dart @@ -1,11 +1,9 @@ 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/dao_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 DaoData? dao; @@ -21,7 +19,7 @@ class HyphaFilterCard extends StatelessWidget { // TODO(Saif): fix the card height (filter by status) return GestureDetector( onTap: () { - valueNotifier.value = index; + valueNotifier.value = valueNotifier.value == index ? null : index; }, child: HyphaCard( child: Padding( diff --git a/lib/ui/proposals/filter/interactor/filter_proposals_bloc.dart b/lib/ui/proposals/filter/interactor/filter_proposals_bloc.dart index c58269d7..6f2324b1 100644 --- a/lib/ui/proposals/filter/interactor/filter_proposals_bloc.dart +++ b/lib/ui/proposals/filter/interactor/filter_proposals_bloc.dart @@ -11,7 +11,6 @@ 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'; @@ -23,14 +22,12 @@ class FilterProposalsBloc extends Bloc(_initial); @@ -40,11 +37,13 @@ class FilterProposalsBloc extends Bloc _selectedDaoIndexNotifier = ValueNotifier(null); + final ValueNotifier _selectedDaoIndexNotifier = ValueNotifier(0); final ValueNotifier _selectedStatusIndexNotifier = ValueNotifier(0); + List? _daoIds; ValueNotifier get selectedDaoIndexNotifier => _selectedDaoIndexNotifier; ValueNotifier get selectedStatusIndexNotifier => _selectedStatusIndexNotifier; + List? get daoIds => _daoIds; Future _initial(_Initial event, Emitter emit) async { emit(state.copyWith(pageState: PageState.loading)); @@ -76,7 +75,8 @@ class FilterProposalsBloc extends Bloc _saveFilters(_SaveFilters event, Emitter emit) async { - _proposalsBloc.add(ProposalsEvent.initial(daos: _getDaosFromProposalCountsUseCase.run(event.daoProposalCounts), filterStatus: event.filterStatus)); + _daoIds = event.daoProposalCounts.map((DaoProposalCountEntity daoProposalCount) => daoProposalCount.dao.docId).toList(); + _proposalsBloc.add(ProposalsEvent.initial(filterStatus: event.filterStatus)); emit(state.copyWith(command: const PageCommand.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 index 61ad439b..7c35c325 100644 --- 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 @@ -20,8 +20,9 @@ class AggregateDaoProposalCountsUseCase { } } - return daoProposalCounts.entries - .map((entry) => DaoProposalCountEntity(entry.key, entry.value)) - .toList(); + final List> sortedEntries = daoProposalCounts.entries.toList() + ..sort((a, b) => a.key.settingsDaoTitle.compareTo(b.key.settingsDaoTitle)); + + return sortedEntries.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 deleted file mode 100644 index ffd5fa31..00000000 --- a/lib/ui/proposals/filter/usecases/get_daos_from_proposal_counts_use_case.dart +++ /dev/null @@ -1,8 +0,0 @@ -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 215fbc2b..44be8146 100644 --- a/lib/ui/proposals/list/components/proposals_view.dart +++ b/lib/ui/proposals/list/components/proposals_view.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:get/get.dart' as GetX; +import 'package:get_it/get_it.dart'; +import 'package:hypha_wallet/core/extension/proposals_filter_extension.dart'; +import 'package:hypha_wallet/core/network/models/proposal_model.dart'; 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/hypha_colors.dart'; @@ -84,36 +87,40 @@ class ProposalsView extends StatelessWidget { ), child: HyphaBodyWidget( pageState: state.pageState, - success: (context) => Padding( - padding: const EdgeInsets.symmetric(horizontal: 22), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox( - height: 22, - ), - Text( - '${state.proposals.length} ${context.read().filterStatus.string} Proposal${state.proposals.length == 1 ? '' : 's'}', - style: context.hyphaTextTheme.ralMediumBody - .copyWith(color: HyphaColors.midGrey), - ), - const SizedBox( - height: 20, - ), - Expanded( - child: ListView.separated( - padding: const EdgeInsets.only(bottom: 22), - itemBuilder: (BuildContext context, - int index) => - HyphaProposalsActionCard(state.proposals[index]), - separatorBuilder: - (BuildContext context, int index) { - return const SizedBox(height: 16); - }, - itemCount: state.proposals.length)), - ], - ), - ), + success: (context) { + final List? daoIds = GetIt.I.get().daoIds; + final List proposals = daoIds != null ? state.proposals.filterByDao(daoIds) : state.proposals; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 22), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 22, + ), + Text( + '${proposals.length} ${context.read().filterStatus.string} Proposal${proposals.length == 1 ? '' : 's'}', + style: context.hyphaTextTheme.ralMediumBody + .copyWith(color: HyphaColors.midGrey), + ), + const SizedBox( + height: 20, + ), + Expanded( + child: ListView.separated( + padding: const EdgeInsets.only(bottom: 22), + itemBuilder: (BuildContext context, + int index) => + HyphaProposalsActionCard(proposals[index]), + separatorBuilder: + (BuildContext context, int index) { + return const SizedBox(height: 16); + }, + itemCount: proposals.length)), + ], + ), + ); + }, ), )), floatingActionButton: IconButton( diff --git a/lib/ui/proposals/list/interactor/proposals_bloc.dart b/lib/ui/proposals/list/interactor/proposals_bloc.dart index ba9d7d76..df94554c 100644 --- a/lib/ui/proposals/list/interactor/proposals_bloc.dart +++ b/lib/ui/proposals/list/interactor/proposals_bloc.dart @@ -24,6 +24,7 @@ class ProposalsBloc extends Bloc { on<_Initial>(_initial); } + List? _daos; FilterStatus filterStatus = FilterStatus.active; Future _initial(_Initial event, Emitter emit) async { @@ -32,19 +33,19 @@ class ProposalsBloc extends Bloc { 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) { - await _fetchAndEmitProposals(emit, profileResult.asValue!.value.daos, filterStatus); + if (_daos == null) { + final Result profileResult = await _fetchProfileUseCase.run(); + + if (profileResult.isValue && profileResult.asValue!.value.daos.isNotEmpty) { + _daos = profileResult.asValue!.value.daos; + await _fetchAndEmitProposals(emit, _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)); + } } 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)); + await _fetchAndEmitProposals(emit, _daos!, filterStatus); } } diff --git a/lib/ui/proposals/list/interactor/proposals_bloc.freezed.dart b/lib/ui/proposals/list/interactor/proposals_bloc.freezed.dart index d58bf8b9..79658a00 100644 --- a/lib/ui/proposals/list/interactor/proposals_bloc.freezed.dart +++ b/lib/ui/proposals/list/interactor/proposals_bloc.freezed.dart @@ -17,27 +17,20 @@ 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, List? daos, FilterStatus filterStatus) - initial, + required TResult Function(bool refresh, FilterStatus filterStatus) initial, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - bool refresh, List? daos, FilterStatus filterStatus)? - initial, + TResult? Function(bool refresh, FilterStatus filterStatus)? initial, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function( - bool refresh, List? daos, FilterStatus filterStatus)? - initial, + TResult Function(bool refresh, FilterStatus filterStatus)? initial, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -71,7 +64,7 @@ abstract class $ProposalsEventCopyWith<$Res> { ProposalsEvent value, $Res Function(ProposalsEvent) then) = _$ProposalsEventCopyWithImpl<$Res, ProposalsEvent>; @useResult - $Res call({bool refresh, List? daos, FilterStatus filterStatus}); + $Res call({bool refresh, FilterStatus filterStatus}); } /// @nodoc @@ -90,7 +83,6 @@ class _$ProposalsEventCopyWithImpl<$Res, $Val extends ProposalsEvent> @override $Res call({ Object? refresh = null, - Object? daos = freezed, Object? filterStatus = null, }) { return _then(_value.copyWith( @@ -98,10 +90,6 @@ class _$ProposalsEventCopyWithImpl<$Res, $Val extends ProposalsEvent> ? _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 @@ -118,7 +106,7 @@ abstract class _$$InitialImplCopyWith<$Res> __$$InitialImplCopyWithImpl<$Res>; @override @useResult - $Res call({bool refresh, List? daos, FilterStatus filterStatus}); + $Res call({bool refresh, FilterStatus filterStatus}); } /// @nodoc @@ -135,7 +123,6 @@ class __$$InitialImplCopyWithImpl<$Res> @override $Res call({ Object? refresh = null, - Object? daos = freezed, Object? filterStatus = null, }) { return _then(_$InitialImpl( @@ -143,10 +130,6 @@ class __$$InitialImplCopyWithImpl<$Res> ? _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 @@ -159,31 +142,18 @@ class __$$InitialImplCopyWithImpl<$Res> class _$InitialImpl implements _Initial { const _$InitialImpl( - {this.refresh = false, - final List? daos, - this.filterStatus = FilterStatus.active}) - : _daos = daos; + {this.refresh = false, this.filterStatus = FilterStatus.active}); @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, daos: $daos, filterStatus: $filterStatus)'; + return 'ProposalsEvent.initial(refresh: $refresh, filterStatus: $filterStatus)'; } @override @@ -192,14 +162,12 @@ class _$InitialImpl implements _Initial { (other.runtimeType == runtimeType && other is _$InitialImpl && (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, - const DeepCollectionEquality().hash(_daos), filterStatus); + int get hashCode => Object.hash(runtimeType, refresh, filterStatus); /// Create a copy of ProposalsEvent /// with the given fields replaced by the non-null parameter values. @@ -212,33 +180,27 @@ class _$InitialImpl implements _Initial { @override @optionalTypeArgs TResult when({ - required TResult Function( - bool refresh, List? daos, FilterStatus filterStatus) - initial, + required TResult Function(bool refresh, FilterStatus filterStatus) initial, }) { - return initial(refresh, daos, filterStatus); + return initial(refresh, filterStatus); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - bool refresh, List? daos, FilterStatus filterStatus)? - initial, + TResult? Function(bool refresh, FilterStatus filterStatus)? initial, }) { - return initial?.call(refresh, daos, filterStatus); + return initial?.call(refresh, filterStatus); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - bool refresh, List? daos, FilterStatus filterStatus)? - initial, + TResult Function(bool refresh, FilterStatus filterStatus)? initial, required TResult orElse(), }) { if (initial != null) { - return initial(refresh, daos, filterStatus); + return initial(refresh, filterStatus); } return orElse(); } @@ -274,15 +236,11 @@ class _$InitialImpl implements _Initial { abstract class _Initial implements ProposalsEvent { const factory _Initial( - {final bool refresh, - final List? daos, - final FilterStatus filterStatus}) = _$InitialImpl; + {final bool refresh, final FilterStatus filterStatus}) = _$InitialImpl; @override bool get refresh; @override - List? get daos; - @override FilterStatus get filterStatus; /// Create a copy of ProposalsEvent diff --git a/lib/ui/proposals/list/interactor/proposals_event.dart b/lib/ui/proposals/list/interactor/proposals_event.dart index fa6ca754..05eb07ce 100644 --- a/lib/ui/proposals/list/interactor/proposals_event.dart +++ b/lib/ui/proposals/list/interactor/proposals_event.dart @@ -4,7 +4,6 @@ part of 'proposals_bloc.dart'; class ProposalsEvent with _$ProposalsEvent { const factory ProposalsEvent.initial({ @Default(false) bool refresh, - List? daos, @Default(FilterStatus.active) FilterStatus filterStatus, }) = _Initial; } From dd3f621bfe15b04d6b08f8797917e7a0a6d3d672 Mon Sep 17 00:00:00 2001 From: Saif Nbet Date: Fri, 27 Sep 2024 00:03:26 +0100 Subject: [PATCH 05/10] feat: Implement the calculation of tokens amounts --- .../proposal_details_model_extension.dart | 45 +- .../components/proposal_details_view.dart | 606 ++++++++++-------- 2 files changed, 361 insertions(+), 290 deletions(-) diff --git a/lib/core/extension/proposal_details_model_extension.dart b/lib/core/extension/proposal_details_model_extension.dart index 6868fb1a..ea68a637 100644 --- a/lib/core/extension/proposal_details_model_extension.dart +++ b/lib/core/extension/proposal_details_model_extension.dart @@ -2,11 +2,15 @@ import 'package:hypha_wallet/core/network/models/proposal_details_model.dart'; import 'package:intl/intl.dart'; extension ProposalDetailsModelExtension on ProposalDetailsModel { - double tokenMixToPercent() => tokenMixPercentage == null ? 0 : tokenMixPercentage! * .01; + double tokenMixToPercent() => + tokenMixPercentage == null ? 0 : tokenMixPercentage! * .01; - String formatCycleStartDate() => cycleStartDate != null ? DateFormat('EEEE, MMMM yyyy').format(cycleStartDate!) : ''; + String formatCycleStartDate() => cycleStartDate != null + ? DateFormat('EEEE, MMMM yyyy').format(cycleStartDate!) + : ''; - String cycleEndDate() => DateFormat('EEEE, MMMM yyyy').format(cycleStartDate!.add(Duration(days: cycleCount! * 7))); + String cycleEndDate() => DateFormat('EEEE, MMMM yyyy') + .format(cycleStartDate!.add(Duration(days: cycleCount! * 7))); String? tokenTitle(int index) { String input; @@ -39,27 +43,42 @@ extension ProposalDetailsModelExtension on ProposalDetailsModel { return null; } - // TODO(Saif): adjust this function String? tokenValue(int index, bool isOneCycleRewardsShown) { - String input; + String? input; + String? perPeriodInput; switch (index) { case 0: - input = utilityAmount ?? utilityAmountPerPeriod!; + input = utilityAmount; + perPeriodInput = utilityAmountPerPeriod; break; case 1: - input = voiceAmount ?? voiceAmountPerPeriod!; + input = voiceAmount; + perPeriodInput = voiceAmountPerPeriod; break; case 2: - input = cashAmount ?? cashAmountPerPeriod!; + input = cashAmount; + perPeriodInput = cashAmountPerPeriod; + break; default: return null; } - - final RegExp regExp = RegExp(r'(\S+)\s'); - final match = regExp.firstMatch(input); - - return match?.group(1); + final RegExp regExp = RegExp(r'(\d+(\.\d+)?)'); + if (input != null) { + final match = regExp.firstMatch(input); + if (isOneCycleRewardsShown || cycleCount == 1) return match?.group(1); + return (double.parse((match?.group(1))!) * cycleCount!) + .toStringAsFixed(3); + } + if (perPeriodInput != null) { + final match = regExp.firstMatch(perPeriodInput); + if (isOneCycleRewardsShown && cycleCount != 1) { + return (double.parse((match?.group(1))!) / cycleCount!) + .toStringAsFixed(3); + } + return match?.group(1); + } + return input; } } diff --git a/lib/ui/proposals/details/components/proposal_details_view.dart b/lib/ui/proposals/details/components/proposal_details_view.dart index 660068f2..771c32e9 100644 --- a/lib/ui/proposals/details/components/proposal_details_view.dart +++ b/lib/ui/proposals/details/components/proposal_details_view.dart @@ -5,7 +5,6 @@ import 'package:hypha_wallet/core/extension/base_proposal_model_extension.dart'; import 'package:hypha_wallet/core/extension/proposal_details_model_extension.dart'; import 'package:hypha_wallet/core/network/models/proposal_details_model.dart'; import 'package:hypha_wallet/core/network/models/vote_model.dart'; -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'; @@ -14,7 +13,6 @@ 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'; import 'package:hypha_wallet/ui/proposals/components/proposal_button.dart'; -import 'package:hypha_wallet/ui/proposals/components/proposal_creator.dart'; import 'package:hypha_wallet/ui/proposals/components/proposal_expiration_timer.dart'; import 'package:hypha_wallet/ui/proposals/components/proposal_header.dart'; import 'package:hypha_wallet/ui/proposals/components/proposal_percentage_indicator.dart'; @@ -30,7 +28,7 @@ class ProposalDetailsView extends StatefulWidget { } class _ProposalDetailsViewState extends State { - final ValueNotifier _isShownNotifier = ValueNotifier(null); + final ValueNotifier _isShownNotifier = ValueNotifier(true); final ValueNotifier _isOverflowingNotifier = ValueNotifier(false); final ValueNotifier _isExpandedNotifier = ValueNotifier(false); final ValueNotifier _detailsNotifier = ValueNotifier(null); @@ -47,9 +45,9 @@ class _ProposalDetailsViewState extends State { } } - void _initSwitchValue(ProposalDetailsModel proposalDetailsModel) { - _isShownNotifier.value = proposalDetailsModel.utilityAmount != null; - } + // void _initSwitchValue(ProposalDetailsModel proposalDetailsModel) { + // _isShownNotifier.value = proposalDetailsModel.utilityAmount != null; + // } @override Widget build(BuildContext context) { @@ -62,312 +60,366 @@ class _ProposalDetailsViewState extends State { title: const Text('Proposal Details'), ), body: BlocBuilder( - builder: (context, state) { - return HyphaBodyWidget(pageState: state.pageState, success: (context) { - final ProposalDetailsModel _proposalDetailsModel = state.proposalDetailsModel!; - final List passVoters = _proposalDetailsModel.fetchVotersByStatus(VoteStatus.pass); - final List failVoters = _proposalDetailsModel.fetchVotersByStatus(VoteStatus.fail); - _detailsNotifier.value = _proposalDetailsModel.description; - WidgetsBinding.instance.addPostFrameCallback((_) { - _checkIfTextIsOverflowing(); - _initSwitchValue(_proposalDetailsModel); - }); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: ListView( - children: [ - const SizedBox(height: 20), - /// Header - ProposalHeader(_proposalDetailsModel.dao), - const Padding( - padding: EdgeInsets.only(top: 10, bottom: 20), - child: HyphaDivider(), - ), - /// Main Section - Wrap( - children: List.generate( - _proposalDetailsModel.commitment != null ? 3 : 2, - (index) => Padding( - padding: const EdgeInsets.only(right: 10), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - border: Border.all( - color: context.isDarkMode - ? HyphaColors.white - : HyphaColors.lightBlack, + builder: (context, state) { + return HyphaBodyWidget(pageState: state.pageState, success: (context) { + final ProposalDetailsModel _proposalDetailsModel = state.proposalDetailsModel!; + final List passVoters = _proposalDetailsModel.fetchVotersByStatus(VoteStatus.pass); + final List failVoters = _proposalDetailsModel.fetchVotersByStatus(VoteStatus.fail); + _detailsNotifier.value = _proposalDetailsModel.description; + WidgetsBinding.instance.addPostFrameCallback((_) { + _checkIfTextIsOverflowing(); + // _initSwitchValue(_proposalDetailsModel); + }); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: ListView( + children: [ + const SizedBox(height: 20), + /// Header + ProposalHeader(_proposalDetailsModel.dao), + const Padding( + padding: EdgeInsets.only(top: 10, bottom: 20), + child: HyphaDivider(), + ), + /// Main Section + Wrap( + children: List.generate( + _proposalDetailsModel.commitment != null ? 3 : 2, + (index) => Padding( + padding: const EdgeInsets.only(right: 10), + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + border: Border.all( + color: context.isDarkMode + ? HyphaColors.white + : HyphaColors.lightBlack, + ), + borderRadius: BorderRadius.circular(20), + ), + // TODO(Zied-Saif): figure these out (B6 and Role) + child: Text( + index == 0 + ? 'Role ${_proposalDetailsModel.type}' + : index == 1 + ? 'B6' + : '${_proposalDetailsModel.commitment}%', + style: context.hyphaTextTheme.ralBold, ), - borderRadius: BorderRadius.circular(20), - ), - // TODO(Zied-Saif): figure these out (B6 and Role) - child: Text( - index == 0 - ? 'Role ${_proposalDetailsModel.type}' - : index == 1 - ? 'B6' - : '${_proposalDetailsModel.commitment}%', - style: context.hyphaTextTheme.ralBold, ), ), ), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: Text( - _proposalDetailsModel.title ?? 'No title', - style: context.hyphaTextTheme.mediumTitles, - ), - ), - // TODO(Saif): display creator image - //ProposalCreator(_proposalDetailsModel.creator), - ...List.generate( - 2, - (index) => Padding( - padding: const EdgeInsets.only(top: 20), - child: ProposalPercentageIndicator( - index == 0 ? 'Commitment' : 'Token Mix Percentage', - index == 0 ? _proposalDetailsModel.commitmentToPercent() : _proposalDetailsModel.tokenMixToPercent(), - HyphaColors.lightBlue - ), - ), - ), - if (!(_proposalDetailsModel.cycleCount == null && _proposalDetailsModel.cycleStartDate == null)) ...[ - const SizedBox(height: 20), - Text( - 'Duration', - style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), - ), Padding( - padding: const EdgeInsets.symmetric(vertical: 10), + padding: const EdgeInsets.symmetric(vertical: 20), child: Text( - '${_proposalDetailsModel.cycleCount} Cycles', - style: context.hyphaTextTheme.reducedTitles, + _proposalDetailsModel.title ?? 'No title', + style: context.hyphaTextTheme.mediumTitles, ), ), + // TODO(Saif): display creator image + //ProposalCreator(_proposalDetailsModel.creator), ...List.generate( 2, - (index) => Text( - index == 0 ? 'Starting on ${_proposalDetailsModel.formatCycleStartDate()}' : - 'Ending on ${_proposalDetailsModel.cycleEndDate()}', - style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), + (index) => Padding( + padding: const EdgeInsets.only(top: 20), + child: ProposalPercentageIndicator( + index == 0 ? 'Commitment' : 'Token Mix Percentage', + index == 0 ? _proposalDetailsModel.commitmentToPercent() : _proposalDetailsModel.tokenMixToPercent(), + HyphaColors.lightBlue + ), ), ), - ], - const Padding( - padding: EdgeInsets.symmetric(vertical: 20), - child: HyphaDivider(), - ), - /// Rewards Section - if (_proposalDetailsModel.utilityAmount != null || _proposalDetailsModel.utilityAmountPerPeriod != null) ... [ - ValueListenableBuilder( - valueListenable: _isShownNotifier, - builder: (BuildContext context, bool? isShown, Widget? child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Reward for 1 cycle', - style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), - ), - ...List.generate( - 3, - (index) => Padding( - padding: const EdgeInsets.only(top: 10), - child: Row( - children: [ - DaoImage(_proposalDetailsModel.dao), - const SizedBox(width: 10), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _proposalDetailsModel.tokenTitle(index) ?? '', - style: context.hyphaTextTheme.reducedTitles, - overflow: TextOverflow.ellipsis, - ), - Text( - index == 0 ? 'Utility Token' : index == 1 ? 'Voice Token' : 'Cash Token', - style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - const SizedBox(width: 10), - Text( - _proposalDetailsModel.tokenValue(index, isShown ?? false) ?? '', - style: context.hyphaTextTheme.bigTitles.copyWith(fontWeight: FontWeight.normal), - ) - ], - ), - )), - const SizedBox(height: 10) - ], - ); - }, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - 'Show rewards for 1 cycle', - style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), - overflow: TextOverflow.ellipsis, - ), + if (!(_proposalDetailsModel.cycleCount == null && _proposalDetailsModel.cycleStartDate == null)) ...[ + const SizedBox(height: 20), + Text( + 'Duration', + style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + '${_proposalDetailsModel.cycleCount} Cycles', + style: context.hyphaTextTheme.reducedTitles, ), - const SizedBox(width: 10), - ValueListenableBuilder( - valueListenable: _isShownNotifier, - builder: (BuildContext context, bool? value, Widget? child) { - return _isShownNotifier.value != null ? Switch( - value: value!, - onChanged: (newValue) { - _isShownNotifier.value = newValue; - }, - ) : const SizedBox.shrink(); - }, + ), + ...List.generate( + 2, + (index) => Text( + index == 0 ? 'Starting on ${_proposalDetailsModel.formatCycleStartDate()}' : + 'Ending on ${_proposalDetailsModel.cycleEndDate()}', + style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), ), - ], - ), + ), + ], const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: HyphaDivider(), ), - ], - /// Details Section - if (_proposalDetailsModel.description != null) ... [ - Text( - 'Proposal Details', - style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), - ), - ValueListenableBuilder( - valueListenable: _isExpandedNotifier, - builder: (context, isExpanded, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(top: 10), - child: Text( - _proposalDetailsModel.description ?? '', - style: context.hyphaTextTheme.ralMediumBody, - maxLines: isExpanded ? null : 3, - overflow: isExpanded ? TextOverflow.visible : TextOverflow.ellipsis, + /// Rewards Section + if (_proposalDetailsModel.utilityAmount != null || + _proposalDetailsModel.utilityAmountPerPeriod != null || + _proposalDetailsModel.voiceAmount != null || + _proposalDetailsModel.voiceAmountPerPeriod != null || + _proposalDetailsModel.cashAmount != null || + _proposalDetailsModel.cashAmountPerPeriod != null) ...[ + ValueListenableBuilder( + valueListenable: _isShownNotifier, + builder: (BuildContext context, bool? isShown, Widget? child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _isShownNotifier.value?'Reward for 1 cycle':'Reward per period', + style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: Row( - children: [ - const Expanded(child: HyphaDivider()), - ValueListenableBuilder( - valueListenable: _isOverflowingNotifier, - builder: (context, isOverflowing, child) { - return isOverflowing - ? ProposalButton( - isExpanded ? 'Collapse' : 'Expand', - isExpanded - ? Icons.keyboard_arrow_up_outlined - : Icons.keyboard_arrow_down_outlined, - () => _isExpandedNotifier.value = !isExpanded, - ) - : const SizedBox.shrink(); - }, - ), - ], + + _buildTokenRow( + context, + _proposalDetailsModel, + 0, // Token index for Utility + 'Utility Token', + _isShownNotifier.value, + ), + _buildTokenRow( + context, + _proposalDetailsModel, + 1, // Token index for Voice + 'Voice Token', + _isShownNotifier.value, ), + _buildTokenRow( + context, + _proposalDetailsModel, + 2, // Token index for Cash + 'Cash Token', + _isShownNotifier.value, + ), + + const SizedBox(height: 10), + ], + ); + }, + ), + if (_proposalDetailsModel.cycleCount!=null)Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + 'Show rewards for 1 cycle', + style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), + overflow: TextOverflow.ellipsis, ), - ], - ); - }, + ), + const SizedBox(width: 10), + ValueListenableBuilder( + valueListenable: _isShownNotifier, + builder: (BuildContext context, bool? value, Widget? child) { + return Switch( + value: value!, + onChanged: (newValue) { + _isShownNotifier.value = newValue; + }, + ); + }, + ), + ], + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: HyphaDivider(), + ), + ], + /// Details Section + if (_proposalDetailsModel.description != null) ... [ + Text( + 'Proposal Details', + style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), + ), + ValueListenableBuilder( + valueListenable: _isExpandedNotifier, + builder: (context, isExpanded, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 10), + child: Text( + _proposalDetailsModel.description ?? '', + style: context.hyphaTextTheme.ralMediumBody, + maxLines: isExpanded ? null : 3, + overflow: isExpanded ? TextOverflow.visible : TextOverflow.ellipsis, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Row( + children: [ + const Expanded(child: HyphaDivider()), + ValueListenableBuilder( + valueListenable: _isOverflowingNotifier, + builder: (context, isOverflowing, child) { + return isOverflowing + ? ProposalButton( + isExpanded ? 'Collapse' : 'Expand', + isExpanded + ? Icons.keyboard_arrow_up_outlined + : Icons.keyboard_arrow_down_outlined, + () => _isExpandedNotifier.value = !isExpanded, + ) + : const SizedBox.shrink(); + }, + ), + ], + ), + ), + ], + ); + }, + ), + ], + /// Voting Scores Section + Text( + 'Voting Scores', + style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), ), - ], - /// Voting Scores Section - Text( - 'Voting Scores', - style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), - ), - if(passVoters.isNotEmpty) ... [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - '${passVoters.length} members voted Yes', - style: context.hyphaTextTheme.reducedTitles, + if(passVoters.isNotEmpty) ... [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + '${passVoters.length} members voted Yes', + style: context.hyphaTextTheme.reducedTitles, + ), + ), + ProposalVoters(passVoters) + ], + if(failVoters.isNotEmpty) ... [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + '${failVoters.length} members voted No', + style: context.hyphaTextTheme.reducedTitles, + ), + ), + ProposalVoters(failVoters) + ], + const SizedBox(height: 20), + ...List.generate( + 2, + (index) => Padding( + padding: const EdgeInsets.only(bottom: 20), + child: ProposalPercentageIndicator( + index == 0 ? 'Unity' : 'Quorum', + index == 0 ? _proposalDetailsModel.unityToPercent() : _proposalDetailsModel.quorumToPercent(), + _proposalDetailsModel.isPassing() ? HyphaColors.success : HyphaColors.error + ), ), ), - ProposalVoters(passVoters) - ], - if(failVoters.isNotEmpty) ... [ + const HyphaDivider(), + /// Expiration Timer Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - '${failVoters.length} members voted No', - style: context.hyphaTextTheme.reducedTitles, + padding: const EdgeInsets.symmetric(vertical: 20), + child: ProposalExpirationTimer( + _proposalDetailsModel.formatExpiration(), ), ), - ProposalVoters(failVoters) - ], - const SizedBox(height: 20), - ...List.generate( - 2, - (index) => Padding( - padding: const EdgeInsets.only(bottom: 20), - child: ProposalPercentageIndicator( - index == 0 ? 'Unity' : 'Quorum', - index == 0 ? _proposalDetailsModel.unityToPercent() : _proposalDetailsModel.quorumToPercent(), - _proposalDetailsModel.isPassing() ? HyphaColors.success : HyphaColors.error + const HyphaDivider(), + /// Vote Section + Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Text( + 'Cast your Vote', + style: context.hyphaTextTheme.smallTitles, ), ), - ), - const HyphaDivider(), - /// Expiration Timer - Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: ProposalExpirationTimer( - _proposalDetailsModel.formatExpiration(), - ), - ), - const HyphaDivider(), - /// Vote Section - Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: Text( - 'Cast your Vote', - style: context.hyphaTextTheme.smallTitles, - ), - ), - ...List.generate( - 3, - (index) => Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: HyphaAppButton( - title: index == 0 - ? 'Yes' - : index == 1 - ? 'Abstain' - : 'No', - onPressed: () async {}, - buttonType: ButtonType.danger, - buttonColor: index == 0 - ? HyphaColors.success - : index == 1 - ? HyphaColors.lightBlack - : HyphaColors.error, + ...List.generate( + 3, + (index) => Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: HyphaAppButton( + title: index == 0 + ? 'Yes' + : index == 1 + ? 'Abstain' + : 'No', + onPressed: () async {}, + buttonType: ButtonType.danger, + buttonColor: index == 0 + ? HyphaColors.success + : index == 1 + ? HyphaColors.lightBlack + : HyphaColors.error, + ), ), ), - ), - const SizedBox(height: 20), - ], - ), - ); - }); - }, + const SizedBox(height: 20), + ], + ), + ); + }); + }, + ), ), + ); + } +} + +Widget _buildTokenRow( + BuildContext context, + ProposalDetailsModel proposalDetailsModel, + int tokenIndex, + String tokenType, + bool? isShown) { + String? amount; + + // Determine the amount and amountPerPeriod based on token type + switch (tokenType) { + case 'Utility Token': + amount = proposalDetailsModel.tokenValue(0, isShown!); + break; + case 'Voice Token': + amount = proposalDetailsModel.tokenValue(1, isShown!); + break; + case 'Cash Token': + amount = proposalDetailsModel.tokenValue(2, isShown!); + break; + } + + if (amount != null) { + return Padding( + padding: const EdgeInsets.only(top: 10), + child: Row( + children: [ + DaoImage(proposalDetailsModel.dao), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + proposalDetailsModel.tokenTitle(tokenIndex) ?? '', + style: context.hyphaTextTheme.reducedTitles, + overflow: TextOverflow.ellipsis, + ), + Text( + tokenType, + style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + const SizedBox(width: 10), + Text( + amount, + style: context.hyphaTextTheme.bigTitles.copyWith(fontWeight: FontWeight.normal), + ), + ], ), ); } + + return const SizedBox.shrink(); // Return an empty widget if there's no data } + From 8fb5bfb88f09155148f210cefed2f775afd79bf1 Mon Sep 17 00:00:00 2001 From: Saif Nbet Date: Fri, 27 Sep 2024 00:05:24 +0100 Subject: [PATCH 06/10] chore: remove unnecessary code --- .../proposals/details/components/proposal_details_view.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/ui/proposals/details/components/proposal_details_view.dart b/lib/ui/proposals/details/components/proposal_details_view.dart index 771c32e9..d9a471a3 100644 --- a/lib/ui/proposals/details/components/proposal_details_view.dart +++ b/lib/ui/proposals/details/components/proposal_details_view.dart @@ -45,10 +45,6 @@ class _ProposalDetailsViewState extends State { } } - // void _initSwitchValue(ProposalDetailsModel proposalDetailsModel) { - // _isShownNotifier.value = proposalDetailsModel.utilityAmount != null; - // } - @override Widget build(BuildContext context) { return HyphaPageBackground( @@ -68,7 +64,6 @@ class _ProposalDetailsViewState extends State { _detailsNotifier.value = _proposalDetailsModel.description; WidgetsBinding.instance.addPostFrameCallback((_) { _checkIfTextIsOverflowing(); - // _initSwitchValue(_proposalDetailsModel); }); return Padding( padding: const EdgeInsets.symmetric(horizontal: 30), From 97391eae3ddda65d3ada9f8c8650366df5d9a503 Mon Sep 17 00:00:00 2001 From: NIK Date: Fri, 27 Sep 2024 11:34:46 +0800 Subject: [PATCH 07/10] add accessors some whitespace fixes made by the IDE formatter --- lib/core/extension/string_extension.dart | 1 + .../models/proposal_details_model.dart | 62 ++++++++++--------- 2 files changed, 35 insertions(+), 28 deletions(-) diff --git a/lib/core/extension/string_extension.dart b/lib/core/extension/string_extension.dart index 89684fd4..7d6b1ab5 100644 --- a/lib/core/extension/string_extension.dart +++ b/lib/core/extension/string_extension.dart @@ -4,6 +4,7 @@ extension NullableStringExtension on String? { extension StringExtension on String { // convert blockchain quantity to double, e.g. "1.0000 SEEDS" + // This parses a the value of type "Asset" and returns it as double double get quantityAsDouble { final List parts = split(' '); return double.parse(parts[0]); diff --git a/lib/core/network/models/proposal_details_model.dart b/lib/core/network/models/proposal_details_model.dart index e014297c..e06627a4 100644 --- a/lib/core/network/models/proposal_details_model.dart +++ b/lib/core/network/models/proposal_details_model.dart @@ -1,3 +1,4 @@ +import 'package:hypha_wallet/core/extension/string_extension.dart'; // Add this import import 'package:hypha_wallet/core/network/models/base_proposal_model.dart'; import 'package:hypha_wallet/core/network/models/dao_data_model.dart'; import 'package:hypha_wallet/core/network/models/vote_model.dart'; @@ -8,6 +9,13 @@ part 'proposal_details_model.g.dart'; @JsonSerializable() class ProposalDetailsModel extends BaseProposalModel { + double? get utilityAmountDouble => utilityAmount?.quantityAsDouble; + double? get voiceAmountDouble => voiceAmount?.quantityAsDouble; + double? get cashAmountDouble => cashAmount?.quantityAsDouble; + double? get utilityAmountPerPeriodDouble => utilityAmountPerPeriod?.quantityAsDouble; + double? get voiceAmountPerPeriodDouble => voiceAmountPerPeriod?.quantityAsDouble; + double? get cashAmountPerPeriodDouble => cashAmountPerPeriod?.quantityAsDouble; + @JsonKey(name: '__typename') final String type; @@ -44,37 +52,35 @@ class ProposalDetailsModel extends BaseProposalModel { @JsonKey(name: 'details_description_s') final String? description; - ProposalDetailsModel({ - required super.id, - required this.type, - required this.creationDate, - super.dao, - super.commitment, - super.title, - super.unity, - super.quorum, - super.expiration, - super.creator, - super.votes, - this.tokenMixPercentage, - this.cycleCount, - this.cycleStartDate, - this.utilityAmount, - this.voiceAmount, - this.cashAmount, - this.utilityAmountPerPeriod, - this.voiceAmountPerPeriod, - this.cashAmountPerPeriod, - this.description - }); + ProposalDetailsModel( + {required super.id, + required this.type, + required this.creationDate, + super.dao, + super.commitment, + super.title, + super.unity, + super.quorum, + super.expiration, + super.creator, + super.votes, + this.tokenMixPercentage, + this.cycleCount, + this.cycleStartDate, + this.utilityAmount, + this.voiceAmount, + this.cashAmount, + this.utilityAmountPerPeriod, + this.voiceAmountPerPeriod, + this.cashAmountPerPeriod, + this.description}); factory ProposalDetailsModel.fromJson(Map json) { - if(json['start'] is List){ - if((json['start'] as List).isNotEmpty){ + if (json['start'] is List) { + if ((json['start'] as List).isNotEmpty) { json['start'] = json['start'][0]['details_startTime_t']; - } - else{ - json['start']=null; + } else { + json['start'] = null; } } // TODO(Saif): check this From 4d80cd794cd8e7922d5ef17dca3ad284d676f6eb Mon Sep 17 00:00:00 2001 From: Zied Dahmani Date: Fri, 27 Sep 2024 11:32:00 +0100 Subject: [PATCH 08/10] fix: clean up post merge --- lib/core/di/di_setup.dart | 1 - lib/core/di/usecases_module.dart | 4 +--- .../proposals/history/interactor/proposals_history_bloc.dart | 3 ++- lib/ui/proposals/list/components/proposals_view.dart | 1 + 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/core/di/di_setup.dart b/lib/core/di/di_setup.dart index b4dbca63..aa26e071 100644 --- a/lib/core/di/di_setup.dart +++ b/lib/core/di/di_setup.dart @@ -76,7 +76,6 @@ 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/history/interactor/proposals_history_bloc.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'; diff --git a/lib/core/di/usecases_module.dart b/lib/core/di/usecases_module.dart index 585debdd..3f0daa6a 100644 --- a/lib/core/di/usecases_module.dart +++ b/lib/core/di/usecases_module.dart @@ -88,6 +88,4 @@ void _registerUseCasesModule() { _registerFactory(() => GetProposalDetailsUseCase(_getIt(), _getIt())); _registerFactory(() => AggregateDaoProposalCountsUseCase()); - - _registerFactory(() => GetDaosFromProposalCountsUseCase()); -} \ No newline at end of file +} diff --git a/lib/ui/proposals/history/interactor/proposals_history_bloc.dart b/lib/ui/proposals/history/interactor/proposals_history_bloc.dart index b0c85339..9268f0da 100644 --- a/lib/ui/proposals/history/interactor/proposals_history_bloc.dart +++ b/lib/ui/proposals/history/interactor/proposals_history_bloc.dart @@ -7,6 +7,7 @@ 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/proposals/filter/interactor/filter_status.dart'; +import 'package:hypha_wallet/ui/proposals/list/interactor/get_proposals_use_case_input.dart'; import 'package:hypha_wallet/ui/proposals/list/usecases/get_proposals_use_case.dart'; part 'proposals_history_bloc.freezed.dart'; @@ -27,7 +28,7 @@ class ProposalsHistoryBloc extends Bloc, HyphaError> proposalsResult = await _getProposalsUseCase.run([_dao], FilterStatus.past); + final Result, HyphaError> proposalsResult = await _getProposalsUseCase.run(GetProposalsUseCaseInput([_dao], FilterStatus.past)); if (proposalsResult.isValue) { emit(state.copyWith(pageState: PageState.success, proposals: proposalsResult.asValue!.value)); diff --git a/lib/ui/proposals/list/components/proposals_view.dart b/lib/ui/proposals/list/components/proposals_view.dart index ecbbf823..d911b295 100644 --- a/lib/ui/proposals/list/components/proposals_view.dart +++ b/lib/ui/proposals/list/components/proposals_view.dart @@ -12,6 +12,7 @@ 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/components/proposals_list.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/interactor/proposals_bloc.dart'; import 'package:hypha_wallet/ui/shared/hypha_body_widget.dart'; From e2863a7864944a90d1234b677bb15ec1097be3de Mon Sep 17 00:00:00 2001 From: Saif Nbet Date: Sat, 28 Sep 2024 17:15:23 +0100 Subject: [PATCH 09/10] refactor: Change the logic of token amount calculation --- .../proposal_details_model_extension.dart | 57 +- .../api/services/proposal_service.dart | 3 +- .../models/proposal_details_model.dart | 57 +- .../models/proposal_details_model.g.dart | 3 + .../components/proposal_details_view.dart | 619 ++++++++++-------- 5 files changed, 416 insertions(+), 323 deletions(-) diff --git a/lib/core/extension/proposal_details_model_extension.dart b/lib/core/extension/proposal_details_model_extension.dart index ea68a637..6db36603 100644 --- a/lib/core/extension/proposal_details_model_extension.dart +++ b/lib/core/extension/proposal_details_model_extension.dart @@ -43,42 +43,53 @@ extension ProposalDetailsModelExtension on ProposalDetailsModel { return null; } - String? tokenValue(int index, bool isOneCycleRewardsShown) { - String? input; - String? perPeriodInput; - + double? tokenValue(int index, bool isOneCycleRewardsShown) { + double? tokenAmount; + double? tokenAmountPerPeriod; + final int cycleDurationSec = 2629800; + final double periodsOnCycle = cycleDurationSec / periodDurationSec!; switch (index) { case 0: - input = utilityAmount; - perPeriodInput = utilityAmountPerPeriod; + tokenAmount = utilityAmountDouble; + tokenAmountPerPeriod = utilityAmountPerPeriodDouble; break; case 1: - input = voiceAmount; - perPeriodInput = voiceAmountPerPeriod; + tokenAmount = voiceAmountDouble; + tokenAmountPerPeriod = voiceAmountPerPeriodDouble; break; case 2: - input = cashAmount; - perPeriodInput = cashAmountPerPeriod; + tokenAmount = cashAmountDouble; + tokenAmountPerPeriod = cashAmountPerPeriodDouble; break; default: return null; } - final RegExp regExp = RegExp(r'(\d+(\.\d+)?)'); - if (input != null) { - final match = regExp.firstMatch(input); - if (isOneCycleRewardsShown || cycleCount == 1) return match?.group(1); - return (double.parse((match?.group(1))!) * cycleCount!) - .toStringAsFixed(3); + if (tokenAmount != null) { + if (isOneCycleRewardsShown) return tokenAmount; + return tokenAmount / periodsOnCycle; } - if (perPeriodInput != null) { - final match = regExp.firstMatch(perPeriodInput); - if (isOneCycleRewardsShown && cycleCount != 1) { - return (double.parse((match?.group(1))!) / cycleCount!) - .toStringAsFixed(3); + if (tokenAmountPerPeriod != null) { + if (isOneCycleRewardsShown) { + return tokenAmountPerPeriod * periodsOnCycle; } - return match?.group(1); + return tokenAmountPerPeriod; + } + return tokenAmount; + } +} + +extension TokenTypeExtension on TokenType { + String get name { + switch (this) { + case TokenType.utility: + return 'Utility Token'; + case TokenType.voice: + return 'Voice Token'; + case TokenType.cash: + return 'Cash Token'; + default: + return ''; } - return input; } } diff --git a/lib/core/network/api/services/proposal_service.dart b/lib/core/network/api/services/proposal_service.dart index 9642b500..6a829e41 100644 --- a/lib/core/network/api/services/proposal_service.dart +++ b/lib/core/network/api/services/proposal_service.dart @@ -23,8 +23,9 @@ class ProposalService { Future, HyphaError>> getProposalDetails( String proposalId, UserProfileData user) async { final String query = - '{"query":"query proposalDetails(\$docId: String!) { getDocument(docId: \$docId) {__typename docId creator createdDate ... on Votable {pass: voteAggregate(filter: { vote_vote_s: { regexp: \\"/.*pass*./\\" } }) {count} fail: voteAggregate(filter: { vote_vote_s: { regexp: \\"/.*fail*./\\" } }) {count} vote { ... on Vote { vote_voter_n vote_vote_s } } } ... on Edit {details_title_s details_description_s details_periodCount_i details_deferredPercX100_i ballot_expiration_t dao {settings {settings_daoTitle_s}} details_ballotAlignment_i details_ballotQuorum_i} ... on Queststart {details_title_s details_description_s details_periodCount_i details_deferredPercX100_i start {details_startTime_t} ballot_expiration_t dao {settings {settings_daoTitle_s}} details_ballotAlignment_i details_ballotQuorum_i details_pegAmount_a details_voiceAmount_a details_rewardAmount_a} ... on Questcomple {details_title_s details_description_s ballot_expiration_t dao {settings {settings_daoTitle_s}} details_pegAmount_a details_voiceAmount_a details_rewardAmount_a} ... on Policy {details_title_s details_description_s details_periodCount_i details_deferredPercX100_i ballot_expiration_t dao {settings {settings_daoTitle_s}} details_ballotAlignment_i details_ballotQuorum_i details_pegAmount_a details_voiceAmount_a details_rewardAmount_a} ... on Payout {details_title_s details_description_s details_periodCount_i details_deferredPercX100_i start {details_startTime_t} ballot_expiration_t dao {settings {settings_daoTitle_s}} details_ballotAlignment_i details_ballotQuorum_i details_pegAmount_a details_voiceAmount_a details_rewardAmount_a} ... on Badge {details_title_s details_description_s details_periodCount_i ballot_expiration_t dao {settings {settings_daoTitle_s}} details_ballotAlignment_i details_ballotQuorum_i} ... on Poll {details_title_s details_description_s ballot_expiration_t dao {settings {settings_daoTitle_s}} details_ballotAlignment_i details_ballotQuorum_i} ... on Budget {details_title_s details_description_s details_deferredPercX100_i ballot_expiration_t dao {settings {settings_daoTitle_s}} details_ballotAlignment_i details_ballotQuorum_i details_pegAmount_a details_voiceAmount_a details_rewardAmount_a} ... on Assignment {details_title_s details_description_s details_periodCount_i details_deferredPercX100_i start {details_startTime_t} details_rewardSalaryPerPeriod_a details_voiceSalaryPerPeriod_a details_pegSalaryPerPeriod_a ballot_expiration_t dao {settings {settings_daoTitle_s}} details_timeShareX100_i details_ballotAlignment_i details_ballotQuorum_i} } }", "variables":{"docId":"$proposalId"}}'; + '{"query":"query proposalDetails(\$docId: String!) { getDocument(docId: \$docId) {__typename docId creator createdDate ... on Votable {pass: voteAggregate(filter: { vote_vote_s: { regexp: \\"/.*pass*./\\" } }) {count} fail: voteAggregate(filter: { vote_vote_s: { regexp: \\"/.*fail*./\\" } }) {count} vote { ... on Vote { vote_voter_n vote_vote_s } } } ... on Edit {details_title_s details_description_s details_periodCount_i details_deferredPercX100_i ballot_expiration_t dao {settings {settings_daoTitle_s settings_periodDurationSec_i}} details_ballotAlignment_i details_ballotQuorum_i} ... on Queststart {details_title_s details_description_s details_periodCount_i details_deferredPercX100_i start {details_startTime_t} ballot_expiration_t dao {settings {settings_daoTitle_s settings_periodDurationSec_i}} details_ballotAlignment_i details_ballotQuorum_i details_pegAmount_a details_voiceAmount_a details_rewardAmount_a} ... on Questcomple {details_title_s details_description_s ballot_expiration_t dao {settings {settings_daoTitle_s settings_periodDurationSec_i}} details_pegAmount_a details_voiceAmount_a details_rewardAmount_a} ... on Policy {details_title_s details_description_s details_periodCount_i details_deferredPercX100_i ballot_expiration_t dao {settings {settings_daoTitle_s settings_periodDurationSec_i}} details_ballotAlignment_i details_ballotQuorum_i details_pegAmount_a details_voiceAmount_a details_rewardAmount_a} ... on Payout {details_title_s details_description_s details_periodCount_i details_deferredPercX100_i start {details_startTime_t} ballot_expiration_t dao {settings {settings_daoTitle_s settings_periodDurationSec_i}} details_ballotAlignment_i details_ballotQuorum_i details_pegAmount_a details_voiceAmount_a details_rewardAmount_a} ... on Badge {details_title_s details_description_s details_periodCount_i ballot_expiration_t dao {settings {settings_daoTitle_s settings_periodDurationSec_i}} details_ballotAlignment_i details_ballotQuorum_i} ... on Poll {details_title_s details_description_s ballot_expiration_t dao {settings {settings_daoTitle_s settings_periodDurationSec_i}} details_ballotAlignment_i details_ballotQuorum_i} ... on Budget {details_title_s details_description_s details_deferredPercX100_i ballot_expiration_t dao {settings {settings_daoTitle_s settings_periodDurationSec_i}} details_ballotAlignment_i details_ballotQuorum_i details_pegAmount_a details_voiceAmount_a details_rewardAmount_a} ... on Assignment {details_title_s details_description_s details_periodCount_i details_deferredPercX100_i start {details_startTime_t} details_rewardSalaryPerPeriod_a details_voiceSalaryPerPeriod_a details_pegSalaryPerPeriod_a ballot_expiration_t dao {settings {settings_daoTitle_s settings_periodDurationSec_i}} details_timeShareX100_i details_ballotAlignment_i details_ballotQuorum_i} } }", "variables":{"docId":"$proposalId"}}'; return _graphQLService.graphQLQuery(network: user.network, query: query); } + } \ No newline at end of file diff --git a/lib/core/network/models/proposal_details_model.dart b/lib/core/network/models/proposal_details_model.dart index e06627a4..60fe94e5 100644 --- a/lib/core/network/models/proposal_details_model.dart +++ b/lib/core/network/models/proposal_details_model.dart @@ -7,18 +7,35 @@ import 'package:json_annotation/json_annotation.dart'; part 'proposal_details_model.g.dart'; +enum TokenType { + utility, + voice, + cash, +} + @JsonSerializable() class ProposalDetailsModel extends BaseProposalModel { double? get utilityAmountDouble => utilityAmount?.quantityAsDouble; + double? get voiceAmountDouble => voiceAmount?.quantityAsDouble; + double? get cashAmountDouble => cashAmount?.quantityAsDouble; - double? get utilityAmountPerPeriodDouble => utilityAmountPerPeriod?.quantityAsDouble; - double? get voiceAmountPerPeriodDouble => voiceAmountPerPeriod?.quantityAsDouble; - double? get cashAmountPerPeriodDouble => cashAmountPerPeriod?.quantityAsDouble; + + double? get utilityAmountPerPeriodDouble => + utilityAmountPerPeriod?.quantityAsDouble; + + double? get voiceAmountPerPeriodDouble => + voiceAmountPerPeriod?.quantityAsDouble; + + double? get cashAmountPerPeriodDouble => + cashAmountPerPeriod?.quantityAsDouble; @JsonKey(name: '__typename') final String type; + @JsonKey(name: 'settings_periodDurationSec_i') + final int? periodDurationSec; + @JsonKey(name: 'createdDate') final DateTime creationDate; @@ -73,9 +90,11 @@ class ProposalDetailsModel extends BaseProposalModel { this.utilityAmountPerPeriod, this.voiceAmountPerPeriod, this.cashAmountPerPeriod, - this.description}); + this.description, + this.periodDurationSec}); factory ProposalDetailsModel.fromJson(Map json) { + // Handle the 'start' field if it's a list if (json['start'] is List) { if ((json['start'] as List).isNotEmpty) { json['start'] = json['start'][0]['details_startTime_t']; @@ -83,17 +102,31 @@ class ProposalDetailsModel extends BaseProposalModel { json['start'] = null; } } - // TODO(Saif): check this + if (json['dao'] is List) { + final daoList = json['dao']; + if (daoList.isNotEmpty && + daoList[0] is Map && + daoList[0]['settings'] is List) { + final settingsList = daoList[0]['settings'] as List; + if (settingsList.isNotEmpty && settingsList[0] is Map) { + json['settings_periodDurationSec_i'] = + settingsList[0]['settings_periodDurationSec_i']; + } + } + } json['dao'] = null; json['creator'] = null; - /*if(json['dao'] != null) { - json['dao'] = json['dao'][0]['settings'][0]['settings_daoTitle_s']; - }*/ return _$ProposalDetailsModelFromJson(json); } - List fetchVotersByStatus(VoteStatus voteStatus) => votes - ?.where((vote) => vote.voteStatus == voteStatus) - .map((vote) => vote) - .toList() ?? []; + List fetchVotersByStatus(VoteStatus voteStatus) => + votes + ?.where((vote) => vote.voteStatus == voteStatus) + .map((vote) => vote) + .toList() ?? + []; + + void printParentDao() { + print(super.dao); // Access the dao from the parent class + } } diff --git a/lib/core/network/models/proposal_details_model.g.dart b/lib/core/network/models/proposal_details_model.g.dart index c0bcf17f..a00287dc 100644 --- a/lib/core/network/models/proposal_details_model.g.dart +++ b/lib/core/network/models/proposal_details_model.g.dart @@ -41,6 +41,8 @@ ProposalDetailsModel _$ProposalDetailsModelFromJson( voiceAmountPerPeriod: json['details_voiceSalaryPerPeriod_a'] as String?, cashAmountPerPeriod: json['details_pegSalaryPerPeriod_a'] as String?, description: json['details_description_s'] as String?, + periodDurationSec: + (json['settings_periodDurationSec_i'] as num?)?.toInt(), ); Map _$ProposalDetailsModelToJson( @@ -56,6 +58,7 @@ Map _$ProposalDetailsModelToJson( 'creator': instance.creator, 'vote': instance.votes, '__typename': instance.type, + 'settings_periodDurationSec_i': instance.periodDurationSec, 'createdDate': instance.creationDate.toIso8601String(), 'details_deferredPercX100_i': instance.tokenMixPercentage, 'details_periodCount_i': instance.cycleCount, diff --git a/lib/ui/proposals/details/components/proposal_details_view.dart b/lib/ui/proposals/details/components/proposal_details_view.dart index d9a471a3..fcafb141 100644 --- a/lib/ui/proposals/details/components/proposal_details_view.dart +++ b/lib/ui/proposals/details/components/proposal_details_view.dart @@ -28,7 +28,7 @@ class ProposalDetailsView extends StatefulWidget { } class _ProposalDetailsViewState extends State { - final ValueNotifier _isShownNotifier = ValueNotifier(true); + final ValueNotifier _isRewardShownPerCycle = ValueNotifier(false); final ValueNotifier _isOverflowingNotifier = ValueNotifier(false); final ValueNotifier _isExpandedNotifier = ValueNotifier(false); final ValueNotifier _detailsNotifier = ValueNotifier(null); @@ -57,302 +57,346 @@ class _ProposalDetailsViewState extends State { ), body: BlocBuilder( builder: (context, state) { - return HyphaBodyWidget(pageState: state.pageState, success: (context) { - final ProposalDetailsModel _proposalDetailsModel = state.proposalDetailsModel!; - final List passVoters = _proposalDetailsModel.fetchVotersByStatus(VoteStatus.pass); - final List failVoters = _proposalDetailsModel.fetchVotersByStatus(VoteStatus.fail); - _detailsNotifier.value = _proposalDetailsModel.description; - WidgetsBinding.instance.addPostFrameCallback((_) { - _checkIfTextIsOverflowing(); - }); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 30), - child: ListView( - children: [ - const SizedBox(height: 20), - /// Header - ProposalHeader(_proposalDetailsModel.dao), - const Padding( - padding: EdgeInsets.only(top: 10, bottom: 20), - child: HyphaDivider(), - ), - /// Main Section - Wrap( - children: List.generate( - _proposalDetailsModel.commitment != null ? 3 : 2, + return HyphaBodyWidget( + pageState: state.pageState, + success: (context) { + final ProposalDetailsModel _proposalDetailsModel = + state.proposalDetailsModel!; + final List passVoters = _proposalDetailsModel + .fetchVotersByStatus(VoteStatus.pass); + final List failVoters = _proposalDetailsModel + .fetchVotersByStatus(VoteStatus.fail); + _detailsNotifier.value = _proposalDetailsModel.description; + WidgetsBinding.instance.addPostFrameCallback((_) { + _checkIfTextIsOverflowing(); + }); + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: ListView( + children: [ + const SizedBox(height: 20), + + /// Header + ProposalHeader(_proposalDetailsModel.dao), + const Padding( + padding: EdgeInsets.only(top: 10, bottom: 20), + child: HyphaDivider(), + ), + + /// Main Section + Wrap( + children: List.generate( + _proposalDetailsModel.commitment != null ? 3 : 2, (index) => Padding( - padding: const EdgeInsets.only(right: 10), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - border: Border.all( - color: context.isDarkMode - ? HyphaColors.white - : HyphaColors.lightBlack, + padding: const EdgeInsets.only(right: 10), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 4), + decoration: BoxDecoration( + border: Border.all( + color: context.isDarkMode + ? HyphaColors.white + : HyphaColors.lightBlack, + ), + borderRadius: BorderRadius.circular(20), + ), + // TODO(Zied-Saif): figure these out (B6 and Role) + child: Text( + index == 0 + ? 'Role ${_proposalDetailsModel.type}' + : index == 1 + ? 'B6' + : '${_proposalDetailsModel.commitment}%', + style: context.hyphaTextTheme.ralBold, + ), ), - borderRadius: BorderRadius.circular(20), - ), - // TODO(Zied-Saif): figure these out (B6 and Role) - child: Text( - index == 0 - ? 'Role ${_proposalDetailsModel.type}' - : index == 1 - ? 'B6' - : '${_proposalDetailsModel.commitment}%', - style: context.hyphaTextTheme.ralBold, ), ), ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: Text( - _proposalDetailsModel.title ?? 'No title', - style: context.hyphaTextTheme.mediumTitles, - ), - ), - // TODO(Saif): display creator image - //ProposalCreator(_proposalDetailsModel.creator), - ...List.generate( - 2, - (index) => Padding( - padding: const EdgeInsets.only(top: 20), - child: ProposalPercentageIndicator( - index == 0 ? 'Commitment' : 'Token Mix Percentage', - index == 0 ? _proposalDetailsModel.commitmentToPercent() : _proposalDetailsModel.tokenMixToPercent(), - HyphaColors.lightBlue - ), - ), - ), - if (!(_proposalDetailsModel.cycleCount == null && _proposalDetailsModel.cycleStartDate == null)) ...[ - const SizedBox(height: 20), - Text( - 'Duration', - style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), - ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - '${_proposalDetailsModel.cycleCount} Cycles', - style: context.hyphaTextTheme.reducedTitles, + Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Text( + _proposalDetailsModel.title ?? 'No title', + style: context.hyphaTextTheme.mediumTitles, + ), ), - ), - ...List.generate( - 2, - (index) => Text( - index == 0 ? 'Starting on ${_proposalDetailsModel.formatCycleStartDate()}' : - 'Ending on ${_proposalDetailsModel.cycleEndDate()}', - style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), + // TODO(Saif): display creator image + //ProposalCreator(_proposalDetailsModel.creator), + ...List.generate( + 2, + (index) => Padding( + padding: const EdgeInsets.only(top: 20), + child: ProposalPercentageIndicator( + index == 0 + ? 'Commitment' + : 'Token Mix Percentage', + index == 0 + ? _proposalDetailsModel + .commitmentToPercent() + : _proposalDetailsModel.tokenMixToPercent(), + HyphaColors.lightBlue), + ), ), - ), - ], - const Padding( - padding: EdgeInsets.symmetric(vertical: 20), - child: HyphaDivider(), - ), - /// Rewards Section - if (_proposalDetailsModel.utilityAmount != null || - _proposalDetailsModel.utilityAmountPerPeriod != null || - _proposalDetailsModel.voiceAmount != null || - _proposalDetailsModel.voiceAmountPerPeriod != null || - _proposalDetailsModel.cashAmount != null || - _proposalDetailsModel.cashAmountPerPeriod != null) ...[ - ValueListenableBuilder( - valueListenable: _isShownNotifier, - builder: (BuildContext context, bool? isShown, Widget? child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - _isShownNotifier.value?'Reward for 1 cycle':'Reward per period', - style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), - ), - - _buildTokenRow( - context, - _proposalDetailsModel, - 0, // Token index for Utility - 'Utility Token', - _isShownNotifier.value, - ), - _buildTokenRow( - context, - _proposalDetailsModel, - 1, // Token index for Voice - 'Voice Token', - _isShownNotifier.value, - ), - _buildTokenRow( - context, - _proposalDetailsModel, - 2, // Token index for Cash - 'Cash Token', - _isShownNotifier.value, - ), - - const SizedBox(height: 10), - ], - ); - }, - ), - if (_proposalDetailsModel.cycleCount!=null)Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( + if (!(_proposalDetailsModel.cycleCount == null && + _proposalDetailsModel.cycleStartDate == null)) ...[ + const SizedBox(height: 20), + Text( + 'Duration', + style: context.hyphaTextTheme.ralMediumSmallNote + .copyWith(color: HyphaColors.midGrey), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), child: Text( - 'Show rewards for 1 cycle', - style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), - overflow: TextOverflow.ellipsis, + '${_proposalDetailsModel.cycleCount} Cycles', + style: context.hyphaTextTheme.reducedTitles, + ), + ), + ...List.generate( + 2, + (index) => Text( + index == 0 + ? 'Starting on ${_proposalDetailsModel.formatCycleStartDate()}' + : 'Ending on ${_proposalDetailsModel.cycleEndDate()}', + style: context.hyphaTextTheme.ralMediumBody + .copyWith(color: HyphaColors.midGrey), ), ), - const SizedBox(width: 10), + ], + const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: HyphaDivider(), + ), + + /// Rewards Section + if (_proposalDetailsModel.utilityAmount != null || + _proposalDetailsModel.utilityAmountPerPeriod != + null || + _proposalDetailsModel.voiceAmount != null || + _proposalDetailsModel.voiceAmountPerPeriod != + null || + _proposalDetailsModel.cashAmount != null || + _proposalDetailsModel.cashAmountPerPeriod != + null) ...[ ValueListenableBuilder( - valueListenable: _isShownNotifier, - builder: (BuildContext context, bool? value, Widget? child) { - return Switch( - value: value!, - onChanged: (newValue) { - _isShownNotifier.value = newValue; - }, + valueListenable: _isRewardShownPerCycle, + builder: (BuildContext context, bool isShown, + Widget? child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _isRewardShownPerCycle.value + ? 'Reward for one cycle' + : 'Reward for one period', + style: context + .hyphaTextTheme.ralMediumSmallNote + .copyWith(color: HyphaColors.midGrey), + ), + _buildTokenRow( + context, + _proposalDetailsModel, + 0, // Token index for Utility + TokenType.utility, + _isRewardShownPerCycle.value, + ), + _buildTokenRow( + context, + _proposalDetailsModel, + 1, // Token index for Voice + TokenType.voice, + _isRewardShownPerCycle.value, + ), + _buildTokenRow( + context, + _proposalDetailsModel, + 2, // Token index for Cash + TokenType.cash, + _isRewardShownPerCycle.value, + ), + const SizedBox(height: 10), + ], ); }, ), - ], - ), - const Padding( - padding: EdgeInsets.symmetric(vertical: 20), - child: HyphaDivider(), - ), - ], - /// Details Section - if (_proposalDetailsModel.description != null) ... [ - Text( - 'Proposal Details', - style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), - ), - ValueListenableBuilder( - valueListenable: _isExpandedNotifier, - builder: (context, isExpanded, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Padding( - padding: const EdgeInsets.only(top: 10), + Expanded( child: Text( - _proposalDetailsModel.description ?? '', - style: context.hyphaTextTheme.ralMediumBody, - maxLines: isExpanded ? null : 3, - overflow: isExpanded ? TextOverflow.visible : TextOverflow.ellipsis, + 'Show rewards for 1 cycle', + style: context.hyphaTextTheme.ralMediumBody + .copyWith(color: HyphaColors.midGrey), + overflow: TextOverflow.ellipsis, ), ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: Row( - children: [ - const Expanded(child: HyphaDivider()), - ValueListenableBuilder( - valueListenable: _isOverflowingNotifier, - builder: (context, isOverflowing, child) { - return isOverflowing - ? ProposalButton( - isExpanded ? 'Collapse' : 'Expand', - isExpanded - ? Icons.keyboard_arrow_up_outlined - : Icons.keyboard_arrow_down_outlined, - () => _isExpandedNotifier.value = !isExpanded, - ) - : const SizedBox.shrink(); - }, - ), - ], - ), + const SizedBox(width: 10), + ValueListenableBuilder( + valueListenable: _isRewardShownPerCycle, + builder: (BuildContext context, bool? value, + Widget? child) { + return Switch( + value: value!, + onChanged: (newValue) { + _isRewardShownPerCycle.value = newValue; + }, + ); + }, ), ], - ); - }, - ), - ], - /// Voting Scores Section - Text( - 'Voting Scores', - style: context.hyphaTextTheme.ralMediumSmallNote.copyWith(color: HyphaColors.midGrey), - ), - if(passVoters.isNotEmpty) ... [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - '${passVoters.length} members voted Yes', - style: context.hyphaTextTheme.reducedTitles, - ), - ), - ProposalVoters(passVoters) - ], - if(failVoters.isNotEmpty) ... [ - Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: Text( - '${failVoters.length} members voted No', - style: context.hyphaTextTheme.reducedTitles, + ), + const Padding( + padding: EdgeInsets.symmetric(vertical: 20), + child: HyphaDivider(), + ), + ], + + /// Details Section + if (_proposalDetailsModel.description != null) ...[ + Text( + 'Proposal Details', + style: context.hyphaTextTheme.ralMediumSmallNote + .copyWith(color: HyphaColors.midGrey), + ), + ValueListenableBuilder( + valueListenable: _isExpandedNotifier, + builder: (context, isExpanded, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(top: 10), + child: Text( + _proposalDetailsModel.description ?? '', + style: + context.hyphaTextTheme.ralMediumBody, + maxLines: isExpanded ? null : 3, + overflow: isExpanded + ? TextOverflow.visible + : TextOverflow.ellipsis, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 20), + child: Row( + children: [ + const Expanded(child: HyphaDivider()), + ValueListenableBuilder( + valueListenable: + _isOverflowingNotifier, + builder: + (context, isOverflowing, child) { + return isOverflowing + ? ProposalButton( + isExpanded + ? 'Collapse' + : 'Expand', + isExpanded + ? Icons + .keyboard_arrow_up_outlined + : Icons + .keyboard_arrow_down_outlined, + () => _isExpandedNotifier + .value = !isExpanded, + ) + : const SizedBox.shrink(); + }, + ), + ], + ), + ), + ], + ); + }, + ), + ], + + /// Voting Scores Section + Text( + 'Voting Scores', + style: context.hyphaTextTheme.ralMediumSmallNote + .copyWith(color: HyphaColors.midGrey), ), - ), - ProposalVoters(failVoters) - ], - const SizedBox(height: 20), - ...List.generate( - 2, + if (passVoters.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + '${passVoters.length} members voted Yes', + style: context.hyphaTextTheme.reducedTitles, + ), + ), + ProposalVoters(passVoters) + ], + if (failVoters.isNotEmpty) ...[ + Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Text( + '${failVoters.length} members voted No', + style: context.hyphaTextTheme.reducedTitles, + ), + ), + ProposalVoters(failVoters) + ], + const SizedBox(height: 20), + ...List.generate( + 2, (index) => Padding( - padding: const EdgeInsets.only(bottom: 20), - child: ProposalPercentageIndicator( - index == 0 ? 'Unity' : 'Quorum', - index == 0 ? _proposalDetailsModel.unityToPercent() : _proposalDetailsModel.quorumToPercent(), - _proposalDetailsModel.isPassing() ? HyphaColors.success : HyphaColors.error + padding: const EdgeInsets.only(bottom: 20), + child: ProposalPercentageIndicator( + index == 0 ? 'Unity' : 'Quorum', + index == 0 + ? _proposalDetailsModel.unityToPercent() + : _proposalDetailsModel.quorumToPercent(), + _proposalDetailsModel.isPassing() + ? HyphaColors.success + : HyphaColors.error), + ), ), - ), - ), - const HyphaDivider(), - /// Expiration Timer - Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: ProposalExpirationTimer( - _proposalDetailsModel.formatExpiration(), - ), - ), - const HyphaDivider(), - /// Vote Section - Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: Text( - 'Cast your Vote', - style: context.hyphaTextTheme.smallTitles, - ), - ), - ...List.generate( - 3, - (index) => Padding( - padding: const EdgeInsets.symmetric(vertical: 10), - child: HyphaAppButton( - title: index == 0 - ? 'Yes' - : index == 1 - ? 'Abstain' - : 'No', - onPressed: () async {}, - buttonType: ButtonType.danger, - buttonColor: index == 0 - ? HyphaColors.success - : index == 1 - ? HyphaColors.lightBlack - : HyphaColors.error, + const HyphaDivider(), + + /// Expiration Timer + Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: ProposalExpirationTimer( + _proposalDetailsModel.formatExpiration(), + ), + ), + const HyphaDivider(), + /// Vote Section + Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Text( + 'Cast your Vote', + style: context.hyphaTextTheme.smallTitles, + ), + ), + ...List.generate( + 3, + (index) => Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: HyphaAppButton( + title: index == 0 + ? 'Yes' + : index == 1 + ? 'Abstain' + : 'No', + onPressed: () async {}, + buttonType: ButtonType.danger, + buttonColor: index == 0 + ? HyphaColors.success + : index == 1 + ? HyphaColors.lightBlack + : HyphaColors.error, + ), + ), ), - ), + const SizedBox(height: 20), + ], ), - const SizedBox(height: 20), - ], - ), - ); - }); + ); + }); }, ), ), @@ -364,24 +408,24 @@ Widget _buildTokenRow( BuildContext context, ProposalDetailsModel proposalDetailsModel, int tokenIndex, - String tokenType, - bool? isShown) { - String? amount; + TokenType tokenType, + bool isRewardShownPerCycle) { + double? tokenAmount; // Determine the amount and amountPerPeriod based on token type switch (tokenType) { - case 'Utility Token': - amount = proposalDetailsModel.tokenValue(0, isShown!); + case TokenType.utility: + tokenAmount = proposalDetailsModel.tokenValue(0, isRewardShownPerCycle); break; - case 'Voice Token': - amount = proposalDetailsModel.tokenValue(1, isShown!); + case TokenType.voice: + tokenAmount = proposalDetailsModel.tokenValue(1, isRewardShownPerCycle); break; - case 'Cash Token': - amount = proposalDetailsModel.tokenValue(2, isShown!); + case TokenType.cash: + tokenAmount = proposalDetailsModel.tokenValue(2, isRewardShownPerCycle); break; } - if (amount != null) { + if (tokenAmount != null) { return Padding( padding: const EdgeInsets.only(top: 10), child: Row( @@ -398,8 +442,9 @@ Widget _buildTokenRow( overflow: TextOverflow.ellipsis, ), Text( - tokenType, - style: context.hyphaTextTheme.ralMediumBody.copyWith(color: HyphaColors.midGrey), + tokenType.name, + style: context.hyphaTextTheme.ralMediumBody + .copyWith(color: HyphaColors.midGrey), overflow: TextOverflow.ellipsis, ), ], @@ -407,14 +452,14 @@ Widget _buildTokenRow( ), const SizedBox(width: 10), Text( - amount, - style: context.hyphaTextTheme.bigTitles.copyWith(fontWeight: FontWeight.normal), + tokenAmount.toStringAsFixed(2), + style: context.hyphaTextTheme.bigTitles + .copyWith(fontWeight: FontWeight.normal), ), ], ), ); } - return const SizedBox.shrink(); // Return an empty widget if there's no data + return const SizedBox.shrink(); } - From 245adced16931db49a6b32c5040c1f3ccadd6d1b Mon Sep 17 00:00:00 2001 From: Saif Nbet Date: Sat, 28 Sep 2024 17:18:55 +0100 Subject: [PATCH 10/10] chore: remove unnecessary code --- lib/core/network/models/proposal_details_model.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/core/network/models/proposal_details_model.dart b/lib/core/network/models/proposal_details_model.dart index 60fe94e5..1a708a85 100644 --- a/lib/core/network/models/proposal_details_model.dart +++ b/lib/core/network/models/proposal_details_model.dart @@ -125,8 +125,4 @@ class ProposalDetailsModel extends BaseProposalModel { .map((vote) => vote) .toList() ?? []; - - void printParentDao() { - print(super.dao); // Access the dao from the parent class - } }