mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Add model downloader activity for voice input
This commit is contained in:
parent
94c606718d
commit
2088909f88
@ -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'
|
||||
|
@ -100,6 +100,14 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".uix.voiceinput.downloader.DownloadActivity"
|
||||
android:exported="false"
|
||||
android:label="@string/model_downloader"
|
||||
android:clearTaskOnLaunch="true"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar" />
|
||||
|
||||
<activity android:name=".permissions.PermissionsActivity"
|
||||
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||
android:exported="false"
|
||||
|
@ -11,4 +11,14 @@
|
||||
<string name="dynamic_system_theme_name">Dynamic System</string>
|
||||
<string name="dynamic_light_theme_name">Dynamic Light</string>
|
||||
<string name="dynamic_dark_theme_name">Dynamic Dark</string>
|
||||
|
||||
<string name="download_required">Voice Input Model Downloader</string>
|
||||
<string name="download_required_body">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.</string>
|
||||
<string name="continue_">Continue</string>
|
||||
|
||||
<string name="download_progress">Voice Input Model Download Progress</string>
|
||||
<string name="download_failed">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.</string>
|
||||
<string name="download_in_progress">Downloading models…</string>
|
||||
|
||||
<string name="model_downloader">Model Downloader</string>
|
||||
</resources>
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<ModelInfo> = 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<ModelInfo> = 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<ModelInfo>
|
||||
private val httpClient = OkHttpClient()
|
||||
private var isDownloading = false
|
||||
|
||||
private val themeOption: MutableState<ThemeOption?> = 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()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user