diff --git a/README.md b/README.md index 142689c9..de3a0111 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ The aim is that this app can work with any tbdex liquidity node, discoverying th * Install Hermit https://cashapp.github.io/hermit/ (on macos you can run `brew install hermit` and then `hermit shell-hooks`) * Ensure you have a mobile app simulator handy (XCode on macOS and runing the Simulator app will do for example) -* Run `flutter run` from this project to build and start the app in the simulator +* Run `flutter run` from this project to build and start the app in the simulator. Use `flutter run --dart-define=DEV_PFI=your_did_string` to run against a local tbdex liquidity node at dev time. diff --git a/frontend/ios/Podfile.lock b/frontend/ios/Podfile.lock index 8069c477..698b6081 100644 --- a/frontend/ios/Podfile.lock +++ b/frontend/ios/Podfile.lock @@ -5,6 +5,9 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS - web5_flutter (0.0.1): - Flutter - webview_flutter_wkwebview (0.0.1): @@ -14,6 +17,7 @@ DEPENDENCIES: - Flutter (from `Flutter`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - web5_flutter (from `.symlinks/plugins/web5_flutter/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) @@ -24,6 +28,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/flutter_secure_storage/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" web5_flutter: :path: ".symlinks/plugins/web5_flutter/ios" webview_flutter_wkwebview: @@ -33,6 +39,7 @@ SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 web5_flutter: 9d0f3466d7bef47a35cf92aca8dbb30465c63d2e webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a diff --git a/frontend/lib/features/pfis/pfi_providers.dart b/frontend/lib/features/pfis/pfi_providers.dart index d69ec83b..513e4eea 100644 --- a/frontend/lib/features/pfis/pfi_providers.dart +++ b/frontend/lib/features/pfis/pfi_providers.dart @@ -1,22 +1,88 @@ +import 'dart:convert'; +import 'package:flutter/foundation.dart'; import 'package:flutter_starter/features/pfis/pfi.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:http/http.dart' as http; +import 'package:shared_preferences/shared_preferences.dart'; -final pfisProvider = Provider>( - (ref) => [ - Pfi( - id: 'prototype', - name: 'Prototype', - didUri: 'did:dht:74hg1efatndi8enx3e4z6c4u8ieh1xfkyay4ntg4dg1w6risu35y', - ), - Pfi( - id: 'africa', - name: 'Africa', - didUri: 'coming soon...', - ), - Pfi( - id: 'mexico', - name: 'Mexico', - didUri: 'coming soon...', - ), - ], -); +final pfisProvider = FutureProvider>((ref) async { + const url = 'https://raw.githubusercontent.com/TBD54566975/pfi-providers-data/main/pfis.json'; + const cacheKey = 'pfi_cache'; + + + // fall back to a dev PFI if passed on command line like: flutter run --dart-define=DEV_PFI=your_did_string + const devPfi = String.fromEnvironment('DEV_PFI'); + if (devPfi != '' && devPfi != null) { + return [ + Pfi( + id: 'dev', + name: 'Dev PFI', + didUri: devPfi, + ), + ]; + } + + // First, try loading from cache + List pfis = await _loadFromCache(cacheKey); + if (pfis.isNotEmpty) { + // If cache has data, return it first + // Then, asynchronously refresh the cache + _refreshCache(url, cacheKey); + return pfis; + } else { + // If cache is empty, fetch from the URL + return await _fetchFromURL(url, cacheKey); + } +}); + +Future> _loadFromCache(String cacheKey) async { + final prefs = await SharedPreferences.getInstance(); + String? cachedData = prefs.getString(cacheKey); + + if (cachedData != null) { + return (json.decode(cachedData) as List).map((data) { + return Pfi( + id: data['id'] as String, + name: data['name'] as String, + didUri: data['didUri'] as String, + ); + }).toList(); + } else { + return []; + } +} + +Future> _fetchFromURL(String url, String cacheKey) async { + try { + final response = await http.get(Uri.parse(url)); + if (response.statusCode == 200) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(cacheKey, response.body); + + return (json.decode(response.body) as List).map((data) { + return Pfi( + id: data['id'] as String, + name: data['name'] as String, + didUri: data['didUri'] as String, + ); + }).toList(); + } + } catch (e) { + // Handle the error or return an empty list + debugPrint(e.toString()); + } + return []; +} + +Future _refreshCache(String url, String cacheKey) async { + try { + final response = await http.get(Uri.parse(url)); + if (response.statusCode == 200) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(cacheKey, response.body); + } + } catch (e) { + // Handle the error silently as this is a background refresh + debugPrint(e.toString()); + } +} diff --git a/frontend/lib/features/pfis/pfis_page.dart b/frontend/lib/features/pfis/pfis_page.dart index 1f5c5467..19333ccb 100644 --- a/frontend/lib/features/pfis/pfis_page.dart +++ b/frontend/lib/features/pfis/pfis_page.dart @@ -9,26 +9,32 @@ class PfisPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final pfis = ref.watch(pfisProvider); + // Watch the provider and get the AsyncValue object + return Scaffold( appBar: AppBar(title: Text(Loc.of(context).selectYourRegion)), - body: ListView( - children: [ - ...pfis.map( - (pfi) => ListTile( - title: Text(pfi.name), - subtitle: Text(pfi.didUri), - trailing: const Icon(Icons.chevron_right), - onTap: () async { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => PfiVerificationPage(pfi: pfi), - ), - ); - }, - ), - ) - ], + body: ref.watch(pfisProvider).when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center(child: Text('Error: $error')), + data: (pfis) { + // Build the list when data is available + return ListView( + children: pfis.map( + (pfi) => ListTile( + title: Text(pfi.name), + subtitle: Text(pfi.didUri), + trailing: const Icon(Icons.chevron_right), + onTap: () async { + Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => PfiVerificationPage(pfi: pfi), + ), + ); + }, + ), + ).toList(), + ); + }, ), ); } diff --git a/frontend/pubspec.lock b/frontend/pubspec.lock index 3153db14..be63ad1d 100644 --- a/frontend/pubspec.lock +++ b/frontend/pubspec.lock @@ -89,6 +89,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter: dependency: "direct main" description: flutter @@ -349,6 +357,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.9" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + 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: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c" + url: "https://pub.dev" + source: hosted + version: "2.3.5" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + url: "https://pub.dev" + source: hosted + version: "2.3.2" sky_engine: dependency: transitive description: flutter @@ -502,4 +566,4 @@ packages: version: "1.0.4" sdks: dart: ">=3.2.3 <4.0.0" - flutter: ">=3.10.0" + flutter: ">=3.16.0" diff --git a/frontend/pubspec.yaml b/frontend/pubspec.yaml index a6f2a1b6..6a33dfbd 100644 --- a/frontend/pubspec.yaml +++ b/frontend/pubspec.yaml @@ -25,6 +25,7 @@ dependencies: http: ^1.1.2 intl: ^0.18.1 webview_flutter: ^4.4.2 + shared_preferences: ^2.2.2 dev_dependencies: flutter_test: