diff --git a/app/src/main/kotlin/com/google/ai/sample/MainActivity.kt b/app/src/main/kotlin/com/google/ai/sample/MainActivity.kt index 623cfad8..d2a6fa4a 100644 --- a/app/src/main/kotlin/com/google/ai/sample/MainActivity.kt +++ b/app/src/main/kotlin/com/google/ai/sample/MainActivity.kt @@ -11,9 +11,12 @@ import android.content.SharedPreferences import android.content.pm.PackageManager import android.net.Uri import android.os.Build +import android.graphics.Rect import android.os.Bundle import android.provider.Settings import android.util.Log +import android.view.View +import android.view.ViewTreeObserver import android.widget.Toast import androidx.activity.ComponentActivity import androidx.activity.compose.setContent @@ -70,6 +73,11 @@ import kotlinx.coroutines.launch class MainActivity : ComponentActivity() { + // Keyboard Visibility + private val _isKeyboardOpen = MutableStateFlow(false) + val isKeyboardOpen: StateFlow = _isKeyboardOpen.asStateFlow() + private var onGlobalLayoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null + private var photoReasoningViewModel: PhotoReasoningViewModel? = null private lateinit var apiKeyManager: ApiKeyManager private var showApiKeyDialog by mutableStateOf(false) @@ -286,6 +294,26 @@ class MainActivity : ComponentActivity() { // Initial check for accessibility service status refreshAccessibilityServiceStatus() + // Keyboard visibility listener + val rootView = findViewById(android.R.id.content) + onGlobalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener { + val rect = Rect() + rootView.getWindowVisibleDisplayFrame(rect) + val screenHeight = rootView.rootView.height + val keypadHeight = screenHeight - rect.bottom + if (keypadHeight > screenHeight * 0.15) { // 0.15 ratio is a common threshold + if (!_isKeyboardOpen.value) { + _isKeyboardOpen.value = true + Log.d(TAG, "Keyboard visible") + } + } else { + if (_isKeyboardOpen.value) { + _isKeyboardOpen.value = false + Log.d(TAG, "Keyboard hidden") + } + } + } + rootView.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener) Log.d(TAG, "onCreate: Calling setContent.") setContent { @@ -752,6 +780,11 @@ class MainActivity : ComponentActivity() { billingClient.endConnection() Log.d(TAG, "onDestroy: BillingClient connection ended.") } + // Remove keyboard listener + onGlobalLayoutListener?.let { + findViewById(android.R.id.content).viewTreeObserver.removeOnGlobalLayoutListener(it) + Log.d(TAG, "onDestroy: Keyboard layout listener removed.") + } if (this == instance) { instance = null Log.d(TAG, "onDestroy: MainActivity instance cleared.") diff --git a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningScreen.kt b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningScreen.kt index a7a13177..63365e78 100644 --- a/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningScreen.kt +++ b/app/src/main/kotlin/com/google/ai/sample/feature/multimodal/PhotoReasoningScreen.kt @@ -6,6 +6,7 @@ import android.graphics.drawable.BitmapDrawable import android.net.Uri import android.provider.Settings import android.widget.Toast // Added for Toast message +import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts @@ -49,9 +50,11 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -88,6 +91,7 @@ internal fun PhotoReasoningRoute( // Observe the accessibility service status from MainActivity val isAccessibilityServiceEffectivelyEnabled by mainActivity?.isAccessibilityServiceEnabledFlow?.collectAsState() ?: mutableStateOf(false) + val isKeyboardOpen by mainActivity?.isKeyboardOpen?.collectAsState() ?: mutableStateOf(false) // Launcher for opening accessibility settings val accessibilitySettingsLauncher = rememberLauncherForActivityResult( @@ -168,7 +172,8 @@ internal fun PhotoReasoningRoute( val vm = it.getPhotoReasoningViewModel() vm?.clearChatHistory(context) } - } + }, + isKeyboardOpen = isKeyboardOpen ) } @@ -183,12 +188,20 @@ fun PhotoReasoningScreen( onReasonClicked: (String, List) -> Unit = { _, _ -> }, isAccessibilityServiceEnabled: Boolean = false, onEnableAccessibilityService: () -> Unit = {}, - onClearChatHistory: () -> Unit = {} + onClearChatHistory: () -> Unit = {}, + isKeyboardOpen: Boolean ) { var userQuestion by rememberSaveable { mutableStateOf("") } val imageUris = rememberSaveable(saver = UriSaver()) { mutableStateListOf() } + var isSystemMessageFocused by rememberSaveable { mutableStateOf(false) } val listState = rememberLazyListState() val context = LocalContext.current // Get context for Toast + val focusManager = LocalFocusManager.current + + BackHandler(enabled = isSystemMessageFocused && !isKeyboardOpen) { + focusManager.clearFocus() // Clear focus first + isSystemMessageFocused = false // Then update the state that controls height + } val pickMedia = rememberLauncherForActivityResult( ActivityResultContracts.PickVisualMedia() @@ -223,15 +236,23 @@ fun PhotoReasoningScreen( color = MaterialTheme.colorScheme.onPrimaryContainer ) Spacer(modifier = Modifier.height(8.dp)) + val systemMessageHeight = when { + isSystemMessageFocused && isKeyboardOpen -> 450.dp // Changed from 600.dp + isSystemMessageFocused && !isKeyboardOpen -> 1000.dp + else -> 120.dp + } + val currentMinLines = if (systemMessageHeight == 120.dp) 3 else 1 + val currentMaxLines = if (systemMessageHeight == 120.dp) 5 else Int.MAX_VALUE OutlinedTextField( value = systemMessage, onValueChange = onSystemMessageChanged, placeholder = { Text("Enter a system message here that will be sent with every request") }, modifier = Modifier .fillMaxWidth() - .height(120.dp), - maxLines = 5, - minLines = 3 + .height(systemMessageHeight) + .onFocusChanged { focusState -> isSystemMessageFocused = focusState.isFocused }, + minLines = currentMinLines, + maxLines = currentMaxLines ) } } @@ -633,13 +654,14 @@ fun PhotoReasoningScreenPreviewWithContent() { text = "I am here to help you. What do you want to know?", participant = PhotoParticipant.MODEL ) - ) + ), + isKeyboardOpen = false ) } @Composable @Preview(showSystemUi = true) fun PhotoReasoningScreenPreviewEmpty() { - PhotoReasoningScreen() + PhotoReasoningScreen(isKeyboardOpen = false) }