diff --git a/lib/constants/storage_keys.dart b/lib/constants/storage_keys.dart index 2eeb77a..387d13e 100644 --- a/lib/constants/storage_keys.dart +++ b/lib/constants/storage_keys.dart @@ -1 +1 @@ -const STARTUP_PAGE_KEY = 'startup_page'; +const CACHE_KEY = '_cache'; diff --git a/lib/constants/values.dart b/lib/constants/values.dart index aadf02e..4aa3605 100644 --- a/lib/constants/values.dart +++ b/lib/constants/values.dart @@ -7,3 +7,4 @@ const PHOTO_SHOW_AFTER_CREATION_DURATION = Duration(milliseconds: 500); final UnmodifiableSetView DEFAULT_ZOOM_LEVELS = UnmodifiableSetView({0.6, 1, 2, 5, 10, 20, 50, 100}); const CALENDAR_DATE_IN_MAX_DELAY = Duration(milliseconds: 500); +const CACHE_INVALIDATION_DURATION = Duration(days: 7); diff --git a/lib/main.dart b/lib/main.dart index f274027..5d24f20 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,6 +13,7 @@ import 'package:quid_faciam_hodie/screens/server_loading_screen.dart'; import 'package:quid_faciam_hodie/screens/settings_screen.dart'; import 'package:quid_faciam_hodie/screens/timeline_screen.dart'; import 'package:quid_faciam_hodie/screens/welcome_screen.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; import 'managers/global_values_manager.dart'; import 'models/memories.dart'; @@ -43,6 +44,27 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { final memories = Memories(); + @override + void initState() { + super.initState(); + + watchAuthenticationStatus(); + } + + Future watchAuthenticationStatus() async { + await GlobalValuesManager.waitForServerInitialization(); + + Supabase.instance.client.auth.onAuthStateChange((event, session) { + switch (event) { + case AuthChangeEvent.signedIn: + memories.refresh(); + break; + default: + break; + } + }); + } + @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( diff --git a/lib/managers/authentication_manager.dart b/lib/managers/authentication_manager.dart index 3294d4e..73f4b62 100644 --- a/lib/managers/authentication_manager.dart +++ b/lib/managers/authentication_manager.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:quid_faciam_hodie/extensions/snackbar.dart'; -import 'package:quid_faciam_hodie/screens/login_screen.dart'; import 'package:quid_faciam_hodie/screens/main_screen.dart'; +import 'package:quid_faciam_hodie/screens/welcome_screen.dart'; import 'package:supabase/supabase.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; @@ -10,7 +10,7 @@ class AuthState extends SupabaseAuthState { void onUnauthenticated() { if (mounted) { Navigator.of(context) - .pushNamedAndRemoveUntil(LoginScreen.ID, (route) => false); + .pushNamedAndRemoveUntil(WelcomeScreen.ID, (route) => false); } } diff --git a/lib/managers/cache_manager.dart b/lib/managers/cache_manager.dart new file mode 100644 index 0000000..69baaaa --- /dev/null +++ b/lib/managers/cache_manager.dart @@ -0,0 +1,55 @@ +import 'dart:convert'; + +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:quid_faciam_hodie/constants/storage_keys.dart'; +import 'package:quid_faciam_hodie/constants/values.dart'; + +const storage = FlutterSecureStorage(); + +class CacheManager { + static _createKey(final String key) => '$CACHE_KEY/$key'; + + static Future isCacheValid(final String key) async { + final cacheKey = _createKey(key); + final existingEntry = await storage.read(key: cacheKey); + + if (existingEntry != null) { + final entry = jsonDecode(existingEntry); + final DateTime creationDate = DateTime.parse(entry['creationDate']); + + // Check if the entry is still valid using CACHE_INVALIDATION_DURATION as the validity duration. + return DateTime.now().difference(creationDate) < + CACHE_INVALIDATION_DURATION; + } + + return false; + } + + static Future set(final String key, final String data) async { + final cacheKey = _createKey(key); + final cacheEntry = { + 'creationDate': DateTime.now().toIso8601String(), + 'data': data, + }; + + return storage.write( + key: cacheKey, + value: json.encode({ + key: cacheEntry, + }), + ); + } + + static Future get(final String key) async { + final cacheKey = _createKey(key); + final existingEntry = await storage.read(key: cacheKey); + + if (existingEntry == null) { + return null; + } + + final entry = jsonDecode(existingEntry); + + return entry['data']; + } +} diff --git a/lib/managers/file_manager.dart b/lib/managers/file_manager.dart index 60917c9..e40359a 100644 --- a/lib/managers/file_manager.dart +++ b/lib/managers/file_manager.dart @@ -1,20 +1,20 @@ import 'dart:io'; import 'dart:typed_data'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:quid_faciam_hodie/enums.dart'; import 'package:quid_faciam_hodie/foreign_types/memory.dart'; +import 'package:quid_faciam_hodie/managers/cache_manager.dart'; import 'package:quid_faciam_hodie/managers/global_values_manager.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:uuid/uuid.dart'; const uuid = Uuid(); +const storage = FlutterSecureStorage(); final supabase = Supabase.instance.client; class FileManager { - static Map fileCache = {}; - static Future getMemoryMetadata(final String id) async { await GlobalValuesManager.waitForServerInitialization(); @@ -56,51 +56,31 @@ class FileManager { } } - static Future getLastFile(final User user) async { - final response = await supabase - .from('memories') - .select() - .eq('user_id', user.id) - .order('created_at', ascending: false) - .limit(1) - .single() - .execute(); - - if (response.data == null) { - return null; - } - - final memory = response.data; - final location = memory['location']; - final memoryType = - location.split('.').last == 'jpg' ? MemoryType.photo : MemoryType.video; - - try { - final file = await _getFileData('memories', location); - - return [file, memoryType]; - } catch (error) { - return null; - } - } - - static Future _getFileData(final String table, final String path, - {final bool disableCache = false}) async { - final key = '$table:$path'; - - if (!disableCache && fileCache.containsKey(key)) { - return fileCache[key]!; - } - + static Future _downloadFileData( + final String table, final String path) async { final response = await supabase.storage.from(table).download(path); if (response.error != null) { throw Exception('Error downloading file: ${response.error!.message}'); } - final data = response.data!; + return response.data!; + } - fileCache[key] = data; + static Future _getFileData(final String table, final String path, + {final bool disableCache = false}) async { + final key = '$table:$path'; + + // Check cache + if (!disableCache && await CacheManager.isCacheValid(key)) { + final data = (await CacheManager.get(key))!; + return Uint8List.fromList(data.codeUnits); + } + + final data = await _downloadFileData(table, path); + + final cacheData = String.fromCharCodes(data); + await CacheManager.set(key, cacheData); return data; } diff --git a/lib/models/memories.dart b/lib/models/memories.dart index 3bc3db3..246915b 100644 --- a/lib/models/memories.dart +++ b/lib/models/memories.dart @@ -68,6 +68,13 @@ class Memories extends PropertyChangeNotifier { .subscribe(); } + Future refresh() async { + memories.clear(); + _serverSubscription?.unsubscribe(); + + setIsInitialized(false); + } + Future _onServerUpdate( final SupabaseRealtimePayload response, ) async { diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart index 0033392..04d0261 100644 --- a/lib/screens/login_screen.dart +++ b/lib/screens/login_screen.dart @@ -73,7 +73,6 @@ class _LoginScreenState extends AuthState with Loadable { message: localizations.loginScreenLoginFailed, ); - emailController.clear(); passwordController.clear(); } return; diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index e82107e..b299e0c 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_platform_widgets/flutter_platform_widgets.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:intl/intl.dart'; import 'package:quid_faciam_hodie/constants/spacing.dart'; import 'package:quid_faciam_hodie/extensions/snackbar.dart'; @@ -14,6 +15,8 @@ import 'package:supabase_flutter/supabase_flutter.dart'; final supabase = Supabase.instance.client; +const storage = FlutterSecureStorage(); + class SettingsScreen extends StatefulWidget { static const ID = '/settings';