From 6c4c6b8965a6b5ac3726f92c050d24a067b03c88 Mon Sep 17 00:00:00 2001
From: Aleksandras Kostarevas <aleks076@protonmail.com>
Date: Wed, 13 Mar 2024 15:26:23 -0500
Subject: [PATCH] Request mic permission during setup, add test text field

---
 build.gradle                                  |  12 +-
 java/res/values/strings-uix.xml               |   1 +
 .../org/futo/inputmethod/latin/LatinIME.kt    |   4 +-
 .../latin/uix/settings/SettingsActivity.kt    |  15 ++-
 .../latin/uix/settings/SettingsUtils.kt       |   4 +-
 .../inputmethod/latin/uix/settings/Setup.kt   |  87 ++++++++++++-
 .../latin/uix/settings/pages/Home.kt          | 122 ++++++++++++------
 voiceinput-shared/build.gradle                |  12 +-
 8 files changed, 191 insertions(+), 66 deletions(-)

diff --git a/build.gradle b/build.gradle
index 8e500f26a9..6bd19e7c76 100644
--- a/build.gradle
+++ b/build.gradle
@@ -144,12 +144,12 @@ android {
 
 dependencies {
     implementation 'androidx.core:core-ktx:1.12.0'
-    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
-    implementation 'androidx.lifecycle:lifecycle-runtime:2.6.2'
-    implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.6.2'
-    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
+    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
+    implementation 'androidx.lifecycle:lifecycle-runtime:2.7.0'
+    implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.7.0'
+    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
     implementation 'androidx.activity:activity-compose:1.8.2'
-    implementation platform('androidx.compose:compose-bom:2022.10.00')
+    implementation platform('androidx.compose:compose-bom:2024.02.02')
     implementation 'androidx.compose.ui:ui'
     implementation 'androidx.compose.ui:ui-graphics'
     implementation 'androidx.compose.ui:ui-tooling-preview'
@@ -157,7 +157,7 @@ dependencies {
     implementation 'com.google.android.material:material:1.11.0'
     implementation 'androidx.appcompat:appcompat:1.6.1'
     implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
-    implementation 'androidx.navigation:navigation-compose:2.7.6'
+    implementation 'androidx.navigation:navigation-compose:2.7.7'
 
     implementation 'com.google.code.findbugs:jsr305:3.0.2'
 
diff --git a/java/res/values/strings-uix.xml b/java/res/values/strings-uix.xml
index 95f66c272c..bc0dfda75b 100644
--- a/java/res/values/strings-uix.xml
+++ b/java/res/values/strings-uix.xml
@@ -39,4 +39,5 @@
 
     <string name="blacklist">Blacklist</string>
     <string name="blacklist_from_suggestions">Blacklist \"%1$s\" from being suggested?</string>
+    <string name="try_typing">Try typing here…</string>
 </resources>
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/LatinIME.kt b/java/src/org/futo/inputmethod/latin/LatinIME.kt
index 2aca377cf6..4314266396 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIME.kt
+++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt
@@ -292,7 +292,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
         key(legacyInputView) {
             AndroidView(factory = {
                 legacyInputView!!
-            }, update = { }, modifier = modifier)
+            }, modifier = modifier)
         }
     }
 
@@ -305,7 +305,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
             latinIMELegacy.setComposeInputView(it)
         }
 
-        latinIMELegacy.setInputView(legacyInputView)
+        latinIMELegacy.setInputView(newView)
     }
 
     override fun setInputView(view: View?) {
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt
index 19627c2d1c..d6e7a3851e 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt
@@ -1,9 +1,11 @@
 package org.futo.inputmethod.latin.uix.settings
 
+import android.Manifest
 import android.app.Activity
 import android.content.Context
 import android.content.Context.INPUT_METHOD_SERVICE
 import android.content.Intent
+import android.content.pm.PackageManager
 import android.os.Bundle
 import android.provider.Settings
 import android.view.inputmethod.InputMethodManager
@@ -27,9 +29,12 @@ import kotlinx.coroutines.GlobalScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.THEME_KEY
+import org.futo.inputmethod.latin.uix.USE_SYSTEM_VOICE_INPUT
 import org.futo.inputmethod.latin.uix.deferGetSetting
+import org.futo.inputmethod.latin.uix.getSetting
 import org.futo.inputmethod.latin.uix.theme.StatusBarColorSetter
 import org.futo.inputmethod.latin.uix.theme.ThemeOption
 import org.futo.inputmethod.latin.uix.theme.ThemeOptions
@@ -68,10 +73,10 @@ class SettingsActivity : ComponentActivity() {
 
     private val inputMethodEnabled = mutableStateOf(false)
     private val inputMethodSelected = mutableStateOf(false)
+    private val micPermissionGrantedOrUsingSystem = mutableStateOf(false)
 
     private var wasImeEverDisabled = false
 
-
     private var fileBeingSaved: File? = null
     fun updateFileBeingSaved(to: File) {
         fileBeingSaved = to
@@ -82,12 +87,16 @@ class SettingsActivity : ComponentActivity() {
     }
 
     @OptIn(DelicateCoroutinesApi::class)
-    private fun updateSystemState() {
+    fun updateSystemState() {
         val inputMethodEnabled = isInputMethodEnabled()
         val inputMethodSelected = isDefaultIMECurrent()
         this.inputMethodEnabled.value = inputMethodEnabled
         this.inputMethodSelected.value = inputMethodSelected
 
+        this.micPermissionGrantedOrUsingSystem.value = (checkSelfPermission(Manifest.permission.RECORD_AUDIO) == PackageManager.PERMISSION_GRANTED) || runBlocking {
+            getSetting(USE_SYSTEM_VOICE_INPUT)
+        }
+
         if(!inputMethodEnabled) {
             wasImeEverDisabled = true
         } else if(wasImeEverDisabled) {
@@ -138,7 +147,7 @@ class SettingsActivity : ComponentActivity() {
                         modifier = Modifier.fillMaxSize(),
                         color = MaterialTheme.colorScheme.background
                     ) {
-                        SetupOrMain(inputMethodEnabled.value, inputMethodSelected.value) {
+                        SetupOrMain(inputMethodEnabled.value, inputMethodSelected.value, micPermissionGrantedOrUsingSystem.value) {
                             SettingsNavigator(navController = navController)
                         }
                     }
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsUtils.kt b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsUtils.kt
index d15ec490cb..6039698687 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsUtils.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsUtils.kt
@@ -9,11 +9,13 @@ import androidx.compose.runtime.Composable
 import org.futo.inputmethod.latin.utils.UncachedInputMethodManagerUtils
 
 @Composable
-fun SetupOrMain(inputMethodEnabled: Boolean, inputMethodSelected: Boolean, main: @Composable () -> Unit) {
+fun SetupOrMain(inputMethodEnabled: Boolean, inputMethodSelected: Boolean, micPermissionGrantedOrUsingSystem: Boolean, main: @Composable () -> Unit) {
     if (!inputMethodEnabled) {
         SetupEnableIME()
     } else if (!inputMethodSelected) {
         SetupChangeDefaultIME()
+    } else if (!micPermissionGrantedOrUsingSystem) {
+        SetupEnableMic()
     } else {
         main()
     }
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/Setup.kt b/java/src/org/futo/inputmethod/latin/uix/settings/Setup.kt
index 620d6476d3..ed829adeb8 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/Setup.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/Setup.kt
@@ -1,9 +1,13 @@
 package org.futo.inputmethod.latin.uix.settings
 
+import android.Manifest
 import android.content.Context
 import android.content.Intent
+import android.net.Uri
 import android.provider.Settings
 import android.view.inputmethod.InputMethodManager
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
@@ -17,6 +21,10 @@ import androidx.compose.material3.LinearProgressIndicator
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
@@ -25,6 +33,7 @@ import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import org.futo.inputmethod.latin.R
+import org.futo.inputmethod.latin.uix.USE_SYSTEM_VOICE_INPUT
 import org.futo.inputmethod.latin.uix.theme.Typography
 
 @Composable
@@ -93,10 +102,10 @@ fun SetupEnableIME() {
 
     SetupContainer {
         Column {
-            Step(fraction = 1.0f/3.0f, text = "Setup - Step 1 of 2")
+            Step(fraction = 1.0f/3.0f, text = "Setup - Step 1 of 3")
 
             Text(
-                "To use FUTO Keyboard, you must first enable FUTO Keyboard as an input method.",
+                "Welcome to FUTO Keyboard pre-alpha! Please keep in mind things may be rough. This is not a finished product in any way.\n\nFirst, enable FUTO Keyboard as an input method.",
                 textAlign = TextAlign.Center,
                 modifier = Modifier.fillMaxWidth()
             )
@@ -123,14 +132,16 @@ fun SetupChangeDefaultIME() {
             context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
 
         inputMethodManager.showInputMethodPicker()
+
+        (context as SettingsActivity).updateSystemState()
     }
 
     SetupContainer {
         Column {
-            Step(fraction = 2.0f/3.0f, text = "Setup - Step 2 of 2")
+            Step(fraction = 2.0f/3.0f, text = "Setup - Step 2 of 3")
 
             Text(
-                "Next, select FUTO Keyboard as your active input method.",
+                "Please select FUTO Keyboard as your active input method.",
                 textAlign = TextAlign.Center,
                 modifier = Modifier.fillMaxWidth()
             )
@@ -144,4 +155,70 @@ fun SetupChangeDefaultIME() {
             }
         }
     }
-}
\ No newline at end of file
+}
+
+
+@Composable
+@Preview
+fun SetupEnableMic(onClick: () -> Unit = { }) {
+    val launcher = rememberLauncherForActivityResult(
+        ActivityResultContracts.RequestPermission()
+    ) { isGranted: Boolean ->
+        if(isGranted) { onClick() }
+    }
+
+    val context = LocalContext.current
+
+    var askedCount by remember { mutableStateOf(0) }
+    val askMicAccess = {
+        if (askedCount++ >= 2) {
+            val packageName = context.packageName
+            val myAppSettings = Intent(
+                Settings.ACTION_APPLICATION_DETAILS_SETTINGS, Uri.parse(
+                    "package:$packageName"
+                )
+            )
+            myAppSettings.addCategory(Intent.CATEGORY_DEFAULT)
+            myAppSettings.flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            context.startActivity(myAppSettings)
+        } else {
+            launcher.launch(Manifest.permission.RECORD_AUDIO)
+        }
+        onClick()
+    }
+
+    val (useSystemVoiceInput, setUseSystemVoiceInput) = useDataStore(key = USE_SYSTEM_VOICE_INPUT.key, default = USE_SYSTEM_VOICE_INPUT.default)
+
+    SetupContainer {
+        Column {
+            Step(fraction = 0.9f, text = "Step 3 of 3")
+            Text(
+                "Choose whether you want to use built-in voice input, or the system voice input.",
+                textAlign = TextAlign.Center,
+                modifier = Modifier.fillMaxWidth()
+            )
+
+            Button(
+                onClick = askMicAccess,
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(16.dp)
+            ) {
+                Text("Use built-in (mic permission needed)")
+            }
+
+            Button(
+                onClick = {
+                    setUseSystemVoiceInput(true)
+                    (context as SettingsActivity).updateSystemState()
+                },
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(16.dp, 4.dp)
+            ) {
+                Text("Use system")
+            }
+        }
+    }
+}
+
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Home.kt b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Home.kt
index 8d6e881529..0e932fd371 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/pages/Home.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/pages/Home.kt
@@ -1,16 +1,26 @@
 package org.futo.inputmethod.latin.uix.settings.pages
 
+import android.widget.EditText
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalInspectionMode
 import androidx.compose.ui.res.painterResource
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
 import androidx.navigation.NavHostController
 import androidx.navigation.compose.rememberNavController
 import org.futo.inputmethod.latin.BuildConfig
@@ -18,64 +28,90 @@ import org.futo.inputmethod.latin.R
 import org.futo.inputmethod.latin.uix.settings.NavigationItem
 import org.futo.inputmethod.latin.uix.settings.NavigationItemStyle
 import org.futo.inputmethod.latin.uix.settings.ScreenTitle
-import org.futo.inputmethod.latin.uix.settings.ScrollableList
 import org.futo.inputmethod.latin.uix.settings.openLanguageSettings
 import org.futo.inputmethod.latin.uix.theme.Typography
 import org.futo.inputmethod.updates.ConditionalUpdate
 
+@Composable
+fun AndroidTextInput() {
+    val context = LocalContext.current
+    val bgColor = MaterialTheme.colorScheme.background
+    val fgColor = MaterialTheme.colorScheme.onBackground
+
+    if(!LocalInspectionMode.current) {
+        val editText = remember {
+            EditText(context).apply {
+                setHint(R.string.try_typing)
+                setBackgroundColor(bgColor.toArgb())
+                setTextColor(fgColor.toArgb())
+                setHintTextColor(fgColor.copy(alpha = 0.7f).toArgb())
+            }
+        }
+        AndroidView({ editText }, modifier = Modifier.fillMaxWidth().padding(8.dp))
+    }
+}
+
 @Preview(showBackground = true)
 @Composable
 fun HomeScreen(navController: NavHostController = rememberNavController()) {
     val context = LocalContext.current
-    ScrollableList {
-        Spacer(modifier = Modifier.height(24.dp))
-        ScreenTitle("FUTO Keyboard Settings")
+    val scrollState = rememberScrollState()
+    Column {
+        Column(
+            modifier = Modifier
+                .weight(1.0f).fillMaxWidth()
+                .verticalScroll(scrollState)
+        ) {
+            Spacer(modifier = Modifier.height(24.dp))
+            ScreenTitle("FUTO Keyboard Settings")
 
-        ConditionalUpdate(navController)
+            ConditionalUpdate(navController)
 
-        NavigationItem(
-            title = "Languages",
-            style = NavigationItemStyle.HomePrimary,
-            navigate = { context.openLanguageSettings() },
-            icon = painterResource(id = R.drawable.globe)
-        )
+            NavigationItem(
+                title = "Languages",
+                style = NavigationItemStyle.HomePrimary,
+                navigate = { context.openLanguageSettings() },
+                icon = painterResource(id = R.drawable.globe)
+            )
 
-        NavigationItem(
-            title = "Predictive Text",
-            style = NavigationItemStyle.HomeSecondary,
-            navigate = { navController.navigate("predictiveText") },
-            icon = painterResource(id = R.drawable.shift)
-        )
+            NavigationItem(
+                title = "Predictive Text",
+                style = NavigationItemStyle.HomeSecondary,
+                navigate = { navController.navigate("predictiveText") },
+                icon = painterResource(id = R.drawable.shift)
+            )
 
-        NavigationItem(
-            title = "Typing Preferences",
-            style = NavigationItemStyle.HomeSecondary,
-            navigate = { navController.navigate("typing") },
-            icon = painterResource(id = R.drawable.delete)
-        )
+            NavigationItem(
+                title = "Typing Preferences",
+                style = NavigationItemStyle.HomeSecondary,
+                navigate = { navController.navigate("typing") },
+                icon = painterResource(id = R.drawable.delete)
+            )
 
-        NavigationItem(
-            title = "Voice Input",
-            style = NavigationItemStyle.HomeSecondary,
-            navigate = { navController.navigate("voiceInput") },
-            icon = painterResource(id = R.drawable.mic_fill)
-        )
+            NavigationItem(
+                title = "Voice Input",
+                style = NavigationItemStyle.HomeSecondary,
+                navigate = { navController.navigate("voiceInput") },
+                icon = painterResource(id = R.drawable.mic_fill)
+            )
 
-        NavigationItem(
-            title = "Theme",
-            style = NavigationItemStyle.HomeTertiary,
-            navigate = { navController.navigate("themes") },
-            icon = painterResource(id = R.drawable.eye)
-        )
+            NavigationItem(
+                title = "Theme",
+                style = NavigationItemStyle.HomeTertiary,
+                navigate = { navController.navigate("themes") },
+                icon = painterResource(id = R.drawable.eye)
+            )
 
 
-        Spacer(modifier = Modifier.height(32.dp))
-        Text(
-            "v${BuildConfig.VERSION_NAME}",
-            style = Typography.labelSmall,
-            modifier = Modifier.fillMaxWidth(),
-            textAlign = TextAlign.Center
-        )
-        Spacer(modifier = Modifier.height(32.dp))
+            Spacer(modifier = Modifier.height(32.dp))
+            Text(
+                "v${BuildConfig.VERSION_NAME}",
+                style = Typography.labelSmall,
+                modifier = Modifier.fillMaxWidth(),
+                textAlign = TextAlign.Center
+            )
+            Spacer(modifier = Modifier.height(32.dp))
+        }
+        AndroidTextInput()
     }
 }
\ No newline at end of file
diff --git a/voiceinput-shared/build.gradle b/voiceinput-shared/build.gradle
index e77e870b89..ddbff311cc 100644
--- a/voiceinput-shared/build.gradle
+++ b/voiceinput-shared/build.gradle
@@ -41,12 +41,12 @@ android {
 
 dependencies {
     implementation 'androidx.core:core-ktx:1.12.0'
-    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2'
-    implementation 'androidx.lifecycle:lifecycle-runtime:2.6.2'
-    implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.6.2'
-    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
+    implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0'
+    implementation 'androidx.lifecycle:lifecycle-runtime:2.7.0'
+    implementation 'androidx.lifecycle:lifecycle-runtime-compose:2.7.0'
+    implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0'
     implementation 'androidx.activity:activity-compose:1.8.2'
-    implementation platform('androidx.compose:compose-bom:2022.10.00')
+    implementation platform('androidx.compose:compose-bom:2024.02.02')
     implementation 'androidx.compose.ui:ui'
     implementation 'androidx.compose.ui:ui-graphics'
     implementation 'androidx.compose.ui:ui-tooling-preview'
@@ -54,7 +54,7 @@ dependencies {
     implementation 'com.google.android.material:material:1.11.0'
     implementation 'androidx.appcompat:appcompat:1.6.1'
     implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
-    implementation 'androidx.navigation:navigation-compose:2.7.6'
+    implementation 'androidx.navigation:navigation-compose:2.7.7'
     implementation 'androidx.datastore:datastore-preferences:1.0.0'
 
     implementation(name:'vad-release', ext:'aar')
-- 
GitLab