diff --git a/app/lib/backend/auth.dart b/app/lib/backend/auth.dart index 165a360fa2b..1319c3ad51d 100644 --- a/app/lib/backend/auth.dart +++ b/app/lib/backend/auth.dart @@ -110,7 +110,8 @@ Future signInWithGoogle() async { try { debugPrint('Signing in with Google'); // Trigger the authentication flow - final GoogleSignInAccount? googleUser = await GoogleSignIn(scopes: ['profile', 'email']).signIn(); + final GoogleSignInAccount? googleUser = await GoogleSignIn(scopes: ['profile', 'email'] + ).signIn(); debugPrint('Google User: $googleUser'); // Obtain the auth details from the request @@ -143,6 +144,11 @@ Future signInWithGoogle() async { SharedPreferencesUtil().givenName = givenName; SharedPreferencesUtil().familyName = familyName; } + // To reduce unnecessary invoking of getIdToken() on web + IdTokenResult? tokenResult = await FirebaseAuth.instance.currentUser?.getIdTokenResult(); + SharedPreferencesUtil().authToken = await FirebaseAuth.instance.currentUser?.getIdToken()?? ''; + SharedPreferencesUtil().tokenExpirationTime = tokenResult?.expirationTime?.millisecondsSinceEpoch ?? 0 ; + // TODO: test subsequent signIn debugPrint('signInWithGoogle Email: ${SharedPreferencesUtil().email}'); debugPrint('signInWithGoogle Name: ${SharedPreferencesUtil().givenName}'); diff --git a/app/lib/backend/http/api/apps.dart b/app/lib/backend/http/api/apps.dart index cc0fcea70a0..bc3324a4a8b 100644 --- a/app/lib/backend/http/api/apps.dart +++ b/app/lib/backend/http/api/apps.dart @@ -1,8 +1,9 @@ import 'dart:convert'; import 'dart:developer'; -import 'dart:io'; - +import 'package:http_parser/http_parser.dart'; +import 'package:mime/mime.dart'; import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:omi/backend/http/shared.dart'; import 'package:omi/backend/preferences.dart'; import 'package:omi/backend/schema/app.dart'; @@ -75,12 +76,20 @@ Future reviewApp(String appId, AppReview review) async { } } -Future> uploadAppThumbnail(File file) async { +Future> uploadAppThumbnail(XFile file) async { var request = http.MultipartRequest( 'POST', Uri.parse('${Env.apiBaseUrl}v1/app/thumbnails'), ); - request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path))); + String filename = basename(file.path); + request.files.add( + http.MultipartFile.fromBytes( + 'file', + await file.readAsBytes(), + contentType: MediaType.parse(lookupMimeType(filename) ?? 'application/octet-stream'), + filename: filename, + ), + ); request.headers.addAll({'Authorization': await getAuthHeader()}); try { @@ -185,12 +194,20 @@ Future isAppSetupCompleted(String? url) async { } } -Future<(bool, String, String?)> submitAppServer(File file, Map appData) async { +Future<(bool, String, String?)> submitAppServer(XFile file, Map appData) async { var request = http.MultipartRequest( 'POST', Uri.parse('${Env.apiBaseUrl}v1/apps'), ); - request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path))); + String filename = basename(file.path); + request.files.add( + http.MultipartFile.fromBytes( + 'file', + await file.readAsBytes(), + contentType: MediaType.parse(lookupMimeType(filename) ?? 'application/octet-stream'), + filename: filename, + ), + ); request.headers.addAll({'Authorization': await getAuthHeader()}); request.fields.addAll({'app_data': jsonEncode(appData)}); debugPrint(jsonEncode(appData)); @@ -221,13 +238,14 @@ Future<(bool, String, String?)> submitAppServer(File file, Map } } -Future updateAppServer(File? file, Map appData) async { +Future updateAppServer(XFile? file, Map appData) async { var request = http.MultipartRequest( 'PATCH', Uri.parse('${Env.apiBaseUrl}v1/apps/${appData['id']}'), ); if (file != null) { - request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path))); + var bytes = await file.readAsBytes(); + request.files.add(http.MultipartFile.fromBytes('file', bytes, filename: basename(file.path))); } request.headers.addAll({'Authorization': await getAuthHeader()}); request.fields.addAll({'app_data': jsonEncode(appData)}); @@ -474,12 +492,20 @@ Future deleteApiKeyServer(String appId, String keyId) async { } } -Future createPersonaApp(File file, Map personaData) async { +Future createPersonaApp(XFile file, Map personaData) async { var request = http.MultipartRequest( 'POST', Uri.parse('${Env.apiBaseUrl}v1/personas'), ); - request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path))); + String filename = basename(file.path); + request.files.add( + http.MultipartFile.fromBytes( + 'file', + await file.readAsBytes(), + filename: filename, + contentType: MediaType.parse(lookupMimeType(filename) ?? 'application/octet-stream'), + ), + ); request.headers.addAll({'Authorization': await getAuthHeader()}); request.fields.addAll({'persona_data': jsonEncode(personaData)}); print(jsonEncode(personaData)); @@ -500,13 +526,21 @@ Future createPersonaApp(File file, Map personaData) async } } -Future updatePersonaApp(File? file, Map personaData) async { +Future updatePersonaApp(XFile? file, Map personaData) async { var request = http.MultipartRequest( 'PATCH', Uri.parse('${Env.apiBaseUrl}v1/personas/${personaData['id']}'), ); if (file != null) { - request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path))); + String filename = basename(file.path); + request.files.add( + http.MultipartFile.fromBytes( + 'file', + await file.readAsBytes(), + contentType: MediaType.parse(lookupMimeType(filename) ?? 'application/octet-stream'), + filename: filename, + ), + ); } request.headers.addAll({'Authorization': await getAuthHeader()}); request.fields.addAll({'persona_data': jsonEncode(personaData)}); diff --git a/app/lib/backend/http/api/conversations.dart b/app/lib/backend/http/api/conversations.dart index acd9604da48..c2d4f893f55 100644 --- a/app/lib/backend/http/api/conversations.dart +++ b/app/lib/backend/http/api/conversations.dart @@ -1,7 +1,9 @@ import 'dart:convert'; -import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:http_parser/http_parser.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:mime/mime.dart'; import 'package:omi/backend/http/shared.dart'; import 'package:omi/backend/schema/conversation.dart'; import 'package:omi/backend/schema/structured.dart'; @@ -9,6 +11,7 @@ import 'package:omi/backend/schema/transcript_segment.dart'; import 'package:omi/env/env.dart'; import 'package:http/http.dart' as http; import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:omi/utils/omi_file/omi_file.dart'; import 'package:path/path.dart'; Future processInProgressConversation() async { @@ -300,13 +303,19 @@ Future deleteConversationActionItem(String conversationId, ActionItem item } //this is expected to return complete memories -Future> sendStorageToBackend(File file, String sdCardDateTimeString) async { +Future> sendStorageToBackend(XFile file, String sdCardDateTimeString) async { var request = http.MultipartRequest( 'POST', Uri.parse('${Env.apiBaseUrl}sdcard_memory?date_time=$sdCardDateTimeString'), ); request.headers.addAll({'Authorization': await getAuthHeader()}); - request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path))); + String filename = basename(file.path); + request.files.add(http.MultipartFile.fromBytes( + 'file', + await file.readAsBytes(), + contentType: MediaType.parse(lookupMimeType(filename) ?? 'application/octet-stream'), + filename: filename, + )); try { var streamedResponse = await request.send(); var response = await http.Response.fromStream(streamedResponse); @@ -330,13 +339,21 @@ Future> sendStorageToBackend(File file, String sdCardDa } } -Future syncLocalFiles(List files) async { +Future syncLocalFiles(List files) async { var request = http.MultipartRequest( 'POST', Uri.parse('${Env.apiBaseUrl}v1/sync-local-files'), ); for (var file in files) { - request.files.add(await http.MultipartFile.fromPath('files', file.path, filename: basename(file.path))); + String filename = basename(file.path); + request.files.add( + http.MultipartFile.fromBytes( + 'files', + await file.readAsBytes(), + contentType: MediaType.parse(lookupMimeType(filename) ?? 'application/octet-stream'), + filename: filename, + ), + ); } request.headers.addAll({'Authorization': await getAuthHeader()}); diff --git a/app/lib/backend/http/api/messages.dart b/app/lib/backend/http/api/messages.dart index d804ab6a09b..6b07a6a5dd5 100644 --- a/app/lib/backend/http/api/messages.dart +++ b/app/lib/backend/http/api/messages.dart @@ -1,11 +1,14 @@ import 'dart:convert'; -import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:http_parser/http_parser.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:mime/mime.dart'; import 'package:omi/backend/http/shared.dart'; import 'package:omi/backend/schema/message.dart'; import 'package:omi/env/env.dart'; import 'package:omi/utils/logger.dart'; +import 'package:omi/utils/omi_file/omi_file.dart'; import 'package:omi/utils/other/string_utils.dart'; import 'package:http/http.dart' as http; import 'package:instabug_flutter/instabug_flutter.dart'; @@ -110,25 +113,36 @@ Stream sendMessageStreamServer(String text, {String? appId, } try { - final request = await HttpClient().postUrl(Uri.parse(url)); - request.headers.set('Authorization', await getAuthHeader()); - request.headers.contentType = ContentType.json; - request.write(jsonEncode({'text': text, 'file_ids': filesId})); + final uri = Uri.parse(url); + final headers = { + 'Authorization': await getAuthHeader(), + 'Content-Type': 'application/json', + }; + final body = jsonEncode({'text': text, 'file_ids': filesId}); - final response = await request.close(); + // Create a client to handle the streamed response + var client = http.Client(); + var request = http.Request('POST', uri); + request.headers.addAll(headers); + request.body = body; + + var response = await client.send(request); if (response.statusCode != 200) { Logger.error('Failed to send message: ${response.statusCode}'); yield ServerMessageChunk.failedMessage(); + client.close(); return; } - var buffers = []; var messageId = "1000"; // Default new message - await for (var data in response.transform(utf8.decoder)) { - var lines = data.split('\n\n'); + var buffers = []; + + // Process the streamed response + await for (var data in response.stream.transform(utf8.decoder)) { + var lines = data.split('\n\n'); // Make sure this is the correct delimiter for your server responses for (var line in lines.where((line) => line.isNotEmpty)) { - // Dealing w/ the package spliting by 1024 bytes in dart + // Dealing w/ the package splitting by certain byte size in http package // Waiting for the next package if (line.length >= 1024) { buffers.add(line); @@ -156,6 +170,8 @@ Stream sendMessageStreamServer(String text, {String? appId, yield messageChunk; } } + + client.close(); } catch (e) { Logger.error('Error sending message: $e'); yield ServerMessageChunk.failedMessage(); @@ -178,13 +194,19 @@ Future getInitialAppMessage(String? appId) { }); } -Stream sendVoiceMessageStreamServer(List files) async* { +Stream sendVoiceMessageStreamServer(List files) async* { var request = http.MultipartRequest( 'POST', Uri.parse('${Env.apiBaseUrl}v2/voice-messages'), ); for (var file in files) { - request.files.add(await http.MultipartFile.fromPath('files', file.path, filename: basename(file.path))); + String filename = basename(file.path); + request.files.add(http.MultipartFile.fromBytes( + 'files', + await file.readAsBytes(), + filename: filename, + contentType: MediaType.parse(lookupMimeType(filename) ?? 'application/octet-stream'), + )); } request.headers.addAll({'Authorization': await getAuthHeader()}); @@ -235,13 +257,21 @@ Stream sendVoiceMessageStreamServer(List files) async* } } -Future> sendVoiceMessageServer(List files) async { +Future> sendVoiceMessageServer(List files) async { var request = http.MultipartRequest( 'POST', Uri.parse('${Env.apiBaseUrl}v1/voice-messages'), ); for (var file in files) { - request.files.add(await http.MultipartFile.fromPath('files', file.path, filename: basename(file.path))); + String filename = basename(file.path); + request.files.add( + http.MultipartFile.fromBytes( + 'files', + await file.readAsBytes(), + filename: filename, + contentType: MediaType.parse(lookupMimeType(filename) ?? 'application/octet-stream'), + ), + ); } request.headers.addAll({'Authorization': await getAuthHeader()}); @@ -261,7 +291,7 @@ Future> sendVoiceMessageServer(List files) async { } } -Future?> uploadFilesServer(List files, {String? appId}) async { +Future?> uploadFilesServer(List files, {String? appId}) async { var url = '${Env.apiBaseUrl}v1/files?plugin_id=$appId'; if (appId == null || appId.isEmpty || appId == 'null' || appId == 'no_selected') { url = '${Env.apiBaseUrl}v1/files'; @@ -312,20 +342,25 @@ Future reportMessageServer(String messageId) async { } } - -Future transcribeVoiceMessage(File audioFile) async { +Future transcribeVoiceMessage(OmiFile audioFile) async { try { var request = http.MultipartRequest( 'POST', Uri.parse('${Env.apiBaseUrl}v1/voice-message/transcribe'), ); - + request.headers.addAll({'Authorization': await getAuthHeader()}); - request.files.add(await http.MultipartFile.fromPath('files', audioFile.path)); - + String filename = basename(audioFile.path); + request.files.add(http.MultipartFile.fromBytes( + 'files', + await audioFile.readAsBytes(), + filename: filename, + contentType: MediaType.parse(lookupMimeType(filename) ?? 'application/octet-stream'), + )); + var streamedResponse = await request.send(); var response = await http.Response.fromStream(streamedResponse); - + if (response.statusCode == 200) { final data = jsonDecode(response.body); return data['transcript'] ?? ''; diff --git a/app/lib/backend/http/api/speech_profile.dart b/app/lib/backend/http/api/speech_profile.dart index 5032a015682..e0e5dcf0282 100644 --- a/app/lib/backend/http/api/speech_profile.dart +++ b/app/lib/backend/http/api/speech_profile.dart @@ -1,7 +1,9 @@ import 'dart:convert'; -import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:http_parser/http_parser.dart'; +import 'package:image_picker/image_picker.dart'; +import 'package:mime/mime.dart'; import 'package:omi/backend/http/shared.dart'; import 'package:omi/env/env.dart'; import 'package:http/http.dart' as http; @@ -35,12 +37,18 @@ Future getUserSpeechProfile() async { return null; } -Future uploadProfile(File file) async { +Future uploadProfile(XFile file) async { var request = http.MultipartRequest( 'POST', Uri.parse('${Env.apiBaseUrl}v3/upload-audio'), ); - request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path))); + String filename = basename(file.path); + request.files.add(http.MultipartFile.fromBytes( + 'file', + await file.readAsBytes(), + contentType: MediaType.parse(lookupMimeType(filename) ?? 'application/octet-stream'), + filename: filename, + )); request.headers.addAll({'Authorization': await getAuthHeader()}); try { diff --git a/app/lib/backend/http/openai.dart b/app/lib/backend/http/openai.dart index eaac7305ce6..9bcc9c5696c 100644 --- a/app/lib/backend/http/openai.dart +++ b/app/lib/backend/http/openai.dart @@ -1,8 +1,8 @@ import 'dart:convert'; -import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:omi/backend/http/shared.dart'; import 'package:omi/backend/preferences.dart'; import 'package:omi/backend/schema/app.dart'; @@ -60,7 +60,7 @@ Future gptApiCall({ String contentToEmbed = '', bool jsonResponseFormat = false, List tools = const [], - File? audioFile, + XFile? audioFile, double temperature = 0.3, int? maxTokens, }) async { diff --git a/app/lib/backend/http/shared.dart b/app/lib/backend/http/shared.dart index 45deba74917..2999ba3dad6 100644 --- a/app/lib/backend/http/shared.dart +++ b/app/lib/backend/http/shared.dart @@ -68,6 +68,8 @@ Future makeApiCall({ message: 'Authentication failed. Please sign in again.'); } } + debugPrint('HTTP request response: ${response.reasonPhrase}'); + debugPrint('HTTP request response: ${response.body}'); return response; } catch (e, stackTrace) { diff --git a/app/lib/main.dart b/app/lib/main.dart index 81c9c86ea2a..0f0f89d14d9 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -43,11 +43,13 @@ import 'package:omi/services/notifications.dart'; import 'package:omi/services/services.dart'; import 'package:omi/utils/alerts/app_snackbar.dart'; import 'package:omi/utils/analytics/growthbook.dart'; -import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; +import 'package:omi/utils/audio/foreground.dart'; import 'package:omi/utils/features/calendar.dart'; import 'package:omi/utils/logger.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; +import 'package:omi/utils/platform.dart'; import 'package:opus_dart/opus_dart.dart'; import 'package:opus_flutter/opus_flutter.dart' as opus_flutter; import 'package:posthog_flutter/posthog_flutter.dart'; @@ -55,16 +57,21 @@ import 'package:provider/provider.dart'; import 'package:talker_flutter/talker_flutter.dart'; Future _init() async { + + await Firebase.initializeApp( + name: PlatformUtil.isWeb + ? '[DEFAULT]' + : F.env == Environment.prod + ? "prod" + : "dev", + options: F.env == Environment.prod + ? prod.DefaultFirebaseOptions.currentPlatform + : dev.DefaultFirebaseOptions.currentPlatform, + ); + // Service manager ServiceManager.init(); - // Firebase - if (F.env == Environment.prod) { - await Firebase.initializeApp(options: prod.DefaultFirebaseOptions.currentPlatform, name: 'prod'); - } else { - await Firebase.initializeApp(options: dev.DefaultFirebaseOptions.currentPlatform, name: 'dev'); - } - await IntercomManager().initIntercom(); await NotificationService.instance.initialize(); await SharedPreferencesUtil.init(); @@ -79,7 +86,7 @@ Future _init() async { await GrowthbookUtil.init(); CalendarUtil.init(); - ble.FlutterBluePlus.setLogLevel(ble.LogLevel.info, color: true); + if(PlatformUtil.isNotWeb) ble.FlutterBluePlus.setLogLevel(ble.LogLevel.info, color: true); return isAuth; } @@ -98,7 +105,7 @@ void main() async { } else { Env.init(DevEnv()); } - FlutterForegroundTask.initCommunicationPort(); + ForegroundUtil.initCommunicationPort(); if (Env.posthogApiKey != null) { await initPostHog(); } @@ -217,7 +224,7 @@ class _MyAppState extends State with WidgetsBindingObserver { ChangeNotifierProvider(create: (context) => MemoriesProvider()), ], builder: (context, child) { - return WithForegroundTask( + return ForegroundTask( child: MaterialApp( navigatorObservers: [ if (Env.instabugApiKey != null) InstabugNavigatorObserver(), @@ -289,6 +296,17 @@ class _MyAppState extends State with WidgetsBindingObserver { } } +Widget ForegroundTask({required MaterialApp child}) { + if(PlatformUtil.isWeb) { + return child; + }else { + return WithForegroundTask( + child: child, + ); + } + +} + class DeciderWidget extends StatefulWidget { const DeciderWidget({super.key}); @@ -311,6 +329,12 @@ class _DeciderWidgetState extends State { } void openAppLink(Uri uri) async { + // Add this check before accessing path segments + if (uri.pathSegments.isEmpty) { + debugPrint('URI has no path segments: $uri'); + return; + } + if (uri.pathSegments.first == 'apps') { if (mounted) { var app = await context.read().getAppFromId(uri.pathSegments[1]); @@ -341,7 +365,7 @@ class _DeciderWidgetState extends State { context.read().setupHasSpeakerProfile(); context.read().setupUserPrimaryLanguage(); try { - await IntercomManager.instance.intercom.loginIdentifiedUser( + await IntercomManager().loginIdentifiedUser( userId: SharedPreferencesUtil().uid, ); } catch (e) { @@ -352,9 +376,9 @@ class _DeciderWidgetState extends State { context.read().setAppsFromCache(); context.read().refreshMessages(); } else { - await IntercomManager.instance.intercom.loginUnidentifiedUser(); + await IntercomManager().loginUnidentifiedUser(); } - IntercomManager.instance.setUserAttributes(); + IntercomManager().setUserAttributes(); }); super.initState(); } diff --git a/app/lib/pages/apps/add_app.dart b/app/lib/pages/apps/add_app.dart index ad71065ae08..e4835b151fa 100644 --- a/app/lib/pages/apps/add_app.dart +++ b/app/lib/pages/apps/add_app.dart @@ -8,7 +8,6 @@ import 'package:omi/pages/apps/providers/add_app_provider.dart'; import 'package:omi/pages/apps/widgets/app_metadata_widget.dart'; import 'package:omi/pages/apps/widgets/external_trigger_fields_widget.dart'; import 'package:omi/pages/apps/widgets/full_screen_image_viewer.dart'; -import 'package:omi/pages/apps/widgets/api_keys_widget.dart'; import 'package:omi/pages/apps/widgets/notification_scopes_chips_widget.dart'; import 'package:omi/pages/apps/widgets/payment_details_widget.dart'; import 'package:omi/pages/payments/payment_method_provider.dart'; diff --git a/app/lib/pages/apps/app_detail/app_detail.dart b/app/lib/pages/apps/app_detail/app_detail.dart index 9775d2cf556..d0e25752cc1 100644 --- a/app/lib/pages/apps/app_detail/app_detail.dart +++ b/app/lib/pages/apps/app_detail/app_detail.dart @@ -28,7 +28,6 @@ import 'dart:async'; import '../../../backend/schema/app.dart'; import '../widgets/show_app_options_sheet.dart'; -import 'widgets/app_analytics_widget.dart'; import 'widgets/info_card_widget.dart'; import 'package:timeago/timeago.dart' as timeago; diff --git a/app/lib/pages/apps/app_detail/widgets/app_analytics_widget.dart b/app/lib/pages/apps/app_detail/widgets/app_analytics_widget.dart index 17832300e12..264d7e8071f 100644 --- a/app/lib/pages/apps/app_detail/widgets/app_analytics_widget.dart +++ b/app/lib/pages/apps/app_detail/widgets/app_analytics_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:omi/gen/assets.gen.dart'; -import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; import 'package:skeletonizer/skeletonizer.dart'; class AppAnalyticsWidget extends StatelessWidget { diff --git a/app/lib/pages/apps/providers/add_app_provider.dart b/app/lib/pages/apps/providers/add_app_provider.dart index f0a357c6c3c..cf870ee31e7 100644 --- a/app/lib/pages/apps/providers/add_app_provider.dart +++ b/app/lib/pages/apps/providers/add_app_provider.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -50,7 +49,7 @@ class AddAppProvider extends ChangeNotifier { List categories = []; - File? imageFile; + XFile? imageFile; String? imageUrl; String? updateAppId; @@ -608,10 +607,9 @@ class AddAppProvider extends ChangeNotifier { ); if (file != null) { setIsUploadingThumbnail(true); - var thumbnailFile = File(file.path); // Upload thumbnail - var result = await uploadAppThumbnail(thumbnailFile); + var result = await uploadAppThumbnail(file); if (result.isNotEmpty) { thumbnailUrls.add(result['thumbnail_url']!); thumbnailIds.add(result['thumbnail_id']!); @@ -645,7 +643,7 @@ class AddAppProvider extends ChangeNotifier { try { var file = await imagePicker.pickImage(source: ImageSource.gallery); if (file != null) { - imageFile = File(file.path); + imageFile = file; } notifyListeners(); } on PlatformException catch (e) { @@ -662,7 +660,7 @@ class AddAppProvider extends ChangeNotifier { try { var file = await imagePicker.pickImage(source: ImageSource.gallery); if (file != null) { - imageFile = File(file.path); + imageFile = file; imageUrl = null; } notifyListeners(); diff --git a/app/lib/pages/apps/update_app.dart b/app/lib/pages/apps/update_app.dart index e204d36bf8d..813387a46ce 100644 --- a/app/lib/pages/apps/update_app.dart +++ b/app/lib/pages/apps/update_app.dart @@ -14,7 +14,6 @@ import 'widgets/capabilities_chips_widget.dart'; import 'widgets/external_trigger_fields_widget.dart'; import 'widgets/payment_details_widget.dart'; import 'widgets/prompt_text_field.dart'; -import 'widgets/action_fields_widget.dart'; class UpdateAppPage extends StatefulWidget { final App app; diff --git a/app/lib/pages/apps/widgets/app_metadata_widget.dart b/app/lib/pages/apps/widgets/app_metadata_widget.dart index bca2e75a6a3..d4427454cb7 100644 --- a/app/lib/pages/apps/widgets/app_metadata_widget.dart +++ b/app/lib/pages/apps/widgets/app_metadata_widget.dart @@ -1,9 +1,9 @@ -import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:omi/backend/schema/app.dart'; import 'package:omi/gen/assets.gen.dart'; import 'package:omi/pages/apps/providers/add_app_provider.dart'; @@ -11,7 +11,7 @@ import 'package:provider/provider.dart'; import 'package:skeletonizer/skeletonizer.dart'; class AppMetadataWidget extends StatelessWidget { - final File? imageFile; + final XFile? imageFile; final String? imageUrl; final VoidCallback pickImage; final TextEditingController appNameController; @@ -64,9 +64,20 @@ class AppMetadataWidget extends StatelessWidget { ), child: imageFile != null || imageUrl != null ? (imageUrl == null - ? ClipRRect( - borderRadius: BorderRadius.circular(30.0), - child: Image.file(imageFile!, fit: BoxFit.cover)) + ? FutureBuilder( + future: imageFile!.readAsBytes(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return ClipRRect( + borderRadius: BorderRadius.circular(30.0), + child: Image.memory(snapshot.data!, fit: BoxFit.cover), + ); + } + return const Center( + child: CircularProgressIndicator(), + ); + }, + ) : ClipRRect( borderRadius: BorderRadius.circular(30.0), child: CachedNetworkImage(imageUrl: imageUrl!), diff --git a/app/lib/pages/chat/page.dart b/app/lib/pages/chat/page.dart index 3b8a1b724a3..59111cfba2c 100644 --- a/app/lib/pages/chat/page.dart +++ b/app/lib/pages/chat/page.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; @@ -20,6 +19,7 @@ import 'package:omi/providers/conversation_provider.dart'; import 'package:omi/providers/message_provider.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; import 'package:omi/utils/other/temp.dart'; +import 'package:omi/utils/platform.dart'; import 'package:omi/widgets/dialog.dart'; import 'package:omi/widgets/extensions/string.dart'; import 'package:gradient_borders/gradient_borders.dart'; @@ -177,10 +177,10 @@ class ChatPageState extends State with AutomaticKeepAliveClientMixin { double bottomPadding = chatIndex == 0 ? provider.selectedFiles.isNotEmpty - ? (Platform.isAndroid + ? (PlatformUtil.isAndroid ? MediaQuery.sizeOf(context).height * 0.32 : MediaQuery.sizeOf(context).height * 0.3) - : (Platform.isAndroid + : (PlatformUtil.isAndroid ? MediaQuery.sizeOf(context).height * 0.21 : MediaQuery.sizeOf(context).height * 0.19) : 0; @@ -368,25 +368,40 @@ class ChatPageState extends State with AutomaticKeepAliveClientMixin { width: MediaQuery.sizeOf(context).width * 0.2, decoration: BoxDecoration( color: Colors.grey[800], - image: provider.selectedFileTypes[idx] == 'image' - ? DecorationImage( - image: FileImage(provider.selectedFiles[idx]), - fit: BoxFit.cover, - ) - : null, borderRadius: BorderRadius.circular(10), ), child: Stack( children: [ - provider.selectedFileTypes[idx] != 'image' - ? const Center( - child: Icon( - Icons.insert_drive_file, - color: Colors.white, - size: 30, + if (provider.selectedFileTypes[idx] == 'image') + FutureBuilder( + future: provider.selectedFiles[idx].readAsBytes(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + image: DecorationImage( + image: MemoryImage(snapshot.data!), + fit: BoxFit.cover, + ), + ), + ); + } + return const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white70), ), - ) - : Container(), + ); + }, + ) + else + const Center( + child: Icon( + Icons.insert_drive_file, + color: Colors.white, + size: 30, + ), + ), if (provider.isFileUploading(provider.selectedFiles[idx].path)) Container( color: Colors.black.withOpacity(0.5), diff --git a/app/lib/pages/chat/widgets/voice_recorder_widget.dart b/app/lib/pages/chat/widgets/voice_recorder_widget.dart index 37804b33038..d1d38372a57 100644 --- a/app/lib/pages/chat/widgets/voice_recorder_widget.dart +++ b/app/lib/pages/chat/widgets/voice_recorder_widget.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:omi/backend/http/api/messages.dart'; import 'package:omi/services/services.dart'; import 'package:omi/utils/alerts/app_snackbar.dart'; -import 'package:omi/utils/enums.dart'; import 'package:omi/utils/file.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shimmer/shimmer.dart'; diff --git a/app/lib/pages/conversation_capturing/page.dart b/app/lib/pages/conversation_capturing/page.dart index 9d888a9e906..2103f650e21 100644 --- a/app/lib/pages/conversation_capturing/page.dart +++ b/app/lib/pages/conversation_capturing/page.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'package:flutter/material.dart'; import 'package:omi/backend/http/api/users.dart'; diff --git a/app/lib/pages/conversation_detail/maps_util.dart b/app/lib/pages/conversation_detail/maps_util.dart index c59d0771355..f88694cc09c 100644 --- a/app/lib/pages/conversation_detail/maps_util.dart +++ b/app/lib/pages/conversation_detail/maps_util.dart @@ -1,7 +1,7 @@ -import 'dart:io'; import 'package:omi/env/env.dart'; import 'package:map_launcher/map_launcher.dart'; +import 'package:omi/utils/platform.dart'; class MapsUtil { static String getMapImageUrl(double lat, double lng) { @@ -15,7 +15,7 @@ class MapsUtil { } static void launchMap(double lat, double lng) async { - if (Platform.isIOS) { + if (PlatformUtil.isIOS) { await MapLauncher.showMarker( mapType: MapType.apple, coords: Coords(lat, lng), diff --git a/app/lib/pages/conversation_detail/page.dart b/app/lib/pages/conversation_detail/page.dart index e31a1296d92..ce8a631fedd 100644 --- a/app/lib/pages/conversation_detail/page.dart +++ b/app/lib/pages/conversation_detail/page.dart @@ -10,7 +10,6 @@ import 'package:omi/pages/conversation_detail/widgets.dart'; import 'package:omi/pages/settings/people.dart'; import 'package:omi/providers/connectivity_provider.dart'; import 'package:omi/providers/conversation_provider.dart'; -import 'package:omi/utils/alerts/app_snackbar.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; import 'package:omi/utils/other/temp.dart'; import 'package:omi/widgets/conversation_bottom_bar.dart'; diff --git a/app/lib/pages/conversation_detail/share.dart b/app/lib/pages/conversation_detail/share.dart index d608c6ecc79..d67d01d3670 100644 --- a/app/lib/pages/conversation_detail/share.dart +++ b/app/lib/pages/conversation_detail/share.dart @@ -1,9 +1,9 @@ -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:omi/backend/http/api/conversations.dart'; import 'package:omi/backend/schema/conversation.dart'; +import 'package:omi/utils/omi_file/omi_file.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:share_plus/share_plus.dart'; @@ -76,7 +76,7 @@ void _exportPDF(ServerConversation conversation, bool isTranscript) async { } final directory = await getApplicationDocumentsDirectory(); - final file = File('${directory.path}/conversation_export.pdf'); + final file = OmiFile('${directory.path}/conversation_export.pdf'); await file.writeAsBytes(await pdf.save()); await Share.shareXFiles([XFile(file.path)], text: header); @@ -84,7 +84,7 @@ void _exportPDF(ServerConversation conversation, bool isTranscript) async { void _exportTranscriptTxt(ServerConversation conversation) async { final directory = await getApplicationDocumentsDirectory(); - final transcriptFile = File('${directory.path}/conversation_transcript.txt'); + final transcriptFile = OmiFile('${directory.path}/conversation_transcript.txt'); final transcript = "$header${conversation.getTranscript(generate: true)}"; await transcriptFile.writeAsString(transcript); @@ -93,14 +93,14 @@ void _exportTranscriptTxt(ServerConversation conversation) async { void _exportSummaryTxt(ServerConversation conversation) async { final directory = await getApplicationDocumentsDirectory(); - final summaryFile = File('${directory.path}/summary.txt'); + final summaryFile = OmiFile('${directory.path}/summary.txt'); await summaryFile.writeAsString("$header${conversation.structured.toString()}"); await Share.shareXFiles([XFile(summaryFile.path)], text: header); } void _exportSummaryMarkdown(ServerConversation conversation) async { final directory = await getApplicationDocumentsDirectory(); - final markdownFile = File('${directory.path}/conversation_export.md'); + final markdownFile = OmiFile('${directory.path}/conversation_export.md'); final structured = conversation.structured; final markdown = """ # Summary Export diff --git a/app/lib/pages/conversations/widgets/processing_capture.dart b/app/lib/pages/conversations/widgets/processing_capture.dart index 3e7b77c0035..9b2c2a4bda0 100644 --- a/app/lib/pages/conversations/widgets/processing_capture.dart +++ b/app/lib/pages/conversations/widgets/processing_capture.dart @@ -85,10 +85,16 @@ class _ConversationCaptureWidgetState extends State { context, () => Navigator.pop(context), () async { - Navigator.pop(context); - provider.updateRecordingState(RecordingState.initialising); - await provider.streamRecording(); - MixpanelManager().phoneMicRecordingStarted(); + try { + Navigator.pop(context); + provider.updateRecordingState(RecordingState.initialising); + await provider.streamRecording(); + MixpanelManager().phoneMicRecordingStarted(); + } catch (e, stackTrace) { + debugPrint('Error starting recording: $e $stackTrace'); + provider.updateRecordingState(RecordingState.error); + + } }, 'Limited Capabilities', 'Recording with your phone microphone has a few limitations, including but not limited to: speaker profiles, background reliability.', diff --git a/app/lib/pages/home/device.dart b/app/lib/pages/home/device.dart index 44e2faf73c4..d0ecb8d7639 100644 --- a/app/lib/pages/home/device.dart +++ b/app/lib/pages/home/device.dart @@ -1,9 +1,10 @@ + import 'package:flutter/material.dart'; import 'package:omi/backend/preferences.dart'; import 'package:omi/backend/schema/bt_device/bt_device.dart'; import 'package:omi/providers/device_provider.dart'; import 'package:omi/services/services.dart'; -import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; import 'package:omi/widgets/device_widget.dart'; import 'package:gradient_borders/box_borders/gradient_box_border.dart'; diff --git a/app/lib/pages/home/firmware_mixin.dart b/app/lib/pages/home/firmware_mixin.dart index 6e447ef8c72..282c05c38ac 100644 --- a/app/lib/pages/home/firmware_mixin.dart +++ b/app/lib/pages/home/firmware_mixin.dart @@ -1,5 +1,4 @@ -import 'dart:convert'; -import 'dart:io'; + import 'dart:typed_data'; import 'package:flutter/widgets.dart'; @@ -8,6 +7,9 @@ import 'package:omi/backend/schema/bt_device/bt_device.dart'; import 'package:omi/http/api/device.dart'; import 'package:omi/services/services.dart'; import 'package:omi/utils/device.dart'; +import 'package:nordic_dfu/nordic_dfu.dart'; +import 'package:omi/utils/omi_file/omi_file.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:omi/utils/manifest/manifest.dart'; import 'package:path_provider/path_provider.dart'; import 'package:uuid/uuid.dart'; @@ -244,9 +246,9 @@ mixin FirmwareMixin on State { }, onDone: () async { // Display percentage of completion debugPrint('downloadPercentage: ${downloaded / r.contentLength! * 100}'); - + // Need Help: // Save the file - File file = File('$dir/firmware.zip'); + OmiFile file = OmiFile('$dir/firmware.zip'); final Uint8List bytes = Uint8List(r.contentLength!); int offset = 0; for (List chunk in chunks) { diff --git a/app/lib/pages/home/firmware_update.dart b/app/lib/pages/home/firmware_update.dart index 109393ec1e7..ee12d944d2c 100644 --- a/app/lib/pages/home/firmware_update.dart +++ b/app/lib/pages/home/firmware_update.dart @@ -4,7 +4,7 @@ import 'firmware_update_dialog.dart'; import 'package:omi/backend/schema/bt_device/bt_device.dart'; import 'package:omi/pages/home/firmware_mixin.dart'; import 'package:omi/pages/home/page.dart'; -import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; import 'package:omi/utils/other/temp.dart'; import 'package:gradient_borders/gradient_borders.dart'; diff --git a/app/lib/pages/home/page.dart b/app/lib/pages/home/page.dart index 06b2077c5b6..3675b244c03 100644 --- a/app/lib/pages/home/page.dart +++ b/app/lib/pages/home/page.dart @@ -1,9 +1,8 @@ import 'dart:async'; -import 'dart:io'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:omi/gen/assets.gen.dart'; import 'package:omi/pages/persona/persona_provider.dart'; import 'package:omi/backend/http/api/users.dart'; @@ -17,7 +16,6 @@ import 'package:omi/pages/conversations/conversations_page.dart'; import 'package:omi/pages/memories/page.dart'; import 'package:omi/pages/home/widgets/chat_apps_dropdown_widget.dart'; import 'package:omi/pages/persona/persona_profile.dart'; -import 'package:omi/pages/home/widgets/speech_language_sheet.dart'; import 'package:omi/pages/settings/page.dart'; import 'package:omi/providers/app_provider.dart'; import 'package:omi/providers/capture_provider.dart'; @@ -31,6 +29,7 @@ import 'package:omi/utils/analytics/analytics_manager.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; import 'package:omi/utils/audio/foreground.dart'; import 'package:omi/utils/other/temp.dart'; +import 'package:omi/utils/platform.dart'; import 'package:omi/widgets/upgrade_alert.dart'; import 'package:gradient_borders/gradient_borders.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; @@ -129,7 +128,7 @@ class _HomePageState extends State with WidgetsBindingObserver, Ticker return; } debugPrint(event); - InstabugLog.logInfo(event); + if(PlatformUtil.isNotWeb) InstabugLog.logInfo(event); } ///Screens with respect to subpage @@ -255,7 +254,7 @@ class _HomePageState extends State with WidgetsBindingObserver, Ticker super.initState(); // After init - FlutterForegroundTask.addTaskDataCallback(_onReceiveTaskData); + ForegroundUtil.addTaskDataCallback(_onReceiveTaskData); } void _listenToMessagesFromNotification() { @@ -274,7 +273,7 @@ class _HomePageState extends State with WidgetsBindingObserver, Ticker Widget build(BuildContext context) { return MyUpgradeAlert( upgrader: _upgrader, - dialogStyle: Platform.isIOS ? UpgradeDialogStyle.cupertino : UpgradeDialogStyle.material, + dialogStyle: PlatformUtil.isIOS ? UpgradeDialogStyle.cupertino : UpgradeDialogStyle.material, child: Consumer( builder: (ctx, connectivityProvider, child) { bool isConnected = connectivityProvider.isConnected; diff --git a/app/lib/pages/memories/widgets/delete_confirmation.dart b/app/lib/pages/memories/widgets/delete_confirmation.dart index aad836fcc7c..a95aab0bbb8 100644 --- a/app/lib/pages/memories/widgets/delete_confirmation.dart +++ b/app/lib/pages/memories/widgets/delete_confirmation.dart @@ -1,14 +1,14 @@ -import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:omi/utils/platform.dart'; class DeleteConfirmation { static Future show(BuildContext context, {String? title, String? content}) async { title ??= 'Delete memory?'; content ??= 'This action cannot be undone.'; - if (Platform.isIOS) { + if (PlatformUtil.isIOS) { return await showCupertinoDialog( context: context, builder: (context) => CupertinoAlertDialog( diff --git a/app/lib/pages/onboarding/auth.dart b/app/lib/pages/onboarding/auth.dart index a62ef06b6a8..cca66593c90 100644 --- a/app/lib/pages/onboarding/auth.dart +++ b/app/lib/pages/onboarding/auth.dart @@ -1,9 +1,9 @@ -import 'dart:io'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:omi/backend/preferences.dart'; import 'package:omi/providers/auth_provider.dart'; +import 'package:omi/utils/platform.dart'; import 'package:omi/widgets/sign_in_button.dart'; import 'package:provider/provider.dart'; @@ -38,7 +38,7 @@ class _AuthComponentState extends State { ), ), SizedBox(height: MediaQuery.of(context).textScaleFactor > 1.0 ? 18 : 32), - if (Platform.isIOS) ...[ + if (PlatformUtil.isIOS) ...[ SignInButton.withApple( title: 'Sign in with Apple', onTap: () async { diff --git a/app/lib/pages/onboarding/find_device/found_devices.dart b/app/lib/pages/onboarding/find_device/found_devices.dart index 03c3774e0f6..48a4cf92352 100644 --- a/app/lib/pages/onboarding/find_device/found_devices.dart +++ b/app/lib/pages/onboarding/find_device/found_devices.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_provider_utilities/flutter_provider_utilities.dart'; import 'package:omi/backend/schema/bt_device/bt_device.dart'; import 'package:omi/providers/onboarding_provider.dart'; -import 'package:omi/widgets/dialog.dart'; import 'package:gradient_borders/gradient_borders.dart'; import 'package:provider/provider.dart'; diff --git a/app/lib/pages/onboarding/name/name_widget.dart b/app/lib/pages/onboarding/name/name_widget.dart index 9bbe705cf0c..47849429ef9 100644 --- a/app/lib/pages/onboarding/name/name_widget.dart +++ b/app/lib/pages/onboarding/name/name_widget.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:omi/backend/auth.dart'; import 'package:omi/backend/preferences.dart'; import 'package:gradient_borders/gradient_borders.dart'; -import 'package:intercom_flutter/intercom_flutter.dart'; -import 'package:omi/providers/home_provider.dart'; -import 'package:provider/provider.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; + +import 'package:omi/utils/analytics/intercom_manager.dart'; class NameWidget extends StatefulWidget { final Function goNext; @@ -117,7 +117,7 @@ class _NameWidgetState extends State { ), ), onTap: () { - Intercom.instance.displayMessenger(); + IntercomManager().displayMessenger(); }, ), ], diff --git a/app/lib/pages/onboarding/permissions/permissions_widget.dart b/app/lib/pages/onboarding/permissions/permissions_widget.dart index 03ef0d4d780..c858da09b15 100644 --- a/app/lib/pages/onboarding/permissions/permissions_widget.dart +++ b/app/lib/pages/onboarding/permissions/permissions_widget.dart @@ -1,10 +1,11 @@ -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:omi/providers/onboarding_provider.dart'; +import 'package:omi/utils/platform.dart'; import 'package:omi/widgets/dialog.dart'; import 'package:gradient_borders/box_borders/gradient_box_border.dart'; -import 'package:intercom_flutter/intercom_flutter.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; + import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; @@ -26,7 +27,7 @@ class _PermissionsWidgetState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.end, children: [ - Platform.isAndroid + PlatformUtil.isAndroid ? CheckboxListTile( value: provider.hasBackgroundPermission, onChanged: (s) async { @@ -162,7 +163,7 @@ class _PermissionsWidgetState extends State { padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), onPressed: () async { provider.setLoading(true); - if (Platform.isAndroid) { + if (PlatformUtil.isAndroid) { if (!provider.hasBackgroundPermission) { await provider.askForBackgroundPermissions(); } @@ -229,7 +230,7 @@ class _PermissionsWidgetState extends State { ), ), onTap: () { - Intercom.instance.displayMessenger(); + IntercomManager().displayMessenger(); }, ), ], diff --git a/app/lib/pages/onboarding/primary_language/primary_language_widget.dart b/app/lib/pages/onboarding/primary_language/primary_language_widget.dart index 5935aea8f47..b879f76faa4 100644 --- a/app/lib/pages/onboarding/primary_language/primary_language_widget.dart +++ b/app/lib/pages/onboarding/primary_language/primary_language_widget.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:gradient_borders/gradient_borders.dart'; -import 'package:intercom_flutter/intercom_flutter.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; + import 'package:omi/backend/preferences.dart'; import 'package:omi/providers/home_provider.dart'; import 'package:provider/provider.dart'; @@ -355,7 +356,7 @@ class _PrimaryLanguageWidgetState extends State { ), ), onTap: () { - Intercom.instance.displayMessenger(); + IntercomManager().displayMessenger(); }, ), ], diff --git a/app/lib/pages/onboarding/welcome/page.dart b/app/lib/pages/onboarding/welcome/page.dart index 5747f97c9aa..b8ecc337d21 100644 --- a/app/lib/pages/onboarding/welcome/page.dart +++ b/app/lib/pages/onboarding/welcome/page.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:omi/providers/onboarding_provider.dart'; -import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; import 'package:omi/widgets/dialog.dart'; import 'package:gradient_borders/box_borders/gradient_box_border.dart'; import 'package:permission_handler/permission_handler.dart'; diff --git a/app/lib/pages/onboarding/wrapper.dart b/app/lib/pages/onboarding/wrapper.dart index 1fcac52d658..1ec0142af41 100644 --- a/app/lib/pages/onboarding/wrapper.dart +++ b/app/lib/pages/onboarding/wrapper.dart @@ -16,7 +16,7 @@ import 'package:omi/pages/onboarding/welcome/page.dart'; import 'package:omi/providers/home_provider.dart'; import 'package:omi/providers/onboarding_provider.dart'; import 'package:omi/services/services.dart'; -import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; import 'package:omi/utils/other/temp.dart'; import 'package:omi/widgets/device_widget.dart'; @@ -96,9 +96,7 @@ class _OnboardingWrapperState extends State with TickerProvid SharedPreferencesUtil().verifiedPersonaId = null; MixpanelManager().onboardingStepCompleted('Auth'); context.read().setupHasSpeakerProfile(); - IntercomManager.instance.intercom.loginIdentifiedUser( - userId: SharedPreferencesUtil().uid, - ); + IntercomManager().loginIdentifiedUser(userId: SharedPreferencesUtil().uid); if (SharedPreferencesUtil().onboardingCompleted) { routeToPage(context, const HomePageWrapper(), replace: true); } else { @@ -107,8 +105,8 @@ class _OnboardingWrapperState extends State with TickerProvid }, ), NameWidget(goNext: () { - _goNext(); // Go to Primary Language page - IntercomManager.instance.updateUser( + _goNext(); + IntercomManager().updateUser( FirebaseAuth.instance.currentUser!.email, FirebaseAuth.instance.currentUser!.displayName, FirebaseAuth.instance.currentUser!.uid, diff --git a/app/lib/pages/persona/persona_profile.dart b/app/lib/pages/persona/persona_profile.dart index 801b3a50811..2dfa6b04853 100644 --- a/app/lib/pages/persona/persona_profile.dart +++ b/app/lib/pages/persona/persona_profile.dart @@ -1,3 +1,5 @@ +import 'dart:typed_data'; + import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -197,9 +199,21 @@ class _PersonaProfilePageState extends State { child: ClipRRect( borderRadius: BorderRadius.circular(50), child: provider.selectedImage != null - ? Image.file( - provider.selectedImage!, - fit: BoxFit.cover, + ? FutureBuilder( + future: provider.selectedImage!.readAsBytes(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return Image.memory( + snapshot.data!, + fit: BoxFit.cover, + ); + } + return const Center( + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ); + }, ) : persona.image.isEmpty ? Image.asset(Assets.images.logoTransparentV2.path) diff --git a/app/lib/pages/persona/persona_provider.dart b/app/lib/pages/persona/persona_provider.dart index 2daee2f1667..37742c23ace 100644 --- a/app/lib/pages/persona/persona_provider.dart +++ b/app/lib/pages/persona/persona_provider.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:omi/backend/http/api/apps.dart'; @@ -41,7 +40,7 @@ class PersonaProvider extends ChangeNotifier { bool hasTwitterConnection = false; ShowSuccessDialogCallback? onShowSuccessDialog; - File? selectedImage; + XFile? selectedImage; String? selectedImageUrl; Future updatePersonaName() async { @@ -176,7 +175,7 @@ class PersonaProvider extends ChangeNotifier { final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage(source: ImageSource.gallery); if (image != null) { - selectedImage = File(image.path); + selectedImage = image; validateForm(); } notifyListeners(); @@ -186,7 +185,7 @@ class PersonaProvider extends ChangeNotifier { final ImagePicker picker = ImagePicker(); final XFile? image = await picker.pickImage(source: ImageSource.gallery); if (image != null) { - selectedImage = File(image.path); + selectedImage = image; validateForm(); // Update diff --git a/app/lib/pages/settings/about.dart b/app/lib/pages/settings/about.dart index a1bc7ba4b28..0981e1a9977 100644 --- a/app/lib/pages/settings/about.dart +++ b/app/lib/pages/settings/about.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:omi/pages/settings/webview.dart'; -import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; import 'package:omi/utils/other/temp.dart'; import 'package:url_launcher/url_launcher.dart'; diff --git a/app/lib/pages/settings/change_name_widget.dart b/app/lib/pages/settings/change_name_widget.dart index cf47d35d321..116a0913ed2 100644 --- a/app/lib/pages/settings/change_name_widget.dart +++ b/app/lib/pages/settings/change_name_widget.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/cupertino.dart'; @@ -6,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:omi/backend/auth.dart'; import 'package:omi/backend/preferences.dart'; import 'package:omi/utils/alerts/app_snackbar.dart'; +import 'package:omi/utils/platform.dart'; class ChangeNameWidget extends StatefulWidget { const ChangeNameWidget({super.key}); @@ -28,7 +28,7 @@ class _ChangeNameWidgetState extends State { @override Widget build(BuildContext context) { - if (Platform.isIOS) { + if (PlatformUtil.isIOS) { return CupertinoAlertDialog( content: Padding( padding: const EdgeInsets.all(8.0), diff --git a/app/lib/pages/settings/developer.dart b/app/lib/pages/settings/developer.dart index 191c6d0d7aa..362ec7ec513 100644 --- a/app/lib/pages/settings/developer.dart +++ b/app/lib/pages/settings/developer.dart @@ -1,11 +1,11 @@ import 'dart:convert'; -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:omi/backend/http/api/conversations.dart'; import 'package:omi/backend/schema/conversation.dart'; import 'package:omi/providers/developer_mode_provider.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; +import 'package:omi/utils/omi_file/omi_file.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'package:share_plus/share_plus.dart'; @@ -168,7 +168,7 @@ class _DeveloperSettingsPageState extends State { await getConversations(limit: 10000, offset: 0); // 10k for now String json = const JsonEncoder.withIndent(" ").convert(memories); final directory = await getApplicationDocumentsDirectory(); - final file = File('${directory.path}/conversations.json'); + final file = OmiFile('${directory.path}/conversations.json'); await file.writeAsString(json); final result = diff --git a/app/lib/pages/settings/device_settings.dart b/app/lib/pages/settings/device_settings.dart index 19ea2ee5572..32f877f7298 100644 --- a/app/lib/pages/settings/device_settings.dart +++ b/app/lib/pages/settings/device_settings.dart @@ -8,7 +8,7 @@ import 'package:omi/pages/conversations/sync_page.dart'; import 'package:omi/providers/device_provider.dart'; import 'package:omi/providers/onboarding_provider.dart'; import 'package:omi/services/services.dart'; -import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; import 'package:omi/utils/other/temp.dart'; import 'package:omi/widgets/dialog.dart'; diff --git a/app/lib/pages/settings/page.dart b/app/lib/pages/settings/page.dart index 8d50db0b57f..80c69dab6f2 100644 --- a/app/lib/pages/settings/page.dart +++ b/app/lib/pages/settings/page.dart @@ -9,7 +9,8 @@ import 'package:omi/pages/settings/profile.dart'; import 'package:omi/pages/settings/widgets.dart'; import 'package:omi/utils/other/temp.dart'; import 'package:omi/widgets/dialog.dart'; -import 'package:intercom_flutter/intercom_flutter.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; + import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; @@ -62,7 +63,7 @@ class _SettingsPageState extends State { getItemAddOn2( 'Need Help? Chat with us', () async { - await Intercom.instance.displayMessenger(); + await IntercomManager().displayMessenger(); }, icon: Icons.chat, ), @@ -88,7 +89,7 @@ class _SettingsPageState extends State { getItemAddOn2( 'Guides & Tutorials', () async { - await Intercom.instance.displayHelpCenter(); + await IntercomManager().displayHelpCenter(); }, icon: Icons.help_outline_outlined, ), @@ -143,7 +144,7 @@ class _SettingsPageState extends State { getItemAddOn2( 'Need Help? Chat with us', () async { - await Intercom.instance.displayMessenger(); + await IntercomManager().displayMessenger(); }, icon: Icons.chat, ), diff --git a/app/lib/pages/settings/people.dart b/app/lib/pages/settings/people.dart index 847bf6d1800..e9532ea642b 100644 --- a/app/lib/pages/settings/people.dart +++ b/app/lib/pages/settings/people.dart @@ -1,4 +1,3 @@ -import 'dart:io'; import 'package:collection/collection.dart'; import 'package:flutter/cupertino.dart'; @@ -6,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:omi/backend/schema/person.dart'; import 'package:omi/providers/people_provider.dart'; import 'package:omi/providers/connectivity_provider.dart'; +import 'package:omi/utils/platform.dart'; import 'package:omi/widgets/dialog.dart'; import 'package:omi/widgets/extensions/functions.dart'; import 'package:just_audio/just_audio.dart'; @@ -40,7 +40,7 @@ class _UserPeoplePageState extends State<_UserPeoplePage> { } Widget _showPersonDialogForm(formKey, nameController) { - return Platform.isIOS + return PlatformUtil.isIOS ? Material( color: Colors.transparent, child: Theme( @@ -107,7 +107,7 @@ class _UserPeoplePageState extends State<_UserPeoplePage> { } } - return Platform.isIOS + return PlatformUtil.isIOS ? [ CupertinoDialogAction( onPressed: () => Navigator.pop(context), @@ -142,7 +142,7 @@ class _UserPeoplePageState extends State<_UserPeoplePage> { await showDialog( context: context, - builder: (BuildContext context) => Platform.isIOS + builder: (BuildContext context) => PlatformUtil.isIOS ? CupertinoAlertDialog( title: Text(person == null ? 'Add New Person' : 'Edit Person'), content: _showPersonDialogForm(formKey, nameController), diff --git a/app/lib/pages/settings/webview.dart b/app/lib/pages/settings/webview.dart index 418dc227f7b..c72ff09bf70 100644 --- a/app/lib/pages/settings/webview.dart +++ b/app/lib/pages/settings/webview.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:omi/utils/platform.dart'; import 'package:webview_flutter/webview_flutter.dart'; class PageWebView extends StatefulWidget { @@ -17,31 +18,34 @@ class _PageWebViewState extends State { @override initState() { - webViewController = WebViewController() - ..setJavaScriptMode(JavaScriptMode.unrestricted) - ..setBackgroundColor(const Color(0x00000000)) - ..setNavigationDelegate( - NavigationDelegate( - onProgress: (int p) { - if (mounted) { - setState(() { - progress = p; - }); - } - }, - onPageStarted: (String url) {}, - onPageFinished: (String url) {}, - onHttpError: (HttpResponseError error) {}, - onWebResourceError: (WebResourceError error) {}, - onNavigationRequest: (NavigationRequest request) { - if (request.url != widget.url) { - return NavigationDecision.prevent; - } - return NavigationDecision.navigate; - }, - ), - ) - ..loadRequest(Uri.parse(widget.url)); + debugPrint("Visit this website: ${widget.url}"); + if (PlatformUtil.isNotWeb) { + webViewController = WebViewController() + ..setJavaScriptMode(JavaScriptMode.unrestricted) + ..setBackgroundColor(const Color(0x00000000)) + ..setNavigationDelegate( + NavigationDelegate( + onProgress: (int p) { + if (mounted) { + setState(() { + progress = p; + }); + } + }, + onPageStarted: (String url) {}, + onPageFinished: (String url) {}, + onHttpError: (HttpResponseError error) {}, + onWebResourceError: (WebResourceError error) {}, + onNavigationRequest: (NavigationRequest request) { + if (request.url != widget.url) { + return NavigationDecision.prevent; + } + return NavigationDecision.navigate; + }, + ), + ) + ..loadRequest(Uri.parse(widget.url)); + } super.initState(); } diff --git a/app/lib/pages/speech_profile/page.dart b/app/lib/pages/speech_profile/page.dart index b881383a533..0e8eb4b3aca 100644 --- a/app/lib/pages/speech_profile/page.dart +++ b/app/lib/pages/speech_profile/page.dart @@ -12,12 +12,13 @@ import 'package:omi/providers/capture_provider.dart'; import 'package:omi/providers/home_provider.dart'; import 'package:omi/providers/speech_profile_provider.dart'; import 'package:omi/services/services.dart'; -import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; import 'package:omi/utils/other/temp.dart'; import 'package:omi/widgets/device_widget.dart'; import 'package:omi/widgets/dialog.dart'; import 'package:gradient_borders/box_borders/gradient_box_border.dart'; -import 'package:intercom_flutter/intercom_flutter.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; + import 'package:provider/provider.dart'; import 'percentage_bar_progress.dart'; diff --git a/app/lib/providers/capture_provider.dart b/app/lib/providers/capture_provider.dart index 3ebe3f4f021..1dba776866d 100644 --- a/app/lib/providers/capture_provider.dart +++ b/app/lib/providers/capture_provider.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_provider_utilities/flutter_provider_utilities.dart'; import 'package:omi/backend/http/api/conversations.dart'; import 'package:omi/backend/preferences.dart'; @@ -23,6 +22,7 @@ import 'package:omi/services/sockets/sdcard_socket.dart'; import 'package:omi/services/sockets/transcription_connection.dart'; import 'package:omi/services/wals.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; +import 'package:omi/utils/audio/foreground.dart'; import 'package:omi/utils/enums.dart'; import 'package:omi/utils/logger.dart'; import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart'; @@ -642,7 +642,7 @@ class CaptureProvider extends ChangeNotifier if (segments.isEmpty) { debugPrint('newSegments: ${newSegments.last}'); - FlutterForegroundTask.sendDataToTask(jsonEncode({'location': true})); + ForegroundUtil.sendDataToTask(jsonEncode({'location': true})); _loadInProgressConversation(); } var remainSegments = TranscriptSegment.updateSegments(segments, newSegments); diff --git a/app/lib/providers/message_provider.dart b/app/lib/providers/message_provider.dart index 1e2344c067c..4eec94e10d0 100644 --- a/app/lib/providers/message_provider.dart +++ b/app/lib/providers/message_provider.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:collection/collection.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; @@ -27,7 +25,7 @@ class MessageProvider extends ChangeNotifier { String firstTimeLoadingText = ''; - List selectedFiles = []; + List selectedFiles = []; List selectedFileTypes = []; List uploadedFiles = []; bool isUploadingFiles = false; @@ -86,7 +84,7 @@ class MessageProvider extends ChangeNotifier { void captureImage() async { var res = await ImagePicker().pickImage(source: ImageSource.camera); if (res != null) { - selectedFiles.add(File(res.path)); + selectedFiles.add(res); selectedFileTypes.add('image'); var index = selectedFiles.length - 1; await uploadFiles([selectedFiles[index]], appProvider?.selectedChatAppId); @@ -99,42 +97,30 @@ class MessageProvider extends ChangeNotifier { AppSnackbar.showSnackbarError('You can only select up to 4 images'); return; } - List res = []; + List res = []; if (4 - selectedFiles.length == 1) { res = [await ImagePicker().pickImage(source: ImageSource.gallery)]; } else { res = await ImagePicker().pickMultiImage(limit: 4 - selectedFiles.length); } if (res.isNotEmpty) { - List files = []; - for (var r in res) { - files.add(File(r.path)); - } - if (files.isNotEmpty) { - selectedFiles.addAll(files); - selectedFileTypes.addAll(res.map((e) => 'image')); - await uploadFiles(files, appProvider?.selectedChatAppId); - } + selectedFiles.addAll(res.nonNulls); + selectedFileTypes.addAll(res.map((e) => 'image')); + await uploadFiles([...res.nonNulls], appProvider?.selectedChatAppId); notifyListeners(); } } void selectFile() async { var res = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowMultiple: true, - allowedExtensions: ['jpeg', 'md', 'pdf', 'gif', 'doc', 'png', 'pptx', 'txt', 'xlsx', 'webp']); + type: FileType.custom, + allowMultiple: true, + allowedExtensions: ['jpeg', 'md', 'pdf', 'gif', 'doc', 'png', 'pptx', 'txt', 'xlsx', 'webp'], + ); if (res != null) { - List files = []; - for (var r in res.files) { - files.add(File(r.path!)); - } - if (files.isNotEmpty) { - selectedFiles.addAll(files); - selectedFileTypes.addAll(res.files.map((e) => 'file')); - await uploadFiles(files, appProvider?.selectedChatAppId); - } - + selectedFiles.addAll(res.xFiles.nonNulls); + selectedFileTypes.addAll(res.files.map((e) => 'file')); + await uploadFiles([...res.xFiles.nonNulls], appProvider?.selectedChatAppId); notifyListeners(); } } @@ -157,9 +143,10 @@ class MessageProvider extends ChangeNotifier { notifyListeners(); } - Future uploadFiles(List files, String? appId) async { + Future uploadFiles(List files, String? appId) async { if (files.isNotEmpty) { setMultiUploadingFileStatus(files.map((e) => e.path).toList(), true); + var res = await uploadFilesServer(files, appId: appId); if (res != null) { uploadedFiles.addAll(res); @@ -279,51 +266,51 @@ class MessageProvider extends ChangeNotifier { messages.insert(0, message); notifyListeners(); - try { - bool firstChunkRecieved = false; - await for (var chunk in sendVoiceMessageStreamServer([file])) { - if (!firstChunkRecieved && [MessageChunkType.data, MessageChunkType.done].contains(chunk.type)) { - firstChunkRecieved = true; - if (onFirstChunkRecived != null) { - onFirstChunkRecived(); - } - } - - if (chunk.type == MessageChunkType.think) { - message.thinkings.add(chunk.text); - notifyListeners(); - continue; - } - - if (chunk.type == MessageChunkType.data) { - message.text += chunk.text; - notifyListeners(); - continue; - } - - if (chunk.type == MessageChunkType.done) { - message = chunk.message!; - messages[0] = message; - notifyListeners(); - continue; - } - - if (chunk.type == MessageChunkType.message) { - messages.insert(1, chunk.message!); - notifyListeners(); - continue; - } - - if (chunk.type == MessageChunkType.error) { - message.text = chunk.text; - notifyListeners(); - continue; - } - } - } catch (e) { - message.text = ServerMessageChunk.failedMessage().text; - notifyListeners(); - } + // try { + // bool firstChunkRecieved = false; + // await for (var chunk in sendVoiceMessageStreamServer([file])) { + // if (!firstChunkRecieved && [MessageChunkType.data, MessageChunkType.done].contains(chunk.type)) { + // firstChunkRecieved = true; + // if (onFirstChunkRecived != null) { + // onFirstChunkRecived(); + // } + // } + + // if (chunk.type == MessageChunkType.think) { + // message.thinkings.add(chunk.text); + // notifyListeners(); + // continue; + // } + + // if (chunk.type == MessageChunkType.data) { + // message.text += chunk.text; + // notifyListeners(); + // continue; + // } + + // if (chunk.type == MessageChunkType.done) { + // message = chunk.message!; + // messages[0] = message; + // notifyListeners(); + // continue; + // } + + // if (chunk.type == MessageChunkType.message) { + // messages.insert(1, chunk.message!); + // notifyListeners(); + // continue; + // } + + // if (chunk.type == MessageChunkType.error) { + // message.text = chunk.text; + // notifyListeners(); + // continue; + // } + // } + // } catch (e) { + // message.text = ServerMessageChunk.failedMessage().text; + // notifyListeners(); + // } setShowTypingIndicator(false); } diff --git a/app/lib/providers/onboarding_provider.dart b/app/lib/providers/onboarding_provider.dart index 3797c84e580..aef672309b4 100644 --- a/app/lib/providers/onboarding_provider.dart +++ b/app/lib/providers/onboarding_provider.dart @@ -1,11 +1,9 @@ import 'dart:async'; -import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; -import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_provider_utilities/flutter_provider_utilities.dart'; import 'package:omi/backend/preferences.dart'; import 'package:omi/backend/schema/bt_device/bt_device.dart'; @@ -17,6 +15,7 @@ import 'package:omi/services/services.dart'; import 'package:omi/utils/alerts/app_snackbar.dart'; import 'package:omi/utils/analytics/analytics_manager.dart'; import 'package:omi/utils/audio/foreground.dart'; +import 'package:omi/utils/platform.dart'; import 'package:permission_handler/permission_handler.dart'; class OnboardingProvider extends BaseProvider with MessageNotifierMixin implements IDeviceServiceSubsciption { @@ -81,12 +80,12 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen Future askForBluetoothPermissions() async { FlutterBluePlus.setLogLevel(LogLevel.info, color: true); - if (Platform.isIOS) { + if (PlatformUtil.isIOS) { PermissionStatus bleStatus = await Permission.bluetooth.request(); debugPrint('bleStatus: $bleStatus'); updateBluetoothPermission(bleStatus.isGranted); } else { - if (Platform.isAndroid) { + if (PlatformUtil.isAndroid) { if (FlutterBluePlus.adapterStateNow != BluetoothAdapterState.on) { try { await FlutterBluePlus.turnOn(); @@ -114,10 +113,12 @@ class OnboardingProvider extends BaseProvider with MessageNotifierMixin implemen } Future askForBackgroundPermissions() async { - await FlutterForegroundTask.requestIgnoreBatteryOptimization(); + await ForegroundUtil.requestIgnoreBatteryOptimization(); // Use abstraction var isAllowed = await ForegroundUtil().isIgnoringBatteryOptimizations; - updateBackgroundPermission(isAllowed); - notifyListeners(); + if (isAllowed != null) { + updateBackgroundPermission(isAllowed); + notifyListeners(); + } } Future<(bool, PermissionStatus)> askForLocationPermissions() async { diff --git a/app/lib/providers/speech_profile_provider.dart b/app/lib/providers/speech_profile_provider.dart index f1a85c02de6..1739276830f 100644 --- a/app/lib/providers/speech_profile_provider.dart +++ b/app/lib/providers/speech_profile_provider.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_provider_utilities/flutter_provider_utilities.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:omi/backend/http/api/speech_profile.dart'; import 'package:omi/backend/http/api/users.dart'; import 'package:omi/backend/preferences.dart'; @@ -166,7 +168,10 @@ class SpeechProfileProvider extends ChangeNotifier updateLoadingText('Memorizing your voice...'); var data = await audioStorage.createWavFile(filename: 'speaker_profile.wav'); try { - await uploadProfile(data.item1); + var bytes = await data.item1.readAsBytes(); + XFile uploadFile = XFile.fromData(Uint8List.fromList(bytes)); + + await uploadProfile(uploadFile); } catch (e) {} updateLoadingText('Personalizing your experience...'); diff --git a/app/lib/providers/vad.dart b/app/lib/providers/vad.dart index 2a059458391..2ab987c6777 100644 --- a/app/lib/providers/vad.dart +++ b/app/lib/providers/vad.dart @@ -1,10 +1,10 @@ import 'dart:async'; -import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/services.dart'; import 'package:flutter_silero_vad/flutter_silero_vad.dart'; import 'package:omi/utils/audio/wav_bytes.dart'; +import 'package:omi/utils/omi_file/omi_file.dart'; import 'package:opus_dart/opus_dart.dart'; import 'package:path_provider/path_provider.dart'; @@ -87,7 +87,7 @@ class AudioProcessorService { Timer(const Duration(seconds: 30), () async { print('Timer started'); Uint8List wavBytes = wavBytesUtil.getUInt8ListBytes(validFrames, sampleRate); - final file = File('${(await getApplicationDocumentsDirectory()).path}/output.wav'); + final file = OmiFile('${(await getApplicationDocumentsDirectory()).path}/output.wav'); await file.writeAsBytes(wavBytes); }); } @@ -209,7 +209,7 @@ class AudioProcessorService { ..setUint8(0x27, 0x61) // 'a' ..setUint32(40, pcmBytes.length, Endian.little); // Subchunk2Size - File(filePath).writeAsBytesSync(wavHeader.buffer.asUint8List() + pcmBytes); + OmiFile(filePath).writeAsBytesSync(wavHeader.buffer.asUint8List() + pcmBytes); } /// Transforms a list of bytes into an [Int16List] for processing. @@ -226,6 +226,6 @@ class AudioProcessorService { Future onnxModelToLocal() async { final data = await rootBundle.load('assets/silero_vad.v5.onnx'); final bytes = data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes); - File(await modelPath).writeAsBytesSync(bytes); + OmiFile(await modelPath).writeAsBytesSync(bytes); } } diff --git a/app/lib/services/devices/device_connection.dart b/app/lib/services/devices/device_connection.dart index 69e064d0256..e5b810e6acb 100644 --- a/app/lib/services/devices/device_connection.dart +++ b/app/lib/services/devices/device_connection.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; @@ -9,6 +8,7 @@ import 'package:omi/services/devices.dart'; import 'package:omi/services/devices/frame_connection.dart'; import 'package:omi/services/devices/omi_connection.dart'; import 'package:omi/services/notifications.dart'; +import 'package:omi/utils/platform.dart'; class DeviceConnectionFactory { static DeviceConnection? create( @@ -82,7 +82,7 @@ abstract class DeviceConnection { } // Mtu - if (Platform.isAndroid && bleDevice.mtuNow < 512) { + if (PlatformUtil.isAndroid && bleDevice.mtuNow < 512) { await bleDevice.requestMtu(512); // This might fix the code 133 error } diff --git a/app/lib/services/devices/models.dart b/app/lib/services/devices/models.dart index 8a37da2f239..46a34b35682 100644 --- a/app/lib/services/devices/models.dart +++ b/app/lib/services/devices/models.dart @@ -46,7 +46,7 @@ Future> getBleServices(String deviceId) async { return []; } else { // TODO: need to be fixed for open glass - // if (Platform.isAndroid && device.servicesList.isNotEmpty) return device.servicesList; + // if (PlatformUtil.isAndroid && device.servicesList.isNotEmpty) return device.servicesList; if (device.servicesList.isNotEmpty) return device.servicesList; return await device.discoverServices(); } diff --git a/app/lib/services/devices/omi_connection.dart b/app/lib/services/devices/omi_connection.dart index 1a2c3eaa55a..2b68671ec2d 100644 --- a/app/lib/services/devices/omi_connection.dart +++ b/app/lib/services/devices/omi_connection.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'dart:math'; import 'package:awesome_notifications/awesome_notifications.dart'; @@ -13,6 +12,7 @@ import 'package:omi/services/devices/errors.dart'; import 'package:omi/services/devices/models.dart'; import 'package:omi/utils/audio/wav_bytes.dart'; import 'package:omi/utils/logger.dart'; +import 'package:omi/utils/platform.dart'; class OmiDeviceConnection extends DeviceConnection { BluetoothService? _batteryService; @@ -166,7 +166,7 @@ class OmiDeviceConnection extends DeviceConnection { // https://github.com/chipweinberger/flutter_blue_plus/issues/612 final device = bleDevice; if (device.isConnected) { - if (Platform.isAndroid && device.mtuNow < 512) { + if (PlatformUtil.isAndroid && device.mtuNow < 512) { await device.requestMtu(512); // This might fix the code 133 error } if (device.isConnected) { @@ -197,7 +197,7 @@ class OmiDeviceConnection extends DeviceConnection { // This will cause a crash in OpenGlass devices // due to a race with discoverServices() that triggers // a bug in the device firmware. - if (Platform.isAndroid && device.isConnected) await device.requestMtu(512); + if (PlatformUtil.isAndroid && device.isConnected) await device.requestMtu(512); return listener; } @@ -223,7 +223,7 @@ class OmiDeviceConnection extends DeviceConnection { // https://github.com/chipweinberger/flutter_blue_plus/issues/612 final device = bleDevice; if (device.isConnected) { - if (Platform.isAndroid && device.mtuNow < 512) { + if (PlatformUtil.isAndroid && device.mtuNow < 512) { await device.requestMtu(512); // This might fix the code 133 error } if (device.isConnected) { @@ -253,7 +253,7 @@ class OmiDeviceConnection extends DeviceConnection { // This will cause a crash in OpenGlass devices // due to a race with discoverServices() that triggers // a bug in the device firmware. - if (Platform.isAndroid && device.isConnected) await device.requestMtu(512); + if (PlatformUtil.isAndroid && device.isConnected) await device.requestMtu(512); return listener; } @@ -384,7 +384,7 @@ class OmiDeviceConnection extends DeviceConnection { // This will cause a crash in OpenGlass devices // due to a race with discoverServices() that triggers // a bug in the device firmware. - if (Platform.isAndroid) await device.requestMtu(512); + if (PlatformUtil.isAndroid) await device.requestMtu(512); return listener; } @@ -518,7 +518,7 @@ class OmiDeviceConnection extends DeviceConnection { // This will cause a crash in OpenGlass devices // due to a race with discoverServices() that triggers // a bug in the device firmware. - // if (Platform.isAndroid) await device.requestMtu(512); + // if (PlatformUtil.isAndroid) await device.requestMtu(512); return listener; } diff --git a/app/lib/services/notifications.dart b/app/lib/services/notifications.dart index 7dbd2f40653..4ba521bbfe5 100644 --- a/app/lib/services/notifications.dart +++ b/app/lib/services/notifications.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'dart:isolate'; import 'dart:math'; import 'dart:ui'; @@ -14,7 +13,9 @@ import 'package:omi/backend/http/api/notifications.dart'; import 'package:omi/backend/schema/message.dart'; import 'package:omi/main.dart'; import 'package:omi/pages/home/page.dart'; -import 'package:intercom_flutter/intercom_flutter.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; +import 'package:omi/utils/platform.dart'; + class NotificationService { NotificationService._(); @@ -37,6 +38,7 @@ class NotificationService { final AwesomeNotifications _awesomeNotifications = AwesomeNotifications(); Future initialize() async { + if (PlatformUtil.isWeb) return; await _initializeAwesomeNotifications(); // Calling it here because the APNS token can sometimes arrive early or it might take some time (like a few seconds) // Reference: https://github.com/firebase/flutterfire/issues/12244#issuecomment-1969286794 @@ -93,6 +95,7 @@ class NotificationService { } Future requestNotificationPermissions() async { + if (PlatformUtil.isWeb) return false; bool isAllowed = await _awesomeNotifications.isNotificationAllowed(); if (!isAllowed) { isAllowed = await _awesomeNotifications.requestPermissionToSendNotifications(); @@ -104,6 +107,7 @@ class NotificationService { // Whereever this method is awaited, it will cause the app to not move forwared in execution due to it being a method call. // This was also the culprit when we had the app freeze on splash screen. Future register() async { + if (PlatformUtil.isWeb) return; try { await platform.invokeMethod( 'setNotificationOnKillService', @@ -126,13 +130,14 @@ class NotificationService { if (token == null) return; String timeZone = await getTimeZone(); if (FirebaseAuth.instance.currentUser != null && token.isNotEmpty) { - await Intercom.instance.sendTokenToIntercom(token); + await IntercomManager().sendTokenToIntercom(token); await saveFcmTokenServer(token: token, timeZone: timeZone); } } void saveNotificationToken() async { - if (Platform.isIOS) { + if (PlatformUtil.isWeb) return; + if (PlatformUtil.isIOS) { await _firebaseMessaging.getAPNSToken(); } String? token = await _firebaseMessaging.getToken(); @@ -150,6 +155,7 @@ class NotificationService { int notificationId = 1, Map? payload, }) async { + if (PlatformUtil.isWeb) return; var allowed = await _awesomeNotifications.isNotificationAllowed(); debugPrint('createNotification: $allowed'); if (!allowed) return; @@ -161,10 +167,11 @@ class NotificationService { // FIXME: Causes the different behavior on android and iOS bool _shouldShowForegroundNotificationOnFCMMessageReceived() { - return Platform.isAndroid; + return PlatformUtil.isAndroid; } Future listenForMessages() async { + if (PlatformUtil.isWeb) return; FirebaseMessaging.onMessage.listen((RemoteMessage message) { final data = message.data; final noti = message.notification; @@ -204,6 +211,7 @@ class NotificationService { {required RemoteNotification noti, NotificationLayout layout = NotificationLayout.Default, Map? payload}) async { + if (PlatformUtil.isWeb) return; final id = Random().nextInt(10000); showNotification(id: id, title: noti.title!, body: noti.body!, layout: layout, payload: payload); } @@ -218,6 +226,7 @@ class NotificationUtil { } static Future initializeIsolateReceivePort() async { + if (PlatformUtil.isWeb) return; receivePort = ReceivePort('Notification action port in main isolate'); receivePort!.listen((serializedData) { final receivedAction = ReceivedAction().fromMap(serializedData); diff --git a/app/lib/services/services.dart b/app/lib/services/services.dart index c2afbf0fa57..2ce02fc02d3 100644 --- a/app/lib/services/services.dart +++ b/app/lib/services/services.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; @@ -8,6 +7,7 @@ import 'package:flutter_sound/flutter_sound.dart'; import 'package:omi/services/devices.dart'; import 'package:omi/services/sockets.dart'; import 'package:omi/services/wals.dart'; +import 'package:omi/utils/platform.dart'; class ServiceManager { late IMicRecorderService _mic; @@ -19,7 +19,8 @@ class ServiceManager { static ServiceManager _create() { ServiceManager sm = ServiceManager(); - sm._mic = MicRecorderBackgroundService( + + sm._mic = PlatformUtil.isWeb ? MicRecorderService(isInBG: false): MicRecorderBackgroundService( runner: BackgroundService(), ); sm._device = DeviceService(); @@ -81,7 +82,7 @@ Future onStart(ServiceInstance service) async { // Recorder MicRecorderService? recorder; service.on('recorder.start').listen((event) async { - recorder = MicRecorderService(isInBG: Platform.isAndroid ? true : false); + recorder = MicRecorderService(isInBG: PlatformUtil.isAndroid ? true : false); recorder?.start(onByteReceived: (bytes) { Uint8List audioBytes = bytes; List audioBytesList = audioBytes.toList(); diff --git a/app/lib/services/sockets/pure_socket.dart b/app/lib/services/sockets/pure_socket.dart index 42507f41c29..d26396e4078 100644 --- a/app/lib/services/sockets/pure_socket.dart +++ b/app/lib/services/sockets/pure_socket.dart @@ -1,11 +1,10 @@ import 'dart:async'; -import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; import 'package:internet_connection_checker_plus/internet_connection_checker_plus.dart'; -import 'package:web_socket_channel/io.dart'; +import 'package:omi/services/sockets/universal_socket/universal_socket.dart'; import 'package:web_socket_channel/status.dart' as socket_channel_status; import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:omi/backend/http/shared.dart'; @@ -116,7 +115,7 @@ class PureSocket implements IPureSocket { } debugPrint("request wss ${url}"); - _channel = IOWebSocketChannel.connect( + _channel = await createUniversalSocket( url, headers: { 'Authorization': await getAuthHeader(), @@ -124,6 +123,7 @@ class PureSocket implements IPureSocket { pingInterval: const Duration(seconds: 20), connectTimeout: const Duration(seconds: 30), ); + if (_channel?.ready == null) { return false; } @@ -132,8 +132,6 @@ class PureSocket implements IPureSocket { dynamic err; try { await channel.ready; - } on SocketException catch (e) { - err = e; } on WebSocketChannelException catch (e) { err = e; } diff --git a/app/lib/services/sockets/sdcard_socket.dart b/app/lib/services/sockets/sdcard_socket.dart index f53c76a8c16..2c608b3063f 100644 --- a/app/lib/services/sockets/sdcard_socket.dart +++ b/app/lib/services/sockets/sdcard_socket.dart @@ -2,17 +2,19 @@ import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:omi/backend/http/shared.dart'; import 'package:omi/backend/preferences.dart'; import 'package:omi/backend/schema/message_event.dart'; import 'package:omi/env/env.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; -import 'package:web_socket_channel/io.dart'; +import 'package:omi/services/sockets/universal_socket/universal_socket.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; enum WebsocketConnectionStatus { notConnected, connected, failed, closed, error } // TODO: Implement from pure socket class SdCardSocketService { - IOWebSocketChannel? sdCardChannel; + WebSocketChannel? sdCardChannel; WebsocketConnectionStatus sdCardConnectionState = WebsocketConnectionStatus.notConnected; Timer? _reconnectionTimer; SdCardSocketService(); @@ -65,7 +67,7 @@ class SdCardSocketService { }); } - Future openSdCardStream({ + Future openSdCardStream({ required VoidCallback onWebsocketConnectionSuccess, required void Function(dynamic) onWebsocketConnectionFailed, required void Function(int?, String?) onWebsocketConnectionClosed, @@ -79,9 +81,11 @@ class SdCardSocketService { // '&include_speech_profile=$includeSpeechProfile&new_memory_watch=$newMemoryWatch&stt_service=${SharedPreferencesUtil().transcriptionModel}'; var params = '?uid=${SharedPreferencesUtil().uid}&bt_connected_time=$btConnectedTime'; debugPrint('btConnectedTime: $btConnectedTime'); - IOWebSocketChannel channel = IOWebSocketChannel.connect( - Uri.parse('${Env.apiBaseUrl!.replaceAll('https', 'wss')}sdcard_stream$params'), - // headers: {'Authorization': await getAuthHeader()}, + final channel = await createUniversalSocket( + '${Env.apiBaseUrl!.replaceAll('https', 'wss')}sdcard_stream$params', + headers: {'Authorization': await getAuthHeader()}, + pingInterval: const Duration(seconds: 20), + connectTimeout: const Duration(seconds: 30), ); await channel.ready.then((v) { diff --git a/app/lib/services/sockets/universal_socket/universal_socket.dart b/app/lib/services/sockets/universal_socket/universal_socket.dart new file mode 100644 index 00000000000..2b26664940a --- /dev/null +++ b/app/lib/services/sockets/universal_socket/universal_socket.dart @@ -0,0 +1,2 @@ +export 'universal_socket_io.dart' + if (dart.library.html) 'universal_socket_web.dart'; diff --git a/app/lib/services/sockets/universal_socket/universal_socket_io.dart b/app/lib/services/sockets/universal_socket/universal_socket_io.dart new file mode 100644 index 00000000000..85cbcb97359 --- /dev/null +++ b/app/lib/services/sockets/universal_socket/universal_socket_io.dart @@ -0,0 +1,11 @@ +import 'package:web_socket_channel/io.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +Future createUniversalSocket(String url, {Map? headers, required Duration pingInterval, required Duration connectTimeout}) async { + return IOWebSocketChannel.connect( + url, + headers: headers, + pingInterval: const Duration(seconds: 20), + connectTimeout: const Duration(seconds: 30), + ); +} diff --git a/app/lib/services/sockets/universal_socket/universal_socket_web.dart b/app/lib/services/sockets/universal_socket/universal_socket_web.dart new file mode 100644 index 00000000000..abd4073d4b0 --- /dev/null +++ b/app/lib/services/sockets/universal_socket/universal_socket_web.dart @@ -0,0 +1,8 @@ +import 'package:web/web.dart' as web; +import 'package:web_socket_channel/html.dart'; +import 'package:web_socket_channel/web_socket_channel.dart'; + +Future createUniversalSocket(String url, {Map? headers, required Duration pingInterval, required Duration connectTimeout}) async { + final socket = web.WebSocket(url.toString()); + return HtmlWebSocketChannel(socket); +} diff --git a/app/lib/services/wals.dart b/app/lib/services/wals.dart index 8f31c2e7b93..72556ee897a 100644 --- a/app/lib/services/wals.dart +++ b/app/lib/services/wals.dart @@ -1,6 +1,5 @@ import 'dart:async'; import 'dart:collection'; -import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; @@ -9,6 +8,7 @@ import 'package:omi/backend/preferences.dart'; import 'package:omi/backend/schema/bt_device/bt_device.dart'; import 'package:omi/backend/schema/conversation.dart'; import 'package:omi/services/services.dart'; +import 'package:omi/utils/omi_file/omi_file.dart'; import 'package:path_provider/path_provider.dart'; const chunkSizeInSeconds = 60; @@ -248,7 +248,7 @@ class SDCardWalSync implements IWalSync { return connection.getBleStorageBytesListener(onStorageBytesReceived: onStorageBytesReceived); } - Future _flushToDisk(List> chunk, int timerStart) async { + Future _flushToDisk(List> chunk, int timerStart) async { final directory = await getApplicationDocumentsDirectory(); String filePath = '${directory.path}/audio_${timerStart}.bin'; List data = []; @@ -263,13 +263,13 @@ class SDCardWalSync implements IWalSync { data.addAll(Uint32List.fromList([frame.length]).buffer.asUint8List()); data.addAll(byteFrame.buffer.asUint8List()); } - final file = File(filePath); + final file = OmiFile(filePath); await file.writeAsBytes(data); return file; } - Future _readStorageBytesToFile(Wal wal, Function(File f, int offset) callback) async { + Future _readStorageBytesToFile(Wal wal, Function(OmiFile f, int offset) callback) async { var deviceId = wal.device; // Move the offset @@ -370,13 +370,13 @@ class SDCardWalSync implements IWalSync { debugPrint("sync wal: ${wal.id} byte offset: ${wal.storageOffset} ts ${wal.timerStart}"); var resp = SyncLocalFilesResponse(newConversationIds: [], updatedConversationIds: []); - List files = []; + List files = []; var limit = 2; // Read with file chunking int lastOffset = 0; - await _readStorageBytesToFile(wal, (File file, int offset) async { + await _readStorageBytesToFile(wal, (OmiFile file, int offset) async { files.add(file); lastOffset = offset; @@ -633,7 +633,7 @@ class LocalWalSync implements IWalSync { data.addAll(Uint32List.fromList([frame.length]).buffer.asUint8List()); data.addAll(byteFrame.buffer.asUint8List()); } - final file = File(filePath); + final file = OmiFile(filePath); await file.writeAsBytes(data); wal.filePath = filePath; wal.storage = WalStorage.disk; @@ -657,8 +657,9 @@ class LocalWalSync implements IWalSync { Future _deleteWal(Wal wal) async { if (wal.filePath != null && wal.filePath!.isNotEmpty) { try { - final file = File(wal.filePath!); - if (file.existsSync()) { + final file = OmiFile(wal.filePath!); + bool exists =await file.exists(); + if (exists) { await file.delete(); } } catch (e) { @@ -713,7 +714,7 @@ class LocalWalSync implements IWalSync { left = 0; } - List files = []; + List files = []; for (var j = left; j <= right; j++) { var wal = wals[j]; debugPrint("sync id ${wal.id} ${wal.timerStart}"); @@ -726,8 +727,9 @@ class LocalWalSync implements IWalSync { debugPrint("sync wal: ${wal.id} file: ${wal.filePath}"); try { - File file = File(wal.filePath!); - if (!file.existsSync()) { + OmiFile file = OmiFile(wal.filePath!); + bool exists = await file.exists(); + if (!exists) { debugPrint("file ${wal.filePath} is not exists"); wal.status = WalStatus.corrupted; continue; @@ -790,14 +792,15 @@ class LocalWalSync implements IWalSync { // Empty resp var resp = SyncLocalFilesResponse(newConversationIds: [], updatedConversationIds: []); - late File walFile; + late OmiFile walFile; if (wal.filePath == null) { debugPrint("file path is not found. wal id ${wal.id}"); wal.status = WalStatus.corrupted; } try { - File file = File(wal.filePath!); - if (!file.existsSync()) { + OmiFile file = OmiFile(wal.filePath!); + bool exists = await file.exists(); + if (!exists) { debugPrint("file ${wal.filePath} is not exists"); wal.status = WalStatus.corrupted; } else { diff --git a/app/lib/utils/alerts/app_dialog.dart b/app/lib/utils/alerts/app_dialog.dart index bf0945514ea..8c852816cb3 100644 --- a/app/lib/utils/alerts/app_dialog.dart +++ b/app/lib/utils/alerts/app_dialog.dart @@ -1,8 +1,8 @@ -import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:omi/main.dart'; +import 'package:omi/utils/platform.dart'; class AppDialog { static _getDialog({ @@ -36,7 +36,7 @@ class AppDialog { ), ), ]; - if (Platform.isIOS) { + if (PlatformUtil.isIOS) { return CupertinoAlertDialog( title: Text(title), content: Text(content), diff --git a/app/lib/utils/analytics/analytics_manager.dart b/app/lib/utils/analytics/analytics_manager.dart index 098d6f47fc2..d9d35d29c6f 100644 --- a/app/lib/utils/analytics/analytics_manager.dart +++ b/app/lib/utils/analytics/analytics_manager.dart @@ -1,4 +1,4 @@ -import 'package:omi/utils/analytics/intercom.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; import 'package:omi/utils/analytics/mixpanel.dart'; class AnalyticsManager { diff --git a/app/lib/utils/analytics/growthbook.dart b/app/lib/utils/analytics/growthbook.dart index b1420d2bbbf..f789ac24a97 100644 --- a/app/lib/utils/analytics/growthbook.dart +++ b/app/lib/utils/analytics/growthbook.dart @@ -1,9 +1,9 @@ -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:omi/backend/preferences.dart'; import 'package:omi/env/env.dart'; import 'package:growthbook_sdk_flutter/growthbook_sdk_flutter.dart'; +import 'package:omi/utils/platform.dart'; class GrowthbookUtil { static final GrowthbookUtil _instance = GrowthbookUtil._internal(); @@ -20,7 +20,7 @@ class GrowthbookUtil { print('GrowthbookUtil init'); var attr = { 'id': SharedPreferencesUtil().uid, - 'device': Platform.isAndroid ? 'android' : 'ios', + 'device': PlatformUtil.isAndroid ? 'android' : 'ios', }; _gb = await GBSDKBuilderApp( apiKey: Env.growthbookApiKey!, diff --git a/app/lib/utils/analytics/intercom.dart b/app/lib/utils/analytics/intercom_manager.dart similarity index 69% rename from app/lib/utils/analytics/intercom.dart rename to app/lib/utils/analytics/intercom_manager.dart index a4291515a1b..0a663754e78 100644 --- a/app/lib/utils/analytics/intercom.dart +++ b/app/lib/utils/analytics/intercom_manager.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:omi/backend/preferences.dart'; import 'package:omi/env/env.dart'; import 'package:intercom_flutter/intercom_flutter.dart'; +import 'package:omi/utils/platform.dart'; class IntercomManager { static final IntercomManager _instance = IntercomManager._internal(); @@ -18,7 +19,7 @@ class IntercomManager { } Future initIntercom() async { - if (Env.intercomAppId == null) return; + if (PlatformUtil.isWeb || Env.intercomAppId == null) return; await intercom.initialize( Env.intercomAppId!, iosApiKey: Env.intercomIOSApiKey, @@ -27,6 +28,7 @@ class IntercomManager { } Future displayChargingArticle(String device) async { + if (PlatformUtil.isWeb) return; if (device == 'Omi DevKit 2') { return await intercom.displayArticle('10003257-how-to-charge-devkit2'); } else { @@ -35,22 +37,27 @@ class IntercomManager { } Future displayEarnMoneyArticle() async { + if (PlatformUtil.isWeb) return; return await intercom.displayArticle('10401566-build-publish-and-earn-with-omi-apps'); } Future displayFirmwareUpdateArticle() async { + if (PlatformUtil.isWeb) return; return await intercom.displayArticle('9995941-updating-your-devkit2-firmware'); } Future logEvent(String eventName, {Map? metaData}) async { + if (PlatformUtil.isWeb) return; return await intercom.logEvent(eventName, metaData); } Future updateCustomAttributes(Map attributes) async { + if (PlatformUtil.isWeb) return; return await intercom.updateUser(customAttributes: attributes); } Future updateUser(String? email, String? name, String? uid) async { + if (PlatformUtil.isWeb) return; return await intercom.updateUser( email: email, name: name, @@ -59,6 +66,7 @@ class IntercomManager { } Future setUserAttributes() async { + if (PlatformUtil.isWeb) return; await updateCustomAttributes({ 'Notifications Enabled': _preferences.notificationsEnabled, 'Location Enabled': _preferences.locationEnabled, @@ -70,4 +78,32 @@ class IntercomManager { 'Authorized Storing Recordings': _preferences.permissionStoreRecordingsEnabled, }); } + + + + Future displayMessenger()async{ + if (PlatformUtil.isWeb) return; + await intercom.displayMessenger(); + } + + Future displayHelpCenter() async { + if (PlatformUtil.isWeb) return; + await intercom.displayHelpCenter(); + } + + Future loginIdentifiedUser({required String userId}) async { + if (PlatformUtil.isWeb) return; + await intercom.loginIdentifiedUser(userId: userId); + } + + Future loginUnidentifiedUser() async { + if (PlatformUtil.isWeb) return; + await intercom.loginUnidentifiedUser(); + } + + Future sendTokenToIntercom(String token) async { + if (PlatformUtil.isWeb) return; + await intercom.sendTokenToIntercom(token); + } + } diff --git a/app/lib/utils/audio/foreground.dart b/app/lib/utils/audio/foreground.dart index b8a1f7bd15f..bc4d2ea4078 100644 --- a/app/lib/utils/audio/foreground.dart +++ b/app/lib/utils/audio/foreground.dart @@ -1,8 +1,7 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:geolocator/geolocator.dart'; +import 'package:omi/utils/platform.dart'; @pragma('vm:entry-point') void _startForegroundCallback() { @@ -64,7 +63,29 @@ class _ForegroundFirstTaskHandler extends TaskHandler { } class ForegroundUtil { + // Add this new method + static void sendDataToTask(Object data) { + if (PlatformUtil.isWeb) return; + FlutterForegroundTask.sendDataToTask(data); + } + + static void addTaskDataCallback(Function(Object) callback) { + if (PlatformUtil.isWeb) return; + FlutterForegroundTask.addTaskDataCallback(callback); + } + + static void initCommunicationPort() { + if (PlatformUtil.isWeb) return; + FlutterForegroundTask.initCommunicationPort(); + } + + static requestIgnoreBatteryOptimization() async { + if (PlatformUtil.isWeb) return; + await FlutterForegroundTask.requestIgnoreBatteryOptimization(); + } + static Future requestPermissions() async { + if (PlatformUtil.isWeb) return; // Android 13+, you need to allow notification permission to display foreground service notification. // // iOS: If you need notification, ask for permission. @@ -74,7 +95,7 @@ class ForegroundUtil { await FlutterForegroundTask.requestNotificationPermission(); } - if (Platform.isAndroid) { + if (PlatformUtil.isAndroid) { // if (!await FlutterForegroundTask.canDrawOverlays) { // await FlutterForegroundTask.openSystemAlertWindowSettings(); // } @@ -84,9 +105,13 @@ class ForegroundUtil { } } - Future get isIgnoringBatteryOptimizations async => await FlutterForegroundTask.isIgnoringBatteryOptimizations; + Future get isIgnoringBatteryOptimizations async { + if (PlatformUtil.isWeb) null; + return await FlutterForegroundTask.isIgnoringBatteryOptimizations; + } static Future initializeForegroundService() async { + if (PlatformUtil.isWeb) return; if (await FlutterForegroundTask.isRunningService) return; debugPrint('initializeForegroundService'); // await Location().requestPermission(); @@ -118,7 +143,8 @@ class ForegroundUtil { ); } - static Future startForegroundTask() async { + static Future startForegroundTask() async { + if (PlatformUtil.isWeb) return null; debugPrint('startForegroundTask'); if (await FlutterForegroundTask.isRunningService) { return FlutterForegroundTask.restartService(); @@ -132,6 +158,7 @@ class ForegroundUtil { } static Future stopForegroundTask() async { + if (PlatformUtil.isWeb) return; debugPrint('stopForegroundTask'); if (await FlutterForegroundTask.isRunningService) { await FlutterForegroundTask.stopService(); diff --git a/app/lib/utils/audio/wav_bytes.dart b/app/lib/utils/audio/wav_bytes.dart index 410c47c4b91..4930ee8e455 100644 --- a/app/lib/utils/audio/wav_bytes.dart +++ b/app/lib/utils/audio/wav_bytes.dart @@ -1,6 +1,6 @@ import 'dart:async'; -import 'dart:io'; import 'dart:math'; +import 'dart:io'; import 'dart:typed_data'; import 'package:flutter/material.dart'; @@ -8,6 +8,8 @@ import 'package:omi/backend/schema/bt_device/bt_device.dart'; import 'package:omi/utils/logger.dart'; import 'package:instabug_flutter/instabug_flutter.dart'; import 'package:intl/intl.dart'; +import 'package:omi/utils/omi_file/omi_file.dart'; +import 'package:omi/utils/platform.dart'; import 'package:opus_dart/opus_dart.dart'; import 'package:path_provider/path_provider.dart'; import 'package:tuple/tuple.dart'; @@ -168,36 +170,36 @@ class WavBytesUtil { // } // } - static Future listFiles(Directory? directory) async { - if (directory == null) return; - final totalBytes = await _getDirectorySize(directory); + // static Future listFiles(Directory? directory) async { + // if (directory == null) return; + // final totalBytes = await _getDirectorySize(directory); - final totalSize = formatBytes(totalBytes); - debugPrint('Total size of $directory: $totalSize'); - } + // final totalSize = formatBytes(totalBytes); + // debugPrint('Total size of $directory: $totalSize'); + // } - static Future _getDirectorySize(Directory dir) async { - int totalBytes = 0; - - try { - if (dir.existsSync()) { - final List entities = dir.listSync(recursive: true, followLinks: false); - for (var entity in entities) { - if (entity is File) { - // debugPrint('File: ${entity.path}'); - totalBytes += await entity.length(); - } else if (entity is Directory) { - // debugPrint('Directory: ${entity.path}'); - totalBytes += await _getDirectorySize(entity); - } - } - } - } catch (e) { - debugPrint("Error calculating directory size: $e"); - } + // static Future _getDirectorySize(Directory dir) async { + // int totalBytes = 0; + + // try { + // if (dir.existsSync()) { + // final List entities = dir.listSync(recursive: true, followLinks: false); + // for (var entity in entities) { + // if (entity is File) { + // // debugPrint('File: ${entity.path}'); + // totalBytes += await entity.length(); + // } else if (entity is Directory) { + // // debugPrint('Directory: ${entity.path}'); + // totalBytes += await _getDirectorySize(entity); + // } + // } + // } + // } catch (e) { + // debugPrint("Error calculating directory size: $e"); + // } - return totalBytes; - } + // return totalBytes; + // } static String formatBytes(int bytes, {int decimals = 2}) { if (bytes <= 0) return "0 B"; @@ -207,25 +209,31 @@ class WavBytesUtil { return "$size ${suffixes[i]}"; } - static Future getDir() => getTemporaryDirectory(); + static Future getDir() async => + PlatformUtil.isWeb ? "/virtual/web/temp" : (await getTemporaryDirectory()).path; /* * FINISHED TESTING LOGIC * */ static clearTempWavFiles() async { - var dirs = [await getTemporaryDirectory(), await getApplicationDocumentsDirectory()]; - for (var directory in dirs) { - var file0 = File('${(await getDir()).path}/temp.wav'); - if (file0.existsSync()) file0.deleteSync(); - - if (directory.existsSync()) { - final List entities = directory.listSync(recursive: false, followLinks: false); - for (var entity in entities) { - if (entity is File && entity.path.endsWith('.wav')) { - debugPrint('Removing file: ${entity.path}'); - if (entity.existsSync()) { - await entity.delete(); + if (PlatformUtil.isWeb) { + var file0 = OmiFile('${await getDir()}/temp.wav'); + if (await file0.exists()) file0.deleteSync(); + } else { + var dirs = [await getTemporaryDirectory(), await getApplicationDocumentsDirectory()]; + for (var directory in dirs) { + var file0 = OmiFile('${await getDir()}/temp.wav'); + if (await file0.exists()) file0.deleteSync(); + + if (directory.existsSync()) { + final List entities = directory.listSync(recursive: false, followLinks: false); + for (var entity in entities) { + if (entity is File && entity.path.endsWith('.wav')) { + debugPrint('Removing file: ${entity.path}'); + if (entity.existsSync()) { + await entity.delete(); + } } } } @@ -240,17 +248,17 @@ class WavBytesUtil { static tempWavExists() async { final directory = await getDir(); - var file0 = File('${directory.path}/temp.wav'); - return file0.existsSync(); + var file0 = OmiFile('$directory/temp.wav'); + return file0.exists(); } static deleteTempWav() async { final directory = await getDir(); - var file0 = File('${directory.path}/temp.wav'); - if (file0.existsSync()) file0.deleteSync(); + var file0 = OmiFile('$directory/temp.wav'); + if (await file0.exists()) file0.deleteSync(); } - Future>>> createWavFile({String? filename, int removeLastNSeconds = 0}) async { + Future>>> createWavFile({String? filename, int removeLastNSeconds = 0}) async { debugPrint('createWavFile $filename'); List> framesCopy; if (removeLastNSeconds > 0) { @@ -263,13 +271,13 @@ class WavBytesUtil { clearAudioBytes(); } debugPrint('about to write to other codec'); - File file = await createWavByCodec(framesCopy, filename: filename); + OmiFile file = await createWavByCodec(framesCopy, filename: filename); return Tuple2(file, framesCopy); } /// OPUS - Future createWavByCodec(List> frames, {String? filename}) async { + Future createWavByCodec(List> frames, {String? filename}) async { Uint8List wavBytes; if (codec == BleAudioCodec.pcm8 || codec == BleAudioCodec.pcm16) { Int16List samples = getPcmSamples(frames); @@ -298,13 +306,13 @@ class WavBytesUtil { return createWav(wavBytes, filename: filename); } - Future createWav(Uint8List wavBytes, {String? filename}) async { + Future createWav(Uint8List wavBytes, {String? filename}) async { final directory = await getDir(); if (filename == null) { final timestamp = DateFormat('yyyyMMdd_HHmmss').format(DateTime.now()); filename = 'recording-$timestamp.wav'; } - final file = File('${directory.path}/$filename'); + final file = OmiFile('$directory/$filename'); await file.writeAsBytes(wavBytes); debugPrint('WAV file created: ${file.path}'); return file; @@ -526,17 +534,17 @@ class StorageBytesUtil extends WavBytesUtil { } @override - Future createWav(Uint8List wavBytes, {String? filename}) async { + Future createWav(Uint8List wavBytes, {String? filename}) async { final directory = await getDir(); String filename2 = 'recording-$fileNum.wav'; - final file = File('${directory.path}/$filename2'); + final file = OmiFile('$directory/$filename2'); await file.writeAsBytes(wavBytes); debugPrint('WAV file created: ${file.path}'); return file; } @override - Future createWavByCodec(List> frames, {String? filename}) async { + Future createWavByCodec(List> frames, {String? filename}) async { Uint8List wavBytes; List decodedSamples = []; @@ -547,5 +555,6 @@ class StorageBytesUtil extends WavBytesUtil { return createWav(wavBytes); } - static Future getDir() => getTemporaryDirectory(); + static Future getDir() async => + PlatformUtil.isWeb ? "/virtual/web/temp" : (await getTemporaryDirectory()).path; } diff --git a/app/lib/utils/device.dart b/app/lib/utils/device.dart index 2e5e6c7d67d..e40bba82821 100644 --- a/app/lib/utils/device.dart +++ b/app/lib/utils/device.dart @@ -1,4 +1,4 @@ -import 'dart:io'; +import 'package:omi/utils/platform.dart'; import 'package:version/version.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -30,7 +30,7 @@ class DeviceUtils { if (Version.parse(packageInfo.version) <= Version.parse(latestFirmwareDetails['min_app_version']) && int.parse(packageInfo.buildNumber) < int.parse(latestFirmwareDetails['min_app_version_code'])) { return ( - 'The latest version of firmware is not compatible with this version of App (${packageInfo.version}+${packageInfo.buildNumber}). Please update the app from ${Platform.isAndroid ? 'Play Store' : 'App Store'}', + 'The latest version of firmware is not compatible with this version of App (${packageInfo.version}+${packageInfo.buildNumber}). Please update the app from ${PlatformUtil.isAndroid ? 'Play Store' : 'App Store'}', false, latestVersionStr ); diff --git a/app/lib/utils/file.dart b/app/lib/utils/file.dart index 45b6e95ae78..3a792f133b8 100644 --- a/app/lib/utils/file.dart +++ b/app/lib/utils/file.dart @@ -1,12 +1,13 @@ -import 'dart:io'; + import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:omi/utils/audio/wav_bytes.dart'; +import 'package:omi/utils/omi_file/omi_file.dart'; import 'package:path_provider/path_provider.dart'; class FileUtils { - static Future saveAudioBytesToTempFile(List> chunk, int timerStart) async { + static Future saveAudioBytesToTempFile(List> chunk, int timerStart) async { final directory = await getTemporaryDirectory(); String filePath = '${directory.path}/audio_${timerStart}.bin'; List data = []; @@ -21,13 +22,13 @@ class FileUtils { data.addAll(Uint32List.fromList([frame.length]).buffer.asUint8List()); data.addAll(byteFrame.buffer.asUint8List()); } - final file = File(filePath); + final file = OmiFile(filePath); await file.writeAsBytes(data); return file; } - static Future convertPcmToWavFile(Uint8List pcmBytes, int sampleRate, int channels) async { + static Future convertPcmToWavFile(Uint8List pcmBytes, int sampleRate, int channels) async { try { // Convert PCM to WAV bytes final wavBytes = WavBytes.fromPcm( @@ -39,7 +40,7 @@ class FileUtils { // Create a temporary file final tempDir = await getTemporaryDirectory(); final tempPath = '${tempDir.path}/recording_${DateTime.now().millisecondsSinceEpoch}.wav'; - final file = File(tempPath); + final file = OmiFile(tempPath); // Write WAV bytes to file await file.writeAsBytes(wavBytes); diff --git a/app/lib/utils/logger.dart b/app/lib/utils/logger.dart index 2a774a9d871..cfd0756eb57 100644 --- a/app/lib/utils/logger.dart +++ b/app/lib/utils/logger.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; -import 'package:intercom_flutter/intercom_flutter.dart'; +import 'package:omi/utils/analytics/intercom_manager.dart'; + import 'package:talker_flutter/talker_flutter.dart'; class Logger { @@ -63,7 +64,7 @@ class LoggerSnackbar extends StatelessWidget { icon: const Icon(Icons.share, color: Colors.white), onPressed: () async { // TODO: Have a custom form which can be prefilled with the error stack trace instead of opening the Gleap Homepage - await Intercom.instance.displayMessenger(); + await IntercomManager().displayMessenger(); }, ), ), diff --git a/app/lib/utils/omi_file/omi_file.dart b/app/lib/utils/omi_file/omi_file.dart new file mode 100644 index 00000000000..a530fd003f7 --- /dev/null +++ b/app/lib/utils/omi_file/omi_file.dart @@ -0,0 +1,3 @@ + + +export 'src/omi_file_io.dart' if (dart.library.html) 'src/omi_file_web.dart'; \ No newline at end of file diff --git a/app/lib/utils/omi_file/omi_file_interface.dart b/app/lib/utils/omi_file/omi_file_interface.dart new file mode 100644 index 00000000000..9f6a486bbcf --- /dev/null +++ b/app/lib/utils/omi_file/omi_file_interface.dart @@ -0,0 +1,31 @@ + +abstract class IOmiFile { + String get path; + String get name; + int get size; + + Future exists(); + Future create(); + Future delete(); + Future writeAsString(String contents); + Future readAsString(); + Future append(String contents); + Future rename(String newName); + Future lastModified(); + + + Future writeAsBytes(List bytes); + Future> readAsBytes(); + + void createSync(); + void deleteSync(); + void writeAsStringSync(String contents); + String readAsStringSync(); + void appendSync(String contents); + void renameSync(String newName); + DateTime lastModifiedSync(); + + + void writeAsBytesSync(List bytes); + List readAsBytesSync(); +} diff --git a/app/lib/utils/omi_file/src/omi_file_io.dart b/app/lib/utils/omi_file/src/omi_file_io.dart new file mode 100644 index 00000000000..65f917c2517 --- /dev/null +++ b/app/lib/utils/omi_file/src/omi_file_io.dart @@ -0,0 +1,83 @@ +import 'dart:io'; + +import 'package:omi/utils/omi_file/omi_file_interface.dart'; + +class OmiFile implements IOmiFile { + final File _file; + + OmiFile(String path) : _file = File(path); + + @override + Future append(String contents) async => await _file.writeAsString(contents, mode: FileMode.append); + + @override + void appendSync(String contents) => _file.writeAsStringSync(contents, mode: FileMode.append); + + @override + Future create() async => await _file.create(); + + @override + void createSync() => _file.createSync(); + + @override + Future delete() async => await _file.delete(); + + @override + void deleteSync() => _file.deleteSync(); + + @override + Future exists() async=> await _file.exists(); + + @override + Future lastModified() async => await _file.lastModified(); + + @override + DateTime lastModifiedSync() => _file.lastModifiedSync(); + + @override + String get name => _file.uri.pathSegments.last; + + @override + String get path => _file.path; + + @override + Future> readAsBytes() async => await _file.readAsBytes(); + + @override + List readAsBytesSync() => _file.readAsBytesSync(); + + @override + Future readAsString() async => await _file.readAsString(); + + @override + String readAsStringSync() => _file.readAsStringSync(); + + @override + Future rename(String newName) async { + final directory = _file.parent; + final newPath = '${directory.path}/$newName'; + return await _file.rename(newPath); + } + + @override + File renameSync(String newName) { + final directory = _file.parent; + final newPath = '${directory.path}/$newName'; + return _file.renameSync(newPath); + } + + @override + int get size => _file.lengthSync(); + + @override + Future writeAsBytes(List bytes)async => await _file.writeAsBytes(bytes); + + @override + void writeAsBytesSync(List bytes) => _file.writeAsBytesSync(bytes); + + @override + Future writeAsString(String contents)async => await _file.writeAsString(contents); + + @override + void writeAsStringSync(String contents) => _file.writeAsStringSync(contents); +} diff --git a/app/lib/utils/omi_file/src/omi_file_web.dart b/app/lib/utils/omi_file/src/omi_file_web.dart new file mode 100644 index 00000000000..3751a1317ab --- /dev/null +++ b/app/lib/utils/omi_file/src/omi_file_web.dart @@ -0,0 +1,133 @@ +import 'dart:convert'; +import 'package:idb_shim/idb_browser.dart'; +import 'package:omi/utils/omi_file/omi_file_interface.dart'; + +class OmiFile implements IOmiFile { + static const _dbName = 'omi_file_db'; + static const _storeName = 'omi_files'; + + final String _path; + Database? _db; + + OmiFile(this._path); + + Future get _database async { + if (_db != null) return _db!; + final factory = idbFactoryBrowser; + _db = await factory.open(_dbName, version: 1, onUpgradeNeeded: (e) { + e.database.createObjectStore(_storeName); + }); + return _db!; + } + + + + @override + Future create() async { + await writeAsString(''); + return this; + } + + @override + void createSync() => throw UnsupportedError('Sync operations are not supported on the web'); + + @override + Future delete() async { + final db = await _database; + await db.transaction(_storeName, idbModeReadWrite).objectStore(_storeName).delete(_path); + return this; + } + + @override + void deleteSync() => throw UnsupportedError('Sync operations are not supported on the web'); + + @override + Future exists() async { + final db = await _database; + final value = await db.transaction(_storeName, idbModeReadOnly).objectStore(_storeName).getObject(_path); + return value != null; + } + + @override + Future lastModified() async { + throw UnsupportedError('lastModified not supported on web'); + } + + @override + DateTime lastModifiedSync() => throw UnsupportedError('Sync operations are not supported on the web'); + + @override + String get name => _path.split('/').last; + + @override + String get path => _path; + + @override + Future> readAsBytes() async { + final str = await readAsString(); + return utf8.encode(str); + } + + @override + List readAsBytesSync() => throw UnsupportedError('Sync operations are not supported on the web'); + + @override + Future readAsString() async { + final db = await _database; + final data = await db.transaction(_storeName, idbModeReadOnly).objectStore(_storeName).getObject(_path); + return data?.toString() ?? ''; + } + + @override + String readAsStringSync() => throw UnsupportedError('Sync operations are not supported on the web'); + + @override + Future rename(String newName) async { + final contents = await readAsString(); + await delete(); + final newFile = OmiFile(newName); + await newFile.writeAsString(contents); + return newFile; + } + + @override + void renameSync(String newName) => throw UnsupportedError('Sync operations are not supported on the web'); + + @override + int get size => throw UnsupportedError('Use sizeAsync on web'); + + Future get sizeAsync async { + final bytes = await readAsBytes(); + return bytes.length; + } + + @override + Future writeAsBytes(List bytes) async { + await writeAsString(utf8.decode(bytes)); + return this; + } + + @override + void writeAsBytesSync(List bytes) => throw UnsupportedError('Sync operations are not supported on the web'); + + @override + Future writeAsString(String contents) async { + final db = await _database; + await db.transaction(_storeName, idbModeReadWrite).objectStore(_storeName).put(contents, _path); + return this; + } + + @override + void writeAsStringSync(String contents) => throw UnsupportedError('Sync operations are not supported on the web'); + + @override + Future append(String contents) async { + final existing = await readAsString(); + await writeAsString(existing + contents); + return this; + } + + @override + void appendSync(String contents) => throw UnsupportedError('Sync operations are not supported on the web'); + +} diff --git a/app/lib/utils/other/temp.dart b/app/lib/utils/other/temp.dart index 1a8a2d75f26..45d00f892fe 100644 --- a/app/lib/utils/other/temp.dart +++ b/app/lib/utils/other/temp.dart @@ -1,8 +1,8 @@ -import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:omi/utils/platform.dart'; String dateTimeFormat(String format, DateTime? dateTime, {String? locale}) { if (dateTime == null) return ''; @@ -10,7 +10,7 @@ String dateTimeFormat(String format, DateTime? dateTime, {String? locale}) { } Future routeToPage(BuildContext context, Widget page, {bool replace = false}) { - var route = Platform.isIOS ? CupertinoPageRoute(builder: (c) => page) : MaterialPageRoute(builder: (c) => page); + var route = PlatformUtil.isIOS ? CupertinoPageRoute(builder: (c) => page) : MaterialPageRoute(builder: (c) => page); if (replace) { if (context.mounted) { return Navigator.of(context).pushAndRemoveUntil(route, (route) => false); diff --git a/app/lib/utils/platform.dart b/app/lib/utils/platform.dart new file mode 100644 index 00000000000..9bdef8019a5 --- /dev/null +++ b/app/lib/utils/platform.dart @@ -0,0 +1,13 @@ +import 'package:flutter/foundation.dart'; + +class PlatformUtil { + static bool get isWeb => kIsWeb; + static bool get isNotWeb => !kIsWeb; + static bool get isIOS => !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS; + static bool get isAndroid => !kIsWeb && defaultTargetPlatform == TargetPlatform.android; + static bool get isWindows => !kIsWeb && defaultTargetPlatform == TargetPlatform.windows; + static bool get isMacOS => !kIsWeb && defaultTargetPlatform == TargetPlatform.macOS; + static bool get isLinux => !kIsWeb && defaultTargetPlatform == TargetPlatform.linux; + static bool get isMobile => isIOS || isAndroid; + static bool get isDesktop => isWindows || isMacOS || isLinux; +} diff --git a/app/lib/widgets/confirmation_dialog.dart b/app/lib/widgets/confirmation_dialog.dart index d32105a8dd0..14d95532378 100644 --- a/app/lib/widgets/confirmation_dialog.dart +++ b/app/lib/widgets/confirmation_dialog.dart @@ -1,7 +1,7 @@ -import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:omi/utils/platform.dart'; class ConfirmationDialog extends StatefulWidget { final String title; @@ -53,7 +53,7 @@ class _ConfirmationDialogState extends State { @override Widget build(BuildContext context) { - if (Platform.isAndroid) { + if (PlatformUtil.isAndroid) { return AlertDialog( backgroundColor: Colors.grey.shade900, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), diff --git a/app/lib/widgets/dialog.dart b/app/lib/widgets/dialog.dart index c7453873643..2c69202e2f3 100644 --- a/app/lib/widgets/dialog.dart +++ b/app/lib/widgets/dialog.dart @@ -1,7 +1,7 @@ -import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:omi/utils/platform.dart'; //TODO: switch to required named parameters getDialog( @@ -29,7 +29,7 @@ getDialog( TextButton( onPressed: () => onConfirm(), child: Text(okButtonText, style: const TextStyle(color: Colors.white))), ]; - if (Platform.isIOS) { + if (PlatformUtil.isIOS) { return CupertinoAlertDialog(title: Text(title), content: Text(content), actions: actions); } return AlertDialog(title: Text(title), content: Text(content), actions: actions); diff --git a/app/lib/widgets/sign_in_button.dart b/app/lib/widgets/sign_in_button.dart index b6e1a862a5a..6881f103548 100644 --- a/app/lib/widgets/sign_in_button.dart +++ b/app/lib/widgets/sign_in_button.dart @@ -1,7 +1,7 @@ -import 'dart:io'; import 'package:flutter/material.dart'; import 'package:omi/gen/assets.gen.dart'; +import 'package:omi/utils/platform.dart'; class SignInButton extends StatelessWidget { final String title; @@ -17,10 +17,10 @@ class SignInButton extends StatelessWidget { assetPath: Assets.images.googleLogo.path, title: title ?? "Sign in with Google", onTap: onTap, - padding: Platform.isIOS + padding: PlatformUtil.isIOS ? const EdgeInsets.symmetric(horizontal: 16, vertical: 12) : const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - iconSpacing: Platform.isIOS ? 12 : 10, + iconSpacing: PlatformUtil.isIOS ? 12 : 10, ); } diff --git a/app/pubspec.yaml b/app/pubspec.yaml index 48b820dcd55..3976f059090 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -33,9 +33,9 @@ dependencies: shimmer: ^3.0.0 # Firebase - firebase_core: 3.3.0 - firebase_auth: 5.1.4 - firebase_messaging: 15.0.4 + firebase_core: ^3.3.0 + firebase_auth: ^5.1.4 + firebase_messaging: ^15.0.4 # Analytics and Support intercom_flutter: @@ -50,7 +50,7 @@ dependencies: pdf: ^3.11.0 intl: ^0.19.0 permission_handler: ^11.3.1 - share_plus: ^9.0.0 + share_plus: ^10.1.4 shared_preferences: ^2.2.3 url_launcher: ^6.3.0 wav: ^1.3.0 @@ -61,7 +61,7 @@ dependencies: envied: ^0.5.4+1 awesome_notifications_core: ^0.9.3 awesome_notifications: any - instabug_flutter: ^13.3.0 + instabug_flutter: ^14.1.0 nordic_dfu: ^6.1.4+hotfix mcumgr_flutter: ^0.4.2 flutter_archive: ^6.0.3 @@ -90,7 +90,7 @@ dependencies: geolocator: version: ^3.0.2 webview_flutter: ^4.8.0 - flutter_sound: ^9.10.0 + flutter_sound: ^9.28.0 just_audio: ^0.9.39 frame_sdk: ^0.0.7 web_socket_channel: ^3.0.1 @@ -102,9 +102,15 @@ dependencies: timeago: ^3.7.0 app_links: ^6.3.2 flutter_svg: ^2.0.16 - file_picker: 8.0.7 + file_picker: ^10.0.0 photo_view: ^0.15.0 + web: ^1.1.1 + idb_shim: ^2.6.5+1 + + vector_graphics: any + http_parser: ^4.1.2 + mime: ^2.0.0 dependency_overrides: http: ^1.2.1 uuid: ^4.4.0 diff --git a/app/web/index.html b/app/web/index.html index aab0145cdec..50b56998113 100644 --- a/app/web/index.html +++ b/app/web/index.html @@ -1,4 +1,6 @@ - + + + - + @@ -18,8 +23,8 @@ - + @@ -29,7 +34,7 @@ - + @@ -37,23 +42,14 @@ Omi AI - - - - - - - + @@ -138,30 +134,10 @@ } - - - - - - - + - \ No newline at end of file + + + + \ No newline at end of file