diff --git a/mobile-app/lib/app_initializer.dart b/mobile-app/lib/app_initializer.dart index 3217fd73..435ccf36 100644 --- a/mobile-app/lib/app_initializer.dart +++ b/mobile-app/lib/app_initializer.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:resonance_network_wallet/providers/feature_flags_provider.dart'; +import 'package:resonance_network_wallet/providers/remote_config_provider.dart'; import 'package:resonance_network_wallet/services/history_polling_manager.dart'; import 'package:resonance_network_wallet/services/local_notifications_service.dart'; @@ -28,7 +28,7 @@ class _AppInitializerState extends ConsumerState { Future _initialize() async { try { - ref.read(featureFlagsProvider.notifier).registerRemoteRefreshListener(ref); + ref.read(remoteConfigProvider.notifier).registerRemoteRefreshListener(ref); final notificationService = ref.read(localNotificationsServiceProvider); await notificationService.init(); diff --git a/mobile-app/lib/app_lifecycle_manager.dart b/mobile-app/lib/app_lifecycle_manager.dart index ee1f0ed5..de441ed8 100644 --- a/mobile-app/lib/app_lifecycle_manager.dart +++ b/mobile-app/lib/app_lifecycle_manager.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/connectivity_provider.dart'; -import 'package:resonance_network_wallet/providers/feature_flags_provider.dart'; +import 'package:resonance_network_wallet/providers/remote_config_provider.dart'; import 'package:resonance_network_wallet/providers/local_auth_provider.dart'; import 'package:resonance_network_wallet/services/history_polling_manager.dart'; @@ -103,8 +103,8 @@ class _AppLifecycleManagerState extends ConsumerState with // Initialize Taskmaster login if wallet exists _initializeTaskmasterLogin(); - // Sync feature flags on background resume - unawaited(ref.read(featureFlagsProvider.notifier).syncFlags()); + // Sync remote config on background resume + unawaited(ref.read(remoteConfigProvider.notifier).syncConfig()); } } else { // Handle background states (inactive, paused, hidden, detached) diff --git a/mobile-app/lib/providers/feature_flags_provider.dart b/mobile-app/lib/providers/remote_config_provider.dart similarity index 71% rename from mobile-app/lib/providers/feature_flags_provider.dart rename to mobile-app/lib/providers/remote_config_provider.dart index 6b431460..4afaf985 100644 --- a/mobile-app/lib/providers/feature_flags_provider.dart +++ b/mobile-app/lib/providers/remote_config_provider.dart @@ -4,44 +4,43 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'dart:async'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/firebase_options.dart'; -import 'package:resonance_network_wallet/services/feature_flags_service.dart'; +import 'package:resonance_network_wallet/services/remote_config_service.dart'; import 'package:resonance_network_wallet/services/firebase_messaging_service.dart'; import 'package:resonance_network_wallet/shared/global_navigator_key.dart'; -final featureFlagsServiceProvider = Provider((ref) { - return FeatureFlagsService(); +final remoteConfigServiceProvider = Provider((ref) { + return RemoteConfigService(); }); -final featureFlagsProvider = StateNotifierProvider((ref) { - return FeatureFlagsNotifier(ref.read(featureFlagsServiceProvider)); +final remoteConfigProvider = StateNotifierProvider((ref) { + return RemoteConfigNotifier(ref.read(remoteConfigServiceProvider)); }); -class FeatureFlagsNotifier extends StateNotifier { - final FeatureFlagsService _service; +class RemoteConfigNotifier extends StateNotifier { + final RemoteConfigService _service; bool _isRefreshingRemote = false; bool _isEnablingRemoteNotifications = false; - FeatureFlagsNotifier(this._service) : super(_service.readLocalFlags()) { - syncFlags(); + RemoteConfigNotifier(this._service) : super(_service.readLocalConfig()) { + syncConfig(); } - Future syncFlags() async { + Future syncConfig() async { // Fetch remote in the background. This should not block startup feel. if (_isRefreshingRemote) return; _isRefreshingRemote = true; unawaited(() async { try { - final remote = await _service.readRemoteFlags(); + final remote = await _service.readRemoteConfig(); if (remote == null) return; if (remote != state) { - _service.cacheFlags(remote.toCacheJson()); + _service.cacheConfig(remote.toCacheJson()); state = remote; } } catch (e) { - // Keep using cached flags on failure. - print('Feature flags remote refresh failed: $e'); + print('Remote config remote refresh failed: $e'); } finally { _isRefreshingRemote = false; } @@ -51,7 +50,7 @@ class FeatureFlagsNotifier extends StateNotifier { void registerRemoteRefreshListener(WidgetRef ref) { // using `listenManual` allows // setting up the side-effect listener from `initState`/async code. - ref.listenManual(featureFlagsProvider, (previous, next) { + ref.listenManual(remoteConfigProvider, (previous, next) { if (!next.enableRemoteNotifications) return; unawaited(_enableRemoteNotificationsIfNeeded(ref)); }); diff --git a/mobile-app/lib/services/feature_flags_service.dart b/mobile-app/lib/services/feature_flags_service.dart deleted file mode 100644 index 70b39c62..00000000 --- a/mobile-app/lib/services/feature_flags_service.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:convert'; - -import 'package:quantus_sdk/quantus_sdk.dart'; - -const String featureFlagsCacheKey = 'feature_flags_cache_v1'; - -class FeatureFlagsService { - final TaskmasterService _taskmasterService = TaskmasterService(); - final SettingsService _settingsService = SettingsService(); - - Future readRemoteFlags() async { - try { - final remoteData = await _taskmasterService.getWalletFeatureFlags(); - return remoteData; - } catch (error) { - print('Feature flags remote read failed: $error'); - return null; - } - } - - FeatureFlagsModel readLocalFlags() { - final jsonString = _settingsService.getString(featureFlagsCacheKey); - - if (jsonString == null || jsonString.isEmpty) { - cacheFlags(FeatureFlagsModel.defaults.toCacheJson()); - return FeatureFlagsModel.defaults; - } - - final decoded = jsonDecode(jsonString); - return FeatureFlagsModel.fromJson(decoded); - } - - Future cacheFlags(Object json) async { - try { - await _settingsService.setString(featureFlagsCacheKey, jsonEncode(json)); - } catch (error) { - print('Feature flags local save failed: $error'); - } - } -} diff --git a/mobile-app/lib/services/remote_config_service.dart b/mobile-app/lib/services/remote_config_service.dart new file mode 100644 index 00000000..16396eb8 --- /dev/null +++ b/mobile-app/lib/services/remote_config_service.dart @@ -0,0 +1,40 @@ +import 'dart:convert'; + +import 'package:quantus_sdk/quantus_sdk.dart'; + +const String remoteConfigCacheKey = 'remote_config_cache_v1'; + +class RemoteConfigService { + final TaskmasterService _taskmasterService = TaskmasterService(); + final SettingsService _settingsService = SettingsService(); + + Future readRemoteConfig() async { + try { + final remoteData = await _taskmasterService.getRemoteConfig(); + return remoteData; + } catch (error) { + print('Remote config remote read failed: $error'); + return null; + } + } + + RemoteConfigModel readLocalConfig() { + final jsonString = _settingsService.getString(remoteConfigCacheKey); + + if (jsonString == null || jsonString.isEmpty) { + cacheConfig(RemoteConfigModel.defaults.toCacheJson()); + return RemoteConfigModel.defaults; + } + + final decoded = jsonDecode(jsonString); + return RemoteConfigModel.fromJson(decoded); + } + + Future cacheConfig(Object json) async { + try { + await _settingsService.setString(remoteConfigCacheKey, jsonEncode(json)); + } catch (error) { + print('Remote config local save failed: $error'); + } + } +} diff --git a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart index 78ed6c8d..f74b7be4 100644 --- a/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart +++ b/mobile-app/lib/v2/screens/create/wallet_ready_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; -import 'package:resonance_network_wallet/providers/feature_flags_provider.dart'; +import 'package:resonance_network_wallet/providers/remote_config_provider.dart'; import 'package:resonance_network_wallet/services/firebase_messaging_service.dart'; import 'package:resonance_network_wallet/services/referral_service.dart'; import 'package:resonance_network_wallet/shared/extensions/clipboard_extensions.dart'; @@ -91,7 +91,7 @@ class _WalletReadyScreenV2State extends ConsumerState { ref.invalidate(accountsProvider); ref.invalidate(activeAccountProvider); - if (ref.read(featureFlagsProvider).enableRemoteNotifications) { + if (ref.read(remoteConfigProvider).enableRemoteNotifications) { ref.read(firebaseMessagingServiceProvider).registerDeviceIfPossible(); } diff --git a/mobile-app/lib/v2/screens/home/home_screen.dart b/mobile-app/lib/v2/screens/home/home_screen.dart index a10050c8..ed595604 100644 --- a/mobile-app/lib/v2/screens/home/home_screen.dart +++ b/mobile-app/lib/v2/screens/home/home_screen.dart @@ -6,7 +6,7 @@ import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/features/components/button.dart'; import 'package:resonance_network_wallet/features/components/shared_address_action_sheet.dart'; import 'package:resonance_network_wallet/features/components/skeleton.dart'; -import 'package:resonance_network_wallet/providers/feature_flags_provider.dart'; +import 'package:resonance_network_wallet/providers/remote_config_provider.dart'; import 'package:resonance_network_wallet/v2/components/glass_button.dart' hide ButtonVariant; import 'package:resonance_network_wallet/v2/components/glass_icon_button.dart'; import 'package:resonance_network_wallet/v2/screens/accounts/accounts_sheet.dart'; @@ -226,7 +226,7 @@ class _HomeScreenState extends ConsumerState { } Widget _buildActionButtons() { - final enableSwap = ref.watch(featureFlagsProvider).enableSwap; + final enableSwap = ref.watch(remoteConfigProvider).enableSwap; final receiveCard = _actionCard( iconAsset: 'assets/v2/action_receive.svg', diff --git a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart index 74150900..6eccd19f 100644 --- a/mobile-app/lib/v2/screens/import/import_wallet_screen.dart +++ b/mobile-app/lib/v2/screens/import/import_wallet_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; import 'package:resonance_network_wallet/providers/account_providers.dart'; -import 'package:resonance_network_wallet/providers/feature_flags_provider.dart'; +import 'package:resonance_network_wallet/providers/remote_config_provider.dart'; import 'package:resonance_network_wallet/services/firebase_messaging_service.dart'; import 'package:resonance_network_wallet/v2/components/glass_button.dart'; import 'package:resonance_network_wallet/v2/components/scaffold_base.dart'; @@ -55,7 +55,7 @@ class _ImportWalletScreenV2State extends ConsumerState { _settingsService.setReferralCheckCompleted(); _settingsService.setExistingUserSeenPromoVideo(); - if (ref.read(featureFlagsProvider).enableRemoteNotifications) { + if (ref.read(remoteConfigProvider).enableRemoteNotifications) { ref.read(firebaseMessagingServiceProvider).registerDeviceIfPossible(); } diff --git a/mobile-app/lib/v2/screens/settings/settings_screen.dart b/mobile-app/lib/v2/screens/settings/settings_screen.dart index 57801e5b..98202e0a 100644 --- a/mobile-app/lib/v2/screens/settings/settings_screen.dart +++ b/mobile-app/lib/v2/screens/settings/settings_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:quantus_sdk/quantus_sdk.dart'; -import 'package:resonance_network_wallet/providers/feature_flags_provider.dart'; +import 'package:resonance_network_wallet/providers/remote_config_provider.dart'; import 'package:resonance_network_wallet/services/firebase_messaging_service.dart'; import 'package:resonance_network_wallet/v2/components/glass_button.dart'; import 'package:resonance_network_wallet/v2/screens/settings/recovery_phrase_screen.dart'; @@ -66,7 +66,7 @@ class _SettingsScreenV2State extends ConsumerState { } Future _resetAndClearData() async { - if (ref.read(featureFlagsProvider).enableRemoteNotifications) { + if (ref.read(remoteConfigProvider).enableRemoteNotifications) { ref.read(firebaseMessagingServiceProvider).unregisterDevice(); } diff --git a/quantus_sdk/lib/quantus_sdk.dart b/quantus_sdk/lib/quantus_sdk.dart index 85f0bb2e..a62d7601 100644 --- a/quantus_sdk/lib/quantus_sdk.dart +++ b/quantus_sdk/lib/quantus_sdk.dart @@ -23,7 +23,7 @@ export 'src/models/event_type.dart'; export 'src/models/extrinsic_data.dart'; export 'src/models/extrinsic_fee_data.dart'; export 'src/models/unsigned_transaction_data.dart'; -export 'src/models/feature_flags_model.dart'; +export 'src/models/remote_config_model.dart'; export 'src/models/miner_reward_event.dart'; export 'src/models/miner_stats.dart'; export 'src/models/opted_in_position.dart'; diff --git a/quantus_sdk/lib/src/models/feature_flags_model.dart b/quantus_sdk/lib/src/models/remote_config_model.dart similarity index 91% rename from quantus_sdk/lib/src/models/feature_flags_model.dart rename to quantus_sdk/lib/src/models/remote_config_model.dart index fde15408..93f83aaa 100644 --- a/quantus_sdk/lib/src/models/feature_flags_model.dart +++ b/quantus_sdk/lib/src/models/remote_config_model.dart @@ -1,11 +1,11 @@ -class FeatureFlagsModel { +class RemoteConfigModel { final bool enableTestButtons; final bool enableKeystoneHardwareWallet; final bool enableHighSecurity; final bool enableRemoteNotifications; final bool enableSwap; - const FeatureFlagsModel({ + const RemoteConfigModel({ required this.enableTestButtons, required this.enableKeystoneHardwareWallet, required this.enableHighSecurity, @@ -32,7 +32,7 @@ class FeatureFlagsModel { ); } - static const FeatureFlagsModel defaults = FeatureFlagsModel( + static const RemoteConfigModel defaults = RemoteConfigModel( enableTestButtons: false, enableKeystoneHardwareWallet: false, enableHighSecurity: true, @@ -52,8 +52,8 @@ class FeatureFlagsModel { ); } - factory FeatureFlagsModel.fromJson(Map json) { - return FeatureFlagsModel( + factory RemoteConfigModel.fromJson(Map json) { + return RemoteConfigModel( enableTestButtons: json['enableTestButtons'] ?? defaults.enableTestButtons, enableKeystoneHardwareWallet: json['enableKeystoneHardwareWallet'] ?? defaults.enableKeystoneHardwareWallet, enableHighSecurity: json['enableHighSecurity'] ?? defaults.enableHighSecurity, @@ -62,7 +62,7 @@ class FeatureFlagsModel { ); } - bool compare(FeatureFlagsModel other) { + bool compare(RemoteConfigModel other) { return match( fn: (enableTestButtons, enableKeystoneHardwareWallet, enableHighSecurity, enableRemoteNotifications, enableSwap) { return other.match( diff --git a/quantus_sdk/lib/src/services/taskmaster_service.dart b/quantus_sdk/lib/src/services/taskmaster_service.dart index b138b05d..a0a96a4c 100644 --- a/quantus_sdk/lib/src/services/taskmaster_service.dart +++ b/quantus_sdk/lib/src/services/taskmaster_service.dart @@ -492,7 +492,7 @@ class TaskmasterService { await ensureIsLoggedIn(); } - Future getWalletFeatureFlags() async { + Future getRemoteConfig() async { final Uri uri = Uri.parse('${AppConstants.taskMasterEndpoint}/feature-flags/wallet'); final http.Response response = await http.get(uri, headers: {'Content-Type': 'application/json'}); @@ -507,7 +507,7 @@ class TaskmasterService { throw Exception('Feature flags request failed with status: ${response.statusCode}. Body: ${response.body}'); } - return FeatureFlagsModel.fromJson(data); + return RemoteConfigModel.fromJson(data); } Future getMinerStats() async { diff --git a/quantus_sdk/test/contract/feature_flags_api_test.dart b/quantus_sdk/test/contract/remote_config_api_test.dart similarity index 94% rename from quantus_sdk/test/contract/feature_flags_api_test.dart rename to quantus_sdk/test/contract/remote_config_api_test.dart index 1eecf60b..e1b804c2 100644 --- a/quantus_sdk/test/contract/feature_flags_api_test.dart +++ b/quantus_sdk/test/contract/remote_config_api_test.dart @@ -5,7 +5,7 @@ import 'package:http/http.dart' as http; void main() { group('API Contract Tests', () { - test('Remote Feature Flags API exactly matches FeatureFlagsModel properties', () async { + test('Remote Feature Flags API exactly matches RemoteConfigModel properties', () async { final Uri uri = Uri.parse('${AppConstants.taskMasterEndpoint}/feature-flags/wallet'); final http.Response response = await http.get(uri, headers: {'Content-Type': 'application/json'}); @@ -39,7 +39,7 @@ void main() { expect(newKeys, isEmpty, reason: 'WARNING: The API sent new properties not handled in your app: $newKeys'); try { - FeatureFlagsModel.fromJson(data); + RemoteConfigModel.fromJson(data); } catch (e) { fail('Failed to parse feature flags model: $e'); }