From e2863a7864944a90d1234b677bb15ec1097be3de Mon Sep 17 00:00:00 2001 From: Saif Nbet Date: Sat, 28 Sep 2024 17:15:23 +0100 Subject: [PATCH] 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(); } -