Skip to content

Commit

Permalink
feat(cat-voices): wallet link transaction (#976)
Browse files Browse the repository at this point in the history
* refactor(cat-voices): migrate apple-mobile-web-app-capable to mobile-web-app-capable

* chore: add missing catalyst compression config

* chore: add word to dictionary

* feat: add loader to submit transaction button

* feat: add transaction config repository

* feat: add registration transaction builder

* feat: submit registration

* feat: update cryptocurrency formatter

* feat: handle submit tx error

* feat: move to next step after submitting a registration

* feat: improve error handling for submitting a transaction

* chore: typo

* chore: review feedback

* feat: localized exception
  • Loading branch information
dtscalac authored Oct 9, 2024
1 parent d9cb81e commit 73807b4
Show file tree
Hide file tree
Showing 30 changed files with 737 additions and 67 deletions.
1 change: 1 addition & 0 deletions .config/dictionaries/project.dic
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ Jörmungandr
junitreport
junitxml
Keyhash
keychains
keyserver
keyspace
KUBECONFIG
Expand Down
8 changes: 7 additions & 1 deletion catalyst_voices/lib/dependency/dependencies.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ final class Dependencies extends DependencyProvider {
..registerLazySingleton<SessionBloc>(SessionBloc.new)
// Factory will rebuild it each time needed
..registerFactory<RegistrationCubit>(() {
return RegistrationCubit(downloader: get());
return RegistrationCubit(
downloader: get(),
transactionConfigRepository: get(),
);
});
}

Expand All @@ -41,6 +44,9 @@ final class Dependencies extends DependencyProvider {
)
..registerSingleton<AuthenticationRepository>(
AuthenticationRepository(credentialsStorageRepository: get()),
)
..registerSingleton<TransactionConfigRepository>(
TransactionConfigRepository(),
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class BlocRegistrationBuilder<T>
extends BlocSelector<RegistrationCubit, RegistrationState, T> {
BlocRegistrationBuilder({
super.key,
required BlocWidgetSelector<RegistrationStateData, T> selector,
required super.builder,
super.bloc,
}) : super(
selector: (state) {
return selector(state.registrationStateData);
},
);
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
import 'dart:async';

import 'package:catalyst_cardano_serialization/catalyst_cardano_serialization.dart';
import 'package:catalyst_voices/common/ext/account_role_ext.dart';
import 'package:catalyst_voices/pages/registration/wallet_link/bloc_wallet_link_builder.dart';
import 'package:catalyst_voices/pages/registration/bloc_registration_builder.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_brands/catalyst_voices_brands.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:catalyst_voices_models/catalyst_voices_models.dart';
import 'package:catalyst_voices_shared/catalyst_voices_shared.dart';
import 'package:catalyst_voices_view_models/catalyst_voices_view_models.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:result_type/result_type.dart';

class RbacTransactionPanel extends StatelessWidget {
const RbacTransactionPanel({
super.key,
});
class RbacTransactionPanel extends StatefulWidget {
const RbacTransactionPanel({super.key});

@override
State<RbacTransactionPanel> createState() => _RbacTransactionPanelState();
}

class _RbacTransactionPanelState extends State<RbacTransactionPanel> {
@override
void initState() {
super.initState();
unawaited(RegistrationCubit.of(context).prepareRegistration());
}

@override
Widget build(BuildContext context) {
Expand All @@ -26,37 +40,82 @@ class RbacTransactionPanel extends StatelessWidget {
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
const _BlocSummary(),
const SizedBox(height: 18),
const _PositiveSmallPrint(),
const Spacer(),
Expanded(
child: _BlocTransactionDetails(onRefreshTap: _onRefresh),
),
const _Navigation(),
],
);
}

void _onRefresh() {
unawaited(RegistrationCubit.of(context).prepareRegistration());
}
}

class _BlocTransactionDetails extends StatelessWidget {
final VoidCallback onRefreshTap;

const _BlocTransactionDetails({required this.onRefreshTap});

@override
Widget build(BuildContext context) {
return BlocRegistrationBuilder(
selector: (state) => state.unsignedTx,
builder: (context, result) {
return switch (result) {
Success() => const _TransactionDetails(),
Failure(:final value) => _Error(error: value, onRetry: onRefreshTap),
_ => const Center(child: VoicesCircularProgressIndicator()),
};
},
);
}
}

class _TransactionDetails extends StatelessWidget {
const _TransactionDetails();

@override
Widget build(BuildContext context) {
return const Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_BlocSummary(),
SizedBox(height: 18),
_PositiveSmallPrint(),
_BlocTxSubmitError(),
],
);
}
}

class _BlocSummary extends StatelessWidget {
const _BlocSummary();

@override
Widget build(BuildContext context) {
return BlocWalletLinkBuilder<
return BlocSelector<
RegistrationCubit,
RegistrationState,
({
Set<AccountRole> roles,
CardanoWalletDetails selectedWallet,
Coin transactionFee,
})?>(
selector: (state) {
final selectedWallet = state.selectedWallet;
if (selectedWallet == null) {
final selectedWallet = state.walletLinkStateData.selectedWallet;
final transactionFee = state.registrationStateData.transactionFee;
final selectedRoles = state.walletLinkStateData.selectedRoles;
final defaultRoles = state.walletLinkStateData.defaultRoles;
if (selectedWallet == null || transactionFee == null) {
return null;
}

return (
roles: state.selectedRoles ?? state.defaultRoles,
roles: selectedRoles ?? defaultRoles,
selectedWallet: selectedWallet,
transactionFee: state.transactionFee,
transactionFee: transactionFee,
);
},
builder: (context, state) {
Expand Down Expand Up @@ -130,7 +189,7 @@ class _Summary extends StatelessWidget {
?.copyWith(fontWeight: FontWeight.bold),
),
Text(
CryptocurrencyFormatter.formatAmount(transactionFee),
CryptocurrencyFormatter.formatExactAmount(transactionFee),
style: Theme.of(context).textTheme.bodySmall,
),
],
Expand Down Expand Up @@ -173,6 +232,55 @@ class _PositiveSmallPrint extends StatelessWidget {
}
}

class _BlocTxSubmitError extends StatelessWidget {
const _BlocTxSubmitError();

@override
Widget build(BuildContext context) {
return BlocRegistrationBuilder(
selector: (state) => state.submittedTx,
builder: (context, result) {
return switch (result) {
Failure(:final value) => _Error(
error: value,
onRetry: () => _onRetry(context),
),
_ => const Offstage(),
};
},
);
}

void _onRetry(BuildContext context) {
unawaited(RegistrationCubit.of(context).submitRegistration());
}
}

class _Error extends StatelessWidget {
final LocalizedException error;
final VoidCallback onRetry;

const _Error({
required this.error,
required this.onRetry,
});

@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.topCenter,
child: Container(
padding: const EdgeInsets.only(top: 20),
width: double.infinity,
child: VoicesErrorIndicator(
message: error.message(context),
onRetry: onRetry,
),
),
);
}
}

class _Navigation extends StatelessWidget {
const _Navigation();

Expand All @@ -181,12 +289,8 @@ class _Navigation extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
VoicesFilledButton(
leading: VoicesAssets.icons.wallet.buildIcon(),
onTap: () {
RegistrationCubit.of(context).walletLink.submitRegistration();
},
child: Text(context.l10n.walletLinkTransactionSign),
_BlocSubmitTxButton(
onSubmit: () => _submitRegistration(context),
),
const SizedBox(height: 10),
VoicesTextButton(
Expand All @@ -199,4 +303,42 @@ class _Navigation extends StatelessWidget {
],
);
}

void _submitRegistration(BuildContext context) {
unawaited(RegistrationCubit.of(context).submitRegistration());
}
}

class _BlocSubmitTxButton extends StatelessWidget {
final VoidCallback onSubmit;

const _BlocSubmitTxButton({required this.onSubmit});

@override
Widget build(BuildContext context) {
return BlocRegistrationBuilder<
({
bool isLoading,
bool canSubmitTx,
})>(
selector: (state) => (
isLoading: state.isSubmittingTx,
canSubmitTx: state.canSubmitTx,
),
builder: (context, state) {
return VoicesFilledButton(
leading: VoicesAssets.icons.wallet.buildIcon(),
onTap: state.canSubmitTx ? onSubmit : null,
trailing: state.isLoading
? const SizedBox(
width: 16,
height: 16,
child: VoicesCircularProgressIndicator(),
)
: null,
child: Text(context.l10n.walletLinkTransactionSign),
);
},
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ abstract interface class WalletLinkManager {
Future<bool> selectWallet(CardanoWallet wallet);

void selectRoles(Set<AccountRole> roles);

void submitRegistration();
}

final class WalletLinkCubit extends Cubit<WalletLinkStateData>
Expand Down Expand Up @@ -64,9 +62,4 @@ final class WalletLinkCubit extends Cubit<WalletLinkStateData>
void selectRoles(Set<AccountRole> roles) {
emit(state.copyWith(selectedRoles: Optional(roles)));
}

@override
void submitRegistration() {
// TODO(dtscalac): submit RBAC transaction
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export 'cubits/wallet_link_cubit.dart' show WalletLinkManager;
export 'registration_cubit.dart';
export 'registration_state.dart';
export 'state_data/recover_state_data.dart';
export 'state_data/registration_state_data.dart';
export 'state_data/seed_phrase_state_data.dart';
export 'state_data/unlock_password_state.dart';
export 'state_data/wallet_link_state_data.dart';
Loading

0 comments on commit 73807b4

Please sign in to comment.