From a14909c34a4f805291248089ede2c78e8441fd59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Damian=20Moli=C5=84ski?= <47773413+damian-molinski@users.noreply.github.com> Date: Wed, 25 Sep 2024 11:37:45 +0200 Subject: [PATCH] feat(cat-voices): account creation type dialog (#881) * wip * feat: localization + rename dialog * fix: missing comma * refactor: extract learn more button --------- Co-authored-by: Dominik Toton <166132265+dtscalac@users.noreply.github.com> --- .../get_started/account_create_dialog.dart | 187 ++++++++++++++++++ .../lib/pages/spaces/spaces_shell_page.dart | 45 ++++- .../session/session_action_header.dart | 19 +- .../lib/src/session/session_bloc.dart | 7 +- .../catalyst_voices_localizations.dart | 36 ++++ .../catalyst_voices_localizations_en.dart | 18 ++ .../catalyst_voices_localizations_es.dart | 18 ++ .../lib/l10n/intl_en.arb | 11 +- 8 files changed, 320 insertions(+), 21 deletions(-) create mode 100644 catalyst_voices/lib/pages/account/creation/get_started/account_create_dialog.dart diff --git a/catalyst_voices/lib/pages/account/creation/get_started/account_create_dialog.dart b/catalyst_voices/lib/pages/account/creation/get_started/account_create_dialog.dart new file mode 100644 index 0000000000..ea69f13db6 --- /dev/null +++ b/catalyst_voices/lib/pages/account/creation/get_started/account_create_dialog.dart @@ -0,0 +1,187 @@ +import 'package:catalyst_voices/pages/account/creation/task_picture.dart'; +import 'package:catalyst_voices/widgets/buttons/voices_buttons.dart'; +import 'package:catalyst_voices/widgets/modals/voices_desktop_dialog.dart'; +import 'package:catalyst_voices/widgets/modals/voices_dialog.dart'; +import 'package:catalyst_voices_assets/catalyst_voices_assets.dart'; +import 'package:catalyst_voices_brands/catalyst_voices_brands.dart'; +import 'package:catalyst_voices_localization/catalyst_voices_localization.dart'; +import 'package:catalyst_voices_shared/catalyst_voices_shared.dart'; +import 'package:flutter/material.dart'; + +enum AccountCreateType { + createNew, + recover; + + SvgGenImage get _icon => switch (this) { + AccountCreateType.createNew => VoicesAssets.icons.colorSwatch, + AccountCreateType.recover => VoicesAssets.icons.download, + }; + + String _getTitle(VoicesLocalizations l10n) => switch (this) { + AccountCreateType.createNew => l10n.accountCreationCreate, + AccountCreateType.recover => l10n.accountCreationRecover, + }; + + String _getSubtitle(VoicesLocalizations l10n) { + return l10n.accountCreationOnThisDevice; + } +} + +class AccountCreateDialog extends StatelessWidget { + const AccountCreateDialog._(); + + static Future show(BuildContext context) { + return VoicesDialog.show( + context: context, + builder: (context) => const AccountCreateDialog._(), + ); + } + + @override + Widget build(BuildContext context) { + return const VoicesDesktopPanelsDialog( + left: _LeftPanel(), + right: _RightPanel(), + ); + } +} + +class _LeftPanel extends StatelessWidget { + const _LeftPanel(); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.l10n.getStarted, + style: theme.textTheme.titleLarge?.copyWith( + color: theme.colors.textOnPrimaryLevel0, + ), + ), + const SizedBox(height: 12), + const Expanded(child: Center(child: TaskKeychainPicture())), + const SizedBox(height: 32), + // TODO(damian-molinski): External url redirect + VoicesLearnMoreButton(onTap: () {}), + ], + ); + } +} + +class _RightPanel extends StatelessWidget { + const _RightPanel(); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 24), + Text( + context.l10n.accountCreationGetStartedTitle, + style: theme.textTheme.titleMedium?.copyWith( + color: theme.colors.textOnPrimaryLevel1, + ), + ), + const SizedBox(height: 12), + Text( + context.l10n.accountCreationGetStatedDesc, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colors.textOnPrimaryLevel1, + ), + ), + const SizedBox(height: 32), + Text( + context.l10n.accountCreationGetStatedWhatNext, + style: theme.textTheme.titleSmall?.copyWith( + color: theme.colors.textOnPrimaryLevel0, + ), + ), + const SizedBox(height: 24), + Column( + mainAxisSize: MainAxisSize.min, + children: AccountCreateType.values + .map((type) { + return _AccountCreateTypeTile( + key: ValueKey(type), + type: type, + onTap: () => Navigator.of(context).pop(type), + ); + }) + .separatedBy(const SizedBox(height: 12)) + .toList(), + ), + ], + ); + } +} + +class _AccountCreateTypeTile extends StatelessWidget { + final AccountCreateType type; + final VoidCallback? onTap; + + const _AccountCreateTypeTile({ + 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, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/catalyst_voices/lib/pages/spaces/spaces_shell_page.dart b/catalyst_voices/lib/pages/spaces/spaces_shell_page.dart index 26b10d01de..8f5b6f9c00 100644 --- a/catalyst_voices/lib/pages/spaces/spaces_shell_page.dart +++ b/catalyst_voices/lib/pages/spaces/spaces_shell_page.dart @@ -1,4 +1,5 @@ import 'package:catalyst_voices/common/ext/ext.dart'; +import 'package:catalyst_voices/pages/account/creation/get_started/account_create_dialog.dart'; import 'package:catalyst_voices/pages/spaces/drawer/spaces_drawer.dart'; import 'package:catalyst_voices/widgets/widgets.dart'; import 'package:catalyst_voices_blocs/catalyst_voices_blocs.dart'; @@ -7,7 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -class SpacesShellPage extends StatelessWidget { +class SpacesShellPage extends StatefulWidget { final Space space; final Widget child; @@ -40,6 +41,11 @@ class SpacesShellPage extends StatelessWidget { required this.child, }); + @override + State createState() => _SpacesShellPageState(); +} + +class _SpacesShellPageState extends State { @override Widget build(BuildContext context) { final sessionBloc = context.watch(); @@ -48,27 +54,50 @@ class SpacesShellPage extends StatelessWidget { return CallbackShortcuts( bindings: { - for (final entry in _spacesShortcutsActivators.entries) + for (final entry in SpacesShellPage._spacesShortcutsActivators.entries) entry.value: () => entry.key.go(context), }, child: Scaffold( appBar: VoicesAppBar( leading: isVisitor ? null : const DrawerToggleButton(), automaticallyImplyLeading: false, - actions: const [ - SessionActionHeader(), - SessionStateHeader(), + actions: [ + SessionActionHeader( + onGetStartedTap: _showAccountGetStarted, + ), + const SessionStateHeader(), ], ), drawer: isVisitor ? null : SpacesDrawer( - space: space, - spacesShortcutsActivators: _spacesShortcutsActivators, + space: widget.space, + spacesShortcutsActivators: + SpacesShellPage._spacesShortcutsActivators, isUnlocked: isUnlocked, ), - body: child, + body: widget.child, ), ); } + + Future _showAccountGetStarted() async { + final type = await AccountCreateDialog.show(context); + if (type == null) { + return; + } + + if (mounted) { + switch (type) { + case AccountCreateType.createNew: + _showCreateAccountFlow().ignore(); + case AccountCreateType.recover: + _showRecoverAccountFlow().ignore(); + } + } + } + + Future _showCreateAccountFlow() async {} + + Future _showRecoverAccountFlow() async {} } diff --git a/catalyst_voices/lib/widgets/app_bar/session/session_action_header.dart b/catalyst_voices/lib/widgets/app_bar/session/session_action_header.dart index 7d52d730a2..d354771106 100644 --- a/catalyst_voices/lib/widgets/app_bar/session/session_action_header.dart +++ b/catalyst_voices/lib/widgets/app_bar/session/session_action_header.dart @@ -8,14 +8,19 @@ import 'package:flutter_bloc/flutter_bloc.dart'; /// Displays current session action and toggling to next when clicked. class SessionActionHeader extends StatelessWidget { - const SessionActionHeader({super.key}); + final VoidCallback? onGetStartedTap; + + const SessionActionHeader({ + super.key, + this.onGetStartedTap, + }); @override Widget build(BuildContext context) { return BlocBuilder( builder: (context, state) { return switch (state) { - VisitorSessionState() => const _GetStartedButton(), + VisitorSessionState() => _GetStartedButton(onTap: onGetStartedTap), GuestSessionState() => const _UnlockButton(), ActiveUserSessionState() => const _LockButton(), }; @@ -25,14 +30,16 @@ class SessionActionHeader extends StatelessWidget { } class _GetStartedButton extends StatelessWidget { - const _GetStartedButton(); + final VoidCallback? onTap; + + const _GetStartedButton({ + this.onTap, + }); @override Widget build(BuildContext context) { return VoicesFilledButton( - onTap: () { - context.read().add(const ActiveUserSessionEvent()); - }, + onTap: onTap, child: Text(context.l10n.getStarted), ); } diff --git a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/session/session_bloc.dart b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/session/session_bloc.dart index 949518e234..77545c8e8a 100644 --- a/catalyst_voices/packages/catalyst_voices_blocs/lib/src/session/session_bloc.dart +++ b/catalyst_voices/packages/catalyst_voices_blocs/lib/src/session/session_bloc.dart @@ -5,12 +5,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; /// Manages the user session. final class SessionBloc extends Bloc { - SessionBloc() - : super( - const ActiveUserSessionState( - user: User(name: 'Account'), - ), - ) { + SessionBloc() : super(const VisitorSessionState()) { on(_handleSessionEvent); } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart index cf70a944c9..a35785803a 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations.dart @@ -699,6 +699,42 @@ abstract class VoicesLocalizations { /// In en, this message translates to: /// **'You\'re almost there! This is the final and most important step in your account setup.\n\nWe\'re going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe\'ll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.'** String get walletLink_intro_content; + + /// No description provided for @accountCreationCreate. + /// + /// In en, this message translates to: + /// **'Create a new 
Catalyst Keychain'** + String get accountCreationCreate; + + /// No description provided for @accountCreationRecover. + /// + /// In en, this message translates to: + /// **'Recover your
Catalyst Keychain'** + String get accountCreationRecover; + + /// Indicates that created keychain will be stored in this device only + /// + /// In en, this message translates to: + /// **'On this device'** + String get accountCreationOnThisDevice; + + /// No description provided for @accountCreationGetStartedTitle. + /// + /// In en, this message translates to: + /// **'Welcome to Catalyst'** + String get accountCreationGetStartedTitle; + + /// No description provided for @accountCreationGetStatedDesc. + /// + /// In en, this message translates to: + /// **'If you already have a Catalyst keychain you can restore it on this device, or you can create a new Catalyst Keychain.'** + String get accountCreationGetStatedDesc; + + /// No description provided for @accountCreationGetStatedWhatNext. + /// + /// In en, this message translates to: + /// **'What do you want to do?'** + String get accountCreationGetStatedWhatNext; } class _VoicesLocalizationsDelegate extends LocalizationsDelegate { diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart index 08a982619f..e8a6c7e7fd 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_en.dart @@ -363,4 +363,22 @@ class VoicesLocalizationsEn extends VoicesLocalizations { @override String get walletLink_intro_content => 'You\'re almost there! This is the final and most important step in your account setup.\n\nWe\'re going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe\'ll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.'; + + @override + String get accountCreationCreate => 'Create a new 
Catalyst Keychain'; + + @override + String get accountCreationRecover => 'Recover your
Catalyst Keychain'; + + @override + String get accountCreationOnThisDevice => 'On this device'; + + @override + String get accountCreationGetStartedTitle => 'Welcome to Catalyst'; + + @override + String get accountCreationGetStatedDesc => 'If you already have a Catalyst keychain you can restore it on this device, or you can create a new Catalyst Keychain.'; + + @override + String get accountCreationGetStatedWhatNext => 'What do you want to do?'; } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart index 33a5b51913..2b10c965db 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/generated/catalyst_voices_localizations_es.dart @@ -363,4 +363,22 @@ class VoicesLocalizationsEs extends VoicesLocalizations { @override String get walletLink_intro_content => 'You\'re almost there! This is the final and most important step in your account setup.\n\nWe\'re going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe\'ll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.'; + + @override + String get accountCreationCreate => 'Create a new 
Catalyst Keychain'; + + @override + String get accountCreationRecover => 'Recover your
Catalyst Keychain'; + + @override + String get accountCreationOnThisDevice => 'On this device'; + + @override + String get accountCreationGetStartedTitle => 'Welcome to Catalyst'; + + @override + String get accountCreationGetStatedDesc => 'If you already have a Catalyst keychain you can restore it on this device, or you can create a new Catalyst Keychain.'; + + @override + String get accountCreationGetStatedWhatNext => 'What do you want to do?'; } diff --git a/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb b/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb index 22a2c7f2fe..e15b1f68e2 100644 --- a/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb +++ b/catalyst_voices/packages/catalyst_voices_localization/lib/l10n/intl_en.arb @@ -449,5 +449,14 @@ "walletLink_intro_content": "You're almost there! This is the final and most important step in your account setup.\n\nWe're going to link a Cardano Wallet to your Catalyst Keychain, so you can start collecting Role Keys.\n\nRole Keys allow you to enter new spaces, discover new ways to participate, and unlock new ways to earn rewards.\n\nWe'll start with your Voter Key by default. You can decide to add a Proposer Key and Drep key if you want, or you can always add them later.", "@walletLink_intro_content": { "description": "A message (content) in link wallet flow on intro screen." - } + }, + "accountCreationCreate": "Create a new \u2028Catalyst Keychain", + "accountCreationRecover": "Recover your\u2028Catalyst Keychain", + "accountCreationOnThisDevice": "On this device", + "@accountCreationOnThisDevice": { + "description": "Indicates that created keychain will be stored in this device only" + }, + "accountCreationGetStartedTitle": "Welcome to Catalyst", + "accountCreationGetStatedDesc": "If you already have a Catalyst keychain you can restore it on this device, or you can create a new Catalyst Keychain.", + "accountCreationGetStatedWhatNext": "What do you want to do?" } \ No newline at end of file