Skip to content

Commit

Permalink
feat(cat-voices): recover flow scaffloding (#971)
Browse files Browse the repository at this point in the history
* feat: recover stages

* feat: info panel recover seed phrase

* chore: wip

* feat: recovery methods and recovery seed phrase instructions panels

* feat: keychain status indicators

* feat: navigation

* feat: recover state data

* RecoverManager

* fix: remove unused import

* fix: PR review adjustments
  • Loading branch information
damian-molinski authored Oct 7, 2024
1 parent 041af7f commit 9d886f6
Show file tree
Hide file tree
Showing 46 changed files with 830 additions and 157 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:catalyst_voices/pages/registration/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/registration_stage_navigation.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:flutter/material.dart';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:catalyst_voices/pages/registration/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/registration_stage_navigation.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:flutter/material.dart';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:catalyst_voices/pages/registration/create_keychain/bloc_seed_phrase_builder.dart';
import 'package:catalyst_voices/pages/registration/registration_stage_navigation.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:catalyst_voices/pages/registration/create_keychain/bloc_seed_phrase_builder.dart';
import 'package:catalyst_voices/pages/registration/next_step.dart';
import 'package:catalyst_voices/pages/registration/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/registration_stage_navigation.dart';
import 'package:catalyst_voices/pages/registration/widgets/next_step.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:flutter/material.dart';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'dart:async';

import 'package:catalyst_voices/pages/registration/create_keychain/bloc_seed_phrase_builder.dart';
import 'package:catalyst_voices/pages/registration/registration_stage_navigation.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'package:catalyst_voices/pages/registration/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_message.dart';
import 'package:catalyst_voices/widgets/widgets.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:catalyst_voices/pages/registration/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/registration_stage_navigation.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart';
import 'package:catalyst_voices_localization/catalyst_voices_localization.dart';
import 'package:flutter/material.dart';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:catalyst_voices/pages/registration/create_keychain/bloc_unlock_password_builder.dart';
import 'package:catalyst_voices/pages/registration/registration_stage_navigation.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_navigation.dart';
import 'package:catalyst_voices/widgets/indicators/voices_password_strength_indicator.dart';
import 'package:catalyst_voices/widgets/text_field/voices_password_text_field.dart';
import 'package:catalyst_voices/widgets/text_field/voices_text_field.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:catalyst_voices/pages/registration/next_step.dart';
import 'package:catalyst_voices/pages/registration/registration_progress.dart';
import 'package:catalyst_voices/pages/registration/widgets/next_step.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_progress.dart';
import 'package:catalyst_voices/widgets/buttons/voices_filled_button.dart';
import 'package:catalyst_voices_assets/catalyst_voices_assets.dart';
import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:catalyst_voices/pages/registration/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_stage_message.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_tile.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';
Expand Down Expand Up @@ -31,92 +32,32 @@ class GetStartedPanel extends StatelessWidget {
color: theme.colors.textOnPrimaryLevel0,
),
),
const SizedBox(height: 24),
Column(
mainAxisSize: MainAxisSize.min,
children: CreateAccountType.values
.map<Widget>((type) {
return _CreateAccountTypeTile(
key: ValueKey(type),
type: type,
onTap: () {
switch (type) {
case CreateAccountType.createNew:
RegistrationCubit.of(context).createNewKeychainStep();
case CreateAccountType.recover:
RegistrationCubit.of(context).recoverKeychainStep();
}
},
);
})
.separatedBy(const SizedBox(height: 12))
.toList(),
),
],
);
}
}

class _CreateAccountTypeTile extends StatelessWidget {
final CreateAccountType type;
final VoidCallback? onTap;

const _CreateAccountTypeTile({
super.key,
required this.type,
this.onTap,
});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

return ConstrainedBox(
constraints: const BoxConstraints.tightFor(height: 80),
child: Material(
color: theme.colorScheme.primary,
borderRadius: BorderRadius.circular(12),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Row(
children: [
type._icon.buildIcon(
size: 48,
color: theme.colorScheme.onPrimary,
),
const SizedBox(width: 10),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
type._getTitle(context.l10n),
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleSmall?.copyWith(
color: theme.colorScheme.onPrimary,
),
),
Text(
type._getSubtitle(context.l10n),
maxLines: 1,
overflow: TextOverflow.clip,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onPrimary,
),
),
],
),
),
],
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: CreateAccountType.values
.map<Widget>((type) {
return RegistrationTile(
key: ValueKey(type),
icon: type._icon,
title: type._getTitle(context.l10n),
subtitle: type._getSubtitle(context.l10n),
onTap: () {
switch (type) {
case CreateAccountType.createNew:
RegistrationCubit.of(context).createNewKeychain();
case CreateAccountType.recover:
RegistrationCubit.of(context).recoverKeychain();
}
},
);
})
.separatedBy(const SizedBox(height: 12))
.toList(),
),
),
),
],
);
}
}
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 BlocRecoverBuilder<T>
extends BlocSelector<RegistrationCubit, RegistrationState, T> {
BlocRecoverBuilder({
super.key,
required BlocWidgetSelector<RecoverStateData, T> selector,
required super.builder,
super.bloc,
}) : super(
selector: (state) {
return selector(state.recoverStateData);
},
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import 'package:catalyst_voices/pages/registration/recover/bloc_recover_builder.dart';
import 'package:catalyst_voices/pages/registration/widgets/registration_tile.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:flutter/material.dart';

class RecoverMethodPanel extends StatelessWidget {
const RecoverMethodPanel({super.key});

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

final colorLvl0 = theme.colors.textOnPrimaryLevel0;
final colorLvl1 = theme.colors.textOnPrimaryLevel1;

return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 24),
Text(
context.l10n.recoverKeychainMethodsTitle,
style: theme.textTheme.titleMedium?.copyWith(color: colorLvl1),
),
const SizedBox(height: 12),
_BlocOnDeviceKeychains(onUnlockTap: _unlockKeychain),
const SizedBox(height: 12),
Text(
context.l10n.recoverKeychainMethodsSubtitle,
style: theme.textTheme.bodyMedium?.copyWith(color: colorLvl1),
),
const SizedBox(height: 32),
Text(
context.l10n.recoverKeychainMethodsListTitle,
style: theme.textTheme.titleSmall?.copyWith(color: colorLvl0),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: RegistrationRecoverMethod.values
.map<Widget>(
(method) {
return RegistrationTile(
key: ValueKey(method),
icon: method._icon,
title: method._getTitle(context.l10n),
subtitle: method._getSubtitle(context.l10n),
onTap: () {
switch (method) {
case RegistrationRecoverMethod.seedPhrase:
RegistrationCubit.of(context)
.recoverWithSeedPhrase();
}
},
);
},
)
.separatedBy(const SizedBox(height: 12))
.toList(),
),
),
],
),
);
}

void _unlockKeychain() {
//
}
}

class _BlocOnDeviceKeychains extends StatelessWidget {
final VoidCallback onUnlockTap;

const _BlocOnDeviceKeychains({
required this.onUnlockTap,
});

@override
Widget build(BuildContext context) {
return BlocRecoverBuilder<bool>(
selector: (state) => state.foundKeychain,
builder: (context, state) {
return _OnDeviceKeychains(
foundKeychain: state,
onUnlockTap: onUnlockTap,
);
},
);
}
}

class _OnDeviceKeychains extends StatelessWidget {
final bool foundKeychain;
final VoidCallback onUnlockTap;

const _OnDeviceKeychains({
this.foundKeychain = false,
required this.onUnlockTap,
});

@override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: TextStyle(color: Theme.of(context).colors.textOnPrimaryLevel1),
child: foundKeychain
? _KeychainFoundIndicator(onUnlockTap: onUnlockTap)
: const _KeychainNotFoundIndicator(),
);
}
}

class _KeychainFoundIndicator extends StatelessWidget {
final VoidCallback onUnlockTap;

const _KeychainFoundIndicator({
required this.onUnlockTap,
});

@override
Widget build(BuildContext context) {
return VoicesIndicator(
type: VoicesIndicatorType.success,
icon: VoicesAssets.icons.check,
message: Text(
context.l10n.recoverKeychainFound,
style: DefaultTextStyle.of(context).style,
),
action: VoicesTextButton(
onTap: onUnlockTap,
leading: VoicesAssets.icons.lockOpen.buildIcon(),
child: Text(context.l10n.unlock),
),
);
}
}

class _KeychainNotFoundIndicator extends StatelessWidget {
const _KeychainNotFoundIndicator();

@override
Widget build(BuildContext context) {
return VoicesIndicator(
type: VoicesIndicatorType.error,
icon: VoicesAssets.icons.exclamation,
message: Text(
context.l10n.recoverKeychainNonFound,
style: DefaultTextStyle.of(context).style,
),
);
}
}

extension _RegistrationRecoverMethodExt on RegistrationRecoverMethod {
SvgGenImage get _icon => switch (this) {
RegistrationRecoverMethod.seedPhrase => VoicesAssets.icons.colorSwatch,
};

String _getTitle(VoicesLocalizations l10n) => switch (this) {
RegistrationRecoverMethod.seedPhrase => l10n.seedPhrase12Words,
};

String? _getSubtitle(VoicesLocalizations l10n) => switch (this) {
RegistrationRecoverMethod.seedPhrase => null,
};
}
Loading

0 comments on commit 9d886f6

Please sign in to comment.