From 27ed84fc0859b41dc679e034bb335640e9389acd Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Tue, 10 Jun 2025 19:57:13 +0100 Subject: [PATCH 1/4] Feat: Add voice support check for UI elements This commit introduces a check for voice recognition support and updates the UI accordingly. - Added a new composable function `supportVoice()` in `MainActions.kt` to determine if the device supports speech recognition. - In `DetailScreen.kt` and `NoteApp.kt`, the `AddDetailBottomSheet2` and `MainBottomBar` composables now conditionally display voice input options based on the result of `supportVoice()`. - The `AddDetailBottomSheet2.kt` composable now accepts an `isVoiceSupport` parameter to control the visibility of the recording option. --- .../com/mshdabiola/playnotepad/ui/NoteApp.kt | 22 +++--- .../detail/AddDetailBottomSheet2.kt | 71 ++++++++++--------- .../com/mshdabiola/detail/DetailScreen.kt | 2 + .../kotlin/com/mshdabiola/ui/MainActions.kt | 11 +++ 4 files changed, 64 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt b/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt index c645020a..a2775edd 100644 --- a/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt +++ b/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt @@ -58,6 +58,7 @@ import com.mshdabiola.playnotepad.navigation.NoteNavHost import com.mshdabiola.setting.navigation.navigateToSetting import com.mshdabiola.ui.AudioDialog import com.mshdabiola.ui.ImageDialog2 +import com.mshdabiola.ui.supportVoice import kotlinx.coroutines.launch @OptIn(ExperimentalComposeUiApi::class) @@ -165,6 +166,7 @@ fun NoteApp( appState.navController.navigateToDrawing(id.first, id.second) } }, + isVoiceSupport = supportVoice(), ) } @@ -246,8 +248,10 @@ fun NoteBottomBar( onAddDrawNote: () -> Unit = {}, onAddVoiceNote: () -> Unit = {}, onAddImageNote: () -> Unit = {}, + isVoiceSupport: Boolean = false, ) { BottomAppBar( + modifier = modifier, actions = { IconButton( modifier = Modifier.testTag("main:check"), @@ -269,14 +273,16 @@ fun NoteBottomBar( ) } - IconButton( - modifier = Modifier.testTag("main:voice"), - onClick = onAddVoiceNote, - ) { - Icon( - imageVector = NoteIcon.KeyboardVoice, - contentDescription = "add note voice", - ) + if (isVoiceSupport) { + IconButton( + modifier = Modifier.testTag("main:voice"), + onClick = onAddVoiceNote, + ) { + Icon( + imageVector = NoteIcon.KeyboardVoice, + contentDescription = "add note voice", + ) + } } IconButton( diff --git a/feature/detail/src/main/kotlin/com/mshdabiola/detail/AddDetailBottomSheet2.kt b/feature/detail/src/main/kotlin/com/mshdabiola/detail/AddDetailBottomSheet2.kt index 0a32ce6a..0261b91c 100644 --- a/feature/detail/src/main/kotlin/com/mshdabiola/detail/AddDetailBottomSheet2.kt +++ b/feature/detail/src/main/kotlin/com/mshdabiola/detail/AddDetailBottomSheet2.kt @@ -36,6 +36,7 @@ fun AddBottomSheet2( onDrawing: () -> Unit = {}, onDismiss: () -> Unit = {}, show: Boolean, + isVoiceSupport: Boolean = false, ) { val background = if (currentImage != -1) { NoteIcon.background[currentImage].fgColor @@ -161,41 +162,43 @@ fun AddBottomSheet2( modifier = androidx.compose.ui.Modifier.testTag("detail:drawing"), ) - NavigationDrawerItem( - icon = { - Icon( - imageVector = NoteIcon.KeyboardVoice, - contentDescription = "", - ) - }, - label = { Text(text = stringResource(Rd.string.modules_designsystem_recording)) }, - selected = false, - onClick = { - onDismiss() - if (context.checkSelfPermission(Manifest.permission.RECORD_AUDIO) == - PackageManager.PERMISSION_GRANTED - ) { - voiceLauncher.launch( - Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { - putExtra( - RecognizerIntent.EXTRA_LANGUAGE_MODEL, - RecognizerIntent.LANGUAGE_MODEL_FREE_FORM, - ) - putExtra(RecognizerIntent.EXTRA_PROMPT, "Speck Now Now") - putExtra( - "android.speech.extra.GET_AUDIO_FORMAT", - "audio/AMR", - ) - putExtra("android.speech.extra.GET_AUDIO", true) - }, + if (isVoiceSupport) { + NavigationDrawerItem( + icon = { + Icon( + imageVector = NoteIcon.KeyboardVoice, + contentDescription = "", ) - } else { - audioPermission.launch(Manifest.permission.RECORD_AUDIO) - } - }, - colors = NavigationDrawerItemDefaults.colors(unselectedContainerColor = background), - modifier = androidx.compose.ui.Modifier.testTag("detail:recording"), - ) + }, + label = { Text(text = stringResource(Rd.string.modules_designsystem_recording)) }, + selected = false, + onClick = { + onDismiss() + if (context.checkSelfPermission(Manifest.permission.RECORD_AUDIO) == + PackageManager.PERMISSION_GRANTED + ) { + voiceLauncher.launch( + Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH).apply { + putExtra( + RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_FREE_FORM, + ) + putExtra(RecognizerIntent.EXTRA_PROMPT, "Speck Now Now") + putExtra( + "android.speech.extra.GET_AUDIO_FORMAT", + "audio/AMR", + ) + putExtra("android.speech.extra.GET_AUDIO", true) + }, + ) + } else { + audioPermission.launch(Manifest.permission.RECORD_AUDIO) + } + }, + colors = NavigationDrawerItemDefaults.colors(unselectedContainerColor = background), + modifier = androidx.compose.ui.Modifier.testTag("detail:recording"), + ) + } if (!isNoteCheck) { NavigationDrawerItem( icon = { diff --git a/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt b/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt index f590d255..38c8d559 100644 --- a/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt +++ b/feature/detail/src/main/kotlin/com/mshdabiola/detail/DetailScreen.kt @@ -100,6 +100,7 @@ import com.mshdabiola.ui.LabelCard import com.mshdabiola.ui.NotificationDialogNew import com.mshdabiola.ui.ReminderCard import com.mshdabiola.ui.TimeDialog +import com.mshdabiola.ui.supportVoice import com.mshdabiola.ui.toTime import java.io.File import com.mshdabiola.designsystem.R as Rd @@ -219,6 +220,7 @@ internal fun DetailRoute( navigateToDrawing(editViewModel.note.value.id, id) }, onDismiss = { showModalState = false }, + isVoiceSupport = supportVoice(), ) // val images = note.images.map { diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt index 76540928..e092db13 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt @@ -1,6 +1,7 @@ package com.mshdabiola.ui import android.Manifest +import android.annotation.SuppressLint import android.content.Intent import android.content.pm.PackageManager import android.net.Uri @@ -101,6 +102,16 @@ fun AudioDialog( ) } +@SuppressLint("QueryPermissionsNeeded") +@Composable +fun supportVoice(): Boolean { + val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) + val context = LocalContext.current + val pm = context.packageManager + val activities = pm.queryIntentActivities(intent, 0) + return activities.isNotEmpty() +} + @Composable fun ImageDialog2( modifier: Modifier = Modifier, From 1f4f418975a408d4fe56754d2971f4637b5c1da9 Mon Sep 17 00:00:00 2001 From: Lawal abiola <41789315+mshdabiola@users.noreply.github.com> Date: Tue, 10 Jun 2025 20:05:04 +0100 Subject: [PATCH 2/4] Update modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../main/kotlin/com/mshdabiola/ui/MainActions.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt index e092db13..0a53f3e1 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt @@ -102,14 +102,18 @@ fun AudioDialog( ) } +@SuppressLint("QueryPermissionsNeeded") +@Composable @SuppressLint("QueryPermissionsNeeded") @Composable fun supportVoice(): Boolean { - val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) - val context = LocalContext.current - val pm = context.packageManager - val activities = pm.queryIntentActivities(intent, 0) - return activities.isNotEmpty() + return remember { + val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) + val context = LocalContext.current + val pm = context.packageManager + val activities = pm.queryIntentActivities(intent, 0) + activities.isNotEmpty() + } } @Composable From c4e3bca7b336c992a8250f95abdc48a4175d6e2d Mon Sep 17 00:00:00 2001 From: Lawal abiola <41789315+mshdabiola@users.noreply.github.com> Date: Tue, 10 Jun 2025 20:05:37 +0100 Subject: [PATCH 3/4] Update app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- .../com/mshdabiola/playnotepad/ui/NoteApp.kt | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt b/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt index a2775edd..b28e703d 100644 --- a/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt +++ b/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt @@ -274,16 +274,28 @@ fun NoteBottomBar( } if (isVoiceSupport) { - IconButton( - modifier = Modifier.testTag("main:voice"), - onClick = onAddVoiceNote, - ) { - Icon( - imageVector = NoteIcon.KeyboardVoice, - contentDescription = "add note voice", - ) - } - } + IconButton( + modifier = Modifier.testTag("main:voice"), + onClick = onAddVoiceNote, + ) { + Icon( + imageVector = NoteIcon.KeyboardVoice, + contentDescription = "add note voice", + ) + } + } else { + IconButton( + modifier = Modifier.testTag("main:voice").then(Modifier.disabled()), + onClick = {}, // or show a tooltip + enabled = false + ) { + Icon( + imageVector = NoteIcon.KeyboardVoice, + contentDescription = "add note voice (unavailable)", + tint = Color.Gray // or other visual cue + ) + } + } IconButton( modifier = Modifier.testTag("main:image"), From 9ed9cb1a22bf40fde6476099242261c970d8db8b Mon Sep 17 00:00:00 2001 From: mshdabiola Date: Tue, 10 Jun 2025 20:09:51 +0100 Subject: [PATCH 4/4] Refactor: Optimize `supportVoice` and code formatting This commit introduces an optimization in the `supportVoice` composable function and applies code formatting changes. - In `MainActions.kt`, the `LocalContext.current` is now fetched outside the `remember` block within `supportVoice()`. This prevents redundant lookups of the context on recomposition. - In `NoteApp.kt`, the code for navigating to the drawing screen after inserting a new drawing and the code for the voice note IconButton has been reformatted for better readability. No functional changes were made in `NoteApp.kt`. --- .../com/mshdabiola/playnotepad/ui/NoteApp.kt | 49 ++++++++++--------- .../kotlin/com/mshdabiola/ui/MainActions.kt | 6 +-- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt b/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt index b28e703d..2f4b8d64 100644 --- a/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt +++ b/app/src/main/java/com/mshdabiola/playnotepad/ui/NoteApp.kt @@ -163,7 +163,10 @@ fun NoteApp( appState.coroutineScope.launch { val id = viewModel.insertNewDrawing() appState.navController.navigateToDetail(DetailArg(id.first)) - appState.navController.navigateToDrawing(id.first, id.second) + appState.navController.navigateToDrawing( + id.first, + id.second, + ) } }, isVoiceSupport = supportVoice(), @@ -274,28 +277,28 @@ fun NoteBottomBar( } if (isVoiceSupport) { - IconButton( - modifier = Modifier.testTag("main:voice"), - onClick = onAddVoiceNote, - ) { - Icon( - imageVector = NoteIcon.KeyboardVoice, - contentDescription = "add note voice", - ) - } - } else { - IconButton( - modifier = Modifier.testTag("main:voice").then(Modifier.disabled()), - onClick = {}, // or show a tooltip - enabled = false - ) { - Icon( - imageVector = NoteIcon.KeyboardVoice, - contentDescription = "add note voice (unavailable)", - tint = Color.Gray // or other visual cue - ) - } - } + IconButton( + modifier = Modifier.testTag("main:voice"), + onClick = onAddVoiceNote, + ) { + Icon( + imageVector = NoteIcon.KeyboardVoice, + contentDescription = "add note voice", + ) + } + } else { + IconButton( + modifier = Modifier.testTag("main:voice"), + onClick = {}, // or show a tooltip + enabled = false, + ) { + Icon( + imageVector = NoteIcon.KeyboardVoice, + contentDescription = "add note voice (unavailable)", + tint = Color.Gray, // or other visual cue + ) + } + } IconButton( modifier = Modifier.testTag("main:image"), diff --git a/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt b/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt index 0a53f3e1..474f016a 100644 --- a/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt +++ b/modules/ui/src/main/kotlin/com/mshdabiola/ui/MainActions.kt @@ -25,6 +25,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext @@ -102,14 +103,13 @@ fun AudioDialog( ) } -@SuppressLint("QueryPermissionsNeeded") -@Composable @SuppressLint("QueryPermissionsNeeded") @Composable fun supportVoice(): Boolean { + val context = LocalContext.current + return remember { val intent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH) - val context = LocalContext.current val pm = context.packageManager val activities = pm.queryIntentActivities(intent, 0) activities.isNotEmpty()