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 all 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.
4 changes: 3 additions & 1 deletion packages/dart/npt_flutter/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class App extends StatelessWidget {
],
child: MultiBlocProvider(
providers: [
// TODO this should be called LocalSettingsCubit and move
// Localization from the SettingsCubit to this
BlocProvider<EnableLoggingCubit>(
create: (_) => EnableLoggingCubit(),
),
Expand All @@ -42,7 +44,7 @@ class App extends StatelessWidget {
create: (_) => LogsCubit(),
),

/// A cubit which manages the onboarding status
// A bloc which manages the atDirectory state
BlocProvider<OnboardingCubit>(
create: (_) => OnboardingCubit(),
),
Expand Down
8 changes: 7 additions & 1 deletion packages/dart/npt_flutter/lib/constants.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -15,5 +16,10 @@ class Constants {
"@rv_ap": "Singapore",
};

static Map<String, String> 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'];
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class FavoriteBloc extends LoggingBloc<FavoriteEvent, FavoritesState> {
on<FavoriteRemoveEvent>(_onRemove);
}

void clearAll() => emit(const FavoritesInitial());

FutureOr<void> _onLoad(
FavoriteLoadEvent event, Emitter<FavoritesState> emit) async {
emit(const FavoritesLoading());
Expand Down
Original file line number Diff line number Diff line change
@@ -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<OnboardingState> {
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 ?? state.atSign,
status: status ?? state.status,
rootDomain: rootDomain ?? 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<Object?> 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)';
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import 'dart:convert';
import 'dart:io';

import 'package:at_onboarding_flutter/at_onboarding_flutter.dart';
import 'package:npt_flutter/app.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';

class AtsignInformation extends Loggable {
final String atSign;
final String rootDomain;

const AtsignInformation({required this.atSign, required this.rootDomain});

Map<String, String> 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"],
);
}

@override
List<Object?> get props => [atSign, rootDomain];

@override
String toString() {
return 'AtsignInformation($atSign, $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<Map<String, AtsignInformation>> getAtsignEntries() async {
var keychainAtSigns = await KeychainUtil.getAtsignList() ?? [];
var atSignInfo = <AtsignInformation>[];
try {
atSignInfo = await _getAtsignInformationFromFile();
} catch (e) {
App.log(
"Failed get Atsign Information, ignoring invalid file: ${e.toString()}".loggable,
);
return {};
}
var atSignMap = <String, AtsignInformation>{};
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<bool> saveAtsignInformation(AtsignInformation info) async {
var f = await _getAtsignInformationFile();
final List<AtsignInformation> 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()).toList()),
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<File?> _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<List<AtsignInformation>> _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();
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
}
var res = <AtsignInformation>[];
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> postOnboard(String atSign) async {
App.navState.currentContext?.read<OnboardingCubit>().onboard(atSign);
Future<void> postOnboard(String atSign, String rootDomain) async {
App.navState.currentContext?.read<OnboardingCubit>().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<ProfileListBloc>()
.add(const ProfileListLoadEvent());
App.navState.currentContext
?.read<SettingsBloc>()
.add(const SettingsLoadEvent());
App.navState.currentContext
?.read<FavoriteBloc>()
.add(const FavoriteLoadEvent());
App.navState.currentContext?.read<ProfileListBloc>().add(const ProfileListLoadEvent());
App.navState.currentContext?.read<SettingsBloc>().add(const SettingsLoadEvent());
App.navState.currentContext?.read<FavoriteBloc>().add(const FavoriteLoadEvent());
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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<bool> preSignout() 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<ProfilesRunningCubit>().stopAllAndClear();
App.navState.currentContext?.read<ProfileCacheCubit>().clear();
App.navState.currentContext?.read<ProfilesSelectedCubit>().deselectAll();
App.navState.currentContext?.read<FavoriteBloc>().clearAll();
App.navState.currentContext?.read<ProfileListBloc>().clearAll();
App.navState.currentContext?.read<SettingsBloc>().clear();
App.navState.currentContext?.read<OnboardingCubit>().setStatus(OnboardingStatus.offboarded);
// - Reset the tray icon
App.navState.currentContext?.read<TrayCubit>().initialize();
return true;
}
Loading