diff --git a/apps/AppWithWearable/Tasks.md b/apps/AppWithWearable/Tasks.md index 76ec7e0e4f..cb92c0c189 100644 --- a/apps/AppWithWearable/Tasks.md +++ b/apps/AppWithWearable/Tasks.md @@ -1,20 +1,57 @@ ### Tasks -- [ ] It shouldn't require to reconnect every time that you open the app, it should load `ConnectDeviceWidget` and in there listen/reconnect to the device. -- [ ] Device disconnected, display dialog to user asking to reconnect, or take the user back to `find_devices` page. -- [ ] Settings bottom sheet, improve way of handling `***` blurring of api keys, as if you save it while is blurred with *, it sets the key to that value, and you have to set them again -- [ ] [iOS] memories and chat page on the bottom do not have the blurred colors pattern, but plain primary color -- [ ] Improve structured memory results performance by sending n previous memories as part of the structuring but as context, not as part of the structure, so that if there's some reference to a person, and then you use a pronoun, the LLM understands what you are referring to. +- [ ] It shouldn't require to reconnect every time that you open the app, it should + load `ConnectDeviceWidget` and in there listen/reconnect to the device. +- [ ] Device disconnected, display dialog to user asking to reconnect, or take the user back + to `find_devices` page. +- [ ] Settings bottom sheet, improve way of handling `***` blurring of api keys, as if you save it + while is blurred with *, it sets the key to that value, and you have to set them again +- [ ] [iOS] memories and chat page on the bottom do not have the blurred colors pattern, but plain + primary color +- [ ] Improve structured memory results performance by sending n previous memories as part of the + structuring but as context, not as part of the structure, so that if there's some reference to a + person, and then you use a pronoun, the LLM understands what you are referring to. - [ ] Migrate MemoryRecord from SharedPreferences to sqlite - [ ] Implement [similarity search](https://www.pinecone.io/learn/vector-similarity/) locally - - [ ] Use from the AppStandalone `_ragContext` function as a baseline for creating the query embedding. - - [ ] When a memory is created, compute the vector embedding and store it locally. - - [ ] When the user sends a question in the chat, extract from the AppStandalone the `function_calling` that determines if the message requires context, if that's the case, retrieve the top 10 most similar vectors ~~ For an initial version we can read all memories from sqlite or SharedPreferences, and compute the formula between the query and each vector. - - [ ] Use that as context, and ask to the LLM. Retrieve the prompt from the AppStandalone. + - [ ] Use from the AppStandalone `_ragContext` function as a baseline for creating the query + embedding. + - [ ] When a memory is created, compute the vector embedding and store it locally. + - [ ] When the user sends a question in the chat, extract from the AppStandalone + the `function_calling` that determines if the message requires context, if that's the case, + retrieve the top 10 most similar vectors ~~ For an initial version we can read all memories + from sqlite or SharedPreferences, and compute the formula between the query and each vector. + - [ ] Use that as context, and ask to the LLM. Retrieve the prompt from the AppStandalone. + - [X] ----- + - [ ] Another option is to use one of the vector db libraries available for + dart https://github.com/FastCodeAI/DVDB or https://pub.dev/packages/chromadb - [ ] Settings Deepgram + openAI key are forced to be set -- [ ] In case an API key fails, either Deepgram WebSocket connection fails, or GPT requests, let the user know the error message, either has no more credits, api key is invalid, etc. -- [ ] Improve connected device page UI, including transcription text, and when memory creates after 30 seconds, let the user know -- [ ] Structure the memory asking JSON output `{"title", "summary"}`, in that way we can have better parsed data. -- [ ] Test/Implement [speaker diarization](https://developers.deepgram.com/docs/diarization) to recognize multiple speakers in transcription, use that for better context when creating the structured memory. -- [ ] Better `AppWithWerable` folders structure. -- [ ] Define flutter code style rules. \ No newline at end of file +- [ ] In case an API key fails, either Deepgram WebSocket connection fails, or GPT requests, let the + user know the error message, either has no more credits, api key is invalid, etc. +- [ ] Improve connected device page UI, including transcription text, and when memory creates after + 30 seconds, let the user know +- [ ] Structure the memory asking JSON output `{"title", "summary"}`, in that way we can have better + parsed data. +- [ ] Test/Implement [speaker diarization](https://developers.deepgram.com/docs/diarization) to + recognize multiple speakers in transcription, use that for better context when creating the + structured memory. +- [x] Better `AppWithWerable` folders structure. +- [ ] Define flutter code style rules. +- [ ] Include documentation on how to run `AppWithWearable`. + +--- + +- [x] Multilanguage option, implement settings selector, and use that for the deepgram websocket + creation +- [ ] Option for storing your transcripts somewhere in the cloud, user inputs their own GCP storage + bucket + auth key, and the files are uploaded there + a reference is stored in the MemoryRecord + object. + +- [ ] ~~ (Idea) Detect a keyword or special order e.g. "Hey Friend" (but not so generic) and + triggers a prompt execution + response. This would require a few hardware updates (could also be a + button on the device), and it's way bigger than it seems. +- [ ] ~~ (Idea) Store the location at which the memory was created, and have saved places, like "at + Home you were chatting about x and y" +- [ ] ~~ (Idea) Speaker detection, use something like the python + library [librosa](https://github.com/librosa/librosa), so that friend recognizes when is you the + one speaking and creates memories better considering that. Maybe even later learns to recognize + other people. \ No newline at end of file diff --git a/apps/AppWithWearable/integration_test/test.dart b/apps/AppWithWearable/integration_test/test.dart deleted file mode 100644 index 3047582d10..0000000000 --- a/apps/AppWithWearable/integration_test/test.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; -import 'package:friend_private/flutter_flow/flutter_flow_icon_button.dart'; -import 'package:friend_private/flutter_flow/flutter_flow_widgets.dart'; -import 'package:friend_private/index.dart'; -import 'package:friend_private/main.dart'; -import 'package:friend_private/flutter_flow/flutter_flow_util.dart'; - -import 'package:provider/provider.dart'; - -void main() async { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - setUpAll(() async { - _overrideOnError(); - }); - - setUp(() async { - FFAppState.reset(); - final appState = FFAppState(); - await appState.initializePersistedState(); - }); - - testWidgets('newTest', (WidgetTester tester) async { - // await FirebaseAuth.instance.signInWithEmailAndPassword( - // email: 'kodjima33+sama@gmail.com', password: '123456'); - await tester.pumpWidget(ChangeNotifierProvider( - create: (context) => FFAppState(), - child: MyApp(), - )); - - await tester.tap(find.byKey(ValueKey('UNDEFINED'))); - await tester.pumpAndSettle(Duration(milliseconds: 5000)); - await tester.longPress(find.byKey(ValueKey('UNDEFINED'))); - await tester.pumpAndSettle(Duration(milliseconds: 1000)); - expect(find.text('Useless Memory'), findsWidgets); - }); -} - -// There are certain types of errors that can happen during tests but -// should not break the test. -void _overrideOnError() { - final originalOnError = FlutterError.onError!; - FlutterError.onError = (errorDetails) { - if (_shouldIgnoreError(errorDetails.toString())) { - return; - } - originalOnError(errorDetails); - }; -} - -bool _shouldIgnoreError(String error) { - // It can fail to decode some SVGs - this should not break the test. - if (error.contains('ImageCodecException')) { - return true; - } - // Overflows happen all over the place, - // but they should not break tests. - if (error.contains('overflowed by')) { - return true; - } - // Sometimes some images fail to load, it generally does not break the test. - if (error.contains('No host specified in URI') || - error.contains('EXCEPTION CAUGHT BY IMAGE RESOURCE SERVICE')) { - return true; - } - // These errors should be avoided, but they should not break the test. - if (error.contains('setState() called after dispose()')) { - return true; - } - - return false; -} diff --git a/apps/AppWithWearable/lib/actions/actions.dart b/apps/AppWithWearable/lib/actions/actions.dart index 616148dc26..396d9612bc 100644 --- a/apps/AppWithWearable/lib/actions/actions.dart +++ b/apps/AppWithWearable/lib/actions/actions.dart @@ -16,6 +16,7 @@ Future memoryCreationBlock(String rawMemory) async { debugPrint('Structured Memory: $structuredMemory'); if (structuredMemory.contains("N/A")) { await saveFailureMemory(rawMemory, structuredMemory); + changeAppStateMemoryCreating(); } else { await finalizeMemoryRecord(rawMemory, structuredMemory); } diff --git a/apps/AppWithWearable/lib/app_constants.dart b/apps/AppWithWearable/lib/app_constants.dart deleted file mode 100644 index a550eef62a..0000000000 --- a/apps/AppWithWearable/lib/app_constants.dart +++ /dev/null @@ -1,8 +0,0 @@ - - -abstract class FFAppConstants { - static const String testuseremail = 'kodjima33@gmail.com'; - static const String testpassword = '123456'; - static const String AlwaysShow = 'Show'; - static const String NA = 'N/A'; -} diff --git a/apps/AppWithWearable/lib/app_state.dart b/apps/AppWithWearable/lib/app_state.dart index facd7db5ab..b443a4fa6c 100644 --- a/apps/AppWithWearable/lib/app_state.dart +++ b/apps/AppWithWearable/lib/app_state.dart @@ -367,83 +367,6 @@ class FFAppState extends ChangeNotifier { set memoryCreationProcessing(bool _value) { _memoryCreationProcessing = _value; } - - List _testlist = []; - - List get testlist => _testlist; - - set testlist(List _value) { - _testlist = _value; - } - - void addToTestlist(String _value) { - _testlist.add(_value); - } - - void removeFromTestlist(String _value) { - _testlist.remove(_value); - } - - void removeAtIndexFromTestlist(int _index) { - _testlist.removeAt(_index); - } - - void updateTestlistAtIndex( - int _index, - String Function(String) updateFn, - ) { - _testlist[_index] = updateFn(_testlist[_index]); - } - - void insertAtIndexInTestlist(int _index, String _value) { - _testlist.insert(_index, _value); - } - - String _wav = ''; - - String get wav => _wav; - - set wav(String _value) { - _wav = _value; - } - - List _whispers = []; - - List get whispers => _whispers; - - set whispers(List _value) { - _whispers = _value; - } - - void addToWhispers(String _value) { - _whispers.add(_value); - } - - void removeFromWhispers(String _value) { - _whispers.remove(_value); - } - - void removeAtIndexFromWhispers(int _index) { - _whispers.removeAt(_index); - } - - void updateWhispersAtIndexWithFunction( - int _index, - String Function(String) updateFn, - ) { - _whispers[_index] = updateFn(_whispers[_index]); - } - - void updateWhispersAtIndex( - int _index, - String _value, - ) { - _whispers[_index] = _value; - } - - void insertAtIndexInWhispers(int _index, String _value) { - _whispers.insert(_index, _value); - } } void _safeInit(Function() initializeField) { diff --git a/apps/AppWithWearable/lib/backend/api_requests/api_calls.dart b/apps/AppWithWearable/lib/backend/api_requests/api_calls.dart index 8c6db636ff..6bf6cfe0aa 100644 --- a/apps/AppWithWearable/lib/backend/api_requests/api_calls.dart +++ b/apps/AppWithWearable/lib/backend/api_requests/api_calls.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:friend_private/backend/storage/memories.dart'; +import 'package:friend_private/backend/utils.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; @@ -96,8 +97,12 @@ Future executeGptPrompt(String? prompt) async { } Future generateTitleAndSummaryForMemory(String? memory) async { + final prefs = await SharedPreferences.getInstance(); + final languageCode = prefs.getString('recordingsLanguage') ?? 'en'; + final language = availableLanguagesByCode[languageCode] ?? 'English'; + var prompt = ''' - Generate a title and a summary for the following recording chunk of a conversation. + ${languageCode == 'en' ? 'Generate a title and a summary for the following recording chunk of a conversation.' : 'Generate a title and a summary in English for the following recording chunk of a conversation that was performed in $language.'} For the title, use the most important topic or the most important action-item in the conversation. For the summary, Identify the specific details in the conversation and specific facts that are important to remember or action-items in very concise short points in second person (use bullet points). diff --git a/apps/AppWithWearable/lib/backend/utils.dart b/apps/AppWithWearable/lib/backend/utils.dart new file mode 100644 index 0000000000..e1d0563b96 --- /dev/null +++ b/apps/AppWithWearable/lib/backend/utils.dart @@ -0,0 +1,75 @@ +final Map availableLanguages = { + 'Bulgarian': 'bg', + 'Catalan': 'ca', + 'Czech': 'cs', + 'Danish': 'da', + 'Dutch': 'nl', + 'English': 'en', + 'Estonian': 'et', + 'Finnish': 'fi', + 'Flemish': 'nl-BE', + 'French': 'fr', + 'French (Canada)': 'fr-CA', + 'German': 'de', + 'German (Switzerland)': 'de-CH', + 'Greek': 'el', + 'Hindi': 'hi', + 'Hungarian': 'hu', + 'Indonesian': 'id', + 'Italian': 'it', + 'Japanese': 'ja', + 'Korean': 'ko', + 'Latvian': 'lv', + 'Lithuanian': 'lt', + 'Malay': 'ms', + 'Norwegian': 'no', + 'Polish': 'pl', + 'Portuguese': 'pt', + 'Romanian': 'ro', + 'Russian': 'ru', + 'Slovak': 'sk', + 'Spanish': 'es', + 'Swedish': 'sv', + 'Thai': 'th', + 'Turkish': 'tr', + 'Ukrainian': 'uk', + 'Vietnamese': 'vi', +}; + +final Map availableLanguagesByCode = { + 'en': 'English', + 'bg': 'Bulgarian', + 'ca': 'Catalan', + 'cs': 'Czech', + 'da': 'Danish', + 'nl': 'Dutch', + 'et': 'Estonian', + 'fi': 'Finnish', + 'nl-BE': 'Flemish', + 'fr': 'French', + 'fr-CA': 'French (Canada)', + 'de': 'German', + 'de-CH': 'German (Switzerland)', + 'el': 'Greek', + 'hi': 'Hindi', + 'hu': 'Hungarian', + 'id': 'Indonesian', + 'it': 'Italian', + 'ja': 'Japanese', + 'ko': 'Korean', + 'lv': 'Latvian', + 'lt': 'Lithuanian', + 'ms': 'Malay', + 'no': 'Norwegian', + 'pl': 'Polish', + 'pt': 'Portuguese', + 'ro': 'Romanian', + 'ru': 'Russian', + 'sk': 'Slovak', + 'es': 'Spanish', + 'sv': 'Swedish', + 'th': 'Thai', + 'tr': 'Turkish', + 'uk': 'Ukrainian', + 'vi': 'Vietnamese', +}; diff --git a/apps/AppWithWearable/lib/components/items/item_permission/item_permission_model.dart b/apps/AppWithWearable/lib/components/items/item_permission/item_permission_model.dart deleted file mode 100644 index af3f61fa53..0000000000 --- a/apps/AppWithWearable/lib/components/items/item_permission/item_permission_model.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; - -import '/flutter_flow/flutter_flow_util.dart'; -import 'item_permission_widget.dart' show ItemPermissionWidget; - -class ItemPermissionModel extends FlutterFlowModel { - /// Local state fields for this component. - - bool isOn = false; - - @override - void initState(BuildContext context) {} - - @override - void dispose() {} -} diff --git a/apps/AppWithWearable/lib/components/items/permissions_list/permissions_list_model.dart b/apps/AppWithWearable/lib/components/items/permissions_list/permissions_list_model.dart deleted file mode 100644 index 11605b160e..0000000000 --- a/apps/AppWithWearable/lib/components/items/permissions_list/permissions_list_model.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; - -import '/components/items/item_permission/item_permission_widget.dart'; -import '/flutter_flow/flutter_flow_util.dart'; -import 'permissions_list_widget.dart' show PermissionsListWidget; - -class PermissionsListModel extends FlutterFlowModel { - /// State fields for stateful widgets in this component. - - // Model for item_permission component. - late ItemPermissionModel itemPermissionModel1; - // Model for item_permission component. - late ItemPermissionModel itemPermissionModel2; - - @override - void initState(BuildContext context) { - itemPermissionModel1 = createModel(context, () => ItemPermissionModel()); - itemPermissionModel2 = createModel(context, () => ItemPermissionModel()); - } - - @override - void dispose() { - itemPermissionModel1.dispose(); - itemPermissionModel2.dispose(); - } -} diff --git a/apps/AppWithWearable/lib/components/items/permissions_list/permissions_list_widget.dart b/apps/AppWithWearable/lib/components/items/permissions_list/permissions_list_widget.dart deleted file mode 100644 index 9dc4af15fd..0000000000 --- a/apps/AppWithWearable/lib/components/items/permissions_list/permissions_list_widget.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:flutter/material.dart'; - -import '/backend/schema/enums/enums.dart'; -import '/components/items/item_permission/item_permission_widget.dart'; -import '/flutter_flow/flutter_flow_theme.dart'; -import '/flutter_flow/flutter_flow_util.dart'; -import 'permissions_list_model.dart'; - -export 'permissions_list_model.dart'; - -class PermissionsListWidget extends StatefulWidget { - const PermissionsListWidget({super.key}); - - @override - State createState() => _PermissionsListWidgetState(); -} - -class _PermissionsListWidgetState extends State { - late PermissionsListModel _model; - - @override - void setState(VoidCallback callback) { - super.setState(callback); - _model.onUpdate(); - } - - @override - void initState() { - super.initState(); - _model = createModel(context, () => PermissionsListModel()); - - WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); - } - - @override - void dispose() { - _model.maybeDispose(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsetsDirectional.fromSTEB(0.0, 16.0, 0.0, 0.0), - child: ListView( - padding: EdgeInsets.zero, - shrinkWrap: true, - scrollDirection: Axis.vertical, - children: [ - wrapWithModel( - model: _model.itemPermissionModel1, - updateCallback: () => setState(() {}), - child: ItemPermissionWidget( - text: 'We need notifications to send feedback and reminders', - icon: Icon( - Icons.notifications_active_sharp, - color: FlutterFlowTheme.of(context).secondary, - size: 24.0, - ), - permission: Permission.notifs, - ), - ), - wrapWithModel( - model: _model.itemPermissionModel2, - updateCallback: () => setState(() {}), - child: ItemPermissionWidget( - text: 'We need notifications to send feedback and reminders', - icon: Icon( - Icons.bluetooth_sharp, - color: FlutterFlowTheme.of(context).secondary, - size: 24.0, - ), - permission: Permission.bluetooth, - ), - ), - ], - ), - ); - } -} diff --git a/apps/AppWithWearable/lib/components/logo/logo_main/logo_main_model.dart b/apps/AppWithWearable/lib/components/logo/logo_main/logo_main_model.dart deleted file mode 100644 index 0775107821..0000000000 --- a/apps/AppWithWearable/lib/components/logo/logo_main/logo_main_model.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; - -import '/flutter_flow/flutter_flow_util.dart'; -import 'logo_main_widget.dart' show LogoMainWidget; - -class LogoMainModel extends FlutterFlowModel { - @override - void initState(BuildContext context) {} - - @override - void dispose() {} -} diff --git a/apps/AppWithWearable/lib/components/logo/logo_main/logo_main_widget.dart b/apps/AppWithWearable/lib/components/logo/logo_main/logo_main_widget.dart deleted file mode 100644 index bb90db9b46..0000000000 --- a/apps/AppWithWearable/lib/components/logo/logo_main/logo_main_widget.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:flutter/material.dart'; - -import '/flutter_flow/flutter_flow_util.dart'; -import 'logo_main_model.dart'; - -export 'logo_main_model.dart'; - -class LogoMainWidget extends StatefulWidget { - const LogoMainWidget({super.key}); - - @override - State createState() => _LogoMainWidgetState(); -} - -class _LogoMainWidgetState extends State { - late LogoMainModel _model; - - @override - void setState(VoidCallback callback) { - super.setState(callback); - _model.onUpdate(); - } - - @override - void initState() { - super.initState(); - _model = createModel(context, () => LogoMainModel()); - - WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); - } - - @override - void dispose() { - _model.maybeDispose(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Container( - decoration: BoxDecoration(), - child: ClipRRect( - borderRadius: BorderRadius.circular(8.0), - child: Image.asset( - 'assets/images/favicon.png', - width: 60.0, - height: 60.0, - fit: BoxFit.contain, - ), - ), - ); - } -} diff --git a/apps/AppWithWearable/lib/custom_code/actions/ble_receive_w_a_v.dart b/apps/AppWithWearable/lib/custom_code/actions/ble_receive_w_a_v.dart index be4cce3844..7a94f39a5e 100644 --- a/apps/AppWithWearable/lib/custom_code/actions/ble_receive_w_a_v.dart +++ b/apps/AppWithWearable/lib/custom_code/actions/ble_receive_w_a_v.dart @@ -1,19 +1,16 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tuple/tuple.dart'; import '/backend/schema/structs/index.dart'; import '/flutter_flow/flutter_flow_util.dart'; import 'package:web_socket_channel/io.dart'; -const serverUrl = - 'wss://api.deepgram.com/v1/listen?encoding=linear16&sample_rate=8000&language=en&model=nova-2-general&no_delay=true&endpointing=100&interim_results=true&smart_format=true&diarize=true'; - -late IOWebSocketChannel channel; - const int sampleRate = 8000; const int channelCount = 1; const int sampleWidth = 2; // 2 bytes for 16-bit samples @@ -23,50 +20,45 @@ const String audioServiceUuid = "19b10000-e8f2-537e-4f6c-d104768a1214"; const String audioCharacteristicUuid = "19b10001-e8f2-537e-4f6c-d104768a1214"; const String audioCharacteristicFormatUuid = "19b10002-e8f2-537e-4f6c-d104768a1214"; -Future _initStream(void Function(String) speechFinalCallback, void Function(String) interimCallback) async { +Future _initStream( + void Function(String) speechFinalCallback, void Function(String, Map) interimCallback) async { final prefs = await SharedPreferences.getInstance(); final apiKey = prefs.getString('deepgramApiKey') ?? ''; + final recordingsLanguage = prefs.getString('recordingsLanguage') ?? 'en'; + + var serverUrl = + 'wss://api.deepgram.com/v1/listen?encoding=linear16&sample_rate=8000&language=$recordingsLanguage&model=nova-2-general&no_delay=true&endpointing=100&interim_results=true&smart_format=true&diarize=true'; debugPrint('Websocket Opening'); - channel = IOWebSocketChannel.connect(Uri.parse(serverUrl), headers: {'Authorization': 'Token $apiKey'}); - // var isFinals = []; + IOWebSocketChannel channel = + IOWebSocketChannel.connect(Uri.parse(serverUrl), headers: {'Authorization': 'Token $apiKey'}); + channel.ready.then((_) { channel.stream.listen((event) { debugPrint('Event from Stream: $event'); final parsedJson = jsonDecode(event); - final transcript = parsedJson['channel']['alternatives'][0]['transcript']; - // final isFinal = parsedJson['is_final']; + final data = parsedJson['channel']['alternatives'][0]; + final transcript = data['transcript']; final speechFinal = parsedJson['is_final']; - // if (transcript.length > 0) { - // debugPrint('~~Transcript: $transcript isFinal: $isFinal speechFinal: $speechFinal'); - // if (speechFinal) { - // debugPrint('isFinals.join(' '): ${isFinals.join(' ')}'); - // interimCallback(isFinals.join(' ') + (isFinals.isNotEmpty ? ' ' : '') + transcript); - // finalizedCallback(''); - // isFinals = []; - // } else { - // if (isFinal) { - // debugPrint('~~ isFinal but it was not speechFinal ~~'); - // isFinals.add(transcript); - // interimCallback(transcript); - // } else { - // interimCallback(transcript); - // } - // } - // } if (transcript.length > 0) { debugPrint('~~Transcript: $transcript ~ speechFinal: $speechFinal'); + Map bySpeaker = {}; + data['words'].forEach((word) { + int speaker = word['speaker']; + bySpeaker[speaker] ??= ''; + bySpeaker[speaker] = '${(bySpeaker[speaker] ?? '') + word['punctuated_word']} '; + }); + // This is step 1 for diarization, but, sometimes "Speaker 1: Hello how" + // but it says it's the previous speaker (e.g. speaker 0), but in the next stream it fixes the transcript, and says it's speaker 1. + debugPrint(bySpeaker.toString()); if (speechFinal) { - interimCallback(transcript); + interimCallback(transcript, bySpeaker); speechFinalCallback(''); } else { - interimCallback(transcript); + interimCallback(transcript, bySpeaker); } } - - // Re-instantiate the Completer for next use - // completer = Completer(); }, onError: (err) { debugPrint('Websocket Error: $err'); // handle stream error @@ -80,19 +72,21 @@ Future _initStream(void Function(String) speechFinalCallback, void Functio try { await channel.ready; + debugPrint('Channel is ready, websocket opened'); } catch (e) { // handle exception here debugPrint("Websocket was unable to establishconnection"); } + return channel; } -Future bleReceiveWAV( - BTDeviceStruct btDevice, void Function(String) speechFinalCallback, void Function(String) interimCallback) async { +Future> bleReceiveWAV(BTDeviceStruct btDevice, + void Function(String) speechFinalCallback, void Function(String, Map) interimCallback) async { final device = BluetoothDevice.fromId(btDevice.id); final completer = Completer(); try { - _initStream(speechFinalCallback, interimCallback); + IOWebSocketChannel channel = await _initStream(speechFinalCallback, interimCallback); await device.connect(); debugPrint('Connected to device: ${device.id}'); List services = await device.discoverServices(); @@ -108,16 +102,15 @@ Future bleReceiveWAV( if (isNotify) { await characteristic.setNotifyValue(true); debugPrint('Subscribed to characteristic: ${characteristic.uuid.str128}'); - // List wavData = []; - // int samplesToRead = 150000; - characteristic.value.listen((value) { + StreamSubscription stream = characteristic.value.listen((value) { if (value.isEmpty) return; value.removeRange(0, 3); channel.sink.add(value); }); - return completer.future; + // return completer.future; + return Tuple2(channel, stream); } } } @@ -135,7 +128,8 @@ Future bleReceiveWAV( } } finally {} - return completer.future; + // return completer.future; + return const Tuple2(null, null); } FFUploadedFile createWavFile(List audioData) { diff --git a/apps/AppWithWearable/lib/flutter_flow/custom_functions.dart b/apps/AppWithWearable/lib/flutter_flow/custom_functions.dart index 5d79639e9e..866888f1ae 100644 --- a/apps/AppWithWearable/lib/flutter_flow/custom_functions.dart +++ b/apps/AppWithWearable/lib/flutter_flow/custom_functions.dart @@ -2,94 +2,14 @@ import 'dart:convert'; import 'package:flutter/material.dart'; -DateTime? sinceLastMonth() { - // function that returns DateTime of 24 hours ago from now - DateTime now = DateTime.now(); - DateTime twentyFourHoursAgo = now.subtract(Duration(hours: 730)); - return twentyFourHoursAgo; -} - -DateTime? sinceYesterday() { - // function that returns DateTime of 24 hours ago from now - DateTime now = DateTime.now(); - DateTime twentyFourHoursAgo = now.subtract(Duration(hours: 24)); - return twentyFourHoursAgo; -} - -DateTime? sinceLastWeek() { - // function that returns DateTime of 24 hours ago from now - DateTime now = DateTime.now(); - DateTime twentyFourHoursAgo = now.subtract(Duration(hours: 168)); - return twentyFourHoursAgo; -} - -DateTime? since18hoursago() { - // function that returns DateTime of 24 hours ago from now - DateTime now = DateTime.now(); - DateTime eighteenHoursAgo = now.subtract(Duration(hours: 24)); - return eighteenHoursAgo; -} - -String? limitTranscript( - String transcript, - int maxChars, -) { - // const int maxChars = 12000; // abt 2000 words - if (transcript.length > maxChars) { - // If the transcript is longer than maxChars, return the last maxChars characters - return transcript.substring(transcript.length - maxChars); - } - return transcript; // If the transcript is shorter than maxChars, return the whole transcript -} - -dynamic updateSystemPromptMemories( - dynamic chatHistory, - List memoriesString, -) { - // memoriesString = limitTranscript(memoriesString, 400000)!; - String memoriesContent = memoriesString.join(" "); - - // Construct the system prompt as a Map - Map systemPrompt = { - "role": "system", - "content": - "\\n\\n Your name is Friend and you are my helpful assistant. BELOW ARE MY MEMORIES. My memories are facts about me and things I remmember. Use these memories in your answers. MEMORIES: " + - memoriesContent + - "END OF MEMORIES \\n\\n We just had a conversation with you. This is our CONVERSATION. Respond to my question in a direct specific and concise manner in the end. Before answering the question, check all my memories above and make sure you have all relevant context needed to answer the question. If there are relevant memories, use them in the conversation " + - memoriesContent - }; - - // Map systemPrompt = {"role": "system", "content": "bruh"}; - - // Construct the system prompt as a Map - // Map systemPrompt = {"role": "system", "content": "hello"}; - - // if (chatHistory is List) { - // chatHistory[0] = (systemPrompt); - // return chatHistory; - // } else { - // return [systemPrompt]; - // } - return updateChatHistoryAtIndex(systemPrompt, 0, chatHistory); -} - dynamic saveChatHistory( dynamic chatHistory, dynamic newChat, ) { - // Ensure chatHistory is a list if (chatHistory is! List) { chatHistory = [chatHistory]; } - - // Add newChat to chatHistory chatHistory.add(newChat); - - // // If chatHistory has more than 30 items, remove the item at index 1 - // if (chatHistory.length > 30) { - // chatHistory.removeAt(1); - // } - return chatHistory; } @@ -98,51 +18,16 @@ dynamic convertToJSONRole( String? role, ) { String? encodedPrompt = jsonEncodeString(prompt); - return json.decode('{"role": "$role", "content": "$encodedPrompt"}'); } -dynamic updateChatHistoryAtIndex( - dynamic messageWithRole, - int index, - dynamic chatHistory, -) { - // dynamic chatHistory = FFAppState().chatHistory; - dynamic newChatHistory; - - // updates the chat history at a certain indexx - if (chatHistory is List) { - chatHistory[index] = (messageWithRole); - newChatHistory = chatHistory; - } else { - newChatHistory = [messageWithRole]; - } - return newChatHistory; -} - String? jsonEncodeString(String? regularString) { if (regularString == null) return null; + if (regularString.isEmpty | (regularString.length == 1)) return regularString; String encodedString = jsonEncode(regularString); debugPrint("jsonEncodeString: $encodedString"); - - // Remove the first and last character which are the double quotes - if (encodedString.length > 1) { - return encodedString.substring(1, encodedString.length - 1); - } - - return regularString; // Return the original string if it's empty or just one character -} - -dynamic truncateChatHistory(dynamic ogChatHistory) { - // If chatHistory has more than 30 items, remove the item at index 1 - int chatLength = 3; - if (ogChatHistory.length > chatLength) { - // Keep the first item and the last 30 items - var truncatedChatHistory = [ogChatHistory.first] + ogChatHistory.sublist(ogChatHistory.length - chatLength); - return truncatedChatHistory; - } - return ogChatHistory; + return encodedString.substring(1, encodedString.length - 1); } bool? stringContainsString( @@ -152,8 +37,8 @@ bool? stringContainsString( return string!.contains(substring!); } +// TODO: truncate to certain token length instead of messages count List retrieveMostRecentMessages(List ogChatHistory, {int count = 5}) { - // TODO: truncate to certain token length instead of messages count if (ogChatHistory.length > count) { return ogChatHistory.sublist(ogChatHistory.length - count); } diff --git a/apps/AppWithWearable/lib/flutter_flow/flutter_flow_util.dart b/apps/AppWithWearable/lib/flutter_flow/flutter_flow_util.dart index bfa7179275..fdaf9967da 100644 --- a/apps/AppWithWearable/lib/flutter_flow/flutter_flow_util.dart +++ b/apps/AppWithWearable/lib/flutter_flow/flutter_flow_util.dart @@ -19,7 +19,6 @@ export 'dart:typed_data' show Uint8List; export 'package:intl/intl.dart'; export 'package:page_transition/page_transition.dart'; -export '../app_constants.dart'; export '../app_state.dart'; export 'flutter_flow_model.dart'; export 'internationalization.dart' show FFLocalizations; diff --git a/apps/AppWithWearable/lib/flutter_flow/nav/nav.dart b/apps/AppWithWearable/lib/flutter_flow/nav/nav.dart index 0e418f14ff..ced2daebb5 100644 --- a/apps/AppWithWearable/lib/flutter_flow/nav/nav.dart +++ b/apps/AppWithWearable/lib/flutter_flow/nav/nav.dart @@ -1,11 +1,19 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:friend_private/pages/ble/connected/connected_widget.dart'; +import 'package:friend_private/pages/ble/connecting/connecting_widget.dart'; +import 'package:friend_private/pages/ble/find_devices/find_devices_widget.dart'; +import 'package:friend_private/pages/ble/scan_devices/scan_devices_widget.dart'; +import 'package:friend_private/pages/chat/page.dart'; +import 'package:friend_private/pages/connect_device/page.dart'; +import 'package:friend_private/pages/memories/page.dart'; +import 'package:friend_private/pages/permissions/page.dart'; +import 'package:friend_private/pages/welcome/page.dart'; import 'package:provider/provider.dart'; import '/backend/schema/structs/index.dart'; import '/flutter_flow/flutter_flow_util.dart'; -import '/index.dart'; export 'package:go_router/go_router.dart'; export 'serialization_util.dart'; @@ -41,7 +49,7 @@ GoRouter createRouter(AppStateNotifier appStateNotifier, [Widget? entryPage]) => ), ), ) - : entryPage ?? WelcomeWidget(), + : entryPage ?? const WelcomeWidget(), routes: [ FFRoute( name: '_initialize', @@ -56,23 +64,23 @@ GoRouter createRouter(AppStateNotifier appStateNotifier, [Widget? entryPage]) => ), ), ) - : entryPage ?? WelcomeWidget(), // TODO: restore back to WelcomeWidget when done with the PR + : entryPage ?? const WelcomeWidget(), // TODO: restore back to WelcomeWidget when done with the PR ), FFRoute( name: 'welcome', path: '/welcome', - builder: (context, params) => WelcomeWidget(), + builder: (context, params) => const WelcomeWidget(), ), FFRoute( name: 'PermissionPage', path: '/permissionPage', - builder: (context, params) => PermissionPageWidget(), + builder: (context, params) => const PermissionPageWidget(), ), FFRoute( name: 'connectDevice', path: '/connectDevice', builder: (context, params) => ConnectDeviceWidget( - btdevice: params.getParam( + btDevice: params.getParam( 'btdevice', ParamType.JSON, ), @@ -91,22 +99,22 @@ GoRouter createRouter(AppStateNotifier appStateNotifier, [Widget? entryPage]) => FFRoute( name: 'findDevices', path: '/findDevices', - builder: (context, params) => FindDevicesWidget(), + builder: (context, params) => const FindDevicesWidget(), ), FFRoute( name: 'memoriesPage', path: '/memoriesPage', - builder: (context, params) => HomePageWidget(), + builder: (context, params) => const MemoriesPage(), ), FFRoute( name: 'chatPage', path: '/chatPage', - builder: (context, params) => ChatPageWidget(), + builder: (context, params) => const ChatPageWidget(), ), FFRoute( name: 'scanDevices', path: '/scanDevices', - builder: (context, params) => ScanDevicesWidget(), + builder: (context, params) => const ScanDevicesWidget(), ), FFRoute( name: 'connected', @@ -278,7 +286,7 @@ class TransitionInfo { final Duration duration; final Alignment? alignment; - static TransitionInfo appDefault() => TransitionInfo(hasTransition: false); + static TransitionInfo appDefault() => const TransitionInfo(hasTransition: false); } class RootPageContext { diff --git a/apps/AppWithWearable/lib/index.dart b/apps/AppWithWearable/lib/index.dart deleted file mode 100644 index 0f947ab1a3..0000000000 --- a/apps/AppWithWearable/lib/index.dart +++ /dev/null @@ -1,10 +0,0 @@ -export '/pages/chat_page/chat_widget.dart' show ChatPageWidget; -export '/pages/home_page/home_page_widget.dart' show HomePageWidget; - -export '/onboarding/permission_page/permission_page_widget.dart' show PermissionPageWidget; -export '/onboarding/welcome/welcome_widget.dart' show WelcomeWidget; -export '/pages/ble/connected/connected_widget.dart' show ConnectedWidget; -export '/pages/ble/connecting/connecting_widget.dart' show ConnectingWidget; -export '/pages/ble/find_devices/find_devices_widget.dart' show FindDevicesWidget; -export '/pages/ble/scan_devices/scan_devices_widget.dart' show ScanDevicesWidget; -export '/pages/connect_device/connect_device_widget.dart' show ConnectDeviceWidget; diff --git a/apps/AppWithWearable/lib/onboarding/permission_page/permission_page_model.dart b/apps/AppWithWearable/lib/onboarding/permission_page/permission_page_model.dart deleted file mode 100644 index b678382f8b..0000000000 --- a/apps/AppWithWearable/lib/onboarding/permission_page/permission_page_model.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; - -import '/components/items/permissions_list/permissions_list_widget.dart'; -import '/flutter_flow/flutter_flow_util.dart'; -import 'permission_page_widget.dart' show PermissionPageWidget; - -class PermissionPageModel extends FlutterFlowModel { - /// State fields for stateful widgets in this page. - - final unfocusNode = FocusNode(); - // State field(s) for PageView widget. - PageController? pageViewController; - - int get pageViewCurrentIndex => pageViewController != null && - pageViewController!.hasClients && - pageViewController!.page != null - ? pageViewController!.page!.round() - : 0; - // Model for permissionsList component. - late PermissionsListModel permissionsListModel; - - @override - void initState(BuildContext context) { - permissionsListModel = createModel(context, () => PermissionsListModel()); - } - - @override - void dispose() { - unfocusNode.dispose(); - permissionsListModel.dispose(); - } -} diff --git a/apps/AppWithWearable/lib/pages/ble/device_data/device_data_model.dart b/apps/AppWithWearable/lib/pages/ble/device_data/device_data_model.dart deleted file mode 100644 index 9bb472342a..0000000000 --- a/apps/AppWithWearable/lib/pages/ble/device_data/device_data_model.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter/material.dart'; - -import '/flutter_flow/flutter_flow_util.dart'; -import 'device_data_widget.dart' show DeviceDataWidget; - -class DeviceDataModel extends FlutterFlowModel { - /// Local state fields for this component. - - List whispers = []; - void addToWhispers(String item) => whispers.add(item); - void removeFromWhispers(String item) => whispers.remove(item); - void removeAtIndexFromWhispers(int index) => whispers.removeAt(index); - void insertAtIndexInWhispers(int index, String item) => - whispers.insert(index, item); - void updateWhispersAtIndexFunction(int index, Function(String) updateFn) => - whispers[index] = updateFn(whispers[index]); - void updateWhispersAtIndex(int index, String _value) => whispers[index] = _value; - List ints = []; - void addToInts(int item) => ints.add(item); - void removeFromInts(int item) => ints.remove(item); - void removeAtIndexFromInts(int index) => ints.removeAt(index); - void insertAtIndexInInts(int index, int item) => ints.insert(index, item); - void updateIntsAtIndex(int index, Function(int) updateFn) => - ints[index] = updateFn(ints[index]); - - /// State fields for stateful widgets in this component. - - // Stores action output result for [Custom Action - bleReceiveWAV] action in deviceData widget. - String wav = ''; - // Stores action output result for [Backend Call - API (WHISPER D)] action in deviceData widget. - - @override - void initState(BuildContext context) {} - - @override - void dispose() {} -} diff --git a/apps/AppWithWearable/lib/pages/ble/device_data/device_data_widget.dart b/apps/AppWithWearable/lib/pages/ble/device_data/device_data_widget.dart deleted file mode 100644 index 701cf7d0e2..0000000000 --- a/apps/AppWithWearable/lib/pages/ble/device_data/device_data_widget.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:friend_private/actions/actions.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:provider/provider.dart'; - -import '/backend/schema/structs/index.dart'; -import '/custom_code/actions/index.dart' as actions; -import '/flutter_flow/flutter_flow_theme.dart'; -import '/flutter_flow/flutter_flow_util.dart'; -import 'device_data_model.dart'; - -export 'device_data_model.dart'; - -class DeviceDataWidget extends StatefulWidget { - const DeviceDataWidget({ - super.key, - required this.btdevice, - }); - - final BTDeviceStruct? btdevice; - - @override - State createState() => _DeviceDataWidgetState(); -} - -class _DeviceDataWidgetState extends State { - late DeviceDataModel _model; - Timer? _timer; - - @override - void setState(VoidCallback callback) { - super.setState(callback); - _model.onUpdate(); - } - - _initiateTimer() { - _timer = Timer(const Duration(seconds: 30), () { - debugPrint('Creating memory from whispers'); - String whispers = FFAppState().whispers.join(' '); - debugPrint('FFAppState().whispers: ${FFAppState().whispers}'); - processTranscriptContent(whispers); - setState(() { - FFAppState().whispers = []; - _model.whispers = []; - }); - }); - } - - @override - void initState() { - super.initState(); - _model = createModel(context, () => DeviceDataModel()); - - // On component load action. - SchedulerBinding.instance.addPostFrameCallback((_) async { - debugPrint('Checking for transcript'); - _model.wav = await actions.bleReceiveWAV(widget.btdevice!, (String receivedData) { - debugPrint("Deepgram Finalized Callback received"); // it's always empty string - setState(() { - _model.addToWhispers(receivedData); - FFAppState().addToWhispers(receivedData); - }); - _initiateTimer(); - }, (String transcript) { - _timer?.cancel(); - - // We dont have any whispers yet so we need to create the first one to update - if (_model.whispers.isEmpty) { - setState(() { - _model.addToWhispers(transcript); - FFAppState().addToWhispers(transcript); - }); - } else { - setState(() { - _model.updateWhispersAtIndex(_model.whispers.length - 1, transcript); - FFAppState().updateWhispersAtIndex(_model.whispers.length - 1, transcript); - }); - } - }); - }); - - WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); - } - - @override - void dispose() { - _model.maybeDispose(); - - super.dispose(); - } - - @override - Widget build(BuildContext context) { - context.watch(); - - return Align( - alignment: const AlignmentDirectional(0.0, 0.0), - child: Container( - width: double.infinity, - height: double.infinity, - decoration: const BoxDecoration(), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Builder( - builder: (context) { - final whispersList = _model.whispers.toList(); - return ListView.separated( - padding: EdgeInsets.zero, - shrinkWrap: true, - scrollDirection: Axis.vertical, - itemCount: whispersList.length, - separatorBuilder: (_, __) => const SizedBox(height: 16.0), - itemBuilder: (context, whispersListIndex) { - final whispersListItem = whispersList[whispersListIndex]; - return Padding( - padding: const EdgeInsetsDirectional.fromSTEB(16.0, 0.0, 16.0, 0.0), - child: Text( - whispersListItem, - style: FlutterFlowTheme - .of(context) - .bodyMedium - .override( - fontFamily: FlutterFlowTheme - .of(context) - .bodyMediumFamily, - letterSpacing: 0.0, - useGoogleFonts: - GoogleFonts.asMap().containsKey(FlutterFlowTheme - .of(context) - .bodyMediumFamily), - ), - ), - ); - }, - ); - }, - ), - ].divide(const SizedBox(height: 16.0)), - ), - ), - ), - ); - } -} diff --git a/apps/AppWithWearable/lib/pages/ble/device_data/widget.dart b/apps/AppWithWearable/lib/pages/ble/device_data/widget.dart new file mode 100644 index 0000000000..77d6aae827 --- /dev/null +++ b/apps/AppWithWearable/lib/pages/ble/device_data/widget.dart @@ -0,0 +1,151 @@ +import 'dart:async'; + +import 'package:collection/collection.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:friend_private/actions/actions.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:provider/provider.dart'; +import 'package:tuple/tuple.dart'; +import 'package:web_socket_channel/io.dart'; + +import '/backend/schema/structs/index.dart'; +import '/custom_code/actions/index.dart' as actions; +import '/flutter_flow/flutter_flow_theme.dart'; +import '/flutter_flow/flutter_flow_util.dart'; + +class DeviceDataWidget extends StatefulWidget { + const DeviceDataWidget({ + super.key, + required this.btDevice, + }); + + final BTDeviceStruct? btDevice; + + @override + State createState() => DeviceDataWidgetState(); +} + +class DeviceDataWidgetState extends State { + Timer? _timer; + + // List whispers = ['']; + List> whispersDiarized = [{}]; + IOWebSocketChannel? channel; + StreamSubscription? streamSubscription; + + String _buildDiarizedTranscriptMessage() { + int totalSpeakers = whispersDiarized + .map((e) => e.keys.isEmpty ? 0 : ((e.keys).max + 1)) + .reduce((value, element) => value > element ? value : element); + + debugPrint('Speakers count: $totalSpeakers'); + + String transcript = ''; + for (int partIdx = 0; partIdx < whispersDiarized.length; partIdx++) { + var part = whispersDiarized[partIdx]; + if (part.isEmpty) continue; + for (int speaker = 0; speaker < totalSpeakers; speaker++) { + if (part.containsKey(speaker)) { + // This part and previous have only 1 speaker, and is the same + if (partIdx > 0 && + whispersDiarized[partIdx - 1].containsKey(speaker) && + whispersDiarized[partIdx - 1].length == 1 && + part.length == 1) { + transcript += '${part[speaker]!} '; + } else { + transcript += 'Speaker $speaker: ${part[speaker]!} '; + } + } + } + transcript += '\n'; + } + return transcript; + } + + _initiateTimer() { + _timer = Timer(const Duration(seconds: 30), () { + debugPrint('Creating memory from whispers'); + String transcript = _buildDiarizedTranscriptMessage(); + debugPrint('Transcript: \n${transcript.trim()}'); + processTranscriptContent(transcript); + setState(() { + whispersDiarized = [{}]; + }); + }); + } + + @override + void initState() { + super.initState(); + initBleConnection(); + } + + void initBleConnection() async { + SchedulerBinding.instance.addPostFrameCallback((_) async { + Tuple2 data = await actions.bleReceiveWAV(widget.btDevice!, (_) { + debugPrint("Deepgram Finalized Callback received"); + setState(() { + whispersDiarized.add({}); + }); + _initiateTimer(); + }, (String transcript, Map transcriptBySpeaker) { + _timer?.cancel(); + var copy = whispersDiarized[whispersDiarized.length - 1]; + transcriptBySpeaker.forEach((speaker, transcript) { + copy[speaker] = transcript; + }); + setState(() { + whispersDiarized[whispersDiarized.length - 1] = copy; + }); + }); + channel = data.item1; + streamSubscription = data.item2; + }); + } + + void resetState() { + streamSubscription?.cancel(); + channel?.sink.close(); + setState(() { + whispersDiarized = [{}]; + _timer?.cancel(); + initBleConnection(); + }); + } + + @override + Widget build(BuildContext context) { + context.watch(); + var filteredNotEmptyWhispers = whispersDiarized.where((e) => e.isNotEmpty).toList(); + // debugPrint('filteredNotEmptyWhispers ${filteredNotEmptyWhispers.length}'); + return ListView.separated( + padding: EdgeInsets.zero, + shrinkWrap: true, + scrollDirection: Axis.vertical, + itemCount: filteredNotEmptyWhispers.length, + physics: const NeverScrollableScrollPhysics(), + separatorBuilder: (_, __) => const SizedBox(height: 16.0), + itemBuilder: (context, idx) { + final data = filteredNotEmptyWhispers[idx]; + String transcriptItem = ''; + for (int speaker = 0; speaker < data.length; speaker++) { + if (data.containsKey(speaker)) { + transcriptItem += 'Speaker $speaker: ${data[speaker]!} '; + } + } + return Padding( + padding: const EdgeInsetsDirectional.fromSTEB(16.0, 0.0, 16.0, 0.0), + child: Text( + transcriptItem, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: FlutterFlowTheme.of(context).bodyMediumFamily, + letterSpacing: 0.0, + useGoogleFonts: GoogleFonts.asMap().containsKey(FlutterFlowTheme.of(context).bodyMediumFamily), + ), + ), + ); + }, + ); + } +} diff --git a/apps/AppWithWearable/lib/pages/ble/find_devices/find_devices_widget.dart b/apps/AppWithWearable/lib/pages/ble/find_devices/find_devices_widget.dart index 2c42878e36..8261aae803 100644 --- a/apps/AppWithWearable/lib/pages/ble/find_devices/find_devices_widget.dart +++ b/apps/AppWithWearable/lib/pages/ble/find_devices/find_devices_widget.dart @@ -22,15 +22,13 @@ class FindDevicesWidget extends StatefulWidget { _FindDevicesWidgetState createState() => _FindDevicesWidgetState(); } -class _FindDevicesWidgetState extends State - with SingleTickerProviderStateMixin { +class _FindDevicesWidgetState extends State with SingleTickerProviderStateMixin { late FindDevicesModel _model; late AnimationController _animationController; late Animation _animation; BTDeviceStruct? _friendDevice; String _stringStatus1 = 'Looking for Friend wearable'; - String _stringStatus2 = - 'Locating your Friend device. Keep it near your phone for pairing'; + String _stringStatus2 = 'Locating your Friend device. Keep it near your phone for pairing'; bool _isConnected = false; @override @@ -74,13 +72,11 @@ class _FindDevicesWidgetState extends State _model.fetchedConnectedDevices = await actions.ble0getConnectedDevices(); setState(() { _model.isFetchingConnectedDevices = false; - _model.connectedDevices = - _model.fetchedConnectedDevices!.toList().cast(); + _model.connectedDevices = _model.fetchedConnectedDevices!.toList().cast(); }); _model.devices = await actions.ble0findDevices(); setState(() { - _model.connectedDevices = - _model.devices!.toList().cast(); + _model.connectedDevices = _model.devices!.toList().cast(); _model.isFetchingDevices = false; }); } else { @@ -104,8 +100,7 @@ class _FindDevicesWidgetState extends State while (true) { _model.devicesScanCopy = await actions.ble0findDevices(); setState(() { - _model.foundDevices = - _model.devicesScanCopy!.toList().cast(); + _model.foundDevices = _model.devicesScanCopy!.toList().cast(); }); try { @@ -121,8 +116,7 @@ class _FindDevicesWidgetState extends State _isConnected = true; _friendDevice = friendDevice; _stringStatus1 = 'Friend Wearable'; - _stringStatus2 = - 'Successfully connected and ready to accelerate your journey with AI'; + _stringStatus2 = 'Successfully connected and ready to accelerate your journey with AI'; }); break; } catch (e) { @@ -130,11 +124,11 @@ class _FindDevicesWidgetState extends State } await Future.delayed(Duration(seconds: 2)); - } } void _navigateToConnecting() { + if (_friendDevice == null) return; context.pushNamed( 'connectDevice', queryParameters: { @@ -164,6 +158,7 @@ class _FindDevicesWidgetState extends State return 450.0; // iPhone 14 Pro Max and larger devices } } + final gifSize = getGifSize(screenHeight); return Scaffold( @@ -184,8 +179,7 @@ class _FindDevicesWidgetState extends State fontSize: 30.0, letterSpacing: 0.0, fontWeight: FontWeight.w700, - useGoogleFonts: - GoogleFonts.asMap().containsKey('SF Pro Display'), + useGoogleFonts: GoogleFonts.asMap().containsKey('SF Pro Display'), lineHeight: 1.2, ), textAlign: TextAlign.center, @@ -196,8 +190,7 @@ class _FindDevicesWidgetState extends State duration: Duration(milliseconds: 500), opacity: _isConnected ? 1.0 : 0.0, child: Padding( - padding: - EdgeInsets.symmetric(vertical: 8.0, horizontal: 32.0), + padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 32.0), child: Container( decoration: BoxDecoration( color: Colors.transparent, @@ -270,16 +263,13 @@ class _FindDevicesWidgetState extends State children: [ Text( _stringStatus1, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( + style: FlutterFlowTheme.of(context).bodyMedium.override( fontFamily: 'SF Pro Display', color: Colors.white, fontSize: 32.0, letterSpacing: 0.0, fontWeight: FontWeight.w700, - useGoogleFonts: GoogleFonts.asMap() - .containsKey('SF Pro Display'), + useGoogleFonts: GoogleFonts.asMap().containsKey('SF Pro Display'), lineHeight: 1.2, ), textAlign: TextAlign.center, @@ -306,21 +296,15 @@ class _FindDevicesWidgetState extends State text: 'Continue', options: FFButtonOptions( height: 50, - padding: - EdgeInsets.symmetric(horizontal: 30), - color: - FlutterFlowTheme.of(context).secondary, - textStyle: FlutterFlowTheme.of(context) - .titleSmall - .copyWith( - color: FlutterFlowTheme.of(context) - .primary, + padding: EdgeInsets.symmetric(horizontal: 30), + color: FlutterFlowTheme.of(context).secondary, + textStyle: FlutterFlowTheme.of(context).titleSmall.copyWith( + color: FlutterFlowTheme.of(context).primary, fontSize: 24, fontWeight: FontWeight.w600, ), borderSide: BorderSide( - color: FlutterFlowTheme.of(context) - .secondary, + color: FlutterFlowTheme.of(context).secondary, width: 1, ), borderRadius: BorderRadius.circular(30), @@ -341,4 +325,4 @@ class _FindDevicesWidgetState extends State ), ); } -} \ No newline at end of file +} diff --git a/apps/AppWithWearable/lib/pages/chat_page/chat_model.dart b/apps/AppWithWearable/lib/pages/chat/model.dart similarity index 97% rename from apps/AppWithWearable/lib/pages/chat_page/chat_model.dart rename to apps/AppWithWearable/lib/pages/chat/model.dart index 5efa2fb9f4..2e08a45ed8 100644 --- a/apps/AppWithWearable/lib/pages/chat_page/chat_model.dart +++ b/apps/AppWithWearable/lib/pages/chat/model.dart @@ -1,5 +1,5 @@ import '/flutter_flow/flutter_flow_util.dart'; -import 'chat_widget.dart' show ChatPageWidget; +import 'page.dart' show ChatPageWidget; import 'package:flutter/material.dart'; class ChatModel extends FlutterFlowModel { diff --git a/apps/AppWithWearable/lib/pages/chat_page/chat_widget.dart b/apps/AppWithWearable/lib/pages/chat/page.dart similarity index 99% rename from apps/AppWithWearable/lib/pages/chat_page/chat_widget.dart rename to apps/AppWithWearable/lib/pages/chat/page.dart index bc47e85ccb..da7d0864d1 100644 --- a/apps/AppWithWearable/lib/pages/chat_page/chat_widget.dart +++ b/apps/AppWithWearable/lib/pages/chat/page.dart @@ -14,8 +14,8 @@ import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:provider/provider.dart'; -import 'chat_model.dart'; -export 'chat_model.dart'; +import 'model.dart'; +export 'model.dart'; class ChatPageWidget extends StatefulWidget { const ChatPageWidget({super.key}); diff --git a/apps/AppWithWearable/lib/pages/connect_device/connect_device_model.dart b/apps/AppWithWearable/lib/pages/connect_device/model.dart similarity index 74% rename from apps/AppWithWearable/lib/pages/connect_device/connect_device_model.dart rename to apps/AppWithWearable/lib/pages/connect_device/model.dart index c594e20f9a..4d3c149368 100644 --- a/apps/AppWithWearable/lib/pages/connect_device/connect_device_model.dart +++ b/apps/AppWithWearable/lib/pages/connect_device/model.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import '/flutter_flow/flutter_flow_util.dart'; import '/flutter_flow/instant_timer.dart'; import '/pages/ble/blur/blur_widget.dart'; -import '/pages/ble/device_data/device_data_widget.dart'; -import 'connect_device_widget.dart' show ConnectDeviceWidget; +import '/pages/ble/device_data/widget.dart'; +import 'page.dart' show ConnectDeviceWidget; class ConnectDeviceModel extends FlutterFlowModel { /// Local state fields for this page. @@ -19,13 +19,10 @@ class ConnectDeviceModel extends FlutterFlowModel { int? updatedRssi; // Model for blur component. late BlurModel blurModel; - // Model for deviceData component. - late DeviceDataModel deviceDataModel; @override void initState(BuildContext context) { blurModel = createModel(context, () => BlurModel()); - deviceDataModel = createModel(context, () => DeviceDataModel()); } @override @@ -33,6 +30,5 @@ class ConnectDeviceModel extends FlutterFlowModel { unfocusNode.dispose(); rssiUpdateTimer?.cancel(); blurModel.dispose(); - deviceDataModel.dispose(); } } diff --git a/apps/AppWithWearable/lib/pages/connect_device/connect_device_widget.dart b/apps/AppWithWearable/lib/pages/connect_device/page.dart similarity index 61% rename from apps/AppWithWearable/lib/pages/connect_device/connect_device_widget.dart rename to apps/AppWithWearable/lib/pages/connect_device/page.dart index 7132750ab5..a0785e1a20 100644 --- a/apps/AppWithWearable/lib/pages/connect_device/connect_device_widget.dart +++ b/apps/AppWithWearable/lib/pages/connect_device/page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; +import 'package:friend_private/backend/utils.dart'; import 'package:friend_private/flutter_flow/flutter_flow_widgets.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -11,25 +12,28 @@ import '/flutter_flow/flutter_flow_theme.dart'; import '/flutter_flow/flutter_flow_util.dart'; import '/flutter_flow/instant_timer.dart'; import '/pages/ble/blur_bot/blur_bot_widget.dart'; -import '/pages/ble/device_data/device_data_widget.dart'; -import 'connect_device_model.dart'; +import '/pages/ble/device_data/widget.dart'; +import 'model.dart'; -export 'connect_device_model.dart'; +export 'model.dart'; class ConnectDeviceWidget extends StatefulWidget { const ConnectDeviceWidget({ super.key, - required this.btdevice, + required this.btDevice, }); - final dynamic btdevice; + final dynamic btDevice; @override State createState() => _ConnectDeviceWidgetState(); } class _ConnectDeviceWidgetState extends State { + GlobalKey childWidgetKey = GlobalKey(); late ConnectDeviceModel _model; + bool deepgramApiIsVisible = false; + bool openaiApiIsVisible = false; final _deepgramApiKeyController = TextEditingController(); final _openaiApiKeyController = TextEditingController(); bool _areApiKeysSet = false; @@ -64,13 +68,13 @@ class _ConnectDeviceWidgetState extends State { // On page load action. SchedulerBinding.instance.addPostFrameCallback((_) async { setState(() { - _model.currentRssi = BTDeviceStruct.maybeFromMap(widget.btdevice)?.rssi; + _model.currentRssi = BTDeviceStruct.maybeFromMap(widget.btDevice)?.rssi; }); _model.rssiUpdateTimer = InstantTimer.periodic( duration: const Duration(milliseconds: 2000), callback: (timer) async { _model.updatedRssi = await actions.ble0getRssi( - BTDeviceStruct.maybeFromMap(widget.btdevice!)!, + BTDeviceStruct.maybeFromMap(widget.btDevice!)!, ); setState(() { _model.currentRssi = _model.updatedRssi; @@ -89,23 +93,18 @@ class _ConnectDeviceWidgetState extends State { super.dispose(); } - String _obscureApiKey(String apiKey) { - if (apiKey.length <= 3) { - return apiKey; - } else { - final obscuredKey = '*' * (apiKey.length - 3); - return apiKey.substring(0, 3) + obscuredKey; - } - } + String _selectedLanguage = 'en'; Future _showSettingsBottomSheet() async { // Load API keys from shared preferences final prefs = await SharedPreferences.getInstance(); final deepgramApiKey = prefs.getString('deepgramApiKey') ?? ''; final openaiApiKey = prefs.getString('openaiApiKey') ?? ''; + final recordingsLanguage = prefs.getString('recordingsLanguage') ?? 'en'; - _deepgramApiKeyController.text = _obscureApiKey(deepgramApiKey); - _openaiApiKeyController.text = _obscureApiKey(openaiApiKey); + _deepgramApiKeyController.text = deepgramApiKey; + _openaiApiKeyController.text = openaiApiKey; + _selectedLanguage = recordingsLanguage; await showModalBottomSheet( context: context, @@ -118,8 +117,8 @@ class _ConnectDeviceWidgetState extends State { ), ), builder: (BuildContext context) { - return WillPopScope( - onWillPop: () async => false, + return PopScope( + canPop: false, child: StatefulBuilder( builder: (BuildContext context, StateSetter setModalState) { return Padding( @@ -127,7 +126,7 @@ class _ConnectDeviceWidgetState extends State { bottom: MediaQuery.of(context).viewInsets.bottom, ), child: Container( - height: MediaQuery.of(context).size.height * 0.6, + height: MediaQuery.of(context).size.height * 0.7, padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( @@ -151,17 +150,29 @@ class _ConnectDeviceWidgetState extends State { const SizedBox(height: 8.0), TextField( controller: _deepgramApiKeyController, - decoration: const InputDecoration( + obscureText: deepgramApiIsVisible ? false : true, + decoration: InputDecoration( labelText: 'Deepgram API Key', - labelStyle: TextStyle(color: Colors.white), - border: OutlineInputBorder(), - enabledBorder: OutlineInputBorder( + labelStyle: const TextStyle(color: Colors.white), + border: const OutlineInputBorder(), + enabledBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.white), borderRadius: BorderRadius.all(Radius.circular(20.0)), ), - focusedBorder: OutlineInputBorder( + focusedBorder: const OutlineInputBorder( borderSide: BorderSide(color: Colors.white), ), + suffixIcon: IconButton( + icon: Icon( + deepgramApiIsVisible ? Icons.visibility : Icons.visibility_off, + color: Theme.of(context).primaryColor, + ), + onPressed: () { + setModalState(() { + deepgramApiIsVisible = !deepgramApiIsVisible; + }); + }, + ), ), style: const TextStyle(color: Colors.white), ), @@ -188,18 +199,31 @@ class _ConnectDeviceWidgetState extends State { const SizedBox(height: 8.0), TextField( controller: _openaiApiKeyController, - decoration: const InputDecoration( - labelText: 'OpenAI API Key', - labelStyle: TextStyle(color: Colors.white), - border: OutlineInputBorder(), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.white), - borderRadius: BorderRadius.all(Radius.circular(20.0)), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.white), - ), - ), + obscureText: openaiApiIsVisible ? false : true, + autocorrect: false, + enableSuggestions: false, + decoration: InputDecoration( + labelText: 'OpenAI API Key', + labelStyle: const TextStyle(color: Colors.white), + border: const OutlineInputBorder(), + enabledBorder: const OutlineInputBorder( + borderSide: BorderSide(color: Colors.white), + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ), + focusedBorder: const OutlineInputBorder( + borderSide: BorderSide(color: Colors.white), + ), + suffixIcon: IconButton( + icon: Icon( + openaiApiIsVisible ? Icons.visibility : Icons.visibility_off, + color: Theme.of(context).primaryColor, + ), + onPressed: () { + setModalState(() { + openaiApiIsVisible = !openaiApiIsVisible; + }); + }, + )), style: const TextStyle(color: Colors.white), ), const SizedBox(height: 8.0), @@ -216,6 +240,48 @@ class _ConnectDeviceWidgetState extends State { ), ), const SizedBox(height: 16.0), + const Center(child: Text('Recordings Language:', style: TextStyle(color: Colors.white))), + const SizedBox(height: 12), + Container( + height: 50, + decoration: BoxDecoration( + border: Border.all(color: Colors.white), + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + child: DropdownButton( + menuMaxHeight: 350, + value: _selectedLanguage, + onChanged: (String? newValue) { + setModalState(() { + _selectedLanguage = newValue!; + debugPrint('Selecting: $newValue'); + }); + }, + dropdownColor: Colors.black, + style: const TextStyle(color: Colors.white, fontSize: 16), + underline: Container( + height: 0, + color: Colors.white, + ), + isExpanded: false, + itemHeight: 48, + items: availableLanguages.keys.map>((String key) { + return DropdownMenuItem( + value: availableLanguages[key], + child: Text( + '$key (${availableLanguages[key]})', + style: TextStyle( + color: _selectedLanguage == availableLanguages[key] + ? Colors.blue[400] + : Colors.white, + fontSize: 16), + ), + ); + }).toList(), + ), + ), + const SizedBox(height: 20), ElevatedButton( onPressed: () { String deepgramApiKey = _deepgramApiKeyController.text; @@ -278,8 +344,8 @@ class _ConnectDeviceWidgetState extends State { ); // Set the obscured API keys in the text fields setState(() { - _deepgramApiKeyController.text = _obscureApiKey(deepgramApiKey); - _openaiApiKeyController.text = _obscureApiKey(openaiApiKey); + _deepgramApiKeyController.text = deepgramApiKey; + _openaiApiKeyController.text = openaiApiKey; }); } @@ -287,8 +353,12 @@ class _ConnectDeviceWidgetState extends State { final prefs = await SharedPreferences.getInstance(); await prefs.setString('deepgramApiKey', deepgramApiKey); await prefs.setString('openaiApiKey', openaiApiKey); - -// Initialize the page and enable the DeviceDataWidget after saving the API keys + if (_selectedLanguage != prefs.getString('recordingsLanguage')) { + // If the language has changed, restart the deepgram websocket + childWidgetKey.currentState?.resetState(); + await prefs.setString('recordingsLanguage', _selectedLanguage); + } + // Initialize the page and enable the DeviceDataWidget after saving the API keys _initializePage(); setState(() { _areApiKeysSet = true; @@ -351,100 +421,64 @@ class _ConnectDeviceWidgetState extends State { updateCallback: () => setState(() {}), child: const BlurBotWidget(), ), - Align( - alignment: const AlignmentDirectional(0.0, 0.0), - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Column( - mainAxisSize: MainAxisSize.max, - children: [ - ClipRRect( - borderRadius: BorderRadius.circular(24.0), - child: Image.network( - 'https://images.unsplash.com/photo-1589128777073-263566ae5e4d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w0NTYyMDF8MHwxfHNlYXJjaHwyfHxuZWNrbGFjZXxlbnwwfHx8fDE3MTEyMDQxNTF8MA&ixlib=rb-4.0.3&q=80&w=1080', - width: 120.0, - height: 120.0, - fit: BoxFit.cover, - alignment: const Alignment(0.0, 1.0), - ), - ), - Align( - alignment: const AlignmentDirectional(0.0, 0.0), - child: Text( - 'Connected Device', - style: FlutterFlowTheme.of(context).headlineLarge.override( - fontFamily: FlutterFlowTheme.of(context).headlineLargeFamily, - fontSize: 24.0, - letterSpacing: 0.0, - fontWeight: FontWeight.bold, - useGoogleFonts: - GoogleFonts.asMap().containsKey(FlutterFlowTheme.of(context).headlineLargeFamily), - ), - ), - ), - Column( - mainAxisSize: MainAxisSize.max, - children: [ - Align( - alignment: const AlignmentDirectional(0.0, 0.0), - child: Text( - valueOrDefault( - BTDeviceStruct.maybeFromMap(widget.btdevice)?.name, - '-', - ), - style: FlutterFlowTheme.of(context).titleSmall.override( - fontFamily: FlutterFlowTheme.of(context).titleSmallFamily, - letterSpacing: 0.0, - useGoogleFonts: - GoogleFonts.asMap().containsKey(FlutterFlowTheme.of(context).titleSmallFamily), - ), - ), - ), - Align( - alignment: const AlignmentDirectional(0.0, 0.0), - child: Text( - valueOrDefault( - BTDeviceStruct.maybeFromMap(widget.btdevice)?.id, - '-', - ), - style: FlutterFlowTheme.of(context).titleSmall.override( - fontFamily: FlutterFlowTheme.of(context).titleSmallFamily, - letterSpacing: 0.0, - useGoogleFonts: - GoogleFonts.asMap().containsKey(FlutterFlowTheme.of(context).titleSmallFamily), - ), - ), - ), - ].divide(const SizedBox(height: 8.0)), + ListView(children: [ + const SizedBox(height: 64), + Center( + child: ClipRRect( + borderRadius: BorderRadius.circular(24.0), + child: Image.network( + 'https://images.unsplash.com/photo-1589128777073-263566ae5e4d?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w0NTYyMDF8MHwxfHNlYXJjaHwyfHxuZWNrbGFjZXxlbnwwfHx8fDE3MTEyMDQxNTF8MA&ixlib=rb-4.0.3&q=80&w=1080', + width: 120.0, + height: 120.0, + fit: BoxFit.cover, + alignment: const Alignment(0.0, 1.0), + ), + )), + const SizedBox(height: 16), + Center( + child: Text( + 'Connected Device', + style: FlutterFlowTheme.of(context).headlineLarge.override( + fontFamily: FlutterFlowTheme.of(context).headlineLargeFamily, + fontSize: 24.0, + letterSpacing: 0.0, + fontWeight: FontWeight.bold, + useGoogleFonts: + GoogleFonts.asMap().containsKey(FlutterFlowTheme.of(context).headlineLargeFamily), ), - ].divide(const SizedBox(height: 16.0)), - ), - Expanded( - child: Align( - alignment: const AlignmentDirectional(0.0, 0.0), - child: _areApiKeysSet - ? wrapWithModel( - model: _model.deviceDataModel, - updateCallback: () => setState(() {}), - updateOnChange: true, - child: DeviceDataWidget( - btdevice: BTDeviceStruct.maybeFromMap(widget.btdevice!)!, - ), - ) - : const SizedBox.shrink(), - ), - ), - ] - .divide(const SizedBox(height: 32.0)) - .addToStart(const SizedBox(height: 48.0)) - .addToEnd(const SizedBox(height: 48.0)), + ), ), - ), + const SizedBox(height: 8), + Center( + child: Text( + BTDeviceStruct.maybeFromMap(widget.btDevice)?.name ?? '-', + style: _getTextStyle(), + )), + const SizedBox(height: 8), + Center( + child: Text( + BTDeviceStruct.maybeFromMap(widget.btDevice)?.id ?? '-', + style: _getTextStyle(), + )), + const SizedBox(height: 32), + _areApiKeysSet + ? DeviceDataWidget( + btDevice: BTDeviceStruct.maybeFromMap(widget.btDevice!)!, + key: childWidgetKey, + ) + : const SizedBox.shrink(), + ]), ], ), ), ); } + + _getTextStyle() { + return FlutterFlowTheme.of(context).titleSmall.override( + fontFamily: FlutterFlowTheme.of(context).titleSmallFamily, + letterSpacing: 0.0, + useGoogleFonts: GoogleFonts.asMap().containsKey(FlutterFlowTheme.of(context).titleSmallFamily), + ); + } } diff --git a/apps/AppWithWearable/lib/pages/home_page/confirm_deletion_model.dart b/apps/AppWithWearable/lib/pages/home_page/confirm_deletion_model.dart deleted file mode 100644 index ef3c58c7e8..0000000000 --- a/apps/AppWithWearable/lib/pages/home_page/confirm_deletion_model.dart +++ /dev/null @@ -1,17 +0,0 @@ -import '/flutter_flow/flutter_flow_util.dart'; -import 'confirm_deletion_widget.dart' show ConfirmDeletionWidget; -import 'package:flutter/material.dart'; - -class ConfirmDeletionModel extends FlutterFlowModel { - /// Initialization and disposal methods. - - @override - void initState(BuildContext context) {} - - @override - void dispose() {} - - /// Action blocks are added here. - - /// Additional helper methods are added here. -} diff --git a/apps/AppWithWearable/lib/pages/home_page/home_page_widget.dart b/apps/AppWithWearable/lib/pages/home_page/home_page_widget.dart deleted file mode 100644 index 721d010800..0000000000 --- a/apps/AppWithWearable/lib/pages/home_page/home_page_widget.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'package:friend_private/backend/api_requests/api_calls.dart'; -import 'package:friend_private/backend/storage/memories.dart'; -import 'package:friend_private/flutter_flow/flutter_flow_theme.dart'; -import 'package:friend_private/pages/ble/blur_bot/blur_bot_widget.dart'; -import 'package:friend_private/pages/home_page/empty_memories.dart'; -import 'package:friend_private/pages/home_page/header_buttons.dart'; -import 'package:friend_private/pages/home_page/home_page_model.dart'; -import 'package:friend_private/pages/home_page/memory_list_item.dart'; -import 'package:friend_private/pages/home_page/memory_processing.dart'; -import 'package:friend_private/pages/home_page/summaries_buttons.dart'; - -import '/flutter_flow/flutter_flow_util.dart'; -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; - -class HomePageWidget extends StatefulWidget { - const HomePageWidget({super.key}); - - @override - State createState() => _HomePageWidgetState(); -} - -class _HomePageWidgetState extends State { - late HomePageModel _model; - String? dailySummary; - String? weeklySummary; - String? monthlySummary; - - final scaffoldKey = GlobalKey(); - - _dailySummary() async { - List memories = await MemoryStorage.getMemoriesByDay(DateTime.now()); - dailySummary = memories.isNotEmpty ? (await requestSummary(memories)) : null; - } - - _weeklySummary() async { - List memories = await MemoryStorage.getMemoriesOfLastWeek(); - weeklySummary = memories.isNotEmpty ? (await requestSummary(memories)) : null; - } - - _monthlySummary() async { - List memories = await MemoryStorage.getMemoriesOfLastMonth(); - monthlySummary = memories.isNotEmpty ? (await requestSummary(memories)) : null; - } - - @override - void initState() { - super.initState(); - _model = createModel(context, () => HomePageModel()); - _dailySummary(); - _weeklySummary(); - _monthlySummary(); - WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); - } - - @override - void dispose() { - _model.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - context.watch(); - - return Builder( - builder: (context) => GestureDetector( - onTap: () => _model.unfocusNode.canRequestFocus - ? FocusScope.of(context).requestFocus(_model.unfocusNode) - : FocusScope.of(context).unfocus(), - child: Scaffold( - key: scaffoldKey, - backgroundColor: FlutterFlowTheme.of(context).primary, - appBar: AppBar( - automaticallyImplyLeading: false, - backgroundColor: FlutterFlowTheme.of(context).primary, - title: const HomePageHeaderButtons(), - centerTitle: true, - ), - body: SafeArea( - top: true, - child: SizedBox( - height: MediaQuery.sizeOf(context).height * 1.0, - child: Stack( - children: [ - const Align( - alignment: AlignmentDirectional(0.0, 0.0), - child: BlurBotWidget(), - ), - SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 16), - HomePageSummariesButtons( - model: _model, - dailySummary: dailySummary, - weeklySummary: weeklySummary, - monthlySummary: monthlySummary, - ), - const SizedBox(height: 8), - if (FFAppState().memoryCreationProcessing) const MemoryProcessing(), - Padding( - padding: const EdgeInsetsDirectional.fromSTEB(16.0, 4.0, 16.0, 0.0), - child: Container( - width: double.infinity, - height: MediaQuery.sizeOf(context).height * 0.9, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12.0), - shape: BoxShape.rectangle, - border: Border.all( - color: const Color(0x00E0E3E7), - ), - ), - child: (FFAppState().memories.isEmpty && !FFAppState().memoryCreationProcessing) - ? Center( - child: SizedBox( - width: MediaQuery.sizeOf(context).width * 1.0, - height: MediaQuery.sizeOf(context).height * 0.4, - child: const EmptyMemoriesWidget(), - ), - ) - : ListView.builder( - padding: EdgeInsets.zero, - primary: false, - shrinkWrap: true, - scrollDirection: Axis.vertical, - itemCount: FFAppState().memories.length, - itemBuilder: (context, index) { - return MemoryListItem(memory: FFAppState().memories[index], model: _model); - }, - ), - ), - ), - ], - ), - ) - ], - )), - ), - ), - ), - ); - } -} diff --git a/apps/AppWithWearable/lib/pages/home_page/home_page_model.dart b/apps/AppWithWearable/lib/pages/memories/model.dart similarity index 83% rename from apps/AppWithWearable/lib/pages/home_page/home_page_model.dart rename to apps/AppWithWearable/lib/pages/memories/model.dart index 84a842e014..4f3dc52fc9 100644 --- a/apps/AppWithWearable/lib/pages/home_page/home_page_model.dart +++ b/apps/AppWithWearable/lib/pages/memories/model.dart @@ -1,10 +1,10 @@ import '/flutter_flow/flutter_flow_util.dart'; import '/flutter_flow/instant_timer.dart'; -import 'home_page_widget.dart' show HomePageWidget; +import 'page.dart' show MemoriesPage; import 'package:flutter/material.dart'; -class HomePageModel extends FlutterFlowModel { +class MemoriesPageModel extends FlutterFlowModel { /// Local state fields for this page. bool isShowFullList = true; diff --git a/apps/AppWithWearable/lib/pages/memories/page.dart b/apps/AppWithWearable/lib/pages/memories/page.dart new file mode 100644 index 0000000000..d1783712e9 --- /dev/null +++ b/apps/AppWithWearable/lib/pages/memories/page.dart @@ -0,0 +1,131 @@ +import 'package:friend_private/backend/api_requests/api_calls.dart'; +import 'package:friend_private/backend/storage/memories.dart'; +import 'package:friend_private/flutter_flow/flutter_flow_theme.dart'; +import 'package:friend_private/pages/ble/blur_bot/blur_bot_widget.dart'; +import 'package:friend_private/pages/memories/widgets/summaries_buttons.dart'; + +import '/flutter_flow/flutter_flow_util.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import 'widgets/empty_memories.dart'; +import 'widgets/header_buttons.dart'; +import 'model.dart'; +import 'widgets/memory_list_item.dart'; +import 'widgets/memory_processing.dart'; + +class MemoriesPage extends StatefulWidget { + const MemoriesPage({super.key}); + + @override + State createState() => _MemoriesPageState(); +} + +class _MemoriesPageState extends State { + late MemoriesPageModel _model; + String? dailySummary; + String? weeklySummary; + String? monthlySummary; + + final scaffoldKey = GlobalKey(); + + _dailySummary() async { + List memories = await MemoryStorage.getMemoriesByDay(DateTime.now()); + dailySummary = memories.isNotEmpty ? (await requestSummary(memories)) : null; + } + + _weeklySummary() async { + List memories = await MemoryStorage.getMemoriesOfLastWeek(); + weeklySummary = memories.isNotEmpty ? (await requestSummary(memories)) : null; + } + + _monthlySummary() async { + List memories = await MemoryStorage.getMemoriesOfLastMonth(); + monthlySummary = memories.isNotEmpty ? (await requestSummary(memories)) : null; + } + + @override + void initState() { + super.initState(); + _model = createModel(context, () => MemoriesPageModel()); + _dailySummary(); + _weeklySummary(); + _monthlySummary(); + WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); + } + + @override + void dispose() { + _model.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + context.watch(); + + return Builder( + builder: (context) => GestureDetector( + onTap: () => _model.unfocusNode.canRequestFocus + ? FocusScope.of(context).requestFocus(_model.unfocusNode) + : FocusScope.of(context).unfocus(), + child: Scaffold( + key: scaffoldKey, + backgroundColor: FlutterFlowTheme.of(context).primary, + appBar: AppBar( + automaticallyImplyLeading: false, + backgroundColor: FlutterFlowTheme.of(context).primary, + title: const HomePageHeaderButtons(), + centerTitle: true, + ), + body: SafeArea( + top: true, + child: SizedBox( + height: MediaQuery.sizeOf(context).height * 1.0, + child: Stack( + children: [ + const Align( + alignment: AlignmentDirectional(0.0, 0.0), + child: BlurBotWidget(), + ), + ListView( + children: [ + const SizedBox(height: 16), + HomePageSummariesButtons( + model: _model, + dailySummary: dailySummary, + weeklySummary: weeklySummary, + monthlySummary: monthlySummary, + ), + const SizedBox(height: 8), + if (FFAppState().memoryCreationProcessing) const MemoryProcessing(), + Padding( + padding: const EdgeInsets.all(16), + child: (FFAppState().memories.isEmpty && !FFAppState().memoryCreationProcessing) + ? const Center( + child: Padding( + padding: EdgeInsets.only(top: 32.0), + child: EmptyMemoriesWidget(), + ), + ) + : ListView.builder( + padding: EdgeInsets.zero, + primary: false, + shrinkWrap: true, + scrollDirection: Axis.vertical, + itemCount: FFAppState().memories.length, + itemBuilder: (context, index) { + return MemoryListItem(memory: FFAppState().memories[index], model: _model); + }, + ), + ), + ], + ) + ], + )), + ), + ), + ), + ); + } +} diff --git a/apps/AppWithWearable/lib/pages/home_page/confirm_deletion_widget.dart b/apps/AppWithWearable/lib/pages/memories/widgets/confirm_deletion_widget.dart similarity index 93% rename from apps/AppWithWearable/lib/pages/home_page/confirm_deletion_widget.dart rename to apps/AppWithWearable/lib/pages/memories/widgets/confirm_deletion_widget.dart index 4d297f35e2..612f24af4d 100644 --- a/apps/AppWithWearable/lib/pages/home_page/confirm_deletion_widget.dart +++ b/apps/AppWithWearable/lib/pages/memories/widgets/confirm_deletion_widget.dart @@ -4,8 +4,6 @@ import '/flutter_flow/flutter_flow_util.dart'; import '/flutter_flow/flutter_flow_widgets.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'confirm_deletion_model.dart'; -export 'confirm_deletion_model.dart'; class ConfirmDeletionWidget extends StatefulWidget { const ConfirmDeletionWidget({ @@ -20,29 +18,12 @@ class ConfirmDeletionWidget extends StatefulWidget { } class _ConfirmDeletionWidgetState extends State { - late ConfirmDeletionModel _model; - - @override - void setState(VoidCallback callback) { - super.setState(callback); - _model.onUpdate(); - } - @override void initState() { super.initState(); - _model = createModel(context, () => ConfirmDeletionModel()); - WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); } - @override - void dispose() { - _model.maybeDispose(); - - super.dispose(); - } - @override Widget build(BuildContext context) { return Align( diff --git a/apps/AppWithWearable/lib/pages/home_page/edit_memory_model.dart b/apps/AppWithWearable/lib/pages/memories/widgets/edit_memory_model.dart similarity index 100% rename from apps/AppWithWearable/lib/pages/home_page/edit_memory_model.dart rename to apps/AppWithWearable/lib/pages/memories/widgets/edit_memory_model.dart diff --git a/apps/AppWithWearable/lib/pages/home_page/edit_memory_widget.dart b/apps/AppWithWearable/lib/pages/memories/widgets/edit_memory_widget.dart similarity index 100% rename from apps/AppWithWearable/lib/pages/home_page/edit_memory_widget.dart rename to apps/AppWithWearable/lib/pages/memories/widgets/edit_memory_widget.dart diff --git a/apps/AppWithWearable/lib/pages/home_page/empty_memories.dart b/apps/AppWithWearable/lib/pages/memories/widgets/empty_memories.dart similarity index 98% rename from apps/AppWithWearable/lib/pages/home_page/empty_memories.dart rename to apps/AppWithWearable/lib/pages/memories/widgets/empty_memories.dart index 17e5b5421f..268871f2c9 100644 --- a/apps/AppWithWearable/lib/pages/home_page/empty_memories.dart +++ b/apps/AppWithWearable/lib/pages/memories/widgets/empty_memories.dart @@ -90,6 +90,7 @@ class _EmptyMemoriesWidgetState extends State { GoogleFonts.asMap().containsKey(FlutterFlowTheme.of(context).labelMediumFamily), ), ), + const SizedBox(width: 4), ], ), ], diff --git a/apps/AppWithWearable/lib/pages/home_page/header_buttons.dart b/apps/AppWithWearable/lib/pages/memories/widgets/header_buttons.dart similarity index 100% rename from apps/AppWithWearable/lib/pages/home_page/header_buttons.dart rename to apps/AppWithWearable/lib/pages/memories/widgets/header_buttons.dart diff --git a/apps/AppWithWearable/lib/pages/home_page/memory_list_item.dart b/apps/AppWithWearable/lib/pages/memories/widgets/memory_list_item.dart similarity index 97% rename from apps/AppWithWearable/lib/pages/home_page/memory_list_item.dart rename to apps/AppWithWearable/lib/pages/memories/widgets/memory_list_item.dart index 98920d91d3..8f038ddfa4 100644 --- a/apps/AppWithWearable/lib/pages/home_page/memory_list_item.dart +++ b/apps/AppWithWearable/lib/pages/memories/widgets/memory_list_item.dart @@ -4,15 +4,16 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:friend_private/backend/storage/memories.dart'; import 'package:friend_private/flutter_flow/flutter_flow_theme.dart'; import 'package:friend_private/flutter_flow/flutter_flow_util.dart'; -import 'package:friend_private/pages/home_page/confirm_deletion_widget.dart'; -import 'package:friend_private/pages/home_page/edit_memory_widget.dart'; -import 'package:friend_private/pages/home_page/home_page_model.dart'; +import 'package:friend_private/pages/memories/model.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:share_plus/share_plus.dart'; +import 'confirm_deletion_widget.dart'; +import 'edit_memory_widget.dart'; + class MemoryListItem extends StatefulWidget { final MemoryRecord memory; - final HomePageModel model; + final MemoriesPageModel model; const MemoryListItem({super.key, required this.memory, required this.model}); diff --git a/apps/AppWithWearable/lib/pages/home_page/memory_processing.dart b/apps/AppWithWearable/lib/pages/memories/widgets/memory_processing.dart similarity index 100% rename from apps/AppWithWearable/lib/pages/home_page/memory_processing.dart rename to apps/AppWithWearable/lib/pages/memories/widgets/memory_processing.dart diff --git a/apps/AppWithWearable/lib/pages/home_page/summaries_buttons.dart b/apps/AppWithWearable/lib/pages/memories/widgets/summaries_buttons.dart similarity index 97% rename from apps/AppWithWearable/lib/pages/home_page/summaries_buttons.dart rename to apps/AppWithWearable/lib/pages/memories/widgets/summaries_buttons.dart index ab42df1084..c8e6f76f3e 100644 --- a/apps/AppWithWearable/lib/pages/home_page/summaries_buttons.dart +++ b/apps/AppWithWearable/lib/pages/memories/widgets/summaries_buttons.dart @@ -2,12 +2,12 @@ import 'package:flutter/material.dart'; import 'package:friend_private/backend/schema/enums/enums.dart'; import 'package:friend_private/flutter_flow/flutter_flow_theme.dart'; import 'package:friend_private/flutter_flow/flutter_flow_widgets.dart'; -import 'package:friend_private/pages/home_page/home_page_model.dart'; -import 'package:friend_private/pages/home_page/summary_widget.dart'; +import 'package:friend_private/pages/memories/model.dart'; +import 'package:friend_private/pages/memories/widgets/summary_widget.dart'; import 'package:google_fonts/google_fonts.dart'; class HomePageSummariesButtons extends StatefulWidget { - final HomePageModel model; + final MemoriesPageModel model; final String? dailySummary; final String? weeklySummary; final String? monthlySummary; diff --git a/apps/AppWithWearable/lib/pages/home_page/summary_widget.dart b/apps/AppWithWearable/lib/pages/memories/widgets/summary_widget.dart similarity index 100% rename from apps/AppWithWearable/lib/pages/home_page/summary_widget.dart rename to apps/AppWithWearable/lib/pages/memories/widgets/summary_widget.dart diff --git a/apps/AppWithWearable/lib/pages/permissions/model.dart b/apps/AppWithWearable/lib/pages/permissions/model.dart new file mode 100644 index 0000000000..0f5a7c1a9e --- /dev/null +++ b/apps/AppWithWearable/lib/pages/permissions/model.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import '/flutter_flow/flutter_flow_util.dart'; +import 'page.dart' show PermissionPageWidget; + +class PermissionPageModel extends FlutterFlowModel { + final unfocusNode = FocusNode(); + PageController? pageViewController; + + int get pageViewCurrentIndex => + pageViewController != null && pageViewController!.hasClients && pageViewController!.page != null + ? pageViewController!.page!.round() + : 0; + + @override + void initState(BuildContext context) {} + + @override + void dispose() { + unfocusNode.dispose(); + } +} diff --git a/apps/AppWithWearable/lib/onboarding/permission_page/permission_page_widget.dart b/apps/AppWithWearable/lib/pages/permissions/page.dart similarity index 63% rename from apps/AppWithWearable/lib/onboarding/permission_page/permission_page_widget.dart rename to apps/AppWithWearable/lib/pages/permissions/page.dart index 7b2e061097..ed8d40df53 100644 --- a/apps/AppWithWearable/lib/onboarding/permission_page/permission_page_widget.dart +++ b/apps/AppWithWearable/lib/pages/permissions/page.dart @@ -1,17 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; +import 'package:friend_private/pages/permissions/widgets/list_item.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:smooth_page_indicator/smooth_page_indicator.dart' - as smooth_page_indicator; - -import '/components/items/permissions_list/permissions_list_widget.dart'; +import 'package:smooth_page_indicator/smooth_page_indicator.dart' as smooth_page_indicator; +import '../../backend/schema/enums/enums.dart'; import '/flutter_flow/flutter_flow_animations.dart'; import '/flutter_flow/flutter_flow_theme.dart'; import '/flutter_flow/flutter_flow_util.dart'; import '/flutter_flow/flutter_flow_widgets.dart'; -import 'permission_page_model.dart'; - -export 'permission_page_model.dart'; +import 'model.dart'; class PermissionPageWidget extends StatefulWidget { const PermissionPageWidget({super.key}); @@ -20,8 +17,7 @@ class PermissionPageWidget extends StatefulWidget { State createState() => _PermissionPageWidgetState(); } -class _PermissionPageWidgetState extends State - with TickerProviderStateMixin { +class _PermissionPageWidgetState extends State with TickerProviderStateMixin { late PermissionPageModel _model; final scaffoldKey = GlobalKey(); @@ -42,15 +38,15 @@ class _PermissionPageWidgetState extends State curve: Curves.easeInOut, delay: 200.ms, duration: 300.ms, - begin: Offset(0.0, 20.0), - end: Offset(0.0, 0.0), + begin: const Offset(0.0, 20.0), + end: const Offset(0.0, 0.0), ), ScaleEffect( curve: Curves.easeInOut, delay: 200.ms, duration: 300.ms, - begin: Offset(0.9, 0.9), - end: Offset(1.0, 1.0), + begin: const Offset(0.9, 0.9), + end: const Offset(1.0, 1.0), ), ], ), @@ -69,25 +65,37 @@ class _PermissionPageWidgetState extends State curve: Curves.easeInOut, delay: 300.ms, duration: 300.ms, - begin: Offset(0.0, 20.0), - end: Offset(0.0, 0.0), + begin: const Offset(0.0, 20.0), + end: const Offset(0.0, 0.0), ), ScaleEffect( curve: Curves.easeInOut, delay: 300.ms, duration: 300.ms, - begin: Offset(0.9, 0.9), - end: Offset(1.0, 1.0), + begin: const Offset(0.9, 0.9), + end: const Offset(1.0, 1.0), ), ], ), }; + List permissions = [ + PermissionItemData( + text: 'We need notifications to send feedback and reminders', + icon: Icons.notifications_active_sharp, + permission: Permission.notifs, + isGranted: false), + PermissionItemData( + text: 'We need notifications to send feedback and reminders', + icon: Icons.bluetooth_sharp, + permission: Permission.bluetooth, + isGranted: false), + ]; + @override void initState() { super.initState(); _model = createModel(context, () => PermissionPageModel()); - WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); } @@ -108,15 +116,15 @@ class _PermissionPageWidgetState extends State key: scaffoldKey, backgroundColor: FlutterFlowTheme.of(context).primary, body: Align( - alignment: AlignmentDirectional(0.0, 1.0), + alignment: const AlignmentDirectional(0.0, 1.0), child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Align( - alignment: AlignmentDirectional(1.0, -1.0), + alignment: const AlignmentDirectional(1.0, -1.0), child: Padding( - padding: EdgeInsetsDirectional.fromSTEB(0.0, 40.0, 40.0, 0.0), + padding: const EdgeInsetsDirectional.fromSTEB(0.0, 40.0, 40.0, 0.0), child: InkWell( splashColor: Colors.transparent, focusColor: Colors.transparent, @@ -128,28 +136,25 @@ class _PermissionPageWidgetState extends State child: Text( 'Skip', style: FlutterFlowTheme.of(context).bodyMedium.override( - fontFamily: - FlutterFlowTheme.of(context).bodyMediumFamily, + fontFamily: FlutterFlowTheme.of(context).bodyMediumFamily, letterSpacing: 0.0, - useGoogleFonts: GoogleFonts.asMap().containsKey( - FlutterFlowTheme.of(context).bodyMediumFamily), + useGoogleFonts: + GoogleFonts.asMap().containsKey(FlutterFlowTheme.of(context).bodyMediumFamily), ), ), ), ), ), Expanded( - child: Container( + child: SizedBox( width: double.infinity, height: MediaQuery.sizeOf(context).height * 0.3, child: Stack( children: [ Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0.0, 0.0, 0.0, 50.0), + padding: const EdgeInsetsDirectional.fromSTEB(0.0, 0.0, 0.0, 50.0), child: PageView( - controller: _model.pageViewController ??= - PageController(initialPage: 0), + controller: _model.pageViewController ??= PageController(initialPage: 0), scrollDirection: Axis.horizontal, children: [ Column( @@ -157,10 +162,9 @@ class _PermissionPageWidgetState extends State mainAxisAlignment: MainAxisAlignment.end, children: [ Align( - alignment: AlignmentDirectional(0.0, 0.0), + alignment: const AlignmentDirectional(0.0, 0.0), child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 0.0, 0.0, 0.0, 12.0), + padding: const EdgeInsetsDirectional.fromSTEB(0.0, 0.0, 0.0, 12.0), child: ClipRRect( borderRadius: BorderRadius.circular(8.0), child: Image.asset( @@ -175,52 +179,41 @@ class _PermissionPageWidgetState extends State Text( 'We need these permissions', textAlign: TextAlign.center, - style: FlutterFlowTheme.of(context) - .displaySmall - .override( - fontFamily: FlutterFlowTheme.of(context) - .displaySmallFamily, + style: FlutterFlowTheme.of(context).displaySmall.override( + fontFamily: FlutterFlowTheme.of(context).displaySmallFamily, fontSize: 24.0, letterSpacing: 0.0, fontWeight: FontWeight.bold, useGoogleFonts: GoogleFonts.asMap() - .containsKey( - FlutterFlowTheme.of(context) - .displaySmallFamily), + .containsKey(FlutterFlowTheme.of(context).displaySmallFamily), lineHeight: 1.5, ), - ).animateOnPageLoad( - animationsMap['textOnPageLoadAnimation1']!), + ).animateOnPageLoad(animationsMap['textOnPageLoadAnimation1']!), Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 0.0, 4.0, 0.0, 0.0), + padding: const EdgeInsetsDirectional.fromSTEB(0.0, 4.0, 0.0, 0.0), child: Text( 'To get started with your new AI notetaker', textAlign: TextAlign.center, - style: FlutterFlowTheme.of(context) - .labelLarge - .override( - fontFamily: - FlutterFlowTheme.of(context) - .labelLargeFamily, + style: FlutterFlowTheme.of(context).labelLarge.override( + fontFamily: FlutterFlowTheme.of(context).labelLargeFamily, letterSpacing: 0.0, useGoogleFonts: GoogleFonts.asMap() - .containsKey( - FlutterFlowTheme.of(context) - .labelLargeFamily), + .containsKey(FlutterFlowTheme.of(context).labelLargeFamily), lineHeight: 1.5, ), - ).animateOnPageLoad(animationsMap[ - 'textOnPageLoadAnimation2']!), + ).animateOnPageLoad(animationsMap['textOnPageLoadAnimation2']!), ), Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 0.0, 40.0, 0.0, 40.0), - child: wrapWithModel( - model: _model.permissionsListModel, - updateCallback: () => setState(() {}), - child: PermissionsListWidget(), - ), + padding: const EdgeInsetsDirectional.fromSTEB(0.0, 56.0, 0.0, 40.0), + child: ListView( + padding: EdgeInsets.zero, + shrinkWrap: true, + scrollDirection: Axis.vertical, + children: permissions + .map((p) => PermissionListItem( + permission: p, + )) + .toList()), ), ], ), @@ -228,19 +221,17 @@ class _PermissionPageWidgetState extends State ), ), Align( - alignment: AlignmentDirectional(0.0, 1.0), + alignment: const AlignmentDirectional(0.0, 1.0), child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 0.0, 0.0, 0.0, 10.0), + padding: const EdgeInsetsDirectional.fromSTEB(0.0, 0.0, 0.0, 10.0), child: smooth_page_indicator.SmoothPageIndicator( - controller: _model.pageViewController ??= - PageController(initialPage: 0), + controller: _model.pageViewController ??= PageController(initialPage: 0), count: 1, axisDirection: Axis.horizontal, onDotClicked: (i) async { await _model.pageViewController!.animateToPage( i, - duration: Duration(milliseconds: 500), + duration: const Duration(milliseconds: 500), curve: Curves.ease, ); }, @@ -251,8 +242,7 @@ class _PermissionPageWidgetState extends State dotWidth: 8.0, dotHeight: 8.0, dotColor: FlutterFlowTheme.of(context).alternate, - activeDotColor: - FlutterFlowTheme.of(context).primaryText, + activeDotColor: FlutterFlowTheme.of(context).primaryText, paintStyle: PaintingStyle.fill, ), ), @@ -263,18 +253,14 @@ class _PermissionPageWidgetState extends State ), ), Padding( - padding: EdgeInsetsDirectional.fromSTEB(0.0, 0.0, 0.0, 20.0), + padding: const EdgeInsetsDirectional.fromSTEB(0.0, 0.0, 0.0, 20.0), child: Column( mainAxisSize: MainAxisSize.max, children: [ Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0.0, 20.0, 0.0, 20.0), + padding: const EdgeInsetsDirectional.fromSTEB(0.0, 20.0, 0.0, 20.0), child: FFButtonWidget( - onPressed: !(_model.permissionsListModel - .itemPermissionModel1.isOn && - _model.permissionsListModel.itemPermissionModel2 - .isOn) + onPressed: !(permissions.every((p) => p.isGranted)) ? null : () async { context.goNamed('scanDevices'); @@ -282,27 +268,23 @@ class _PermissionPageWidgetState extends State text: 'Next', options: FFButtonOptions( height: 60.0, - padding: EdgeInsetsDirectional.fromSTEB( - 40.0, 0.0, 40.0, 0.0), - iconPadding: EdgeInsetsDirectional.fromSTEB( - 0.0, 0.0, 0.0, 0.0), + padding: const EdgeInsetsDirectional.fromSTEB(40.0, 0.0, 40.0, 0.0), + iconPadding: const EdgeInsetsDirectional.fromSTEB(0.0, 0.0, 0.0, 0.0), color: FlutterFlowTheme.of(context).secondary, - textStyle: - FlutterFlowTheme.of(context).titleSmall.override( - fontFamily: 'SF Pro Display', - color: FlutterFlowTheme.of(context).primary, - fontSize: 20.0, - letterSpacing: 0.0, - fontWeight: FontWeight.bold, - useGoogleFonts: GoogleFonts.asMap() - .containsKey('SF Pro Display'), - ), + textStyle: FlutterFlowTheme.of(context).titleSmall.override( + fontFamily: 'SF Pro Display', + color: FlutterFlowTheme.of(context).primary, + fontSize: 20.0, + letterSpacing: 0.0, + fontWeight: FontWeight.bold, + useGoogleFonts: GoogleFonts.asMap().containsKey('SF Pro Display'), + ), elevation: 3.0, - borderSide: BorderSide( + borderSide: const BorderSide( width: 1.0, ), borderRadius: BorderRadius.circular(30.0), - disabledColor: Color(0x63F7F4F4), + disabledColor: const Color(0x63F7F4F4), ), ), ), diff --git a/apps/AppWithWearable/lib/components/items/item_permission/item_permission_widget.dart b/apps/AppWithWearable/lib/pages/permissions/widgets/list_item.dart similarity index 62% rename from apps/AppWithWearable/lib/components/items/item_permission/item_permission_widget.dart rename to apps/AppWithWearable/lib/pages/permissions/widgets/list_item.dart index 973052fd71..d8a96999c0 100644 --- a/apps/AppWithWearable/lib/components/items/item_permission/item_permission_widget.dart +++ b/apps/AppWithWearable/lib/pages/permissions/widgets/list_item.dart @@ -3,68 +3,55 @@ import 'package:google_fonts/google_fonts.dart'; import '/backend/schema/enums/enums.dart'; import '/flutter_flow/flutter_flow_theme.dart'; -import '/flutter_flow/flutter_flow_util.dart'; import '/flutter_flow/permissions_util.dart'; -import 'item_permission_model.dart'; -export 'item_permission_model.dart'; +class PermissionItemData { + final String text; + final IconData icon; + final Permission permission; + bool isGranted; -class ItemPermissionWidget extends StatefulWidget { - const ItemPermissionWidget({ - super.key, + PermissionItemData({ required this.text, - this.icon, + required this.icon, required this.permission, + required this.isGranted, }); - - final String? text; - final Widget? icon; - final Permission? permission; - - @override - State createState() => _ItemPermissionWidgetState(); } -class _ItemPermissionWidgetState extends State { - late ItemPermissionModel _model; +class PermissionListItem extends StatefulWidget { + final PermissionItemData permission; - @override - void setState(VoidCallback callback) { - super.setState(callback); - _model.onUpdate(); - } + const PermissionListItem({super.key, required this.permission}); @override - void initState() { - super.initState(); - _model = createModel(context, () => ItemPermissionModel()); - - WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); - } - - @override - void dispose() { - _model.maybeDispose(); + State createState() => _PermissionListItemState(); +} - super.dispose(); - } +class _PermissionListItemState extends State { + // @override + // void initState() { + // super.initState(); + // + // WidgetsBinding.instance.addPostFrameCallback((_) => setState(() {})); + // } @override Widget build(BuildContext context) { return Padding( - padding: EdgeInsetsDirectional.fromSTEB(8.0, 8.0, 8.0, 8.0), + padding: const EdgeInsetsDirectional.fromSTEB(8.0, 8.0, 8.0, 8.0), child: InkWell( splashColor: Colors.transparent, focusColor: Colors.transparent, hoverColor: Colors.transparent, highlightColor: Colors.transparent, onTap: () async { - if (widget.permission == Permission.bluetooth) { + if (widget.permission.permission == Permission.bluetooth) { await requestPermission(bluetoothPermission); if (!(await getPermissionStatus(bluetoothPermission))) { return; } - } else if (widget.permission == Permission.notifs) { + } else if (widget.permission.permission == Permission.notifs) { await requestPermission(notificationsPermission); if (!(await getPermissionStatus(notificationsPermission))) { return; @@ -72,7 +59,7 @@ class _ItemPermissionWidgetState extends State { } setState(() { - _model.isOn = true; + widget.permission.isGranted = true; }); }, child: Container( @@ -81,7 +68,7 @@ class _ItemPermissionWidgetState extends State { color: FlutterFlowTheme.of(context).primary, ), child: Padding( - padding: EdgeInsetsDirectional.fromSTEB(16.0, 8.0, 8.0, 8.0), + padding: const EdgeInsetsDirectional.fromSTEB(16.0, 8.0, 8.0, 8.0), child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -93,41 +80,43 @@ class _ItemPermissionWidgetState extends State { color: FlutterFlowTheme.of(context).primary, shape: BoxShape.circle, ), - alignment: AlignmentDirectional(0.0, 0.0), + alignment: const AlignmentDirectional(0.0, 0.0), child: Stack( children: [ - widget.icon!, + Icon( + widget.permission.icon, + color: FlutterFlowTheme.of(context).secondary, + size: 24.0, + ), ], ), ), Expanded( child: Padding( - padding: - EdgeInsetsDirectional.fromSTEB(12.0, 0.0, 0.0, 0.0), + padding: const EdgeInsetsDirectional.fromSTEB(12.0, 0.0, 0.0, 0.0), child: Text( () { - if (widget.permission == Permission.microphone) { + if (widget.permission.permission == Permission.microphone) { return 'We need access to your microphone'; - } else if (widget.permission == Permission.bluetooth) { + } else if (widget.permission.permission == Permission.bluetooth) { return 'We need access to your Bluetooth'; } else { - return 'We need access to send you notifications'; + return 'We need access to send you notifications'; } }(), style: FlutterFlowTheme.of(context).bodyMedium.override( - fontFamily: - FlutterFlowTheme.of(context).bodyMediumFamily, + fontFamily: FlutterFlowTheme.of(context).bodyMediumFamily, letterSpacing: 0.0, fontWeight: FontWeight.bold, - useGoogleFonts: GoogleFonts.asMap().containsKey( - FlutterFlowTheme.of(context).bodyMediumFamily), + useGoogleFonts: + GoogleFonts.asMap().containsKey(FlutterFlowTheme.of(context).bodyMediumFamily), ), ), ), ), Builder( builder: (context) { - if (_model.isOn) { + if (widget.permission.isGranted) { return Icon( Icons.check_circle, color: FlutterFlowTheme.of(context).secondary, diff --git a/apps/AppWithWearable/lib/onboarding/welcome/welcome_model.dart b/apps/AppWithWearable/lib/pages/welcome/model.dart similarity index 57% rename from apps/AppWithWearable/lib/onboarding/welcome/welcome_model.dart rename to apps/AppWithWearable/lib/pages/welcome/model.dart index 133e35dd93..352b77f508 100644 --- a/apps/AppWithWearable/lib/onboarding/welcome/welcome_model.dart +++ b/apps/AppWithWearable/lib/pages/welcome/model.dart @@ -1,29 +1,21 @@ import 'package:flutter/material.dart'; -import '/components/logo/logo_main/logo_main_widget.dart'; import '/flutter_flow/flutter_flow_util.dart'; import '/pages/ble/blur_bot/blur_bot_widget.dart'; -import 'welcome_widget.dart' show WelcomeWidget; +import 'page.dart' show WelcomeWidget; class WelcomeModel extends FlutterFlowModel { - /// State fields for stateful widgets in this page. - final unfocusNode = FocusNode(); - // Model for blurBot component. late BlurBotModel blurBotModel; - // Model for logo_main component. - late LogoMainModel logoMainModel; @override void initState(BuildContext context) { blurBotModel = createModel(context, () => BlurBotModel()); - logoMainModel = createModel(context, () => LogoMainModel()); } @override void dispose() { unfocusNode.dispose(); blurBotModel.dispose(); - logoMainModel.dispose(); } } diff --git a/apps/AppWithWearable/lib/onboarding/welcome/welcome_widget.dart b/apps/AppWithWearable/lib/pages/welcome/page.dart similarity index 81% rename from apps/AppWithWearable/lib/onboarding/welcome/welcome_widget.dart rename to apps/AppWithWearable/lib/pages/welcome/page.dart index d28dea9cc3..33a00d22aa 100644 --- a/apps/AppWithWearable/lib/onboarding/welcome/welcome_widget.dart +++ b/apps/AppWithWearable/lib/pages/welcome/page.dart @@ -1,18 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:flutter_tilt/flutter_tilt.dart'; -import '/components/logo/logo_main/logo_main_widget.dart'; import '/flutter_flow/flutter_flow_theme.dart'; import '/flutter_flow/flutter_flow_util.dart'; -import '/flutter_flow/flutter_flow_widgets.dart'; import '/pages/ble/blur_bot/blur_bot_widget.dart'; -import 'welcome_model.dart'; +import 'model.dart'; import 'package:lottie/lottie.dart'; import 'dart:math'; -import 'dart:ui'; import 'package:permission_handler/permission_handler.dart'; class WelcomeWidget extends StatefulWidget { @@ -22,8 +18,7 @@ class WelcomeWidget extends StatefulWidget { State createState() => _WelcomeWidgetState(); } -class _WelcomeWidgetState extends State - with SingleTickerProviderStateMixin { +class _WelcomeWidgetState extends State with SingleTickerProviderStateMixin { late WelcomeModel _model; late AnimationController _animationController; late Animation _animation; @@ -42,11 +37,10 @@ class _WelcomeWidgetState extends State // Initialize the animation controller and animation _animationController = AnimationController( vsync: this, - duration: Duration(seconds: 1), + duration: const Duration(seconds: 1), )..repeat(reverse: true); - _animation = Tween(begin: pi / 200, end: pi / 200) - .animate(_animationController); + _animation = Tween(begin: pi / 200, end: pi / 200).animate(_animationController); // Initialize the bounce animation _bounceAnimation = Tween(begin: 0, end: 10).animate( @@ -65,8 +59,7 @@ class _WelcomeWidgetState extends State _animationController.dispose(); // Show the status bar again when the widget is disposed - SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, - overlays: SystemUiOverlay.values); + SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: SystemUiOverlay.values); super.dispose(); } @@ -85,16 +78,16 @@ class _WelcomeWidgetState extends State wrapWithModel( model: _model.blurBotModel, updateCallback: () => setState(() {}), - child: BlurBotWidget(), + child: const BlurBotWidget(), ), Column( mainAxisSize: MainAxisSize.max, children: [ Expanded( child: Tilt( - shadowConfig: ShadowConfig(disable: true), - lightConfig: LightConfig(disable: true), - tiltConfig: TiltConfig( + shadowConfig: const ShadowConfig(disable: true), + lightConfig: const LightConfig(disable: true), + tiltConfig: const TiltConfig( angle: 30, moveDuration: Duration(milliseconds: 0), ), @@ -120,8 +113,7 @@ class _WelcomeWidgetState extends State Align( alignment: Alignment.bottomCenter, child: Padding( - padding: - EdgeInsetsDirectional.fromSTEB(30.0, 80.0, 30.0, 40.0), + padding: const EdgeInsetsDirectional.fromSTEB(30.0, 80.0, 30.0, 40.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -129,20 +121,18 @@ class _WelcomeWidgetState extends State Text( "Friend helps you remember everything", textAlign: TextAlign.start, - style: - FlutterFlowTheme.of(context).bodyMedium.override( - fontFamily: 'SF Pro Display', - color: Colors.white, - fontSize: 29.0, - letterSpacing: 0.0, - fontWeight: FontWeight.w900, - useGoogleFonts: GoogleFonts.asMap() - .containsKey('SF Pro Display'), - lineHeight: 1.2, - ), + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'SF Pro Display', + color: Colors.white, + fontSize: 29.0, + letterSpacing: 0.0, + fontWeight: FontWeight.w900, + useGoogleFonts: GoogleFonts.asMap().containsKey('SF Pro Display'), + lineHeight: 1.2, + ), ), - SizedBox(height: 10.0), - Text( + const SizedBox(height: 10.0), + const Text( "Your Personal growth journey with AI that listens to your every word", style: TextStyle( color: Color.fromARGB(255, 101, 101, 101), @@ -151,11 +141,11 @@ class _WelcomeWidgetState extends State height: 1.5, ), ), - SizedBox(height: 20.0), + const SizedBox(height: 20.0), Center( child: Material( elevation: 4.0, - shape: CircleBorder(), + shape: const CircleBorder(), child: InkWell( splashColor: Colors.transparent, focusColor: Colors.transparent, @@ -164,25 +154,20 @@ class _WelcomeWidgetState extends State mouseCursor: SystemMouseCursors.click, onTap: () async { // Check if Bluetooth permission is granted - PermissionStatus bluetoothStatus = - await Permission.bluetooth.status; + PermissionStatus bluetoothStatus = await Permission.bluetooth.status; if (bluetoothStatus.isGranted) { // Bluetooth permission is already granted // Request notification permission - PermissionStatus notificationStatus = - await Permission.notification.request(); + PermissionStatus notificationStatus = await Permission.notification.request(); // Navigate to the 'scanDevices' screen context.goNamed('findDevices'); } else { // Bluetooth permission is not granted - if (await Permission.bluetooth - .request() - .isGranted) { + if (await Permission.bluetooth.request().isGranted) { // Bluetooth permission is granted now // Request notification permission - PermissionStatus notificationStatus = - await Permission.notification.request(); + PermissionStatus notificationStatus = await Permission.notification.request(); // Navigate to the 'scanDevices' screen context.goNamed('findDevices'); @@ -194,7 +179,7 @@ class _WelcomeWidgetState extends State builder: (BuildContext context) { return AlertDialog( backgroundColor: Colors.grey[900], - title: Text( + title: const Text( 'Bluetooth Required', style: TextStyle( color: Colors.white, @@ -202,7 +187,7 @@ class _WelcomeWidgetState extends State fontWeight: FontWeight.bold, ), ), - content: Text( + content: const Text( 'This app needs Bluetooth to function properly. Please enable it in the settings.', style: TextStyle( color: Colors.white, @@ -215,7 +200,7 @@ class _WelcomeWidgetState extends State Navigator.of(context).pop(); openAppSettings(); }, - child: Text( + child: const Text( 'OK', style: TextStyle( color: Colors.blue, @@ -236,17 +221,14 @@ class _WelcomeWidgetState extends State return RadialGradient( center: Alignment.center, radius: 0.45, - colors: [ - Colors.white, - Colors.white.withOpacity(0.0) - ], + colors: [Colors.white, Colors.white.withOpacity(0.0)], stops: [0.9, 1.0], ).createShader(bounds); }, child: Container( width: 100, height: 100, - decoration: BoxDecoration( + decoration: const BoxDecoration( shape: BoxShape.circle, ), child: Transform( @@ -270,7 +252,7 @@ class _WelcomeWidgetState extends State Center( child: Transform.rotate( angle: -pi / 4, - child: Icon( + child: const Icon( Icons.arrow_forward, size: 60, color: Colors.white, diff --git a/apps/AppWithWearable/pubspec.yaml b/apps/AppWithWearable/pubspec.yaml index f76fe5b4e8..ba4b608a9f 100644 --- a/apps/AppWithWearable/pubspec.yaml +++ b/apps/AppWithWearable/pubspec.yaml @@ -53,6 +53,7 @@ dependencies: flutter_tilt: any path: ^1.9.0 uuid: ^4.4.0 + tuple: ^2.0.2 dependency_overrides: http: 1.2.0