diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 00ef991..e72c19d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -12,7 +12,8 @@ + android:icon="@mipmap/ic_launcher" + android:usesCleartextTraffic="true"> + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 2885430..a9948d4 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -4,6 +4,12 @@ CADisableMinimumFrameDurationOnPhone + LSApplicationQueriesSchemes + + com.even.g1 + eveng1 + even-g1 + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName diff --git a/lib/screens/landing_screen.dart b/lib/screens/landing_screen.dart index 06e326b..22ad44d 100644 --- a/lib/screens/landing_screen.dart +++ b/lib/screens/landing_screen.dart @@ -3,14 +3,17 @@ import 'dart:async'; import 'package:even_realities_g1/even_realities_g1.dart'; import 'package:flutter/material.dart'; import 'package:front/services/audio_pipeline.dart'; +import 'package:android_intent_plus/android_intent.dart'; +import 'package:url_launcher/url_launcher.dart'; +import 'dart:io'; +import '../widgets/g1_connection.dart'; +import '../services/websocket_service.dart'; import 'package:front/services/lc3_decoder.dart'; import '../models/api_models.dart'; import '../services/calendar_service.dart'; import '../services/phone_audio_service.dart'; import '../services/rest_api_service.dart'; -import '../services/websocket_service.dart'; -import '../widgets/g1_connection.dart'; import '../widgets/side_panel.dart'; import 'login_screen.dart'; import 'register_screen.dart'; @@ -60,6 +63,94 @@ class _LandingScreenState extends State { final List _displayedSentences = []; static const int _maxDisplayedSentences = 4; + // Show confirmation dialog before switching to Even app + Future _switchToEvenApp() async { + if (_isRecording.value) { + return; + } + final confirmed = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text("Switch app"), + content: const Text( + "Glasses will disconnect and the Even app will open. Continue?", + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text("Cancel"), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: const Text("Open Even"), + ), + ], + ); + }, + ); + if (confirmed != true) return; + if (_manager.isConnected) { + await _manager.disconnect(); + } + await _openEvenApp(); + } + + // Open the Even app + // Android: check if app is installed via intent URI before launching, + // show error dialog if not found (no Play Store fallback) + // iOS: attempt possible deep link schemes, fallback to App Store + Future _openEvenApp() async { + if (Platform.isAndroid) { + try { + const intent = AndroidIntent( + action: 'android.intent.action.MAIN', + package: 'com.even.g1', + componentName: 'com.even.g1.MainActivity', + flags: [0x10000000], + ); + await intent.launch(); + } catch (_) { + if (mounted) { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Even app not found'), + content: const Text( + 'The Even G1 app is not installed on this device.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), + ), + ], + ), + ); + } + } + } else if (Platform.isIOS) { + const schemes = [ + 'com.even.g1://', + 'eveng1://', + 'even-g1://', + ]; + + for (final scheme in schemes) { + final uri = Uri.parse(scheme); + + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + return; + } + } + + // jos appia ei voitu avata mennään App Storeen + final store = Uri.parse('https://apps.apple.com/app/id6499140518'); + await launchUrl(store, mode: LaunchMode.externalApplication); + } + } + @override void initState() { super.initState(); @@ -651,23 +742,44 @@ class _LandingScreenState extends State { ), const SizedBox(height: 8), Center( - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 14, - vertical: 5, - ), - decoration: BoxDecoration( - border: Border.all(color: Colors.black12), - borderRadius: BorderRadius.circular(8), - ), - child: const Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon(Icons.battery_full, size: 18), - SizedBox(width: 8), - Text('G1 smart glasses'), - ], - ), + child: ValueListenableBuilder( + valueListenable: _isRecording, + builder: (context, isRecording, _) { + return GestureDetector( + onTap: + isRecording ? null : _switchToEvenApp, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 14, + vertical: 5, + ), + decoration: BoxDecoration( + border: Border.all( + color: isRecording + ? Colors.grey + : Colors.black12, + ), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.battery_full, + size: 18), + const SizedBox(width: 8), + Text( + 'G1 smart glasses', + style: TextStyle( + color: isRecording + ? Colors.grey + : Colors.black, + ), + ), + ], + ), + ), + ); + }, ), ), ], diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 938839d..9c92be3 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_sound_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSoundPlugin"); flutter_sound_plugin_register_with_registrar(flutter_sound_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 86609f6..8f31061 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_sound + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 8e1adb5..71a3ff5 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -7,8 +7,10 @@ import Foundation import flutter_blue_plus_darwin import flutter_sound +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterBluePlusPlugin.register(with: registry.registrar(forPlugin: "FlutterBluePlusPlugin")) FlutterSoundPlugin.register(with: registry.registrar(forPlugin: "FlutterSoundPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 9a7d202..dfd7e5c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + android_intent_plus: + dependency: "direct main" + description: + name: android_intent_plus + sha256: e1c62bb41c90e15083b7fb84dc327fe90396cc9c1445b55ff1082144fabfb4d9 + url: "https://pub.dev" + source: hosted + version: "4.0.3" args: dependency: transitive description: @@ -315,10 +323,10 @@ packages: dependency: transitive description: name: matcher - sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" url: "https://pub.dev" source: hosted - version: "0.12.19" + version: "0.12.18" material_color_utilities: dependency: transitive description: @@ -560,10 +568,10 @@ packages: dependency: transitive description: name: test_api - sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a" + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" url: "https://pub.dev" source: hosted - version: "0.7.10" + version: "0.7.9" timezone: dependency: transitive description: @@ -580,6 +588,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.dev" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" + url: "https://pub.dev" + source: hosted + version: "6.4.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + url: "https://pub.dev" + source: hosted + version: "2.4.2" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b2a8782..6b14a9e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -32,6 +32,9 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 + url_launcher: ^6.3.1 + android_intent_plus: ^4.0.0 + # Local path dependency device_calendar: ^4.3.3 even_realities_g1: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index e4eb53a..671da66 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,10 +8,13 @@ #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FlutterSoundPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSoundPluginCApi")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 183a45b..849e876 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_sound permission_handler_windows + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST