diff --git a/build.gradle b/build.gradle
index a54bccff9..3924cae97 100644
--- a/build.gradle
+++ b/build.gradle
@@ -163,6 +163,8 @@ dependencies {
implementation 'ch.acra:acra-http:5.11.1'
implementation 'ch.acra:acra-dialog:5.11.1'
+ implementation 'com.squareup.okhttp3:okhttp:4.11.0'
+
implementation project(":voiceinput-shared")
debugImplementation 'androidx.compose.ui:ui-tooling'
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index b4084ab5a..d016de755 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -100,6 +100,14 @@
+
+
Dynamic System
Dynamic Light
Dynamic Dark
+
+ Voice Input Model Downloader
+ Download of some model files is necessary for voice input functionality. This may incur data fees if you are using Mobile Data instead of Wi-Fi.
+ Continue
+
+ Voice Input Model Download Progress
+ Download of one or more resources has failed. Please make sure you\'re connected to a network, the app has network permission, or try again later.
+ Downloading models…
+
+ Model Downloader
\ No newline at end of file
diff --git a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
index 0800a4607..e54f01a57 100644
--- a/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
+++ b/java/src/org/futo/inputmethod/latin/LatinIMELegacy.java
@@ -85,12 +85,12 @@ import org.futo.inputmethod.latin.inputlogic.InputLogic;
import org.futo.inputmethod.latin.permissions.PermissionsManager;
import org.futo.inputmethod.latin.personalization.PersonalizationHelper;
import org.futo.inputmethod.latin.settings.Settings;
-import org.futo.inputmethod.latin.settings.SettingsActivity;
import org.futo.inputmethod.latin.settings.SettingsValues;
import org.futo.inputmethod.latin.suggestions.SuggestionStripView;
import org.futo.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
import org.futo.inputmethod.latin.touchinputconsumer.GestureConsumer;
import org.futo.inputmethod.latin.uix.DynamicThemeProviderOwner;
+import org.futo.inputmethod.latin.uix.settings.SettingsActivity;
import org.futo.inputmethod.latin.utils.ApplicationUtils;
import org.futo.inputmethod.latin.utils.DialogUtils;
import org.futo.inputmethod.latin.utils.ImportantNoticeUtils;
@@ -1803,8 +1803,8 @@ public class LatinIMELegacy implements KeyboardActionListener,
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.putExtra(SettingsActivity.EXTRA_SHOW_HOME_AS_UP, false);
- intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY, extraEntryValue);
+ //intent.putExtra(SettingsActivity.EXTRA_SHOW_HOME_AS_UP, false);
+ //intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY, extraEntryValue);
startActivityOnTheSameDisplay(intent);
}
@@ -1832,7 +1832,7 @@ public class LatinIMELegacy implements KeyboardActionListener,
startActivityOnTheSameDisplay(intent);
break;
case 1:
- launchSettings(SettingsActivity.EXTRA_ENTRY_VALUE_LONG_PRESS_COMMA);
+ launchSettings("");
break;
}
}
diff --git a/java/src/org/futo/inputmethod/latin/settings/SettingsActivity.java b/java/src/org/futo/inputmethod/latin/settings/SettingsActivity.java
deleted file mode 100644
index afe437d2a..000000000
--- a/java/src/org/futo/inputmethod/latin/settings/SettingsActivity.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.futo.inputmethod.latin.settings;
-
-import org.futo.inputmethod.latin.permissions.PermissionsManager;
-import org.futo.inputmethod.latin.utils.FragmentUtils;
-import org.futo.inputmethod.latin.utils.StatsUtils;
-import org.futo.inputmethod.latin.utils.StatsUtilsManager;
-
-import android.app.ActionBar;
-import android.content.Intent;
-import android.os.Bundle;
-import android.preference.PreferenceActivity;
-import androidx.core.app.ActivityCompat;
-import android.view.MenuItem;
-
-public final class SettingsActivity extends PreferenceActivity
- implements ActivityCompat.OnRequestPermissionsResultCallback {
- private static final String DEFAULT_FRAGMENT = SettingsFragment.class.getName();
-
- public static final String EXTRA_SHOW_HOME_AS_UP = "show_home_as_up";
- public static final String EXTRA_ENTRY_KEY = "entry";
- public static final String EXTRA_ENTRY_VALUE_LONG_PRESS_COMMA = "long_press_comma";
- public static final String EXTRA_ENTRY_VALUE_APP_ICON = "app_icon";
- public static final String EXTRA_ENTRY_VALUE_NOTICE_DIALOG = "important_notice";
- public static final String EXTRA_ENTRY_VALUE_SYSTEM_SETTINGS = "system_settings";
-
- private boolean mShowHomeAsUp;
-
- @Override
- protected void onCreate(final Bundle savedState) {
- super.onCreate(savedState);
- final ActionBar actionBar = getActionBar();
- final Intent intent = getIntent();
- if (actionBar != null) {
- mShowHomeAsUp = intent.getBooleanExtra(EXTRA_SHOW_HOME_AS_UP, true);
- actionBar.setDisplayHomeAsUpEnabled(mShowHomeAsUp);
- actionBar.setHomeButtonEnabled(mShowHomeAsUp);
- }
- StatsUtils.onSettingsActivity(
- intent.hasExtra(EXTRA_ENTRY_KEY) ? intent.getStringExtra(EXTRA_ENTRY_KEY)
- : EXTRA_ENTRY_VALUE_SYSTEM_SETTINGS);
- }
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- if (mShowHomeAsUp && item.getItemId() == android.R.id.home) {
- finish();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public Intent getIntent() {
- final Intent intent = super.getIntent();
- final String fragment = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
- if (fragment == null) {
- intent.putExtra(EXTRA_SHOW_FRAGMENT, DEFAULT_FRAGMENT);
- }
- intent.putExtra(EXTRA_NO_HEADERS, true);
- return intent;
- }
-
- @Override
- public boolean isValidFragment(final String fragmentName) {
- return FragmentUtils.isValidFragment(fragmentName);
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
- PermissionsManager.get(this).onRequestPermissionsResult(requestCode, permissions, grantResults);
- }
-}
diff --git a/java/src/org/futo/inputmethod/latin/settings/SettingsFragment.java b/java/src/org/futo/inputmethod/latin/settings/SettingsFragment.java
index b84df68f2..7e2f3950c 100644
--- a/java/src/org/futo/inputmethod/latin/settings/SettingsFragment.java
+++ b/java/src/org/futo/inputmethod/latin/settings/SettingsFragment.java
@@ -29,6 +29,7 @@ import android.view.MenuItem;
import org.futo.inputmethod.latin.R;
import org.futo.inputmethod.latin.define.ProductionFlags;
+import org.futo.inputmethod.latin.uix.settings.SettingsActivity;
import org.futo.inputmethod.latin.utils.ApplicationUtils;
import org.futo.inputmethod.latin.utils.FeedbackUtils;
import org.futo.inputmethodcommon.InputMethodSettingsFragment;
diff --git a/java/src/org/futo/inputmethod/latin/setup/SetupWizardActivity.java b/java/src/org/futo/inputmethod/latin/setup/SetupWizardActivity.java
index 3cbb302e8..28c6df60a 100644
--- a/java/src/org/futo/inputmethod/latin/setup/SetupWizardActivity.java
+++ b/java/src/org/futo/inputmethod/latin/setup/SetupWizardActivity.java
@@ -36,7 +36,7 @@ import android.widget.VideoView;
import org.futo.inputmethod.compat.TextViewCompatUtils;
import org.futo.inputmethod.compat.ViewCompatUtils;
import org.futo.inputmethod.latin.R;
-import org.futo.inputmethod.latin.settings.SettingsActivity;
+import org.futo.inputmethod.latin.uix.settings.SettingsActivity;
import org.futo.inputmethod.latin.utils.LeakGuardHandlerWrapper;
import org.futo.inputmethod.latin.utils.UncachedInputMethodManagerUtils;
@@ -265,8 +265,8 @@ public final class SetupWizardActivity extends Activity implements View.OnClickL
intent.setClass(this, SettingsActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
- intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY,
- SettingsActivity.EXTRA_ENTRY_VALUE_APP_ICON);
+ //intent.putExtra(SettingsActivity.EXTRA_ENTRY_KEY,
+ // SettingsActivity.EXTRA_ENTRY_VALUE_APP_ICON);
startActivity(intent);
}
diff --git a/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt b/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt
index 2aa629f12..d4ee4504f 100644
--- a/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt
+++ b/java/src/org/futo/inputmethod/latin/uix/actions/VoiceInputAction.kt
@@ -1,5 +1,7 @@
package org.futo.inputmethod.latin.uix.actions
+import android.app.Activity
+import android.content.Intent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Box
@@ -12,6 +14,7 @@ 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.res.stringResource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
@@ -34,6 +37,7 @@ import org.futo.inputmethod.latin.uix.MULTILINGUAL_MODEL_INDEX
import org.futo.inputmethod.latin.uix.PersistentActionState
import org.futo.inputmethod.latin.uix.VERBOSE_PROGRESS
import org.futo.inputmethod.latin.uix.getSetting
+import org.futo.inputmethod.latin.uix.voiceinput.downloader.DownloadActivity
import org.futo.voiceinput.shared.ENGLISH_MODELS
import org.futo.voiceinput.shared.MULTILINGUAL_MODELS
import org.futo.voiceinput.shared.ModelDoesNotExistException
@@ -155,10 +159,18 @@ private class VoiceInputActionWindow(
@Composable
private fun ModelDownloader(modelException: ModelDoesNotExistException) {
- Column {
- Text("Model Download Required")
- Text("Not yet implemented")
- // TODO
+ val context = LocalContext.current
+ Box(modifier = Modifier.fillMaxSize().clickable {
+ val intent = Intent(context, DownloadActivity::class.java)
+ intent.putStringArrayListExtra("models", ArrayList(modelException.models.map { model -> model.getRequiredDownloadList(context) }.flatten()))
+
+ if(context !is Activity) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+
+ context.startActivity(intent)
+ }) {
+ Text("Tap to complete setup", modifier = Modifier.align(Alignment.Center))
}
}
diff --git a/java/src/org/futo/inputmethod/latin/uix/voiceinput/downloader/DownloadActivity.kt b/java/src/org/futo/inputmethod/latin/uix/voiceinput/downloader/DownloadActivity.kt
new file mode 100644
index 000000000..f69408ca7
--- /dev/null
+++ b/java/src/org/futo/inputmethod/latin/uix/voiceinput/downloader/DownloadActivity.kt
@@ -0,0 +1,431 @@
+package org.futo.inputmethod.latin.uix.voiceinput.downloader
+
+// TODO: Rework this
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.material.icons.filled.Warning
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LinearProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.Alignment.Companion.CenterVertically
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.rememberNavController
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import okhttp3.Call
+import okhttp3.Callback
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import org.futo.inputmethod.latin.R
+import org.futo.inputmethod.latin.uix.THEME_KEY
+import org.futo.inputmethod.latin.uix.deferGetSetting
+import org.futo.inputmethod.latin.uix.settings.useDataStore
+import org.futo.inputmethod.latin.uix.theme.ThemeOption
+import org.futo.inputmethod.latin.uix.theme.ThemeOptions
+import org.futo.inputmethod.latin.uix.theme.UixThemeWrapper
+import org.futo.inputmethod.latin.uix.theme.presets.VoiceInputTheme
+import org.futo.voiceinput.shared.ui.theme.Typography
+import java.io.File
+import java.io.IOException
+
+
+data class ModelInfo(
+ val name: String,
+ val url: String,
+ var size: Long?,
+ var progress: Float = 0.0f,
+ var error: Boolean = false,
+ var finished: Boolean = false
+)
+
+val EXAMPLE_MODELS = listOf(
+ ModelInfo(
+ name = "tiny-encoder-xatn.tflite",
+ url = "example.com",
+ size = 56L * 1024L * 1024L,
+ progress = 0.5f,
+ error = true
+ ),
+ ModelInfo(
+ name = "tiny-decoder.tflite",
+ url = "example.com",
+ size = 73L * 1024L * 1024L,
+ progress = 0.3f,
+ error = false
+ ),
+)
+
+@Composable
+fun ModelItem(model: ModelInfo, showProgress: Boolean) {
+ Column(modifier = Modifier.padding(4.dp)) {
+ val color = if (model.error) {
+ MaterialTheme.colorScheme.errorContainer
+ } else {
+ MaterialTheme.colorScheme.primaryContainer
+ }
+ Surface(modifier = Modifier, color = color, shape = RoundedCornerShape(4.dp)) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(8.dp)
+ ) {
+ if (model.error) {
+ Icon(
+ Icons.Default.Warning, contentDescription = "Failed", modifier = Modifier
+ .align(CenterVertically)
+ .padding(4.dp)
+ )
+ }
+
+ val size = if (model.size != null) {
+ "%.1f".format(model.size!!.toFloat() / 1000000.0f)
+ } else {
+ "?"
+ }
+
+ Column {
+ Text(model.name, style = Typography.bodyLarge)
+ Text(
+ "$size MB",
+ style = Typography.bodySmall,
+ color = MaterialTheme.colorScheme.outline
+ )
+ if (showProgress && !model.error) {
+ LinearProgressIndicator(
+ progress = model.progress, modifier = Modifier
+ .fillMaxWidth()
+ .padding(0.dp, 8.dp),
+ color = MaterialTheme.colorScheme.onPrimary
+ )
+ }
+ }
+
+ }
+ }
+ }
+}
+
+
+@Composable
+fun ScreenTitle(title: String, showBack: Boolean = false, navController: NavHostController = rememberNavController()) {
+ val rowModifier = if(showBack) {
+ Modifier
+ .fillMaxWidth()
+ .clickable { navController.popBackStack() }
+ } else {
+ Modifier.fillMaxWidth()
+ }
+ Row(modifier = rowModifier) {
+ Spacer(modifier = Modifier.width(16.dp))
+
+ if(showBack) {
+ Icon(Icons.Default.ArrowBack, contentDescription = "Back", modifier = Modifier.align(CenterVertically))
+ Spacer(modifier = Modifier.width(18.dp))
+ }
+ Text(title, style = Typography.titleLarge, modifier = Modifier
+ .align(CenterVertically)
+ .padding(0.dp, 16.dp))
+ }
+}
+@Composable
+fun ScrollableList(content: @Composable () -> Unit) {
+ val scrollState = rememberScrollState()
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(scrollState)
+ ) {
+ content()
+ }
+}
+
+
+
+@Composable
+@Preview
+fun DownloadPrompt(
+ onContinue: () -> Unit = {},
+ onCancel: () -> Unit = {},
+ models: List = EXAMPLE_MODELS
+) {
+ ScrollableList {
+ ScreenTitle(title = stringResource(R.string.download_required))
+ Text(
+ stringResource(R.string.download_required_body),
+ style = Typography.bodyMedium
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ for(model in models) {
+ ModelItem(model, showProgress = false)
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Row {
+ Button(
+ onClick = onCancel, colors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.secondary,
+ contentColor = MaterialTheme.colorScheme.onSecondary
+ ), modifier = Modifier
+ .padding(8.dp)
+ .weight(1.0f)
+ ) {
+ Text(stringResource(R.string.cancel))
+ }
+ Button(
+ onClick = onContinue, modifier = Modifier
+ .padding(8.dp)
+ .weight(1.5f)
+ ) {
+ Text(stringResource(R.string.continue_))
+ }
+ }
+ }
+}
+
+
+@Composable
+@Preview
+fun DownloadScreen(models: List = EXAMPLE_MODELS) {
+ ScrollableList {
+ ScreenTitle(stringResource(R.string.download_progress))
+ if (models.any { it.error }) {
+ Text(
+ stringResource(R.string.download_failed),
+ style = Typography.bodyMedium
+ )
+ } else {
+ Text(
+ stringResource(R.string.download_in_progress),
+ style = Typography.bodyMedium
+ )
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ for(model in models) {
+ ModelItem(model, showProgress = true)
+ }
+ }
+}
+
+fun Context.fileNeedsDownloading(file: String): Boolean {
+ return !File(this.filesDir, file).exists()
+}
+
+class DownloadActivity : ComponentActivity() {
+ private lateinit var modelsToDownload: List
+ private val httpClient = OkHttpClient()
+ private var isDownloading = false
+
+ private val themeOption: MutableState = mutableStateOf(null)
+ private fun updateContent() {
+ setContent {
+ themeOption.value?.let { themeOption ->
+ val themeIdx = useDataStore(key = THEME_KEY.key, default = themeOption.key)
+ val theme: ThemeOption = ThemeOptions[themeIdx.value] ?: themeOption
+ UixThemeWrapper(theme.obtainColors(LocalContext.current)) {
+ // A surface container using the 'background' color from the theme
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background
+ ) {
+ if (isDownloading) {
+ DownloadScreen(models = modelsToDownload)
+ } else {
+ DownloadPrompt(
+ onContinue = { startDownload() },
+ onCancel = { cancel() },
+ models = modelsToDownload
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun startDownload() {
+ isDownloading = true
+ updateContent()
+
+ modelsToDownload.forEach {
+ val request = Request.Builder().method("GET", null).url(it.url).build()
+
+ httpClient.newCall(request).enqueue(object : Callback {
+ override fun onFailure(call: Call, e: IOException) {
+ it.error = true
+ updateContent()
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ response.body?.source()?.let { source ->
+
+ try {
+ it.size = response.headers["content-length"]!!.toLong()
+ } catch (e: Exception) {
+ println("url failed ${it.url}")
+ println(response.headers)
+ e.printStackTrace()
+ }
+
+ val fileName = it.name + ".download"
+ val file =
+ File.createTempFile(fileName, null, this@DownloadActivity.cacheDir)
+ val os = file.outputStream()
+
+ val buffer = ByteArray(128 * 1024)
+ var downloaded = 0
+ while (true) {
+ val read = source.read(buffer)
+ if (read == -1) {
+ break
+ }
+
+ os.write(buffer.sliceArray(0 until read))
+
+ downloaded += read
+
+ if (it.size != null) {
+ it.progress = downloaded.toFloat() / it.size!!.toFloat()
+ }
+
+ lifecycleScope.launch {
+ withContext(Dispatchers.Main) {
+ updateContent()
+ }
+ }
+ }
+
+ it.finished = true
+ it.progress = 1.0f
+ os.flush()
+ os.close()
+
+ assert(file.renameTo(File(this@DownloadActivity.filesDir, it.name)))
+
+ if (modelsToDownload.all { a -> a.finished }) {
+ downloadsFinished()
+ }
+ }
+ }
+ })
+ }
+ }
+
+ private fun cancel() {
+ val returnIntent = Intent()
+ setResult(RESULT_CANCELED, returnIntent)
+ finish()
+ }
+
+ private fun downloadsFinished() {
+ val returnIntent = Intent()
+ setResult(RESULT_OK, returnIntent)
+ finish()
+ }
+
+ private fun obtainModelSizes() {
+ modelsToDownload.forEach {
+ val request =
+ Request.Builder().method("HEAD", null).header("accept-encoding", "identity")
+ .url(it.url).build()
+
+ httpClient.newCall(request).enqueue(object : Callback {
+ override fun onFailure(call: Call, e: IOException) {
+ it.error = true
+ updateContent()
+ }
+
+ override fun onResponse(call: Call, response: Response) {
+ try {
+ it.size = response.headers["content-length"]!!.toLong()
+ } catch (e: Exception) {
+ println("url failed ${it.url}")
+ println(response.headers)
+ e.printStackTrace()
+ it.error = true
+ }
+
+ if (response.code != 200) {
+ println("Bad response code ${response.code}")
+ it.error = true
+ }
+ updateContent()
+ }
+ })
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val models = intent.getStringArrayListExtra("models")
+ ?: throw IllegalStateException("intent extra `models` must be specified for DownloadActivity")
+
+ modelsToDownload = models.distinct().filter { this.fileNeedsDownloading(it) }.map {
+ ModelInfo(
+ name = it,
+ url = "https://voiceinput.futo.org/VoiceInput/${it}",
+ size = null,
+ progress = 0.0f
+ )
+ }
+
+ if (modelsToDownload.isEmpty()) {
+ cancel()
+ }
+
+ isDownloading = false
+
+ deferGetSetting(THEME_KEY) {
+ val themeOptionFromSettings = ThemeOptions[it]
+ val themeOption = when {
+ themeOptionFromSettings == null -> VoiceInputTheme
+ !themeOptionFromSettings.available(this) -> VoiceInputTheme
+ else -> themeOptionFromSettings
+ }
+
+ this.themeOption.value = themeOption
+ }
+
+ updateContent()
+ obtainModelSizes()
+ }
+}