Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: add onboarding screen to npt flutter #1428

Merged
merged 27 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1eee5e4
fix: profile header aligned with profile list in the simple view.
CurtlyCritchlow Sep 25, 2024
0acae07
fix: appBar alignment and padding updated.
CurtlyCritchlow Sep 26, 2024
62588e1
fix: back button shown conditionally on appBar.
CurtlyCritchlow Sep 26, 2024
39ff8b7
fix: profile, device name, service mapping and status updated to supp…
CurtlyCritchlow Sep 26, 2024
5539f13
fix: dashboard page resizes based on user device size.
CurtlyCritchlow Sep 30, 2024
d0c81ff
fix: auto spacing occurs between dashboard content and all rights res…
CurtlyCritchlow Sep 30, 2024
a73fbe5
fix: profile form view sizes based on device size.
CurtlyCritchlow Sep 30, 2024
2b4ea13
fix: settings screen resizes based on device size.
CurtlyCritchlow Oct 1, 2024
5736dbf
feat: onboarding flow added
CurtlyCritchlow Oct 5, 2024
48c6d17
feat: add interface for new atSign management utilities
XavierChanth Oct 7, 2024
383f629
feat: add atsign manager utility functions
XavierChanth Oct 7, 2024
eaa5002
feat: add pre_offboard utility function
XavierChanth Oct 7, 2024
a3712ca
chore: add log message to preSignout since we bypass bloc events
XavierChanth Oct 7, 2024
35e008d
Merge pull request #1431 from atsign-foundation/npt-flutter-onboardin…
CurtlyCritchlow Oct 7, 2024
e65da55
fix: forgot this file
XavierChanth Oct 7, 2024
53b8958
feat: signout button added to the settings screen
CurtlyCritchlow Oct 8, 2024
c825674
fix: parameter removed from preSignout.
CurtlyCritchlow Oct 8, 2024
54c49e5
feat: root domain removed from onboarding flow.
CurtlyCritchlow Oct 8, 2024
0dd3caf
fix: handle empty file case
XavierChanth Oct 8, 2024
a4f738f
chore: localize relay selection
XavierChanth Oct 9, 2024
e6522b0
fix: user can select atsign if available in keychain when onboarding.
CurtlyCritchlow Oct 9, 2024
c168542
Merge branch 'trunk' into 1412-add-onboarding-screen-to-npt-flutter
CurtlyCritchlow Oct 9, 2024
bbeb832
Merge branch 'trunk' into 1412-add-onboarding-screen-to-npt-flutter
CurtlyCritchlow Oct 10, 2024
d3e2546
fix: onboarding wrapper edge cases
XavierChanth Oct 16, 2024
fc49f12
chore: cleanup window manager
XavierChanth Oct 16, 2024
c81fed3
cleanup onboarding cubit
XavierChanth Oct 16, 2024
cc7f038
Merge branch 'trunk' into 1412-add-onboarding-screen-to-npt-flutter
XavierChanth Oct 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions packages/dart/npt_flutter/assets/onboarding_bg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions packages/dart/npt_flutter/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -88,6 +89,11 @@ class App extends StatelessWidget {
BlocProvider<FavoriteBloc>(
create: (ctx) => FavoriteBloc(ctx.read<FavoriteRepository>()),
),

// A bloc which manages the atDirectory state
BlocProvider<AtDirectoryCubit>(
create: (_) => AtDirectoryCubit(),
),
],
child: BlocSelector<SettingsBloc, SettingsState, Language>(selector: (state) {
if (state is SettingsLoadedState) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import 'package:flutter_bloc/flutter_bloc.dart';

class AtDirectoryCubit extends Cubit<String> {
AtDirectoryCubit() : super('root.atsign.org');

void setRootDomain(String rootDomain) => emit(rootDomain);
}
Original file line number Diff line number Diff line change
@@ -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(),
),
)
],
);
}
}
Original file line number Diff line number Diff line change
@@ -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<AtDirectoryDialog> createState() => _AtDirectoryDialogState();
}

class _AtDirectoryDialogState extends State<AtDirectoryDialog> {
@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),
),
],
))
],
),
),
);
}
}
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<AtDirectoryCubit, String>(builder: (context, rootDomain) {
controller.text = rootDomain;
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: DropdownMenu<String>(
initialSelection: options.contains(rootDomain) ? rootDomain : null,
dropdownMenuEntries: options
.map<DropdownMenuEntry<String>>(
(o) => DropdownMenuEntry(
value: o,
label: o,
),
)
.toList(),
onSelected: (value) {
if (value == null) return;

context.read<AtDirectoryCubit>().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<AtDirectoryCubit>().setRootDomain(value);
},
),
),
)
],
),
],
);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,71 +1,81 @@
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<AtClientPreference> loadAtClientPreference() async {
Future<AtClientPreference> loadAtClientPreference(String rootDomain) async {
var dir = await getApplicationSupportDirectory();

return AtClientPreference()
..rootDomain = Constants.rootDomain
..rootDomain = rootDomain
..namespace = Constants.namespace
..hiveStoragePath = dir.path
..commitLogPath = dir.path
..isLocalStoreRequired = true;
}

class OnboardingButton extends StatefulWidget {
const OnboardingButton({super.key, required this.nextRoute});
final String nextRoute;
const OnboardingButton({
super.key,
});

@override
State<OnboardingButton> createState() => _OnboardingButtonState();
}

class _OnboardingButtonState extends State<OnboardingButton> {
final Future<AtClientPreference> 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<AtDirectoryCubit, String>(builder: (context, rootDomain) {
return ElevatedButton.icon(
onPressed: () async {
final result = await selectOptions();

if (result && context.mounted) onboard(rootDomain: context.read<AtDirectoryCubit>().state);
},
icon: PhosphorIcon(PhosphorIcons.arrowUpRight()),
label: Text(
strings.getStarted,
),
iconAlignment: IconAlignment.end,
);
});
}

Future<void> onboard({bool isFromInitState = false}) async {
Future<void> 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,
),
);

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;
Expand All @@ -74,4 +84,12 @@ class _OnboardingButtonState extends State<OnboardingButton> {
}
}
}

Future<bool> selectOptions() async {
final results = await showDialog(
context: context,
builder: (BuildContext context) => const AtDirectoryDialog(),
);
return results ?? false;
}
}
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -14,6 +15,7 @@ class ProfileHeaderView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final strings = AppLocalizations.of(context)!;

return BlocBuilder<ProfileListBloc, ProfileListState>(builder: (context, state) {
if (state is ProfileListInitial) {
context.read<ProfileListBloc>().add(const ProfileListLoadEvent());
Expand Down Expand Up @@ -50,12 +52,12 @@ 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(),
gapW10,
SizedBox(width: Sizes.p150, child: Text(strings.profileName)),
ProfileHeaderColumn(title: strings.profileName, layout: PreferredViewLayout.minimal),
gapW10,
Text(strings.status),
// gapW10,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -21,6 +22,7 @@ class ProfileView extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center,
children: [
LoaderBar(),
gapW10,
ProfileRefreshButton(),
],
);
Expand Down
Loading