diff --git a/apps/mobile_app/ios/Podfile b/apps/mobile_app/ios/Podfile index 7eff7c9..547bfad 100644 --- a/apps/mobile_app/ios/Podfile +++ b/apps/mobile_app/ios/Podfile @@ -27,8 +27,6 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do - use_frameworks! - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) target 'RunnerTests' do inherit! :search_paths diff --git a/apps/mobile_app/ios/Podfile.lock b/apps/mobile_app/ios/Podfile.lock index ca2d366..7e278f5 100644 --- a/apps/mobile_app/ios/Podfile.lock +++ b/apps/mobile_app/ios/Podfile.lock @@ -1,23 +1,16 @@ PODS: - Flutter (1.0.0) - - shared_preferences_foundation (0.0.1): - - Flutter - - FlutterMacOS DEPENDENCIES: - Flutter (from `Flutter`) - - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) EXTERNAL SOURCES: Flutter: :path: Flutter - shared_preferences_foundation: - :path: ".symlinks/plugins/shared_preferences_foundation/darwin" SPEC CHECKSUMS: Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb -PODFILE CHECKSUM: f8c2dcdfb50bb67645580d28a6bf814fca30bdec +PODFILE CHECKSUM: 3c41d3a6488201d7e0d12c3265a22101f620ec8e COCOAPODS: 1.16.2 diff --git a/apps/mobile_app/lib/core/network/agent_base_uri_storage.dart b/apps/mobile_app/lib/core/network/agent_base_uri_storage.dart new file mode 100644 index 0000000..d8b15b7 --- /dev/null +++ b/apps/mobile_app/lib/core/network/agent_base_uri_storage.dart @@ -0,0 +1,44 @@ +import 'dart:io'; + +class AgentBaseUriStorage { + AgentBaseUriStorage({Future Function()? storageFileLoader}) + : _storageFileLoader = storageFileLoader ?? _defaultStorageFile; + + static const String storageFileName = 'agent_base_uri_v1.txt'; + + final Future Function() _storageFileLoader; + + Future read() async { + final storageFile = await _storageFileLoader(); + if (!await storageFile.exists()) { + return null; + } + + try { + final raw = await storageFile.readAsString(); + final parsed = Uri.tryParse(raw.trim()); + if (parsed == null || parsed.scheme.isEmpty || parsed.host.isEmpty) { + return null; + } + return parsed; + } catch (_) { + return null; + } + } + + Future write(Uri uri) async { + try { + final storageFile = await _storageFileLoader(); + await storageFile.parent.create(recursive: true); + await storageFile.writeAsString(uri.toString(), flush: true); + } catch (_) {} + } + + static Future _defaultStorageFile() async { + final rootDirectory = Directory.systemTemp.parent; + final appSupportDirectory = Directory( + '${rootDirectory.path}/Library/Application Support', + ); + return File('${appSupportDirectory.path}/$storageFileName'); + } +} diff --git a/apps/mobile_app/lib/core/network/agent_connection_providers.dart b/apps/mobile_app/lib/core/network/agent_connection_providers.dart index abe858e..cf7cf36 100644 --- a/apps/mobile_app/lib/core/network/agent_connection_providers.dart +++ b/apps/mobile_app/lib/core/network/agent_connection_providers.dart @@ -1,11 +1,16 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'agent_base_uri_storage.dart'; import 'agent_api_client.dart'; import '../../features/projects/project.dart'; import '../../features/projects/project_repository.dart'; import '../../features/sessions/session_repository.dart'; import '../../features/sessions/session.dart'; +final agentBaseUriStorageProvider = Provider((ref) { + return AgentBaseUriStorage(); +}); + final agentBaseUriProvider = StateProvider((ref) { return Uri.parse('http://100.81.30.82:5067'); }); diff --git a/apps/mobile_app/lib/features/presets/preset_repository.dart b/apps/mobile_app/lib/features/presets/preset_repository.dart index d639611..80f8e0d 100644 --- a/apps/mobile_app/lib/features/presets/preset_repository.dart +++ b/apps/mobile_app/lib/features/presets/preset_repository.dart @@ -1,29 +1,38 @@ import 'dart:convert'; - -import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:io'; import 'preset_command.dart'; class PresetRepository { - PresetRepository({ - Future Function()? sharedPreferencesLoader, - }) : _sharedPreferencesLoader = - sharedPreferencesLoader ?? SharedPreferences.getInstance; + PresetRepository({Future Function()? storageFileLoader}) + : _storageFileLoader = storageFileLoader ?? _defaultStorageFile; - static const String storageKey = 'preset_commands_v1'; + static const String storageFileName = 'preset_commands_v1.json'; - final Future Function() _sharedPreferencesLoader; + final Future Function() _storageFileLoader; Future> listPresets() async { - final sharedPreferences = await _sharedPreferencesLoader(); - final storedItems = - sharedPreferences.getStringList(storageKey) ?? const []; - return storedItems - .map((item) => jsonDecode(item)) - .whereType>() - .map(PresetCommand.fromJson) - .where((preset) => preset.id.isNotEmpty) - .toList(growable: false); + final storageFile = await _storageFileLoader(); + if (!await storageFile.exists()) { + return const []; + } + + try { + final raw = await storageFile.readAsString(); + final decoded = jsonDecode(raw); + if (decoded is! List) { + return const []; + } + + return decoded + .whereType() + .map((item) => Map.from(item)) + .map(PresetCommand.fromJson) + .where((preset) => preset.id.isNotEmpty) + .toList(growable: false); + } catch (_) { + return const []; + } } Future savePreset(PresetCommand preset) async { @@ -47,10 +56,21 @@ class PresetRepository { } Future _persistPresets(List presets) async { - final sharedPreferences = await _sharedPreferencesLoader(); - await sharedPreferences.setStringList( - storageKey, - presets.map((preset) => jsonEncode(preset.toJson())).toList(), + final storageFile = await _storageFileLoader(); + await storageFile.parent.create(recursive: true); + await storageFile.writeAsString( + jsonEncode( + presets.map((preset) => preset.toJson()).toList(growable: false), + ), + flush: true, ); } + + static Future _defaultStorageFile() async { + final rootDirectory = Directory.systemTemp.parent; + final appSupportDirectory = Directory( + '${rootDirectory.path}/Library/Application Support', + ); + return File('${appSupportDirectory.path}/$storageFileName'); + } } diff --git a/apps/mobile_app/lib/features/projects/project_list_page.dart b/apps/mobile_app/lib/features/projects/project_list_page.dart index 0f2fa9f..d71b33c 100644 --- a/apps/mobile_app/lib/features/projects/project_list_page.dart +++ b/apps/mobile_app/lib/features/projects/project_list_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'dart:async'; import '../../app/app_theme.dart'; import '../../app/ui_shell.dart'; @@ -30,6 +31,7 @@ class _ProjectListPageState extends ConsumerState { _agentUrlController = TextEditingController( text: ref.read(agentBaseUriProvider).toString(), ); + unawaited(_restoreAgentUrl()); } @override @@ -40,9 +42,8 @@ class _ProjectListPageState extends ConsumerState { Future _reloadProjects() async { _detailFutures.clear(); - ref.invalidate(projectsProvider); try { - await ref.read(projectsProvider.future); + await ref.refresh(projectsProvider.future); } catch (error) { if (!mounted) { return; @@ -52,6 +53,25 @@ class _ProjectListPageState extends ConsumerState { } } + Future _restoreAgentUrl() async { + final restoredUri = await ref.read(agentBaseUriStorageProvider).read(); + if (!mounted || restoredUri == null) { + return; + } + + final currentUri = ref.read(agentBaseUriProvider); + if (currentUri == restoredUri) { + if (_agentUrlController.text != restoredUri.toString()) { + _agentUrlController.text = restoredUri.toString(); + } + return; + } + + ref.read(agentBaseUriProvider.notifier).state = restoredUri; + _agentUrlController.text = restoredUri.toString(); + await _reloadProjects(); + } + void _showMessage(String message) { ScaffoldMessenger.of( context, @@ -71,6 +91,7 @@ class _ProjectListPageState extends ConsumerState { if (current != parsedUri) { ref.read(agentBaseUriProvider.notifier).state = parsedUri; } + await ref.read(agentBaseUriStorageProvider).write(parsedUri); ref.invalidate(sessionsProvider); await _reloadProjects(); diff --git a/apps/mobile_app/lib/features/sessions/session_list_page.dart b/apps/mobile_app/lib/features/sessions/session_list_page.dart index 65df7f8..13cdf1a 100644 --- a/apps/mobile_app/lib/features/sessions/session_list_page.dart +++ b/apps/mobile_app/lib/features/sessions/session_list_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'dart:async'; import '../../app/app_theme.dart'; import '../../app/ui_shell.dart'; @@ -23,6 +24,7 @@ class _SessionListPageState extends ConsumerState { _agentUrlController = TextEditingController( text: ref.read(agentBaseUriProvider).toString(), ); + unawaited(_restoreAgentUrl()); } @override @@ -41,6 +43,25 @@ class _SessionListPageState extends ConsumerState { await _reloadSessions(); } + Future _restoreAgentUrl() async { + final restoredUri = await ref.read(agentBaseUriStorageProvider).read(); + if (!mounted || restoredUri == null) { + return; + } + + final currentUri = ref.read(agentBaseUriProvider); + if (currentUri == restoredUri) { + if (_agentUrlController.text != restoredUri.toString()) { + _agentUrlController.text = restoredUri.toString(); + } + return; + } + + ref.read(agentBaseUriProvider.notifier).state = restoredUri; + _agentUrlController.text = restoredUri.toString(); + await _reloadSessions(); + } + Future _createSession() async { final repository = ref.read(sessionRepositoryProvider); var sessionNameInput = ''; @@ -159,18 +180,16 @@ class _SessionListPageState extends ConsumerState { } final current = ref.read(agentBaseUriProvider); - if (current == parsedUri) { - return; + if (current != parsedUri) { + ref.read(agentBaseUriProvider.notifier).state = parsedUri; } - - ref.read(agentBaseUriProvider.notifier).state = parsedUri; + await ref.read(agentBaseUriStorageProvider).write(parsedUri); await _reloadSessions(); } Future _reloadSessions() async { - ref.invalidate(sessionsProvider); try { - await ref.read(sessionsProvider.future); + await ref.refresh(sessionsProvider.future); } catch (error) { if (!mounted) { return; diff --git a/apps/mobile_app/pubspec.lock b/apps/mobile_app/pubspec.lock index e1b3849..6c1530c 100644 --- a/apps/mobile_app/pubspec.lock +++ b/apps/mobile_app/pubspec.lock @@ -6,7 +6,7 @@ packages: description: name: _fe_analyzer_shared sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "85.0.0" analyzer: @@ -14,7 +14,7 @@ packages: description: name: analyzer sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.7.1" args: @@ -22,7 +22,7 @@ packages: description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.7.0" async: @@ -30,7 +30,7 @@ packages: description: name: async sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.13.1" boolean_selector: @@ -38,7 +38,7 @@ packages: description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" build: @@ -46,7 +46,7 @@ packages: description: name: build sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.4" build_config: @@ -54,7 +54,7 @@ packages: description: name: build_config sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" build_daemon: @@ -62,7 +62,7 @@ packages: description: name: build_daemon sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.1" build_resolvers: @@ -70,7 +70,7 @@ packages: description: name: build_resolvers sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.4" build_runner: @@ -78,7 +78,7 @@ packages: description: name: build_runner sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.4" build_runner_core: @@ -86,7 +86,7 @@ packages: description: name: build_runner_core sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "9.1.2" built_collection: @@ -94,7 +94,7 @@ packages: description: name: built_collection sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.1" built_value: @@ -102,23 +102,23 @@ packages: description: name: built_value sha256: "0730c18c770d05636a8f945c32a4d7d81cb6e0f0148c8db4ad12e7748f7e49af" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "8.12.5" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.flutter-io.cn" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: name: checked_yaml sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.4" clock: @@ -126,7 +126,7 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.2" code_builder: @@ -134,7 +134,7 @@ packages: description: name: code_builder sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.11.1" collection: @@ -142,7 +142,7 @@ packages: description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.19.1" convert: @@ -150,7 +150,7 @@ packages: description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.2" crypto: @@ -158,7 +158,7 @@ packages: description: name: crypto sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.7" cupertino_icons: @@ -166,7 +166,7 @@ packages: description: name: cupertino_icons sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.9" dart_style: @@ -174,7 +174,7 @@ packages: description: name: dart_style sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.1" dio: @@ -182,7 +182,7 @@ packages: description: name: dio sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.9.2" dio_web_adapter: @@ -190,7 +190,7 @@ packages: description: name: dio_web_adapter sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.2" equatable: @@ -198,7 +198,7 @@ packages: description: name: equatable sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.8" fake_async: @@ -206,23 +206,15 @@ packages: description: name: fake_async sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.3" - ffi: - dependency: transitive - description: - name: ffi - sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.0" file: dependency: transitive description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "7.0.1" fixnum: @@ -230,7 +222,7 @@ packages: description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" flutter: @@ -243,7 +235,7 @@ packages: description: name: flutter_lints sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.0.0" flutter_riverpod: @@ -251,7 +243,7 @@ packages: description: name: flutter_riverpod sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.6.1" flutter_test: @@ -269,7 +261,7 @@ packages: description: name: freezed sha256: "59a584c24b3acdc5250bb856d0d3e9c0b798ed14a4af1ddb7dc1c7b41df91c9c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.5.8" freezed_annotation: @@ -277,7 +269,7 @@ packages: description: name: freezed_annotation sha256: c2e2d632dd9b8a2b7751117abcfc2b4888ecfe181bd9fca7170d9ef02e595fe2 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.4.4" frontend_server_client: @@ -285,7 +277,7 @@ packages: description: name: frontend_server_client sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.0" glob: @@ -293,7 +285,7 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.3" go_router: @@ -301,7 +293,7 @@ packages: description: name: go_router sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "14.8.1" graphs: @@ -309,7 +301,7 @@ packages: description: name: graphs sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.3.2" http: @@ -317,7 +309,7 @@ packages: description: name: http sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.6.0" http_multi_server: @@ -325,7 +317,7 @@ packages: description: name: http_multi_server sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.2" http_parser: @@ -333,7 +325,7 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.1.2" io: @@ -341,7 +333,7 @@ packages: description: name: io sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.5" js: @@ -349,7 +341,7 @@ packages: description: name: js sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.7.2" json_annotation: @@ -357,7 +349,7 @@ packages: description: name: json_annotation sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.9.0" json_serializable: @@ -365,7 +357,7 @@ packages: description: name: json_serializable sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "6.9.5" leak_tracker: @@ -373,7 +365,7 @@ packages: description: name: leak_tracker sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "11.0.2" leak_tracker_flutter_testing: @@ -381,7 +373,7 @@ packages: description: name: leak_tracker_flutter_testing sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.10" leak_tracker_testing: @@ -389,7 +381,7 @@ packages: description: name: leak_tracker_testing sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.2" lints: @@ -397,7 +389,7 @@ packages: description: name: lints sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "5.1.1" logging: @@ -405,39 +397,39 @@ packages: description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.flutter-io.cn" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.flutter-io.cn" + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.0" mocktail: @@ -445,7 +437,7 @@ packages: description: name: mocktail sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.4" package_config: @@ -453,7 +445,7 @@ packages: description: name: package_config sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" path: @@ -461,55 +453,15 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.9.1" - path_provider_linux: - dependency: transitive - description: - name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.2.1" - path_provider_platform_interface: - dependency: transitive - description: - name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.2" - path_provider_windows: - dependency: transitive - description: - name: path_provider_windows - sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.3.0" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.1.6" - plugin_platform_interface: - dependency: transitive - description: - name: plugin_platform_interface - sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.8" pool: dependency: transitive description: name: pool sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.5.2" pub_semver: @@ -517,7 +469,7 @@ packages: description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" pubspec_parse: @@ -525,7 +477,7 @@ packages: description: name: pubspec_parse sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.5.0" quiver: @@ -533,7 +485,7 @@ packages: description: name: quiver sha256: ea0b925899e64ecdfbf9c7becb60d5b50e706ade44a85b2363be2a22d88117d2 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.2.2" riverpod: @@ -541,71 +493,15 @@ packages: description: name: riverpod sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.6.1" - shared_preferences: - dependency: "direct main" - description: - name: shared_preferences - sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.5.5" - shared_preferences_android: - dependency: transitive - description: - name: shared_preferences_android - sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.23" - shared_preferences_foundation: - dependency: transitive - description: - name: shared_preferences_foundation - sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.5.6" - shared_preferences_linux: - dependency: transitive - description: - name: shared_preferences_linux - sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.1" - shared_preferences_platform_interface: - dependency: transitive - description: - name: shared_preferences_platform_interface - sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.2" - shared_preferences_web: - dependency: transitive - description: - name: shared_preferences_web - sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.3" - shared_preferences_windows: - dependency: transitive - description: - name: shared_preferences_windows - sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.4.1" shelf: dependency: transitive description: name: shelf sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.2" shelf_web_socket: @@ -613,7 +509,7 @@ packages: description: name: shelf_web_socket sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.0" sky_engine: @@ -626,7 +522,7 @@ packages: description: name: source_gen sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.0.0" source_helper: @@ -634,7 +530,7 @@ packages: description: name: source_helper sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.3.7" source_span: @@ -642,7 +538,7 @@ packages: description: name: source_span sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.10.2" stack_trace: @@ -650,7 +546,7 @@ packages: description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.12.1" state_notifier: @@ -658,7 +554,7 @@ packages: description: name: state_notifier sha256: b8677376aa54f2d7c58280d5a007f9e8774f1968d1fb1c096adcb4792fba29bb - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.0" stream_channel: @@ -666,7 +562,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.4" stream_transform: @@ -674,7 +570,7 @@ packages: description: name: stream_transform sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.1.1" string_scanner: @@ -682,7 +578,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.1" term_glyph: @@ -690,23 +586,23 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" - url: "https://pub.flutter-io.cn" + sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.10" timing: dependency: transitive description: name: timing sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.2" typed_data: @@ -714,7 +610,7 @@ packages: description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.4.0" vector_math: @@ -722,7 +618,7 @@ packages: description: name: vector_math sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "2.2.0" vm_service: @@ -730,7 +626,7 @@ packages: description: name: vm_service sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "15.0.2" watcher: @@ -738,7 +634,7 @@ packages: description: name: watcher sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.2.1" web: @@ -746,7 +642,7 @@ packages: description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.1.1" web_socket: @@ -754,7 +650,7 @@ packages: description: name: web_socket sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "1.0.1" web_socket_channel: @@ -762,23 +658,15 @@ packages: description: name: web_socket_channel sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.0.3" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.0" xterm: dependency: "direct main" description: name: xterm sha256: "168dfedca77cba33fdb6f52e2cd001e9fde216e398e89335c19b524bb22da3a2" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "4.0.0" yaml: @@ -786,7 +674,7 @@ packages: description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "3.1.3" zmodem: @@ -794,9 +682,9 @@ packages: description: name: zmodem sha256: "3b7e5b29f3a7d8aee472029b05165a68438eff2f3f7766edf13daba1e297adbf" - url: "https://pub.flutter-io.cn" + url: "https://pub.dev" source: hosted version: "0.0.6" sdks: dart: ">=3.9.2 <4.0.0" - flutter: ">=3.35.0" + flutter: ">=3.22.0" diff --git a/apps/mobile_app/pubspec.yaml b/apps/mobile_app/pubspec.yaml index 38eadc5..651f70d 100644 --- a/apps/mobile_app/pubspec.yaml +++ b/apps/mobile_app/pubspec.yaml @@ -37,7 +37,6 @@ dependencies: freezed_annotation: ^2.4.4 json_annotation: ^4.9.0 xterm: ^4.0.0 - shared_preferences: ^2.5.3 cupertino_icons: ^1.0.8 dev_dependencies: diff --git a/apps/mobile_app/test/core/network/agent_base_uri_storage_test.dart b/apps/mobile_app/test/core/network/agent_base_uri_storage_test.dart new file mode 100644 index 0000000..506c2c2 --- /dev/null +++ b/apps/mobile_app/test/core/network/agent_base_uri_storage_test.dart @@ -0,0 +1,47 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:term_remote_ctl/core/network/agent_base_uri_storage.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + late Directory tempDirectory; + late File storageFile; + late AgentBaseUriStorage storage; + + setUp(() async { + tempDirectory = await Directory.systemTemp.createTemp( + 'agent_base_uri_storage_test_', + ); + storageFile = File( + '${tempDirectory.path}/${AgentBaseUriStorage.storageFileName}', + ); + storage = AgentBaseUriStorage(storageFileLoader: () async => storageFile); + }); + + tearDown(() async { + if (await tempDirectory.exists()) { + await tempDirectory.delete(recursive: true); + } + }); + + test('returns null when no stored uri exists', () async { + expect(await storage.read(), isNull); + }); + + test('writes and restores a previously saved uri', () async { + final expected = Uri.parse('https://host.example:9443'); + + await storage.write(expected); + final restored = await storage.read(); + + expect(restored, expected); + }); + + test('ignores malformed persisted content', () async { + await storageFile.writeAsString('not-a-uri', flush: true); + + expect(await storage.read(), isNull); + }); +} diff --git a/apps/mobile_app/test/features/presets/preset_repository_test.dart b/apps/mobile_app/test/features/presets/preset_repository_test.dart index 0364200..e45b31d 100644 --- a/apps/mobile_app/test/features/presets/preset_repository_test.dart +++ b/apps/mobile_app/test/features/presets/preset_repository_test.dart @@ -1,23 +1,39 @@ +import 'dart:io'; + import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:term_remote_ctl/features/presets/preset_command.dart'; import 'package:term_remote_ctl/features/presets/preset_repository.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); - setUp(() { - SharedPreferences.setMockInitialValues({}); + late Directory tempDirectory; + late PresetRepository repository; + + setUp(() async { + tempDirectory = await Directory.systemTemp.createTemp( + 'preset_repository_test_', + ); + repository = PresetRepository( + storageFileLoader: () async => + File('${tempDirectory.path}/${PresetRepository.storageFileName}'), + ); + }); + + tearDown(() async { + if (await tempDirectory.exists()) { + await tempDirectory.delete(recursive: true); + } }); test('listPresets restores persisted presets from local storage', () async { - SharedPreferences.setMockInitialValues({ - 'preset_commands_v1': [ - '{"id":"preset-1","label":"ssh prod","commandText":"ssh admin@prod"}', - ], - }); + final storageFile = File( + '${tempDirectory.path}/${PresetRepository.storageFileName}', + ); + await storageFile.writeAsString( + '[{"id":"preset-1","label":"ssh prod","commandText":"ssh admin@prod"}]', + ); - final repository = PresetRepository(); final presets = await repository.listPresets(); expect(presets, [ @@ -33,8 +49,6 @@ void main() { }); test('savePreset appends a new preset and persists it', () async { - final repository = PresetRepository(); - final savedPreset = await repository.savePreset( const PresetCommand( id: 'preset-1', @@ -52,13 +66,12 @@ void main() { }); test('savePreset updates an existing preset in place', () async { - SharedPreferences.setMockInitialValues({ - 'preset_commands_v1': [ - '{"id":"preset-1","label":"ssh prod","commandText":"ssh admin@prod"}', - ], - }); - - final repository = PresetRepository(); + final storageFile = File( + '${tempDirectory.path}/${PresetRepository.storageFileName}', + ); + await storageFile.writeAsString( + '[{"id":"preset-1","label":"ssh prod","commandText":"ssh admin@prod"}]', + ); await repository.savePreset( const PresetCommand( @@ -75,14 +88,13 @@ void main() { }); test('deletePreset removes a persisted preset', () async { - SharedPreferences.setMockInitialValues({ - 'preset_commands_v1': [ - '{"id":"preset-1","label":"ssh prod","commandText":"ssh admin@prod"}', - '{"id":"preset-2","label":"git pull","commandText":"git pull --ff-only"}', - ], - }); - - final repository = PresetRepository(); + final storageFile = File( + '${tempDirectory.path}/${PresetRepository.storageFileName}', + ); + await storageFile.writeAsString( + '[{"id":"preset-1","label":"ssh prod","commandText":"ssh admin@prod"},' + '{"id":"preset-2","label":"git pull","commandText":"git pull --ff-only"}]', + ); await repository.deletePreset('preset-1'); final reloaded = await repository.listPresets(); diff --git a/apps/mobile_app/test/project_home_test.dart b/apps/mobile_app/test/project_home_test.dart index 2e240de..1555ec9 100644 --- a/apps/mobile_app/test/project_home_test.dart +++ b/apps/mobile_app/test/project_home_test.dart @@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:term_remote_ctl/app/app.dart'; import 'package:term_remote_ctl/core/network/agent_api_client.dart'; +import 'package:term_remote_ctl/core/network/agent_base_uri_storage.dart'; import 'package:term_remote_ctl/core/network/agent_connection_providers.dart'; import 'package:term_remote_ctl/features/projects/project.dart'; import 'package:term_remote_ctl/features/projects/project_repository.dart'; @@ -26,6 +27,9 @@ void main() { ProviderScope( overrides: [ agentApiClientProvider.overrideWithValue(_FakeAgentApiClient()), + agentBaseUriStorageProvider.overrideWithValue( + _MemoryAgentBaseUriStorage(), + ), projectRepositoryProvider.overrideWithValue(projectRepository), sessionRepositoryProvider.overrideWithValue(sessionRepository), terminalSocketSessionFactoryProvider.overrideWithValue( @@ -136,3 +140,13 @@ class _FakeTerminalSocketTransport implements TerminalSocketTransport { _incoming.add(message); } } + +class _MemoryAgentBaseUriStorage extends AgentBaseUriStorage { + @override + Future read() async { + return null; + } + + @override + Future write(Uri uri) async {} +} diff --git a/apps/mobile_app/test/widget_test.dart b/apps/mobile_app/test/widget_test.dart index 8125bb8..dd677c6 100644 --- a/apps/mobile_app/test/widget_test.dart +++ b/apps/mobile_app/test/widget_test.dart @@ -8,6 +8,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:term_remote_ctl/app/app.dart'; import 'package:term_remote_ctl/core/network/agent_api_client.dart'; +import 'package:term_remote_ctl/core/network/agent_base_uri_storage.dart'; import 'package:term_remote_ctl/core/network/agent_connection_providers.dart'; import 'package:term_remote_ctl/features/presets/preset_command.dart'; import 'package:term_remote_ctl/features/presets/preset_providers.dart'; @@ -947,6 +948,9 @@ Future _pumpApp( agentApiClientProvider.overrideWithValue( apiClient ?? _FakeAgentApiClient(), ), + agentBaseUriStorageProvider.overrideWithValue( + _MemoryAgentBaseUriStorage(), + ), projectRepositoryProvider.overrideWithValue(projectRepository), sessionRepositoryProvider.overrideWithValue(sessionRepository), presetRepositoryProvider.overrideWithValue( @@ -987,6 +991,9 @@ Future _pumpTerminalPage( agentApiClientProvider.overrideWithValue( apiClient ?? _FakeAgentApiClient(), ), + agentBaseUriStorageProvider.overrideWithValue( + _MemoryAgentBaseUriStorage(), + ), presetRepositoryProvider.overrideWithValue( _MemoryPresetRepository(const []), ), @@ -1151,6 +1158,22 @@ class _MemoryPresetRepository extends PresetRepository { } } +class _MemoryAgentBaseUriStorage extends AgentBaseUriStorage { + _MemoryAgentBaseUriStorage([this._storedUri]); + + Uri? _storedUri; + + @override + Future read() async { + return _storedUri; + } + + @override + Future write(Uri uri) async { + _storedUri = uri; + } +} + int _countOccurrences(String source, String pattern) { if (pattern.isEmpty) { return 0;