From 00cb636f65480b168973e87aa72a11b241c47437 Mon Sep 17 00:00:00 2001 From: Michael Bisgaard Olesen Date: Wed, 15 Nov 2023 23:27:51 +0100 Subject: [PATCH 01/28] Improve initialization and network structure --- lib/main.dart | 113 +++++++++++++++---------- lib/screens/home/screen.dart | 6 +- lib/services/wallet_proxy/service.dart | 6 +- lib/state/config.dart | 2 + lib/state/network.dart | 21 ++--- lib/state/services.dart | 46 +++++----- 6 files changed, 109 insertions(+), 85 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 7501b63..825ad0e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,58 +15,83 @@ void main() { runApp(const App()); } -// In the future, this will be loaded from a proper source rather than being hardcoded. -final config = Config.ofNetworks([ - const Network( - name: NetworkName.testnet, - walletProxyConfig: WalletProxyConfig( - baseUrl: 'https://wallet-proxy.testnet.concordium.com', +/// Load fundamental configuration from the source of truth. +Future loadConfig(HttpService http) async { + // In the future, this will be loaded from a proper source rather than being hardcoded. + return Config.ofNetworks([ + const Network( + name: NetworkName.testnet, + walletProxyConfig: WalletProxyConfig( + baseUrl: 'https://wallet-proxy.testnet.concordium.com', + ), ), - ), -]); + ]); +} + +Future bootstrap() async { + const http = HttpService(); + final config = await loadConfig(http); + final prefs = await SharedPreferences.getInstance(); + return ServiceRepository( + config: config, + http: http, + sharedPreferences: SharedPreferencesService(prefs), + ); +} + +const initialNetwork = NetworkName.testnet; class App extends StatelessWidget { const App({super.key}); @override Widget build(BuildContext context) { - return FutureBuilder( - future: SharedPreferences.getInstance(), + // Initialize configuration and service repository. + return FutureBuilder( + future: bootstrap(), builder: (_, snapshot) { - final prefs = snapshot.data; - if (prefs == null) { - return const _LoadingSharedPreferences(); + final services = snapshot.data; + if (services == null) { + // Initializing configuration and service repository. + return const _Initializing(); } - // Initialize services and provide them to the nested components + // Provide initialized service repository to the nested components // (including the blocs created in the child provider). + // Then activate the initial network (starting services related to that network). return RepositoryProvider( - create: (_) { - final prefsSvc = SharedPreferencesService(prefs); - const httpService = HttpService(); - // TODO 'enableNetwork' is async so should be initialized in a 'FutureBuilder'. - // Currently it actually is sync though, so this change can wait a bit. - return ServiceRepository( - config: config, - httpService: httpService, - sharedPreferences: prefsSvc, - )..enableNetwork(NetworkName.testnet); - }, - child: MultiBlocProvider( - providers: [ - BlocProvider( - create: (_) => ActiveNetwork(config.availableNetworks[NetworkName.testnet]!), - ), - BlocProvider( - create: (context) { - final prefs = context.read().sharedPreferences; - return TermsAndConditionAcceptance(prefs); - }, - ), - ], - child: MaterialApp( - routes: appRoutes, - theme: concordiumTheme(), - ), + create: (_) => services, + child: FutureBuilder( + future: services.activateNetwork(initialNetwork), + builder: (_, snapshot) { + final networkServices = snapshot.data; + if (networkServices == null) { + // Initializing network services. + return const _Initializing(); + } + + // Initialize blocs/cubits. + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (_) { + // Initialize selected network as the one that was just activated. + return SelectedNetwork(networkServices); + }, + ), + BlocProvider( + create: (context) { + // Initialize T&C by loading the currently accepted version from shared preferences. + final prefs = context.read().sharedPreferences; + return TermsAndConditionAcceptance(prefs); + }, + ), + ], + child: MaterialApp( + routes: appRoutes, + theme: concordiumTheme(), + ), + ); + }, ), ); }, @@ -74,8 +99,8 @@ class App extends StatelessWidget { } } -class _LoadingSharedPreferences extends StatelessWidget { - const _LoadingSharedPreferences(); +class _Initializing extends StatelessWidget { + const _Initializing(); @override Widget build(BuildContext context) { @@ -86,7 +111,7 @@ class _LoadingSharedPreferences extends StatelessWidget { CircularProgressIndicator(), SizedBox(height: 16), // Setting text direction is required because we're outside 'MaterialApp' widget. - Text('Loading shared preferences...', textDirection: TextDirection.ltr), + Text('Initializing...', textDirection: TextDirection.ltr), ], ); } diff --git a/lib/screens/home/screen.dart b/lib/screens/home/screen.dart index cf86f80..577346a 100644 --- a/lib/screens/home/screen.dart +++ b/lib/screens/home/screen.dart @@ -1,7 +1,6 @@ import 'package:concordium_wallet/screens/terms_and_conditions/screen.dart'; import 'package:concordium_wallet/services/wallet_proxy/service.dart'; import 'package:concordium_wallet/state/network.dart'; -import 'package:concordium_wallet/state/services.dart'; import 'package:concordium_wallet/state/terms_and_conditions.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -30,10 +29,9 @@ class _HomeScreenState extends State { } void _refresh(BuildContext context) { - final network = context.read().state; - final services = context.read().networkServices[network.active]!; + final network = context.read().state; final tacAcceptance = context.read(); - _updateValidTac(services.walletProxy, tacAcceptance); + _updateValidTac(network.services.walletProxy, tacAcceptance); } @override diff --git a/lib/services/wallet_proxy/service.dart b/lib/services/wallet_proxy/service.dart index a5e6c3a..7c6442c 100644 --- a/lib/services/wallet_proxy/service.dart +++ b/lib/services/wallet_proxy/service.dart @@ -35,14 +35,14 @@ class WalletProxyService { final WalletProxyConfig config; /// HTTP service used to send requests to the instance. - final HttpService httpService; + final HttpService http; - const WalletProxyService({required this.config, required this.httpService}); + const WalletProxyService({required this.config, required this.http}); /// Fetches the currently valid T&C. Future fetchTermsAndConditions() async { final url = config.urlOf(WalletProxyEndpoint.termsAndConditionsVersion); - final response = await httpService.get(url); + final response = await http.get(url); final jsonResponse = jsonDecode(response.body); return TermsAndConditions.fromJson(jsonResponse); } diff --git a/lib/state/config.dart b/lib/state/config.dart index 8bfcfd0..91a4572 100644 --- a/lib/state/config.dart +++ b/lib/state/config.dart @@ -20,6 +20,8 @@ class Config { /// At some other point we'll introduce a notion of "enabled" networks, /// i.e. the list of networks to be included in the user's network selector. /// That list will be a subset of the available networks. + /// Furthermore, the "active" networks are are subset of the enabled networks where we have actual services initialized. + /// Lastly, the "selected" network is the active network that we're actually currently using in the app. /// The purpose of describing the concept here already is to allow other doc comments to reference it early. final Map availableNetworks; diff --git a/lib/state/network.dart b/lib/state/network.dart index c2cc6d1..1640b23 100644 --- a/lib/state/network.dart +++ b/lib/state/network.dart @@ -1,5 +1,6 @@ import 'package:concordium_wallet/state/config.dart'; import 'package:concordium_wallet/services/wallet_proxy/service.dart'; +import 'package:concordium_wallet/state/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; /// Name of a network. @@ -26,20 +27,20 @@ class Network { const Network({required this.name, required this.walletProxyConfig}); } -class ActiveNetworkState { - /// Currently active network. +class SelectedNetworkState { + /// Services corresponding to the currently selected network (as defined in [Config.availableNetworks]). /// - /// The network is guaranteed to be one of the "enabled" networks (as defined in [Config.availableNetworks]). - Network active; + /// The network is guaranteed to be one of the entries in [ServiceRepository.activeNetworks]. + final NetworkServices services; - ActiveNetworkState(this.active); + const SelectedNetworkState(this.services); } -/// State component acting as the source of truth for what network is currently active in the app. -class ActiveNetwork extends Cubit { - ActiveNetwork(Network active) : super(ActiveNetworkState(active)); +/// State component acting as the source of truth for what network is currently selected in the app. +class SelectedNetwork extends Cubit { + SelectedNetwork(NetworkServices services) : super(SelectedNetworkState(services)); - void setActive(Network n) { - emit(ActiveNetworkState(n)); + void select(NetworkServices networkServices) { + emit(SelectedNetworkState(networkServices)); } } diff --git a/lib/state/services.dart b/lib/state/services.dart index 9a752c0..8b1c044 100644 --- a/lib/state/services.dart +++ b/lib/state/services.dart @@ -4,18 +4,22 @@ import 'package:concordium_wallet/services/wallet_proxy/service.dart'; import 'package:concordium_wallet/state/config.dart'; import 'package:concordium_wallet/state/network.dart'; -/// Collection of all services of a network. +/// Collection of all services related to a specific network. class NetworkServices { - /// The Wallet Proxy service on the network. + /// Network to which the services are related. + final Network network; + + /// Wallet Proxy service on the network. final WalletProxyService walletProxy; - const NetworkServices({required this.walletProxy}); + const NetworkServices({required this.network, required this.walletProxy}); - factory NetworkServices.forNetwork(Network n, {required HttpService httpService}) { + factory NetworkServices.forNetwork(Network n, {required HttpService http}) { return NetworkServices( + network: n, walletProxy: WalletProxyService( config: n.walletProxyConfig, - httpService: httpService, + http: http, ), ); } @@ -23,40 +27,34 @@ class NetworkServices { /// Collection of all services available to the app. class ServiceRepository { - /// Service collections for all "enabled" networks (as defined in [Config.availableNetworks]). - final Map networkServices = {}; + /// Service collections for all "active" networks (as defined in [Config.availableNetworks]). + final Map activeNetworks = {}; /// Global configuration used when starting services. final Config config; /// Global service for performing HTTP calls. - final HttpService httpService; + final HttpService http; /// Global service for interacting with shared preferences. final SharedPreferencesService sharedPreferences; - ServiceRepository({required this.config, required this.httpService, required this.sharedPreferences}); + ServiceRepository({required this.config, required this.http, required this.sharedPreferences}); - /// Enable the network with the provided name. + /// Activate the network with the provided name. /// /// The services for interacting with the network are initialized using the global configuration. - /// Once the future completes, the services may be looked up by the network name in [networkServices]. - Future enableNetwork(NetworkName name) async { + /// Once the future completes, the services may be looked up by the network name in [activeNetworks]. + Future activateNetwork(NetworkName name) async { + if (activeNetworks.containsKey(name)) { + throw Exception('network is already enabled'); + } final n = config.availableNetworks[name]; if (n == null) { throw Exception('unknown network'); } - return _enableNetwork(n); - } - - void _enableNetwork(Network n) { - if (networkServices.containsKey(n)) { - throw Exception('network is already enabled'); - } - _setNetwork(n, NetworkServices.forNetwork(n, httpService: httpService)); - } - - void _setNetwork(Network n, NetworkServices services) { - networkServices[n] = services; + final res = NetworkServices.forNetwork(n, http: http); + activeNetworks[name] = res; + return res; } } From 8b06c261fe6990c2bf9879d55543afc122ffce96 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 20 Nov 2023 12:57:56 +0100 Subject: [PATCH 02/28] migrated to Hive --- .../accepted_terms_and_conditions.dart | 26 +++++ .../accepted_terms_and_conditions.g.dart | 45 ++++++++ lib/main.dart | 9 +- lib/screens/home/screen.dart | 7 +- lib/screens/terms_and_conditions/screen.dart | 5 +- lib/services/shared_preferences/service.dart | 51 ++++++--- lib/services/wallet_proxy/model.g.dart | 6 +- lib/state/services.dart | 2 +- lib/state/terms_and_conditions.dart | 38 +++---- pubspec.lock | 104 ++++++++---------- pubspec.yaml | 4 +- 11 files changed, 190 insertions(+), 107 deletions(-) create mode 100644 lib/entities/accepted_terms_and_conditions.dart create mode 100644 lib/entities/accepted_terms_and_conditions.g.dart diff --git a/lib/entities/accepted_terms_and_conditions.dart b/lib/entities/accepted_terms_and_conditions.dart new file mode 100644 index 0000000..40b40e5 --- /dev/null +++ b/lib/entities/accepted_terms_and_conditions.dart @@ -0,0 +1,26 @@ +import 'package:concordium_wallet/services/wallet_proxy/model.dart'; +import 'package:hive_flutter/hive_flutter.dart'; + +part 'accepted_terms_and_conditions.g.dart'; + +/// Version of the Terms & Conditions accepted by the user. +@HiveType(typeId: 1) +class AcceptedTermsAndConditions { + static const table = "accepted_terms_and_conditions"; + + @HiveField(1) + final String acceptedVersion; + @HiveField(2) + final DateTime acceptedAt; + + AcceptedTermsAndConditions({required this.acceptedVersion, required this.acceptedAt}); + + factory AcceptedTermsAndConditions.acceptNow(String acceptedVersion) { + return AcceptedTermsAndConditions(acceptedVersion: acceptedVersion, acceptedAt: DateTime.now()); + } + + /// Whether the accepted version is valid with respect to the provided valid version. + bool isValid(TermsAndConditions tac) { + return acceptedVersion == tac.version; + } +} diff --git a/lib/entities/accepted_terms_and_conditions.g.dart b/lib/entities/accepted_terms_and_conditions.g.dart new file mode 100644 index 0000000..ce10e45 --- /dev/null +++ b/lib/entities/accepted_terms_and_conditions.g.dart @@ -0,0 +1,45 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'accepted_terms_and_conditions.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class AcceptedTermsAndConditionsAdapter + extends TypeAdapter { + @override + final int typeId = 1; + + @override + AcceptedTermsAndConditions read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return AcceptedTermsAndConditions( + acceptedVersion: fields[1] as String, + acceptedAt: fields[2] as DateTime, + ); + } + + @override + void write(BinaryWriter writer, AcceptedTermsAndConditions obj) { + writer + ..writeByte(2) + ..writeByte(1) + ..write(obj.acceptedVersion) + ..writeByte(2) + ..write(obj.acceptedAt); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is AcceptedTermsAndConditionsAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/main.dart b/lib/main.dart index 825ad0e..e06848a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,7 +9,7 @@ import 'package:concordium_wallet/state/terms_and_conditions.dart'; import 'package:concordium_wallet/theme.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:shared_preferences/shared_preferences.dart'; +import 'package:hive_flutter/hive_flutter.dart'; void main() { runApp(const App()); @@ -31,11 +31,12 @@ Future loadConfig(HttpService http) async { Future bootstrap() async { const http = HttpService(); final config = await loadConfig(http); - final prefs = await SharedPreferences.getInstance(); + final storageService = await StorageService.init(); + await Hive.initFlutter(); return ServiceRepository( config: config, http: http, - sharedPreferences: SharedPreferencesService(prefs), + sharedPreferences: storageService, ); } @@ -82,7 +83,7 @@ class App extends StatelessWidget { create: (context) { // Initialize T&C by loading the currently accepted version from shared preferences. final prefs = context.read().sharedPreferences; - return TermsAndConditionAcceptance(prefs); + return TermsAndConditionAcceptance(prefs, networkServices.network.name); }, ), ], diff --git a/lib/screens/home/screen.dart b/lib/screens/home/screen.dart index 577346a..3c3ec23 100644 --- a/lib/screens/home/screen.dart +++ b/lib/screens/home/screen.dart @@ -1,3 +1,4 @@ +import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; import 'package:concordium_wallet/screens/terms_and_conditions/screen.dart'; import 'package:concordium_wallet/services/wallet_proxy/service.dart'; import 'package:concordium_wallet/state/network.dart'; @@ -64,7 +65,7 @@ class _HomeScreenState extends State { if (acceptedTac == null || !acceptedTac.isValid(validTac.termsAndConditions)) { return TermsAndConditionsScreen( validTermsAndConditions: validTac.termsAndConditions, - acceptedTermsAndConditionsVersion: acceptedTac?.version, + acceptedTermsAndConditionsVersion: acceptedTac?.acceptedVersion, ); } return Column( @@ -72,7 +73,7 @@ class _HomeScreenState extends State { Expanded( child: Column( children: [ - Text('Accepted T&C version: ${tacState.accepted?.version}'), + Text('Accepted T&C version: ${tacState.accepted?.acceptedVersion}'), Text('Valid T&C last refreshed at ${tacState.valid?.refreshedAt}.'), ], ), @@ -90,7 +91,7 @@ class _HomeScreenState extends State { const SizedBox(height: 8), ElevatedButton( onPressed: () { - const tac = AcceptedTermsAndConditions(version: '1.2.3'); + final tac = AcceptedTermsAndConditions.acceptNow('1.2.3'); context.read().userAccepted(tac); }, child: const Text('Set accepted T&C version to 1.2.3'), diff --git a/lib/screens/terms_and_conditions/screen.dart b/lib/screens/terms_and_conditions/screen.dart index 198428d..1204165 100644 --- a/lib/screens/terms_and_conditions/screen.dart +++ b/lib/screens/terms_and_conditions/screen.dart @@ -1,3 +1,4 @@ +import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; import 'package:concordium_wallet/screens/terms_and_conditions/widget.dart'; import 'package:concordium_wallet/services/wallet_proxy/model.dart'; import 'package:concordium_wallet/state/terms_and_conditions.dart'; @@ -122,9 +123,7 @@ class _TermsAndConditionsScreenState extends State { Function()? _onAcceptButtonPressed(BuildContext context) { if (isAccepted) { return () { - final tac = AcceptedTermsAndConditions( - version: widget.validTermsAndConditions.version, - ); + final tac = AcceptedTermsAndConditions.acceptNow(widget.validTermsAndConditions.version); context.read().userAccepted(tac); }; } diff --git a/lib/services/shared_preferences/service.dart b/lib/services/shared_preferences/service.dart index cb18c14..a98848d 100644 --- a/lib/services/shared_preferences/service.dart +++ b/lib/services/shared_preferences/service.dart @@ -1,25 +1,50 @@ -import 'package:shared_preferences/shared_preferences.dart'; -/// Service for interacting with [SharedPreferences]. -class SharedPreferencesService { - /// String key associated with the persisted accepted T&C version. - static const _tacAcceptedVersionKey = 'tac:accepted_version'; - /// Wrapped instance. - final SharedPreferences _prefs; - const SharedPreferencesService(this._prefs); +import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; +import 'package:concordium_wallet/state/network.dart'; +import 'package:hive_flutter/hive_flutter.dart'; + +/// Service for interacting with [Hive]. +class StorageService { + final Box _acceptedTermsAndConditionBox; + + const StorageService._(this._acceptedTermsAndConditionBox); + + static Future init() async { + await Hive.initFlutter(); + + _registerAdapters(); + await _openBoxes(); + + return StorageService._( + await Hive.openBox(AcceptedTermsAndConditions.table) + ); + } + + /// Register all adapters needed for typed boxes. + static void _registerAdapters() { + Hive.registerAdapter(AcceptedTermsAndConditionsAdapter()); + } + + /// Opens all boxes asynchronously. + static Future _openBoxes() async { + final atcFuture = Hive.openBox(AcceptedTermsAndConditions.table); + await Future.wait([atcFuture]); + } /// Reads the currently accepted T&C version. - String? get termsAndConditionsAcceptedVersion => _prefs.getString(_tacAcceptedVersionKey); + AcceptedTermsAndConditions? getAcceptedTermsAndConditions(NetworkName networkName) { + return _acceptedTermsAndConditionBox.get(networkName.name); + } /// Writes the currently accepted T&C version. - Future writeTermsAndConditionsAcceptedVersion(String version) async { - await _prefs.setString(_tacAcceptedVersionKey, version); + Future writeAcceptedTermsAndConditions(NetworkName networkName, AcceptedTermsAndConditions acceptedTermsAndConditions) { + return _acceptedTermsAndConditionBox.put(networkName.name, acceptedTermsAndConditions); } /// Deletes the currently accepted T&C version. - Future deleteTermsAndConditionsAcceptedVersion() async { - await _prefs.remove(_tacAcceptedVersionKey); + Future deleteTermsAndConditionsAcceptedVersion(NetworkName networkName) { + return _acceptedTermsAndConditionBox.delete(networkName); } } diff --git a/lib/services/wallet_proxy/model.g.dart b/lib/services/wallet_proxy/model.g.dart index 1f8d7d1..1f748aa 100644 --- a/lib/services/wallet_proxy/model.g.dart +++ b/lib/services/wallet_proxy/model.g.dart @@ -6,12 +6,14 @@ part of 'model.dart'; // JsonSerializableGenerator // ************************************************************************** -TermsAndConditions _$TermsAndConditionsFromJson(Map json) => TermsAndConditions( +TermsAndConditions _$TermsAndConditionsFromJson(Map json) => + TermsAndConditions( Uri.parse(json['url'] as String), json['version'] as String, ); -Map _$TermsAndConditionsToJson(TermsAndConditions instance) => { +Map _$TermsAndConditionsToJson(TermsAndConditions instance) => + { 'url': instance.url.toString(), 'version': instance.version, }; diff --git a/lib/state/services.dart b/lib/state/services.dart index 8b1c044..5f82c8e 100644 --- a/lib/state/services.dart +++ b/lib/state/services.dart @@ -37,7 +37,7 @@ class ServiceRepository { final HttpService http; /// Global service for interacting with shared preferences. - final SharedPreferencesService sharedPreferences; + final StorageService sharedPreferences; ServiceRepository({required this.config, required this.http, required this.sharedPreferences}); diff --git a/lib/state/terms_and_conditions.dart b/lib/state/terms_and_conditions.dart index 699c9ef..587479d 100644 --- a/lib/state/terms_and_conditions.dart +++ b/lib/state/terms_and_conditions.dart @@ -1,20 +1,9 @@ +import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; import 'package:concordium_wallet/services/shared_preferences/service.dart'; import 'package:concordium_wallet/services/wallet_proxy/model.dart'; +import 'package:concordium_wallet/state/network.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -/// Version of the Terms & Conditions accepted by the user. -class AcceptedTermsAndConditions { - /// Accepted version. - final String version; - - const AcceptedTermsAndConditions({required this.version}); - - /// Whether the accepted version is valid with respect to the provided valid version. - bool isValid(TermsAndConditions tac) { - return version == tac.version; - } -} - /// Version of the Terms & Conditions that is considered valid. /// /// The user has to have accepted this version (or more generally, a compatible version) @@ -49,12 +38,13 @@ class TermsAndConditionsAcceptanceState { /// State component of the currently accepted and valid Terms & Conditions. class TermsAndConditionAcceptance extends Cubit { /// Service used to persist the accepted T&C version. - final SharedPreferencesService _prefs; + final StorageService _storage; + final NetworkName networkName; - TermsAndConditionAcceptance(this._prefs) : super(const TermsAndConditionsAcceptanceState(accepted: null, valid: null)) { - final acceptedVersion = _prefs.termsAndConditionsAcceptedVersion; + TermsAndConditionAcceptance(this._storage, this.networkName) : super(const TermsAndConditionsAcceptanceState(accepted: null, valid: null)) { + final acceptedVersion = _storage.getAcceptedTermsAndConditions(networkName); if (acceptedVersion != null) { - userAccepted(AcceptedTermsAndConditions(version: acceptedVersion)); + userAccepted(acceptedVersion); } } @@ -101,17 +91,17 @@ class TermsAndConditionAcceptance extends Cubit change) { super.onChange(change); + if (change.currentState == change.nextState) { + return; + } // TODO: Pass success/failure status to notification service. - _persistAcceptedVersionIfChanged(change.nextState.accepted?.version, change.currentState.accepted?.version); + _persistAcceptedVersionIfChanged(change.nextState.accepted); } - Future _persistAcceptedVersionIfChanged(String? nextAcceptedVersion, String? currentAcceptedVersion) { - if (nextAcceptedVersion == currentAcceptedVersion) { - return Future.value(); - } + Future _persistAcceptedVersionIfChanged(AcceptedTermsAndConditions? nextAcceptedVersion) { if (nextAcceptedVersion == null) { - return _prefs.deleteTermsAndConditionsAcceptedVersion(); + return _storage.deleteTermsAndConditionsAcceptedVersion(networkName); } - return _prefs.writeTermsAndConditionsAcceptedVersion(nextAcceptedVersion); + return _storage.writeAcceptedTermsAndConditions(networkName, nextAcceptedVersion); } } diff --git a/pubspec.lock b/pubspec.lock index 3cb3419..971d46f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -280,6 +280,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" + source: hosted + version: "1.1.0" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + url: "https://pub.dev" + source: hosted + version: "2.0.1" http: dependency: "direct main" description: @@ -416,6 +440,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" path_provider_linux: dependency: transitive description: @@ -496,62 +544,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.3" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: b7f41bad7e521d205998772545de63ff4e6c97714775902c199353f8bf1511ac - url: "https://pub.dev" - source: hosted - version: "2.2.1" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06" - url: "https://pub.dev" - source: hosted - version: "2.2.1" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7" - url: "https://pub.dev" - source: hosted - version: "2.3.4" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: c2eb5bf57a2fe9ad6988121609e47d3e07bb3bdca5b6f8444e4cf302428a128a - url: "https://pub.dev" - source: hosted - version: "2.3.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a - url: "https://pub.dev" - source: hosted - version: "2.3.1" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: d762709c2bbe80626ecc819143013cc820fa49ca5e363620ee20a8b15a3e3daf - url: "https://pub.dev" - source: hosted - version: "2.2.1" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: f763a101313bd3be87edffe0560037500967de9c394a714cd598d945517f694f - url: "https://pub.dev" - source: hosted - version: "2.3.1" shelf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2af12dd..d5292d0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,9 +39,10 @@ dependencies: build_runner: ^2.4.6 flutter_svg: ^2.0.7 url_launcher: ^6.1.14 - shared_preferences: ^2.2.1 http: ^1.1.0 flutter_bloc: ^8.1.3 + hive: ^2.2.3 + hive_flutter: ^1.1.0 dev_dependencies: flutter_test: @@ -54,6 +55,7 @@ dev_dependencies: # rules and activating additional ones. flutter_lints: ^2.0.0 json_serializable: ^6.7.1 + hive_generator: ^2.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec From 8af61af2e4afb40ca95a274902398b3de8a3dd17 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 20 Nov 2023 13:06:58 +0100 Subject: [PATCH 03/28] changed name --- lib/state/services.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/state/services.dart b/lib/state/services.dart index 5f82c8e..ceff72e 100644 --- a/lib/state/services.dart +++ b/lib/state/services.dart @@ -36,10 +36,10 @@ class ServiceRepository { /// Global service for performing HTTP calls. final HttpService http; - /// Global service for interacting with shared preferences. - final StorageService sharedPreferences; + /// Global service for interacting with storage. + final StorageService storage; - ServiceRepository({required this.config, required this.http, required this.sharedPreferences}); + ServiceRepository({required this.config, required this.http, required this.storage}); /// Activate the network with the provided name. /// From b3252d8987720c571fcc515091a54dd238a2f8dc Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 20 Nov 2023 14:40:02 +0100 Subject: [PATCH 04/28] added test --- accepted_terms_and_conditions.hive | Bin 0 -> 164 bytes accepted_terms_and_conditions.lock | 0 lib/main.dart | 4 +- .../service.dart => providers/storage.dart} | 12 +-- lib/state/services.dart | 4 +- lib/state/terms_and_conditions.dart | 5 +- pubspec.lock | 72 +++++++++--------- test/providers/storage_test.dart | 39 ++++++++++ 8 files changed, 86 insertions(+), 50 deletions(-) create mode 100644 accepted_terms_and_conditions.hive create mode 100644 accepted_terms_and_conditions.lock rename lib/{services/shared_preferences/service.dart => providers/storage.dart} (90%) create mode 100644 test/providers/storage_test.dart diff --git a/accepted_terms_and_conditions.hive b/accepted_terms_and_conditions.hive new file mode 100644 index 0000000000000000000000000000000000000000..06096fc890f6f985c72c4c74be3b4c62977a0c44 GIT binary patch literal 164 zcmdO7U|?WmOUutsN-R=jVq{?h@(uJ1^h}JHgcua=Ejzcb!imA*s loadConfig(HttpService http) async { Future bootstrap() async { const http = HttpService(); final configFuture = loadConfig(http); - final storageFuture = StorageService.init(); + final storageFuture = StorageProvider.init(); final config = await configFuture; final storageService = await storageFuture; return ServiceRepository( diff --git a/lib/services/shared_preferences/service.dart b/lib/providers/storage.dart similarity index 90% rename from lib/services/shared_preferences/service.dart rename to lib/providers/storage.dart index a98848d..b7988ec 100644 --- a/lib/services/shared_preferences/service.dart +++ b/lib/providers/storage.dart @@ -1,23 +1,19 @@ - - - import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; import 'package:concordium_wallet/state/network.dart'; import 'package:hive_flutter/hive_flutter.dart'; /// Service for interacting with [Hive]. -class StorageService { +class StorageProvider { final Box _acceptedTermsAndConditionBox; - const StorageService._(this._acceptedTermsAndConditionBox); + const StorageProvider._(this._acceptedTermsAndConditionBox); - static Future init() async { + static Future init() async { await Hive.initFlutter(); - _registerAdapters(); await _openBoxes(); - return StorageService._( + return StorageProvider._( await Hive.openBox(AcceptedTermsAndConditions.table) ); } diff --git a/lib/state/services.dart b/lib/state/services.dart index ceff72e..b80c18b 100644 --- a/lib/state/services.dart +++ b/lib/state/services.dart @@ -1,5 +1,5 @@ import 'package:concordium_wallet/services/http.dart'; -import 'package:concordium_wallet/services/shared_preferences/service.dart'; +import 'package:concordium_wallet/providers/storage.dart'; import 'package:concordium_wallet/services/wallet_proxy/service.dart'; import 'package:concordium_wallet/state/config.dart'; import 'package:concordium_wallet/state/network.dart'; @@ -37,7 +37,7 @@ class ServiceRepository { final HttpService http; /// Global service for interacting with storage. - final StorageService storage; + final StorageProvider storage; ServiceRepository({required this.config, required this.http, required this.storage}); diff --git a/lib/state/terms_and_conditions.dart b/lib/state/terms_and_conditions.dart index 587479d..5d72e21 100644 --- a/lib/state/terms_and_conditions.dart +++ b/lib/state/terms_and_conditions.dart @@ -1,5 +1,5 @@ import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; -import 'package:concordium_wallet/services/shared_preferences/service.dart'; +import 'package:concordium_wallet/providers/storage.dart'; import 'package:concordium_wallet/services/wallet_proxy/model.dart'; import 'package:concordium_wallet/state/network.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -38,10 +38,11 @@ class TermsAndConditionsAcceptanceState { /// State component of the currently accepted and valid Terms & Conditions. class TermsAndConditionAcceptance extends Cubit { /// Service used to persist the accepted T&C version. - final StorageService _storage; + final StorageProvider _storage; final NetworkName networkName; TermsAndConditionAcceptance(this._storage, this.networkName) : super(const TermsAndConditionsAcceptanceState(accepted: null, valid: null)) { + final acceptedVersion = _storage.getAcceptedTermsAndConditions(networkName); if (acceptedVersion != null) { userAccepted(acceptedVersion); diff --git a/pubspec.lock b/pubspec.lock index 971d46f..10d29e1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" + sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "4.0.1" build_resolvers: dependency: transitive description: @@ -109,10 +109,10 @@ packages: dependency: transitive description: name: built_value - sha256: a8de5955205b4d1dbbbc267daddf2178bd737e4bab8987c04a500478c9651e74 + sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" url: "https://pub.dev" source: hosted - version: "8.6.3" + version: "8.7.0" characters: dependency: transitive description: @@ -205,10 +205,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: @@ -242,10 +242,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338" + sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.9" flutter_test: dependency: "direct dev" description: flutter @@ -508,10 +508,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d + sha256: f4f88d4a900933e7267e2b353594774fc0d07fb072b47eedcd5b54e1ea3269f8 url: "https://pub.dev" source: hosted - version: "2.1.6" + version: "2.1.7" pool: dependency: transitive description: @@ -524,10 +524,10 @@ packages: dependency: transitive description: name: provider - sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096" url: "https://pub.dev" source: hosted - version: "6.0.5" + version: "6.1.1" pub_semver: dependency: transitive description: @@ -657,90 +657,90 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "47e208a6711459d813ba18af120d9663c20bdf6985d6ad39fe165d2538378d27" + sha256: b1c9e98774adf8820c96fbc7ae3601231d324a7d5ebd8babe27b6dfac91357ba url: "https://pub.dev" source: hosted - version: "6.1.14" + version: "6.2.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: b04af59516ab45762b2ca6da40fa830d72d0f6045cd97744450b73493fa76330 + sha256: "31222ffb0063171b526d3e569079cf1f8b294075ba323443fdc690842bfd4def" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.0" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7c65021d5dee51813d652357bc65b8dd4a6177082a9966bc8ba6ee477baa795f" + sha256: bba3373219b7abb6b5e0d071b0fe66dfbe005d07517a68e38d4fc3638f35c6d3 url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "6.2.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: b651aad005e0cb06a01dbd84b428a301916dc75f0e7ea6165f80057fee2d8e8e + sha256: "9f2d390e096fdbe1e6e6256f97851e51afc2d9c423d3432f1d6a02a8a9a8b9fd" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.0" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: b55486791f666e62e0e8ff825e58a023fd6b1f71c49926483f1128d3bbd8fe88 + sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 url: "https://pub.dev" source: hosted - version: "3.0.7" + version: "3.1.0" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "95465b39f83bfe95fcb9d174829d6476216f2d548b79c38ab2506e0458787618" + sha256: "980e8d9af422f477be6948bdfb68df8433be71f5743a188968b0c1b887807e50" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5" + sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" url: "https://pub.dev" source: hosted - version: "2.0.20" + version: "2.2.0" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "95fef3129dc7cfaba2bc3d5ba2e16063bb561fc6d78e63eee16162bc70029069" + sha256: "7754a1ad30ee896b265f8d14078b0513a4dba28d358eabb9d5f339886f4a1adc" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.1.0" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "670f6e07aca990b4a2bcdc08a784193c4ccdd1932620244c3a86bb72a0eac67f" + sha256: "0f0c746dd2d6254a0057218ff980fc7f5670fd0fcf5e4db38a490d31eed4ad43" url: "https://pub.dev" source: hosted - version: "1.1.7" + version: "1.1.9+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "7451721781d967db9933b63f5733b1c4533022c0ba373a01bdd79d1a5457f69f" + sha256: "0edf6d630d1bfd5589114138ed8fada3234deacc37966bec033d3047c29248b7" url: "https://pub.dev" source: hosted - version: "1.1.7" + version: "1.1.9+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "80a13c613c8bde758b1464a1755a7b3a8f2b6cec61fbf0f5a53c94c30f03ba2e" + sha256: d24333727332d9bd20990f1483af4e09abdb9b1fc7c3db940b56ab5c42790c26 url: "https://pub.dev" source: hosted - version: "1.1.7" + version: "1.1.9+1" vector_math: dependency: transitive description: @@ -777,10 +777,10 @@ packages: dependency: transitive description: name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "5.1.0" xdg_directories: dependency: transitive description: diff --git a/test/providers/storage_test.dart b/test/providers/storage_test.dart new file mode 100644 index 0000000..2711558 --- /dev/null +++ b/test/providers/storage_test.dart @@ -0,0 +1,39 @@ +import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; +import 'package:concordium_wallet/providers/storage.dart'; +import 'package:concordium_wallet/state/network.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + + /// Mock getApplicationDocumentsDirectory on channel plugins.flutter.io/path_provider + mockPathProvider() { + TestWidgetsFlutterBinding.ensureInitialized(); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler( + const MethodChannel("plugins.flutter.io/path_provider"), + (MethodCall methodCall) async { + return "."; + } + ); + } + + setUpAll(() => mockPathProvider()); + + test('When add terms and condition to storage, then saved', () async { + // Arrange + final storage = await StorageProvider.init(); + const name = "foobar"; + const expectedVersion = "0.0.42"; + const networkName = NetworkName(name); + final accepted = AcceptedTermsAndConditions.acceptNow(expectedVersion); + + // Act + storage.writeAcceptedTermsAndConditions(networkName, accepted); + + // Assert + final actual = storage.getAcceptedTermsAndConditions(networkName); + expect(actual, isNotNull); + expect(actual!.acceptedVersion, expectedVersion); + }); +} From e45fd2b24bbced09ae714fd46eb7ecf924d2415b Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 20 Nov 2023 14:42:44 +0100 Subject: [PATCH 05/28] updated ci to include test --- .github/workflows/{analyze-format.yml => ci.yml} | 3 +++ 1 file changed, 3 insertions(+) rename .github/workflows/{analyze-format.yml => ci.yml} (94%) diff --git a/.github/workflows/analyze-format.yml b/.github/workflows/ci.yml similarity index 94% rename from .github/workflows/analyze-format.yml rename to .github/workflows/ci.yml index 66c42bc..beb665f 100644 --- a/.github/workflows/analyze-format.yml +++ b/.github/workflows/ci.yml @@ -31,3 +31,6 @@ jobs: - name: Check format run: dart format --output=none --set-exit-if-changed . -l 150 + + - name: Test + run: flutter test From 1a097ea9d410259a3c9ec57af044600213f0a6b4 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 20 Nov 2023 14:51:39 +0100 Subject: [PATCH 06/28] added test location and updated gitignore --- .gitignore | 3 +++ accepted_terms_and_conditions.hive | Bin 164 -> 0 bytes accepted_terms_and_conditions.lock | 0 test/providers/storage_test.dart | 2 +- 4 files changed, 4 insertions(+), 1 deletion(-) delete mode 100644 accepted_terms_and_conditions.hive delete mode 100644 accepted_terms_and_conditions.lock diff --git a/.gitignore b/.gitignore index ad3c2ca..b1bbdd9 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# Hive testing +/test/hive_storage_test diff --git a/accepted_terms_and_conditions.hive b/accepted_terms_and_conditions.hive deleted file mode 100644 index 06096fc890f6f985c72c4c74be3b4c62977a0c44..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 164 zcmdO7U|?WmOUutsN-R=jVq{?h@(uJ1^h}JHgcua=Ejzcb!imA*s Date: Mon, 20 Nov 2023 15:23:25 +0100 Subject: [PATCH 07/28] adding test for all cases --- lib/providers/storage.dart | 2 +- test/providers/storage_test.dart | 55 +++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/lib/providers/storage.dart b/lib/providers/storage.dart index b7988ec..c8c716f 100644 --- a/lib/providers/storage.dart +++ b/lib/providers/storage.dart @@ -41,6 +41,6 @@ class StorageProvider { /// Deletes the currently accepted T&C version. Future deleteTermsAndConditionsAcceptedVersion(NetworkName networkName) { - return _acceptedTermsAndConditionBox.delete(networkName); + return _acceptedTermsAndConditionBox.delete(networkName.name); } } diff --git a/test/providers/storage_test.dart b/test/providers/storage_test.dart index 552e9a7..4a31de0 100644 --- a/test/providers/storage_test.dart +++ b/test/providers/storage_test.dart @@ -3,6 +3,7 @@ import 'package:concordium_wallet/providers/storage.dart'; import 'package:concordium_wallet/state/network.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:hive/hive.dart'; void main() { @@ -18,22 +19,66 @@ void main() { ); } - setUpAll(() => mockPathProvider()); + late StorageProvider storage; - test('When add terms and condition to storage, then saved', () async { + setUpAll(() async { + mockPathProvider(); + storage = await StorageProvider.init(); + }); + + tearDownAll(() => Hive.deleteFromDisk()); + + tearDown(() => Hive.box(AcceptedTermsAndConditions.table).clear()); + + test('When add accepted terms and condition to storage, then saved', () async { // Arrange - final storage = await StorageProvider.init(); const name = "foobar"; const expectedVersion = "0.0.42"; const networkName = NetworkName(name); final accepted = AcceptedTermsAndConditions.acceptNow(expectedVersion); - + // Act - storage.writeAcceptedTermsAndConditions(networkName, accepted); + await storage.writeAcceptedTermsAndConditions(networkName, accepted); // Assert final actual = storage.getAcceptedTermsAndConditions(networkName); expect(actual, isNotNull); expect(actual!.acceptedVersion, expectedVersion); }); + + test("When delete accepted terms and condition from storage, then empty", () async { + // Arrange + const name = "foobar"; + const expectedVersion = "0.0.42"; + const networkName = NetworkName(name); + final accepted = AcceptedTermsAndConditions.acceptNow(expectedVersion); + await storage.writeAcceptedTermsAndConditions(networkName, accepted); + expect(Hive.box(AcceptedTermsAndConditions.table).get(networkName.name), isNotNull); + + // Act + await storage.deleteTermsAndConditionsAcceptedVersion(networkName); + + // Assert + final actual = storage.getAcceptedTermsAndConditions(networkName); + expect(actual, null); + }); + + test("When update accepted terms and conditions, then version updated", () async { + // Arrange + const name = "foobar"; + const oldVersion = "0.0.42"; + const newVersion = "0.0.84"; + const networkName = NetworkName(name); + final oldAccepted = AcceptedTermsAndConditions.acceptNow(oldVersion); + final newAccepted = AcceptedTermsAndConditions.acceptNow(newVersion); + await storage.writeAcceptedTermsAndConditions(networkName, oldAccepted); + expect(Hive.box(AcceptedTermsAndConditions.table).get(networkName.name), isNotNull); + + // Act + await storage.writeAcceptedTermsAndConditions(networkName, newAccepted); + + // Assert + final actual = storage.getAcceptedTermsAndConditions(networkName); + expect(actual, newAccepted); + }); } From 6b2e8a326b894c2465c0568046ba5cb1ab999a9c Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 20 Nov 2023 15:26:44 +0100 Subject: [PATCH 08/28] Fix merge errors --- test/terms_and_conditions_test.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/terms_and_conditions_test.dart b/test/terms_and_conditions_test.dart index b8edcb2..2b3d516 100644 --- a/test/terms_and_conditions_test.dart +++ b/test/terms_and_conditions_test.dart @@ -1,4 +1,5 @@ import 'package:bloc_test/bloc_test.dart'; +import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; import 'package:concordium_wallet/services/url_launcher.dart'; import 'package:concordium_wallet/state/terms_and_conditions.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -25,7 +26,7 @@ void main() { const String acceptedVersion = "1.0.0"; setUpAll(() { - registerFallbackValue(const AcceptedTermsAndConditions(version: validVersion)); + registerFallbackValue(AcceptedTermsAndConditions.acceptNow(validVersion)); }); setUp(() { @@ -33,7 +34,7 @@ void main() { final terms = TermsAndConditions(Uri.parse("localhost"), validVersion); state = TermsAndConditionsAcceptanceState( - accepted: const AcceptedTermsAndConditions(version: acceptedVersion), + accepted: AcceptedTermsAndConditions.acceptNow(acceptedVersion), valid: ValidTermsAndConditions.refreshedNow(termsAndConditions: terms)); // Build the terms and condition screen we wish to test From 533d1640148bd8e9f115d9c6628a5cc428d524e8 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 20 Nov 2023 15:27:23 +0100 Subject: [PATCH 09/28] Update ci.yml --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index beb665f..cbbd80b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Analyze and format +name: CI on: # Trigger the workflow on pushes to the main and feature branches as well as PRs targeting them. From 33ec8dbcaf7539dfd65f122d13c207a71c1b491f Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 20 Nov 2023 15:30:41 +0100 Subject: [PATCH 10/28] formatted --- .github/workflows/ci.yml | 2 +- lib/entities/accepted_terms_and_conditions.dart | 2 +- lib/entities/accepted_terms_and_conditions.g.dart | 8 ++------ lib/providers/storage.dart | 10 ++++------ lib/services/wallet_proxy/model.g.dart | 6 ++---- lib/state/terms_and_conditions.dart | 1 - test/providers/storage_test.dart | 12 ++++-------- test/terms_and_conditions_test.dart | 3 +-- 8 files changed, 15 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cbbd80b..9053297 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ env: flutter_version: 3.13.6 jobs: - analyze-format: + analyze-format-test: runs-on: ubuntu-22.04 steps: - name: Checkout repository diff --git a/lib/entities/accepted_terms_and_conditions.dart b/lib/entities/accepted_terms_and_conditions.dart index 40b40e5..92149c5 100644 --- a/lib/entities/accepted_terms_and_conditions.dart +++ b/lib/entities/accepted_terms_and_conditions.dart @@ -22,5 +22,5 @@ class AcceptedTermsAndConditions { /// Whether the accepted version is valid with respect to the provided valid version. bool isValid(TermsAndConditions tac) { return acceptedVersion == tac.version; - } + } } diff --git a/lib/entities/accepted_terms_and_conditions.g.dart b/lib/entities/accepted_terms_and_conditions.g.dart index ce10e45..0a94586 100644 --- a/lib/entities/accepted_terms_and_conditions.g.dart +++ b/lib/entities/accepted_terms_and_conditions.g.dart @@ -6,8 +6,7 @@ part of 'accepted_terms_and_conditions.dart'; // TypeAdapterGenerator // ************************************************************************** -class AcceptedTermsAndConditionsAdapter - extends TypeAdapter { +class AcceptedTermsAndConditionsAdapter extends TypeAdapter { @override final int typeId = 1; @@ -38,8 +37,5 @@ class AcceptedTermsAndConditionsAdapter @override bool operator ==(Object other) => - identical(this, other) || - other is AcceptedTermsAndConditionsAdapter && - runtimeType == other.runtimeType && - typeId == other.typeId; + identical(this, other) || other is AcceptedTermsAndConditionsAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } diff --git a/lib/providers/storage.dart b/lib/providers/storage.dart index c8c716f..c5f0e7d 100644 --- a/lib/providers/storage.dart +++ b/lib/providers/storage.dart @@ -5,7 +5,7 @@ import 'package:hive_flutter/hive_flutter.dart'; /// Service for interacting with [Hive]. class StorageProvider { final Box _acceptedTermsAndConditionBox; - + const StorageProvider._(this._acceptedTermsAndConditionBox); static Future init() async { @@ -13,9 +13,7 @@ class StorageProvider { _registerAdapters(); await _openBoxes(); - return StorageProvider._( - await Hive.openBox(AcceptedTermsAndConditions.table) - ); + return StorageProvider._(await Hive.openBox(AcceptedTermsAndConditions.table)); } /// Register all adapters needed for typed boxes. @@ -27,7 +25,7 @@ class StorageProvider { static Future _openBoxes() async { final atcFuture = Hive.openBox(AcceptedTermsAndConditions.table); await Future.wait([atcFuture]); - } + } /// Reads the currently accepted T&C version. AcceptedTermsAndConditions? getAcceptedTermsAndConditions(NetworkName networkName) { @@ -36,7 +34,7 @@ class StorageProvider { /// Writes the currently accepted T&C version. Future writeAcceptedTermsAndConditions(NetworkName networkName, AcceptedTermsAndConditions acceptedTermsAndConditions) { - return _acceptedTermsAndConditionBox.put(networkName.name, acceptedTermsAndConditions); + return _acceptedTermsAndConditionBox.put(networkName.name, acceptedTermsAndConditions); } /// Deletes the currently accepted T&C version. diff --git a/lib/services/wallet_proxy/model.g.dart b/lib/services/wallet_proxy/model.g.dart index 1f748aa..1f8d7d1 100644 --- a/lib/services/wallet_proxy/model.g.dart +++ b/lib/services/wallet_proxy/model.g.dart @@ -6,14 +6,12 @@ part of 'model.dart'; // JsonSerializableGenerator // ************************************************************************** -TermsAndConditions _$TermsAndConditionsFromJson(Map json) => - TermsAndConditions( +TermsAndConditions _$TermsAndConditionsFromJson(Map json) => TermsAndConditions( Uri.parse(json['url'] as String), json['version'] as String, ); -Map _$TermsAndConditionsToJson(TermsAndConditions instance) => - { +Map _$TermsAndConditionsToJson(TermsAndConditions instance) => { 'url': instance.url.toString(), 'version': instance.version, }; diff --git a/lib/state/terms_and_conditions.dart b/lib/state/terms_and_conditions.dart index 5d72e21..bf4b589 100644 --- a/lib/state/terms_and_conditions.dart +++ b/lib/state/terms_and_conditions.dart @@ -42,7 +42,6 @@ class TermsAndConditionAcceptance extends Cubit Date: Wed, 22 Nov 2023 12:42:14 +0100 Subject: [PATCH 11/28] Sync iOS project files --- ios/Podfile.lock | 12 ++++++------ pubspec.lock | 34 +++++++++++++++++----------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a9bf442..b9d2152 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,6 +1,6 @@ PODS: - Flutter (1.0.0) - - shared_preferences_foundation (0.0.1): + - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - url_launcher_ios (0.0.1): @@ -8,21 +8,21 @@ PODS: DEPENDENCIES: - Flutter (from `Flutter`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: Flutter: :path: Flutter - shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126 - url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4 + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + url_launcher_ios: bf5ce03e0e2088bad9cc378ea97fa0ed5b49673b PODFILE CHECKSUM: 70d9d25280d0dd177a5f637cdb0f0b0b12c6a189 diff --git a/pubspec.lock b/pubspec.lock index e6dbe84..52626e0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: collection - sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.18.0" convert: dependency: transitive description: @@ -420,10 +420,10 @@ packages: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" mime: dependency: transitive description: @@ -665,18 +665,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" stream_transform: dependency: transitive description: @@ -705,26 +705,26 @@ packages: dependency: transitive description: name: test - sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46" + sha256: a1f7595805820fcc05e5c52e3a231aedd0b72972cb333e8c738a8b1239448b6f url: "https://pub.dev" source: hosted - version: "1.24.3" + version: "1.24.9" test_api: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.6.1" test_core: dependency: transitive description: name: test_core - sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e" + sha256: a757b14fc47507060a162cc2530d9a4a2f92f5100a952c7443b5cad5ef5b106a url: "https://pub.dev" source: hosted - version: "0.5.3" + version: "0.5.9" timing: dependency: transitive description: @@ -857,10 +857,10 @@ packages: dependency: transitive description: name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + sha256: afe077240a270dcfd2aafe77602b4113645af95d0ad31128cc02bce5ac5d5152 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "0.3.0" web_socket_channel: dependency: transitive description: @@ -910,5 +910,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.1.3 <4.0.0" + dart: ">=3.2.0-194.0.dev <4.0.0" flutter: ">=3.13.0" From 8b03d5848eb2321e72ddc964c01bcf333f8d4db6 Mon Sep 17 00:00:00 2001 From: Michael Bisgaard Olesen Date: Fri, 24 Nov 2023 12:13:01 +0100 Subject: [PATCH 12/28] Refactor: Split root widget The current widget 'App' uses two nested `FutureBuilder`s in a wrong way: The provided future should not be constructed inside the build method. As the [documentation](https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html#managing-the-future) puts it: > The future must have been obtained earlier, e.g. during State.initState, State.didUpdateWidget, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted. This behavior has been observed during development as flickering and crashing due to duplicate network activation. With this change the two `FutureBuilder`s now live in their own separate stateful widgets where the futures are constructed by their respective `initState` methods. This change has the furtunate side effect of making the original widget much simpler and easier to understand. --- lib/main.dart | 131 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 38 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 91cdf55..b45311f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -50,53 +50,108 @@ class App extends StatelessWidget { @override Widget build(BuildContext context) { - // Initialize configuration and service repository. + return _WithServiceRepository( + child: _WithSelectedNetwork( + initialNetwork: initialNetwork, + child: MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) { + // Initialize T&C by loading the currently accepted version from shared preferences. + final prefs = context.read().sharedPreferences; + return TermsAndConditionAcceptance(prefs); + }, + ), + ], + child: MaterialApp( + routes: appRoutes, + theme: concordiumTheme(), + ), + ), + ), + ); + } +} + +class _WithServiceRepository extends StatefulWidget { + final Widget child; + + const _WithServiceRepository({super.key, required this.child}); + + @override + State<_WithServiceRepository> createState() => _WithServiceRepositoryState(); +} + +class _WithServiceRepositoryState extends State<_WithServiceRepository> { + Future? _future; + + @override + void initState() { + super.initState(); + setState(() { + _future = bootstrap(); + }); + } + + @override + Widget build(BuildContext context) { return FutureBuilder( - future: bootstrap(), + future: _future, builder: (_, snapshot) { final services = snapshot.data; if (services == null) { // Initializing configuration and service repository. return const _Initializing(); } - // Provide initialized service repository to the nested components - // (including the blocs created in the child provider). - // Then activate the initial network (starting services related to that network). return RepositoryProvider( create: (_) => services, - child: FutureBuilder( - future: services.activateNetwork(initialNetwork), - builder: (_, snapshot) { - final networkServices = snapshot.data; - if (networkServices == null) { - // Initializing network services. - return const _Initializing(); - } - - // Initialize blocs/cubits. - return MultiBlocProvider( - providers: [ - BlocProvider( - create: (_) { - // Initialize selected network as the one that was just activated. - return SelectedNetwork(networkServices); - }, - ), - BlocProvider( - create: (context) { - // Initialize T&C by loading the currently accepted version from shared preferences. - final prefs = context.read().sharedPreferences; - return TermsAndConditionAcceptance(prefs); - }, - ), - ], - child: MaterialApp( - routes: appRoutes, - theme: concordiumTheme(), - ), - ); - }, - ), + child: widget.child, + ); + }, + ); + } +} + +class _WithSelectedNetwork extends StatefulWidget { + final NetworkName initialNetwork; + final Widget child; + + const _WithSelectedNetwork({super.key, required this.initialNetwork, required this.child}); + + @override + State<_WithSelectedNetwork> createState() => _WithSelectedNetworkState(); +} + +class _WithSelectedNetworkState extends State<_WithSelectedNetwork> { + Future? _future; + + @override + void initState() { + super.initState(); + final services = context.read(); + setState(() { + _future = services.activateNetwork(initialNetwork); + }); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _future, + builder: (_, snapshot) { + final networkServices = snapshot.data; + if (networkServices == null) { + // Initializing network services. + return const _Initializing(); + } + + // Initialize blocs/cubits. + return BlocProvider( + create: (_) { + // Initialize selected network as the one that was just activated. + return SelectedNetwork(networkServices); + }, + child: widget.child, ); }, ); From 6fa18ff3630fb0a02d1914d82850e0b1cd02f1d1 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Fri, 24 Nov 2023 12:50:58 +0100 Subject: [PATCH 13/28] fix simple fixes --- .github/workflows/ci.yml | 16 +++++++++++++++- lib/entities/accepted_terms_and_conditions.dart | 4 ++-- lib/providers/storage.dart | 2 +- lib/state/terms_and_conditions.dart | 10 +++++----- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9053297..c013042 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: Analyze, format and test on: # Trigger the workflow on pushes to the main and feature branches as well as PRs targeting them. @@ -32,5 +32,19 @@ jobs: - name: Check format run: dart format --output=none --set-exit-if-changed . -l 150 + test: + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: ${{ env.flutter_version }} + channel: 'stable' + cache: true + - name: Test run: flutter test + diff --git a/lib/entities/accepted_terms_and_conditions.dart b/lib/entities/accepted_terms_and_conditions.dart index 92149c5..6330bc7 100644 --- a/lib/entities/accepted_terms_and_conditions.dart +++ b/lib/entities/accepted_terms_and_conditions.dart @@ -8,9 +8,9 @@ part 'accepted_terms_and_conditions.g.dart'; class AcceptedTermsAndConditions { static const table = "accepted_terms_and_conditions"; - @HiveField(1) + @HiveField(0) final String acceptedVersion; - @HiveField(2) + @HiveField(1) final DateTime acceptedAt; AcceptedTermsAndConditions({required this.acceptedVersion, required this.acceptedAt}); diff --git a/lib/providers/storage.dart b/lib/providers/storage.dart index c5f0e7d..3315bf1 100644 --- a/lib/providers/storage.dart +++ b/lib/providers/storage.dart @@ -13,7 +13,7 @@ class StorageProvider { _registerAdapters(); await _openBoxes(); - return StorageProvider._(await Hive.openBox(AcceptedTermsAndConditions.table)); + return StorageProvider._(Hive.box(AcceptedTermsAndConditions.table)); } /// Register all adapters needed for typed boxes. diff --git a/lib/state/terms_and_conditions.dart b/lib/state/terms_and_conditions.dart index bf4b589..c492fd4 100644 --- a/lib/state/terms_and_conditions.dart +++ b/lib/state/terms_and_conditions.dart @@ -91,14 +91,14 @@ class TermsAndConditionAcceptance extends Cubit change) { super.onChange(change); - if (change.currentState == change.nextState) { - return; + if (change.currentState != change.nextState) { + // TODO: Pass success/failure status to notification service. + _persistAcceptedVersion(change.nextState.accepted); } - // TODO: Pass success/failure status to notification service. - _persistAcceptedVersionIfChanged(change.nextState.accepted); + } - Future _persistAcceptedVersionIfChanged(AcceptedTermsAndConditions? nextAcceptedVersion) { + Future _persistAcceptedVersion(AcceptedTermsAndConditions? nextAcceptedVersion) { if (nextAcceptedVersion == null) { return _storage.deleteTermsAndConditionsAcceptedVersion(networkName); } From 914560432ecc9148267378235fe05b47cc31d369 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Fri, 24 Nov 2023 12:55:07 +0100 Subject: [PATCH 14/28] comments --- .github/workflows/ci.yml | 2 +- lib/entities/accepted_terms_and_conditions.dart | 8 ++++---- lib/entities/accepted_terms_and_conditions.g.dart | 4 ++-- lib/screens/home/screen.dart | 4 ++-- test/providers/storage_test.dart | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c013042..8e6bd30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ env: flutter_version: 3.13.6 jobs: - analyze-format-test: + analyze-format: runs-on: ubuntu-22.04 steps: - name: Checkout repository diff --git a/lib/entities/accepted_terms_and_conditions.dart b/lib/entities/accepted_terms_and_conditions.dart index 6330bc7..99f1c47 100644 --- a/lib/entities/accepted_terms_and_conditions.dart +++ b/lib/entities/accepted_terms_and_conditions.dart @@ -9,18 +9,18 @@ class AcceptedTermsAndConditions { static const table = "accepted_terms_and_conditions"; @HiveField(0) - final String acceptedVersion; + final String version; @HiveField(1) final DateTime acceptedAt; - AcceptedTermsAndConditions({required this.acceptedVersion, required this.acceptedAt}); + AcceptedTermsAndConditions({required this.version, required this.acceptedAt}); factory AcceptedTermsAndConditions.acceptNow(String acceptedVersion) { - return AcceptedTermsAndConditions(acceptedVersion: acceptedVersion, acceptedAt: DateTime.now()); + return AcceptedTermsAndConditions(version: acceptedVersion, acceptedAt: DateTime.now()); } /// Whether the accepted version is valid with respect to the provided valid version. bool isValid(TermsAndConditions tac) { - return acceptedVersion == tac.version; + return version == tac.version; } } diff --git a/lib/entities/accepted_terms_and_conditions.g.dart b/lib/entities/accepted_terms_and_conditions.g.dart index 0a94586..21484a7 100644 --- a/lib/entities/accepted_terms_and_conditions.g.dart +++ b/lib/entities/accepted_terms_and_conditions.g.dart @@ -17,7 +17,7 @@ class AcceptedTermsAndConditionsAdapter extends TypeAdapter { if (acceptedTac == null || !acceptedTac.isValid(validTac.termsAndConditions)) { return TermsAndConditionsScreen( validTermsAndConditions: validTac.termsAndConditions, - acceptedTermsAndConditionsVersion: acceptedTac?.acceptedVersion, + acceptedTermsAndConditionsVersion: acceptedTac?.version, urlLauncher: UrlLauncher(), ); } @@ -75,7 +75,7 @@ class _HomeScreenState extends State { Expanded( child: Column( children: [ - Text('Accepted T&C version: ${tacState.accepted?.acceptedVersion}'), + Text('Accepted T&C version: ${tacState.accepted?.version}'), Text('Valid T&C last refreshed at ${tacState.valid?.refreshedAt}.'), ], ), diff --git a/test/providers/storage_test.dart b/test/providers/storage_test.dart index 98b09cf..1692d2f 100644 --- a/test/providers/storage_test.dart +++ b/test/providers/storage_test.dart @@ -39,7 +39,7 @@ void main() { // Assert final actual = storage.getAcceptedTermsAndConditions(networkName); expect(actual, isNotNull); - expect(actual!.acceptedVersion, expectedVersion); + expect(actual!.version, expectedVersion); }); test("When delete accepted terms and condition from storage, then empty", () async { From 4fb69c49cfb0a4fafb1ccc7628ea5c8cba14f418 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Fri, 24 Nov 2023 12:55:34 +0100 Subject: [PATCH 15/28] Update terms_and_conditions.dart --- lib/state/terms_and_conditions.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/state/terms_and_conditions.dart b/lib/state/terms_and_conditions.dart index c492fd4..5280448 100644 --- a/lib/state/terms_and_conditions.dart +++ b/lib/state/terms_and_conditions.dart @@ -95,7 +95,6 @@ class TermsAndConditionAcceptance extends Cubit _persistAcceptedVersion(AcceptedTermsAndConditions? nextAcceptedVersion) { From 401017e287fecc59bba6e6b4d01eb4fa38551612 Mon Sep 17 00:00:00 2001 From: Michael Bisgaard Olesen Date: Fri, 24 Nov 2023 13:50:06 +0100 Subject: [PATCH 16/28] Declare fields as 'late final' instead of optional MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Søren Schwartz <132270889+schwartz-concordium@users.noreply.github.com> --- lib/main.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index b45311f..91a4c49 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -83,7 +83,7 @@ class _WithServiceRepository extends StatefulWidget { } class _WithServiceRepositoryState extends State<_WithServiceRepository> { - Future? _future; + late final Future _future; @override void initState() { @@ -123,7 +123,7 @@ class _WithSelectedNetwork extends StatefulWidget { } class _WithSelectedNetworkState extends State<_WithSelectedNetwork> { - Future? _future; + late final Future _future; @override void initState() { From b1630c5378f2c819e8aa85536943833ef9447244 Mon Sep 17 00:00:00 2001 From: Michael Bisgaard Olesen Date: Fri, 24 Nov 2023 15:03:47 +0100 Subject: [PATCH 17/28] Address review comments --- lib/main.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 91a4c49..dc68a28 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -76,27 +76,27 @@ class App extends StatelessWidget { class _WithServiceRepository extends StatefulWidget { final Widget child; - const _WithServiceRepository({super.key, required this.child}); + const _WithServiceRepository({required this.child}); @override State<_WithServiceRepository> createState() => _WithServiceRepositoryState(); } class _WithServiceRepositoryState extends State<_WithServiceRepository> { - late final Future _future; + late final Future _bootstrapping; @override void initState() { super.initState(); setState(() { - _future = bootstrap(); + _bootstrapping = bootstrap(); }); } @override Widget build(BuildContext context) { return FutureBuilder( - future: _future, + future: _bootstrapping, builder: (_, snapshot) { final services = snapshot.data; if (services == null) { @@ -116,28 +116,28 @@ class _WithSelectedNetwork extends StatefulWidget { final NetworkName initialNetwork; final Widget child; - const _WithSelectedNetwork({super.key, required this.initialNetwork, required this.child}); + const _WithSelectedNetwork({required this.initialNetwork, required this.child}); @override State<_WithSelectedNetwork> createState() => _WithSelectedNetworkState(); } class _WithSelectedNetworkState extends State<_WithSelectedNetwork> { - late final Future _future; + late final Future _activating; @override void initState() { super.initState(); final services = context.read(); setState(() { - _future = services.activateNetwork(initialNetwork); + _activating = services.activateNetwork(initialNetwork); }); } @override Widget build(BuildContext context) { return FutureBuilder( - future: _future, + future: _activating, builder: (_, snapshot) { final networkServices = snapshot.data; if (networkServices == null) { From 2af34d4a34ec7f142a7d0b1eed039d6092da3916 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Fri, 24 Nov 2023 16:29:50 +0100 Subject: [PATCH 18/28] added terms and condition repository --- .../accepted_terms_and_conditions.dart | 10 ----- lib/providers/storage.dart | 16 +------- .../terms_and_conditions_repository.dart | 39 +++++++++++++++++++ lib/state/terms_and_conditions.dart | 23 +++++++++-- 4 files changed, 60 insertions(+), 28 deletions(-) create mode 100644 lib/repositories/terms_and_conditions_repository.dart diff --git a/lib/entities/accepted_terms_and_conditions.dart b/lib/entities/accepted_terms_and_conditions.dart index 99f1c47..e6a194f 100644 --- a/lib/entities/accepted_terms_and_conditions.dart +++ b/lib/entities/accepted_terms_and_conditions.dart @@ -1,4 +1,3 @@ -import 'package:concordium_wallet/services/wallet_proxy/model.dart'; import 'package:hive_flutter/hive_flutter.dart'; part 'accepted_terms_and_conditions.g.dart'; @@ -14,13 +13,4 @@ class AcceptedTermsAndConditions { final DateTime acceptedAt; AcceptedTermsAndConditions({required this.version, required this.acceptedAt}); - - factory AcceptedTermsAndConditions.acceptNow(String acceptedVersion) { - return AcceptedTermsAndConditions(version: acceptedVersion, acceptedAt: DateTime.now()); - } - - /// Whether the accepted version is valid with respect to the provided valid version. - bool isValid(TermsAndConditions tac) { - return version == tac.version; - } } diff --git a/lib/providers/storage.dart b/lib/providers/storage.dart index 3315bf1..52c3e46 100644 --- a/lib/providers/storage.dart +++ b/lib/providers/storage.dart @@ -1,5 +1,4 @@ import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; -import 'package:concordium_wallet/state/network.dart'; import 'package:hive_flutter/hive_flutter.dart'; /// Service for interacting with [Hive]. @@ -27,18 +26,5 @@ class StorageProvider { await Future.wait([atcFuture]); } - /// Reads the currently accepted T&C version. - AcceptedTermsAndConditions? getAcceptedTermsAndConditions(NetworkName networkName) { - return _acceptedTermsAndConditionBox.get(networkName.name); - } - - /// Writes the currently accepted T&C version. - Future writeAcceptedTermsAndConditions(NetworkName networkName, AcceptedTermsAndConditions acceptedTermsAndConditions) { - return _acceptedTermsAndConditionBox.put(networkName.name, acceptedTermsAndConditions); - } - - /// Deletes the currently accepted T&C version. - Future deleteTermsAndConditionsAcceptedVersion(NetworkName networkName) { - return _acceptedTermsAndConditionBox.delete(networkName.name); - } + Box get acceptedTermsAndConditionBox => _acceptedTermsAndConditionBox; } diff --git a/lib/repositories/terms_and_conditions_repository.dart b/lib/repositories/terms_and_conditions_repository.dart new file mode 100644 index 0000000..b5e0723 --- /dev/null +++ b/lib/repositories/terms_and_conditions_repository.dart @@ -0,0 +1,39 @@ +import 'package:concordium_wallet/providers/storage.dart'; +import 'package:concordium_wallet/state/terms_and_conditions.dart' as states; +import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart' as models; + +class TermsAndConditionsRepository { + static const String key = "accepted_terms_and_condition"; + + final StorageProvider _storageProvider; + + const TermsAndConditionsRepository({required StorageProvider storageProvider}) + : _storageProvider = storageProvider; + + /// Reads the currently accepted T&C version. + states.AcceptedTermsAndConditions? getAcceptedTermsAndConditions() { + var model = _storageProvider.acceptedTermsAndConditionBox.get(key); + return toState(model); + } + + /// Writes the currently accepted T&C version. + Future writeAcceptedTermsAndConditions(states.AcceptedTermsAndConditions acceptedTermsAndConditions) { + return _storageProvider.acceptedTermsAndConditionBox.put(key, fromState(acceptedTermsAndConditions)); + } + + /// Deletes the currently accepted T&C version. + Future deleteTermsAndConditionsAcceptedVersion() { + return _storageProvider.acceptedTermsAndConditionBox.delete(key); + } + + models.AcceptedTermsAndConditions fromState(states.AcceptedTermsAndConditions state) { + return models.AcceptedTermsAndConditions(version: state.version, acceptedAt: state.acceptedAt); + } + + states.AcceptedTermsAndConditions? toState(models.AcceptedTermsAndConditions? model) { + if (model == null) { + return null; + } + return states.AcceptedTermsAndConditions(version: model.version, acceptedAt: model.acceptedAt); + } +} diff --git a/lib/state/terms_and_conditions.dart b/lib/state/terms_and_conditions.dart index 5280448..30d3e54 100644 --- a/lib/state/terms_and_conditions.dart +++ b/lib/state/terms_and_conditions.dart @@ -1,9 +1,26 @@ -import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; -import 'package:concordium_wallet/providers/storage.dart'; +import 'package:concordium_wallet/repositories/terms_and_conditions_repository.dart'; import 'package:concordium_wallet/services/wallet_proxy/model.dart'; import 'package:concordium_wallet/state/network.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +/// Version of the Terms & Conditions accepted by the user. +class AcceptedTermsAndConditions { + /// Accepted version. + final String version; + final DateTime acceptedAt; + + const AcceptedTermsAndConditions({required this.version, required this.acceptedAt}); + + factory AcceptedTermsAndConditions.acceptNow(String acceptedVersion) { + return AcceptedTermsAndConditions(version: acceptedVersion, acceptedAt: DateTime.now()); + } + + /// Whether the accepted version is valid with respect to the provided valid version. + bool isValid(TermsAndConditions tac) { + return version == tac.version; + } +} + /// Version of the Terms & Conditions that is considered valid. /// /// The user has to have accepted this version (or more generally, a compatible version) @@ -38,7 +55,7 @@ class TermsAndConditionsAcceptanceState { /// State component of the currently accepted and valid Terms & Conditions. class TermsAndConditionAcceptance extends Cubit { /// Service used to persist the accepted T&C version. - final StorageProvider _storage; + final TermsAndConditionsRepository _storage; final NetworkName networkName; TermsAndConditionAcceptance(this._storage, this.networkName) : super(const TermsAndConditionsAcceptanceState(accepted: null, valid: null)) { From 7b511df7938a59ea28cd1bf47472691c58b7bd74 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:39:25 +0100 Subject: [PATCH 19/28] change to repository pattern --- lib/main.dart | 7 +-- .../terms_and_conditions_repository.dart | 20 ++++---- lib/screens/home/screen.dart | 3 +- lib/screens/terms_and_conditions/screen.dart | 3 +- lib/state/terms_and_conditions.dart | 28 ++++++----- test/providers/storage_test.dart | 46 +++++++++---------- test/terms_and_conditions_test.dart | 5 +- 7 files changed, 53 insertions(+), 59 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 5667d3d..3d4c956 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:concordium_wallet/repositories/terms_and_conditions_repository.dart'; import 'package:concordium_wallet/screens/routes.dart'; import 'package:concordium_wallet/services/http.dart'; import 'package:concordium_wallet/providers/storage.dart'; @@ -83,9 +84,9 @@ class App extends StatelessWidget { ), BlocProvider( create: (context) { - // Initialize T&C by loading the currently accepted version from shared preferences. - final prefs = context.read().storage; - return TermsAndConditionAcceptance(prefs, networkServices.network.name); + // Initialize T&C by loading the currently accepted version. + final storage = context.read().storage; + return TermsAndConditionAcceptance(TermsAndConditionsRepository(storageProvider: storage)); }, ), ], diff --git a/lib/repositories/terms_and_conditions_repository.dart b/lib/repositories/terms_and_conditions_repository.dart index b5e0723..318ab03 100644 --- a/lib/repositories/terms_and_conditions_repository.dart +++ b/lib/repositories/terms_and_conditions_repository.dart @@ -1,6 +1,6 @@ import 'package:concordium_wallet/providers/storage.dart'; -import 'package:concordium_wallet/state/terms_and_conditions.dart' as states; -import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart' as models; +import 'package:concordium_wallet/state/terms_and_conditions.dart'; +import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; class TermsAndConditionsRepository { static const String key = "accepted_terms_and_condition"; @@ -11,14 +11,14 @@ class TermsAndConditionsRepository { : _storageProvider = storageProvider; /// Reads the currently accepted T&C version. - states.AcceptedTermsAndConditions? getAcceptedTermsAndConditions() { + AcceptedTermsAndConditionsState? getAcceptedTermsAndConditions() { var model = _storageProvider.acceptedTermsAndConditionBox.get(key); - return toState(model); + return _toState(model); } /// Writes the currently accepted T&C version. - Future writeAcceptedTermsAndConditions(states.AcceptedTermsAndConditions acceptedTermsAndConditions) { - return _storageProvider.acceptedTermsAndConditionBox.put(key, fromState(acceptedTermsAndConditions)); + Future writeAcceptedTermsAndConditions(AcceptedTermsAndConditionsState acceptedTermsAndConditions) { + return _storageProvider.acceptedTermsAndConditionBox.put(key, _fromState(acceptedTermsAndConditions)); } /// Deletes the currently accepted T&C version. @@ -26,14 +26,14 @@ class TermsAndConditionsRepository { return _storageProvider.acceptedTermsAndConditionBox.delete(key); } - models.AcceptedTermsAndConditions fromState(states.AcceptedTermsAndConditions state) { - return models.AcceptedTermsAndConditions(version: state.version, acceptedAt: state.acceptedAt); + AcceptedTermsAndConditions _fromState(AcceptedTermsAndConditionsState state) { + return AcceptedTermsAndConditions(version: state.version, acceptedAt: state.acceptedAt); } - states.AcceptedTermsAndConditions? toState(models.AcceptedTermsAndConditions? model) { + AcceptedTermsAndConditionsState? _toState(AcceptedTermsAndConditions? model) { if (model == null) { return null; } - return states.AcceptedTermsAndConditions(version: model.version, acceptedAt: model.acceptedAt); + return AcceptedTermsAndConditionsState(version: model.version, acceptedAt: model.acceptedAt); } } diff --git a/lib/screens/home/screen.dart b/lib/screens/home/screen.dart index 241cc4e..5da0b89 100644 --- a/lib/screens/home/screen.dart +++ b/lib/screens/home/screen.dart @@ -1,4 +1,3 @@ -import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; import 'package:concordium_wallet/screens/terms_and_conditions/screen.dart'; import 'package:concordium_wallet/services/url_launcher.dart'; import 'package:concordium_wallet/services/wallet_proxy/service.dart'; @@ -93,7 +92,7 @@ class _HomeScreenState extends State { const SizedBox(height: 8), ElevatedButton( onPressed: () { - final tac = AcceptedTermsAndConditions.acceptNow('1.2.3'); + final tac = AcceptedTermsAndConditionsState.acceptNow('1.2.3'); context.read().userAccepted(tac); }, child: const Text('Set accepted T&C version to 1.2.3'), diff --git a/lib/screens/terms_and_conditions/screen.dart b/lib/screens/terms_and_conditions/screen.dart index 6812a94..0b034dc 100644 --- a/lib/screens/terms_and_conditions/screen.dart +++ b/lib/screens/terms_and_conditions/screen.dart @@ -1,4 +1,3 @@ -import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; import 'package:concordium_wallet/screens/terms_and_conditions/widget.dart'; import 'package:concordium_wallet/services/url_launcher.dart'; import 'package:concordium_wallet/services/wallet_proxy/model.dart'; @@ -125,7 +124,7 @@ class _TermsAndConditionsScreenState extends State { Function()? _onAcceptButtonPressed(BuildContext context) { if (isAccepted) { return () { - final tac = AcceptedTermsAndConditions.acceptNow(widget.validTermsAndConditions.version); + final tac = AcceptedTermsAndConditionsState.acceptNow(widget.validTermsAndConditions.version); context.read().userAccepted(tac); }; } diff --git a/lib/state/terms_and_conditions.dart b/lib/state/terms_and_conditions.dart index 30d3e54..81b41ca 100644 --- a/lib/state/terms_and_conditions.dart +++ b/lib/state/terms_and_conditions.dart @@ -1,18 +1,17 @@ import 'package:concordium_wallet/repositories/terms_and_conditions_repository.dart'; import 'package:concordium_wallet/services/wallet_proxy/model.dart'; -import 'package:concordium_wallet/state/network.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; /// Version of the Terms & Conditions accepted by the user. -class AcceptedTermsAndConditions { +class AcceptedTermsAndConditionsState { /// Accepted version. final String version; final DateTime acceptedAt; - const AcceptedTermsAndConditions({required this.version, required this.acceptedAt}); + const AcceptedTermsAndConditionsState({required this.version, required this.acceptedAt}); - factory AcceptedTermsAndConditions.acceptNow(String acceptedVersion) { - return AcceptedTermsAndConditions(version: acceptedVersion, acceptedAt: DateTime.now()); + factory AcceptedTermsAndConditionsState.acceptNow(String acceptedVersion) { + return AcceptedTermsAndConditionsState(version: acceptedVersion, acceptedAt: DateTime.now()); } /// Whether the accepted version is valid with respect to the provided valid version. @@ -43,8 +42,8 @@ class ValidTermsAndConditions { class TermsAndConditionsAcceptanceState { /// Currently accepted T&C. /// - /// The accepted version is persisted into shared preferences. - final AcceptedTermsAndConditions? accepted; + /// The accepted version persisted. + final AcceptedTermsAndConditionsState? accepted; /// Currently valid T&C. final ValidTermsAndConditions? valid; @@ -55,11 +54,10 @@ class TermsAndConditionsAcceptanceState { /// State component of the currently accepted and valid Terms & Conditions. class TermsAndConditionAcceptance extends Cubit { /// Service used to persist the accepted T&C version. - final TermsAndConditionsRepository _storage; - final NetworkName networkName; + final TermsAndConditionsRepository _termsAndConditionRepo; - TermsAndConditionAcceptance(this._storage, this.networkName) : super(const TermsAndConditionsAcceptanceState(accepted: null, valid: null)) { - final acceptedVersion = _storage.getAcceptedTermsAndConditions(networkName); + TermsAndConditionAcceptance(this._termsAndConditionRepo) : super(const TermsAndConditionsAcceptanceState(accepted: null, valid: null)) { + final acceptedVersion = _termsAndConditionRepo.getAcceptedTermsAndConditions(); if (acceptedVersion != null) { userAccepted(acceptedVersion); } @@ -68,7 +66,7 @@ class TermsAndConditionAcceptance extends Cubit _persistAcceptedVersion(AcceptedTermsAndConditions? nextAcceptedVersion) { + Future _persistAcceptedVersion(AcceptedTermsAndConditionsState? nextAcceptedVersion) { if (nextAcceptedVersion == null) { - return _storage.deleteTermsAndConditionsAcceptedVersion(networkName); + return _termsAndConditionRepo.deleteTermsAndConditionsAcceptedVersion(); } - return _storage.writeAcceptedTermsAndConditions(networkName, nextAcceptedVersion); + return _termsAndConditionRepo.writeAcceptedTermsAndConditions(nextAcceptedVersion); } } diff --git a/test/providers/storage_test.dart b/test/providers/storage_test.dart index 1692d2f..aef3870 100644 --- a/test/providers/storage_test.dart +++ b/test/providers/storage_test.dart @@ -1,6 +1,7 @@ import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; import 'package:concordium_wallet/providers/storage.dart'; -import 'package:concordium_wallet/state/network.dart'; +import 'package:concordium_wallet/repositories/terms_and_conditions_repository.dart'; +import 'package:concordium_wallet/state/terms_and_conditions.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; @@ -15,11 +16,12 @@ void main() { }); } - late StorageProvider storage; + late TermsAndConditionsRepository repository; setUpAll(() async { mockPathProvider(); - storage = await StorageProvider.init(); + final storage = await StorageProvider.init(); + repository = TermsAndConditionsRepository(storageProvider: storage); }); tearDownAll(() => Hive.deleteFromDisk()); @@ -28,53 +30,49 @@ void main() { test('When add accepted terms and condition to storage, then saved', () async { // Arrange - const name = "foobar"; const expectedVersion = "0.0.42"; - const networkName = NetworkName(name); - final accepted = AcceptedTermsAndConditions.acceptNow(expectedVersion); + final accepted = AcceptedTermsAndConditionsState.acceptNow(expectedVersion); // Act - await storage.writeAcceptedTermsAndConditions(networkName, accepted); + await repository.writeAcceptedTermsAndConditions(accepted); // Assert - final actual = storage.getAcceptedTermsAndConditions(networkName); + final actual = repository.getAcceptedTermsAndConditions(); expect(actual, isNotNull); expect(actual!.version, expectedVersion); }); test("When delete accepted terms and condition from storage, then empty", () async { // Arrange - const name = "foobar"; const expectedVersion = "0.0.42"; - const networkName = NetworkName(name); - final accepted = AcceptedTermsAndConditions.acceptNow(expectedVersion); - await storage.writeAcceptedTermsAndConditions(networkName, accepted); - expect(Hive.box(AcceptedTermsAndConditions.table).get(networkName.name), isNotNull); + final accepted = AcceptedTermsAndConditionsState.acceptNow(expectedVersion); + await repository.writeAcceptedTermsAndConditions(accepted); + expect(Hive.box(AcceptedTermsAndConditions.table).get(TermsAndConditionsRepository.key), isNotNull); // Act - await storage.deleteTermsAndConditionsAcceptedVersion(networkName); + await repository.deleteTermsAndConditionsAcceptedVersion(); // Assert - final actual = storage.getAcceptedTermsAndConditions(networkName); + final actual = repository.getAcceptedTermsAndConditions(); expect(actual, null); }); test("When update accepted terms and conditions, then version updated", () async { // Arrange - const name = "foobar"; const oldVersion = "0.0.42"; const newVersion = "0.0.84"; - const networkName = NetworkName(name); - final oldAccepted = AcceptedTermsAndConditions.acceptNow(oldVersion); - final newAccepted = AcceptedTermsAndConditions.acceptNow(newVersion); - await storage.writeAcceptedTermsAndConditions(networkName, oldAccepted); - expect(Hive.box(AcceptedTermsAndConditions.table).get(networkName.name), isNotNull); + final oldAccepted = AcceptedTermsAndConditionsState.acceptNow(oldVersion); + final newAccepted = AcceptedTermsAndConditionsState.acceptNow(newVersion); + await repository.writeAcceptedTermsAndConditions(oldAccepted); + expect(Hive.box(AcceptedTermsAndConditions.table).get(TermsAndConditionsRepository.key), isNotNull); // Act - await storage.writeAcceptedTermsAndConditions(networkName, newAccepted); + await repository.writeAcceptedTermsAndConditions(newAccepted); // Assert - final actual = storage.getAcceptedTermsAndConditions(networkName); - expect(actual, newAccepted); + final actual = repository.getAcceptedTermsAndConditions(); + expect(actual, isNotNull); + expect(actual!.acceptedAt, newAccepted.acceptedAt); + expect(actual.version, newAccepted.version); }); } diff --git a/test/terms_and_conditions_test.dart b/test/terms_and_conditions_test.dart index e92d958..fc77b7e 100644 --- a/test/terms_and_conditions_test.dart +++ b/test/terms_and_conditions_test.dart @@ -1,5 +1,4 @@ import 'package:bloc_test/bloc_test.dart'; -import 'package:concordium_wallet/entities/accepted_terms_and_conditions.dart'; import 'package:concordium_wallet/services/url_launcher.dart'; import 'package:concordium_wallet/state/terms_and_conditions.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -26,7 +25,7 @@ void main() { const String acceptedVersion = "1.0.0"; setUpAll(() { - registerFallbackValue(AcceptedTermsAndConditions.acceptNow(validVersion)); + registerFallbackValue(AcceptedTermsAndConditionsState.acceptNow(validVersion)); }); setUp(() { @@ -34,7 +33,7 @@ void main() { final terms = TermsAndConditions(Uri.parse("localhost"), validVersion); state = TermsAndConditionsAcceptanceState( - accepted: AcceptedTermsAndConditions.acceptNow(acceptedVersion), valid: ValidTermsAndConditions.refreshedNow(termsAndConditions: terms)); + accepted: AcceptedTermsAndConditionsState.acceptNow(acceptedVersion), valid: ValidTermsAndConditions.refreshedNow(termsAndConditions: terms)); // Build the terms and condition screen we wish to test final rawTacScreen = TermsAndConditionsScreen(validTermsAndConditions: terms, urlLauncher: MockUrlLauncher()); From 1bc4804b126c823b925ae800bb9a2b631f2c63c0 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:51:25 +0100 Subject: [PATCH 20/28] format --- lib/repositories/terms_and_conditions_repository.dart | 3 +-- test/terms_and_conditions_test.dart | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/repositories/terms_and_conditions_repository.dart b/lib/repositories/terms_and_conditions_repository.dart index 318ab03..622087c 100644 --- a/lib/repositories/terms_and_conditions_repository.dart +++ b/lib/repositories/terms_and_conditions_repository.dart @@ -7,8 +7,7 @@ class TermsAndConditionsRepository { final StorageProvider _storageProvider; - const TermsAndConditionsRepository({required StorageProvider storageProvider}) - : _storageProvider = storageProvider; + const TermsAndConditionsRepository({required StorageProvider storageProvider}) : _storageProvider = storageProvider; /// Reads the currently accepted T&C version. AcceptedTermsAndConditionsState? getAcceptedTermsAndConditions() { diff --git a/test/terms_and_conditions_test.dart b/test/terms_and_conditions_test.dart index fc77b7e..b3eb77f 100644 --- a/test/terms_and_conditions_test.dart +++ b/test/terms_and_conditions_test.dart @@ -33,7 +33,8 @@ void main() { final terms = TermsAndConditions(Uri.parse("localhost"), validVersion); state = TermsAndConditionsAcceptanceState( - accepted: AcceptedTermsAndConditionsState.acceptNow(acceptedVersion), valid: ValidTermsAndConditions.refreshedNow(termsAndConditions: terms)); + accepted: AcceptedTermsAndConditionsState.acceptNow(acceptedVersion), + valid: ValidTermsAndConditions.refreshedNow(termsAndConditions: terms)); // Build the terms and condition screen we wish to test final rawTacScreen = TermsAndConditionsScreen(validTermsAndConditions: terms, urlLauncher: MockUrlLauncher()); From 0b29e8228f53bb118de7dc6438e7c828a2af3e13 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Sat, 25 Nov 2023 20:47:34 +0100 Subject: [PATCH 21/28] refactor to lazy box --- lib/main.dart | 60 ++++++++++++++----- lib/providers/storage.dart | 30 ++++++++-- .../terms_and_conditions_repository.dart | 4 +- lib/state/terms_and_conditions.dart | 4 +- ...terms_and_conditions_repository_test.dart} | 12 ++-- 5 files changed, 81 insertions(+), 29 deletions(-) rename test/{providers/storage_test.dart => repositories/terms_and_conditions_repository_test.dart} (81%) diff --git a/lib/main.dart b/lib/main.dart index a9489e6..984f9d2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,4 @@ +import 'package:concordium_wallet/repositories/terms_and_conditions_repository.dart'; import 'package:concordium_wallet/screens/routes.dart'; import 'package:concordium_wallet/services/http.dart'; import 'package:concordium_wallet/providers/storage.dart'; @@ -52,21 +53,11 @@ class App extends StatelessWidget { return _WithServiceRepository( child: _WithSelectedNetwork( initialNetwork: initialNetwork, - child: MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) { - // Initialize T&C by loading the currently accepted version from shared preferences. - final prefs = context.read().sharedPreferences; - return TermsAndConditionAcceptance(prefs); - }, - ), - ], - child: MaterialApp( - routes: appRoutes, - theme: concordiumTheme(), - ), - ), + child: _WithTermsAndConditionAcceptance( + child: MaterialApp( + routes: appRoutes, + theme: concordiumTheme(), + )), ), ); } @@ -157,6 +148,45 @@ class _WithSelectedNetworkState extends State<_WithSelectedNetwork> { } } +class _WithTermsAndConditionAcceptance extends StatefulWidget { + final Widget child; + + const _WithTermsAndConditionAcceptance({required this.child}); + + @override + State<_WithTermsAndConditionAcceptance> createState() => _WithTermsAndConditionAcceptanceState(); +} + +class _WithTermsAndConditionAcceptanceState extends State<_WithTermsAndConditionAcceptance> { + late final Future _lastAccepted; + late final TermsAndConditionsRepository _repository; + + @override + void initState() { + super.initState(); + final storage = context.read().storage; + setState(() { + _repository = TermsAndConditionsRepository(storageProvider: storage); + _lastAccepted = _repository.getAcceptedTermsAndConditions(); + }); + } + + @override + Widget build(BuildContext context) { + return FutureBuilder( + future: _lastAccepted, + builder: (_, snapshot) { + // Check on the connection state since last accepted possible null + if (snapshot.connectionState != ConnectionState.done) { + return const _Initializing(); + } + return BlocProvider(create: (_) { + return TermsAndConditionAcceptance(_repository, snapshot.data); + }); + }); + } +} + class _Initializing extends StatelessWidget { const _Initializing(); diff --git a/lib/providers/storage.dart b/lib/providers/storage.dart index 52c3e46..1b14bc9 100644 --- a/lib/providers/storage.dart +++ b/lib/providers/storage.dart @@ -3,7 +3,7 @@ import 'package:hive_flutter/hive_flutter.dart'; /// Service for interacting with [Hive]. class StorageProvider { - final Box _acceptedTermsAndConditionBox; + final LazyBox _acceptedTermsAndConditionBox; const StorageProvider._(this._acceptedTermsAndConditionBox); @@ -12,19 +12,41 @@ class StorageProvider { _registerAdapters(); await _openBoxes(); - return StorageProvider._(Hive.box(AcceptedTermsAndConditions.table)); + return StorageProvider._(Hive.lazyBox(AcceptedTermsAndConditions.table)); } /// Register all adapters needed for typed boxes. static void _registerAdapters() { Hive.registerAdapter(AcceptedTermsAndConditionsAdapter()); + Hive.registerAdapter(PerciseDateTimeAdapter(), override: true, internal: true); } /// Opens all boxes asynchronously. static Future _openBoxes() async { - final atcFuture = Hive.openBox(AcceptedTermsAndConditions.table); + final atcFuture = Hive.openLazyBox(AcceptedTermsAndConditions.table); await Future.wait([atcFuture]); } - Box get acceptedTermsAndConditionBox => _acceptedTermsAndConditionBox; + LazyBox get acceptedTermsAndConditionBox => _acceptedTermsAndConditionBox; +} + +/// A bit modified DateTimeWithTimezoneAdapter (https://github.com/hivedb/hive/blob/master/hive/lib/src/adapters/date_time_adapter.dart#L25-L42) +/// This adapter is relevant because by default, [Hive] only stores datetimes down to millisecond precision. +/// It's derived from issue in link and proposed as a solution (https://github.com/isar/hive/issues/474#issuecomment-730562545). +class PerciseDateTimeAdapter extends TypeAdapter { + @override + final typeId = 18; + + @override + DateTime read(BinaryReader reader) { + var micros = reader.readInt(); + var isUtc = reader.readBool(); + return DateTime.fromMicrosecondsSinceEpoch(micros, isUtc: isUtc); + } + + @override + void write(BinaryWriter writer, DateTime obj) { + writer.writeInt(obj.microsecondsSinceEpoch); + writer.writeBool(obj.isUtc); + } } diff --git a/lib/repositories/terms_and_conditions_repository.dart b/lib/repositories/terms_and_conditions_repository.dart index 622087c..0a78b08 100644 --- a/lib/repositories/terms_and_conditions_repository.dart +++ b/lib/repositories/terms_and_conditions_repository.dart @@ -10,8 +10,8 @@ class TermsAndConditionsRepository { const TermsAndConditionsRepository({required StorageProvider storageProvider}) : _storageProvider = storageProvider; /// Reads the currently accepted T&C version. - AcceptedTermsAndConditionsState? getAcceptedTermsAndConditions() { - var model = _storageProvider.acceptedTermsAndConditionBox.get(key); + Future getAcceptedTermsAndConditions() async { + var model = await _storageProvider.acceptedTermsAndConditionBox.get(key); return _toState(model); } diff --git a/lib/state/terms_and_conditions.dart b/lib/state/terms_and_conditions.dart index 81b41ca..bd303a7 100644 --- a/lib/state/terms_and_conditions.dart +++ b/lib/state/terms_and_conditions.dart @@ -56,8 +56,8 @@ class TermsAndConditionAcceptance extends Cubit Hive.deleteFromDisk()); - tearDown(() => Hive.box(AcceptedTermsAndConditions.table).clear()); + tearDown(() => Hive.lazyBox(AcceptedTermsAndConditions.table).clear()); test('When add accepted terms and condition to storage, then saved', () async { // Arrange @@ -37,7 +37,7 @@ void main() { await repository.writeAcceptedTermsAndConditions(accepted); // Assert - final actual = repository.getAcceptedTermsAndConditions(); + final actual = await repository.getAcceptedTermsAndConditions(); expect(actual, isNotNull); expect(actual!.version, expectedVersion); }); @@ -47,13 +47,13 @@ void main() { const expectedVersion = "0.0.42"; final accepted = AcceptedTermsAndConditionsState.acceptNow(expectedVersion); await repository.writeAcceptedTermsAndConditions(accepted); - expect(Hive.box(AcceptedTermsAndConditions.table).get(TermsAndConditionsRepository.key), isNotNull); + expect(await Hive.lazyBox(AcceptedTermsAndConditions.table).get(TermsAndConditionsRepository.key), isNotNull); // Act await repository.deleteTermsAndConditionsAcceptedVersion(); // Assert - final actual = repository.getAcceptedTermsAndConditions(); + final actual = await repository.getAcceptedTermsAndConditions(); expect(actual, null); }); @@ -64,13 +64,13 @@ void main() { final oldAccepted = AcceptedTermsAndConditionsState.acceptNow(oldVersion); final newAccepted = AcceptedTermsAndConditionsState.acceptNow(newVersion); await repository.writeAcceptedTermsAndConditions(oldAccepted); - expect(Hive.box(AcceptedTermsAndConditions.table).get(TermsAndConditionsRepository.key), isNotNull); + expect(await Hive.lazyBox(AcceptedTermsAndConditions.table).get(TermsAndConditionsRepository.key), isNotNull); // Act await repository.writeAcceptedTermsAndConditions(newAccepted); // Assert - final actual = repository.getAcceptedTermsAndConditions(); + final actual = await repository.getAcceptedTermsAndConditions(); expect(actual, isNotNull); expect(actual!.acceptedAt, newAccepted.acceptedAt); expect(actual.version, newAccepted.version); From f4ffbb2804562e9153bf376d4082ea218dd33057 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Sat, 25 Nov 2023 20:48:51 +0100 Subject: [PATCH 22/28] format --- lib/providers/storage.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/providers/storage.dart b/lib/providers/storage.dart index 1b14bc9..7a7674c 100644 --- a/lib/providers/storage.dart +++ b/lib/providers/storage.dart @@ -31,7 +31,7 @@ class StorageProvider { } /// A bit modified DateTimeWithTimezoneAdapter (https://github.com/hivedb/hive/blob/master/hive/lib/src/adapters/date_time_adapter.dart#L25-L42) -/// This adapter is relevant because by default, [Hive] only stores datetimes down to millisecond precision. +/// This adapter is relevant because by default, [Hive] only stores datetimes down to millisecond precision. /// It's derived from issue in link and proposed as a solution (https://github.com/isar/hive/issues/474#issuecomment-730562545). class PerciseDateTimeAdapter extends TypeAdapter { @override From 77c63f497c20f9bf95f9af511b02bc2a42c10906 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Sat, 25 Nov 2023 20:50:52 +0100 Subject: [PATCH 23/28] fix missing child and format --- lib/main.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 984f9d2..32f3938 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -180,9 +180,11 @@ class _WithTermsAndConditionAcceptanceState extends State<_WithTermsAndCondition if (snapshot.connectionState != ConnectionState.done) { return const _Initializing(); } - return BlocProvider(create: (_) { - return TermsAndConditionAcceptance(_repository, snapshot.data); - }); + return BlocProvider( + create: (_) { + return TermsAndConditionAcceptance(_repository, snapshot.data); + }, + child: widget.child); }); } } From 5d98c697dcb79b43c69a0613cbf1b9af8edfffd0 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:06:17 +0100 Subject: [PATCH 24/28] updated packages and changed type id --- lib/providers/storage.dart | 2 +- pubspec.lock | 48 +++++++++++++++++++------------------- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/lib/providers/storage.dart b/lib/providers/storage.dart index 7a7674c..dd3f7a0 100644 --- a/lib/providers/storage.dart +++ b/lib/providers/storage.dart @@ -35,7 +35,7 @@ class StorageProvider { /// It's derived from issue in link and proposed as a solution (https://github.com/isar/hive/issues/474#issuecomment-730562545). class PerciseDateTimeAdapter extends TypeAdapter { @override - final typeId = 18; + final typeId = 0; @override DateTime read(BinaryReader reader) { diff --git a/pubspec.lock b/pubspec.lock index 52626e0..6b397d4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 url: "https://pub.dev" source: hosted - version: "61.0.0" + version: "64.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" url: "https://pub.dev" source: hosted - version: "5.13.0" + version: "6.2.0" args: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: built_value - sha256: "723b4021e903217dfc445ec4cf5b42e27975aece1fc4ebbc1ca6329c2d9fb54e" + sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2" url: "https://pub.dev" source: hosted - version: "8.7.0" + version: "8.8.0" characters: dependency: transitive description: @@ -149,10 +149,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "1be9be30396d7e4c0db42c35ea6ccd7cc6a1e19916b5dc64d6ac216b5544d677" + sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.8.0" collection: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: coverage - sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb" + sha256: ac86d3abab0f165e4b8f561280ff4e066bceaac83c424dd19f1ae2c2fcd12ca9 url: "https://pub.dev" source: hosted - version: "1.6.4" + version: "1.7.1" crypto: dependency: transitive description: @@ -197,10 +197,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.4" diff_match_patch: dependency: transitive description: @@ -332,10 +332,10 @@ packages: dependency: "direct main" description: name: http - sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" http_multi_server: dependency: transitive description: @@ -532,10 +532,10 @@ packages: dependency: transitive description: name: petitparser - sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + sha256: eeb2d1428ee7f4170e2bd498827296a18d4e7fc462b71727d111c0ac7707cfa6 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "6.0.1" platform: dependency: transitive description: @@ -793,10 +793,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "7fd2f55fe86cea2897b963e864dc01a7eb0719ecc65fcef4c1cc3d686d718bb2" + sha256: "138bd45b3a456dcfafc46d1a146787424f8d2edfbf2809c9324361e58f851cf7" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" url_launcher_windows: dependency: transitive description: @@ -841,10 +841,10 @@ packages: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "11.10.0" + version: "13.0.0" watcher: dependency: transitive description: @@ -897,10 +897,10 @@ packages: dependency: transitive description: name: xml - sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" + sha256: af5e77e9b83f2f4adc5d3f0a4ece1c7f45a2467b695c2540381bac793e34e556 url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.4.2" yaml: dependency: transitive description: @@ -910,5 +910,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.2.0-194.0.dev <4.0.0" - flutter: ">=3.13.0" + dart: ">=3.2.0 <4.0.0" + flutter: ">=3.16.0" From f7a43d2b34989c7c3b83f0aa9a78bb22cec9c0c6 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:41:21 +0100 Subject: [PATCH 25/28] updated with docs --- lib/providers/storage.dart | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/providers/storage.dart b/lib/providers/storage.dart index dd3f7a0..f6c9648 100644 --- a/lib/providers/storage.dart +++ b/lib/providers/storage.dart @@ -18,7 +18,7 @@ class StorageProvider { /// Register all adapters needed for typed boxes. static void _registerAdapters() { Hive.registerAdapter(AcceptedTermsAndConditionsAdapter()); - Hive.registerAdapter(PerciseDateTimeAdapter(), override: true, internal: true); + Hive.registerAdapter(PreciseDateTimeAdapter(), override: true, internal: true); } /// Opens all boxes asynchronously. @@ -30,12 +30,13 @@ class StorageProvider { LazyBox get acceptedTermsAndConditionBox => _acceptedTermsAndConditionBox; } -/// A bit modified DateTimeWithTimezoneAdapter (https://github.com/hivedb/hive/blob/master/hive/lib/src/adapters/date_time_adapter.dart#L25-L42) +/// A bit modified DateTimeWithTimezoneAdapter (https://github.com/isar/hive/blob/470473ffc1ba39f6c90f31ababe0ee63b76b69fe/hive/lib/src/adapters/date_time_adapter.dart#L25) /// This adapter is relevant because by default, [Hive] only stores datetimes down to millisecond precision. /// It's derived from issue in link and proposed as a solution (https://github.com/isar/hive/issues/474#issuecomment-730562545). -class PerciseDateTimeAdapter extends TypeAdapter { +/// The type ID needs to be 18 as it's required to overwrite the existing one registered. (https://github.com/isar/hive/blob/470473ffc1ba39f6c90f31ababe0ee63b76b69fe/hive/lib/src/adapters/date_time_adapter.dart#L28). +class PreciseDateTimeAdapter extends TypeAdapter { @override - final typeId = 0; + final typeId = 18; @override DateTime read(BinaryReader reader) { From ab69c281ce9df3d1138a5f3b91dfc807299dcfe9 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Mon, 27 Nov 2023 19:41:34 +0100 Subject: [PATCH 26/28] format --- lib/providers/storage.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/providers/storage.dart b/lib/providers/storage.dart index f6c9648..36cac0b 100644 --- a/lib/providers/storage.dart +++ b/lib/providers/storage.dart @@ -33,7 +33,7 @@ class StorageProvider { /// A bit modified DateTimeWithTimezoneAdapter (https://github.com/isar/hive/blob/470473ffc1ba39f6c90f31ababe0ee63b76b69fe/hive/lib/src/adapters/date_time_adapter.dart#L25) /// This adapter is relevant because by default, [Hive] only stores datetimes down to millisecond precision. /// It's derived from issue in link and proposed as a solution (https://github.com/isar/hive/issues/474#issuecomment-730562545). -/// The type ID needs to be 18 as it's required to overwrite the existing one registered. (https://github.com/isar/hive/blob/470473ffc1ba39f6c90f31ababe0ee63b76b69fe/hive/lib/src/adapters/date_time_adapter.dart#L28). +/// The type ID needs to be 18 as it's required to overwrite the existing one registered. (https://github.com/isar/hive/blob/470473ffc1ba39f6c90f31ababe0ee63b76b69fe/hive/lib/src/adapters/date_time_adapter.dart#L28). class PreciseDateTimeAdapter extends TypeAdapter { @override final typeId = 18; From 307bb76be745cfca1ffa888d9a1ea678c1f58cc7 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:42:18 +0100 Subject: [PATCH 27/28] added a future wrapper --- lib/main.dart | 9 +++++---- lib/types/future_value.dart | 9 +++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 lib/types/future_value.dart diff --git a/lib/main.dart b/lib/main.dart index 32f3938..1690cd4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:concordium_wallet/state/network.dart'; import 'package:concordium_wallet/state/services.dart'; import 'package:concordium_wallet/state/terms_and_conditions.dart'; import 'package:concordium_wallet/theme.dart'; +import 'package:concordium_wallet/types/future_value.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -158,7 +159,7 @@ class _WithTermsAndConditionAcceptance extends StatefulWidget { } class _WithTermsAndConditionAcceptanceState extends State<_WithTermsAndConditionAcceptance> { - late final Future _lastAccepted; + late final Future> _lastAccepted; late final TermsAndConditionsRepository _repository; @override @@ -167,7 +168,7 @@ class _WithTermsAndConditionAcceptanceState extends State<_WithTermsAndCondition final storage = context.read().storage; setState(() { _repository = TermsAndConditionsRepository(storageProvider: storage); - _lastAccepted = _repository.getAcceptedTermsAndConditions(); + _lastAccepted = _repository.getAcceptedTermsAndConditions().then((value) => FutureValue(value)); }); } @@ -177,12 +178,12 @@ class _WithTermsAndConditionAcceptanceState extends State<_WithTermsAndCondition future: _lastAccepted, builder: (_, snapshot) { // Check on the connection state since last accepted possible null - if (snapshot.connectionState != ConnectionState.done) { + if (snapshot.data == null) { return const _Initializing(); } return BlocProvider( create: (_) { - return TermsAndConditionAcceptance(_repository, snapshot.data); + return TermsAndConditionAcceptance(_repository, snapshot.data!.value); }, child: widget.child); }); diff --git a/lib/types/future_value.dart b/lib/types/future_value.dart new file mode 100644 index 0000000..0ed1d89 --- /dev/null +++ b/lib/types/future_value.dart @@ -0,0 +1,9 @@ +import 'package:flutter/widgets.dart'; + +/// Wrapper around a nullable return type in a future. +/// By using this one can use [AsyncSnapshot.hasData] in [FutureBuilder]. +class FutureValue { + T? value; + + FutureValue(this.value); +} From 68a7f4a5d95f335d54d9e7b52fc9c8f85ace0ce4 Mon Sep 17 00:00:00 2001 From: schwartz-concordium <132270889+schwartz-concordium@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:21:13 +0100 Subject: [PATCH 28/28] add todo for error --- lib/main.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 1690cd4..9c8df58 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -168,7 +168,7 @@ class _WithTermsAndConditionAcceptanceState extends State<_WithTermsAndCondition final storage = context.read().storage; setState(() { _repository = TermsAndConditionsRepository(storageProvider: storage); - _lastAccepted = _repository.getAcceptedTermsAndConditions().then((value) => FutureValue(value)); + _lastAccepted = _repository.getAcceptedTermsAndConditions().then(FutureValue.new); }); } @@ -177,15 +177,16 @@ class _WithTermsAndConditionAcceptanceState extends State<_WithTermsAndCondition return FutureBuilder( future: _lastAccepted, builder: (_, snapshot) { - // Check on the connection state since last accepted possible null - if (snapshot.data == null) { - return const _Initializing(); + if (snapshot.data != null) { + return BlocProvider( + create: (_) { + return TermsAndConditionAcceptance(_repository, snapshot.requireData.value); + }, + child: widget.child); + } else if (snapshot.hasError) { + // TODO Handle error } - return BlocProvider( - create: (_) { - return TermsAndConditionAcceptance(_repository, snapshot.data!.value); - }, - child: widget.child); + return const _Initializing(); }); } }