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 } }