Skip to content

Commit

Permalink
Merge pull request #343 from hypha-dao/feat/handle-vote-casting
Browse files Browse the repository at this point in the history
feat: handle vote casting
  • Loading branch information
nbetsaif authored Oct 10, 2024
2 parents 335e4fd + c2634f6 commit 6429177
Show file tree
Hide file tree
Showing 10 changed files with 327 additions and 50 deletions.
2 changes: 1 addition & 1 deletion lib/core/di/repositories_module.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ void _registerRepositoriesModule() {

_registerLazySingleton(() => TransactionHistoryRepository(service: _getIt<TransactionHistoryService>()));

_registerLazySingleton(() => ProposalRepository(_getIt<DaoService>(),_getIt<ProposalService>(),_getIt<ProfileService>()));
_registerLazySingleton(() => ProposalRepository(_getIt<RemoteConfigService>(),_getIt<EOSService>(),_getIt<DaoService>(),_getIt<ProposalService>(),_getIt<ProfileService>()));
}
15 changes: 5 additions & 10 deletions lib/core/network/api/actions/vote_action_factory.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import 'package:hypha_wallet/core/crypto/seeds_esr/eos_action.dart';
import 'package:hypha_wallet/core/network/models/vote_model.dart';

class Voteactionfactory {
class VoteActionFactory {
// Vote action
// Note: Don't hard-code the dao contract since it's different on different networks
// get daoContract by calling
// final daoContract = remoteConfigService.daoContract(network: network);
//
static EOSAction voteAction(String daoContract, String voter, int proposalId, String vote) {
if (vote != 'pass' && vote != 'fail') {
throw 'vote needs to be one of pass or fail';
}
static EOSAction voteAction(String daoContract, String voter, String proposalId, VoteStatus vote) {
return EOSAction()
..account = daoContract
..name = 'vote'
..data = {
'voter': voter,
'proposal_id': proposalId,
'vote': vote,
'vote': vote.name,
'notes': ''
};
}
}
30 changes: 30 additions & 0 deletions lib/core/network/repository/proposal_repository.dart
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import 'package:hypha_wallet/core/error_handler/model/hypha_error.dart';
import 'package:hypha_wallet/core/extension/base_proposal_model_extension.dart';
import 'package:hypha_wallet/core/logging/log_helper.dart';
import 'package:hypha_wallet/core/network/api/actions/vote_action_factory.dart';
import 'package:hypha_wallet/core/network/api/services/dao_service.dart';
import 'package:hypha_wallet/core/network/api/services/proposal_service.dart';
import 'package:hypha_wallet/core/network/api/services/remote_config_service.dart';
import 'package:hypha_wallet/core/network/models/dao_data_model.dart';
import 'package:hypha_wallet/core/network/models/dao_proposals_model.dart';
import 'package:hypha_wallet/core/network/models/network.dart';
import 'package:hypha_wallet/core/network/models/proposal_details_model.dart';
import 'package:hypha_wallet/core/network/models/proposal_model.dart';
import 'package:hypha_wallet/core/network/models/user_profile_data.dart';
import 'package:hypha_wallet/core/network/models/vote_model.dart';
import 'package:hypha_wallet/core/network/repository/profile_repository.dart';
import 'package:hypha_wallet/ui/architecture/result/result.dart';
import 'package:hypha_wallet/ui/profile/interactor/profile_data.dart';
import 'package:hypha_wallet/ui/proposals/filter/interactor/filter_status.dart';
import 'package:hypha_wallet/ui/proposals/list/interactor/get_proposals_use_case_input.dart';

import '../../crypto/seeds_esr/eos_action.dart';
import '../api/eos_service.dart';

class ProposalRepository {
final ProposalService _proposalService;
final ProfileService _profileService;
final DaoService _daoService;
final EOSService _eosService;
final RemoteConfigService _remoteConfigService;



ProposalRepository(
this._remoteConfigService,
this._eosService,
this._daoService,
this._proposalService, this._profileService);

Expand Down Expand Up @@ -213,4 +223,24 @@ class ProposalRepository {
return Result.error(result.asError!.error);
}
}

Future<Result<String, HyphaError>> castVote(
String proposalId,
VoteStatus vote,
UserProfileData user
) async {
// Get the DAO contract for the user's network
final String daoContract = _remoteConfigService.daoContract(network: user.network);
// Create the EOS action for casting the vote
final EOSAction eosAction = VoteActionFactory.voteAction(daoContract, user.accountName, proposalId, vote);

try {
// Execute the action using EOS service and get the result
final castVoteResult = await _eosService.runAction(signer: user, action: eosAction);
return Result.value(castVoteResult.asValue!.value);
} catch (e, stackTrace) {
LogHelper.e('Error casting vote', error: e, stacktrace: stackTrace);
return Result.error(HyphaError.generic('Failed to cast vote'));
}
}
}
1 change: 0 additions & 1 deletion lib/ui/profile/usecases/fetch_profile_use_case.dart
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ class FetchProfileUseCase {

final Result<List<DaoData>, HyphaError> daosResult = futureResults.first as Result<List<DaoData>, HyphaError>;
final Result<ProfileData, HyphaError> profileResult = futureResults.last as Result<ProfileData, HyphaError>;

if (profileResult.isValue && daosResult.isValue) {
var profile = profileResult.asValue!.value;
profile = profile.updateDaos(daosResult.asValue!.value);
Expand Down
126 changes: 98 additions & 28 deletions lib/ui/proposals/details/components/proposal_details_view.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:get/get_utils/src/extensions/context_extensions.dart';
import 'package:get/get.dart';
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';
Expand All @@ -12,6 +12,7 @@ import 'package:hypha_wallet/design/dao_image.dart';
import 'package:hypha_wallet/design/dividers/hypha_divider.dart';
import 'package:hypha_wallet/design/hypha_colors.dart';
import 'package:hypha_wallet/design/themes/extensions/theme_extension_provider.dart';
import 'package:hypha_wallet/ui/blocs/authentication/authentication_bloc.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';
Expand All @@ -33,6 +34,7 @@ class _ProposalDetailsViewState extends State<ProposalDetailsView> {
final ValueNotifier<bool> _isOverflowingNotifier = ValueNotifier<bool>(false);
final ValueNotifier<bool> _isExpandedNotifier = ValueNotifier<bool>(false);
final ValueNotifier<String?> _detailsNotifier = ValueNotifier<String?>(null);
final ValueNotifier<bool> _changeVoteNotifier = ValueNotifier<bool>(false);

void _checkIfTextIsOverflowing() {
final TextPainter textPainter = TextPainter(
Expand All @@ -46,6 +48,15 @@ class _ProposalDetailsViewState extends State<ProposalDetailsView> {
}
}

VoteModel? _userVote(BuildContext context, List<VoteModel>? voters) {
final userProfileData =
context.read<AuthenticationBloc>().state.userProfileData;
final myVoteIndex = voters?.indexWhere(
(element) => element.voter == userProfileData?.accountName);
if (myVoteIndex == null || myVoteIndex == -1 || voters == null) return null;
return voters[myVoteIndex];
}

@override
Widget build(BuildContext context) {
return HyphaPageBackground(
Expand All @@ -61,6 +72,8 @@ class _ProposalDetailsViewState extends State<ProposalDetailsView> {
return HyphaBodyWidget(
pageState: state.pageState,
success: (context) {
final VoteModel? userVote =
_userVote(context, state.proposalDetailsModel!.votes);
final ProposalDetailsModel _proposalDetailsModel =
state.proposalDetailsModel!;
final List<VoteModel> passVoters = _proposalDetailsModel
Expand Down Expand Up @@ -362,36 +375,50 @@ class _ProposalDetailsViewState extends State<ProposalDetailsView> {
_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,
),
if (_proposalDetailsModel.formatExpiration() !=
'Expired')
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const HyphaDivider(),
Padding(
padding:
const EdgeInsets.symmetric(vertical: 20),
child: Text(
'Cast your Vote',
style: context.hyphaTextTheme.smallTitles,
),
),
ValueListenableBuilder<bool>(
valueListenable: _changeVoteNotifier,
builder: (context, isChangingVote, child) =>
userVote != null && isChangingVote == false
? HyphaAppButton(
onPressed: () {
_changeVoteNotifier.value = true;
},
buttonType: ButtonType.danger,
buttonColor: {
VoteStatus.pass:
HyphaColors.success,
VoteStatus.fail:
HyphaColors.error,
}[userVote.voteStatus] ??
HyphaColors.lightBlack,
title: {
VoteStatus.pass:
'You Voted Yes',
VoteStatus.fail:
'You Voted No',
}[userVote.voteStatus] ??
'You chose to abstain',
)
: _buildVoteWidget(context),
),
],
),
),
const SizedBox(height: 20),
],
),
Expand All @@ -404,6 +431,49 @@ class _ProposalDetailsViewState extends State<ProposalDetailsView> {
}
}

Widget _buildVoteWidget(BuildContext context) => Column(
children: List.generate(
3,
(index) => Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: HyphaAppButton(
title: index == 0
? 'Yes'
: index == 1
? 'Abstain'
: 'No',
onPressed: () async {
late final VoteStatus voteStatus;

// Determine the vote status based on the index
switch (index) {
case 0:
voteStatus = VoteStatus.pass;
break;
case 1:
voteStatus = VoteStatus.abstain;
break;
default:
voteStatus = VoteStatus.fail;
break;
}

// Get the bloc instances
final proposalDetailBloc = context.read<ProposalDetailBloc>();
// Dispatch the castVote event
proposalDetailBloc.add(ProposalDetailEvent.castVote(voteStatus));
},
buttonType: ButtonType.danger,
buttonColor: index == 0
? HyphaColors.success
: index == 1
? HyphaColors.lightBlack
: HyphaColors.error,
),
),
),
);

Widget _buildTokenRow(
BuildContext context,
ProposalDetailsModel proposalDetailsModel,
Expand Down
39 changes: 34 additions & 5 deletions lib/ui/proposals/details/interactor/proposal_detail_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,32 +1,61 @@
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:get/get.dart' as Get;
import 'package:get_it/get_it.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/proposal_details_model.dart';
import 'package:hypha_wallet/core/network/models/vote_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/details/usecases/get_proposal_details_use_case.dart';
import 'package:hypha_wallet/ui/proposals/list/interactor/proposals_bloc.dart';

part 'proposal_detail_bloc.freezed.dart';

part 'proposal_detail_event.dart';

part 'proposal_detail_state.dart';

class ProposalDetailBloc extends Bloc<ProposalDetailEvent, ProposalDetailState> {
class ProposalDetailBloc
extends Bloc<ProposalDetailEvent, ProposalDetailState> {
final GetProposalDetailsUseCase _getProposalDetailsUseCase;
final ErrorHandlerManager _errorHandlerManager;
final String _proposalId;

ProposalDetailBloc(this._proposalId,this._getProposalDetailsUseCase, this._errorHandlerManager) : super(const ProposalDetailState()) {
ProposalDetailBloc(this._proposalId, this._getProposalDetailsUseCase,
this._errorHandlerManager)
: super(const ProposalDetailState()) {
on<_Initial>(_initial);
on<_CastVote>(_castVote);
}

Future<void> _initial(_Initial event, Emitter<ProposalDetailState> emit) async {
Future<void> _initial(
_Initial event, Emitter<ProposalDetailState> emit) async {
emit(state.copyWith(pageState: PageState.loading));

final Result<ProposalDetailsModel, HyphaError> result = await _getProposalDetailsUseCase.run(_proposalId);
final Result<ProposalDetailsModel, HyphaError> result =
await _getProposalDetailsUseCase.run(_proposalId);
if (result.isValue) {
emit(state.copyWith(
pageState: PageState.success,
proposalDetailsModel: result.asValue!.value));
} else {
await _errorHandlerManager.handlerError(result.asError!.error);
emit(state.copyWith(pageState: PageState.failure));
}
}

Future<void> _castVote(
_CastVote event, Emitter<ProposalDetailState> emit) async {
emit(state.copyWith(pageState: PageState.loading));
final Result<String, HyphaError> result =
await _getProposalDetailsUseCase.castVote(_proposalId, event.vote);
if (result.isValue) {
emit(state.copyWith(pageState: PageState.success, proposalDetailsModel: result.asValue!.value));
GetIt.I.get<ProposalsBloc>().add(const ProposalsEvent.initial());
Get.Get.back();
emit(state.copyWith(pageState: PageState.success));
} else {
await _errorHandlerManager.handlerError(result.asError!.error);
emit(state.copyWith(pageState: PageState.failure));
Expand Down
Loading

0 comments on commit 6429177

Please sign in to comment.