diff --git a/android/build.gradle b/android/build.gradle index f0ae813..d2ffbff 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -16,6 +16,3 @@ subprojects { tasks.register("clean", Delete) { delete rootProject.buildDir } - - - diff --git a/lib/main.dart b/lib/main.dart index a85331a..7fb9f5f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,13 +1,65 @@ +import 'dart:async'; import 'package:audio_recorder/models/api_response.dart'; +import 'package:audio_recorder/models/language_model.dart'; import 'package:audio_recorder/models/websocket_config.dart'; -import 'package:audio_recorder/pages/login_page.dart'; +// import 'package:audio_recorder/pages/login_page.dart'; +import 'package:audio_recorder/pages/main_page.dart'; +import 'package:audio_recorder/services/storage_service.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +// Create a stream controller to broadcast language changes +final languageChangeController = StreamController.broadcast(); + void main() async { WidgetsFlutterBinding.ensureInitialized(); + + // Make sure storage is ready before initializing + await StorageService.initialize(); + + // Initialize API and WebSocket URLs await OmniAPI.initializeBaseUrl(); await WebSocketConfig.initializeServerUrl(); - runApp(const MyApp()); + + // Initialize language preference + await initializeLanguagePreference(); + + // Listen for language changes and update currentLanguage + languageChangeController.stream.listen((languageCode) { + if (kDebugMode) { + print("LANGUAGE CHANGE DETECTED: $languageCode"); + } + if (Languages.languages.containsKey(languageCode)) { + currentLanguage = Languages.languages[languageCode]!; + } + }); + + runApp(const LanguageAwareApp()); +} + +// Initialize the language model based on stored preference +Future initializeLanguagePreference() async { + // Debug current language preferences + await StorageService.debugLanguagePreferences(); + + final bottomLanguage = await StorageService.getBottomLanguage(); + if (kDebugMode) { + print("MAIN: Initializing language with: $bottomLanguage"); + } + + if (bottomLanguage != null && + Languages.languages.containsKey(bottomLanguage)) { + currentLanguage = Languages.languages[bottomLanguage]!; + if (kDebugMode) { + print("MAIN: Set current language to: ${currentLanguage.name}"); + } + } else { + // Default to English if no language is set or the saved language doesn't exist + currentLanguage = Languages.languages['en']!; + if (kDebugMode) { + print("MAIN: Defaulting to English"); + } + } } class MyApp extends StatelessWidget { @@ -39,7 +91,47 @@ class MyApp extends StatelessWidget { dynamicSchemeVariant: DynamicSchemeVariant.monochrome, ), ), - home: LoginScreen(), + home: TranslationApp(), ); } } + +// Add a stateful wrapper that rebuilds when language changes +class LanguageAwareApp extends StatefulWidget { + const LanguageAwareApp({super.key}); + + @override + State createState() => _LanguageAwareAppState(); +} + +class _LanguageAwareAppState extends State { + late StreamSubscription _languageSubscription; + + @override + void initState() { + super.initState(); + // Listen for language changes and trigger rebuild + _languageSubscription = languageChangeController.stream.listen((_) { + if (mounted) { + setState(() { + // This will rebuild with the new language + if (kDebugMode) { + print( + "APP REBUILD TRIGGERED - CURRENT LANGUAGE: ${currentLanguage.name}"); + } + }); + } + }); + } + + @override + void dispose() { + _languageSubscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return MyApp(); + } +} diff --git a/lib/models/websocket_config.dart b/lib/models/websocket_config.dart index 8ee85a6..12e8fd0 100644 --- a/lib/models/websocket_config.dart +++ b/lib/models/websocket_config.dart @@ -1,8 +1,7 @@ import 'package:audio_recorder/services/storage_service.dart'; class WebSocketConfig { - static String serverUrl = - 'ws://ec2-51-21-138-103.eu-north-1.compute.amazonaws.com:8001/ws/client'; + static String serverUrl = 'ws://jenny.coldpeak.co/ws/client'; static Future initializeServerUrl() async { final savedUrl = await StorageService.getWebsocketUrl(); diff --git a/lib/pages/login_page.dart b/lib/pages/login_page.dart index 2f6a113..c76c698 100644 --- a/lib/pages/login_page.dart +++ b/lib/pages/login_page.dart @@ -265,6 +265,9 @@ class _LoginScreenState extends State { _loginPasswordController.text, ); + // Restore any previously saved language preferences + await StorageService.restoreLanguagePreferences(); + if (mounted) { // Show success message ScaffoldMessenger.of(context).showSnackBar( @@ -309,6 +312,12 @@ class _LoginScreenState extends State { _signupPasswordController.text, ); + // For new users, set their selected signup language as the bottom language preference + await StorageService.saveBottomLanguagePreference(_selectedLanguage); + + // But also restore any other preserved preferences (top language, full sentence mode) + await StorageService.restoreLanguagePreferences(); + if (mounted) { // Show success message ScaffoldMessenger.of(context).showSnackBar( diff --git a/lib/pages/main_page.dart b/lib/pages/main_page.dart index 6253c65..54f355c 100644 --- a/lib/pages/main_page.dart +++ b/lib/pages/main_page.dart @@ -24,20 +24,23 @@ class TranslationAppState extends State { bool isExpandedTop = false; bool isExpandedBottom = false; bool isWebSocketConnected = false; - String topLanguage = 'fr'; - String bottomLanguage = 'it'; + // Default languages set to English for both (will be overridden by stored preferences) + String topLanguage = 'en'; + String bottomLanguage = 'en'; double heightTop = 100; double heightBottom = 100; + // Add fullSentence feature + bool fullSentence = true; + List translatedSentences = []; + String translatedText = ''; + String serverUrl = WebSocketConfig.serverUrl; String? userID = 'ronaldo'; // Changed to nullable String? tokenJWT = ''; // Changed to nullable bool isRecording = false; - String translatedText = ''; - List translatedSentences = []; - RealtimeAudio? audioEngine; List>? _subscriptions; // ignore: unused_field @@ -87,14 +90,54 @@ class TranslationAppState extends State { Future _initializeUser() async { final (token, _, username, language) = await StorageService.getStoredData(); - print(username); + if (kDebugMode) { + print("INIT USER - Username: $username, Saved Language: $language"); + } if (username != null && mounted) { setState(() { userID = username; tokenJWT = token; - bottomLanguage = language ?? 'it'; }); } + + // Load top language preference + final topLangPreference = await StorageService.getTopLanguage(); + if (mounted) { + setState(() { + // Use the saved top language preference, or default to English if not set + topLanguage = topLangPreference ?? 'en'; + if (kDebugMode) { + print("TOP LANGUAGE SET TO: $topLanguage"); + } + }); + } + + // For consistency, also load bottom language preference directly using the new method + final bottomLangPreference = await StorageService.getBottomLanguage(); + if (mounted) { + setState(() { + // Use saved bottom language or default to English if not set + bottomLanguage = bottomLangPreference ?? 'en'; + if (kDebugMode) { + print("BOTTOM LANGUAGE LOADED DIRECTLY: $bottomLanguage"); + } + }); + } + + // Load full sentence mode preference + final savedFullSentenceMode = await StorageService.getFullSentenceMode(); + if (mounted) { + setState(() { + fullSentence = savedFullSentenceMode; + if (kDebugMode) { + print("FULL SENTENCE MODE: $fullSentence"); + } + }); + } + + // Ensure the language preferences are saved + await StorageService.saveBottomLanguagePreference(bottomLanguage); + await StorageService.saveTopLanguagePreference(topLanguage); } @override @@ -204,7 +247,7 @@ class TranslationAppState extends State { void _toggleSectionExpansion(isTop) { setState(() { - print("Toofle"); + print("Toggling expansion - isTop: $isTop"); if (isExpandedTop) { heightTop = MediaQuery.of(context).size.height * 1; heightBottom = MediaQuery.of(context).size.height * 0; @@ -214,8 +257,26 @@ class TranslationAppState extends State { } if (isExpandedTop && isTop) return; if (isExpandedBottom && !isTop) return; + isExpandedTop = isTop; isExpandedBottom = !isTop; + + // Update the currentLanguage based on which section is active + if (isExpandedTop && Languages.languages.containsKey(topLanguage)) { + currentLanguage = Languages.languages[topLanguage]!; + if (kDebugMode) { + print( + "ACTIVE LANGUAGE SWITCHED TO TOP: ${currentLanguage.name} (${currentLanguage.code})"); + } + } else if (isExpandedBottom && + Languages.languages.containsKey(bottomLanguage)) { + currentLanguage = Languages.languages[bottomLanguage]!; + if (kDebugMode) { + print( + "ACTIVE LANGUAGE SWITCHED TO BOTTOM: ${currentLanguage.name} (${currentLanguage.code})"); + } + } + if (isRecording) { _toggleRecording(); } @@ -359,6 +420,7 @@ class TranslationAppState extends State { ? 1.0 : heightTop / (MediaQuery.of(context).size.height * 0.5), child: Column( + mainAxisSize: MainAxisSize.min, children: [ Spacer(), Text( @@ -372,9 +434,10 @@ class TranslationAppState extends State { style: TextStyle( fontSize: 14, fontWeight: FontWeight.w300, color: Colors.black), ), + SizedBox(height: 4), const Icon( SFSymbols.chevron_compact_down, - size: 40, + size: 36, color: Colors.black, ), ], @@ -389,12 +452,14 @@ class TranslationAppState extends State { : heightBottom / (MediaQuery.of(context).size.height * 0.5), child: Column( mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ const Icon( SFSymbols.chevron_compact_up, - size: 40, + size: 36, color: Colors.black, ), + SizedBox(height: 4), Text( Languages.languages[bottomLanguage]!.upText, style: TextStyle( @@ -412,7 +477,7 @@ class TranslationAppState extends State { ); } - // Add this method to the TranslationAppState class + // Fixed version of _showLanguageSelector method void _showLanguageSelector(bool isTop) { showModalBottomSheet( context: context, @@ -477,14 +542,70 @@ class TranslationAppState extends State { language.name, style: TextStyle(color: Colors.white), ), - onTap: () { - setState(() { - if (isTop) { + onTap: () async { + // Store old language for logging + final oldLanguage = + isTop ? topLanguage : bottomLanguage; + + if (isTop) { + setState(() { topLanguage = langCode; - } else { + }); + + // Save the chosen top language preference with debug logging + if (kDebugMode) { + print( + "SAVING TOP LANGUAGE PREFERENCE: $langCode (was: $oldLanguage)"); + } + await StorageService.saveTopLanguagePreference( + langCode); + + // Verify the save worked + String? savedTopLang = + await StorageService.getTopLanguage(); + if (kDebugMode) { + print("VERIFIED SAVED TOP LANGUAGE: $savedTopLang"); + } + + // Update current language if top section is active + if (isExpandedTop && + Languages.languages.containsKey(langCode)) { + currentLanguage = Languages.languages[langCode]!; + if (kDebugMode) { + print( + "UPDATED CURRENT LANGUAGE TO: ${currentLanguage.name} (TOP ACTIVE)"); + } + } + } else { + setState(() { bottomLanguage = langCode; + }); + + // Save the chosen bottom language preference with debug logging + if (kDebugMode) { + print( + "SAVING BOTTOM LANGUAGE PREFERENCE: $langCode (was: $oldLanguage)"); + } + await StorageService.saveBottomLanguagePreference( + langCode); + + // Verify the save worked + String? savedLang = + await StorageService.getBottomLanguage(); + if (kDebugMode) { + print("VERIFIED SAVED LANGUAGE: $savedLang"); + } + + // Update current language if bottom section is active + if (isExpandedBottom && + Languages.languages.containsKey(langCode)) { + currentLanguage = Languages.languages[langCode]!; + if (kDebugMode) { + print( + "UPDATED CURRENT LANGUAGE TO: ${currentLanguage.name} (BOTTOM ACTIVE)"); + } } - }); + } Navigator.pop(context); }, ); @@ -501,7 +622,11 @@ class TranslationAppState extends State { void _processText(String text) { translatedSentences.add(text); setState(() { - translatedText += '$text '; + if (fullSentence) { + translatedText = text; // Replace with full sentence + } else { + translatedText += '$text '; // Append text as before + } }); } @@ -515,8 +640,42 @@ class TranslationAppState extends State { } if (_websocketService != null && (_websocketService?.isWebSocketConnected ?? false)) { - _websocketService?.sendData(_sampleRate, userID ?? 'ronaldo', - isExpandedTop ? topLanguage : bottomLanguage, chunk); + String targetLanguage = isExpandedTop ? topLanguage : bottomLanguage; + + // Save language preference each time we record in that language + // and ensure currentLanguage is up to date + if (isExpandedTop) { + if (kDebugMode) { + print("RECORDING WITH TOP LANGUAGE: $topLanguage"); + } + StorageService.saveTopLanguagePreference(topLanguage); + + // Update current language if needed + if (currentLanguage.code != topLanguage && + Languages.languages.containsKey(topLanguage)) { + currentLanguage = Languages.languages[topLanguage]!; + if (kDebugMode) { + print("UPDATED CURRENT LANGUAGE TO: ${currentLanguage.name}"); + } + } + } else { + if (kDebugMode) { + print("RECORDING WITH BOTTOM LANGUAGE: $bottomLanguage"); + } + StorageService.saveBottomLanguagePreference(bottomLanguage); + + // Update current language if needed + if (currentLanguage.code != bottomLanguage && + Languages.languages.containsKey(bottomLanguage)) { + currentLanguage = Languages.languages[bottomLanguage]!; + if (kDebugMode) { + print("UPDATED CURRENT LANGUAGE TO: ${currentLanguage.name}"); + } + } + } + + _websocketService?.sendData( + _sampleRate, userID ?? 'ronaldo', targetLanguage, chunk); } } @@ -707,6 +866,21 @@ class TranslationAppState extends State { if (!isWebSocketConnected) { await _connectWebSocket(); } + + // Save language preferences at the start of recording + if (isExpandedTop) { + if (kDebugMode) { + print("SAVING TOP LANGUAGE AT START OF RECORDING: $topLanguage"); + } + await StorageService.saveTopLanguagePreference(topLanguage); + } else { + if (kDebugMode) { + print( + "SAVING BOTTOM LANGUAGE AT START OF RECORDING: $bottomLanguage"); + } + await StorageService.saveBottomLanguagePreference(bottomLanguage); + } + setState(() { isRecording = true; }); @@ -740,6 +914,33 @@ class TranslationAppState extends State { labelText: "User ID", ), ), + const SizedBox(height: 15), + SwitchListTile( + title: Text("Full Sentence Mode"), + subtitle: Text("Show complete sentences instead of word-by-word"), + value: fullSentence, + onChanged: (value) async { + setState(() { + fullSentence = value; + }); + + // Save the full sentence mode preference + await StorageService.saveFullSentenceMode(value); + if (kDebugMode) { + print("SAVED FULL SENTENCE MODE: $value"); + } + + Navigator.pop(context); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(fullSentence + ? 'Full sentence mode enabled' + : 'Word-by-word mode enabled'), + duration: Duration(seconds: 2), + ), + ); + }, + ), const SizedBox(height: 20), ElevatedButton( onPressed: () async { @@ -782,6 +983,16 @@ class TranslationAppState extends State { // Save WebSocket URL to storage await StorageService.saveWebsocketUrl(serverUrl); WebSocketConfig.serverUrl = serverUrl; + + // Save both language preferences when settings are updated + await StorageService.saveBottomLanguagePreference(bottomLanguage); + await StorageService.saveTopLanguagePreference(topLanguage); + + if (kDebugMode) { + print( + "SAVED LANGUAGES FROM SETTINGS DIALOG - Top: $topLanguage, Bottom: $bottomLanguage"); + } + Navigator.pop(context); }, child: Text("OK"), diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index 7a484f5..b725b38 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -1,4 +1,6 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:flutter/foundation.dart'; // Added for kDebugMode +import '../main.dart'; // Import for languageChangeController class StorageService { static const _storage = FlutterSecureStorage(); @@ -6,6 +8,10 @@ class StorageService { static const _embeddingsKey = 'embeddings_exist'; static const _usernameKey = 'username'; static const _languageKey = 'language'; + // Added key for top language + static const _topLanguageKey = 'top_language'; + // Added key for full sentence mode + static const _fullSentenceKey = 'full_sentence_mode'; // New keys for credentials static const _savedEmailKey = 'saved_email'; static const _savedPasswordKey = 'saved_password'; @@ -14,16 +20,73 @@ class StorageService { // Constants for WebSocket URL static const _websocketUrlKey = 'websocket_url'; + // Add constants for preserving language preferences during logout + static const _preservedTopLanguageKey = 'preserved_top_language'; + static const _preservedBottomLanguageKey = 'preserved_bottom_language'; + static const _preservedFullSentenceKey = 'preserved_full_sentence_mode'; + + // This method ensures storage is initialized and ready to use + static Future initialize() async { + try { + // Test storage by reading a value + await _storage.read(key: _languageKey); + if (kDebugMode) { + print("STORAGE: Successfully initialized"); + } + } catch (e) { + if (kDebugMode) { + print("STORAGE: Error initializing - $e"); + } + // Handle initialization errors + // Most platforms don't require special initialization, + // but this method provides a hook for future needs + } + } + static Future saveToken( String username, String token, bool embeddings, String language, ) async { + // Save authentication info await _storage.write(key: _tokenKey, value: token); await _storage.write(key: _embeddingsKey, value: embeddings.toString()); await _storage.write(key: _usernameKey, value: username); - await _storage.write(key: _languageKey, value: language); + + if (kDebugMode) { + print("STORAGE: Token saved for user: $username"); + print( + "STORAGE: Checking existing language preferences before setting defaults"); + } + + // Check if we already have language preferences saved before overwriting + final existingBottomLang = await _storage.read(key: _languageKey); + + // Only set the bottom language if it's not already set + if (existingBottomLang == null) { + if (kDebugMode) { + print( + "STORAGE: No existing bottom language found, setting to: $language"); + } + await saveBottomLanguagePreference(language); + } else if (kDebugMode) { + print("STORAGE: Keeping existing bottom language: $existingBottomLang"); + } + + // Also check for existing top language preference + final existingTopLang = await _storage.read(key: _topLanguageKey); + + // Set default top language to English if not already set + if (existingTopLang == null) { + if (kDebugMode) { + print( + "STORAGE: No existing top language found, setting to default: en"); + } + await saveTopLanguagePreference('en'); + } else if (kDebugMode) { + print("STORAGE: Keeping existing top language: $existingTopLang"); + } } static Future getUsername() async { @@ -31,7 +94,83 @@ class StorageService { } static Future getLanguage() async { - return await _storage.read(key: _languageKey); + final lang = await _storage.read(key: _languageKey); + if (kDebugMode) { + print("STORAGE: Retrieved language: $lang from key $_languageKey"); + } + return lang; + } + + // Added method to make it more explicit that this is for bottom language + static Future getBottomLanguage() async { + final lang = await _storage.read(key: _languageKey); + if (kDebugMode) { + print("STORAGE: Retrieved bottom language: $lang from key $_languageKey"); + } + return lang; + } + + // Get top language preference + static Future getTopLanguage() async { + final lang = await _storage.read(key: _topLanguageKey); + if (kDebugMode) { + print("STORAGE: Retrieved top language: $lang from key $_topLanguageKey"); + } + return lang; + } + + // Updated method name for clarity and consistency + static Future saveBottomLanguagePreference(String language) async { + if (kDebugMode) { + print( + "STORAGE: Saving bottom language preference: $language to key $_languageKey"); + } + await _storage.write(key: _languageKey, value: language); + + // Verify the save + final saved = await _storage.read(key: _languageKey); + if (kDebugMode) { + print("STORAGE: Verified saved bottom language: $saved"); + } + + // Notify listeners that bottom language has changed + languageChangeController.add(language); + } + + // This method is now deprecated - use saveBottomLanguagePreference instead + @Deprecated('Use saveBottomLanguagePreference instead for clarity') + static Future saveLanguagePreference(String language) async { + return saveBottomLanguagePreference(language); + } + + // Add method to save top language preference + static Future saveTopLanguagePreference(String language) async { + if (kDebugMode) { + print( + "STORAGE: Saving top language preference: $language to key $_topLanguageKey"); + } + await _storage.write(key: _topLanguageKey, value: language); + + // Verify the save + final saved = await _storage.read(key: _topLanguageKey); + if (kDebugMode) { + print("STORAGE: Verified saved top language: $saved"); + } + + // Only notify listeners if we're currently using the top language + // This would need to be enhanced with knowledge of which section is active + // but for now we'll just notify so the language model stays updated + languageChangeController.add(language); + } + + // Add methods for fullSentence mode + static Future saveFullSentenceMode(bool enabled) async { + await _storage.write(key: _fullSentenceKey, value: enabled.toString()); + } + + static Future getFullSentenceMode() async { + final value = await _storage.read(key: _fullSentenceKey); + return value == 'true'; // Default to false if not set } static Future<(String?, String?, String?, String?)> getStoredData() async { @@ -44,10 +183,54 @@ class StorageService { } static Future deleteToken() async { + // Before deleting, save the current language preferences + final topLanguage = await _storage.read(key: _topLanguageKey); + final bottomLanguage = await _storage.read(key: _languageKey); + final fullSentenceMode = await _storage.read(key: _fullSentenceKey); + + if (topLanguage != null) { + await _storage.write(key: _preservedTopLanguageKey, value: topLanguage); + } + + if (bottomLanguage != null) { + await _storage.write( + key: _preservedBottomLanguageKey, value: bottomLanguage); + } + + if (fullSentenceMode != null) { + await _storage.write( + key: _preservedFullSentenceKey, value: fullSentenceMode); + } + + // Now delete the authentication related data await _storage.delete(key: _tokenKey); await _storage.delete(key: _embeddingsKey); await _storage.delete(key: _usernameKey); await _storage.delete(key: _languageKey); + await _storage.delete(key: _topLanguageKey); + await _storage.delete(key: _fullSentenceKey); + } + + // Restore previously saved language preferences when logging in + static Future restoreLanguagePreferences() async { + final preservedTopLanguage = + await _storage.read(key: _preservedTopLanguageKey); + final preservedBottomLanguage = + await _storage.read(key: _preservedBottomLanguageKey); + final preservedFullSentenceMode = + await _storage.read(key: _preservedFullSentenceKey); + + if (preservedTopLanguage != null) { + await saveTopLanguagePreference(preservedTopLanguage); + } + + if (preservedBottomLanguage != null) { + await saveBottomLanguagePreference(preservedBottomLanguage); + } + + if (preservedFullSentenceMode != null) { + await saveFullSentenceMode(preservedFullSentenceMode == 'true'); + } } // New functions for handling credentials @@ -86,4 +269,22 @@ class StorageService { static Future getWebsocketUrl() async { return await _storage.read(key: _websocketUrlKey); } + + // Add a utility method to debug the current language preferences + static Future debugLanguagePreferences() async { + if (kDebugMode) { + final bottomLang = await getBottomLanguage(); + final topLang = await getTopLanguage(); + final preservedBottom = + await _storage.read(key: _preservedBottomLanguageKey); + final preservedTop = await _storage.read(key: _preservedTopLanguageKey); + + print("==== LANGUAGE PREFERENCES DEBUG ===="); + print("Current Bottom Language: $bottomLang"); + print("Current Top Language: $topLang"); + print("Preserved Bottom Language: $preservedBottom"); + print("Preserved Top Language: $preservedTop"); + print("==================================="); + } + } } diff --git a/pubspec.lock b/pubspec.lock index a170f4b..329f4f4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" audio_streamer: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -308,10 +308,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -617,10 +617,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" web: dependency: transitive description: