From d6efead23973184805830f4418aada4d1371e14d Mon Sep 17 00:00:00 2001 From: Matias Palmroth Date: Fri, 13 Mar 2026 16:48:18 +0200 Subject: [PATCH 1/3] Add switch to Even app feature --- ios/Runner/Info.plist | 6 + lib/screens/landing_screen.dart | 145 +++++++++++++++--- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 80 +++++++++- pubspec.yaml | 3 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 9 files changed, 223 insertions(+), 22 deletions(-) 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 db042dd..1a1dc1b 100644 --- a/lib/screens/landing_screen.dart +++ b/lib/screens/landing_screen.dart @@ -2,6 +2,9 @@ import 'package:even_realities_g1/even_realities_g1.dart'; import 'package:flutter/material.dart'; import 'package:front/services/lc3_decoder.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 '../services/phone_audio_service.dart'; @@ -44,6 +47,93 @@ 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: launch the app directly via package and activity + // 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 (_) { + const storeIntent = AndroidIntent( + action: 'android.intent.action.VIEW', + data: 'market://details?id=com.even.g1', + flags: [0x10000000], + ); + + try { + await storeIntent.launch(); + } catch (_) { + const webIntent = AndroidIntent( + action: 'android.intent.action.VIEW', + data: 'https://play.google.com/store/apps/details?id=com.even.g1', + flags: [0x10000000], + ); + await webIntent.launch(); + } + } + } 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(); @@ -615,24 +705,43 @@ 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'), - ], - ), - ), - ), + ValueListenableBuilder( + valueListenable: _isRecording, + builder: (context, isRecording, _) { + return Center( + child: 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 From 5cc4aacbdc5a53a13678aeae907566b3e4235b7b Mon Sep 17 00:00:00 2001 From: Matias Palmroth Date: Tue, 17 Mar 2026 15:17:54 +0200 Subject: [PATCH 2/3] FIX: Remove Play Store fallback and launch Even app directly via AndroidIntent --- android/app/src/main/AndroidManifest.xml | 2 ++ lib/screens/landing_screen.dart | 33 ++++++++++++------------ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 72c9895..67454bf 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -50,5 +50,7 @@ + + diff --git a/lib/screens/landing_screen.dart b/lib/screens/landing_screen.dart index 1a1dc1b..a56c6e1 100644 --- a/lib/screens/landing_screen.dart +++ b/lib/screens/landing_screen.dart @@ -81,7 +81,8 @@ class _LandingScreenState extends State { } // Open the Even app - // Android: launch the app directly via package and activity + // 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) { @@ -92,24 +93,24 @@ class _LandingScreenState extends State { componentName: 'com.even.g1.MainActivity', flags: [0x10000000], ); - await intent.launch(); } catch (_) { - const storeIntent = AndroidIntent( - action: 'android.intent.action.VIEW', - data: 'market://details?id=com.even.g1', - flags: [0x10000000], - ); - - try { - await storeIntent.launch(); - } catch (_) { - const webIntent = AndroidIntent( - action: 'android.intent.action.VIEW', - data: 'https://play.google.com/store/apps/details?id=com.even.g1', - flags: [0x10000000], + 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'), + ), + ], + ), ); - await webIntent.launch(); } } } else if (Platform.isIOS) { From ed4c8c8fcc7d3e6a3ef7bd36e9007cebdbc48ed5 Mon Sep 17 00:00:00 2001 From: Matias Palmroth Date: Tue, 17 Mar 2026 21:20:11 +0200 Subject: [PATCH 3/3] FIX: merge conflicts and AndroidManifest --- android/app/src/main/AndroidManifest.xml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e57b283..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">