diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 51ba7f76d..d3f0778d0 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -37,6 +37,7 @@
+
Model import failed
Failed to import the selected model
Dismiss
+ Update
\ 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 06e7aa030..faf1cff99 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIME.kt
+++ b/java/src/org/futo/inputmethod/latin/LatinIME.kt
@@ -38,6 +38,7 @@ import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.futo.inputmethod.latin.uix.BasicThemeProvider
import org.futo.inputmethod.latin.uix.DynamicThemeProvider
@@ -213,6 +214,7 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
languageModelFacilitator.loadHistoryLog()
scheduleUpdateCheckingJob(this)
+ lifecycleScope.launch { uixManager.showUpdateNoticeIfNeeded() }
}
override fun onDestroy() {
diff --git a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt
index c728b755b..54cf426d5 100644
--- a/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/ActionBar.kt
@@ -1,5 +1,6 @@
package org.futo.inputmethod.latin.uix
+import android.content.Context
import android.os.Build
import android.view.View
import androidx.annotation.RequiresApi
@@ -105,6 +106,12 @@ import kotlin.math.roundToInt
* TODO: Will need to make RTL languages work
*/
+interface ImportantNotice {
+ @Composable fun getText(): String
+ fun onDismiss(context: Context)
+ fun onOpen(context: Context)
+}
+
val suggestionStylePrimary = TextStyle(
fontFamily = FontFamily.SansSerif,
@@ -386,6 +393,45 @@ fun ExpandActionsButton(isActionsOpen: Boolean, onClick: () -> Unit) {
}
}
+@Composable
+fun ImportantNoticeView(
+ importantNotice: ImportantNotice
+) {
+ val context = LocalContext.current
+
+ Row {
+ TextButton(
+ onClick = { importantNotice.onOpen(context) },
+ modifier = Modifier
+ .weight(1.0f)
+ .fillMaxHeight(),
+ shape = RectangleShape,
+ colors = ButtonDefaults.textButtonColors(contentColor = MaterialTheme.colorScheme.onBackground),
+ enabled = true
+ ) {
+ AutoFitText(importantNotice.getText(), style = suggestionStylePrimary.copy(color = MaterialTheme.colorScheme.onBackground))
+ }
+
+ val color = MaterialTheme.colorScheme.primary
+ IconButton(
+ onClick = { importantNotice.onDismiss(context) },
+ modifier = Modifier
+ .width(42.dp)
+ .fillMaxHeight()
+ .drawBehind {
+ drawCircle(color = color, radius = size.width / 3.0f + 1.0f)
+ },
+
+ colors = IconButtonDefaults.iconButtonColors(contentColor = MaterialTheme.colorScheme.onPrimary)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.close),
+ contentDescription = "Close"
+ )
+ }
+ }
+}
+
@Composable
fun ActionBar(
words: SuggestedWords?,
@@ -393,7 +439,9 @@ fun ActionBar(
onActionActivated: (Action) -> Unit,
inlineSuggestions: List>,
forceOpenActionsInitially: Boolean = false,
+ importantNotice: ImportantNotice? = null
) {
+ val context = LocalContext.current
val isActionsOpen = remember { mutableStateOf(forceOpenActionsInitially) }
Surface(modifier = Modifier
@@ -401,28 +449,38 @@ fun ActionBar(
.height(40.dp), color = MaterialTheme.colorScheme.background)
{
Row {
- ExpandActionsButton(isActionsOpen.value) { isActionsOpen.value = !isActionsOpen.value }
-
- if(isActionsOpen.value) {
- LazyRow {
- item {
- ActionItems(onActionActivated)
- }
+ ExpandActionsButton(isActionsOpen.value) {
+ isActionsOpen.value = !isActionsOpen.value
+ if(isActionsOpen.value && importantNotice != null) {
+ importantNotice.onDismiss(context)
}
- } else if(inlineSuggestions.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- InlineSuggestions(inlineSuggestions)
- } else if(words != null) {
- SuggestionItems(words) {
- suggestionStripListener.pickSuggestionManually(
- words.getInfo(it)
- )
- }
- } else {
- Spacer(modifier = Modifier.weight(1.0f))
}
- if(!isActionsOpen.value) {
- ActionItemSmall(VoiceInputAction, onActionActivated)
+ if(importantNotice != null && !isActionsOpen.value) {
+ ImportantNoticeView(importantNotice)
+ }else {
+
+ if (isActionsOpen.value) {
+ LazyRow {
+ item {
+ ActionItems(onActionActivated)
+ }
+ }
+ } else if (inlineSuggestions.isNotEmpty() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ InlineSuggestions(inlineSuggestions)
+ } else if (words != null) {
+ SuggestionItems(words) {
+ suggestionStripListener.pickSuggestionManually(
+ words.getInfo(it)
+ )
+ }
+ } else {
+ Spacer(modifier = Modifier.weight(1.0f))
+ }
+
+ if (!isActionsOpen.value) {
+ ActionItemSmall(VoiceInputAction, onActionActivated)
+ }
}
}
}
@@ -589,6 +647,34 @@ fun PreviewActionBarWithSuggestions(colorScheme: ColorScheme = DarkColorScheme)
}
}
+@Composable
+@Preview
+fun PreviewActionBarWithNotice(colorScheme: ColorScheme = DarkColorScheme) {
+ UixThemeWrapper(colorScheme) {
+ ActionBar(
+ words = exampleSuggestedWords,
+ suggestionStripListener = ExampleListener(),
+ onActionActivated = { },
+ inlineSuggestions = listOf(),
+ importantNotice = object : ImportantNotice {
+ @Composable
+ override fun getText(): String {
+ return "Update available: v1.2.3"
+ }
+
+ override fun onDismiss(context: Context) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onOpen(context: Context) {
+ TODO("Not yet implemented")
+ }
+
+ }
+ )
+ }
+}
+
@Composable
@Preview
fun PreviewActionBarWithEmptySuggestions(colorScheme: ColorScheme = DarkColorScheme) {
diff --git a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
index 96e9a63a6..2a67d4e62 100644
--- a/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/UixManager.kt
@@ -1,6 +1,8 @@
package org.futo.inputmethod.latin.uix
+import android.app.Activity
import android.content.Context
+import android.content.Intent
import android.os.Build
import android.view.View
import android.view.inputmethod.InlineSuggestionsResponse
@@ -13,6 +15,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.ComposeView
@@ -27,8 +30,12 @@ import org.futo.inputmethod.latin.common.Constants
import org.futo.inputmethod.latin.inputlogic.InputLogic
import org.futo.inputmethod.latin.suggestions.SuggestionStripView
import org.futo.inputmethod.latin.uix.actions.EmojiAction
+import org.futo.inputmethod.latin.uix.settings.SettingsActivity
import org.futo.inputmethod.latin.uix.theme.ThemeOption
import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
+import org.futo.inputmethod.latin.uix.voiceinput.downloader.DownloadActivity
+import org.futo.inputmethod.updates.checkForUpdateAndSaveToPreferences
+import org.futo.inputmethod.updates.retrieveSavedLastUpdateCheckResult
private class LatinIMEActionInputTransaction(
private val inputLogic: InputLogic,
@@ -141,6 +148,7 @@ class UixManager(private val latinIME: LatinIME) {
private val keyboardManagerForAction = UixActionKeyboardManager(this, latinIME)
private var mainKeyboardHidden = false
+ private var currentNotice: MutableState = mutableStateOf(null)
var currWindowActionWindow: ActionWindow? = null
@@ -172,7 +180,8 @@ class UixManager(private val latinIME: LatinIME) {
suggestedWordsOrNull,
latinIME.latinIMELegacy as SuggestionStripView.Listener,
inlineSuggestions = inlineSuggestions,
- onActionActivated = { onActionActivated(it) }
+ onActionActivated = { onActionActivated(it) },
+ importantNotice = currentNotice.value
)
}
}
@@ -286,6 +295,35 @@ class UixManager(private val latinIME: LatinIME) {
}
}
+ suspend fun showUpdateNoticeIfNeeded() {
+ val updateInfo = retrieveSavedLastUpdateCheckResult(latinIME)
+ if(updateInfo != null && updateInfo.isNewer()) {
+ currentNotice.value = object : ImportantNotice {
+ @Composable
+ override fun getText(): String {
+ return "Update available: ${updateInfo.nextVersionString}"
+ }
+
+ override fun onDismiss(context: Context) {
+ currentNotice.value = null
+ }
+
+ override fun onOpen(context: Context) {
+ currentNotice.value = null
+
+ val intent = Intent(context, SettingsActivity::class.java)
+ intent.putExtra("navDest", "update")
+
+ if(context !is Activity) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ context.startActivity(intent)
+ }
+ }
+ }
+ }
+
fun createComposeView(): View {
if(composeView != null) {
composeView = null
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 790cdf7b1..19627c2d1 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsActivity.kt
@@ -167,6 +167,17 @@ class SettingsActivity : ComponentActivity() {
this.themeOption.value = themeOption
}
+
+ val intent = intent
+ if(intent != null) {
+ val destination = intent.getStringExtra("navDest")
+ if(destination != null) {
+ lifecycleScope.launch {
+ delay(1000L)
+ navController.navigate(destination)
+ }
+ }
+ }
}
override fun onResume() {
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsNavigator.kt b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsNavigator.kt
index 1a06c7967..6dcc2b80d 100644
--- a/java/src/org/futo/inputmethod/latin/uix/settings/SettingsNavigator.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/SettingsNavigator.kt
@@ -57,6 +57,9 @@ fun SettingsNavigator(
navController
)
}
+ dialog("update") {
+ UpdateDialog(navController = navController)
+ }
addModelManagerNavigation(navController)
}
}
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/uix/settings/UpdateScreen.kt b/java/src/org/futo/inputmethod/latin/uix/settings/UpdateScreen.kt
new file mode 100644
index 000000000..8d7a8e1da
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/settings/UpdateScreen.kt
@@ -0,0 +1,215 @@
+package org.futo.inputmethod.latin.uix.settings
+
+import android.app.PendingIntent.FLAG_MUTABLE
+import android.app.PendingIntent.FLAG_UPDATE_CURRENT
+import android.app.PendingIntent.getBroadcast
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageInstaller
+import android.util.Log
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Info
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.stringResource
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.NavHostController
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.DelicateCoroutinesApi
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withContext
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import org.futo.inputmethod.latin.R
+import org.futo.inputmethod.latin.uix.InfoDialog
+import org.futo.inputmethod.latin.uix.getSetting
+import org.futo.inputmethod.updates.InstallReceiver
+import org.futo.inputmethod.updates.LAST_UPDATE_CHECK_RESULT
+import org.futo.inputmethod.updates.UpdateResult
+import java.io.InputStream
+import java.io.OutputStream
+
+
+private fun InputStream.copyToOutputStream(inputStreamLength: Long, outputStream: OutputStream, onProgress: (Float) -> Unit) {
+ val buffer = ByteArray(16384);
+ var n: Int;
+ var total = 0;
+ val inputStreamLengthFloat = inputStreamLength.toFloat();
+
+ while (read(buffer).also { n = it } >= 0) {
+ total += n;
+ outputStream.write(buffer, 0, n);
+ onProgress.invoke(total.toFloat() / inputStreamLengthFloat);
+ }
+}
+
+
+private suspend fun install(scope: CoroutineScope, context: Context, inputStream: InputStream, dataLength: Long, updateStatusText: (String) -> Unit) {
+ var lastProgressText = "";
+ var session: PackageInstaller.Session? = null;
+
+ try {
+ //Log.i("UpdateScreen", "Hooked InstallReceiver.onReceiveResult.")
+ InstallReceiver.onReceiveResult.onEach { message -> updateStatusText("Fatal error: $message") }.launchIn(scope)
+
+ val packageInstaller: PackageInstaller = context.packageManager.packageInstaller;
+ val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ val sessionId = packageInstaller.createSession(params);
+ session = packageInstaller.openSession(sessionId)
+
+ session.openWrite("package", 0, dataLength).use { sessionStream ->
+ inputStream.copyToOutputStream(dataLength, sessionStream) { progress ->
+ val progressText = "${(progress * 100.0f).toInt()}%";
+ if (lastProgressText != progressText) {
+ lastProgressText = progressText;
+
+ //TODO: Use proper scope
+ //GlobalScope.launch(Dispatchers.Main) {
+ //_textProgress.text = progressText;
+ //};
+ }
+ }
+
+ session.fsync(sessionStream);
+ };
+
+ val intent = Intent(context, InstallReceiver::class.java);
+ val pendingIntent = getBroadcast(context, 0, intent, FLAG_MUTABLE or FLAG_UPDATE_CURRENT);
+ val statusReceiver = pendingIntent.intentSender;
+
+ session.commit(statusReceiver);
+ session.close();
+
+ withContext(Dispatchers.Main) {
+ updateStatusText("Installing update")
+ //_textProgress.text = "";
+ //_text.text = context.resources.getText(R.string.installing_update);
+ }
+ } catch (e: Throwable) {
+ Log.w("UpdateScreen", "Exception thrown while downloading and installing latest version of app.", e);
+ session?.abandon();
+ withContext(Dispatchers.Main) {
+ updateStatusText("Failed to install update")
+ }
+ } finally {
+ withContext(Dispatchers.Main) {
+ Log.i("UpdateScreen", "Keep screen on unset install")
+ //window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+}
+
+@DelicateCoroutinesApi
+private suspend fun downloadAndInstall(scope: CoroutineScope, context: Context, updateResult: UpdateResult, updateStatusText: (String) -> Unit) = GlobalScope.launch(Dispatchers.IO) {
+ var inputStream: InputStream? = null;
+ try {
+ val httpClient = OkHttpClient()
+ val request = Request.Builder().method("GET", null).url(updateResult.apkUrl).build()
+
+ val response = httpClient.newCall(request).execute()
+ val body = response.body
+ if (response.isSuccessful && body != null) {
+ inputStream = body.byteStream();
+ val dataLength = body.contentLength();
+ install(scope, context, inputStream, dataLength, updateStatusText)
+ } else {
+ throw Exception("Failed to download latest version of app.");
+ }
+ } catch (e: Throwable) {
+ Log.w("UpdateScreen", "Exception thrown while downloading and installing latest version of app.", e);
+ withContext(Dispatchers.Main) {
+ updateStatusText("Failed to download update: ${e.message}");
+ }
+ } finally {
+ inputStream?.close();
+ }
+}
+
+@Composable
+fun UpdateDialog(navController: NavHostController) {
+ val scope = LocalLifecycleOwner.current
+ val context = LocalContext.current
+ val updateInfo = remember { runBlocking {
+ context.getSetting(LAST_UPDATE_CHECK_RESULT, "")
+ } }
+
+ val lastUpdateResult = if(!LocalInspectionMode.current){
+ remember { UpdateResult.fromString(updateInfo) }
+ } else {
+ UpdateResult(123, "abc", "1.2.3")
+ }
+
+ val isDownloading = remember { mutableStateOf(false) }
+ val showSpinner = remember { mutableStateOf(true) }
+ val statusText = remember { mutableStateOf("Downloading ${lastUpdateResult?.nextVersionString}") }
+
+ if(lastUpdateResult == null || !lastUpdateResult.isNewer()) {
+ InfoDialog(title = "Up-to-date", body = "As of the last update check, the app is up to date.")
+ } else {
+ AlertDialog(
+ icon = {
+ Icon(Icons.Filled.Info, contentDescription = "Info")
+ },
+ title = {
+ Text(text = "Update available")
+ },
+ text = {
+ if(isDownloading.value) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ if(showSpinner.value) {
+ CircularProgressIndicator(
+ modifier = Modifier.align(Alignment.CenterHorizontally),
+ color = MaterialTheme.colorScheme.onSurface
+ )
+ }
+ Text(text = statusText.value)
+ }
+ } else {
+ Text(text = "A new version ${lastUpdateResult.nextVersionString} is available, would you like to update?")
+ }
+ },
+ onDismissRequest = {
+ if(!isDownloading.value) {
+ navController.navigateUp()
+ }
+ },
+ confirmButton = {
+ TextButton(onClick = {
+ isDownloading.value = true
+ GlobalScope.launch { downloadAndInstall(scope.lifecycleScope, context, lastUpdateResult) {
+ statusText.value = it
+ showSpinner.value = false
+ } }
+ }, enabled = !isDownloading.value) {
+ Text(stringResource(R.string.update))
+ }
+ },
+ dismissButton = {
+ TextButton(onClick = {
+ navController.navigateUp()
+ }, enabled = !isDownloading.value) {
+ Text(stringResource(R.string.dismiss))
+ }
+ }
+ )
+ }
+}
\ No newline at end of file
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 95d5cfbc6..8d6e88152 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
@@ -31,7 +31,7 @@ fun HomeScreen(navController: NavHostController = rememberNavController()) {
Spacer(modifier = Modifier.height(24.dp))
ScreenTitle("FUTO Keyboard Settings")
- ConditionalUpdate()
+ ConditionalUpdate(navController)
NavigationItem(
title = "Languages",
diff --git a/java/src/org/futo/inputmethod/updates/InstallReceiver.kt b/java/src/org/futo/inputmethod/updates/InstallReceiver.kt
new file mode 100644
index 000000000..5fab42dad
--- /dev/null
+++ b/java/src/org/futo/inputmethod/updates/InstallReceiver.kt
@@ -0,0 +1,57 @@
+package org.futo.inputmethod.updates
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageInstaller
+import android.os.Build
+import android.util.Log
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.launch
+
+
+class InstallReceiver : BroadcastReceiver() {
+ private val TAG = "InstallReceiver"
+
+ companion object {
+ val onReceiveResult = MutableSharedFlow(0)
+ }
+
+ override fun onReceive(context: Context, intent: Intent) {
+ val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1)
+ Log.i(TAG, "Received status $status.")
+
+ GlobalScope.launch {
+ when (status) {
+ PackageInstaller.STATUS_PENDING_USER_ACTION -> {
+ val activityIntent: Intent? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ intent.getParcelableExtra(Intent.EXTRA_INTENT, Intent::class.java)
+ } else {
+ intent.getParcelableExtra(Intent.EXTRA_INTENT)
+ }
+
+ if (activityIntent == null) {
+ Log.w(TAG, "Received STATUS_PENDING_USER_ACTION and activity intent is null.")
+ return@launch
+ }
+ context.startActivity(activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+ }
+ PackageInstaller.STATUS_SUCCESS -> onReceiveResult.emit("Success!")
+ PackageInstaller.STATUS_FAILURE -> onReceiveResult.emit("General failure")
+ PackageInstaller.STATUS_FAILURE_ABORTED -> onReceiveResult.emit("The operation failed because it was actively aborted")
+ PackageInstaller.STATUS_FAILURE_BLOCKED -> onReceiveResult.emit("The operation failed because it was blocked")
+ PackageInstaller.STATUS_FAILURE_CONFLICT -> onReceiveResult.emit("The operation failed because it conflicts (or is inconsistent with) with another package already installed on the device")
+ PackageInstaller.STATUS_FAILURE_INCOMPATIBLE -> onReceiveResult.emit("The operation failed because it is fundamentally incompatible with this device")
+ PackageInstaller.STATUS_FAILURE_INVALID -> onReceiveResult.emit("The operation failed because one or more of the APKs was invalid")
+ PackageInstaller.STATUS_FAILURE_STORAGE -> onReceiveResult.emit("The operation failed because of storage issues")
+ else -> {
+ val msg = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE)
+ if(msg != null) {
+ onReceiveResult.emit(msg)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/java/src/org/futo/inputmethod/updates/Update.kt b/java/src/org/futo/inputmethod/updates/Update.kt
index 02139ce98..0bf799ffd 100644
--- a/java/src/org/futo/inputmethod/updates/Update.kt
+++ b/java/src/org/futo/inputmethod/updates/Update.kt
@@ -13,6 +13,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.tooling.preview.Preview
import androidx.datastore.preferences.core.stringPreferencesKey
+import androidx.navigation.NavHostController
import org.futo.inputmethod.latin.uix.settings.SettingItem
import org.futo.inputmethod.latin.uix.settings.useDataStore
@@ -33,7 +34,7 @@ fun Context.openURI(uri: String, newTask: Boolean = false) {
@Composable
@Preview
-fun ConditionalUpdate() {
+fun ConditionalUpdate(navController: NavHostController) {
val (updateInfo, _) = useDataStore(key = LAST_UPDATE_CHECK_RESULT, default = "")
val lastUpdateResult = if(!LocalInspectionMode.current){
@@ -48,7 +49,8 @@ fun ConditionalUpdate() {
title = "Update Available",
subtitle = "${UpdateResult.currentVersionString()} -> ${lastUpdateResult.nextVersionString}",
onClick = {
- context.openURI(lastUpdateResult.apkUrl)
+ navController.navigate("update")
+ //context.openURI(lastUpdateResult.apkUrl)
}
) {
Icon(Icons.Default.ArrowForward, contentDescription = "Go")
diff --git a/java/src/org/futo/inputmethod/updates/UpdateChecking.kt b/java/src/org/futo/inputmethod/updates/UpdateChecking.kt
index 01263becd..95bc27720 100644
--- a/java/src/org/futo/inputmethod/updates/UpdateChecking.kt
+++ b/java/src/org/futo/inputmethod/updates/UpdateChecking.kt
@@ -30,7 +30,6 @@ suspend fun checkForUpdate(): UpdateResult? {
val response = httpClient.newCall(request).execute()
val body = response.body
-
val result = if (body != null) {
val data = body.string().lines()
body.closeQuietly()
@@ -39,15 +38,18 @@ suspend fun checkForUpdate(): UpdateResult? {
val latestVersionUrl = data[1]
val latestVersionString = data[2]
if(latestVersionUrl.startsWith("https://voiceinput.futo.org/") || latestVersionUrl.startsWith("https://keyboard.futo.org/")){
+ Log.d("UpdateChecking", "Retrieved update for version ${latestVersionString}")
UpdateResult(
nextVersion = latestVersion,
apkUrl = latestVersionUrl,
nextVersionString = latestVersionString
)
} else {
+ Log.e("UpdateChecking", "Update URL contains unknown prefix: ${latestVersionUrl}")
null
}
} else {
+ Log.e("UpdateChecking", "Body of result is null")
null
}
@@ -55,6 +57,8 @@ suspend fun checkForUpdate(): UpdateResult? {
result
} catch (e: Exception) {
+ Log.e("UpdateChecking", "Checking update failed with exception")
+ e.printStackTrace()
null
}
}
diff --git a/java/src/org/futo/inputmethod/updates/UpdateResult.kt b/java/src/org/futo/inputmethod/updates/UpdateResult.kt
index 97eff1c7e..2b6339191 100644
--- a/java/src/org/futo/inputmethod/updates/UpdateResult.kt
+++ b/java/src/org/futo/inputmethod/updates/UpdateResult.kt
@@ -1,5 +1,6 @@
package org.futo.inputmethod.updates
+import android.util.Log
import kotlinx.serialization.Serializable
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
@@ -32,8 +33,12 @@ data class UpdateResult(
try {
return Json.decodeFromString(value)
} catch(e: SerializationException) {
+ Log.e("UpdateResult", "Failed to deserialize UpdateResult value $value")
+ e.printStackTrace()
return null
} catch(e: IllegalArgumentException) {
+ Log.e("UpdateResult", "Failed to deserialize UpdateResult value $value")
+ e.printStackTrace()
return null
}
}