diff --git a/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt b/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt index ffd7c9c..178c66c 100644 --- a/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt +++ b/android/app/src/main/kotlin/app/cogwheel/conduit/ConduitVoiceInteractionSession.kt @@ -34,11 +34,10 @@ class ConduitVoiceInteractionSession(context: Context) : VoiceInteractionSession launchApp() } - // Voice button + // Voice button - opens voice call directly val voiceButton = view.findViewById(app.cogwheel.conduit.R.id.btn_voice) voiceButton?.setOnClickListener { - // TODO: Implement voice input functionality - launchAppWithContext(includeScreenshot = false) + launchAppForVoiceCall() } return view @@ -158,13 +157,32 @@ class ConduitVoiceInteractionSession(context: Context) : VoiceInteractionSession } } + private fun launchAppForVoiceCall() { + try { + android.util.Log.d("ConduitVoiceSession", "Attempting to launch app for voice call") + val intent = Intent(context, MainActivity::class.java) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + + intent.putExtra("start_voice_call", true) + android.util.Log.d("ConduitVoiceSession", "Voice call flag attached") + + context.startActivity(intent) + android.util.Log.d("ConduitVoiceSession", "App launch requested for voice call") + finish() // Close the overlay + } catch (e: Exception) { + android.util.Log.e("ConduitVoiceSession", "Failed to launch app for voice call", e) + } + } + private fun traverseNode(node: AssistStructure.ViewNode?, builder: StringBuilder) { if (node == null) return if (node.text != null) { builder.append(node.text).append("\n") } - + // Also check content description for accessibility text if (node.contentDescription != null) { builder.append(node.contentDescription).append("\n") diff --git a/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt b/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt index 04fa508..e2cb63f 100644 --- a/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt +++ b/android/app/src/main/kotlin/app/cogwheel/conduit/MainActivity.kt @@ -50,12 +50,17 @@ class MainActivity : FlutterActivity() { val screenContext = intent.getStringExtra("screen_context") val screenshotPath = intent.getStringExtra("screenshot_path") + val startVoiceCall = intent.getBooleanExtra("start_voice_call", false) android.util.Log.d("MainActivity", "screenContext: $screenContext") android.util.Log.d("MainActivity", "screenshotPath: $screenshotPath") + android.util.Log.d("MainActivity", "startVoiceCall: $startVoiceCall") android.util.Log.d("MainActivity", "methodChannel: $methodChannel") - if (screenContext != null) { + if (startVoiceCall) { + android.util.Log.d("MainActivity", "Invoking startVoiceCall") + methodChannel?.invokeMethod("startVoiceCall", null) + } else if (screenContext != null) { android.util.Log.d("MainActivity", "Invoking analyzeScreen") methodChannel?.invokeMethod("analyzeScreen", screenContext) } else if (screenshotPath != null) { diff --git a/lib/core/utils/android_assistant_handler.dart b/lib/core/utils/android_assistant_handler.dart index 1e079cb..4b03461 100644 --- a/lib/core/utils/android_assistant_handler.dart +++ b/lib/core/utils/android_assistant_handler.dart @@ -1,9 +1,11 @@ import 'dart:io'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path/path.dart' as path; import '../../features/chat/providers/chat_providers.dart'; import '../../features/chat/services/file_attachment_service.dart'; +import '../../features/chat/views/voice_call_page.dart'; import '../services/navigation_service.dart'; import '../../shared/services/tasks/task_queue.dart'; import '../providers/app_providers.dart'; @@ -42,6 +44,8 @@ class AndroidAssistantHandler { } else if (call.method == 'analyzeScreenshot') { final String screenshotPath = call.arguments as String; await _processScreenshot(screenshotPath); + } else if (call.method == 'startVoiceCall') { + await _startVoiceCall(); } } @@ -102,4 +106,45 @@ class AndroidAssistantHandler { DebugLogger.log('Failed to process screenshot: $e', scope: 'assistant'); } } + + Future _startVoiceCall() async { + try { + DebugLogger.log('Starting voice call from assistant', scope: 'assistant'); + + // Wait for app to be ready (authenticated and model available) + final navState = _ref.read(authNavigationStateProvider); + final model = _ref.read(selectedModelProvider); + + if (navState != AuthNavigationState.authenticated || model == null) { + DebugLogger.log('App not ready for voice call', scope: 'assistant'); + return; + } + + // Navigate to chat if not already there + final isOnChatRoute = NavigationService.currentRoute == Routes.chat; + if (!isOnChatRoute) { + // Navigation will happen via auth state + return; + } + + // Get the current BuildContext from the navigation service + final context = NavigationService.navigatorKey.currentContext; + if (context == null) { + DebugLogger.log('No context available for voice call navigation', scope: 'assistant'); + return; + } + + // Navigate to voice call page + await Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const VoiceCallPage(), + fullscreenDialog: true, + ), + ); + + DebugLogger.log('Voice call page launched', scope: 'assistant'); + } catch (e) { + DebugLogger.log('Failed to start voice call: $e', scope: 'assistant'); + } + } }