From 1eee5e451abc8614bb4725d386ba813bb27bf7ac Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Wed, 25 Sep 2024 15:32:31 -0400 Subject: [PATCH 01/23] fix: profile header aligned with profile list in the simple view. --- .../lib/features/profile/view/profile_header_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dart/npt_flutter/lib/features/profile/view/profile_header_view.dart b/packages/dart/npt_flutter/lib/features/profile/view/profile_header_view.dart index 7100478c2..b959a68e6 100644 --- a/packages/dart/npt_flutter/lib/features/profile/view/profile_header_view.dart +++ b/packages/dart/npt_flutter/lib/features/profile/view/profile_header_view.dart @@ -50,7 +50,7 @@ class ProfileHeaderView extends StatelessWidget { null => const Center(child: Spinner()), PreferredViewLayout.minimal => CustomCard.profileHeader( child: Padding( - padding: const EdgeInsets.all(Sizes.p10), + padding: const EdgeInsets.symmetric(vertical: Sizes.p10), child: Row( children: [ const ProfileSelectAllBox(), From 0acae0707c7ef3208137435c472b4bfcf26081eb Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Thu, 26 Sep 2024 07:57:42 -0400 Subject: [PATCH 02/23] fix: appBar alignment and padding updated. --- .../features/profile_list/cubit/profiles_running_cubit.dart | 2 -- packages/dart/npt_flutter/lib/styles/sizes.dart | 3 ++- packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/dart/npt_flutter/lib/features/profile_list/cubit/profiles_running_cubit.dart b/packages/dart/npt_flutter/lib/features/profile_list/cubit/profiles_running_cubit.dart index 64bdb8290..c4578049d 100644 --- a/packages/dart/npt_flutter/lib/features/profile_list/cubit/profiles_running_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/profile_list/cubit/profiles_running_cubit.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:npt_flutter/app.dart'; import 'package:socket_connector/socket_connector.dart'; diff --git a/packages/dart/npt_flutter/lib/styles/sizes.dart b/packages/dart/npt_flutter/lib/styles/sizes.dart index 20c681dcc..78f5cf7af 100644 --- a/packages/dart/npt_flutter/lib/styles/sizes.dart +++ b/packages/dart/npt_flutter/lib/styles/sizes.dart @@ -35,6 +35,7 @@ class Sizes { static const p43 = 43.0; static const p50 = 50.0; static const p54 = 54.0; + static const p70 = 70.0; static const p80 = 80.0; // static const p99 = 99.0; @@ -94,7 +95,7 @@ const gapH30 = SizedBox(height: Sizes.p30); // const gapH36 = SizedBox(height: Sizes.p36); const gapH40 = SizedBox(height: Sizes.p40); // const gapH46 = SizedBox(height: Sizes.p46); -// const gapH60 = SizedBox(height: Sizes.p60); + const gapH108 = SizedBox(height: Sizes.p108); const kWindowsMinWindowSize = Size(684, 541); diff --git a/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart b/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart index 158fe05ef..06fcf74e2 100644 --- a/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart +++ b/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart @@ -27,13 +27,13 @@ class NptAppBar extends StatelessWidget implements PreferredSizeWidget { children: [ Column( children: [ - gapH40, + gapH16, SvgPicture.asset( 'assets/noports_logo.svg', height: Sizes.p54, width: Sizes.p175, ), - gapH25, + gapH16, TextButton.icon( onPressed: () { Navigator.pop(context); @@ -72,6 +72,7 @@ class NptAppBar extends StatelessWidget implements PreferredSizeWidget { ), actions: [ IconButton( + padding: const EdgeInsets.only(bottom: Sizes.p30), color: settingsSelectedColor, icon: const Icon(Icons.settings_outlined), onPressed: () { From 62588e177a166fe38c7511ff9ea653cd84233893 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Thu, 26 Sep 2024 08:02:41 -0400 Subject: [PATCH 03/23] fix: back button shown conditionally on appBar. --- .../npt_flutter/lib/widgets/npt_app_bar.dart | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart b/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart index 06fcf74e2..7bb797010 100644 --- a/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart +++ b/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart @@ -34,18 +34,20 @@ class NptAppBar extends StatelessWidget implements PreferredSizeWidget { width: Sizes.p175, ), gapH16, - TextButton.icon( - onPressed: () { - Navigator.pop(context); - }, - label: Text( - strings.back, - ), - icon: const Icon( - Icons.arrow_back_ios, - ), - style: StyleConstants.backButtonStyle, - ), + isNavigateBack + ? TextButton.icon( + onPressed: () { + Navigator.pop(context); + }, + label: Text( + strings.back, + ), + icon: const Icon( + Icons.arrow_back_ios, + ), + style: StyleConstants.backButtonStyle, + ) + : gap0, ], ), gapW27, From 39ff8b79ca1401ffa8e9a53a1896915a3addb83a Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Thu, 26 Sep 2024 09:58:14 -0400 Subject: [PATCH 04/23] fix: profile, device name, service mapping and status updated to support scrolling. --- .../features/profile/view/profile_view.dart | 2 ++ .../profile/view/profile_view_minimal.dart | 1 - .../profile/view/profile_view_ssh_style.dart | 1 - .../profile/widgets/profile_device_name.dart | 19 +++++++----- .../profile/widgets/profile_display_name.dart | 25 +++++++++------- .../profile/widgets/profile_service_view.dart | 19 +++++++----- .../widgets/profile_status_indicator.dart | 30 ++++++++++--------- 7 files changed, 54 insertions(+), 43 deletions(-) diff --git a/packages/dart/npt_flutter/lib/features/profile/view/profile_view.dart b/packages/dart/npt_flutter/lib/features/profile/view/profile_view.dart index 9d8e829ca..16b47cda0 100644 --- a/packages/dart/npt_flutter/lib/features/profile/view/profile_view.dart +++ b/packages/dart/npt_flutter/lib/features/profile/view/profile_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:npt_flutter/features/profile/profile.dart'; import 'package:npt_flutter/features/settings/settings.dart'; +import 'package:npt_flutter/styles/sizes.dart'; import 'package:npt_flutter/widgets/loader_bar.dart'; import 'package:npt_flutter/widgets/spinner.dart'; @@ -21,6 +22,7 @@ class ProfileView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ LoaderBar(), + gapW10, ProfileRefreshButton(), ], ); diff --git a/packages/dart/npt_flutter/lib/features/profile/view/profile_view_minimal.dart b/packages/dart/npt_flutter/lib/features/profile/view/profile_view_minimal.dart index ec4c8eb73..adbb58171 100644 --- a/packages/dart/npt_flutter/lib/features/profile/view/profile_view_minimal.dart +++ b/packages/dart/npt_flutter/lib/features/profile/view/profile_view_minimal.dart @@ -14,7 +14,6 @@ class ProfileViewMinimal extends StatelessWidget { gapW10, ProfileStatusIndicator(), gapW10, - Spacer(), ProfileRunButton(), gapW10, ProfileFavoriteButton(), diff --git a/packages/dart/npt_flutter/lib/features/profile/view/profile_view_ssh_style.dart b/packages/dart/npt_flutter/lib/features/profile/view/profile_view_ssh_style.dart index a32bed57f..88689560f 100644 --- a/packages/dart/npt_flutter/lib/features/profile/view/profile_view_ssh_style.dart +++ b/packages/dart/npt_flutter/lib/features/profile/view/profile_view_ssh_style.dart @@ -18,7 +18,6 @@ class ProfileViewSshStyle extends StatelessWidget { gapW10, ProfileStatusIndicator(), gapW10, - Spacer(), ProfileRunButton(), gapW10, ProfileFavoriteButton(), diff --git a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_device_name.dart b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_device_name.dart index 30c1f3700..c92bb97b0 100644 --- a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_device_name.dart +++ b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_device_name.dart @@ -11,14 +11,17 @@ class ProfileDeviceName extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( width: Sizes.p150, - child: BlocSelector(selector: (state) { - if (state is! ProfileLoadedState) return null; - return (state.profile.deviceName, state.profile.sshnpdAtsign); - }, builder: (BuildContext context, (String, String)? tuple) { - if (tuple == null) return gap0; - var (deviceName, sshnpdAtSign) = tuple; - return Text('$deviceName$sshnpdAtSign'); - }), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: BlocSelector(selector: (state) { + if (state is! ProfileLoadedState) return null; + return (state.profile.deviceName, state.profile.sshnpdAtsign); + }, builder: (BuildContext context, (String, String)? tuple) { + if (tuple == null) return gap0; + var (deviceName, sshnpdAtSign) = tuple; + return Text('$deviceName$sshnpdAtSign'); + }), + ), ); } } diff --git a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_display_name.dart b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_display_name.dart index a04292431..5dadf4a8b 100644 --- a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_display_name.dart +++ b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_display_name.dart @@ -11,17 +11,20 @@ class ProfileDisplayName extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( width: Sizes.p150, - child: BlocSelector( - selector: (ProfileState state) { - if (state is ProfileLoadedState) { - return state.profile.displayName; - } - return null; - }, - builder: (BuildContext context, String? displayName) { - if (displayName == null) return gap0; - return Text(displayName); - }, + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: BlocSelector( + selector: (ProfileState state) { + if (state is ProfileLoadedState) { + return state.profile.displayName; + } + return null; + }, + builder: (BuildContext context, String? displayName) { + if (displayName == null) return gap0; + return Text(displayName); + }, + ), ), ); } diff --git a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_service_view.dart b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_service_view.dart index 34f4f9803..6895b6113 100644 --- a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_service_view.dart +++ b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_service_view.dart @@ -11,14 +11,17 @@ class ProfileServiceView extends StatelessWidget { Widget build(BuildContext context) { return SizedBox( width: Sizes.p150, - child: BlocSelector(selector: (state) { - if (state is! ProfileLoadedState) return null; - return (state.profile.localPort, state.profile.remoteHost, state.profile.remotePort); - }, builder: (BuildContext context, (int, String, int)? triple) { - if (triple == null) return gap0; - var (localPort, remoteHost, remotePort) = triple; - return Text('$localPort:$remoteHost:$remotePort'); - }), + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: BlocSelector(selector: (state) { + if (state is! ProfileLoadedState) return null; + return (state.profile.localPort, state.profile.remoteHost, state.profile.remotePort); + }, builder: (BuildContext context, (int, String, int)? triple) { + if (triple == null) return gap0; + var (localPort, remoteHost, remotePort) = triple; + return Text('$localPort:$remoteHost:$remotePort'); + }), + ), ); } } diff --git a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_status_indicator.dart b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_status_indicator.dart index 1e1085625..9bf2c41b7 100644 --- a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_status_indicator.dart +++ b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_status_indicator.dart @@ -8,23 +8,25 @@ class ProfileStatusIndicator extends StatelessWidget { @override Widget build(BuildContext context) { - return SizedBox( - width: Sizes.p150, - child: BlocBuilder(builder: (BuildContext context, ProfileState state) { - if (state is ProfileFailedSave) { - return const Tooltip(message: 'error saving profile', child: Text("Failed")); - } + return Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: BlocBuilder(builder: (BuildContext context, ProfileState state) { + if (state is ProfileFailedSave) { + return const Tooltip(message: 'error saving profile', child: Text("Failed")); + } - if (state is ProfileFailedStart) { - return Tooltip(message: state.reason ?? 'No Reason Provided', child: const Text("Failed")); - } + if (state is ProfileFailedStart) { + return Tooltip(message: state.reason ?? 'No Reason Provided', child: const Text("Failed")); + } - if (state is ProfileStarting && state.status != null) { - return Text(state.status!); - } + if (state is ProfileStarting && state.status != null) { + return Text(state.status!); + } - return gap0; - }), + return gap0; + }), + ), ); } } From 5539f132cc8021464d2ff1f366852eb2071979d4 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Mon, 30 Sep 2024 08:14:22 -0400 Subject: [PATCH 05/23] fix: dashboard page resizes based on user device size. --- .../profile/view/profile_header_view.dart | 10 ++++++---- .../profile/view/profile_view_minimal.dart | 3 ++- .../profile/widgets/profile_device_name.dart | 3 ++- .../profile/widgets/profile_display_name.dart | 10 ++++++++-- .../widgets/profile_header_column.dart | 19 +++++++++++++++++++ .../profile/widgets/profile_service_view.dart | 3 ++- .../profile_list/view/profile_list_view.dart | 10 +++++++++- .../settings/widgets/contact_list_tile.dart | 2 +- .../npt_flutter/lib/pages/dashboard_page.dart | 1 + .../dart/npt_flutter/lib/styles/sizes.dart | 10 ++++++++-- .../npt_flutter/lib/widgets/custom_card.dart | 6 +++--- 11 files changed, 61 insertions(+), 16 deletions(-) create mode 100644 packages/dart/npt_flutter/lib/features/profile/widgets/profile_header_column.dart diff --git a/packages/dart/npt_flutter/lib/features/profile/view/profile_header_view.dart b/packages/dart/npt_flutter/lib/features/profile/view/profile_header_view.dart index b959a68e6..75efd1e6f 100644 --- a/packages/dart/npt_flutter/lib/features/profile/view/profile_header_view.dart +++ b/packages/dart/npt_flutter/lib/features/profile/view/profile_header_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:npt_flutter/features/profile/widgets/profile_header_column.dart'; import 'package:npt_flutter/features/profile_list/profile_list.dart'; import 'package:npt_flutter/features/settings/settings.dart'; import 'package:npt_flutter/styles/sizes.dart'; @@ -14,6 +15,7 @@ class ProfileHeaderView extends StatelessWidget { @override Widget build(BuildContext context) { final strings = AppLocalizations.of(context)!; + return BlocBuilder(builder: (context, state) { if (state is ProfileListInitial) { context.read().add(const ProfileListLoadEvent()); @@ -55,7 +57,7 @@ class ProfileHeaderView extends StatelessWidget { children: [ const ProfileSelectAllBox(), gapW10, - SizedBox(width: Sizes.p150, child: Text(strings.profileName)), + ProfileHeaderColumn(title: strings.profileName, layout: PreferredViewLayout.minimal), gapW10, Text(strings.status), // gapW10, @@ -74,11 +76,11 @@ class ProfileHeaderView extends StatelessWidget { children: [ const ProfileSelectAllBox(), gapW10, - SizedBox(width: Sizes.p150, child: Text(strings.profileName)), + ProfileHeaderColumn(title: strings.profileName), gapW10, - SizedBox(width: Sizes.p150, child: Text(strings.deviceName)), + ProfileHeaderColumn(title: strings.deviceName), gapW10, - SizedBox(width: Sizes.p150, child: Text(strings.serviceMapping)), + ProfileHeaderColumn(title: strings.serviceMapping), gapW10, Text(strings.status), // gapW10, diff --git a/packages/dart/npt_flutter/lib/features/profile/view/profile_view_minimal.dart b/packages/dart/npt_flutter/lib/features/profile/view/profile_view_minimal.dart index adbb58171..e147f682f 100644 --- a/packages/dart/npt_flutter/lib/features/profile/view/profile_view_minimal.dart +++ b/packages/dart/npt_flutter/lib/features/profile/view/profile_view_minimal.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:npt_flutter/features/profile/profile.dart'; +import 'package:npt_flutter/features/settings/models/settings.dart'; import 'package:npt_flutter/styles/sizes.dart'; class ProfileViewMinimal extends StatelessWidget { @@ -10,7 +11,7 @@ class ProfileViewMinimal extends StatelessWidget { return const Row(children: [ ProfileSelectBox(), gapW10, - ProfileDisplayName(), + ProfileDisplayName(layout: PreferredViewLayout.minimal), gapW10, ProfileStatusIndicator(), gapW10, diff --git a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_device_name.dart b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_device_name.dart index c92bb97b0..0d898bdcb 100644 --- a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_device_name.dart +++ b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_device_name.dart @@ -9,8 +9,9 @@ class ProfileDeviceName extends StatelessWidget { @override Widget build(BuildContext context) { + final deviceWidth = MediaQuery.of(context).size.width; return SizedBox( - width: Sizes.p150, + width: deviceWidth * Sizes.profileFieldsWidthFactor, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: BlocSelector(selector: (state) { diff --git a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_display_name.dart b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_display_name.dart index 5dadf4a8b..79c35a3ea 100644 --- a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_display_name.dart +++ b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_display_name.dart @@ -1,16 +1,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:npt_flutter/features/profile/profile.dart'; +import 'package:npt_flutter/features/settings/models/settings.dart'; import '../../../styles/sizes.dart'; class ProfileDisplayName extends StatelessWidget { - const ProfileDisplayName({super.key}); + const ProfileDisplayName({super.key, this.layout = PreferredViewLayout.sshStyle}); + + final PreferredViewLayout layout; @override Widget build(BuildContext context) { + final deviceWidth = MediaQuery.of(context).size.width; + final double widthFactor = + layout == PreferredViewLayout.sshStyle ? Sizes.profileFieldsWidthFactor : Sizes.profileFieldsWidthFactorAlt; return SizedBox( - width: Sizes.p150, + width: deviceWidth * widthFactor, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: BlocSelector( diff --git a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_header_column.dart b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_header_column.dart new file mode 100644 index 000000000..e00f1882b --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_header_column.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import 'package:npt_flutter/features/settings/models/settings.dart'; +import 'package:npt_flutter/styles/sizes.dart'; + +class ProfileHeaderColumn extends StatelessWidget { + const ProfileHeaderColumn({super.key, required this.title, this.layout = PreferredViewLayout.sshStyle}); + + final String title; + final PreferredViewLayout layout; + + @override + Widget build(BuildContext context) { + final deviceWidth = MediaQuery.of(context).size.width; + final double widthFactor = + layout == PreferredViewLayout.sshStyle ? Sizes.profileFieldsWidthFactor : Sizes.profileFieldsWidthFactorAlt; + + return SizedBox(width: deviceWidth * widthFactor, child: Text(title)); + } +} diff --git a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_service_view.dart b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_service_view.dart index 6895b6113..51da54c86 100644 --- a/packages/dart/npt_flutter/lib/features/profile/widgets/profile_service_view.dart +++ b/packages/dart/npt_flutter/lib/features/profile/widgets/profile_service_view.dart @@ -9,8 +9,9 @@ class ProfileServiceView extends StatelessWidget { @override Widget build(BuildContext context) { + final deviceWidth = MediaQuery.of(context).size.width; return SizedBox( - width: Sizes.p150, + width: deviceWidth * Sizes.profileFieldsWidthFactor, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: BlocSelector(selector: (state) { diff --git a/packages/dart/npt_flutter/lib/features/profile_list/view/profile_list_view.dart b/packages/dart/npt_flutter/lib/features/profile_list/view/profile_list_view.dart index bfe02ac40..6f42a1603 100644 --- a/packages/dart/npt_flutter/lib/features/profile_list/view/profile_list_view.dart +++ b/packages/dart/npt_flutter/lib/features/profile_list/view/profile_list_view.dart @@ -16,6 +16,9 @@ class ProfileListView extends StatelessWidget { @override Widget build(BuildContext context) { final strings = AppLocalizations.of(context)!; + final deviceSize = MediaQuery.of(context).size; + final bodyMedium = Theme.of(context).textTheme.labelSmall; + SizeConfig().init(); return BlocBuilder(builder: (context, state) { return switch (state) { ProfileListInitial() || ProfileListLoading() => const Center(child: Spinner()), @@ -52,6 +55,8 @@ class ProfileListView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomCard.dashboardContent( + height: deviceSize.height * Sizes.dashboardCardHeightFactor, + width: deviceSize.width * Sizes.dashboardCardWidthFactor, child: Column( children: [ isFullProfile @@ -113,7 +118,10 @@ class ProfileListView extends StatelessWidget { ), ), gapH16, - Text(strings.allRightsReserved) + Text( + strings.allRightsReserved, + style: bodyMedium?.copyWith(fontSize: bodyMedium.fontSize?.toFont), + ), ], ), ), diff --git a/packages/dart/npt_flutter/lib/features/settings/widgets/contact_list_tile.dart b/packages/dart/npt_flutter/lib/features/settings/widgets/contact_list_tile.dart index 336eba6b2..10b0255a2 100644 --- a/packages/dart/npt_flutter/lib/features/settings/widgets/contact_list_tile.dart +++ b/packages/dart/npt_flutter/lib/features/settings/widgets/contact_list_tile.dart @@ -9,7 +9,7 @@ class ContactListTile extends StatelessWidget { @override Widget build(BuildContext context) { - SizeConfig().init(context); + SizeConfig().init(); final contactRepo = ContactsService.getInstance(); final bodyMedium = Theme.of(context).textTheme.bodyMedium!; diff --git a/packages/dart/npt_flutter/lib/pages/dashboard_page.dart b/packages/dart/npt_flutter/lib/pages/dashboard_page.dart index 8b6fe312f..86866072b 100644 --- a/packages/dart/npt_flutter/lib/pages/dashboard_page.dart +++ b/packages/dart/npt_flutter/lib/pages/dashboard_page.dart @@ -9,6 +9,7 @@ class DashboardPage extends StatelessWidget { @override Widget build(BuildContext context) { final strings = AppLocalizations.of(context)!; + return Scaffold( appBar: NptAppBar( title: strings.dashboard, diff --git a/packages/dart/npt_flutter/lib/styles/sizes.dart b/packages/dart/npt_flutter/lib/styles/sizes.dart index 78f5cf7af..098a19f2c 100644 --- a/packages/dart/npt_flutter/lib/styles/sizes.dart +++ b/packages/dart/npt_flutter/lib/styles/sizes.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:npt_flutter/app.dart'; /// Constant sizes to be used in the app (paddings, gaps, rounded corners etc.) class Sizes { @@ -61,6 +62,11 @@ class Sizes { static const p654 = 654.0; static const p664 = 664.0; static const p941 = 941.0; + // The below size factors are constants that are used to determine the height or width based on the device size. + static const dashboardCardHeightFactor = 489 / 691; + static const dashboardCardWidthFactor = 941 / 1053; + static const profileFieldsWidthFactor = 150 / 1053; + static const profileFieldsWidthFactorAlt = 300 / 1053; } const gap0 = SizedBox(); @@ -132,8 +138,8 @@ class SizeConfig { MediaQuery.of(context).size.width >= 700 && MediaQuery.of(context).size.width < 1200; bool isDesktop(BuildContext context) => MediaQuery.of(context).size.width >= 1200; - void init(BuildContext context) { - _mediaQueryData = MediaQuery.of(context); + void init() { + _mediaQueryData = MediaQuery.of(App.navState.currentContext!); screenWidth = _mediaQueryData.size.width; screenHeight = _mediaQueryData.size.height; refHeight = 505; diff --git a/packages/dart/npt_flutter/lib/widgets/custom_card.dart b/packages/dart/npt_flutter/lib/widgets/custom_card.dart index 7b741b596..bcace83f9 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_card.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_card.dart @@ -69,10 +69,10 @@ class CustomCard extends StatelessWidget { bottomBorderSide = BorderSide.none; const CustomCard.dashboardContent({ required this.child, + this.height = Sizes.p500, + this.width = Sizes.p941, super.key, - }) : height = Sizes.p500, - width = Sizes.p941, - color = AppColor.cardColorDark, + }) : color = AppColor.cardColorDark, radiusTopLeft = const Radius.circular(Sizes.p20), radiusTopRight = const Radius.circular(Sizes.p20), radiusBottomLeft = const Radius.circular(Sizes.p20), From d0c81ff761c5eed9a429dfaa39cdbd100f2cad0c Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Mon, 30 Sep 2024 08:21:06 -0400 Subject: [PATCH 06/23] fix: auto spacing occurs between dashboard content and all rights reserved. --- .../lib/features/profile_list/view/profile_list_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dart/npt_flutter/lib/features/profile_list/view/profile_list_view.dart b/packages/dart/npt_flutter/lib/features/profile_list/view/profile_list_view.dart index 6f42a1603..6c689682a 100644 --- a/packages/dart/npt_flutter/lib/features/profile_list/view/profile_list_view.dart +++ b/packages/dart/npt_flutter/lib/features/profile_list/view/profile_list_view.dart @@ -53,6 +53,7 @@ class ProfileListView extends StatelessWidget { alignment: Alignment.topCenter, child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ CustomCard.dashboardContent( height: deviceSize.height * Sizes.dashboardCardHeightFactor, @@ -117,7 +118,6 @@ class ProfileListView extends StatelessWidget { ], ), ), - gapH16, Text( strings.allRightsReserved, style: bodyMedium?.copyWith(fontSize: bodyMedium.fontSize?.toFont), From a73fbe5c208d62eacd09f952b7fff2d9a7e864e3 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Mon, 30 Sep 2024 08:39:51 -0400 Subject: [PATCH 07/23] fix: profile form view sizes based on device size. --- .../lib/features/profile_form/view/profile_form_view.dart | 6 ++++-- packages/dart/npt_flutter/lib/widgets/custom_card.dart | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/dart/npt_flutter/lib/features/profile_form/view/profile_form_view.dart b/packages/dart/npt_flutter/lib/features/profile_form/view/profile_form_view.dart index 7f7ed730f..7f1ede75c 100644 --- a/packages/dart/npt_flutter/lib/features/profile_form/view/profile_form_view.dart +++ b/packages/dart/npt_flutter/lib/features/profile_form/view/profile_form_view.dart @@ -14,21 +14,24 @@ class ProfileFormView extends StatelessWidget { Widget build(BuildContext context) { final strings = AppLocalizations.of(context)!; final GlobalKey formkey = GlobalKey(); + final deviceSize = MediaQuery.of(context).size; return BlocProvider( create: (BuildContext context) => /// Local copy of the profile which is used by the form ProfileBloc(context.read(), uuid)..add(const ProfileLoadOrCreateEvent()), child: Padding( - padding: const EdgeInsets.only(left: Sizes.p100, right: Sizes.p100, top: Sizes.p20), + padding: const EdgeInsets.only(left: Sizes.p100, right: Sizes.p100), child: Stack( children: [ Align( alignment: Alignment.topCenter, child: Column( crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ CustomCard.profileFormContent( + height: deviceSize.height * Sizes.dashboardCardHeightFactor, child: SingleChildScrollView( child: Form( key: formkey, @@ -91,7 +94,6 @@ class ProfileFormView extends StatelessWidget { ), ), ), - gapH16, Text(strings.allRightsReserved), ], ), diff --git a/packages/dart/npt_flutter/lib/widgets/custom_card.dart b/packages/dart/npt_flutter/lib/widgets/custom_card.dart index bcace83f9..396cb931c 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_card.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_card.dart @@ -54,9 +54,9 @@ class CustomCard extends StatelessWidget { bottomBorderSide = BorderSide.none; const CustomCard.profileFormContent({ required this.child, + this.height = Sizes.p500, super.key, - }) : height = Sizes.p450, - width = null, + }) : width = null, color = AppColor.cardColorDark, radiusTopLeft = const Radius.circular(Sizes.p20), radiusTopRight = const Radius.circular(Sizes.p20), From 2b4ea13574a206ef5d1b8775d34bd1e7182fa9ee Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Tue, 1 Oct 2024 07:25:44 -0400 Subject: [PATCH 08/23] fix: settings screen resizes based on device size. --- .../features/settings/view/settings_view.dart | 70 +++++++++---------- .../settings_dashboard_layout_selector.dart | 40 ++++++----- .../dart/npt_flutter/lib/styles/sizes.dart | 2 + .../npt_flutter/lib/widgets/custom_card.dart | 12 ++-- 4 files changed, 63 insertions(+), 61 deletions(-) diff --git a/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart b/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart index a43939245..84550e741 100644 --- a/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart +++ b/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart @@ -19,6 +19,7 @@ class SettingsView extends StatelessWidget { @override Widget build(BuildContext context) { final strings = AppLocalizations.of(context)!; + final deviceSize = MediaQuery.of(context).size; return BlocBuilder( builder: (context, state) { if (state is SettingsInitial) { @@ -29,14 +30,34 @@ class SettingsView extends StatelessWidget { case SettingsLoading(): return const Center(child: Spinner()); case SettingsLoadedState(): - return Padding( - padding: const EdgeInsets.only(top: 18, bottom: 92, left: 120, right: 77), - child: Stack( - clipBehavior: Clip.none, - children: [ - Positioned( - left: Sizes.p192, - child: CustomCard.settingsContent( + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomCard.settingsRail( + height: deviceSize.height * Sizes.SettingsCardHeightFactor, + child: const Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + // gapH30, + CustomTextButton.discord(), + CustomTextButton.email(), + CustomTextButton.faq(), + CustomTextButton.privacyPolicy(), + CustomTextButton.feedback(), + CustomTextButton.backUpYourKey(), + CustomTextButton.resetAtsign(), + ContactListTile(), + ], + ), + ), + CustomCard.settingsContent( + height: deviceSize.height * Sizes.SettingsCardHeightFactor, + width: deviceSize.width * Sizes.SettingsCardWidthFactor, child: Padding( padding: const EdgeInsets.only( left: Sizes.p43, @@ -55,35 +76,10 @@ class SettingsView extends StatelessWidget { ]), ), ), - ), - const Positioned( - left: 0, - child: CustomCard.settingsRail( - child: Padding( - padding: EdgeInsets.all(0.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - gapH30, - CustomTextButton.discord(), - CustomTextButton.email(), - CustomTextButton.faq(), - CustomTextButton.privacyPolicy(), - CustomTextButton.feedback(), - CustomTextButton.backUpYourKey(), - CustomTextButton.resetAtsign(), - ContactListTile(), - ], - ), - ), - ), - ), - Positioned( - top: Sizes.p470, - child: Text(strings.allRightsReserved), - ), - ], - ), + ], + ), + Text(strings.allRightsReserved) + ], ); } }, diff --git a/packages/dart/npt_flutter/lib/features/settings/widgets/settings_dashboard_layout_selector.dart b/packages/dart/npt_flutter/lib/features/settings/widgets/settings_dashboard_layout_selector.dart index d1b4680dd..0adc07e15 100644 --- a/packages/dart/npt_flutter/lib/features/settings/widgets/settings_dashboard_layout_selector.dart +++ b/packages/dart/npt_flutter/lib/features/settings/widgets/settings_dashboard_layout_selector.dart @@ -46,25 +46,29 @@ class SettingsDashboardLayoutSelector extends StatelessWidget { ], ), gapH18, - CustomCard.settingsPreview( - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - gapH13, - Padding( - padding: const EdgeInsets.only(left: Sizes.p20), - child: Align( - alignment: Alignment.centerLeft, - child: Text(strings.preview), + SizedBox( + height: 295, + width: 537, + child: CustomCard.settingsPreview( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + gapH13, + Padding( + padding: const EdgeInsets.only(left: Sizes.p20), + child: Align( + alignment: Alignment.centerLeft, + child: Text(strings.preview), + ), ), - ), - gapH10, - viewLayout == PreferredViewLayout.minimal - ? SvgPicture.asset('assets/simple.svg') - : SvgPicture.asset('assets/advance.svg'), - gapH16, - ], - )) + gapH10, + viewLayout == PreferredViewLayout.minimal + ? SvgPicture.asset('assets/simple.svg') + : SvgPicture.asset('assets/advance.svg'), + gapH16, + ], + )), + ) ], ); }); diff --git a/packages/dart/npt_flutter/lib/styles/sizes.dart b/packages/dart/npt_flutter/lib/styles/sizes.dart index 098a19f2c..1fbab9989 100644 --- a/packages/dart/npt_flutter/lib/styles/sizes.dart +++ b/packages/dart/npt_flutter/lib/styles/sizes.dart @@ -67,6 +67,8 @@ class Sizes { static const dashboardCardWidthFactor = 941 / 1053; static const profileFieldsWidthFactor = 150 / 1053; static const profileFieldsWidthFactorAlt = 300 / 1053; + static const SettingsCardWidthFactor = 654 / 1053; + static const SettingsCardHeightFactor = 438 / 691; } const gap0 = SizedBox(); diff --git a/packages/dart/npt_flutter/lib/widgets/custom_card.dart b/packages/dart/npt_flutter/lib/widgets/custom_card.dart index 396cb931c..8c5dd2a01 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_card.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_card.dart @@ -23,10 +23,10 @@ class CustomCard extends StatelessWidget { const CustomCard.settingsRail({ required this.child, + this.height = Sizes.p436, + this.width = Sizes.p202, super.key, - }) : height = Sizes.p436, - width = Sizes.p202, - color = Colors.white, + }) : color = Colors.white, radiusTopLeft = const Radius.circular(Sizes.p10), radiusTopRight = const Radius.circular(Sizes.p10), radiusBottomLeft = const Radius.circular(Sizes.p10), @@ -39,10 +39,10 @@ class CustomCard extends StatelessWidget { const CustomCard.settingsContent({ required this.child, + this.height = Sizes.p470, + this.width = Sizes.p664, super.key, - }) : height = Sizes.p436, - width = Sizes.p664, - color = AppColor.cardColorDark, + }) : color = AppColor.cardColorDark, radiusTopLeft = Radius.zero, radiusTopRight = const Radius.circular(Sizes.p20), radiusBottomLeft = Radius.zero, From 5736dbf7da1d67bccb819bd7ebaa5d47adc2a681 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Fri, 4 Oct 2024 20:22:07 -0400 Subject: [PATCH 09/23] feat: onboarding flow added --- .../dart/npt_flutter/assets/onboarding_bg.svg | 114 ++++++++++++++++++ packages/dart/npt_flutter/lib/app.dart | 6 + .../onboarding/cubit/at_directory_cubit.dart | 7 ++ .../onboarding/view/onboaring_view.dart | 52 ++++++++ .../widgets/at_directory_dialog.dart | 61 ++++++++++ .../onboarding_at_directory_selector.dart | 74 ++++++++++++ .../onboarding/widgets/onboarding_button.dart | 64 ++++++---- .../npt_flutter/lib/localization/app_en.arb | 7 ++ .../lib/pages/onboarding_page.dart | 13 +- .../npt_flutter/lib/styles/app_theme.dart | 8 ++ .../dart/npt_flutter/lib/styles/sizes.dart | 2 +- .../lib/widgets/custom_text_button.dart | 35 ++++-- .../npt_flutter/lib/widgets/npt_app_bar.dart | 27 +++-- 13 files changed, 422 insertions(+), 48 deletions(-) create mode 100644 packages/dart/npt_flutter/assets/onboarding_bg.svg create mode 100644 packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart create mode 100644 packages/dart/npt_flutter/lib/features/onboarding/view/onboaring_view.dart create mode 100644 packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart create mode 100644 packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart diff --git a/packages/dart/npt_flutter/assets/onboarding_bg.svg b/packages/dart/npt_flutter/assets/onboarding_bg.svg new file mode 100644 index 000000000..c925206fa --- /dev/null +++ b/packages/dart/npt_flutter/assets/onboarding_bg.svg @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/dart/npt_flutter/lib/app.dart b/packages/dart/npt_flutter/lib/app.dart index 72ccacfd4..1522a593f 100644 --- a/packages/dart/npt_flutter/lib/app.dart +++ b/packages/dart/npt_flutter/lib/app.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/features/features.dart'; +import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; import 'package:npt_flutter/routes.dart'; import 'package:npt_flutter/styles/app_theme.dart'; @@ -88,6 +89,11 @@ class App extends StatelessWidget { BlocProvider( create: (ctx) => FavoriteBloc(ctx.read()), ), + + // A bloc which manages the atDirectory state + BlocProvider( + create: (_) => AtDirectoryCubit(), + ), ], child: BlocSelector(selector: (state) { if (state is SettingsLoadedState) { diff --git a/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart b/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart new file mode 100644 index 000000000..7dcdad21b --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart @@ -0,0 +1,7 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; + +class AtDirectoryCubit extends Cubit { + AtDirectoryCubit() : super('root.atsign.org'); + + void setRootDomain(String rootDomain) => emit(rootDomain); +} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/view/onboaring_view.dart b/packages/dart/npt_flutter/lib/features/onboarding/view/onboaring_view.dart new file mode 100644 index 000000000..f7fc2090a --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/onboarding/view/onboaring_view.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:npt_flutter/features/onboarding/widgets/onboarding_button.dart'; +import 'package:npt_flutter/styles/sizes.dart'; +import 'package:npt_flutter/widgets/custom_text_button.dart'; + +class OnboardingView extends StatelessWidget { + const OnboardingView({super.key}); + + @override + Widget build(BuildContext context) { + final strings = AppLocalizations.of(context)!; + final textTheme = Theme.of(context).textTheme; + return Stack( + children: [ + Positioned.fill( + child: SvgPicture.asset( + 'assets/onboarding_bg.svg', + fit: BoxFit.cover, + ), + ), + Align( + child: Column( + children: [ + gapH108, + Text( + strings.onboardingTitle, + style: textTheme.headlineLarge!.copyWith( + color: Colors.black, + ), + ), + Text(strings.onboardingSubTitle, style: textTheme.headlineMedium), + gapH20, + const OnboardingButton(), + ], + ), + ), + const Align( + alignment: Alignment.bottomRight, + child: Padding( + padding: EdgeInsets.only( + bottom: Sizes.p44, + right: Sizes.p44, + ), + child: CustomTextButton.resetAtsign(), + ), + ) + ], + ); + } +} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart new file mode 100644 index 000000000..5c4be911f --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:npt_flutter/features/onboarding/widgets/onboarding_at_directory_selector.dart'; +import 'package:npt_flutter/styles/sizes.dart'; +import 'package:npt_flutter/widgets/custom_container.dart'; + +class AtDirectoryDialog extends StatefulWidget { + const AtDirectoryDialog({super.key}); + + @override + State createState() => _AtDirectoryDialogState(); +} + +class _AtDirectoryDialogState extends State { + @override + Widget build(BuildContext context) { + final strings = AppLocalizations.of(context)!; + return AlertDialog( + backgroundColor: Colors.white, + content: Padding( + padding: const EdgeInsets.symmetric(vertical: Sizes.p12, horizontal: Sizes.p16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CustomContainer.background( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(strings.atDirectory), + Text(strings.atDirectorySubtitle), + gapH16, + OnboardingAtDirectorySelector(), + ], + ), + ), + gapH10, + CustomContainer.background( + child: Row( + children: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(strings.cancel), + ), + const Spacer(), + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text(strings.onboard), + ), + ], + )) + ], + ), + ), + ); + } +} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart new file mode 100644 index 000000000..08e0f3225 --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; + +typedef OnboardingMapCallback = void Function(Map val); + +class OnboardingAtDirectorySelector extends StatelessWidget { + OnboardingAtDirectorySelector({ + super.key, + }); + + final options = ['root.atsign.org', 'vip.ve.atsign.zone']; + + final FocusNode focusNode = FocusNode(); + final TextEditingController controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, rootDomain) { + controller.text = rootDomain; + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: DropdownMenu( + initialSelection: options.contains(rootDomain) ? rootDomain : null, + dropdownMenuEntries: options + .map>( + (o) => DropdownMenuEntry( + value: o, + label: o, + ), + ) + .toList(), + onSelected: (value) { + if (value == null) return; + + context.read().setRootDomain(value); + }, + ), + ), + Flexible( + child: KeyboardListener( + focusNode: focusNode, + onKeyEvent: (value) { + if (value.logicalKey == LogicalKeyboardKey.backspace) { + if (options.length > 2) options.removeLast(); + } + }, + child: TextFormField( + controller: controller, + autovalidateMode: AutovalidateMode.onUserInteraction, + // validator: FormValidator.validateRequiredAtsignField, + onChanged: (value) { + // prevent the user from adding the default values to the dropdown a second time. + if (value != options[0] || value != options[1]) options.add(value); + //removes the third element making the final entry the only additional value in options. This prevents the dropdown from having more than 3 entries. + if (options.length > 3) options.removeAt(2); + + context.read().setRootDomain(value); + }, + ), + ), + ) + ], + ), + ], + ); + }); + } +} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart index 53d269738..638eeba70 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart @@ -1,15 +1,21 @@ import 'package:at_contacts_flutter/at_contacts_flutter.dart'; import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/constants.dart'; +import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; import 'package:npt_flutter/features/onboarding/onboarding.dart'; +import 'package:npt_flutter/features/onboarding/widgets/at_directory_dialog.dart'; +import 'package:npt_flutter/routes.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:phosphor_flutter/phosphor_flutter.dart'; -Future loadAtClientPreference() async { +Future loadAtClientPreference(String rootDomain) async { var dir = await getApplicationSupportDirectory(); return AtClientPreference() - ..rootDomain = Constants.rootDomain + ..rootDomain = rootDomain ..namespace = Constants.namespace ..hiveStoragePath = dir.path ..commitLogPath = dir.path @@ -17,38 +23,42 @@ Future loadAtClientPreference() async { } class OnboardingButton extends StatefulWidget { - const OnboardingButton({super.key, required this.nextRoute}); - final String nextRoute; + const OnboardingButton({ + super.key, + }); @override State createState() => _OnboardingButtonState(); } class _OnboardingButtonState extends State { - final Future futurePreference = loadAtClientPreference(); - - @override - void initState() { - super.initState(); - onboard(isFromInitState: true); - } - @override Widget build(BuildContext context) { - return ElevatedButton( - onPressed: onboard, - child: const Text('Login'), - ); + final strings = AppLocalizations.of(context)!; + return BlocBuilder(builder: (context, rootDomain) { + return ElevatedButton.icon( + onPressed: () async { + final result = await selectOptions(); + + if (result && context.mounted) onboard(rootDomain: context.read().state); + }, + icon: PhosphorIcon(PhosphorIcons.arrowUpRight()), + label: Text( + strings.getStarted, + ), + iconAlignment: IconAlignment.end, + ); + }); } - Future onboard({bool isFromInitState = false}) async { + Future onboard({required String rootDomain, bool isFromInitState = false}) async { AtOnboardingResult onboardingResult = await AtOnboarding.onboard( // ignore: use_build_context_synchronously context: context, config: AtOnboardingConfig( - atClientPreference: await futurePreference, + atClientPreference: await loadAtClientPreference(rootDomain), rootEnvironment: RootEnvironment.Testing, - domain: Constants.rootDomain, + domain: rootDomain, appAPIKey: Constants.appAPIKey, ), ); @@ -56,16 +66,16 @@ class _OnboardingButtonState extends State { if (mounted) { switch (onboardingResult.status) { case AtOnboardingResultStatus.success: - await initializeContactsService(rootDomain: Constants.rootDomain); + await initializeContactsService(rootDomain: rootDomain); postOnboard(onboardingResult.atsign!); - Navigator.of(context).pushReplacementNamed(widget.nextRoute); + Navigator.of(context).pushReplacementNamed(Routes.dashboard); break; case AtOnboardingResultStatus.error: if (isFromInitState) break; ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( + SnackBar( backgroundColor: Colors.red, - content: Text('An error has occurred'), + content: Text(AppLocalizations.of(context)!.onboardingError), ), ); break; @@ -74,4 +84,12 @@ class _OnboardingButtonState extends State { } } } + + Future selectOptions() async { + final results = await showDialog( + context: context, + builder: (BuildContext context) => const AtDirectoryDialog(), + ); + return results ?? false; + } } diff --git a/packages/dart/npt_flutter/lib/localization/app_en.arb b/packages/dart/npt_flutter/lib/localization/app_en.arb index 1bb1406b5..ca172fb49 100644 --- a/packages/dart/npt_flutter/lib/localization/app_en.arb +++ b/packages/dart/npt_flutter/lib/localization/app_en.arb @@ -68,5 +68,12 @@ "validationErrorRemoteHostField" : "Field must be partially or fully qualified hostname or an IP address", "profileRunningActionDeniedMessage" : "Cannot perform this action while profile is running", "emptyProfileMessage" : "No profiles found\nCreate or Import a profile to start using NoPorts.", + "onboardingTitle" : "Welcome", + "onboardingSubTitle" : "to NoPorts Desktop", + "getStarted" : "Get Started", + "atDirectory" : "AtDirectory", + "atDirectorySubtitle" : "Select the domain you want to use", + "onboard" : "Onboard", + "onboardingError" : "An error has occurred", "yaml" : "YAML" } \ No newline at end of file diff --git a/packages/dart/npt_flutter/lib/pages/onboarding_page.dart b/packages/dart/npt_flutter/lib/pages/onboarding_page.dart index 0e24abfde..8a52b2d28 100644 --- a/packages/dart/npt_flutter/lib/pages/onboarding_page.dart +++ b/packages/dart/npt_flutter/lib/pages/onboarding_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:npt_flutter/features/onboarding/onboarding.dart'; +import 'package:npt_flutter/features/onboarding/view/onboaring_view.dart'; +import 'package:npt_flutter/widgets/npt_app_bar.dart'; class OnboardingPage extends StatelessWidget { const OnboardingPage({super.key, required this.nextRoute}); @@ -7,6 +8,14 @@ class OnboardingPage extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold(body: OnboardingButton(nextRoute: nextRoute)); + return const Scaffold( + extendBodyBehindAppBar: true, + extendBody: true, + appBar: NptAppBar( + isNavigateBack: false, + showSettings: false, + ), + body: OnboardingView(), + ); } } diff --git a/packages/dart/npt_flutter/lib/styles/app_theme.dart b/packages/dart/npt_flutter/lib/styles/app_theme.dart index 01d8210eb..65d0c7a2d 100644 --- a/packages/dart/npt_flutter/lib/styles/app_theme.dart +++ b/packages/dart/npt_flutter/lib/styles/app_theme.dart @@ -4,6 +4,14 @@ import 'package:npt_flutter/styles/sizes.dart'; class AppTheme { static TextTheme lightTextTheme = const TextTheme( + headlineLarge: TextStyle( + fontSize: Sizes.p32, + fontWeight: FontWeight.w600, + ), + headlineMedium: TextStyle( + fontSize: Sizes.p24, + fontWeight: FontWeight.w500, + ), titleMedium: TextStyle( fontSize: Sizes.p18, fontWeight: FontWeight.w600, diff --git a/packages/dart/npt_flutter/lib/styles/sizes.dart b/packages/dart/npt_flutter/lib/styles/sizes.dart index 1fbab9989..3f4d15374 100644 --- a/packages/dart/npt_flutter/lib/styles/sizes.dart +++ b/packages/dart/npt_flutter/lib/styles/sizes.dart @@ -19,7 +19,7 @@ class Sizes { static const p20 = 20.0; // static const p21 = 21.0; // static const p28 = 28.0; - // static const p24 = 24.0; + static const p24 = 24.0; static const p25 = 25.0; static const p27 = 27.0; static const p28 = 28.0; diff --git a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart index 454f4bfb7..1af50a9b2 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart @@ -1,13 +1,17 @@ +import 'dart:developer'; + import 'package:at_contacts_flutter/services/contact_service.dart'; import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; import 'package:at_onboarding_flutter/services/onboarding_service.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/constants.dart'; +import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; +import 'package:npt_flutter/features/onboarding/widgets/onboarding_button.dart'; import 'package:npt_flutter/routes.dart'; import 'package:url_launcher/url_launcher.dart'; -import '../features/onboarding/onboarding.dart'; import '../styles/sizes.dart'; import 'custom_snack_bar.dart'; @@ -77,7 +81,7 @@ class CustomTextButton extends StatelessWidget { // final bodyMedium = Theme.of(context).textTheme.bodyMedium!; // final bodySmall = Theme.of(context).textTheme.bodySmall!; final strings = AppLocalizations.of(context)!; - Future onTap() async { + Future onTap(String rootDomain) async { switch (type) { case CustomListTileType.email: Uri emailUri = Uri( @@ -117,14 +121,15 @@ class CustomTextButton extends StatelessWidget { } break; case CustomListTileType.resetAtsign: - final futurePreference = await loadAtClientPreference(); + log(rootDomain); + final futurePreference = await loadAtClientPreference(rootDomain); if (context.mounted) { final result = await AtOnboarding.reset( context: context, config: AtOnboardingConfig( atClientPreference: futurePreference, rootEnvironment: RootEnvironment.Testing, - domain: Constants.rootDomain, + domain: rootDomain, appAPIKey: Constants.appAPIKey, ), ); @@ -171,16 +176,20 @@ class CustomTextButton extends StatelessWidget { } } - return Padding( - padding: const EdgeInsets.only(left: Sizes.p30, right: Sizes.p30, bottom: Sizes.p10), - child: TextButton.icon( - label: Text(getTitle(strings)), - onPressed: onTap, - icon: Icon( - iconData, + return BlocBuilder(builder: (context, rootDomain) { + return Padding( + padding: const EdgeInsets.only(left: Sizes.p30, right: Sizes.p30, bottom: Sizes.p10), + child: TextButton.icon( + label: Text(getTitle(strings)), + onPressed: () { + onTap(rootDomain); + }, + icon: Icon( + iconData, + ), ), - ), - ); + ); + }); } } diff --git a/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart b/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart index 7bb797010..8f9e9382e 100644 --- a/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart +++ b/packages/dart/npt_flutter/lib/widgets/npt_app_bar.dart @@ -10,8 +10,15 @@ class NptAppBar extends StatelessWidget implements PreferredSizeWidget { final String title; final Color? settingsSelectedColor; final bool isNavigateBack; + final bool showSettings; - const NptAppBar({super.key, required this.title, this.settingsSelectedColor, this.isNavigateBack = true}); + const NptAppBar({ + super.key, + this.title = '', + this.settingsSelectedColor, + this.isNavigateBack = true, + this.showSettings = true, + }); @override Size get preferredSize => Size.fromHeight(isNavigateBack ? Sizes.p150 : Sizes.p100); @@ -73,14 +80,16 @@ class NptAppBar extends StatelessWidget implements PreferredSizeWidget { ], ), actions: [ - IconButton( - padding: const EdgeInsets.only(bottom: Sizes.p30), - color: settingsSelectedColor, - icon: const Icon(Icons.settings_outlined), - onPressed: () { - Navigator.pushNamed(context, '/settings'); - }, - ), + showSettings + ? IconButton( + padding: const EdgeInsets.only(bottom: Sizes.p30), + color: settingsSelectedColor, + icon: const Icon(Icons.settings_outlined), + onPressed: () { + Navigator.pushNamed(context, '/settings'); + }, + ) + : gap0, ], centerTitle: true, ); From 48c6d176597ea76d9669385dd2ae78e6e790e732 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 7 Oct 2024 12:22:23 -0400 Subject: [PATCH 10/23] feat: add interface for new atSign management utilities --- .../onboarding/util/atsign_manager.dart | 30 +++++++++++++++++++ .../onboarding/util/pre_offboard.dart | 8 +++++ 2 files changed, 38 insertions(+) create mode 100644 packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart create mode 100644 packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart new file mode 100644 index 000000000..73fc2a1a0 --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart @@ -0,0 +1,30 @@ +abstract class AtsignInformation { + String get atSign; + String get rootDomain; +} + +// This will return a map which looks like: +// +// { +// "@alice": AtsignInformation{ atSign: "@alice", rootDomain: "root.atsign.org" }, +// "@bob": AtsignInformation{ atSign: "@alice", rootDomain: "vip.ve.atsign.zone" }, +// } +// +// Note: AtsignInformation is a class, so usage will look like +// +// var atSign = "@alice"; +// var atSignInfo = await getAtsignEntries(); +// var rootDomain = atSignInfo[atSign].rootDomain; +// +// Now you have the rootDomain for the existing atSign and can use it to onboard +// correctly + +Future> getAtsignEntries() { + return Future.value({}); +} + +// This class will allow you to store atSign information +// you need to call this after onboarding a NEW atSign +Future saveAtsignInformation(AtsignInformation info) { + return Future.value(true); +} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart new file mode 100644 index 000000000..267285637 --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart @@ -0,0 +1,8 @@ +// Hand this method the atSign you wish to offboard +// Returns: a boolean, true = success, false = failed +Future preSignout(String atSign) async { + // We need to do the following before "signing out" + // - Wipe all application state + // - Remove the tray icon + return true; +} From 383f6292409d2ad6b04174f34e04385a696a114f Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 7 Oct 2024 15:20:37 -0400 Subject: [PATCH 11/23] feat: add atsign manager utility functions --- .../onboarding/util/atsign_manager.dart | 146 +++++++++++++++++- packages/dart/npt_flutter/pubspec.lock | 56 +++---- packages/dart/npt_flutter/pubspec.yaml | 1 + 3 files changed, 168 insertions(+), 35 deletions(-) diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart index 73fc2a1a0..4102aeb63 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart @@ -1,6 +1,31 @@ -abstract class AtsignInformation { - String get atSign; - String get rootDomain; +import 'dart:convert'; +import 'dart:io'; + +import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; +import 'package:npt_flutter/app.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as p; + +class AtsignInformation { + final String atSign; + final String rootDomain; + + AtsignInformation({required this.atSign, required this.rootDomain}); + + Map toJson() => { + "atsign": atSign, + "root-domain": rootDomain, + }; + + static AtsignInformation? fromJson(Map json) { + if (json["atsign"] is! String || json["root-domain"] is! String) { + return null; + } + return AtsignInformation( + atSign: json["atsign"], + rootDomain: json["root-domain"], + ); + } } // This will return a map which looks like: @@ -19,12 +44,119 @@ abstract class AtsignInformation { // Now you have the rootDomain for the existing atSign and can use it to onboard // correctly -Future> getAtsignEntries() { - return Future.value({}); +Future> getAtsignEntries() async { + var keychainAtSigns = await KeychainUtil.getAtsignList() ?? []; + var atSignInfo = []; + try { + atSignInfo = await _getAtsignInformationFromFile(); + } catch (e) { + App.log( + "Failed get Atsign Information, ignoring invalid file: ${e.toString()}" + .loggable, + ); + return {}; + } + var atSignMap = {}; + for (var item in atSignInfo) { + if (keychainAtSigns.contains(item.atSign)) { + atSignMap[item.atSign] = item; + } + } + return atSignMap; } // This class will allow you to store atSign information // you need to call this after onboarding a NEW atSign -Future saveAtsignInformation(AtsignInformation info) { - return Future.value(true); +Future saveAtsignInformation(AtsignInformation info) async { + var f = await _getAtsignInformationFile(); + final List atSignInfo; + try { + atSignInfo = await _getAtsignInformationFromFile(f); + } catch (e) { + // We only end up here if we failed to create, get, or read the file + // we don't want to overwrite it in that scenario, so return false + // + // We won't end up here if it was a json parse error, such as invalid + // json, we do want to overwrite that so that the app can recover as best + // as possible + return false; + } + if (f == null) return false; + + // Replace the existing entry with the new one if it exists + bool found = false; + for (int i = 0; i < atSignInfo.length; i++) { + if (atSignInfo[i].atSign == info.atSign) { + found = true; + atSignInfo[i] = info; + } + } + // Otherwise add it as a new entry + if (!found) { + atSignInfo.add(info); + } + try { + f.writeAsString( + jsonEncode(atSignInfo.map((e) => e.toJson())), + mode: FileMode.writeOnly, + flush: true, + ); + return true; + } catch (e) { + App.log( + "Failed to write Atsign Information to file: ${e.toString()}".loggable, + ); + return false; + } +} + +// Does not throw, returns null if it can't get / create the file +Future _getAtsignInformationFile() async { + final Directory dir; + try { + dir = await getApplicationSupportDirectory(); + dir.create(recursive: true); // This checks if it exists internally + } catch (e) { + App.log( + "Failed to Get Application Support Directory: ${e.toString()}".loggable, + ); + return null; + } + final f = File(p.join(dir.path, "atsign_information.json")); + try { + if (!await f.exists()) { + f.create(recursive: true); + } + return f; + } catch (e) { + App.log( + "Failed to Get Atsign Information File : ${e.toString()}".loggable, + ); + return null; + } +} + +Future> _getAtsignInformationFromFile([File? f]) async { + f ??= await _getAtsignInformationFile(); + if (f == null) throw Exception("Failed to get the Atsign Information File"); + try { + var contents = await f.readAsString(); + var json = jsonDecode(contents); + if (json is! Iterable) { + return []; // The file format is invalid so return as a non-error and we will overwrite it + } + var res = []; + for (var item in json) { + if (item is! Map) continue; + var info = AtsignInformation.fromJson(item); + if (info == null) continue; + res.add(info); + } + return res; + } catch (e) { + App.log( + "Failed to Parse Atsign Information File : ${e.toString()}".loggable, + ); + rethrow; + } } diff --git a/packages/dart/npt_flutter/pubspec.lock b/packages/dart/npt_flutter/pubspec.lock index 3cc2e0107..8a461d80d 100644 --- a/packages/dart/npt_flutter/pubspec.lock +++ b/packages/dart/npt_flutter/pubspec.lock @@ -67,10 +67,10 @@ packages: dependency: transitive description: name: at_auth - sha256: "28f72f0fc26ec7f5f58d28fd29f964c9b2b35ecdc8dd4805ed7174851da2cbcc" + sha256: f4fec32e2a1ca8827604b5e54a7611ddad092c6ba607c138675c1cba5215b038 url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.7" at_backupkey_flutter: dependency: transitive description: @@ -91,26 +91,26 @@ packages: dependency: transitive description: name: at_chops - sha256: "825171a3132b3756119bd16b6fd1fa6257f74a64babaf13cae2d82d53b8c6be1" + sha256: "0b3d84b8bd2e5027946253d907ff23f967922105efe27432b15743beb74b31f8" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" at_client: dependency: transitive description: name: at_client - sha256: "2e98fe0c0c520b8e7ad6dfd0ad53ecb97f1ceb33c9b117dda69417b72a067c60" + sha256: "2c6aca2b3a2dab16b58330f99bdd00fe05bd05a76ffc5ed6b0d0eb34aaaaab8a" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.2" at_client_mobile: dependency: "direct main" description: name: at_client_mobile - sha256: "749b686cf403d4f396fbce5b7684574d983669274553950d92542b50830e9d28" + sha256: "41f45cc094bfc7748303ce263e490d9744bb6014e4c1183df9e983488714fb7e" url: "https://pub.dev" source: hosted - version: "3.2.18" + version: "3.2.19" at_common_flutter: dependency: transitive description: @@ -123,26 +123,26 @@ packages: dependency: transitive description: name: at_commons - sha256: "2d0490a0c5bcd43c6a37911d85b71c133767aec47abc65bd8ecb20c8caaddeab" + sha256: "796eb7f49ab8894782010146368b4ae4f9ed716f2174c29c37d5c53b81281ff6" url: "https://pub.dev" source: hosted - version: "4.0.11" + version: "5.0.0" at_contact: dependency: "direct main" description: name: at_contact - sha256: e1b8904116e6e0fcbc5627409bffe3b620417c62b76bbedc84b1f66acc28adfe + sha256: e67a3545f2df3f0c8e28f0360c8cb301c1677043cd4b797c8d78dc92a69f2e62 url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.9" at_contacts_flutter: dependency: "direct main" description: name: at_contacts_flutter - sha256: "530a5112e303fdf8ae26bfbe477112b14c16c8b35f7005ea4919f05584fa8dec" + sha256: e39b77b302c5e12ec7a543b8f1f6b65eed42c36d153f8298dd3b52d7d1ad2051 url: "https://pub.dev" source: hosted - version: "4.0.15" + version: "4.0.16" at_demo_data: dependency: transitive description: @@ -163,27 +163,27 @@ packages: dependency: transitive description: name: at_lookup - sha256: e989099d5f2cd6415097c8e4353340bd2048c9ee1bc82665f2b4f7c4615ad055 + sha256: "2fa727fbdd6d3e5a79132786a74cbf03776833e1671f8cb471d21585f8448f95" url: "https://pub.dev" source: hosted - version: "3.0.47" + version: "3.0.49" at_onboarding_flutter: dependency: "direct main" description: path: "packages/at_onboarding_flutter" ref: trunk - resolved-ref: "8df0a468041dd332534acf5e5e487e018f6ba2da" + resolved-ref: "2a32ac2461673e0df16f5de2e24305309a8fcd95" url: "https://github.com/atsign-foundation/at_widgets" source: git - version: "6.1.8" + version: "6.1.9" at_persistence_secondary_server: dependency: transitive description: name: at_persistence_secondary_server - sha256: "1ec73b56e61b8aee94104ad4610c17cf07e366239337bedd43fa80c7765a391d" + sha256: "387ff2853ee98a8c65526e1df9220fa58c4631b9b1cd6002e9a7372f1a491ed3" url: "https://pub.dev" source: hosted - version: "3.0.63" + version: "3.0.64" at_persistence_spec: dependency: transitive description: @@ -196,10 +196,10 @@ packages: dependency: transitive description: name: at_server_status - sha256: "316c3e6717592677207d4f0a836b013271ca0f729e8b575c9195d19cfc57e71b" + sha256: "2773fa7c4377802b671f6854863214aabe8ee8cd49be87226352dd14562a5d6b" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" at_sync_ui_flutter: dependency: transitive description: @@ -220,10 +220,10 @@ packages: dependency: "direct main" description: name: at_utils - sha256: ec28600e4eec321ee5e22be051109fa7b2e94590dc51d9f957730c2540beb681 + sha256: b4461b0743f323429d57c387e91186537df8a6aeb4608bbeb6c2adf01d9f08f9 url: "https://pub.dev" source: hosted - version: "3.0.16" + version: "3.0.19" biometric_storage: dependency: transitive description: @@ -899,7 +899,7 @@ packages: path: "../noports_core" relative: true source: path - version: "6.1.0" + version: "6.2.0" openssh_ed25519: dependency: transitive description: @@ -933,7 +933,7 @@ packages: source: hosted version: "3.0.1" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -1509,10 +1509,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" watcher: dependency: transitive description: diff --git a/packages/dart/npt_flutter/pubspec.yaml b/packages/dart/npt_flutter/pubspec.yaml index a2e490743..e698155a1 100644 --- a/packages/dart/npt_flutter/pubspec.yaml +++ b/packages/dart/npt_flutter/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: meta: ^1.15.0 noports_core: path: ../noports_core + path: ^1.9.0 path_provider: ^2.1.4 phosphor_flutter: ^2.1.0 socket_connector: ^2.2.0 From eaa500221447b181a1f174c32b4226a8deb70294 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 7 Oct 2024 16:25:07 -0400 Subject: [PATCH 12/23] feat: add pre_offboard utility function --- packages/dart/npt_flutter/lib/app.dart | 15 +++--- .../features/favorite/bloc/favorite_bloc.dart | 2 + .../onboarding/cubit/at_directory_cubit.dart | 10 ++-- .../onboarding/util/pre_offboard.dart | 14 ++++- .../onboarding_at_directory_selector.dart | 14 +++-- .../onboarding/widgets/onboarding_button.dart | 13 +++-- .../features/profile/bloc/profile_bloc.dart | 53 +++++++++++++------ .../profile/cubit/profile_cache_cubit.dart | 2 + .../profile_list/bloc/profile_list_bloc.dart | 14 +++-- .../cubit/profiles_running_cubit.dart | 7 +++ .../features/settings/bloc/settings_bloc.dart | 2 + .../repository/contact_repository.dart | 11 ++-- .../tray_manager/cubit/tray_cubit.dart | 16 ++++-- .../dart/npt_flutter/lib/styles/sizes.dart | 25 +++++---- .../lib/widgets/custom_text_button.dart | 21 +++++--- 15 files changed, 156 insertions(+), 63 deletions(-) diff --git a/packages/dart/npt_flutter/lib/app.dart b/packages/dart/npt_flutter/lib/app.dart index 1522a593f..6ca42d71e 100644 --- a/packages/dart/npt_flutter/lib/app.dart +++ b/packages/dart/npt_flutter/lib/app.dart @@ -33,6 +33,8 @@ class App extends StatelessWidget { ], child: MultiBlocProvider( providers: [ + // TODO this should be called LocalSettingsCubit and move + // Localization from the SettingsCubit to this BlocProvider( create: (_) => EnableLoggingCubit(), ), @@ -48,6 +50,11 @@ class App extends StatelessWidget { create: (_) => OnboardingCubit(), ), + // A bloc which manages the atDirectory state + BlocProvider( + create: (_) => AtDirectoryCubit(), + ), + /// Settings provider, not much else to say /// - If settings are not found, we automatically load some defaults /// so it is possible that someone's settings get wiped if there is @@ -89,13 +96,9 @@ class App extends StatelessWidget { BlocProvider( create: (ctx) => FavoriteBloc(ctx.read()), ), - - // A bloc which manages the atDirectory state - BlocProvider( - create: (_) => AtDirectoryCubit(), - ), ], - child: BlocSelector(selector: (state) { + child: BlocSelector( + selector: (state) { if (state is SettingsLoadedState) { return state.settings.language; } diff --git a/packages/dart/npt_flutter/lib/features/favorite/bloc/favorite_bloc.dart b/packages/dart/npt_flutter/lib/features/favorite/bloc/favorite_bloc.dart index 0401e3f9d..1f6bd41ce 100644 --- a/packages/dart/npt_flutter/lib/features/favorite/bloc/favorite_bloc.dart +++ b/packages/dart/npt_flutter/lib/features/favorite/bloc/favorite_bloc.dart @@ -15,6 +15,8 @@ class FavoriteBloc extends LoggingBloc { on(_onRemove); } + void clearAll() => emit(const FavoritesInitial()); + FutureOr _onLoad( FavoriteLoadEvent event, Emitter emit) async { emit(const FavoritesLoading()); diff --git a/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart b/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart index 7dcdad21b..6ffd96e92 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart @@ -1,7 +1,9 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:npt_flutter/features/logging/models/loggable.dart'; +import 'package:npt_flutter/features/logging/models/logging_bloc.dart'; -class AtDirectoryCubit extends Cubit { - AtDirectoryCubit() : super('root.atsign.org'); +class AtDirectoryCubit extends LoggingCubit { + AtDirectoryCubit() : super(const LoggableString('root.atsign.org')); - void setRootDomain(String rootDomain) => emit(rootDomain); + void setRootDomain(String rootDomain) => emit(LoggableString(rootDomain)); + String getRootDomain() => (state.string); } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart index 267285637..152bfa6ef 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart @@ -1,8 +1,20 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:npt_flutter/app.dart'; +import 'package:npt_flutter/features/features.dart'; + // Hand this method the atSign you wish to offboard // Returns: a boolean, true = success, false = failed Future preSignout(String atSign) async { // We need to do the following before "signing out" // - Wipe all application state - // - Remove the tray icon + App.navState.currentContext?.read().stopAllAndClear(); + App.navState.currentContext?.read().clear(); + App.navState.currentContext?.read().deselectAll(); + App.navState.currentContext?.read().clearAll(); + App.navState.currentContext?.read().clearAll(); + App.navState.currentContext?.read().clear(); + App.navState.currentContext?.read().offboard(); + // - Reset the tray icon + App.navState.currentContext?.read().initialize(); return true; } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart index 08e0f3225..65864b456 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; typedef OnboardingMapCallback = void Function(Map val); @@ -17,8 +18,9 @@ class OnboardingAtDirectorySelector extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder(builder: (context, rootDomain) { - controller.text = rootDomain; + return BlocBuilder( + builder: (context, rootDomain) { + controller.text = rootDomain.string; return Column( children: [ Row( @@ -26,7 +28,9 @@ class OnboardingAtDirectorySelector extends StatelessWidget { children: [ Flexible( child: DropdownMenu( - initialSelection: options.contains(rootDomain) ? rootDomain : null, + initialSelection: options.contains(rootDomain.string) + ? rootDomain.string + : null, dropdownMenuEntries: options .map>( (o) => DropdownMenuEntry( @@ -56,7 +60,9 @@ class OnboardingAtDirectorySelector extends StatelessWidget { // validator: FormValidator.validateRequiredAtsignField, onChanged: (value) { // prevent the user from adding the default values to the dropdown a second time. - if (value != options[0] || value != options[1]) options.add(value); + if (value != options[0] || value != options[1]) { + options.add(value); + } //removes the third element making the final entry the only additional value in options. This prevents the dropdown from having more than 3 entries. if (options.length > 3) options.removeAt(2); diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart index 638eeba70..a44afba50 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/constants.dart'; +import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; import 'package:npt_flutter/features/onboarding/onboarding.dart'; import 'package:npt_flutter/features/onboarding/widgets/at_directory_dialog.dart'; @@ -35,12 +36,13 @@ class _OnboardingButtonState extends State { @override Widget build(BuildContext context) { final strings = AppLocalizations.of(context)!; - return BlocBuilder(builder: (context, rootDomain) { + return BlocBuilder( + builder: (context, rootDomain) { return ElevatedButton.icon( onPressed: () async { final result = await selectOptions(); - if (result && context.mounted) onboard(rootDomain: context.read().state); + if (result && context.mounted) onboard(rootDomain: rootDomain.string); }, icon: PhosphorIcon(PhosphorIcons.arrowUpRight()), label: Text( @@ -51,7 +53,8 @@ class _OnboardingButtonState extends State { }); } - Future onboard({required String rootDomain, bool isFromInitState = false}) async { + Future onboard( + {required String rootDomain, bool isFromInitState = false}) async { AtOnboardingResult onboardingResult = await AtOnboarding.onboard( // ignore: use_build_context_synchronously context: context, @@ -68,7 +71,9 @@ class _OnboardingButtonState extends State { case AtOnboardingResultStatus.success: await initializeContactsService(rootDomain: rootDomain); postOnboard(onboardingResult.atsign!); - Navigator.of(context).pushReplacementNamed(Routes.dashboard); + if (mounted) { + Navigator.of(context).pushReplacementNamed(Routes.dashboard); + } break; case AtOnboardingResultStatus.error: if (isFromInitState) break; diff --git a/packages/dart/npt_flutter/lib/features/profile/bloc/profile_bloc.dart b/packages/dart/npt_flutter/lib/features/profile/bloc/profile_bloc.dart index 480a1e453..2b9c92885 100644 --- a/packages/dart/npt_flutter/lib/features/profile/bloc/profile_bloc.dart +++ b/packages/dart/npt_flutter/lib/features/profile/bloc/profile_bloc.dart @@ -24,7 +24,8 @@ class ProfileBloc extends LoggingBloc { on(_onStart); on(_onStop); } - Future _onLoad(ProfileLoadEvent event, Emitter emit) async { + Future _onLoad( + ProfileLoadEvent event, Emitter emit) async { emit(ProfileLoading(uuid)); Profile? profile; @@ -42,7 +43,8 @@ class ProfileBloc extends LoggingBloc { emit(ProfileLoaded(uuid, profile: profile)); } - Future _onLoadOrCreate(ProfileLoadOrCreateEvent event, Emitter emit) async { + Future _onLoadOrCreate( + ProfileLoadOrCreateEvent event, Emitter emit) async { emit(ProfileLoading(uuid)); Profile? profile; @@ -71,14 +73,16 @@ class ProfileBloc extends LoggingBloc { emit(ProfileLoaded(uuid, profile: profile)); } - Future _onEdit(ProfileEditEvent event, Emitter emit) async { + Future _onEdit( + ProfileEditEvent event, Emitter emit) async { if (state is! ProfileLoaded && state is! ProfileFailedSave) { return; } emit(ProfileLoaded(uuid, profile: event.profile)); } - FutureOr _onSave(ProfileSaveEvent event, Emitter emit) async { + FutureOr _onSave( + ProfileSaveEvent event, Emitter emit) async { emit(ProfileLoading(uuid)); bool res; try { @@ -88,7 +92,9 @@ class ProfileBloc extends LoggingBloc { } if (res) { - App.navState.currentContext?.read().invalidate(uuid); + App.navState.currentContext + ?.read() + .invalidate(uuid); var listBloc = App.navState.currentContext?.read(); if (listBloc != null && listBloc.state is ProfileListLoaded) { @@ -103,12 +109,15 @@ class ProfileBloc extends LoggingBloc { } emit(ProfileLoaded(uuid, profile: event.profile)); } else { - App.navState.currentContext?.read().invalidate(uuid); + App.navState.currentContext + ?.read() + .invalidate(uuid); emit(ProfileFailedSave(uuid, profile: event.profile)); } } - Future _onStart(ProfileStartEvent event, Emitter emit) async { + Future _onStart( + ProfileStartEvent event, Emitter emit) async { if (state is! ProfileLoadedState || state is ProfileStarting || state is ProfileStopping || @@ -125,18 +134,23 @@ class ProfileBloc extends LoggingBloc { String? atSign = atClient.getCurrentAtSign(); if (atSign == null) { emit(ProfileFailedStart(uuid, profile: profile)); - App.navState.currentContext?.read().invalidate(uuid); + App.navState.currentContext + ?.read() + .invalidate(uuid); return; } - SettingsState? currentSettingsState = App.navState.currentContext?.read().state; + SettingsState? currentSettingsState = + App.navState.currentContext?.read().state; if (currentSettingsState is! SettingsLoadedState) { emit(ProfileFailedStart( uuid, profile: profile, reason: "Couldn't fetch settings", )); - App.navState.currentContext?.read().invalidate(uuid); + App.navState.currentContext + ?.read() + .invalidate(uuid); return; } var settings = currentSettingsState.settings; @@ -181,7 +195,9 @@ class ProfileBloc extends LoggingBloc { profile: profile, reason: 'Npt startup timedout', )); - App.navState.currentContext?.read().invalidate(uuid); + App.navState.currentContext + ?.read() + .invalidate(uuid); return; } @@ -192,7 +208,9 @@ class ProfileBloc extends LoggingBloc { profile: profile, reason: 'Socketconnector closed prematurely', )); - App.navState.currentContext?.read().invalidate(uuid); + App.navState.currentContext + ?.read() + .invalidate(uuid); return; } @@ -206,16 +224,21 @@ class ProfileBloc extends LoggingBloc { profile: profile, reason: 'Error during startup: $err', )); - App.navState.currentContext?.read().invalidate(uuid); + App.navState.currentContext + ?.read() + .invalidate(uuid); } finally { await npt?.done; cancel?.call(); - App.navState.currentContext?.read().invalidate(uuid); + App.navState.currentContext + ?.read() + .invalidate(uuid); emit(ProfileLoaded(uuid, profile: profile)); } } - Future _onStop(ProfileStopEvent event, Emitter emit) async { + Future _onStop( + ProfileStopEvent event, Emitter emit) async { if (state is! ProfileStarted) return; var profile = (state as ProfileStarted).profile; emit(ProfileStopping(uuid, profile: profile)); diff --git a/packages/dart/npt_flutter/lib/features/profile/cubit/profile_cache_cubit.dart b/packages/dart/npt_flutter/lib/features/profile/cubit/profile_cache_cubit.dart index 3420ce22f..3f620453a 100644 --- a/packages/dart/npt_flutter/lib/features/profile/cubit/profile_cache_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/profile/cubit/profile_cache_cubit.dart @@ -16,4 +16,6 @@ class ProfileCacheCubit extends LoggingCubit { emit(state.withAdded(uuid, bloc)); return bloc; } + + void clear() => emit(const ProfileCacheState({})); } diff --git a/packages/dart/npt_flutter/lib/features/profile_list/bloc/profile_list_bloc.dart b/packages/dart/npt_flutter/lib/features/profile_list/bloc/profile_list_bloc.dart index c3ea1178c..4cf5f4c19 100644 --- a/packages/dart/npt_flutter/lib/features/profile_list/bloc/profile_list_bloc.dart +++ b/packages/dart/npt_flutter/lib/features/profile_list/bloc/profile_list_bloc.dart @@ -17,7 +17,10 @@ class ProfileListBloc extends LoggingBloc { on(_onAdd); } - Future _onLoad(ProfileListLoadEvent event, Emitter emit) async { + void clearAll() => emit(const ProfileListInitial()); + + Future _onLoad( + ProfileListLoadEvent event, Emitter emit) async { emit(const ProfileListLoading()); Iterable? profiles; @@ -35,11 +38,13 @@ class ProfileListBloc extends LoggingBloc { emit(ProfileListLoaded(profiles: profiles)); } - Future _onUpdate(ProfileListUpdateEvent event, Emitter emit) async { + Future _onUpdate( + ProfileListUpdateEvent event, Emitter emit) async { emit(ProfileListLoaded(profiles: event.profiles)); } - Future _onDelete(ProfileListDeleteEvent event, Emitter emit) async { + Future _onDelete( + ProfileListDeleteEvent event, Emitter emit) async { // Don't allow deletes unless listed is loaded - this reduces the number of edge cases significantly if (state is! ProfileListLoaded) { return; @@ -64,7 +69,8 @@ class ProfileListBloc extends LoggingBloc { bloc?.add(FavoriteRemoveEvent(favoritesToRemove)); } - Future _onAdd(ProfileListAddEvent event, Emitter emit) async { + Future _onAdd( + ProfileListAddEvent event, Emitter emit) async { // Don't allow async bulk adds unless listed is loaded - this reduces the number of edge cases significantly if (state is! ProfileListLoaded) { return; diff --git a/packages/dart/npt_flutter/lib/features/profile_list/cubit/profiles_running_cubit.dart b/packages/dart/npt_flutter/lib/features/profile_list/cubit/profiles_running_cubit.dart index c4578049d..57b5c4da9 100644 --- a/packages/dart/npt_flutter/lib/features/profile_list/cubit/profiles_running_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/profile_list/cubit/profiles_running_cubit.dart @@ -17,4 +17,11 @@ class ProfilesRunningCubit extends LoggingCubit { void invalidate(String uuid) { emit(state.withoutConnector(uuid)); } + + void stopAllAndClear() { + state.socketConnectors.forEach((_, socketConnector) { + socketConnector?.close(); + }); + emit(const ProfilesRunningState({})); + } } diff --git a/packages/dart/npt_flutter/lib/features/settings/bloc/settings_bloc.dart b/packages/dart/npt_flutter/lib/features/settings/bloc/settings_bloc.dart index 1a073303b..01c28217f 100644 --- a/packages/dart/npt_flutter/lib/features/settings/bloc/settings_bloc.dart +++ b/packages/dart/npt_flutter/lib/features/settings/bloc/settings_bloc.dart @@ -12,6 +12,8 @@ class SettingsBloc extends LoggingBloc { on(_onEdit); } + void clear() => emit(const SettingsInitial()); + Future _onLoad( SettingsLoadEvent event, Emitter emit) async { emit(const SettingsLoading()); diff --git a/packages/dart/npt_flutter/lib/features/settings/repository/contact_repository.dart b/packages/dart/npt_flutter/lib/features/settings/repository/contact_repository.dart index 2d5ca1066..bd2e6d291 100644 --- a/packages/dart/npt_flutter/lib/features/settings/repository/contact_repository.dart +++ b/packages/dart/npt_flutter/lib/features/settings/repository/contact_repository.dart @@ -20,7 +20,6 @@ class ContactsService { final AtSignLogger _logger = AtSignLogger(Constants.namespace!); AtClient? atClient; - AtClientService? atClientService; var atClientManager = AtClientManager.getInstance(); static var atContactService = ContactService(); @@ -31,20 +30,24 @@ class ContactsService { /// Fetch the current atsign profile image Future getCurrentAtsignProfileImage() async { - return atContactService.getContactDetails(atClientManager.atClient.getCurrentAtSign(), null).then((value) { + return atContactService + .getContactDetails(atClientManager.atClient.getCurrentAtSign(), null) + .then((value) { return value['image']; }); } /// Fetch details for the current atsign Future> getCurrentAtsignContactDetails() { - return atContactService.getContactDetails(atClientManager.atClient.getCurrentAtSign(), null); + return atContactService.getContactDetails( + atClientManager.atClient.getCurrentAtSign(), null); } /// Delete contact from contact list. Future addContact(String atSign, String? nickname) async { try { - bool isAdded = await atContactService.addAtSign(atSign: atSign, nickName: nickname); + bool isAdded = + await atContactService.addAtSign(atSign: atSign, nickName: nickname); return isAdded; } on AtClientException catch (atClientExcep) { diff --git a/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart b/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart index ef2f751c5..433b983b2 100644 --- a/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart @@ -15,7 +15,8 @@ import 'package:window_manager/window_manager.dart'; part 'tray_cubit.g.dart'; part 'tray_state.dart'; -(String, void Function(MenuItem)) getAction(TrayAction action) => switch (action) { +(String, void Function(MenuItem)) getAction(TrayAction action) => + switch (action) { TrayAction.showDashboard => ('Show Window', (_) => windowManager.focus()), TrayAction.showSettings => ( 'Settings', @@ -85,10 +86,13 @@ class TrayCubit extends LoggingCubit { } Future reloadIcon() async { - final brightness = WidgetsBinding.instance.platformDispatcher.platformBrightness; + final brightness = + WidgetsBinding.instance.platformDispatcher.platformBrightness; await trayManager.setIcon(switch (brightness) { - Brightness.light => Platform.isWindows ? Constants.icoIconLight : Constants.pngIconLight, - Brightness.dark => Platform.isWindows ? Constants.icoIconDark : Constants.pngIconDark, + Brightness.light => + Platform.isWindows ? Constants.icoIconLight : Constants.pngIconLight, + Brightness.dark => + Platform.isWindows ? Constants.icoIconDark : Constants.pngIconDark, }); } @@ -114,7 +118,9 @@ class TrayCubit extends LoggingCubit { /// Generate the new menu based on current state var favMenuItems = await Future.wait( - favorites.where((fav) => fav.isLoadedInProfiles(profiles)).map((fav) async { + favorites + .where((fav) => fav.isLoadedInProfiles(profiles)) + .map((fav) async { /// Make sure to call [e.displayName] and [e.isRunning] only once to /// ensure good performance - these getters call a bunch of nested /// information from elsewhere in the app state diff --git a/packages/dart/npt_flutter/lib/styles/sizes.dart b/packages/dart/npt_flutter/lib/styles/sizes.dart index 3f4d15374..9ab9e4bb4 100644 --- a/packages/dart/npt_flutter/lib/styles/sizes.dart +++ b/packages/dart/npt_flutter/lib/styles/sizes.dart @@ -67,8 +67,8 @@ class Sizes { static const dashboardCardWidthFactor = 941 / 1053; static const profileFieldsWidthFactor = 150 / 1053; static const profileFieldsWidthFactorAlt = 300 / 1053; - static const SettingsCardWidthFactor = 654 / 1053; - static const SettingsCardHeightFactor = 438 / 691; + static const settingsCardWidthFactor = 654 / 1053; + static const settingsCardHeightFactor = 438 / 691; } const gap0 = SizedBox(); @@ -134,11 +134,14 @@ class SizeConfig { double textFactor = 1.0; - bool isMobile(BuildContext context) => MediaQuery.of(context).size.width < 700; + bool isMobile(BuildContext context) => + MediaQuery.of(context).size.width < 700; bool isTablet(BuildContext context) => - MediaQuery.of(context).size.width >= 700 && MediaQuery.of(context).size.width < 1200; - bool isDesktop(BuildContext context) => MediaQuery.of(context).size.width >= 1200; + MediaQuery.of(context).size.width >= 700 && + MediaQuery.of(context).size.width < 1200; + bool isDesktop(BuildContext context) => + MediaQuery.of(context).size.width >= 1200; void init() { _mediaQueryData = MediaQuery.of(App.navState.currentContext!); @@ -155,16 +158,20 @@ class SizeConfig { blockSizeHorizontal = screenWidth / 100; blockSizeVertical = screenHeight / 100; - _safeAreaHorizontal = _mediaQueryData.padding.left + _mediaQueryData.padding.right; - _safeAreaVertical = _mediaQueryData.padding.top + _mediaQueryData.padding.bottom; + _safeAreaHorizontal = + _mediaQueryData.padding.left + _mediaQueryData.padding.right; + _safeAreaVertical = + _mediaQueryData.padding.top + _mediaQueryData.padding.bottom; safeBlockHorizontal = (screenWidth - _safeAreaHorizontal) / 100; safeBlockVertical = (screenHeight - _safeAreaVertical) / 100; } else { blockSizeHorizontal = screenWidth / 120; blockSizeVertical = screenHeight / 120; - _safeAreaHorizontal = _mediaQueryData.padding.left + _mediaQueryData.padding.right; - _safeAreaVertical = _mediaQueryData.padding.top + _mediaQueryData.padding.bottom; + _safeAreaHorizontal = + _mediaQueryData.padding.left + _mediaQueryData.padding.right; + _safeAreaVertical = + _mediaQueryData.padding.top + _mediaQueryData.padding.bottom; safeBlockHorizontal = (screenWidth - _safeAreaHorizontal) / 120; safeBlockVertical = (screenHeight - _safeAreaVertical) / 120; } diff --git a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart index 1af50a9b2..6761fa15c 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart @@ -7,6 +7,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/constants.dart'; +import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; import 'package:npt_flutter/features/onboarding/widgets/onboarding_button.dart'; import 'package:npt_flutter/routes.dart'; @@ -93,13 +94,15 @@ class CustomTextButton extends StatelessWidget { } break; case CustomListTileType.discord: - final Uri url = Uri.parse('https://discord.gg/atsign-778383211214536722'); + final Uri url = + Uri.parse('https://discord.gg/atsign-778383211214536722'); if (!await launchUrl(url)) { throw Exception('Could not launch $url'); } break; case CustomListTileType.faq: - final Uri url = Uri.parse('https://docs.noports.com/ssh-no-ports/faq'); + final Uri url = + Uri.parse('https://docs.noports.com/ssh-no-ports/faq'); if (!await launchUrl(url)) { throw Exception('Could not launch $url'); } @@ -117,7 +120,8 @@ class CustomTextButton extends StatelessWidget { // break; case CustomListTileType.backupYourKey: if (context.mounted) { - BackupKeyWidget(atsign: ContactService().currentAtsign).showBackupDialog(context); + BackupKeyWidget(atsign: ContactService().currentAtsign) + .showBackupDialog(context); } break; case CustomListTileType.resetAtsign: @@ -133,7 +137,8 @@ class CustomTextButton extends StatelessWidget { appAPIKey: Constants.appAPIKey, ), ); - final OnboardingService onboardingService = OnboardingService.getInstance(); + final OnboardingService onboardingService = + OnboardingService.getInstance(); if (context.mounted && result == AtOnboardingResetResult.success) { onboardingService.setAtsign = null; @@ -176,13 +181,15 @@ class CustomTextButton extends StatelessWidget { } } - return BlocBuilder(builder: (context, rootDomain) { + return BlocBuilder( + builder: (context, rootDomain) { return Padding( - padding: const EdgeInsets.only(left: Sizes.p30, right: Sizes.p30, bottom: Sizes.p10), + padding: const EdgeInsets.only( + left: Sizes.p30, right: Sizes.p30, bottom: Sizes.p10), child: TextButton.icon( label: Text(getTitle(strings)), onPressed: () { - onTap(rootDomain); + onTap(rootDomain.string); }, icon: Icon( iconData, From a3712ca09fca1556858adee5894298f634aeffb0 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 7 Oct 2024 16:31:28 -0400 Subject: [PATCH 13/23] chore: add log message to preSignout since we bypass bloc events --- .../npt_flutter/lib/features/onboarding/util/pre_offboard.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart index 152bfa6ef..2705e5650 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart @@ -5,6 +5,7 @@ import 'package:npt_flutter/features/features.dart'; // Hand this method the atSign you wish to offboard // Returns: a boolean, true = success, false = failed Future preSignout(String atSign) async { + App.log("Resetting all application state before signout".loggable); // We need to do the following before "signing out" // - Wipe all application state App.navState.currentContext?.read().stopAllAndClear(); From e65da55a8f7f6712966c8d70a42d37816fca4547 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Mon, 7 Oct 2024 16:47:43 -0400 Subject: [PATCH 14/23] fix: forgot this file --- .../lib/features/settings/view/settings_view.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart b/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart index 84550e741..f4817f64a 100644 --- a/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart +++ b/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart @@ -38,7 +38,8 @@ class SettingsView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ CustomCard.settingsRail( - height: deviceSize.height * Sizes.SettingsCardHeightFactor, + height: + deviceSize.height * Sizes.settingsCardHeightFactor, child: const Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -56,8 +57,9 @@ class SettingsView extends StatelessWidget { ), ), CustomCard.settingsContent( - height: deviceSize.height * Sizes.SettingsCardHeightFactor, - width: deviceSize.width * Sizes.SettingsCardWidthFactor, + height: + deviceSize.height * Sizes.settingsCardHeightFactor, + width: deviceSize.width * Sizes.settingsCardWidthFactor, child: Padding( padding: const EdgeInsets.only( left: Sizes.p43, From 53b8958476d5d88da9e24a9d18039eb3e574aaf2 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Tue, 8 Oct 2024 08:47:56 -0400 Subject: [PATCH 15/23] feat: signout button added to the settings screen --- .../features/settings/view/settings_view.dart | 8 ++-- .../npt_flutter/lib/pages/loading_page.dart | 21 ++++++++++ .../dart/npt_flutter/lib/pages/pages.dart | 1 + packages/dart/npt_flutter/lib/routes.dart | 2 + .../lib/widgets/custom_text_button.dart | 41 ++++++++++++------- 5 files changed, 53 insertions(+), 20 deletions(-) create mode 100644 packages/dart/npt_flutter/lib/pages/loading_page.dart diff --git a/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart b/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart index f4817f64a..c1b63347f 100644 --- a/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart +++ b/packages/dart/npt_flutter/lib/features/settings/view/settings_view.dart @@ -38,8 +38,7 @@ class SettingsView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ CustomCard.settingsRail( - height: - deviceSize.height * Sizes.settingsCardHeightFactor, + height: deviceSize.height * Sizes.settingsCardHeightFactor, child: const Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -51,14 +50,13 @@ class SettingsView extends StatelessWidget { CustomTextButton.privacyPolicy(), CustomTextButton.feedback(), CustomTextButton.backUpYourKey(), - CustomTextButton.resetAtsign(), + CustomTextButton.signOut(), ContactListTile(), ], ), ), CustomCard.settingsContent( - height: - deviceSize.height * Sizes.settingsCardHeightFactor, + height: deviceSize.height * Sizes.settingsCardHeightFactor, width: deviceSize.width * Sizes.settingsCardWidthFactor, child: Padding( padding: const EdgeInsets.only( diff --git a/packages/dart/npt_flutter/lib/pages/loading_page.dart b/packages/dart/npt_flutter/lib/pages/loading_page.dart new file mode 100644 index 000000000..b26038519 --- /dev/null +++ b/packages/dart/npt_flutter/lib/pages/loading_page.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:npt_flutter/widgets/npt_app_bar.dart'; + +class LoadingPage extends StatelessWidget { + const LoadingPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + extendBodyBehindAppBar: true, + extendBody: true, + appBar: NptAppBar( + isNavigateBack: false, + showSettings: false, + ), + body: Center( + child: CircularProgressIndicator.adaptive(), + ), + ); + } +} diff --git a/packages/dart/npt_flutter/lib/pages/pages.dart b/packages/dart/npt_flutter/lib/pages/pages.dart index c5290f584..9a42a3181 100644 --- a/packages/dart/npt_flutter/lib/pages/pages.dart +++ b/packages/dart/npt_flutter/lib/pages/pages.dart @@ -1,4 +1,5 @@ export 'dashboard_page.dart'; +export 'loading_page.dart'; export 'onboarding_page.dart'; export 'profile_form_page.dart'; export 'settings_page.dart'; diff --git a/packages/dart/npt_flutter/lib/routes.dart b/packages/dart/npt_flutter/lib/routes.dart index ba5db84ab..765c46c68 100644 --- a/packages/dart/npt_flutter/lib/routes.dart +++ b/packages/dart/npt_flutter/lib/routes.dart @@ -7,11 +7,13 @@ class Routes { static const dashboard = '/dashboard'; static const settings = '/settings'; static const profileForm = '/profile'; + static const loadingPage = '/loading'; static final Map routes = { onboarding: (_) => const OnboardingPage(nextRoute: dashboard), dashboard: (_) => const DashboardPage(), settings: (_) => const SettingsPage(), profileForm: (_) => const ProfileFormPage(), + loadingPage: (_) => const LoadingPage(), }; } diff --git a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart index 6761fa15c..f89677c2e 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart @@ -1,15 +1,16 @@ -import 'dart:developer'; - import 'package:at_contacts_flutter/services/contact_service.dart'; import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; import 'package:at_onboarding_flutter/services/onboarding_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:npt_flutter/app.dart'; import 'package:npt_flutter/constants.dart'; import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; +import 'package:npt_flutter/features/onboarding/util/pre_offboard.dart'; import 'package:npt_flutter/features/onboarding/widgets/onboarding_button.dart'; +import 'package:npt_flutter/pages/loading_page.dart'; import 'package:npt_flutter/routes.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -65,6 +66,11 @@ class CustomTextButton extends StatelessWidget { this.title = 'Reset App', this.type = CustomListTileType.resetAtsign, super.key}); + const CustomTextButton.signOut( + {this.iconData = Icons.logout_outlined, + this.title = 'Sign Out', + this.type = CustomListTileType.signOut, + super.key}); const CustomTextButton.feedback( {this.iconData = Icons.feedback_outlined, @@ -94,15 +100,13 @@ class CustomTextButton extends StatelessWidget { } break; case CustomListTileType.discord: - final Uri url = - Uri.parse('https://discord.gg/atsign-778383211214536722'); + final Uri url = Uri.parse('https://discord.gg/atsign-778383211214536722'); if (!await launchUrl(url)) { throw Exception('Could not launch $url'); } break; case CustomListTileType.faq: - final Uri url = - Uri.parse('https://docs.noports.com/ssh-no-ports/faq'); + final Uri url = Uri.parse('https://docs.noports.com/ssh-no-ports/faq'); if (!await launchUrl(url)) { throw Exception('Could not launch $url'); } @@ -120,12 +124,10 @@ class CustomTextButton extends StatelessWidget { // break; case CustomListTileType.backupYourKey: if (context.mounted) { - BackupKeyWidget(atsign: ContactService().currentAtsign) - .showBackupDialog(context); + BackupKeyWidget(atsign: ContactService().currentAtsign).showBackupDialog(context); } break; case CustomListTileType.resetAtsign: - log(rootDomain); final futurePreference = await loadAtClientPreference(rootDomain); if (context.mounted) { final result = await AtOnboarding.reset( @@ -137,8 +139,7 @@ class CustomTextButton extends StatelessWidget { appAPIKey: Constants.appAPIKey, ), ); - final OnboardingService onboardingService = - OnboardingService.getInstance(); + final OnboardingService onboardingService = OnboardingService.getInstance(); if (context.mounted && result == AtOnboardingResetResult.success) { onboardingService.setAtsign = null; @@ -157,6 +158,14 @@ class CustomTextButton extends StatelessWidget { if (!await launchUrl(emailUri)) { CustomSnackBar.notification(content: 'No email client available'); } + break; + + case CustomListTileType.signOut: + Navigator.of(context) + .pushAndRemoveUntil(MaterialPageRoute(builder: (context) => const LoadingPage()), (route) => false); + await preSignout(ContactService().currentAtsign); + Navigator.of(context).pushReplacementNamed(Routes.onboarding); + break; } } @@ -178,14 +187,15 @@ class CustomTextButton extends StatelessWidget { return strings.resetAtsign; case CustomListTileType.feedback: return strings.feedback; + case CustomListTileType.signOut: + // TODO Localize in the next PR. + return 'signOut'; } } - return BlocBuilder( - builder: (context, rootDomain) { + return BlocBuilder(builder: (context, rootDomain) { return Padding( - padding: const EdgeInsets.only( - left: Sizes.p30, right: Sizes.p30, bottom: Sizes.p10), + padding: const EdgeInsets.only(left: Sizes.p30, right: Sizes.p30, bottom: Sizes.p10), child: TextButton.icon( label: Text(getTitle(strings)), onPressed: () { @@ -209,4 +219,5 @@ enum CustomListTileType { backupYourKey, resetAtsign, feedback, + signOut, } From c825674bf3bd8603dde453ac0464ff0dcef10527 Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Tue, 8 Oct 2024 08:55:53 -0400 Subject: [PATCH 16/23] fix: parameter removed from preSignout. --- .../npt_flutter/lib/features/onboarding/util/pre_offboard.dart | 2 +- packages/dart/npt_flutter/lib/widgets/custom_text_button.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart index 2705e5650..5661b47e5 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart @@ -4,7 +4,7 @@ import 'package:npt_flutter/features/features.dart'; // Hand this method the atSign you wish to offboard // Returns: a boolean, true = success, false = failed -Future preSignout(String atSign) async { +Future preSignout() async { App.log("Resetting all application state before signout".loggable); // We need to do the following before "signing out" // - Wipe all application state diff --git a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart index f89677c2e..7c51ceb99 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart @@ -163,7 +163,7 @@ class CustomTextButton extends StatelessWidget { case CustomListTileType.signOut: Navigator.of(context) .pushAndRemoveUntil(MaterialPageRoute(builder: (context) => const LoadingPage()), (route) => false); - await preSignout(ContactService().currentAtsign); + await preSignout(); Navigator.of(context).pushReplacementNamed(Routes.onboarding); break; } From 54c49e57353d291bcbe876b8c83fed7316795f7f Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Tue, 8 Oct 2024 19:23:11 -0400 Subject: [PATCH 17/23] feat: root domain removed from onboarding flow. --- .../onboarding/util/atsign_manager.dart | 5 +- ...boaring_view.dart => onboarding_view.dart} | 8 +- .../widgets/at_directory_dialog.dart | 59 +++---------- .../onboarding/widgets/atsign_dialog.dart | 22 +++++ .../onboarding/widgets/atsign_selector.dart | 83 +++++++++++++++++++ .../onboarding/widgets/onboarding_button.dart | 58 +++++++------ .../onboarding/widgets/onboarding_dialog.dart | 64 ++++++++++++++ .../lib/pages/onboarding_page.dart | 2 +- .../lib/widgets/custom_text_button.dart | 16 ++++ 9 files changed, 238 insertions(+), 79 deletions(-) rename packages/dart/npt_flutter/lib/features/onboarding/view/{onboaring_view.dart => onboarding_view.dart} (86%) create mode 100644 packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_dialog.dart create mode 100644 packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart create mode 100644 packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_dialog.dart diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart index 4102aeb63..198c0c54f 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart @@ -3,8 +3,8 @@ import 'dart:io'; import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; import 'package:npt_flutter/app.dart'; -import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; class AtsignInformation { final String atSign; @@ -51,8 +51,7 @@ Future> getAtsignEntries() async { atSignInfo = await _getAtsignInformationFromFile(); } catch (e) { App.log( - "Failed get Atsign Information, ignoring invalid file: ${e.toString()}" - .loggable, + "Failed get Atsign Information, ignoring invalid file: ${e.toString()}".loggable, ); return {}; } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/view/onboaring_view.dart b/packages/dart/npt_flutter/lib/features/onboarding/view/onboarding_view.dart similarity index 86% rename from packages/dart/npt_flutter/lib/features/onboarding/view/onboaring_view.dart rename to packages/dart/npt_flutter/lib/features/onboarding/view/onboarding_view.dart index f7fc2090a..1f5b5db67 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/view/onboaring_view.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/view/onboarding_view.dart @@ -43,7 +43,13 @@ class OnboardingView extends StatelessWidget { bottom: Sizes.p44, right: Sizes.p44, ), - child: CustomTextButton.resetAtsign(), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + CustomTextButton.resetAtsign(), + CustomTextButton.selectRootDomain(), + ], + ), ), ) ], diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart index 5c4be911f..a365208f6 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart @@ -1,61 +1,22 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/features/onboarding/widgets/onboarding_at_directory_selector.dart'; -import 'package:npt_flutter/styles/sizes.dart'; -import 'package:npt_flutter/widgets/custom_container.dart'; +import 'package:npt_flutter/features/onboarding/widgets/onboarding_dialog.dart'; -class AtDirectoryDialog extends StatefulWidget { +class AtDirectoryDialog extends StatelessWidget { const AtDirectoryDialog({super.key}); - @override - State createState() => _AtDirectoryDialogState(); -} - -class _AtDirectoryDialogState extends State { @override Widget build(BuildContext context) { final strings = AppLocalizations.of(context)!; - return AlertDialog( - backgroundColor: Colors.white, - content: Padding( - padding: const EdgeInsets.symmetric(vertical: Sizes.p12, horizontal: Sizes.p16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - CustomContainer.background( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(strings.atDirectory), - Text(strings.atDirectorySubtitle), - gapH16, - OnboardingAtDirectorySelector(), - ], - ), - ), - gapH10, - CustomContainer.background( - child: Row( - children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(false); - }, - child: Text(strings.cancel), - ), - const Spacer(), - ElevatedButton( - onPressed: () { - Navigator.of(context).pop(true); - }, - child: Text(strings.onboard), - ), - ], - )) - ], - ), - ), + return OnboardingDialog( + title: strings.atDirectory, + subtitle: strings.atDirectorySubtitle, + // TODO: Add success button text to the AppLocalizations + successButtonText: 'select', + children: [ + OnboardingAtDirectorySelector(), + ], ); } } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_dialog.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_dialog.dart new file mode 100644 index 000000000..f3c840111 --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_dialog.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:npt_flutter/features/onboarding/widgets/atsign_selector.dart'; +import 'package:npt_flutter/features/onboarding/widgets/onboarding_dialog.dart'; + +class AtSignDialog extends StatelessWidget { + const AtSignDialog({super.key}); + + @override + Widget build(BuildContext context) { + final strings = AppLocalizations.of(context)!; + return OnboardingDialog( + // TODO: Add title, subtitle and success button text to the AppLocalizations + title: 'AtSign', + subtitle: 'Please select your @sign', + successButtonText: 'Next', + children: [ + AtsignSelector(), + ], + ); + } +} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart new file mode 100644 index 000000000..b429b6843 --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:npt_flutter/features/logging/models/loggable.dart'; +import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; + +typedef OnboardingMapCallback = void Function(Map val); + +class AtsignSelector extends StatefulWidget { + AtsignSelector({ + super.key, + }); + + final List options = []; + + @override + State createState() => _AtsignSelectorState(); +} + +class _AtsignSelectorState extends State { + final FocusNode focusNode = FocusNode(); + + final TextEditingController controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + return BlocBuilder(builder: (context, rootDomain) { + controller.text = rootDomain.string; + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: DropdownMenu( + initialSelection: widget.options.contains(rootDomain.string) ? rootDomain.string : null, + dropdownMenuEntries: widget.options + .map>( + (o) => DropdownMenuEntry( + value: o, + label: o, + ), + ) + .toList(), + onSelected: (value) { + if (value == null) return; + + context.read().setRootDomain(value); + }, + ), + ), + Flexible( + child: KeyboardListener( + focusNode: focusNode, + onKeyEvent: (value) { + if (value.logicalKey == LogicalKeyboardKey.backspace) { + if (widget.options.length > 2) widget.options.removeLast(); + } + }, + child: TextFormField( + controller: controller, + autovalidateMode: AutovalidateMode.onUserInteraction, + // validator: FormValidator.validateRequiredAtsignField, + onChanged: (value) { + // prevent the user from adding the default values to the dropdown a second time. + if (value != widget.options[0] || value != widget.options[1]) { + widget.options.add(value); + } + //removes the third element making the final entry the only additional value in options. This prevents the dropdown from having more than 3 entries. + if (widget.options.length > 3) widget.options.removeAt(2); + + context.read().setRootDomain(value); + }, + ), + ), + ) + ], + ), + ], + ); + }); + } +} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart index a44afba50..0ffa954e8 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart @@ -1,3 +1,5 @@ +import 'dart:developer'; + import 'package:at_contacts_flutter/at_contacts_flutter.dart'; import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; import 'package:flutter/material.dart'; @@ -7,7 +9,8 @@ import 'package:npt_flutter/constants.dart'; import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; import 'package:npt_flutter/features/onboarding/onboarding.dart'; -import 'package:npt_flutter/features/onboarding/widgets/at_directory_dialog.dart'; +import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; +import 'package:npt_flutter/features/onboarding/widgets/atsign_dialog.dart'; import 'package:npt_flutter/routes.dart'; import 'package:path_provider/path_provider.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; @@ -33,28 +36,7 @@ class OnboardingButton extends StatefulWidget { } class _OnboardingButtonState extends State { - @override - Widget build(BuildContext context) { - final strings = AppLocalizations.of(context)!; - return BlocBuilder( - builder: (context, rootDomain) { - return ElevatedButton.icon( - onPressed: () async { - final result = await selectOptions(); - - if (result && context.mounted) onboard(rootDomain: rootDomain.string); - }, - icon: PhosphorIcon(PhosphorIcons.arrowUpRight()), - label: Text( - strings.getStarted, - ), - iconAlignment: IconAlignment.end, - ); - }); - } - - Future onboard( - {required String rootDomain, bool isFromInitState = false}) async { + Future onboard({required String rootDomain, bool isFromInitState = false}) async { AtOnboardingResult onboardingResult = await AtOnboarding.onboard( // ignore: use_build_context_synchronously context: context, @@ -71,6 +53,10 @@ class _OnboardingButtonState extends State { case AtOnboardingResultStatus.success: await initializeContactsService(rootDomain: rootDomain); postOnboard(onboardingResult.atsign!); + final result = + await saveAtsignInformation(AtsignInformation(atSign: onboardingResult.atsign!, rootDomain: rootDomain)); + log('atsign result is:$result'); + if (mounted) { Navigator.of(context).pushReplacementNamed(Routes.dashboard); } @@ -90,11 +76,33 @@ class _OnboardingButtonState extends State { } } - Future selectOptions() async { + Future selectAtsign() async { final results = await showDialog( context: context, - builder: (BuildContext context) => const AtDirectoryDialog(), + builder: (BuildContext context) => const AtSignDialog(), ); return results ?? false; } + + @override + Widget build(BuildContext context) { + final strings = AppLocalizations.of(context)!; + return BlocBuilder(builder: (context, rootDomain) { + return ElevatedButton.icon( + onPressed: () async { + final isEmptyAtsignList = (await getAtsignEntries()).isNotEmpty; + log(isEmptyAtsignList.toString()); + + if (isEmptyAtsignList) await selectAtsign(); + + onboard(rootDomain: rootDomain.string); + }, + icon: PhosphorIcon(PhosphorIcons.arrowUpRight()), + label: Text( + strings.getStarted, + ), + iconAlignment: IconAlignment.end, + ); + }); + } } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_dialog.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_dialog.dart new file mode 100644 index 000000000..796f8f46b --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_dialog.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:npt_flutter/styles/sizes.dart'; +import 'package:npt_flutter/widgets/custom_container.dart'; + +class OnboardingDialog extends StatelessWidget { + const OnboardingDialog( + {required this.title, + required this.subtitle, + required this.successButtonText, + required this.children, + super.key}); + final String title; + final String subtitle; + final String successButtonText; + final List children; + + @override + Widget build(BuildContext context) { + final strings = AppLocalizations.of(context)!; + return AlertDialog( + backgroundColor: Colors.white, + content: Padding( + padding: const EdgeInsets.symmetric(vertical: Sizes.p12, horizontal: Sizes.p16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + CustomContainer.background( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(title), + Text(subtitle), + gapH16, + ...children, + ], + ), + ), + gapH10, + CustomContainer.background( + child: Row( + children: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(false); + }, + child: Text(strings.cancel), + ), + const Spacer(), + ElevatedButton( + onPressed: () { + Navigator.of(context).pop(true); + }, + child: Text(successButtonText), + ), + ], + )) + ], + ), + ), + ); + } +} diff --git a/packages/dart/npt_flutter/lib/pages/onboarding_page.dart b/packages/dart/npt_flutter/lib/pages/onboarding_page.dart index 8a52b2d28..4c55a08a8 100644 --- a/packages/dart/npt_flutter/lib/pages/onboarding_page.dart +++ b/packages/dart/npt_flutter/lib/pages/onboarding_page.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:npt_flutter/features/onboarding/view/onboaring_view.dart'; +import 'package:npt_flutter/features/onboarding/view/onboarding_view.dart'; import 'package:npt_flutter/widgets/npt_app_bar.dart'; class OnboardingPage extends StatelessWidget { diff --git a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart index 7c51ceb99..478db0f35 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart @@ -9,6 +9,7 @@ import 'package:npt_flutter/constants.dart'; import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; import 'package:npt_flutter/features/onboarding/util/pre_offboard.dart'; +import 'package:npt_flutter/features/onboarding/widgets/at_directory_dialog.dart'; import 'package:npt_flutter/features/onboarding/widgets/onboarding_button.dart'; import 'package:npt_flutter/pages/loading_page.dart'; import 'package:npt_flutter/routes.dart'; @@ -77,6 +78,11 @@ class CustomTextButton extends StatelessWidget { this.title = 'Feedback', this.type = CustomListTileType.feedback, super.key}); + const CustomTextButton.selectRootDomain( + {this.iconData = Icons.dns_outlined, + this.title = 'Select Root Domain', + this.type = CustomListTileType.selectRootDomain, + super.key}); final IconData iconData; final String title; @@ -166,6 +172,12 @@ class CustomTextButton extends StatelessWidget { await preSignout(); Navigator.of(context).pushReplacementNamed(Routes.onboarding); break; + case CustomListTileType.selectRootDomain: + await showDialog( + context: context, + builder: (BuildContext context) => const AtDirectoryDialog(), + ); + break; } } @@ -190,6 +202,9 @@ class CustomTextButton extends StatelessWidget { case CustomListTileType.signOut: // TODO Localize in the next PR. return 'signOut'; + case CustomListTileType.selectRootDomain: + // TODO Localize in the next PR. + return 'Select Root Domain'; } } @@ -220,4 +235,5 @@ enum CustomListTileType { resetAtsign, feedback, signOut, + selectRootDomain, } From 0dd3caf99b27b22996ea4cf71d8737fe4aff4942 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Tue, 8 Oct 2024 19:40:04 -0400 Subject: [PATCH 18/23] fix: handle empty file case --- .../npt_flutter/lib/features/onboarding/util/atsign_manager.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart index 198c0c54f..6e9dbd2a1 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart @@ -140,6 +140,7 @@ Future> _getAtsignInformationFromFile([File? f]) async { if (f == null) throw Exception("Failed to get the Atsign Information File"); try { var contents = await f.readAsString(); + if (contents.trim().isEmpty) return []; var json = jsonDecode(contents); if (json is! Iterable) { return []; // The file format is invalid so return as a non-error and we will overwrite it From a4f738f0dee0ba154e17fc0fc34e97fd9ffc24b4 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Tue, 8 Oct 2024 20:10:32 -0400 Subject: [PATCH 19/23] chore: localize relay selection --- .../widgets/profile_relay_quick_buttons.dart | 42 +++++++++---------- .../repository/settings_repository.dart | 12 ++---- .../widgets/settings_relay_quick_buttons.dart | 42 +++++++++---------- .../npt_flutter/lib/localization/app_en.arb | 5 ++- packages/dart/npt_flutter/lib/util/relay.dart | 17 ++++++++ .../lib/widgets/custom_text_button.dart | 2 +- 6 files changed, 68 insertions(+), 52 deletions(-) create mode 100644 packages/dart/npt_flutter/lib/util/relay.dart diff --git a/packages/dart/npt_flutter/lib/features/profile_form/widgets/profile_relay_quick_buttons.dart b/packages/dart/npt_flutter/lib/features/profile_form/widgets/profile_relay_quick_buttons.dart index 2e5ffcfdf..753d7f973 100644 --- a/packages/dart/npt_flutter/lib/features/profile_form/widgets/profile_relay_quick_buttons.dart +++ b/packages/dart/npt_flutter/lib/features/profile_form/widgets/profile_relay_quick_buttons.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:npt_flutter/constants.dart'; import 'package:npt_flutter/features/profile/profile.dart'; import 'package:npt_flutter/features/profile_form/widgets/profile_relay_at_sign_text_field.dart'; import 'package:npt_flutter/styles/sizes.dart'; +import 'package:npt_flutter/util/relay.dart'; import 'package:npt_flutter/widgets/custom_container.dart'; class ProfileRelayQuickButtons extends StatelessWidget { @@ -44,29 +44,29 @@ class ProfileRelayQuickButtons extends StatelessWidget { scrollDirection: Axis.horizontal, controller: controller, children: [ - ...Constants.defaultRelayOptions.entries.map( - (e) => Padding( - padding: const EdgeInsets.only(right: Sizes.p10), - child: CustomContainer.foreground( - key: Key(e.key), - child: SizedBox( - width: Sizes.p200, - height: Sizes.p50, - child: RadioListTile( - title: Text(e.value), - value: e.key, - groupValue: relayAtsign, - onChanged: (value) { - var bloc = context.read(); - bloc.add(ProfileEditEvent( - profile: (bloc.state as ProfileLoadedState).profile.copyWith(relayAtsign: value), - )); - }, + ...RelayUtil.getRelayDisplayNameMap(context).entries.map( + (e) => Padding( + padding: const EdgeInsets.only(right: Sizes.p10), + child: CustomContainer.foreground( + key: Key(e.key), + child: SizedBox( + width: Sizes.p200, + height: Sizes.p50, + child: RadioListTile( + title: Text(e.value), + value: e.key, + groupValue: relayAtsign, + onChanged: (value) { + var bloc = context.read(); + bloc.add(ProfileEditEvent( + profile: (bloc.state as ProfileLoadedState).profile.copyWith(relayAtsign: value), + )); + }, + ), + ), ), ), ), - ), - ), const ProfileRelayAtSignTextField(), ], ), diff --git a/packages/dart/npt_flutter/lib/features/settings/repository/settings_repository.dart b/packages/dart/npt_flutter/lib/features/settings/repository/settings_repository.dart index 9c6adfd1a..863f97f8e 100644 --- a/packages/dart/npt_flutter/lib/features/settings/repository/settings_repository.dart +++ b/packages/dart/npt_flutter/lib/features/settings/repository/settings_repository.dart @@ -6,11 +6,9 @@ import 'package:npt_flutter/features/settings/settings.dart'; class SettingsRepository { const SettingsRepository(); - AtKey get settingsAtKey => - AtKey.self('settings', namespace: Constants.namespace).build(); + AtKey get settingsAtKey => AtKey.self('settings', namespace: Constants.namespace).build(); - Settings get defaultSettings => Settings( - relayAtsign: Constants.defaultRelayOptions.values.first, + Settings get defaultSettings => const Settings( viewLayout: PreferredViewLayout.minimal, overrideRelay: false, ); @@ -18,8 +16,7 @@ class SettingsRepository { Future getSettings() async { AtClient atClient = AtClientManager.getInstance().atClient; try { - var value = await atClient - .get(settingsAtKey..sharedBy = atClient.getCurrentAtSign()); + var value = await atClient.get(settingsAtKey..sharedBy = atClient.getCurrentAtSign()); if (value.value == null) { // No settings saved, so use the defaults return defaultSettings; @@ -43,8 +40,7 @@ class SettingsRepository { Future deleteSettings(Settings settings) async { AtClient atClient = AtClientManager.getInstance().atClient; try { - return await atClient - .delete(settingsAtKey..sharedBy = atClient.getCurrentAtSign()); + return await atClient.delete(settingsAtKey..sharedBy = atClient.getCurrentAtSign()); } catch (_) { return false; } diff --git a/packages/dart/npt_flutter/lib/features/settings/widgets/settings_relay_quick_buttons.dart b/packages/dart/npt_flutter/lib/features/settings/widgets/settings_relay_quick_buttons.dart index 7c2b57bab..1888bfb47 100644 --- a/packages/dart/npt_flutter/lib/features/settings/widgets/settings_relay_quick_buttons.dart +++ b/packages/dart/npt_flutter/lib/features/settings/widgets/settings_relay_quick_buttons.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:npt_flutter/constants.dart'; import 'package:npt_flutter/features/settings/settings.dart'; +import 'package:npt_flutter/util/relay.dart'; import 'package:npt_flutter/widgets/custom_container.dart'; import '../../../styles/sizes.dart'; @@ -29,29 +29,29 @@ class SettingsRelayQuickButtons extends StatelessWidget { controller: controller, scrollDirection: Axis.horizontal, children: [ - ...Constants.defaultRelayOptions.entries.map( - (e) => Padding( - padding: const EdgeInsets.only(right: Sizes.p10), - child: CustomContainer.foreground( - key: Key(e.key), - child: SizedBox( - width: Sizes.p180, - child: RadioListTile( - title: Text(e.value), - value: e.key, - groupValue: relayAtsign, - onChanged: (value) { - var bloc = context.read(); - bloc.add(SettingsEditEvent( - settings: (bloc.state as SettingsLoadedState).settings.copyWith(relayAtsign: value), - save: true, - )); - }, + ...RelayUtil.getRelayDisplayNameMap(context).entries.map( + (e) => Padding( + padding: const EdgeInsets.only(right: Sizes.p10), + child: CustomContainer.foreground( + key: Key(e.key), + child: SizedBox( + width: Sizes.p180, + child: RadioListTile( + title: Text(e.value), + value: e.key, + groupValue: relayAtsign, + onChanged: (value) { + var bloc = context.read(); + bloc.add(SettingsEditEvent( + settings: (bloc.state as SettingsLoadedState).settings.copyWith(relayAtsign: value), + save: true, + )); + }, + ), + ), ), ), ), - ), - ), const SettingsRelayAtSignTextField(), ], ), diff --git a/packages/dart/npt_flutter/lib/localization/app_en.arb b/packages/dart/npt_flutter/lib/localization/app_en.arb index ca172fb49..0ca210b3e 100644 --- a/packages/dart/npt_flutter/lib/localization/app_en.arb +++ b/packages/dart/npt_flutter/lib/localization/app_en.arb @@ -53,6 +53,9 @@ "remotePort" : "Remote Port", "remotePortDescription" : "", "resetAtsign" : "Reset Atsign", + "rvAmDisplayName" : "Americas", + "rvApDisplayName" : "Asia-Pacific", + "rvEuDisplayName" : "Europe", "selectExportFile": "Please select a file to export to:", "serviceMapping" : "Service Mapping", "settings" : "Settings", @@ -76,4 +79,4 @@ "onboard" : "Onboard", "onboardingError" : "An error has occurred", "yaml" : "YAML" -} \ No newline at end of file +} diff --git a/packages/dart/npt_flutter/lib/util/relay.dart b/packages/dart/npt_flutter/lib/util/relay.dart new file mode 100644 index 000000000..4df31f2f9 --- /dev/null +++ b/packages/dart/npt_flutter/lib/util/relay.dart @@ -0,0 +1,17 @@ +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter/material.dart'; + +class RelayUtil { + static Map getRelayDisplayNameMap(BuildContext context) { + final strings = AppLocalizations.of(context)!; + return { + "@rv_am": strings.rvAmDisplayName, + "@rv_ap": strings.rvApDisplayName, + "@rv_eu": strings.rvEuDisplayName, + }; + } + + static List getRelayAtsignList() { + return ["@rv_am", "@rv_ap", "@rv_eu"]; + } +} diff --git a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart index 478db0f35..432f4a0ef 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart @@ -170,7 +170,7 @@ class CustomTextButton extends StatelessWidget { Navigator.of(context) .pushAndRemoveUntil(MaterialPageRoute(builder: (context) => const LoadingPage()), (route) => false); await preSignout(); - Navigator.of(context).pushReplacementNamed(Routes.onboarding); + if (context.mounted) Navigator.of(context).pushReplacementNamed(Routes.onboarding); break; case CustomListTileType.selectRootDomain: await showDialog( From e6522b0296f9d002d13bfa79071998242f39bf7d Mon Sep 17 00:00:00 2001 From: Curtly Critchlow Date: Wed, 9 Oct 2024 12:00:07 -0400 Subject: [PATCH 20/23] fix: user can select atsign if available in keychain when onboarding. --- packages/dart/npt_flutter/lib/app.dart | 3 +- .../onboarding/cubit/at_directory_cubit.dart | 13 +++-- .../onboarding/util/atsign_manager.dart | 14 ++++-- .../widgets/at_directory_dialog.dart | 2 +- .../onboarding/widgets/atsign_selector.dart | 49 +++++++++++++------ .../onboarding_at_directory_selector.dart | 12 ++--- .../onboarding/widgets/onboarding_button.dart | 7 ++- .../lib/widgets/custom_text_button.dart | 45 +++++++++++------ 8 files changed, 92 insertions(+), 53 deletions(-) diff --git a/packages/dart/npt_flutter/lib/app.dart b/packages/dart/npt_flutter/lib/app.dart index 6ca42d71e..ba17559d9 100644 --- a/packages/dart/npt_flutter/lib/app.dart +++ b/packages/dart/npt_flutter/lib/app.dart @@ -97,8 +97,7 @@ class App extends StatelessWidget { create: (ctx) => FavoriteBloc(ctx.read()), ), ], - child: BlocSelector( - selector: (state) { + child: BlocSelector(selector: (state) { if (state is SettingsLoadedState) { return state.settings.language; } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart b/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart index 6ffd96e92..85177e8ba 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart @@ -1,9 +1,12 @@ -import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/logging/models/logging_bloc.dart'; +import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; -class AtDirectoryCubit extends LoggingCubit { - AtDirectoryCubit() : super(const LoggableString('root.atsign.org')); +class AtDirectoryCubit extends LoggingCubit { + AtDirectoryCubit() : super(const AtsignInformation(atSign: '', rootDomain: 'root.atsign.org')); - void setRootDomain(String rootDomain) => emit(LoggableString(rootDomain)); - String getRootDomain() => (state.string); + void setRootDomain(String rootDomain) => emit(AtsignInformation(atSign: state.atSign, rootDomain: rootDomain)); + String getRootDomain() => (state.rootDomain); + + void setAtSign(String atSign) => emit(AtsignInformation(atSign: atSign, rootDomain: state.rootDomain)); + String getAtSign() => (state.atSign); } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart index 6e9dbd2a1..1290e5b8e 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/atsign_manager.dart @@ -6,11 +6,11 @@ import 'package:npt_flutter/app.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; -class AtsignInformation { +class AtsignInformation extends Loggable { final String atSign; final String rootDomain; - AtsignInformation({required this.atSign, required this.rootDomain}); + const AtsignInformation({required this.atSign, required this.rootDomain}); Map toJson() => { "atsign": atSign, @@ -26,6 +26,14 @@ class AtsignInformation { rootDomain: json["root-domain"], ); } + + @override + List get props => [atSign, rootDomain]; + + @override + String toString() { + return 'AtsignInformation($atSign, $rootDomain)'; + } } // This will return a map which looks like: @@ -96,7 +104,7 @@ Future saveAtsignInformation(AtsignInformation info) async { } try { f.writeAsString( - jsonEncode(atSignInfo.map((e) => e.toJson())), + jsonEncode(atSignInfo.map((e) => e.toJson()).toList()), mode: FileMode.writeOnly, flush: true, ); diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart index a365208f6..d46c64bd5 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart @@ -13,7 +13,7 @@ class AtDirectoryDialog extends StatelessWidget { title: strings.atDirectory, subtitle: strings.atDirectorySubtitle, // TODO: Add success button text to the AppLocalizations - successButtonText: 'select', + successButtonText: 'Done', children: [ OnboardingAtDirectorySelector(), ], diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart index b429b6843..4e311e074 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart @@ -1,18 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; +import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; typedef OnboardingMapCallback = void Function(Map val); class AtsignSelector extends StatefulWidget { - AtsignSelector({ + const AtsignSelector({ super.key, }); - final List options = []; - @override State createState() => _AtsignSelectorState(); } @@ -21,11 +19,29 @@ class _AtsignSelectorState extends State { final FocusNode focusNode = FocusNode(); final TextEditingController controller = TextEditingController(); + List options = []; + late final List originalOptions; + + late int originalOptionsLength; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + options = (await getAtsignEntries()).keys.toList(); + if (mounted) context.read().setAtSign(options[0]); + + controller.text = options[0]; + originalOptions = List.from(options); + originalOptionsLength = options.length; + setState(() {}); + }); + } @override Widget build(BuildContext context) { - return BlocBuilder(builder: (context, rootDomain) { - controller.text = rootDomain.string; + return BlocBuilder(builder: (context, atsignInformation) { + controller.text = atsignInformation.atSign; return Column( children: [ Row( @@ -33,8 +49,9 @@ class _AtsignSelectorState extends State { children: [ Flexible( child: DropdownMenu( - initialSelection: widget.options.contains(rootDomain.string) ? rootDomain.string : null, - dropdownMenuEntries: widget.options + initialSelection: + options.contains(atsignInformation.atSign) ? atsignInformation.atSign : controller.text, + dropdownMenuEntries: options .map>( (o) => DropdownMenuEntry( value: o, @@ -45,7 +62,7 @@ class _AtsignSelectorState extends State { onSelected: (value) { if (value == null) return; - context.read().setRootDomain(value); + context.read().setAtSign(value); }, ), ), @@ -54,7 +71,7 @@ class _AtsignSelectorState extends State { focusNode: focusNode, onKeyEvent: (value) { if (value.logicalKey == LogicalKeyboardKey.backspace) { - if (widget.options.length > 2) widget.options.removeLast(); + if (options.length > originalOptionsLength) options.removeLast(); } }, child: TextFormField( @@ -63,13 +80,15 @@ class _AtsignSelectorState extends State { // validator: FormValidator.validateRequiredAtsignField, onChanged: (value) { // prevent the user from adding the default values to the dropdown a second time. - if (value != widget.options[0] || value != widget.options[1]) { - widget.options.add(value); + if (!originalOptions.contains(value)) { + options.add(value); + + setState(() {}); } - //removes the third element making the final entry the only additional value in options. This prevents the dropdown from having more than 3 entries. - if (widget.options.length > 3) widget.options.removeAt(2); + //removes the last element making the final entry the only additional value in options. This prevents the dropdown from having more than original options + one entries. + if (options.length > originalOptionsLength + 1) options.removeAt(originalOptionsLength); - context.read().setRootDomain(value); + context.read().setAtSign(value); }, ), ), diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart index 65864b456..2455ff46e 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; +import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; typedef OnboardingMapCallback = void Function(Map val); @@ -18,9 +18,8 @@ class OnboardingAtDirectorySelector extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, rootDomain) { - controller.text = rootDomain.string; + return BlocBuilder(builder: (context, atsignInformation) { + controller.text = atsignInformation.rootDomain; return Column( children: [ Row( @@ -28,9 +27,8 @@ class OnboardingAtDirectorySelector extends StatelessWidget { children: [ Flexible( child: DropdownMenu( - initialSelection: options.contains(rootDomain.string) - ? rootDomain.string - : null, + initialSelection: + options.contains(atsignInformation.rootDomain) ? atsignInformation.rootDomain : null, dropdownMenuEntries: options .map>( (o) => DropdownMenuEntry( diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart index 0ffa954e8..d040e814e 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/constants.dart'; -import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; import 'package:npt_flutter/features/onboarding/onboarding.dart'; import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; @@ -87,15 +86,15 @@ class _OnboardingButtonState extends State { @override Widget build(BuildContext context) { final strings = AppLocalizations.of(context)!; - return BlocBuilder(builder: (context, rootDomain) { + return BlocBuilder(builder: (context, atsignInformation) { return ElevatedButton.icon( onPressed: () async { final isEmptyAtsignList = (await getAtsignEntries()).isNotEmpty; - log(isEmptyAtsignList.toString()); + log('atsign entries is empty state: $isEmptyAtsignList'); if (isEmptyAtsignList) await selectAtsign(); - onboard(rootDomain: rootDomain.string); + onboard(rootDomain: atsignInformation.rootDomain); }, icon: PhosphorIcon(PhosphorIcons.arrowUpRight()), label: Text( diff --git a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart index 432f4a0ef..6719bba29 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart @@ -4,10 +4,9 @@ import 'package:at_onboarding_flutter/services/onboarding_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:npt_flutter/app.dart'; import 'package:npt_flutter/constants.dart'; -import 'package:npt_flutter/features/logging/models/loggable.dart'; import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; +import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; import 'package:npt_flutter/features/onboarding/util/pre_offboard.dart'; import 'package:npt_flutter/features/onboarding/widgets/at_directory_dialog.dart'; import 'package:npt_flutter/features/onboarding/widgets/onboarding_button.dart'; @@ -94,7 +93,7 @@ class CustomTextButton extends StatelessWidget { // final bodyMedium = Theme.of(context).textTheme.bodyMedium!; // final bodySmall = Theme.of(context).textTheme.bodySmall!; final strings = AppLocalizations.of(context)!; - Future onTap(String rootDomain) async { + Future onTap({String? rootDomain}) async { switch (type) { case CustomListTileType.email: Uri emailUri = Uri( @@ -134,7 +133,7 @@ class CustomTextButton extends StatelessWidget { } break; case CustomListTileType.resetAtsign: - final futurePreference = await loadAtClientPreference(rootDomain); + final futurePreference = await loadAtClientPreference(rootDomain!); if (context.mounted) { final result = await AtOnboarding.reset( context: context, @@ -208,20 +207,34 @@ class CustomTextButton extends StatelessWidget { } } - return BlocBuilder(builder: (context, rootDomain) { - return Padding( - padding: const EdgeInsets.only(left: Sizes.p30, right: Sizes.p30, bottom: Sizes.p10), - child: TextButton.icon( - label: Text(getTitle(strings)), - onPressed: () { - onTap(rootDomain.string); - }, - icon: Icon( - iconData, + if (type == CustomListTileType.resetAtsign) { + return BlocBuilder(builder: (context, atsignInformation) { + return Padding( + padding: const EdgeInsets.only(left: Sizes.p30, right: Sizes.p30, bottom: Sizes.p10), + child: TextButton.icon( + label: Text(getTitle(strings)), + onPressed: () { + onTap(rootDomain: atsignInformation.rootDomain); + }, + icon: Icon( + iconData, + ), ), + ); + }); + } + return Padding( + padding: const EdgeInsets.only(left: Sizes.p30, right: Sizes.p30, bottom: Sizes.p10), + child: TextButton.icon( + label: Text(getTitle(strings)), + onPressed: () { + onTap(); + }, + icon: Icon( + iconData, ), - ); - }); + ), + ); } } From d3e25464f9c68224c5140579907a06476cdf7c8a Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Wed, 16 Oct 2024 16:14:13 -0400 Subject: [PATCH 21/23] fix: onboarding wrapper edge cases --- packages/dart/npt_flutter/lib/app.dart | 8 +- packages/dart/npt_flutter/lib/constants.dart | 8 +- .../onboarding/cubit/at_directory_cubit.dart | 12 -- .../onboarding/cubit/onboarding_cubit.dart | 51 ++++++- .../onboarding/cubit/onboarding_state.dart | 30 ---- .../onboarding/util/post_onboard.dart | 20 ++- .../onboarding/util/pre_offboard.dart | 2 +- .../onboarding/view/onboarding_view.dart | 1 - .../widgets/at_directory_dialog.dart | 22 --- .../widgets/at_directory_selector.dart | 73 ++++++++++ .../onboarding/widgets/atsign_dialog.dart | 22 --- .../onboarding/widgets/atsign_selector.dart | 131 +++++++----------- .../onboarding_at_directory_selector.dart | 78 ----------- .../onboarding/widgets/onboarding_button.dart | 128 ++++++++++++----- .../onboarding/widgets/onboarding_dialog.dart | 29 ++-- .../features/profile/bloc/profile_bloc.dart | 54 +++----- .../lib/features/profile/models/profile.dart | 8 +- .../tray_manager/cubit/tray_cubit.dart | 22 ++- .../lib/widgets/custom_text_button.dart | 22 +-- 19 files changed, 325 insertions(+), 396 deletions(-) delete mode 100644 packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart delete mode 100644 packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_state.dart delete mode 100644 packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart create mode 100644 packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_selector.dart delete mode 100644 packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_dialog.dart delete mode 100644 packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart diff --git a/packages/dart/npt_flutter/lib/app.dart b/packages/dart/npt_flutter/lib/app.dart index ba17559d9..2a19be78e 100644 --- a/packages/dart/npt_flutter/lib/app.dart +++ b/packages/dart/npt_flutter/lib/app.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/features/features.dart'; -import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; import 'package:npt_flutter/routes.dart'; import 'package:npt_flutter/styles/app_theme.dart'; @@ -45,16 +44,11 @@ class App extends StatelessWidget { create: (_) => LogsCubit(), ), - /// A cubit which manages the onboarding status + // A bloc which manages the atDirectory state BlocProvider( create: (_) => OnboardingCubit(), ), - // A bloc which manages the atDirectory state - BlocProvider( - create: (_) => AtDirectoryCubit(), - ), - /// Settings provider, not much else to say /// - If settings are not found, we automatically load some defaults /// so it is possible that someone's settings get wiped if there is diff --git a/packages/dart/npt_flutter/lib/constants.dart b/packages/dart/npt_flutter/lib/constants.dart index 58c1eb465..d412655ab 100644 --- a/packages/dart/npt_flutter/lib/constants.dart +++ b/packages/dart/npt_flutter/lib/constants.dart @@ -1,5 +1,6 @@ +import 'package:flutter/material.dart'; + class Constants { - static const rootDomain = 'root.atsign.org'; static String? get namespace => 'noports'; // TODO: issue & secure API key properly static String? get appAPIKey => 'asdf'; @@ -15,5 +16,10 @@ class Constants { "@rv_ap": "Singapore", }; + static Map getRootDomains(BuildContext context) { + // TODO localize right hand side of map + return {'root.atsign.org': 'Default (Prod)', 'vip.ve.atsign.zone': 'Demo (VE)'}; + } + static const languages = ['English', 'Spanish', 'Br portuguese', 'Mandarin', 'Cantonese']; } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart b/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart deleted file mode 100644 index 85177e8ba..000000000 --- a/packages/dart/npt_flutter/lib/features/onboarding/cubit/at_directory_cubit.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:npt_flutter/features/logging/models/logging_bloc.dart'; -import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; - -class AtDirectoryCubit extends LoggingCubit { - AtDirectoryCubit() : super(const AtsignInformation(atSign: '', rootDomain: 'root.atsign.org')); - - void setRootDomain(String rootDomain) => emit(AtsignInformation(atSign: state.atSign, rootDomain: rootDomain)); - String getRootDomain() => (state.rootDomain); - - void setAtSign(String atSign) => emit(AtsignInformation(atSign: atSign, rootDomain: state.rootDomain)); - String getAtSign() => (state.atSign); -} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_cubit.dart b/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_cubit.dart index 65f4a9bbc..d23812dd4 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_cubit.dart @@ -1,10 +1,49 @@ -import 'package:npt_flutter/features/logging/logging.dart'; - -part 'onboarding_state.dart'; +import 'package:npt_flutter/features/logging/models/logging_bloc.dart'; +import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; class OnboardingCubit extends LoggingCubit { - OnboardingCubit() : super(const OnboardingInitial()); + OnboardingCubit() + : super(const OnboardingState(atSign: '', status: OnboardingStatus.offboarded, rootDomain: 'root.atsign.org')); + + void setRootDomain(String rootDomain) => + emit(OnboardingState(atSign: state.atSign, status: state.status, rootDomain: rootDomain)); + String getRootDomain() => (state.rootDomain); + + void setAtSign(String atSign) => + emit(OnboardingState(atSign: atSign, status: state.status, rootDomain: state.rootDomain)); + String getAtSign() => (state.atSign); + + void setStatus(OnboardingStatus status) => + emit(OnboardingState(atSign: state.atSign, status: status, rootDomain: state.rootDomain)); + OnboardingStatus getStatus() => (state.status); + + /// If state is passed, all other arguments are ignored + /// If individual arguments (atsign, rootDomain, status) are passed + /// then they will override the value of the current state + /// keeping unspecified values the same + void setState({ + String? atSign, + OnboardingStatus? status, + String? rootDomain, + }) => + emit(OnboardingState( + atSign: atSign ?? this.state.atSign, + status: status ?? this.state.status, + rootDomain: rootDomain ?? this.state.rootDomain, + )); +} + +enum OnboardingStatus { onboarded, offboarded } + +class OnboardingState extends AtsignInformation { + final OnboardingStatus status; + const OnboardingState({required this.status, required super.atSign, required super.rootDomain}); + + @override + List get props => [atSign, status, rootDomain]; - void onboard(String atSign) => emit(Onboarded(atSign)); - void offboard() => emit(const OnboardingInitial()); + @override + String toString() { + return 'OnboardingState($atSign, ${status.name}, $rootDomain)'; + } } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_state.dart b/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_state.dart deleted file mode 100644 index 760bf8f38..000000000 --- a/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_state.dart +++ /dev/null @@ -1,30 +0,0 @@ -part of 'onboarding_cubit.dart'; - -sealed class OnboardingState extends Loggable { - const OnboardingState(); - - @override - List get props => []; -} - -final class OnboardingInitial extends OnboardingState { - const OnboardingInitial(); - - @override - String toString() { - return 'OnboardingInitial'; - } -} - -final class Onboarded extends OnboardingState { - final String atSign; - const Onboarded(this.atSign); - - @override - List get props => [atSign]; - - @override - String toString() { - return 'Onboarded($atSign)'; - } -} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/post_onboard.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/post_onboard.dart index 16777c09b..4e9ebeee8 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/util/post_onboard.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/post_onboard.dart @@ -2,16 +2,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:npt_flutter/app.dart'; import 'package:npt_flutter/features/features.dart'; -Future postOnboard(String atSign) async { - App.navState.currentContext?.read().onboard(atSign); +Future postOnboard(String atSign, String rootDomain) async { + App.navState.currentContext?.read().setState( + atSign: atSign, + rootDomain: rootDomain, + status: OnboardingStatus.onboarded, + ); // Start loading application data in the background as soon as we have an atClient - App.navState.currentContext - ?.read() - .add(const ProfileListLoadEvent()); - App.navState.currentContext - ?.read() - .add(const SettingsLoadEvent()); - App.navState.currentContext - ?.read() - .add(const FavoriteLoadEvent()); + App.navState.currentContext?.read().add(const ProfileListLoadEvent()); + App.navState.currentContext?.read().add(const SettingsLoadEvent()); + App.navState.currentContext?.read().add(const FavoriteLoadEvent()); } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart b/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart index 5661b47e5..a26eb76e8 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/util/pre_offboard.dart @@ -14,7 +14,7 @@ Future preSignout() async { App.navState.currentContext?.read().clearAll(); App.navState.currentContext?.read().clearAll(); App.navState.currentContext?.read().clear(); - App.navState.currentContext?.read().offboard(); + App.navState.currentContext?.read().setStatus(OnboardingStatus.offboarded); // - Reset the tray icon App.navState.currentContext?.read().initialize(); return true; diff --git a/packages/dart/npt_flutter/lib/features/onboarding/view/onboarding_view.dart b/packages/dart/npt_flutter/lib/features/onboarding/view/onboarding_view.dart index 1f5b5db67..b9581ba05 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/view/onboarding_view.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/view/onboarding_view.dart @@ -47,7 +47,6 @@ class OnboardingView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.end, children: [ CustomTextButton.resetAtsign(), - CustomTextButton.selectRootDomain(), ], ), ), diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart deleted file mode 100644 index d46c64bd5..000000000 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_dialog.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:npt_flutter/features/onboarding/widgets/onboarding_at_directory_selector.dart'; -import 'package:npt_flutter/features/onboarding/widgets/onboarding_dialog.dart'; - -class AtDirectoryDialog extends StatelessWidget { - const AtDirectoryDialog({super.key}); - - @override - Widget build(BuildContext context) { - final strings = AppLocalizations.of(context)!; - return OnboardingDialog( - title: strings.atDirectory, - subtitle: strings.atDirectorySubtitle, - // TODO: Add success button text to the AppLocalizations - successButtonText: 'Done', - children: [ - OnboardingAtDirectorySelector(), - ], - ); - } -} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_selector.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_selector.dart new file mode 100644 index 000000000..ef2a22f60 --- /dev/null +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/at_directory_selector.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:npt_flutter/constants.dart'; +import 'package:npt_flutter/features/onboarding/cubit/onboarding_cubit.dart'; +import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; + +class AtDirectorySelector extends StatefulWidget { + const AtDirectorySelector({ + required this.options, + super.key, + }); + final Map options; + + @override + State createState() => _AtDirectorySelectorState(); +} + +class _AtDirectorySelectorState extends State { + final focusNode = FocusNode(); + final controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + final rootDomains = Constants.getRootDomains(context); + return BlocBuilder(builder: (context, state) { + controller.value = TextEditingValue(text: state.rootDomain); + return TextFormField( + enabled: !widget.options.containsKey(state.atSign), + controller: controller, + onChanged: (rootDomain) { + context.read().setRootDomain(rootDomain); + }, + decoration: InputDecoration( + /// This menuAnchor is a dropdown button that allows you to quickly select + /// existing values from [options] + suffixIcon: rootDomains.isNotEmpty + ? Directionality( + textDirection: TextDirection.rtl, + child: MenuAnchor( + style: const MenuStyle(alignment: AlignmentDirectional.bottomStart), + childFocusNode: focusNode, + menuChildren: rootDomains.entries.map((e) { + return Directionality( + textDirection: TextDirection.ltr, + child: MenuItemButton( + child: Text(e.value), + onPressed: () { + context.read().setRootDomain(e.key); + }, + ), + ); + }).toList(), + builder: (BuildContext context, MenuController controller, Widget? child) { + return IconButton( + focusNode: focusNode, + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + icon: const Icon(Icons.arrow_drop_down), + ); + }, + ), + ) + : null, + ), + ); + }); + } +} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_dialog.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_dialog.dart deleted file mode 100644 index f3c840111..000000000 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_dialog.dart +++ /dev/null @@ -1,22 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:npt_flutter/features/onboarding/widgets/atsign_selector.dart'; -import 'package:npt_flutter/features/onboarding/widgets/onboarding_dialog.dart'; - -class AtSignDialog extends StatelessWidget { - const AtSignDialog({super.key}); - - @override - Widget build(BuildContext context) { - final strings = AppLocalizations.of(context)!; - return OnboardingDialog( - // TODO: Add title, subtitle and success button text to the AppLocalizations - title: 'AtSign', - subtitle: 'Please select your @sign', - successButtonText: 'Next', - children: [ - AtsignSelector(), - ], - ); - } -} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart index 4e311e074..92464dd73 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/atsign_selector.dart @@ -1,101 +1,74 @@ import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; +import 'package:npt_flutter/features/onboarding/cubit/onboarding_cubit.dart'; import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; -typedef OnboardingMapCallback = void Function(Map val); - class AtsignSelector extends StatefulWidget { const AtsignSelector({ + required this.options, super.key, }); - + final Map options; @override State createState() => _AtsignSelectorState(); } class _AtsignSelectorState extends State { - final FocusNode focusNode = FocusNode(); - - final TextEditingController controller = TextEditingController(); - List options = []; - late final List originalOptions; - - late int originalOptionsLength; - - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) async { - options = (await getAtsignEntries()).keys.toList(); - if (mounted) context.read().setAtSign(options[0]); - - controller.text = options[0]; - originalOptions = List.from(options); - originalOptionsLength = options.length; - setState(() {}); - }); - } + final focusNode = FocusNode(); + final controller = TextEditingController(); @override Widget build(BuildContext context) { - return BlocBuilder(builder: (context, atsignInformation) { - controller.text = atsignInformation.atSign; - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: DropdownMenu( - initialSelection: - options.contains(atsignInformation.atSign) ? atsignInformation.atSign : controller.text, - dropdownMenuEntries: options - .map>( - (o) => DropdownMenuEntry( - value: o, - label: o, + return BlocBuilder(builder: (context, state) { + controller.value = TextEditingValue(text: state.atSign); + return TextFormField( + controller: controller, + onChanged: (atsign) { + context.read().setState( + atSign: atsign, + rootDomain: widget.options[atsign]?.rootDomain, + ); + }, + decoration: InputDecoration( + /// This menuAnchor is a dropdown button that allows you to quickly select + /// existing values from [options] + suffixIcon: widget.options.isNotEmpty + ? Directionality( + textDirection: TextDirection.rtl, + child: MenuAnchor( + style: const MenuStyle(alignment: AlignmentDirectional.bottomStart), + childFocusNode: focusNode, + menuChildren: widget.options.keys.map((atsign) { + return Directionality( + textDirection: TextDirection.ltr, + child: MenuItemButton( + child: Text(atsign), + onPressed: () { + context.read().setState( + atSign: atsign, + rootDomain: widget.options[atsign]?.rootDomain, + ); + }, ), - ) - .toList(), - onSelected: (value) { - if (value == null) return; - - context.read().setAtSign(value); - }, - ), - ), - Flexible( - child: KeyboardListener( - focusNode: focusNode, - onKeyEvent: (value) { - if (value.logicalKey == LogicalKeyboardKey.backspace) { - if (options.length > originalOptionsLength) options.removeLast(); - } - }, - child: TextFormField( - controller: controller, - autovalidateMode: AutovalidateMode.onUserInteraction, - // validator: FormValidator.validateRequiredAtsignField, - onChanged: (value) { - // prevent the user from adding the default values to the dropdown a second time. - if (!originalOptions.contains(value)) { - options.add(value); - - setState(() {}); - } - //removes the last element making the final entry the only additional value in options. This prevents the dropdown from having more than original options + one entries. - if (options.length > originalOptionsLength + 1) options.removeAt(originalOptionsLength); - - context.read().setAtSign(value); + ); + }).toList(), + builder: (BuildContext context, MenuController controller, Widget? child) { + return IconButton( + focusNode: focusNode, + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }, + icon: const Icon(Icons.arrow_drop_down), + ); }, ), - ), - ) - ], - ), - ], + ) + : null, + ), ); }); } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart deleted file mode 100644 index 2455ff46e..000000000 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_at_directory_selector.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; -import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; - -typedef OnboardingMapCallback = void Function(Map val); - -class OnboardingAtDirectorySelector extends StatelessWidget { - OnboardingAtDirectorySelector({ - super.key, - }); - - final options = ['root.atsign.org', 'vip.ve.atsign.zone']; - - final FocusNode focusNode = FocusNode(); - final TextEditingController controller = TextEditingController(); - - @override - Widget build(BuildContext context) { - return BlocBuilder(builder: (context, atsignInformation) { - controller.text = atsignInformation.rootDomain; - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible( - child: DropdownMenu( - initialSelection: - options.contains(atsignInformation.rootDomain) ? atsignInformation.rootDomain : null, - dropdownMenuEntries: options - .map>( - (o) => DropdownMenuEntry( - value: o, - label: o, - ), - ) - .toList(), - onSelected: (value) { - if (value == null) return; - - context.read().setRootDomain(value); - }, - ), - ), - Flexible( - child: KeyboardListener( - focusNode: focusNode, - onKeyEvent: (value) { - if (value.logicalKey == LogicalKeyboardKey.backspace) { - if (options.length > 2) options.removeLast(); - } - }, - child: TextFormField( - controller: controller, - autovalidateMode: AutovalidateMode.onUserInteraction, - // validator: FormValidator.validateRequiredAtsignField, - onChanged: (value) { - // prevent the user from adding the default values to the dropdown a second time. - if (value != options[0] || value != options[1]) { - options.add(value); - } - //removes the third element making the final entry the only additional value in options. This prevents the dropdown from having more than 3 entries. - if (options.length > 3) options.removeAt(2); - - context.read().setRootDomain(value); - }, - ), - ), - ) - ], - ), - ], - ); - }); - } -} diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart index d040e814e..cddb86cea 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_button.dart @@ -2,14 +2,14 @@ import 'dart:developer'; import 'package:at_contacts_flutter/at_contacts_flutter.dart'; import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; +import 'package:at_onboarding_flutter/screen/at_onboarding_home_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/constants.dart'; -import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; import 'package:npt_flutter/features/onboarding/onboarding.dart'; import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; -import 'package:npt_flutter/features/onboarding/widgets/atsign_dialog.dart'; +import 'package:npt_flutter/features/onboarding/widgets/onboarding_dialog.dart'; import 'package:npt_flutter/routes.dart'; import 'package:path_provider/path_provider.dart'; import 'package:phosphor_flutter/phosphor_flutter.dart'; @@ -35,23 +35,62 @@ class OnboardingButton extends StatefulWidget { } class _OnboardingButtonState extends State { - Future onboard({required String rootDomain, bool isFromInitState = false}) async { - AtOnboardingResult onboardingResult = await AtOnboarding.onboard( - // ignore: use_build_context_synchronously - context: context, - config: AtOnboardingConfig( - atClientPreference: await loadAtClientPreference(rootDomain), - rootEnvironment: RootEnvironment.Testing, - domain: rootDomain, - appAPIKey: Constants.appAPIKey, - ), + Future onboard({String? atsign, required String rootDomain, bool isFromInitState = false}) async { + var atSigns = await KeyChainManager.getInstance().getAtSignListFromKeychain(); + var config = AtOnboardingConfig( + atClientPreference: await loadAtClientPreference(rootDomain), + rootEnvironment: RootEnvironment.Production, + domain: rootDomain, + appAPIKey: Constants.appAPIKey, ); + AtOnboardingResult? onboardingResult; + if (!atSigns.contains(atsign)) { + // This is a hack. + // Ideally it should be possible to skip the home screen in onboarding + // and go straight to either of the following (based on current atSign status): + // A) opening the file picker + // B) activating the atSign + // But unfortunately that code is SO coupled to the widget that it is really + // not worth the effort to fix right now. + // + // Assumptions made in at_onboarding_flutter which have caused this problem: + // if [atsign] is non-null the atSign already exists in the keychain + // thus any atsign that isn't in the keychain isn't handled if explicitly passed... + // this means there is no edge case handling for new or unactivated atSigns + // nor for atSigns that are activated but not in the keychain... + // + // Given that we are working on new user flows, I'm not going to waste countless + // hours for this tiny UX fix + + // TODO: fix localizations + await AtOnboardingLocalizations.load(const Locale("en")); + onboardingResult = await Navigator.push( + // ignore: use_build_context_synchronously + context, + MaterialPageRoute( + builder: (BuildContext context) { + return AtOnboardingHomeScreen( + config: config, + isFromIntroScreen: false, + ); + }, + ), + ); + } else { + onboardingResult = await AtOnboarding.onboard( + atsign: atsign, + // ignore: use_build_context_synchronously + context: context, + config: config, + ); + } + if (mounted) { - switch (onboardingResult.status) { + switch (onboardingResult?.status ?? AtOnboardingResultStatus.cancel) { case AtOnboardingResultStatus.success: await initializeContactsService(rootDomain: rootDomain); - postOnboard(onboardingResult.atsign!); + postOnboard(onboardingResult!.atsign!, rootDomain); final result = await saveAtsignInformation(AtsignInformation(atSign: onboardingResult.atsign!, rootDomain: rootDomain)); log('atsign result is:$result'); @@ -76,32 +115,49 @@ class _OnboardingButtonState extends State { } Future selectAtsign() async { - final results = await showDialog( - context: context, - builder: (BuildContext context) => const AtSignDialog(), - ); - return results ?? false; + var options = await getAtsignEntries(); + if (mounted) { + final cubit = context.read(); + String atsign = cubit.state.atSign; + String? rootDomain = cubit.state.rootDomain; + + if (options.isEmpty) { + atsign = ""; + } else if (atsign.isEmpty) { + atsign = options.keys.first; + } + if (options.keys.contains(atsign)) { + rootDomain = options[atsign]?.rootDomain; + } else { + rootDomain = Constants.getRootDomains(context).keys.first; + } + + cubit.setState(atSign: atsign, rootDomain: rootDomain); + final results = await showDialog( + context: context, + builder: (BuildContext context) => OnboardingDialog(options: options), + ); + return results ?? false; + } + return false; } @override Widget build(BuildContext context) { final strings = AppLocalizations.of(context)!; - return BlocBuilder(builder: (context, atsignInformation) { - return ElevatedButton.icon( - onPressed: () async { - final isEmptyAtsignList = (await getAtsignEntries()).isNotEmpty; - log('atsign entries is empty state: $isEmptyAtsignList'); - - if (isEmptyAtsignList) await selectAtsign(); - - onboard(rootDomain: atsignInformation.rootDomain); - }, - icon: PhosphorIcon(PhosphorIcons.arrowUpRight()), - label: Text( - strings.getStarted, - ), - iconAlignment: IconAlignment.end, - ); - }); + return ElevatedButton.icon( + onPressed: () async { + bool shouldOnboard = await selectAtsign(); + if (shouldOnboard && context.mounted) { + var atsignInformation = context.read().state; + onboard(atsign: atsignInformation.atSign, rootDomain: atsignInformation.rootDomain); + } + }, + icon: PhosphorIcon(PhosphorIcons.arrowUpRight()), + label: Text( + strings.getStarted, + ), + iconAlignment: IconAlignment.end, + ); } } diff --git a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_dialog.dart b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_dialog.dart index 796f8f46b..ebbfd857f 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_dialog.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/widgets/onboarding_dialog.dart @@ -1,19 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; +import 'package:npt_flutter/features/onboarding/widgets/at_directory_selector.dart'; +import 'package:npt_flutter/features/onboarding/widgets/atsign_selector.dart'; import 'package:npt_flutter/styles/sizes.dart'; import 'package:npt_flutter/widgets/custom_container.dart'; class OnboardingDialog extends StatelessWidget { - const OnboardingDialog( - {required this.title, - required this.subtitle, - required this.successButtonText, - required this.children, - super.key}); - final String title; - final String subtitle; - final String successButtonText; - final List children; + const OnboardingDialog({required this.options, super.key}); + final Map options; @override Widget build(BuildContext context) { @@ -30,10 +25,16 @@ class OnboardingDialog extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(title), - Text(subtitle), + const Text("Select or type the client atSign"), gapH16, - ...children, + AtsignSelector( + options: options, + ), + gapH16, + const Text("Select or type the root domain"), + AtDirectorySelector( + options: options, + ), ], ), ), @@ -52,7 +53,7 @@ class OnboardingDialog extends StatelessWidget { onPressed: () { Navigator.of(context).pop(true); }, - child: Text(successButtonText), + child: const Text("Next"), ), ], )) diff --git a/packages/dart/npt_flutter/lib/features/profile/bloc/profile_bloc.dart b/packages/dart/npt_flutter/lib/features/profile/bloc/profile_bloc.dart index 2b9c92885..4c1167bab 100644 --- a/packages/dart/npt_flutter/lib/features/profile/bloc/profile_bloc.dart +++ b/packages/dart/npt_flutter/lib/features/profile/bloc/profile_bloc.dart @@ -24,8 +24,7 @@ class ProfileBloc extends LoggingBloc { on(_onStart); on(_onStop); } - Future _onLoad( - ProfileLoadEvent event, Emitter emit) async { + Future _onLoad(ProfileLoadEvent event, Emitter emit) async { emit(ProfileLoading(uuid)); Profile? profile; @@ -43,8 +42,7 @@ class ProfileBloc extends LoggingBloc { emit(ProfileLoaded(uuid, profile: profile)); } - Future _onLoadOrCreate( - ProfileLoadOrCreateEvent event, Emitter emit) async { + Future _onLoadOrCreate(ProfileLoadOrCreateEvent event, Emitter emit) async { emit(ProfileLoading(uuid)); Profile? profile; @@ -73,16 +71,14 @@ class ProfileBloc extends LoggingBloc { emit(ProfileLoaded(uuid, profile: profile)); } - Future _onEdit( - ProfileEditEvent event, Emitter emit) async { + Future _onEdit(ProfileEditEvent event, Emitter emit) async { if (state is! ProfileLoaded && state is! ProfileFailedSave) { return; } emit(ProfileLoaded(uuid, profile: event.profile)); } - FutureOr _onSave( - ProfileSaveEvent event, Emitter emit) async { + FutureOr _onSave(ProfileSaveEvent event, Emitter emit) async { emit(ProfileLoading(uuid)); bool res; try { @@ -92,9 +88,7 @@ class ProfileBloc extends LoggingBloc { } if (res) { - App.navState.currentContext - ?.read() - .invalidate(uuid); + App.navState.currentContext?.read().invalidate(uuid); var listBloc = App.navState.currentContext?.read(); if (listBloc != null && listBloc.state is ProfileListLoaded) { @@ -109,15 +103,12 @@ class ProfileBloc extends LoggingBloc { } emit(ProfileLoaded(uuid, profile: event.profile)); } else { - App.navState.currentContext - ?.read() - .invalidate(uuid); + App.navState.currentContext?.read().invalidate(uuid); emit(ProfileFailedSave(uuid, profile: event.profile)); } } - Future _onStart( - ProfileStartEvent event, Emitter emit) async { + Future _onStart(ProfileStartEvent event, Emitter emit) async { if (state is! ProfileLoadedState || state is ProfileStarting || state is ProfileStopping || @@ -134,23 +125,18 @@ class ProfileBloc extends LoggingBloc { String? atSign = atClient.getCurrentAtSign(); if (atSign == null) { emit(ProfileFailedStart(uuid, profile: profile)); - App.navState.currentContext - ?.read() - .invalidate(uuid); + App.navState.currentContext?.read().invalidate(uuid); return; } - SettingsState? currentSettingsState = - App.navState.currentContext?.read().state; + SettingsState? currentSettingsState = App.navState.currentContext?.read().state; if (currentSettingsState is! SettingsLoadedState) { emit(ProfileFailedStart( uuid, profile: profile, reason: "Couldn't fetch settings", )); - App.navState.currentContext - ?.read() - .invalidate(uuid); + App.navState.currentContext?.read().invalidate(uuid); return; } var settings = currentSettingsState.settings; @@ -163,6 +149,7 @@ class ProfileBloc extends LoggingBloc { atClient: atClient, params: profile.toNptParams( clientAtsign: atSign, + rootDomain: atClient.getPreferences()!.rootDomain, fallbackRelayAtsign: settings.relayAtsign, overrideRelayWithFallback: settings.overrideRelay, ), @@ -195,9 +182,7 @@ class ProfileBloc extends LoggingBloc { profile: profile, reason: 'Npt startup timedout', )); - App.navState.currentContext - ?.read() - .invalidate(uuid); + App.navState.currentContext?.read().invalidate(uuid); return; } @@ -208,9 +193,7 @@ class ProfileBloc extends LoggingBloc { profile: profile, reason: 'Socketconnector closed prematurely', )); - App.navState.currentContext - ?.read() - .invalidate(uuid); + App.navState.currentContext?.read().invalidate(uuid); return; } @@ -224,21 +207,16 @@ class ProfileBloc extends LoggingBloc { profile: profile, reason: 'Error during startup: $err', )); - App.navState.currentContext - ?.read() - .invalidate(uuid); + App.navState.currentContext?.read().invalidate(uuid); } finally { await npt?.done; cancel?.call(); - App.navState.currentContext - ?.read() - .invalidate(uuid); + App.navState.currentContext?.read().invalidate(uuid); emit(ProfileLoaded(uuid, profile: profile)); } } - Future _onStop( - ProfileStopEvent event, Emitter emit) async { + Future _onStop(ProfileStopEvent event, Emitter emit) async { if (state is! ProfileStarted) return; var profile = (state as ProfileStarted).profile; emit(ProfileStopping(uuid, profile: profile)); diff --git a/packages/dart/npt_flutter/lib/features/profile/models/profile.dart b/packages/dart/npt_flutter/lib/features/profile/models/profile.dart index 0bf929839..73798499c 100644 --- a/packages/dart/npt_flutter/lib/features/profile/models/profile.dart +++ b/packages/dart/npt_flutter/lib/features/profile/models/profile.dart @@ -1,6 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:noports_core/npt.dart'; -import 'package:npt_flutter/constants.dart'; import 'package:npt_flutter/app.dart'; import 'package:npt_flutter/features/favorite/favorite.dart'; import 'package:npt_flutter/util/uuid.dart'; @@ -87,13 +86,12 @@ final class Profile extends Loggable with Favoritable { NptParams toNptParams({ required String clientAtsign, + required String rootDomain, required String fallbackRelayAtsign, bool overrideRelayWithFallback = false, }) { String srvdAtSign = fallbackRelayAtsign; - if (!overrideRelayWithFallback && - relayAtsign != null && - relayAtsign!.isNotEmpty) { + if (!overrideRelayWithFallback && relayAtsign != null && relayAtsign!.isNotEmpty) { srvdAtSign = relayAtsign!; } return NptParams( @@ -104,7 +102,7 @@ final class Profile extends Loggable with Favoritable { remotePort: remotePort, device: deviceName, localPort: localPort, - rootDomain: Constants.rootDomain, + rootDomain: rootDomain, // hardcoded for now, because it makes the app simpler // and there's very few use-cases where you wouldn't want these settings diff --git a/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart b/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart index 433b983b2..3ed3c2beb 100644 --- a/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/tray_manager/cubit/tray_cubit.dart @@ -15,8 +15,7 @@ import 'package:window_manager/window_manager.dart'; part 'tray_cubit.g.dart'; part 'tray_state.dart'; -(String, void Function(MenuItem)) getAction(TrayAction action) => - switch (action) { +(String, void Function(MenuItem)) getAction(TrayAction action) => switch (action) { TrayAction.showDashboard => ('Show Window', (_) => windowManager.focus()), TrayAction.showSettings => ( 'Settings', @@ -26,7 +25,7 @@ part 'tray_state.dart'; if (context == null) return; if (context.mounted) { var cubit = context.read(); - if (cubit.state is! Onboarded) return; + if (cubit.getStatus() != OnboardingStatus.onboarded) return; Navigator.of(context).pushNamedAndRemoveUntil( Routes.settings, (route) => route.isFirst, @@ -71,7 +70,7 @@ class TrayCubit extends LoggingCubit { if (state is! TrayInitial) return; var context = App.navState.currentContext; if (context == null) return; - var showSettings = context.read().state is Onboarded; + var showSettings = context.read().getStatus() == OnboardingStatus.onboarded; await reloadIcon(); @@ -86,13 +85,10 @@ class TrayCubit extends LoggingCubit { } Future reloadIcon() async { - final brightness = - WidgetsBinding.instance.platformDispatcher.platformBrightness; + final brightness = WidgetsBinding.instance.platformDispatcher.platformBrightness; await trayManager.setIcon(switch (brightness) { - Brightness.light => - Platform.isWindows ? Constants.icoIconLight : Constants.pngIconLight, - Brightness.dark => - Platform.isWindows ? Constants.icoIconDark : Constants.pngIconDark, + Brightness.light => Platform.isWindows ? Constants.icoIconLight : Constants.pngIconLight, + Brightness.dark => Platform.isWindows ? Constants.icoIconDark : Constants.pngIconDark, }); } @@ -102,7 +98,7 @@ class TrayCubit extends LoggingCubit { var init = initialize(); /// Access the context before any awaited function calls - var showSettings = context.read().state is Onboarded; + var showSettings = context.read().getStatus() == OnboardingStatus.onboarded; var favoriteBloc = context.read(); var profilesList = context.read(); @@ -118,9 +114,7 @@ class TrayCubit extends LoggingCubit { /// Generate the new menu based on current state var favMenuItems = await Future.wait( - favorites - .where((fav) => fav.isLoadedInProfiles(profiles)) - .map((fav) async { + favorites.where((fav) => fav.isLoadedInProfiles(profiles)).map((fav) async { /// Make sure to call [e.displayName] and [e.isRunning] only once to /// ensure good performance - these getters call a bunch of nested /// information from elsewhere in the app state diff --git a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart index 6719bba29..f33930d3f 100644 --- a/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart +++ b/packages/dart/npt_flutter/lib/widgets/custom_text_button.dart @@ -5,10 +5,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:npt_flutter/constants.dart'; -import 'package:npt_flutter/features/onboarding/cubit/at_directory_cubit.dart'; +import 'package:npt_flutter/features/onboarding/cubit/onboarding_cubit.dart'; import 'package:npt_flutter/features/onboarding/util/atsign_manager.dart'; import 'package:npt_flutter/features/onboarding/util/pre_offboard.dart'; -import 'package:npt_flutter/features/onboarding/widgets/at_directory_dialog.dart'; import 'package:npt_flutter/features/onboarding/widgets/onboarding_button.dart'; import 'package:npt_flutter/pages/loading_page.dart'; import 'package:npt_flutter/routes.dart'; @@ -77,11 +76,6 @@ class CustomTextButton extends StatelessWidget { this.title = 'Feedback', this.type = CustomListTileType.feedback, super.key}); - const CustomTextButton.selectRootDomain( - {this.iconData = Icons.dns_outlined, - this.title = 'Select Root Domain', - this.type = CustomListTileType.selectRootDomain, - super.key}); final IconData iconData; final String title; @@ -171,12 +165,6 @@ class CustomTextButton extends StatelessWidget { await preSignout(); if (context.mounted) Navigator.of(context).pushReplacementNamed(Routes.onboarding); break; - case CustomListTileType.selectRootDomain: - await showDialog( - context: context, - builder: (BuildContext context) => const AtDirectoryDialog(), - ); - break; } } @@ -200,15 +188,12 @@ class CustomTextButton extends StatelessWidget { return strings.feedback; case CustomListTileType.signOut: // TODO Localize in the next PR. - return 'signOut'; - case CustomListTileType.selectRootDomain: - // TODO Localize in the next PR. - return 'Select Root Domain'; + return 'Sign out'; } } if (type == CustomListTileType.resetAtsign) { - return BlocBuilder(builder: (context, atsignInformation) { + return BlocBuilder(builder: (context, atsignInformation) { return Padding( padding: const EdgeInsets.only(left: Sizes.p30, right: Sizes.p30, bottom: Sizes.p10), child: TextButton.icon( @@ -248,5 +233,4 @@ enum CustomListTileType { resetAtsign, feedback, signOut, - selectRootDomain, } From fc49f127fdf70e65c3a07eb04f842acb28219212 Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Wed, 16 Oct 2024 16:30:03 -0400 Subject: [PATCH 22/23] chore: cleanup window manager --- packages/dart/npt_flutter/lib/main.dart | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/packages/dart/npt_flutter/lib/main.dart b/packages/dart/npt_flutter/lib/main.dart index e74895cc1..a4dfdd900 100644 --- a/packages/dart/npt_flutter/lib/main.dart +++ b/packages/dart/npt_flutter/lib/main.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:flutter/material.dart'; import 'package:window_manager/window_manager.dart'; @@ -8,11 +6,10 @@ import 'app.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); windowManager.ensureInitialized(); - try { - await windowManager.setSkipTaskbar(true); // Don't show the app icon in dock - } catch (_) { - log("Failed to setSkipTaskbar"); - } finally { - runApp(const App()); - } + var windowOptions = const WindowOptions( + title: "NoPorts Desktop", + skipTaskbar: true, + ); + windowManager.waitUntilReadyToShow(windowOptions); + runApp(const App()); } From c81fed3537c8a9f559eb52d84c6c2772a52ec6fd Mon Sep 17 00:00:00 2001 From: xavierchanth Date: Wed, 16 Oct 2024 16:30:20 -0400 Subject: [PATCH 23/23] cleanup onboarding cubit --- .../lib/features/onboarding/cubit/onboarding_cubit.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_cubit.dart b/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_cubit.dart index d23812dd4..890e5a93b 100644 --- a/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_cubit.dart +++ b/packages/dart/npt_flutter/lib/features/onboarding/cubit/onboarding_cubit.dart @@ -27,9 +27,9 @@ class OnboardingCubit extends LoggingCubit { String? rootDomain, }) => emit(OnboardingState( - atSign: atSign ?? this.state.atSign, - status: status ?? this.state.status, - rootDomain: rootDomain ?? this.state.rootDomain, + atSign: atSign ?? state.atSign, + status: status ?? state.status, + rootDomain: rootDomain ?? state.rootDomain, )); }