diff --git a/app/build.gradle b/app/build.gradle
index 1e8aefb..b62be49 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -26,7 +26,12 @@ android {
}
buildFeatures {
viewBinding true
+ compose true
}
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.3"
+ }
+
namespace 'com.elishaazaria.sayboard'
packagingOptions {
@@ -63,10 +68,10 @@ dependencies {
implementation group: 'com.alphacephei', name: 'vosk-android', version: '0.3.32'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'com.google.android.material:material:1.9.0'
- implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
- implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
- implementation 'androidx.navigation:navigation-fragment:2.7.1'
- implementation 'androidx.navigation:navigation-ui:2.7.1'
+ implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
+ implementation 'androidx.navigation:navigation-fragment:2.7.2'
+ implementation 'androidx.navigation:navigation-ui:2.7.2'
implementation 'androidx.preference:preference:1.2.1'
implementation 'org.greenrobot:eventbus:3.3.1'
@@ -79,5 +84,51 @@ dependencies {
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0'
+
+ implementation 'dev.patrickgold.jetpref:jetpref-datastore-model:0.1.0-beta14'
+ implementation 'dev.patrickgold.jetpref:jetpref-datastore-ui:0.1.0-beta14'
+ implementation 'dev.patrickgold.jetpref:jetpref-material-ui:0.1.0-beta14'
+
+ def composeBom = platform('androidx.compose:compose-bom:2023.08.00')
+ implementation composeBom
+ androidTestImplementation composeBom
+
+ // Choose one of the following:
+ // Material Design 3
+ implementation 'androidx.compose.material3:material3'
+ // or Material Design 2
+// implementation 'androidx.compose.material:material'
+ // or skip Material Design and build directly on top of foundational components
+// implementation 'androidx.compose.foundation:foundation'
+ // or only import the main APIs for the underlying toolkit systems,
+ // such as input and measurement/layout
+// implementation 'androidx.compose.ui:ui'
+
+ // Android Studio Preview support
+ implementation 'androidx.compose.ui:ui-tooling-preview'
+ debugImplementation 'androidx.compose.ui:ui-tooling'
+
+ // UI Tests
+ androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
+ debugImplementation 'androidx.compose.ui:ui-test-manifest'
+
+ // Optional - Included automatically by material, only add when you need
+ // the icons but not the material library (e.g. when using Material3 or a
+ // custom design system based on Foundation)
+// implementation 'androidx.compose.material:material-icons-core'
+ // Optional - Add full set of material icons
+ implementation 'androidx.compose.material:material-icons-extended'
+ // Optional - Add window size utils
+ implementation 'androidx.compose.material3:material3-window-size-class'
+
+ // Optional - Integration with activities
+ implementation 'androidx.activity:activity-compose:1.7.2'
+ // Optional - Integration with ViewModels
+ implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
+ // Optional - Integration with LiveData
+ implementation 'androidx.compose.runtime:runtime-livedata'
+ // Optional - Integration with RxJava
+ implementation 'androidx.compose.runtime:runtime-rxjava2'
+
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9d07c87..3091a42 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
+
diff --git a/app/src/main/java/com/elishaazaria/sayboard/AppPrefs.kt b/app/src/main/java/com/elishaazaria/sayboard/AppPrefs.kt
new file mode 100644
index 0000000..1303c4b
--- /dev/null
+++ b/app/src/main/java/com/elishaazaria/sayboard/AppPrefs.kt
@@ -0,0 +1,90 @@
+package com.elishaazaria.sayboard
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import com.elishaazaria.sayboard.data.KeepScreenAwakeMode
+import dev.patrickgold.jetpref.datastore.JetPref
+import dev.patrickgold.jetpref.datastore.model.PreferenceModel
+
+// Defining a getter function for easy retrieval of the AppPrefs model.
+// You can name this however you want, the convention is PreferenceModel
+fun sayboardPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
+
+// Defining a preference model for our app prefs
+// The name we give here is the file name of the preferences and is saved
+// within the app's `jetpref_datastore` directory.
+class AppPrefs : PreferenceModel("example-app-preferences") {
+ val logicKeepScreenAwake = enum(
+ key = "e_keep_screen_awake",
+ default = KeepScreenAwakeMode.NEVER
+ )
+
+ val logicListenImmediately = boolean(
+ key = "b_listen_immediately",
+ default = false
+ )
+
+ val logicAutoSwitchBack = boolean(
+ key = "b_auto_switch_back_ime",
+ default = false
+ )
+
+ val logicWeakRefToModel = boolean(
+ key = "b_weak_ref_to_model",
+ default = false
+ )
+
+ val uiDayForegroundMaterialYou = boolean(
+ key = "b_day_foreground_material_you",
+ default = false
+ )
+ val uiDayForeground = int(
+ key = "c_day_foreground_color",
+ default = Color(0xFF377A00).toArgb()
+ )
+ val uiDayBackground = int(
+ key = "c_day_background_color",
+ default = Color(0xFFFFFFFF).toArgb()
+ )
+
+ val uiNightForegroundMaterialYou = boolean(
+ key = "b_night_foreground_material_you",
+ default = false
+ )
+ val uiNightForeground = int(
+ key = "c_night_foreground_color",
+ default = Color(0xFF66BB6A).toArgb()
+ )
+ val uiNightBackground = int(
+ key = "c_night_background_color",
+ default = Color(0xFF000000).toArgb()
+ )
+
+ val uiKeyboardHeightPortrait = float(
+ key = "f_keyboard_height_portrait",
+ default = 0.3f
+ )
+
+ val uiKeyboardHeightLandscape = float(
+ key = "f_keyboard_height_landscape",
+ default = 0.5f
+ )
+
+
+ val showExampleGroup = boolean(
+ key = "show_example_group",
+ default = true,
+ )
+ val boxSizePortrait = int(
+ key = "box_size_portrait",
+ default = 40,
+ )
+ val boxSizeLandscape = int(
+ key = "box_size_landscape",
+ default = 20,
+ )
+ val welcomeMessage = string(
+ key = "welcome_message",
+ default = "Hello world!",
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/SayboardApplication.kt b/app/src/main/java/com/elishaazaria/sayboard/SayboardApplication.kt
index 4a33e0d..f5c217a 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/SayboardApplication.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/SayboardApplication.kt
@@ -2,11 +2,24 @@ package com.elishaazaria.sayboard
import android.app.Application
import com.elishaazaria.sayboard.AppCtx.setAppCtx
+import dev.patrickgold.jetpref.datastore.JetPref
import org.greenrobot.eventbus.EventBus
class SayboardApplication : Application() {
+ private val prefs by sayboardPreferenceModel()
override fun onCreate() {
super.onCreate()
+
+ // Optionally initialize global JetPref configs. This must be done before
+ // any preference datastore is initialized!
+ JetPref.configure(
+ saveIntervalMs = 500,
+ encodeDefaultValues = true,
+ )
+
+ // Initialize your datastore here (required)
+ prefs.initializeBlocking(this)
+
EventBus.builder().logNoSubscriberMessages(false).sendNoSubscriberEvent(false)
.installDefaultEventBus()
setAppCtx(this)
diff --git a/app/src/main/java/com/elishaazaria/sayboard/SettingsActivity.kt b/app/src/main/java/com/elishaazaria/sayboard/SettingsActivity.kt
index c59b67c..4c74d99 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/SettingsActivity.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/SettingsActivity.kt
@@ -1,48 +1,156 @@
package com.elishaazaria.sayboard
+import android.Manifest
+import android.content.Intent
import android.os.Bundle
-import android.view.View
-import androidx.appcompat.app.AppCompatActivity
-import androidx.navigation.NavController
-import androidx.navigation.fragment.NavHostFragment
-import androidx.navigation.ui.AppBarConfiguration
-import androidx.navigation.ui.NavigationUI.setupActionBarWithNavController
-import androidx.navigation.ui.NavigationUI.setupWithNavController
-import com.elishaazaria.sayboard.databinding.ActivitySettingsBinding
-
-class SettingsActivity : AppCompatActivity() {
- private var binding: ActivitySettingsBinding? = null
- private var navController: NavController? = null
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Settings
+import androidx.compose.material3.Icon
+import androidx.compose.material3.NavigationBar
+import androidx.compose.material3.NavigationBarItem
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.core.app.ActivityCompat
+import androidx.lifecycle.MutableLiveData
+import com.elishaazaria.sayboard.data.LocalModel
+import com.elishaazaria.sayboard.ui.GrantPermissionUi
+import com.elishaazaria.sayboard.ui.LogicSettingsUi
+import com.elishaazaria.sayboard.ui.ModelsSettingsUi
+import com.elishaazaria.sayboard.ui.UISettingsUi
+import java.util.Locale
+
+class SettingsActivity : ComponentActivity() {
+
+ private val micGranted = MutableLiveData(true)
+ private val imeGranted = MutableLiveData(true)
+
+ private val modelSettingsUi = ModelsSettingsUi(this)
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Tools.createNotificationChannel(this)
- binding = ActivitySettingsBinding.inflate(
- layoutInflater
- )
- setContentView(binding!!.root)
- val navHostFragment =
- supportFragmentManager.findFragmentById(R.id.nav_host_fragment_activity_settings) as NavHostFragment?
- navController = navHostFragment!!.navController
- if (Tools.isMicrophonePermissionGranted(this) && Tools.isIMEEnabled(this)) {
- permissionsGranted()
- } else {
- navController!!.navigate(R.id.navigation_setup)
- binding!!.navView.visibility = View.GONE
+
+ checkPermissions()
+
+ modelSettingsUi.onCreate()
+
+ setContent {
+ val micGrantedState = micGranted.observeAsState(true)
+ val imeGrantedState = imeGranted.observeAsState(true)
+ if (micGrantedState.value && imeGrantedState.value) {
+
+ MainUi()
+ } else {
+ GrantPermissionUi(mic = micGrantedState, ime = imeGrantedState, requestMic = {
+ ActivityCompat.requestPermissions(
+ this, arrayOf(
+ Manifest.permission.RECORD_AUDIO
+ ), PERMISSIONS_REQUEST_RECORD_AUDIO
+ )
+ }) {
+ startActivity(Intent("android.settings.INPUT_METHOD_SETTINGS"))
+ }
+ }
+ }
+ }
+
+ @Composable
+ private fun MainUi() {
+ val tabs = listOf("Models", "UI", "Logic")
+ var selectedIndex by remember {
+ mutableIntStateOf(0)
}
+
+ Scaffold(bottomBar = {
+ NavigationBar() {
+ tabs.forEachIndexed { index, tab ->
+ NavigationBarItem(
+ selected = index == selectedIndex,
+ onClick = { selectedIndex = index },
+ icon = {
+ when (index) {
+ 0 -> Icon(
+ imageVector = Icons.Default.Home,
+ contentDescription = null
+ )
+
+ 1 -> Icon(
+ painter = painterResource(id = R.drawable.ic_baseline_color_lens_24),
+ contentDescription = null
+ )
+
+ 2 -> Icon(
+ imageVector = Icons.Default.Settings,
+ contentDescription = null
+ )
+ }
+ }, label = {
+ Text(text = tab)
+ })
+ }
+ }
+ }, floatingActionButton = {
+ if (selectedIndex == 0) {
+ modelSettingsUi.Fab()
+ }
+ }) {
+ Box(modifier = Modifier
+ .padding(it)
+ .padding(10.dp)) {
+ when (selectedIndex) {
+ 0 -> modelSettingsUi.Content()
+ 1 -> UISettingsUi()
+ 2 -> LogicSettingsUi()
+ }
+ }
+ }
+ }
+
+ private fun checkPermissions() {
+ micGranted.postValue(Tools.isMicrophonePermissionGranted(this))
+ imeGranted.postValue(Tools.isIMEEnabled(this))
+ }
+
+ override fun onStart() {
+ super.onStart()
+ modelSettingsUi.onStart()
+ }
+
+ override fun onStop() {
+ modelSettingsUi.onStop()
+ super.onStop()
+ }
+
+ override fun onResume() {
+ super.onResume()
+ checkPermissions()
+ modelSettingsUi.onResume()
+ }
+
+ companion object {
+ /* Used to handle permission request */
+ private const val PERMISSIONS_REQUEST_RECORD_AUDIO = 1
}
- fun permissionsGranted() {
- binding!!.navView.visibility = View.VISIBLE
- // Passing each menu ID as a set of Ids because each
- // menu should be considered as top level destinations.
- val appBarConfiguration: AppBarConfiguration = AppBarConfiguration.Builder( // R.id.navigation_setup,
- R.id.navigation_models,
- R.id.navigation_ui,
- R.id.navigation_logic
- )
- .build()
- setupActionBarWithNavController(this, navController!!, appBarConfiguration)
- setupWithNavController(binding!!.navView, navController!!)
- navController!!.navigate(R.id.navigation_models)
+ @Preview
+ @Composable
+ fun DefaultPreview() {
+ modelSettingsUi.models.postValue(listOf(LocalModel("abc/def", Locale.ENGLISH, "english")))
+ MainUi()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/Tools.kt b/app/src/main/java/com/elishaazaria/sayboard/Tools.kt
index b8273a7..a037a82 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/Tools.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/Tools.kt
@@ -12,11 +12,12 @@ import androidx.core.content.ContextCompat
import com.elishaazaria.sayboard.Constants.getModelsDirectory
import com.elishaazaria.sayboard.data.LocalModel
import com.elishaazaria.sayboard.data.ModelLink
-import com.elishaazaria.sayboard.settingsfragments.modelsfragment.ModelsAdapterLocalData
import java.io.File
import java.util.*
object Tools {
+ const val VOSK_SERVER_ENABLED = false
+
@JvmStatic
fun isMicrophonePermissionGranted(activity: Activity): Boolean {
val permissionCheck = ContextCompat.checkSelfPermission(
@@ -38,35 +39,6 @@ object Tools {
return false
}
- // public static void downloadModelFromLink(ModelLink model, OnDownloadStatusListener listener, Context context) {
- // String serverFilePath = model.link;
- //
- // File tempFolder = Constants.getTemporaryDownloadLocation(context);
- // if (!tempFolder.exists()) {
- // tempFolder.mkdirs();
- // }
- //
- // String fileName = model.link.substring(model.link.lastIndexOf('/') + 1); // file name
- // File tempFile = new File(tempFolder, fileName);
- //
- // String localPath = tempFile.getAbsolutePath();
- //
- // File modelFolder = Constants.getDirectoryForModel(context, model.locale);
- //
- // if (!modelFolder.exists()) {
- // modelFolder.mkdirs();
- // }
- //
- // String unzipPath = modelFolder.getAbsolutePath();
- //
- // DownloadRequest downloadRequest = new DownloadRequest(serverFilePath, localPath, true);
- // downloadRequest.setRequiresUnzip(true);
- // downloadRequest.setDeleteZipAfterExtract(true);
- // downloadRequest.setUnzipAtFilePath(unzipPath);
- //
- // FileDownloader downloader = FileDownloader.getInstance(listener);
- // downloader.download(downloadRequest, context);
- // }
@JvmStatic
fun deleteModel(model: LocalModel, context: Context?) {
val modelFile = File(model.path)
@@ -75,7 +47,7 @@ object Tools {
@JvmStatic
fun deleteRecursive(fileOrDirectory: File) {
- if (fileOrDirectory.isDirectory) for (child in fileOrDirectory.listFiles()) deleteRecursive(
+ if (fileOrDirectory.isDirectory) for (child in fileOrDirectory.listFiles()!!) deleteRecursive(
child
)
fileOrDirectory.delete()
@@ -85,11 +57,11 @@ object Tools {
val localeMap: MutableMap> = HashMap()
val modelsDir = getModelsDirectory(context!!)
if (!modelsDir.exists()) return localeMap
- for (localeFolder in modelsDir.listFiles()) {
+ for (localeFolder in modelsDir.listFiles()!!) {
if (!localeFolder.isDirectory) continue
val locale = Locale.forLanguageTag(localeFolder.name)
val models: MutableList = ArrayList()
- for (modelFolder in localeFolder.listFiles()) {
+ for (modelFolder in localeFolder.listFiles()!!) {
if (!modelFolder.isDirectory) continue
val name = modelFolder.name
val model = LocalModel(modelFolder.absolutePath, locale, name)
@@ -105,10 +77,10 @@ object Tools {
val models: MutableList = ArrayList()
val modelsDir = getModelsDirectory(context!!)
if (!modelsDir.exists()) return models
- for (localeFolder in modelsDir.listFiles()) {
+ for (localeFolder in modelsDir.listFiles()!!) {
if (!localeFolder.isDirectory) continue
val locale = Locale.forLanguageTag(localeFolder.name)
- for (modelFolder in localeFolder.listFiles()) {
+ for (modelFolder in localeFolder.listFiles()!!) {
if (!modelFolder.isDirectory) continue
val name = modelFolder.name
val model = LocalModel(modelFolder.absolutePath, locale, name)
@@ -118,34 +90,6 @@ object Tools {
return models
}
- @JvmStatic
- fun getModelsData(context: Context?): List {
- val data: MutableList = ArrayList()
- val installedModels = getInstalledModelsMap(context)
- for (link in ModelLink.values()) {
- var found = false
- if (installedModels.containsKey(link.locale)) {
- val localeModels = installedModels[link.locale]!!
- for (i in localeModels.indices) {
- val model = localeModels[i]
- if (model.filename == link.filename) {
- data.add(ModelsAdapterLocalData(link, model))
- localeModels.removeAt(i)
- found = true
- break
- }
- }
- }
- if (!found) data.add(ModelsAdapterLocalData(link))
- }
- for (models in installedModels.values) {
- for (model in models) {
- data.add(ModelsAdapterLocalData(model))
- }
- }
- return data
- }
-
@JvmStatic
fun getModelForLink(modelLink: ModelLink, context: Context?): LocalModel? {
val modelsDir = getModelsDirectory(context!!)
diff --git a/app/src/main/java/com/elishaazaria/sayboard/data/LocalModel.kt b/app/src/main/java/com/elishaazaria/sayboard/data/LocalModel.kt
index 5813f56..0d96e63 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/data/LocalModel.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/data/LocalModel.kt
@@ -3,21 +3,7 @@ package com.elishaazaria.sayboard.data
import java.io.Serializable
import java.util.*
-class LocalModel(val path: String?, val locale: Locale?, val filename: String?) : Serializable {
- override fun equals(o: Any?): Boolean {
- if (this === o) return true
- if (o == null || javaClass != o.javaClass) return false
- val model = o as LocalModel
- if (path != model.path) return false
- return if (locale != model.locale) false else filename == model.filename
- }
-
- override fun hashCode(): Int {
- var result = path?.hashCode() ?: 0
- result = 31 * result + (locale?.hashCode() ?: 0)
- result = 31 * result + (filename?.hashCode() ?: 0)
- return result
- }
+data class LocalModel(val path: String, val locale: Locale, val filename: String) : Serializable {
companion object {
fun serialize(model: LocalModel): String {
diff --git a/app/src/main/java/com/elishaazaria/sayboard/data/SimpleEnums.kt b/app/src/main/java/com/elishaazaria/sayboard/data/SimpleEnums.kt
new file mode 100644
index 0000000..e7fe087
--- /dev/null
+++ b/app/src/main/java/com/elishaazaria/sayboard/data/SimpleEnums.kt
@@ -0,0 +1,28 @@
+package com.elishaazaria.sayboard.data
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.elishaazaria.sayboard.R
+import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
+
+enum class KeepScreenAwakeMode {
+ NEVER, WHEN_LISTENING, WHEN_OPEN;
+
+ companion object {
+ @Composable
+ fun listEntries() = listPrefEntries {
+ entry(
+ key = NEVER,
+ label = stringResource(id = R.string.p_keep_screen_awake_mode_never),
+ )
+ entry(
+ key = WHEN_LISTENING,
+ label = stringResource(id = R.string.p_keep_screen_awake_mode_when_listening),
+ )
+ entry(
+ key = WHEN_OPEN,
+ label = stringResource(id = R.string.p_keep_screen_awake_mode_when_open),
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/FileDownloadService.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/FileDownloadService.kt
index 98fcac9..3d43250 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/downloader/FileDownloadService.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/FileDownloadService.kt
@@ -1,9 +1,12 @@
package com.elishaazaria.sayboard.downloader
+import android.Manifest
import android.app.Service
import android.content.Intent
+import android.content.pm.PackageManager
import android.os.IBinder
import android.util.Log
+import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.elishaazaria.sayboard.Constants
@@ -11,9 +14,11 @@ import com.elishaazaria.sayboard.Constants.getDirectoryForModel
import com.elishaazaria.sayboard.Constants.getTemporaryDownloadLocation
import com.elishaazaria.sayboard.Constants.getTemporaryUnzipLocation
import com.elishaazaria.sayboard.R
+import com.elishaazaria.sayboard.Tools
import com.elishaazaria.sayboard.downloader.messages.*
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
import java.io.*
import java.net.URL
import java.util.*
@@ -27,8 +32,11 @@ class FileDownloadService : Service() {
private var currentModel: ModelInfo? = null
private val queuedModels: Queue = LinkedList()
private var currentState = State.NONE
- private var downloadProgress = 0
- private var unzipProgress = 0
+ private var downloadProgress = 0f
+ private var unzipProgress = 0f
+
+ private var interrupt = false
+
override fun onCreate() {
super.onCreate()
EventBus.getDefault().register(this)
@@ -47,8 +55,8 @@ class FileDownloadService : Service() {
?: return START_NOT_STICKY
Log.d(TAG, "Got message $modelInfo")
queuedModels.add(modelInfo)
- executor.execute { main() }
sendEnqueued(modelInfo)
+ executor.execute { main() }
startForeground(notificationId, notificationBuilder.build())
return START_NOT_STICKY
}
@@ -57,8 +65,8 @@ class FileDownloadService : Service() {
currentModel = queuedModels.poll()
val currentModelS = currentModel ?: return
Log.d(TAG, "Started processing $currentModelS")
- downloadProgress = 0
- unzipProgress = 0
+ downloadProgress = 0f
+ unzipProgress = 0f
setState(State.NONE)
val downloadLocation = getTemporaryDownloadLocation(
applicationContext, currentModelS.filename
@@ -74,6 +82,10 @@ class FileDownloadService : Service() {
mainEnd()
return
}
+ if (interrupt) {
+ interrupted(downloadLocation)
+ return
+ }
Log.d(TAG, "Finished downloading")
try {
unzipFile(downloadLocation)
@@ -83,6 +95,9 @@ class FileDownloadService : Service() {
mainEnd()
return
}
+ if (interrupt) {
+ interrupted(downloadLocation)
+ }
Log.d(TAG, "Finished unzipping")
downloadLocation.delete()
setState(State.FINISHED)
@@ -90,9 +105,29 @@ class FileDownloadService : Service() {
mainEnd()
}
+ private fun interrupted(downloadLocation: File) {
+ if (downloadLocation.exists()) {
+ downloadLocation.delete()
+ }
+ val unzipFolder = getTemporaryUnzipLocation(this)
+ if (unzipFolder.exists()) {
+ Tools.deleteRecursive(unzipFolder)
+ }
+
+ Log.d(TAG, "Download Canceled")
+ EventBus.getDefault()
+ .post(
+ CancelFinished(currentModel!!)
+ )
+ updateNotification()
+
+ interrupt = false
+ mainEnd()
+ }
+
private fun mainEnd() {
- downloadProgress = 0
- unzipProgress = 0
+ downloadProgress = 0f
+ unzipProgress = 0f
currentState = State.NONE
currentModel = null
if (queuedModels.isEmpty()) stopForeground(false)
@@ -106,22 +141,24 @@ class FileDownloadService : Service() {
urlConnection.connect()
val lengthOfFile = urlConnection.contentLength
Log.d("TAG", "Length of file: $lengthOfFile")
- setDownloadProgress(0)
+ setDownloadProgress(0f)
val input: InputStream = BufferedInputStream(url.openStream())
val output: OutputStream = FileOutputStream(downloadLocation)
val data = ByteArray(1024) // 1mb
- var total: Long = 0
+ var total: Float = 0f
var count: Int
- while (input.read(data).also { count = it } != -1) {
- total += count.toLong()
- setDownloadProgress((total * PROGRESS_MAX / lengthOfFile).toInt())
+ while (input.read(data).also { count = it } != -1 && !interrupt) {
+ total += count
+ setDownloadProgress(total / lengthOfFile)
output.write(data, 0, count)
}
output.flush()
output.close()
input.close()
- setDownloadProgress(PROGRESS_MAX)
- setState(State.DOWNLOAD_FINISHED)
+ if (!interrupt) {
+ setDownloadProgress(1f)
+ setState(State.DOWNLOAD_FINISHED)
+ }
}
@Throws(IOException::class)
@@ -139,13 +176,13 @@ class FileDownloadService : Service() {
downloadLocation,
currentUnzipFolder,
unzipDestination
- ) { d: Double -> setUnzipProgress((d * PROGRESS_MAX).toInt()) }
- setUnzipProgress(PROGRESS_MAX)
+ ) { d: Double -> setUnzipProgress(d.toFloat()) }
+ setUnzipProgress(1f)
setState(State.UNZIP_FINISHED)
}
- private var lastDownloadProgress = 0
- private var lastUnzipProgress = 0
+ private var lastDownloadProgress = 0f
+ private var lastUnzipProgress = 0f
private var lastState: State = State.NONE
private var lastUpdateTime: Long = 0
private fun updateNotification() {
@@ -154,8 +191,8 @@ class FileDownloadService : Service() {
val currentTime = System.currentTimeMillis()
if (currentTime - lastUpdateTime < minUpdateTime) {
if (lastState == currentState) { // it's a progress update
- if (!(downloadProgress == PROGRESS_MAX && unzipProgress == 0) &&
- unzipProgress != PROGRESS_MAX
+ if (!(downloadProgress == 1f && unzipProgress == 0f) &&
+ unzipProgress != 1f
) { // it's not the last progress update
lastUpdateTime = currentTime
return
@@ -166,21 +203,36 @@ class FileDownloadService : Service() {
when (currentState) {
State.NONE -> notificationBuilder.setContentText(getString(R.string.notification_download_content_unknown))
.setProgress(0, 0, true)
+
State.DOWNLOAD_STARTED, State.DOWNLOAD_FINISHED -> notificationBuilder.setContentText(
getString(R.string.notification_download_content_downloading)
)
- .setProgress(PROGRESS_MAX, downloadProgress, false)
+ .setProgress(PROGRESS_MAX, (downloadProgress * PROGRESS_MAX).toInt(), false)
+
State.UNZIP_STARTED, State.UNZIP_FINISHED -> notificationBuilder.setContentText(
getString(R.string.notification_download_content_unzipping)
)
- .setProgress(PROGRESS_MAX, unzipProgress, false)
+ .setProgress(PROGRESS_MAX, (unzipProgress * PROGRESS_MAX).toInt(), false)
+
State.FINISHED -> notificationBuilder.setContentText(getString(R.string.notification_download_content_finished))
.setProgress(0, 0, false)
+
State.ERROR -> notificationBuilder.setContentText(getString(R.string.notification_download_content_error))
.setProgress(0, 0, false)
- State.QUEUED -> TODO()
+
+ State.CANCELED -> notificationBuilder.setContentText("Downloading Canceled")
+ .setProgress(0, 0, false)
+
+ else -> {}
+ }
+
+ if (ActivityCompat.checkSelfPermission(
+ this,
+ Manifest.permission.POST_NOTIFICATIONS
+ ) == PackageManager.PERMISSION_GRANTED
+ ) {
+ notificationManager.notify(notificationId, notificationBuilder.build())
}
- notificationManager.notify(notificationId, notificationBuilder.build())
lastDownloadProgress = downloadProgress
lastUnzipProgress = unzipProgress
lastState = currentState
@@ -192,13 +244,13 @@ class FileDownloadService : Service() {
updateNotification()
}
- private fun setDownloadProgress(progress: Int) {
+ private fun setDownloadProgress(progress: Float) {
downloadProgress = progress
EventBus.getDefault().post(DownloadProgress(currentModel!!, downloadProgress))
updateNotification()
}
- private fun setUnzipProgress(progress: Int) {
+ private fun setUnzipProgress(progress: Float) {
unzipProgress = progress
EventBus.getDefault().post(UnzipProgress(currentModel!!, unzipProgress))
updateNotification()
@@ -214,10 +266,34 @@ class FileDownloadService : Service() {
EventBus.getDefault().post(DownloadState(modelInfo, State.QUEUED))
}
- @Subscribe
+ @Subscribe(threadMode = ThreadMode.MAIN)
fun handleStatusQuery(event: StatusQuery?) {
EventBus.getDefault()
- .post(Status(currentModel!!, queuedModels, downloadProgress, unzipProgress, currentState))
+ .post(Status(currentModel, queuedModels, downloadProgress, unzipProgress, currentState))
+ }
+
+ @Subscribe
+ fun handleCancelPending(event: CancelPending) {
+ if (queuedModels.remove(event.info)) {
+ EventBus.getDefault()
+ .post(
+ Status(
+ currentModel,
+ queuedModels,
+ downloadProgress,
+ unzipProgress,
+ currentState
+ )
+ )
+ }
+ }
+
+ @Subscribe
+ fun handleCancelCurrent(event: CancelCurrent) {
+ if (currentModel == event.info) {
+ interrupt = true
+ setState(State.CANCELED)
+ }
}
override fun onBind(intent: Intent): IBinder? {
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/CancelCurrent.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/CancelCurrent.kt
new file mode 100644
index 0000000..424b279
--- /dev/null
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/CancelCurrent.kt
@@ -0,0 +1,3 @@
+package com.elishaazaria.sayboard.downloader.messages
+
+data class CancelCurrent(val info: ModelInfo)
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/CancelFinished.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/CancelFinished.kt
new file mode 100644
index 0000000..f32c553
--- /dev/null
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/CancelFinished.kt
@@ -0,0 +1,3 @@
+package com.elishaazaria.sayboard.downloader.messages
+
+data class CancelFinished(val info: ModelInfo)
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/CancelPending.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/CancelPending.kt
new file mode 100644
index 0000000..993db5d
--- /dev/null
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/CancelPending.kt
@@ -0,0 +1,3 @@
+package com.elishaazaria.sayboard.downloader.messages
+
+data class CancelPending(val info: ModelInfo)
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadError.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadError.kt
index 6740c36..b8544fa 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadError.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadError.kt
@@ -1,3 +1,3 @@
package com.elishaazaria.sayboard.downloader.messages
-class DownloadError(val info: ModelInfo, val message: String)
\ No newline at end of file
+data class DownloadError(val info: ModelInfo, val message: String)
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadProgress.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadProgress.kt
index dd14ba1..f33eb5a 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadProgress.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadProgress.kt
@@ -1,3 +1,3 @@
package com.elishaazaria.sayboard.downloader.messages
-class DownloadProgress(val info: ModelInfo, val progress: Int)
\ No newline at end of file
+data class DownloadProgress(val info: ModelInfo, val progress: Float)
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadState.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadState.kt
index ed4f6e8..bdbc27f 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadState.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/DownloadState.kt
@@ -1,3 +1,3 @@
package com.elishaazaria.sayboard.downloader.messages
-class DownloadState(val info: ModelInfo, val state: State)
\ No newline at end of file
+data class DownloadState(val info: ModelInfo, val state: State)
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/ModelInfo.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/ModelInfo.kt
index a713738..50fba3c 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/ModelInfo.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/ModelInfo.kt
@@ -2,12 +2,12 @@ package com.elishaazaria.sayboard.downloader.messages
import java.util.*
-class ModelInfo(val url: String, val filename: String, val locale: Locale) {
- override fun toString(): String {
- return "ModelInfo{" +
- "url='" + url + '\'' +
- ", filename='" + filename + '\'' +
- ", locale=" + locale +
- '}'
- }
+data class ModelInfo(val url: String, val filename: String, val locale: Locale) {
+// override fun toString(): String {
+// return "ModelInfo{" +
+// "url='" + url + '\'' +
+// ", filename='" + filename + '\'' +
+// ", locale=" + locale +
+// '}'
+// }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/State.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/State.kt
index 809a4de..1fd2546 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/State.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/State.kt
@@ -1,5 +1,5 @@
package com.elishaazaria.sayboard.downloader.messages
enum class State {
- NONE, QUEUED, DOWNLOAD_STARTED, DOWNLOAD_FINISHED, UNZIP_STARTED, UNZIP_FINISHED, FINISHED, ERROR
+ NONE, QUEUED, DOWNLOAD_STARTED, DOWNLOAD_FINISHED, UNZIP_STARTED, UNZIP_FINISHED, FINISHED, ERROR, CANCELED
}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/Status.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/Status.kt
index 01cef2f..34547a7 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/Status.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/Status.kt
@@ -2,10 +2,10 @@ package com.elishaazaria.sayboard.downloader.messages
import java.util.*
-class Status(
- val current: ModelInfo,
+data class Status(
+ val current: ModelInfo?,
val queued: Queue,
- val downloadProgress: Int,
- val unzipProgress: Int,
+ val downloadProgress: Float,
+ val unzipProgress: Float,
val state: State
)
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/UnzipProgress.kt b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/UnzipProgress.kt
index 871e298..c4fba7b 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/UnzipProgress.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/downloader/messages/UnzipProgress.kt
@@ -1,3 +1,3 @@
package com.elishaazaria.sayboard.downloader.messages
-class UnzipProgress(val info: ModelInfo, val progress: Int)
\ No newline at end of file
+data class UnzipProgress(val info: ModelInfo, val progress: Float)
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/ime/IME.kt b/app/src/main/java/com/elishaazaria/sayboard/ime/IME.kt
index 42823f0..5fc7896 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/ime/IME.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/ime/IME.kt
@@ -32,14 +32,15 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import com.elishaazaria.sayboard.BuildConfig
import com.elishaazaria.sayboard.R
-import com.elishaazaria.sayboard.preferences.LogicPreferences
-import com.elishaazaria.sayboard.preferences.LogicPreferences.isAutoSwitchBack
-import com.elishaazaria.sayboard.preferences.LogicPreferences.keepScreenAwake
+import com.elishaazaria.sayboard.data.KeepScreenAwakeMode
+import com.elishaazaria.sayboard.sayboardPreferenceModel
import org.vosk.LibVosk
import org.vosk.LogLevel
import org.vosk.android.RecognitionListener
class IME : InputMethodService(), RecognitionListener, LifecycleOwner {
+ private val prefs by sayboardPreferenceModel()
+
private val lifecycleRegistry = LifecycleRegistry(this)
private lateinit var editorInfo: EditorInfo
private lateinit var viewManager: ViewManager
@@ -82,7 +83,7 @@ class IME : InputMethodService(), RecognitionListener, LifecycleOwner {
}
modelManager.initializeRecognizer()
viewManager.refresh()
- setKeepScreenOn(keepScreenAwake == LogicPreferences.KEEP_SCREEN_AWAKE_WHEN_OPEN)
+ setKeepScreenOn(prefs.logicKeepScreenAwake.get() == KeepScreenAwakeMode.WHEN_OPEN)
}
override fun onFinishInputView(finishingInput: Boolean) {
@@ -90,7 +91,7 @@ class IME : InputMethodService(), RecognitionListener, LifecycleOwner {
// text input has ended
setKeepScreenOn(false)
modelManager.stop()
- if (isAutoSwitchBack) {
+ if (prefs.logicAutoSwitchBack.get()) {
// switch back
actionManager.switchToLastIme(false)
}
@@ -103,20 +104,17 @@ class IME : InputMethodService(), RecognitionListener, LifecycleOwner {
if (modelManager.isRunning) {
if (modelManager.isPaused) {
modelManager.pause(false)
- if (keepScreenAwake == LogicPreferences.KEEP_SCREEN_AWAKE_WHEN_LISTENING) setKeepScreenOn(
- true
- )
+ if (prefs.logicKeepScreenAwake.get() == KeepScreenAwakeMode.WHEN_LISTENING)
+ setKeepScreenOn(true)
} else {
modelManager.pause(true)
- if (keepScreenAwake == LogicPreferences.KEEP_SCREEN_AWAKE_WHEN_LISTENING) setKeepScreenOn(
- false
- )
+ if (prefs.logicKeepScreenAwake.get() == KeepScreenAwakeMode.WHEN_LISTENING)
+ setKeepScreenOn(false)
}
} else {
modelManager.start()
- if (keepScreenAwake == LogicPreferences.KEEP_SCREEN_AWAKE_WHEN_LISTENING) setKeepScreenOn(
- true
- )
+ if (prefs.logicKeepScreenAwake.get() == KeepScreenAwakeMode.WHEN_LISTENING)
+ setKeepScreenOn(true)
}
}
@@ -281,11 +279,11 @@ class IME : InputMethodService(), RecognitionListener, LifecycleOwner {
override fun onError(e: Exception) {
viewManager.errorMessageLD.postValue(R.string.mic_error_recognizer_error)
- viewManager.stateLD.postValue(ViewManager.Companion.STATE_ERROR)
+ viewManager.stateLD.postValue(ViewManager.STATE_ERROR)
}
override fun onTimeout() {
- viewManager.stateLD.postValue(ViewManager.Companion.STATE_PAUSED)
+ viewManager.stateLD.postValue(ViewManager.STATE_PAUSED)
}
companion object {
diff --git a/app/src/main/java/com/elishaazaria/sayboard/ime/ModelManager.kt b/app/src/main/java/com/elishaazaria/sayboard/ime/ModelManager.kt
index 33fb434..5a586f3 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/ime/ModelManager.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/ime/ModelManager.kt
@@ -5,18 +5,18 @@ import android.content.pm.PackageManager
import androidx.core.app.ActivityCompat
import androidx.lifecycle.Observer
import com.elishaazaria.sayboard.R
+import com.elishaazaria.sayboard.Tools
import com.elishaazaria.sayboard.ime.recognizers.RecognizerSource
import com.elishaazaria.sayboard.ime.recognizers.providers.RecognizerSourceProvider
import com.elishaazaria.sayboard.ime.recognizers.providers.VoskLocalProvider
import com.elishaazaria.sayboard.ime.recognizers.providers.VoskServerProvider
-import com.elishaazaria.sayboard.preferences.LogicPreferences.isListenImmediately
-import com.elishaazaria.sayboard.preferences.LogicPreferences.isWeakRefModel
-import com.elishaazaria.sayboard.preferences.ModelPreferences
+import com.elishaazaria.sayboard.sayboardPreferenceModel
import java.io.IOException
import java.util.concurrent.Executor
import java.util.concurrent.Executors
class ModelManager(private val ime: IME, private val viewManager: ViewManager) {
+ private val prefs by sayboardPreferenceModel()
private var speechService: MySpeechService? = null
var isRunning = false
private set
@@ -28,7 +28,7 @@ class ModelManager(private val ime: IME, private val viewManager: ViewManager) {
fun initializeRecognizer() {
if (recognizerSources.size == 0) return
val onLoaded = Observer { r: RecognizerSource? ->
- if (isListenImmediately) {
+ if (prefs.logicListenImmediately.get()) {
start() // execute after initialize
}
}
@@ -39,7 +39,7 @@ class ModelManager(private val ime: IME, private val viewManager: ViewManager) {
}
private fun stopRecognizerSource() {
- currentRecognizerSource.close(isWeakRefModel)
+ currentRecognizerSource.close(prefs.logicWeakRefToModel.get())
currentRecognizerSource.stateLD.removeObserver(viewManager)
}
@@ -57,7 +57,7 @@ class ModelManager(private val ime: IME, private val viewManager: ViewManager) {
if (isRunning || speechService != null) {
speechService!!.stop()
}
- viewManager.stateLD.postValue(ViewManager.Companion.STATE_LISTENING)
+ viewManager.stateLD.postValue(ViewManager.STATE_LISTENING)
try {
val recognizer = currentRecognizerSource.recognizer
if (ActivityCompat.checkSelfPermission(
@@ -71,7 +71,7 @@ class ModelManager(private val ime: IME, private val viewManager: ViewManager) {
speechService!!.startListening(ime)
} catch (e: IOException) {
viewManager.errorMessageLD.postValue(R.string.mic_error_mic_in_use)
- viewManager.stateLD.postValue(ViewManager.Companion.STATE_ERROR)
+ viewManager.stateLD.postValue(ViewManager.STATE_ERROR)
}
isRunning = true
}
@@ -80,7 +80,7 @@ class ModelManager(private val ime: IME, private val viewManager: ViewManager) {
init {
sourceProviders.add(VoskLocalProvider(ime))
- if (ModelPreferences.VOSK_SERVER_ENABLED) {
+ if (Tools.VOSK_SERVER_ENABLED) {
sourceProviders.add(VoskServerProvider())
}
for (provider in sourceProviders) {
@@ -88,7 +88,7 @@ class ModelManager(private val ime: IME, private val viewManager: ViewManager) {
}
if (recognizerSources.size == 0) {
viewManager.errorMessageLD.postValue(R.string.mic_error_no_recognizers)
- viewManager.stateLD.postValue(ViewManager.Companion.STATE_ERROR)
+ viewManager.stateLD.postValue(ViewManager.STATE_ERROR)
} else {
currentRecognizerSourceIndex = 0
initializeRecognizer()
@@ -100,9 +100,9 @@ class ModelManager(private val ime: IME, private val viewManager: ViewManager) {
speechService!!.setPause(checked)
pausedState = checked
if (checked) {
- viewManager.stateLD.postValue(ViewManager.Companion.STATE_PAUSED)
+ viewManager.stateLD.postValue(ViewManager.STATE_PAUSED)
} else {
- viewManager.stateLD.postValue(ViewManager.Companion.STATE_LISTENING)
+ viewManager.stateLD.postValue(ViewManager.STATE_LISTENING)
}
} else {
pausedState = false
diff --git a/app/src/main/java/com/elishaazaria/sayboard/ime/ViewManager.kt b/app/src/main/java/com/elishaazaria/sayboard/ime/ViewManager.kt
index a2be8d6..605bc8f 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/ime/ViewManager.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/ime/ViewManager.kt
@@ -12,17 +12,16 @@ import android.widget.ImageButton
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.ContextCompat
import androidx.core.widget.TextViewCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.elishaazaria.sayboard.R
import com.elishaazaria.sayboard.ime.recognizers.RecognizerState
-import com.elishaazaria.sayboard.preferences.UIPreferences.getBackgroundColor
-import com.elishaazaria.sayboard.preferences.UIPreferences.getForegroundColor
-import com.elishaazaria.sayboard.preferences.UIPreferences.screenHeightLandscape
-import com.elishaazaria.sayboard.preferences.UIPreferences.screenHeightPortrait
+import com.elishaazaria.sayboard.sayboardPreferenceModel
class ViewManager(private val ime: IME) : Observer {
+ private val prefs by sayboardPreferenceModel()
lateinit var root: ConstraintLayout
private set
private lateinit var micButton: ImageButton
@@ -84,8 +83,26 @@ class ViewManager(private val ime: IME) : Observer {
private fun setUpTheme() {
val dark =
ime.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
- val foreground = getForegroundColor(dark, ime)
- val background = getBackgroundColor(dark)
+
+ val foreground = if (dark) {
+ if (prefs.uiNightForegroundMaterialYou.get()) {
+ ContextCompat.getColor(ime, R.color.materialYouForeground)
+ } else {
+ prefs.uiNightForeground.get()
+ }
+ } else {
+ if (prefs.uiDayForegroundMaterialYou.get()) {
+ ContextCompat.getColor(ime, R.color.materialYouForeground)
+ } else {
+ prefs.uiDayForeground.get()
+ }
+ }
+ val background = if (dark) {
+ prefs.uiNightBackground.get()
+ } else {
+ prefs.uiDayBackground.get()
+ }
+
if (currentForeground == foreground && currentBackground == background) return
currentForeground = foreground
currentBackground = background
@@ -107,9 +124,9 @@ class ViewManager(private val ime: IME) : Observer {
val screenHeight = ime.resources.displayMetrics.heightPixels
val percent: Float
percent = if (landscape) {
- screenHeightLandscape
+ prefs.uiKeyboardHeightLandscape.get()
} else {
- screenHeightPortrait
+ prefs.uiKeyboardHeightPortrait.get()
}
val height = (percent * screenHeight).toInt()
Log.d("ViewManager", "Screen height: $screenHeight, height: $height")
@@ -138,21 +155,25 @@ class ViewManager(private val ime: IME) : Observer {
icon = R.drawable.ic_settings_voice
enabled = false
}
+
STATE_READY, STATE_PAUSED -> {
text = R.string.mic_info_ready
icon = R.drawable.ic_mic_none
enabled = true
}
+
STATE_LISTENING -> {
text = R.string.mic_info_recording
icon = R.drawable.ic_mic
enabled = true
}
+
STATE_ERROR -> {
text = R.string.mic_info_error
icon = R.drawable.ic_mic_off
enabled = false
}
+
else -> return
}
resultView.setText(text)
@@ -188,6 +209,7 @@ class ViewManager(private val ime: IME) : Observer {
RecognizerState.CLOSED, RecognizerState.NONE -> stateLD.setValue(
STATE_INITIAL
)
+
RecognizerState.LOADING -> stateLD.setValue(STATE_LOADING)
RecognizerState.READY -> stateLD.setValue(STATE_READY)
RecognizerState.IN_RAM -> stateLD.setValue(STATE_PAUSED)
diff --git a/app/src/main/java/com/elishaazaria/sayboard/ime/recognizers/providers/VoskServerProvider.kt b/app/src/main/java/com/elishaazaria/sayboard/ime/recognizers/providers/VoskServerProvider.kt
index 9e4c713..30ed4df 100644
--- a/app/src/main/java/com/elishaazaria/sayboard/ime/recognizers/providers/VoskServerProvider.kt
+++ b/app/src/main/java/com/elishaazaria/sayboard/ime/recognizers/providers/VoskServerProvider.kt
@@ -1,13 +1,11 @@
package com.elishaazaria.sayboard.ime.recognizers.providers
import com.elishaazaria.sayboard.ime.recognizers.RecognizerSource
-import com.elishaazaria.sayboard.ime.recognizers.VoskServer
-import com.elishaazaria.sayboard.preferences.ModelPreferences.voskServers
class VoskServerProvider : RecognizerSourceProvider {
override fun loadSources(recognizerSources: MutableList) {
- for (voskServer in voskServers) {
- recognizerSources.add(VoskServer(voskServer))
- }
+// for (voskServer in voskServers) {
+// recognizerSources.add(VoskServer(voskServer))
+// }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/preferences/LogicPreferences.kt b/app/src/main/java/com/elishaazaria/sayboard/preferences/LogicPreferences.kt
deleted file mode 100644
index be9f169..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/preferences/LogicPreferences.kt
+++ /dev/null
@@ -1,48 +0,0 @@
-package com.elishaazaria.sayboard.preferences
-
-import com.elishaazaria.sayboard.AppCtx.getStringRes
-import com.elishaazaria.sayboard.AppCtx.getBoolRes
-import com.elishaazaria.sayboard.AppCtx.appCtx
-import com.elishaazaria.sayboard.AppCtx.getIntegerRes
-import com.elishaazaria.sayboard.preferences.MyPreferences
-import com.elishaazaria.sayboard.AppCtx
-import com.elishaazaria.sayboard.R
-import com.elishaazaria.sayboard.preferences.LogicPreferences
-import com.elishaazaria.sayboard.data.VoskServerData
-import android.content.SharedPreferences
-import android.os.Build
-import com.elishaazaria.sayboard.preferences.UIPreferences
-import androidx.core.content.ContextCompat
-
-object LogicPreferences {
- const val KEEP_SCREEN_AWAKE_NEVER = 0
- const val KEEP_SCREEN_AWAKE_WHEN_LISTENING = 1
- const val KEEP_SCREEN_AWAKE_WHEN_OPEN = 2
- @JvmStatic
- val keepScreenAwake: Int
- get() {
- val `val` = MyPreferences.sharedPref
- .getString(
- getStringRes(R.string.pref_logic_keep_screen_awake_l),
- getStringRes(R.string.pref_keep_awake_default)
- )
- if (`val` == getStringRes(R.string.value_keep_awake_never)) return KEEP_SCREEN_AWAKE_NEVER
- if (`val` == getStringRes(R.string.value_keep_awake_when_listening)) return KEEP_SCREEN_AWAKE_WHEN_LISTENING
- return if (`val` == getStringRes(R.string.value_keep_awake_when_open)) KEEP_SCREEN_AWAKE_WHEN_OPEN else -1
- }
- val isListenImmediately: Boolean
- get() = MyPreferences.sharedPref.getBoolean(
- getStringRes(R.string.pref_logic_listen_immediately_b),
- getBoolRes(R.bool.pref_listen_immediately_default)
- )
- val isAutoSwitchBack: Boolean
- get() = MyPreferences.sharedPref.getBoolean(
- getStringRes(R.string.pref_logic_auto_switch_back_b),
- getBoolRes(R.bool.pref_auto_switch_back_default)
- )
- val isWeakRefModel: Boolean
- get() = MyPreferences.sharedPref.getBoolean(
- getStringRes(R.string.pref_logic_weak_ref_model_b),
- getBoolRes(R.bool.pref_weak_ref_model_default)
- )
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/preferences/ModelPreferences.kt b/app/src/main/java/com/elishaazaria/sayboard/preferences/ModelPreferences.kt
deleted file mode 100644
index a77427b..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/preferences/ModelPreferences.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-package com.elishaazaria.sayboard.preferences
-
-import com.elishaazaria.sayboard.AppCtx.getStringRes
-import com.elishaazaria.sayboard.AppCtx.getBoolRes
-import com.elishaazaria.sayboard.AppCtx.appCtx
-import com.elishaazaria.sayboard.AppCtx.getIntegerRes
-import com.elishaazaria.sayboard.preferences.MyPreferences
-import com.elishaazaria.sayboard.AppCtx
-import com.elishaazaria.sayboard.R
-import com.elishaazaria.sayboard.preferences.LogicPreferences
-import com.elishaazaria.sayboard.data.VoskServerData
-import android.content.SharedPreferences
-import android.os.Build
-import com.elishaazaria.sayboard.preferences.UIPreferences
-import androidx.core.content.ContextCompat
-import java.util.*
-import kotlin.collections.HashSet
-
-object ModelPreferences {
- const val VOSK_SERVER_ENABLED = false
- @JvmStatic
- var voskServers: List
- get() {
- val set = MyPreferences.sharedPref.getStringSet(
- getStringRes(R.string.pref_models_vosk_servers_set),
- null
- ) ?: return ArrayList()
- val list = ArrayList()
- for (stringData in set) {
- val data = VoskServerData.deserialize(stringData)
- if (data != null) {
- list.add(data)
- }
- }
- Collections.sort(list)
- return list
- }
- set(servers) {
- val set: MutableSet = HashSet()
- for (data in servers) {
- set.add(VoskServerData.serialize(data))
- }
- MyPreferences.sharedPref.edit()
- .putStringSet(getStringRes(R.string.pref_models_vosk_servers_set), set).apply()
- }
-
- fun addToVoskServers(data: VoskServerData) {
- val set: MutableSet = HashSet(
- MyPreferences.sharedPref.getStringSet(
- getStringRes(R.string.pref_models_vosk_servers_set),
- HashSet()
- )
- )
- set.add(VoskServerData.serialize(data))
- MyPreferences.sharedPref.edit()
- .putStringSet(getStringRes(R.string.pref_models_vosk_servers_set), set).apply()
- }
-
- fun removeFromVoskServers(data: VoskServerData) {
- val set: MutableSet = HashSet(
- MyPreferences.sharedPref.getStringSet(
- getStringRes(R.string.pref_models_vosk_servers_set),
- HashSet()
- )
- )
- set.remove(VoskServerData.serialize(data))
- MyPreferences.sharedPref.edit()
- .putStringSet(getStringRes(R.string.pref_models_vosk_servers_set), set).apply()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/preferences/MyPreferences.kt b/app/src/main/java/com/elishaazaria/sayboard/preferences/MyPreferences.kt
deleted file mode 100644
index 1cdd777..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/preferences/MyPreferences.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.elishaazaria.sayboard.preferences
-
-import android.content.Context
-import android.content.SharedPreferences
-import com.elishaazaria.sayboard.AppCtx.appCtx
-import com.elishaazaria.sayboard.AppCtx.getStringRes
-import com.elishaazaria.sayboard.R
-
-object MyPreferences {
- private var mySharedPref: SharedPreferences? = null
-
- val sharedPref: SharedPreferences
- get() {
- if (mySharedPref == null) {
- mySharedPref = appCtx!!.getSharedPreferences(
- getStringRes(R.string.main_shared_pref), Context.MODE_PRIVATE
- )
- }
- return mySharedPref!!
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/preferences/UIPreferences.kt b/app/src/main/java/com/elishaazaria/sayboard/preferences/UIPreferences.kt
deleted file mode 100644
index 88f1495..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/preferences/UIPreferences.kt
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.elishaazaria.sayboard.preferences
-
-import android.content.Context
-import android.os.Build
-import androidx.core.content.ContextCompat
-import com.elishaazaria.sayboard.AppCtx.getBoolRes
-import com.elishaazaria.sayboard.AppCtx.getIntegerRes
-import com.elishaazaria.sayboard.AppCtx.getStringRes
-import com.elishaazaria.sayboard.R
-
-object UIPreferences {
- fun isForegroundMaterialYou(dark: Boolean): Boolean {
- return if (!dark) {
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && MyPreferences.sharedPref
- .getBoolean(
- getStringRes(R.string.pref_ui_light_foreground_material_you_b),
- getBoolRes(R.bool.pref_light_foreground_material_you_default)
- )
- } else {
- Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && MyPreferences.sharedPref
- .getBoolean(
- getStringRes(R.string.pref_ui_dark_foreground_material_you),
- getBoolRes(R.bool.pref_dark_foreground_material_you_default)
- )
- }
- }
-
- @JvmStatic
- fun getForegroundColor(dark: Boolean, ctx: Context?): Int {
- if (isForegroundMaterialYou(dark)) {
- return ContextCompat.getColor(ctx!!, R.color.materialYouForeground)
- }
- return if (!dark) {
- MyPreferences.sharedPref
- .getInt(
- getStringRes(R.string.pref_ui_light_foreground_c),
- getIntegerRes(R.integer.pref_light_foreground_color_default)
- )
- } else {
- MyPreferences.sharedPref
- .getInt(
- getStringRes(R.string.pref_ui_dark_foreground_c),
- getIntegerRes(R.integer.pref_dark_foreground_color_default)
- )
- }
- }
-
- @JvmStatic
- fun getBackgroundColor(dark: Boolean): Int {
- return if (!dark) {
- MyPreferences.sharedPref
- .getInt(
- getStringRes(R.string.pref_ui_light_background_c),
- getIntegerRes(R.integer.pref_light_background_color_default)
- )
- } else {
- MyPreferences.sharedPref
- .getInt(
- getStringRes(R.string.pref_ui_dark_background_c),
- getIntegerRes(R.integer.pref_dark_background_color_default)
- )
- }
- }
-
- @JvmStatic
- val screenHeightLandscape: Float
- get() = MyPreferences.sharedPref.getInt(
- getStringRes(R.string.pref_ui_keyboard_height_landscape_i),
- getIntegerRes(R.integer.pref_keyboard_height_landscape_default)
- ) / getIntegerRes(R.integer.keyboard_height_max).toFloat()
- @JvmStatic
- val screenHeightPortrait: Float
- get() = MyPreferences.sharedPref.getInt(
- getStringRes(R.string.pref_ui_keyboard_height_portrait_i),
- getIntegerRes(R.integer.pref_keyboard_height_portrait_default)
- ) / getIntegerRes(R.integer.keyboard_height_max).toFloat()
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/LogicSettingsFragment.kt b/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/LogicSettingsFragment.kt
deleted file mode 100644
index 9b6ac8c..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/LogicSettingsFragment.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.elishaazaria.sayboard.settingsfragments
-
-import android.os.Bundle
-import androidx.preference.PreferenceFragmentCompat
-import com.elishaazaria.sayboard.AppCtx.getStringRes
-import com.elishaazaria.sayboard.R
-
-class LogicSettingsFragment : PreferenceFragmentCompat() {
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- val preferenceManager = preferenceManager
- preferenceManager.sharedPreferencesName = getStringRes(R.string.main_shared_pref)
- setPreferencesFromResource(R.xml.logic_preferences, rootKey)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/ModelsFragment.kt b/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/ModelsFragment.kt
deleted file mode 100644
index 0d33153..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/ModelsFragment.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-package com.elishaazaria.sayboard.settingsfragments
-
-import android.os.Bundle
-import android.view.*
-import android.widget.ProgressBar
-import android.widget.Toast
-import androidx.core.view.MenuHost
-import androidx.core.view.MenuProvider
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.Lifecycle
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import com.elishaazaria.sayboard.R
-import com.elishaazaria.sayboard.Tools.getModelForLink
-import com.elishaazaria.sayboard.Tools.getModelsData
-import com.elishaazaria.sayboard.data.VoskServerData
-import com.elishaazaria.sayboard.databinding.FragmentModelsBinding
-import com.elishaazaria.sayboard.downloader.messages.*
-import com.elishaazaria.sayboard.preferences.ModelPreferences
-import com.elishaazaria.sayboard.settingsfragments.modelsfragment.*
-import com.elishaazaria.sayboard.settingsfragments.modelsfragment.ModelsAdapter.ItemClickListener
-import org.greenrobot.eventbus.EventBus
-import org.greenrobot.eventbus.Subscribe
-import org.greenrobot.eventbus.ThreadMode
-import java.net.URI
-
-class ModelsFragment : Fragment(), ItemClickListener, MenuProvider {
- private lateinit var binding: FragmentModelsBinding
- private lateinit var adapter: ModelsAdapter
- private lateinit var progressBar: ProgressBar
- private lateinit var recyclerView: RecyclerView
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
- binding = FragmentModelsBinding.inflate(inflater, container, false)
- val root: View = binding.root
- recyclerView = binding.recyclerView
- progressBar = binding.progressBar
- recyclerView.layoutManager = LinearLayoutManager(context)
- val dataProvider: AdapterDataProvider = object : AdapterDataProvider {
- override val data: List
- get() {
- val list = ArrayList()
- if (ModelPreferences.VOSK_SERVER_ENABLED) {
- for (data in ModelPreferences.voskServers) {
- list.add(ModelsAdapterServerData(data))
- }
- }
- list.addAll(getModelsData(context))
- return list
- }
- }
- adapter = ModelsAdapter(requireContext(), dataProvider)
- adapter.setClickListener(this)
- recyclerView.adapter = adapter
- val activity: MenuHost = requireActivity()
- activity.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED)
- return root
- }
-
- override fun onItemClick(view: View, position: Int, data: ModelsAdapterData) {}
- override fun onButtonClicked(view: View, position: Int, data: ModelsAdapterData) {
- data.buttonClicked(adapter, requireContext())
- }
-
- fun getAdapterDataForModel(modelInfo: ModelInfo): ModelsAdapterLocalData? {
- for (i in 0 until adapter.size()) {
- val data = adapter.getItem(i)
- if (data is ModelsAdapterLocalData) {
- val localData = data
- if (localData.filename == modelInfo.filename) {
- return localData
- }
- }
- }
- return null
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun onState(state: DownloadState) {
- val current = getAdapterDataForModel(state.info) ?: return
- when (state.state) {
- State.DOWNLOAD_STARTED -> {
- progressBar.visibility = View.VISIBLE
- current.downloading()
- adapter.changed(current)
- }
- State.FINISHED -> {
- progressBar.visibility = View.GONE
- val model = getModelForLink(current.modelLink!!, context)
- if (model != null) current.wasInstalled(model)
- adapter.changed(current)
- }
- State.ERROR -> {
- progressBar.visibility = View.GONE
- Toast.makeText(
- context,
- "Download failed for " + current.filename,
- Toast.LENGTH_SHORT
- ).show()
- current.downloadCanceled()
- adapter.changed(current)
- }
- State.QUEUED -> {
- current.wasQueued()
- adapter.changed(current)
- }
- else -> {}
- }
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun onStatus(status: Status) {
- onState(DownloadState(status.current, status.state))
- when (status.state) {
- State.DOWNLOAD_STARTED -> onDownloadProgress(
- DownloadProgress(
- status.current,
- status.downloadProgress
- )
- )
- State.UNZIP_STARTED -> onUnzipProgress(
- UnzipProgress(
- status.current,
- status.unzipProgress
- )
- )
- else -> {}
- }
- for (modelInfo in status.queued) {
- onState(DownloadState(modelInfo, State.QUEUED))
- }
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun onDownloadProgress(progress: DownloadProgress) {
- progressBar.progress = progress.progress
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun onUnzipProgress(progress: UnzipProgress) {
- progressBar.secondaryProgress = progress.progress
- }
-
- @Subscribe(threadMode = ThreadMode.MAIN)
- fun onDownloadError(error: DownloadError?) {
-// Toast.makeText(getContext(), error.message, Toast.LENGTH_SHORT).show();
- }
-
- override fun onStart() {
- super.onStart()
- EventBus.getDefault().register(this)
- EventBus.getDefault().post(StatusQuery())
- }
-
- override fun onStop() {
- EventBus.getDefault().unregister(this)
- super.onStop()
- }
-
- override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
- if (ModelPreferences.VOSK_SERVER_ENABLED) {
- menuInflater.inflate(R.menu.models_fragment_menu, menu)
- }
- }
-
- override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
- if (ModelPreferences.VOSK_SERVER_ENABLED) {
- if (menuItem.title === getString(R.string.menu_models_add_server)) {
- AddVoskServerDialogFragment { add: Boolean, uri: URI? ->
- if (add && uri != null) {
- ModelPreferences.addToVoskServers(VoskServerData(uri, null))
- adapter.reload()
- }
- }.show(requireActivity().supportFragmentManager, "AddVoskServerDialogFragment")
- }
- }
- return false
- }
-
- companion object {
- private const val TAG = "ModelsFragment"
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/SetupFragment.kt b/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/SetupFragment.kt
deleted file mode 100644
index 9a7d1ea..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/SetupFragment.kt
+++ /dev/null
@@ -1,75 +0,0 @@
-package com.elishaazaria.sayboard.settingsfragments
-
-import android.Manifest
-import android.content.Intent
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.core.app.ActivityCompat
-import androidx.fragment.app.Fragment
-import com.elishaazaria.sayboard.R
-import com.elishaazaria.sayboard.SettingsActivity
-import com.elishaazaria.sayboard.Tools.isIMEEnabled
-import com.elishaazaria.sayboard.Tools.isMicrophonePermissionGranted
-import com.elishaazaria.sayboard.databinding.FragmentSetupBinding
-
-class SetupFragment : Fragment() {
- private var binding: FragmentSetupBinding? = null
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?, savedInstanceState: Bundle?
- ): View? {
- binding = FragmentSetupBinding.inflate(inflater, container, false)
- val root: View = binding!!.root
- registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean? -> reloadButtons() }
- binding!!.microphonePermission.setOnClickListener { v: View? ->
- ActivityCompat.requestPermissions(
- requireActivity(), arrayOf(
- Manifest.permission.RECORD_AUDIO
- ), PERMISSIONS_REQUEST_RECORD_AUDIO
- )
- }
- binding!!.enableKeyboard.setOnClickListener { v: View? -> startActivity(Intent("android.settings.INPUT_METHOD_SETTINGS")) }
-
-// InputMethodManager imeManager = (InputMethodManager) requireActivity().getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-// binding.openImeSwitcher.setOnClickListener(v -> imeManager.showInputMethodPicker());
- reloadButtons()
- return root
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- binding = null
- }
-
- private fun reloadButtons() {
- val permissionGranted = isMicrophonePermissionGranted(requireActivity())
- binding!!.microphonePermission.text =
- getString(if (permissionGranted) R.string.mic_permission_granted else R.string.mic_permission_not_granted)
- binding!!.microphonePermission.isEnabled = !permissionGranted
- val keyboardInEnabledList = isIMEEnabled(requireActivity())
- binding!!.enableKeyboard.text =
- getString(if (keyboardInEnabledList) R.string.keyboard_enabled else R.string.keyboard_not_enabled)
- binding!!.enableKeyboard.isEnabled = !keyboardInEnabledList
- if (permissionGranted && keyboardInEnabledList) {
- (requireActivity() as SettingsActivity).permissionsGranted()
- }
- }
-
- // @Override
- // public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- // super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- // reloadButtons();
- // }
- override fun onResume() {
- super.onResume()
- reloadButtons()
- }
-
- companion object {
- /* Used to handle permission request */
- private const val PERMISSIONS_REQUEST_RECORD_AUDIO = 1
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/UIFragment.kt b/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/UIFragment.kt
deleted file mode 100644
index 21737ca..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/UIFragment.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.elishaazaria.sayboard.settingsfragments
-
-import android.os.Bundle
-import androidx.preference.Preference
-import androidx.preference.PreferenceFragmentCompat
-import com.elishaazaria.sayboard.AppCtx.getStringRes
-import com.elishaazaria.sayboard.R
-import com.rarepebble.colorpicker.ColorPreference
-
-class UIFragment : PreferenceFragmentCompat() {
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- val preferenceManager = preferenceManager
- preferenceManager.sharedPreferencesName = getStringRes(R.string.main_shared_pref)
- setPreferencesFromResource(R.xml.ui_preferences, rootKey)
- }
-
- override fun onDisplayPreferenceDialog(preference: Preference) {
- if (preference is ColorPreference) {
- preference.showDialog(this, 0)
- } else super.onDisplayPreferenceDialog(preference)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/AdapterDataProvider.kt b/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/AdapterDataProvider.kt
deleted file mode 100644
index d3fa553..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/AdapterDataProvider.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.elishaazaria.sayboard.settingsfragments.modelsfragment
-
-interface AdapterDataProvider {
- val data: List
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/AddVoskServerDialogFragment.kt b/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/AddVoskServerDialogFragment.kt
deleted file mode 100644
index 4eca6de..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/AddVoskServerDialogFragment.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.elishaazaria.sayboard.settingsfragments.modelsfragment
-
-import android.annotation.SuppressLint
-import android.app.AlertDialog
-import android.app.Dialog
-import android.content.DialogInterface
-import android.os.Bundle
-import android.util.Log
-import android.view.View
-import android.widget.EditText
-import androidx.fragment.app.DialogFragment
-import com.elishaazaria.sayboard.R
-import java.net.URI
-import java.net.URISyntaxException
-
-class AddVoskServerDialogFragment(private val callback: Callback) : DialogFragment() {
- @SuppressLint("InflateParams")
- override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
- val dialogBuilder = AlertDialog.Builder(requireActivity())
- // Get the layout inflater
- val inflater = requireActivity().layoutInflater
-
- // Inflate and set the layout for the dialog
- // Pass null as the parent view because its going in the dialog layout
- dialogBuilder.setView(
- inflater.inflate(
- R.layout.add_vosk_server_dialog,
- null
- )
- ) // Add action buttons
- .setPositiveButton(R.string.dialog_add_vosk_server_add) { dialog: DialogInterface?, id: Int ->
- val hostname =
- (getDialog()!!.findViewById(R.id.hostname) as EditText).text.toString()
- val portString =
- (getDialog()!!.findViewById(R.id.port) as EditText).text.toString()
- Log.d("VoskServerDialog", "$hostname:$portString")
- try {
- val port = portString.toInt()
- callback.callback(true, URI(null, null, hostname, port, null, null, null))
- } catch (e: NumberFormatException) {
- e.printStackTrace()
- callback.callback(false, null)
- } catch (e: URISyntaxException) {
- e.printStackTrace()
- callback.callback(false, null)
- }
- }
- .setNegativeButton(R.string.dialog_add_vosk_server_cancel) { dialog: DialogInterface, id: Int -> dialog.cancel() }
- return dialogBuilder.create()
- }
-
- fun interface Callback {
- fun callback(add: Boolean, uri: URI?)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapter.kt b/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapter.kt
deleted file mode 100644
index 6cb587d..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapter.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-package com.elishaazaria.sayboard.settingsfragments.modelsfragment
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageButton
-import android.widget.TextView
-import androidx.recyclerview.widget.RecyclerView
-import com.elishaazaria.sayboard.R
-
-class ModelsAdapter(context: Context, dataProvider: AdapterDataProvider) :
- RecyclerView.Adapter() {
- enum class DataState {
- CLOUD, INSTALLED, DOWNLOADING, QUEUED
- }
-
- private val context: Context
- private val dataProvider: AdapterDataProvider
- private val mData: MutableList
- private val mInflater: LayoutInflater
- private var mClickListener: ItemClickListener? = null
-
- // data is passed into the constructor
- init {
- mInflater = LayoutInflater.from(context)
- this.context = context
- this.dataProvider = dataProvider
- mData = ArrayList(dataProvider.data)
- }
-
- // inflates the row layout from xml when needed
- override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
- val view = mInflater.inflate(R.layout.fragment_models_entry, parent, false)
- return ViewHolder(view)
- }
-
- // binds the data to the TextView in each row
- override fun onBindViewHolder(holder: ViewHolder, position: Int) {
- val data = mData[position]
- holder.titleTextView.text = data.title
- holder.subtitleTextView.text = data.subtitle
- holder.downloadButton.setImageResource(data.imageRes)
- holder.data = data
- }
-
- // total number of rows
- override fun getItemCount(): Int {
- return mData.size
- }
-
- // stores and recycles views as they are scrolled off screen
- inner class ViewHolder internal constructor(itemView: View) :
- RecyclerView.ViewHolder(itemView) {
- var titleTextView: TextView
- var subtitleTextView: TextView
- var downloadButton: ImageButton
- var data: ModelsAdapterData? = null
-
- init {
- titleTextView = itemView.findViewById(R.id.titleTextView)
- subtitleTextView = itemView.findViewById(R.id.subtitleTextView)
- downloadButton = itemView.findViewById(R.id.downloadButton)
- downloadButton.setOnClickListener { view: View -> onButtonClick(view) }
- itemView.setOnClickListener { view: View -> onClick(view) }
- }
-
- private fun onClick(view: View) {
- if (mClickListener != null) mClickListener!!.onItemClick(view, adapterPosition, data!!)
- }
-
- private fun onButtonClick(view: View) {
- if (mClickListener != null) {
- mClickListener!!.onButtonClicked(view, adapterPosition, data!!)
- }
- }
- }
-
- // convenience method for getting data at click position
- fun getItem(index: Int): ModelsAdapterData {
- return mData[index]
- }
-
- fun size(): Int {
- return mData.size
- }
-
- fun changed(data: ModelsAdapterData?): Boolean {
- val index = mData.indexOf(data)
- if (index == -1) return false
- notifyItemChanged(index)
- return true
- }
-
- fun removed(data: ModelsAdapterData?): Boolean {
- val index = mData.indexOf(data)
- if (index == -1) return false
- mData.removeAt(index)
- notifyItemRemoved(index)
- return true
- }
-
- @SuppressLint("NotifyDataSetChanged")
- fun reload() {
- mData.clear()
- mData.addAll(dataProvider.data)
- notifyDataSetChanged()
- }
-
- // allows clicks events to be caught
- fun setClickListener(itemClickListener: ItemClickListener?) {
- mClickListener = itemClickListener
- }
-
- // parent activity will implement this method to respond to click events
- interface ItemClickListener {
- fun onItemClick(view: View, position: Int, data: ModelsAdapterData)
- fun onButtonClicked(view: View, position: Int, data: ModelsAdapterData)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapterData.kt b/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapterData.kt
deleted file mode 100644
index 1ab2ad5..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapterData.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.elishaazaria.sayboard.settingsfragments.modelsfragment
-
-import android.content.Context
-
-interface ModelsAdapterData {
- val title: String
- val subtitle: String
- val imageRes: Int
- fun buttonClicked(adapter: ModelsAdapter, context: Context)
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapterLocalData.kt b/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapterLocalData.kt
deleted file mode 100644
index d3bed22..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapterLocalData.kt
+++ /dev/null
@@ -1,107 +0,0 @@
-package com.elishaazaria.sayboard.settingsfragments.modelsfragment
-
-import android.content.Context
-import com.elishaazaria.sayboard.R
-import com.elishaazaria.sayboard.Tools.deleteModel
-import com.elishaazaria.sayboard.data.LocalModel
-import com.elishaazaria.sayboard.data.ModelLink
-import com.elishaazaria.sayboard.downloader.FileDownloader
-import com.elishaazaria.sayboard.settingsfragments.modelsfragment.ModelsAdapter.DataState
-import java.util.*
-
-class ModelsAdapterLocalData : ModelsAdapterData {
- var modelLink: ModelLink?
- private set
- var model: LocalModel?
- private set
- var state: DataState
- private set
-
- constructor(modelLink: ModelLink?) {
- this.modelLink = modelLink
- model = null
- state = DataState.CLOUD
- }
-
- constructor(model: LocalModel?) {
- modelLink = null
- this.model = model
- state = DataState.INSTALLED
- }
-
- constructor(modelLink: ModelLink?, model: LocalModel?) {
- this.modelLink = modelLink
- this.model = model
- state = DataState.INSTALLED
- }
-
- val filename: String
- get() = if (modelLink != null) {
- modelLink!!.filename
- } else if (model != null) {
- model!!.filename!!
- } else {
- "Undefined"
- }
- val locale: Locale
- get() = if (model != null) {
- model!!.locale!!
- } else if (modelLink != null) {
- modelLink!!.locale
- } else Locale.forLanguageTag("und")
-
- fun wasInstalled(model: LocalModel?) {
- this.model = model
- state = DataState.INSTALLED
- }
-
- fun wasDeleted(): Boolean {
- model = null
- state = DataState.CLOUD
- return modelLink == null
- }
-
- fun wasQueued() {
- state = DataState.QUEUED
- }
-
- fun downloading() {
- state = DataState.DOWNLOADING
- }
-
- fun downloadCanceled() {
- if (state == DataState.DOWNLOADING) state = DataState.CLOUD
- }
-
- override val title: String
- get() = locale.displayName
- override val subtitle: String
- get() = filename
- override val imageRes: Int
- get() {
- return when (state) {
- DataState.CLOUD -> R.drawable.ic_download
- DataState.INSTALLED -> R.drawable.ic_delete
- DataState.DOWNLOADING -> R.drawable.ic_downloading
- DataState.QUEUED -> R.drawable.ic_add_circle_outline
- }
- return 0
- }
-
- override fun buttonClicked(adapter: ModelsAdapter, context: Context) {
- when (state) {
- DataState.CLOUD -> FileDownloader.downloadModel(modelLink!!, context)
- DataState.INSTALLED -> {
- deleteModel(model!!, context)
- val removed = wasDeleted()
- if (removed) {
- adapter.removed(this)
- } else {
- adapter.changed(this)
- }
- }
- DataState.DOWNLOADING -> {}
- DataState.QUEUED -> {}
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapterServerData.kt b/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapterServerData.kt
deleted file mode 100644
index ecc8953..0000000
--- a/app/src/main/java/com/elishaazaria/sayboard/settingsfragments/modelsfragment/ModelsAdapterServerData.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.elishaazaria.sayboard.settingsfragments.modelsfragment
-
-import android.content.Context
-import com.elishaazaria.sayboard.R
-import com.elishaazaria.sayboard.data.VoskServerData
-import com.elishaazaria.sayboard.preferences.ModelPreferences
-
-class ModelsAdapterServerData(private val data: VoskServerData) : ModelsAdapterData {
- override val title: String
- get() = data.uri.toString()
- override val subtitle: String
- get() = "vosk server"
- override val imageRes: Int
- get() = R.drawable.ic_delete
-
- override fun buttonClicked(adapter: ModelsAdapter, context: Context) {
- ModelPreferences.removeFromVoskServers(data)
- adapter.removed(this)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/ui/ColorPickerPreference.kt b/app/src/main/java/com/elishaazaria/sayboard/ui/ColorPickerPreference.kt
new file mode 100644
index 0000000..bace630
--- /dev/null
+++ b/app/src/main/java/com/elishaazaria/sayboard/ui/ColorPickerPreference.kt
@@ -0,0 +1,113 @@
+package com.elishaazaria.sayboard.ui
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Card
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import dev.patrickgold.jetpref.datastore.model.PreferenceData
+import dev.patrickgold.jetpref.datastore.model.PreferenceModel
+import dev.patrickgold.jetpref.datastore.ui.PreferenceUiScope
+import dev.patrickgold.jetpref.material.ui.ExperimentalJetPrefMaterialUi
+import dev.patrickgold.jetpref.material.ui.JetPrefColorPicker
+import dev.patrickgold.jetpref.material.ui.JetPrefListItem
+import dev.patrickgold.jetpref.material.ui.rememberJetPrefColorPickerState
+
+@OptIn(ExperimentalJetPrefMaterialUi::class, ExperimentalMaterial3Api::class)
+@Composable
+fun PreferenceUiScope.ColorPickerPreference(
+ pref: PreferenceData,
+ title: String,
+ modifier: Modifier = Modifier,
+ summary: String? = null,
+ enabled: Boolean = true
+) {
+ var setColor by remember {
+ mutableStateOf(Color(pref.get()))
+ }
+ var selectedColor by remember {
+ mutableStateOf(Color(pref.get()))
+ }
+ val colorPickerState = rememberJetPrefColorPickerState(initColor = selectedColor)
+ var showDialog by remember {
+ mutableStateOf(false)
+ }
+ JetPrefListItem(
+ modifier =
+ modifier.clickable(
+ enabled = enabled,
+ role = Role.Button,
+ onClick = {
+ showDialog = true
+ },
+ ),
+ icon = {
+ Spacer(
+ modifier = Modifier
+ .background(Color.Black)
+ .padding(1.dp)
+ .background(setColor)
+ .size(40.dp)
+ )
+ },
+ text = title,
+ secondaryText = summary,
+ enabled = enabled
+ )
+
+ if (showDialog) {
+ AlertDialog(
+ onDismissRequest = {
+ showDialog = false
+ }) {
+ Card {
+ Column(
+ modifier = Modifier.padding(20.dp),
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ Text(text = title, fontSize = 20.sp)
+ JetPrefColorPicker(state = colorPickerState,
+ onColorChange = {
+ selectedColor = it
+ })
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ Spacer(modifier = Modifier.weight(1f))
+ Button(onClick = {
+ showDialog = false
+ }) {
+ Text(text = "Cancel")
+ }
+ Button(onClick = {
+ showDialog = false
+ setColor = selectedColor
+ pref.set(selectedColor.toArgb())
+ }) {
+ Text(text = "Select")
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/ui/GrantPermissionsUi.kt b/app/src/main/java/com/elishaazaria/sayboard/ui/GrantPermissionsUi.kt
new file mode 100644
index 0000000..8bb0dcb
--- /dev/null
+++ b/app/src/main/java/com/elishaazaria/sayboard/ui/GrantPermissionsUi.kt
@@ -0,0 +1,41 @@
+package com.elishaazaria.sayboard.ui
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+
+@Composable
+public fun GrantPermissionUi(
+ mic: State,
+ ime: State,
+ requestMic: () -> Unit,
+ requestIme: () -> Unit
+) {
+ Column(
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Button(onClick = requestMic, enabled = mic.value) {
+ if (mic.value) {
+ Text(text = "Grant Microphone Permission")
+ } else {
+ Text(text = "Microphone Permission Granted")
+ }
+
+ }
+ Button(onClick = requestIme, enabled = ime.value) {
+ if (ime.value) {
+ Text(text = "Enable IME")
+ } else {
+ Text(text = "IME enabled")
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/ui/LogicSettingsUi.kt b/app/src/main/java/com/elishaazaria/sayboard/ui/LogicSettingsUi.kt
new file mode 100644
index 0000000..7710ba1
--- /dev/null
+++ b/app/src/main/java/com/elishaazaria/sayboard/ui/LogicSettingsUi.kt
@@ -0,0 +1,40 @@
+package com.elishaazaria.sayboard.ui
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.elishaazaria.sayboard.R
+import com.elishaazaria.sayboard.data.KeepScreenAwakeMode
+import com.elishaazaria.sayboard.sayboardPreferenceModel
+import dev.patrickgold.jetpref.datastore.ui.ListPreference
+import dev.patrickgold.jetpref.datastore.ui.ScrollablePreferenceLayout
+import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
+
+@Composable
+fun LogicSettingsUi() = ScrollablePreferenceLayout(sayboardPreferenceModel()) {
+ ListPreference(
+ listPref = prefs.logicKeepScreenAwake,
+ title = stringResource(id = R.string.other_keep_screen_awake_title),
+ entries = KeepScreenAwakeMode.listEntries()
+ )
+ SwitchPreference(
+ pref = prefs.logicListenImmediately,
+ title = stringResource(id = R.string.other_listen_immediately_title),
+ summary = stringResource(
+ id = R.string.other_listen_immediately_summery
+ )
+ )
+ SwitchPreference(
+ pref = prefs.logicAutoSwitchBack,
+ title = stringResource(id = R.string.other_auto_switch_back_title),
+ summary = stringResource(
+ id = R.string.other_auto_switch_back_summery
+ )
+ )
+ SwitchPreference(
+ pref = prefs.logicWeakRefToModel,
+ title = stringResource(id = R.string.other_weak_ref_to_model_title),
+ summary = stringResource(
+ id = R.string.other_weak_ref_to_model_summery
+ )
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/ui/ModelsSettingsUi.kt b/app/src/main/java/com/elishaazaria/sayboard/ui/ModelsSettingsUi.kt
new file mode 100644
index 0000000..776ddd5
--- /dev/null
+++ b/app/src/main/java/com/elishaazaria/sayboard/ui/ModelsSettingsUi.kt
@@ -0,0 +1,381 @@
+package com.elishaazaria.sayboard.ui
+
+import android.util.Log
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Cancel
+import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.Download
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Card
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FilledIconButton
+import androidx.compose.material3.FloatingActionButton
+import androidx.compose.material3.Icon
+import androidx.compose.material3.LinearProgressIndicator
+import androidx.compose.material3.ShapeDefaults
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.livedata.observeAsState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.window.DialogProperties
+import androidx.lifecycle.MutableLiveData
+import com.elishaazaria.sayboard.SettingsActivity
+import com.elishaazaria.sayboard.Tools
+import com.elishaazaria.sayboard.data.LocalModel
+import com.elishaazaria.sayboard.data.ModelLink
+import com.elishaazaria.sayboard.downloader.FileDownloader
+import com.elishaazaria.sayboard.downloader.messages.CancelCurrent
+import com.elishaazaria.sayboard.downloader.messages.CancelFinished
+import com.elishaazaria.sayboard.downloader.messages.CancelPending
+import com.elishaazaria.sayboard.downloader.messages.DownloadError
+import com.elishaazaria.sayboard.downloader.messages.DownloadProgress
+import com.elishaazaria.sayboard.downloader.messages.DownloadState
+import com.elishaazaria.sayboard.downloader.messages.ModelInfo
+import com.elishaazaria.sayboard.downloader.messages.State
+import com.elishaazaria.sayboard.downloader.messages.Status
+import com.elishaazaria.sayboard.downloader.messages.StatusQuery
+import com.elishaazaria.sayboard.downloader.messages.UnzipProgress
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+
+data class DownloadModelProgress(
+ val info: ModelInfo,
+ var state: State, var progress: Float
+)
+
+class ModelsSettingsUi(private val activity: SettingsActivity) {
+ val models = MutableLiveData>(mutableListOf())
+
+ private val modelsPendingDownloadLD = MutableLiveData>(mutableListOf())
+ private val modelsPendingDownload = mutableListOf()
+
+ private val currentDownloadingModel = MutableLiveData()
+
+ fun onCreate() {
+ reloadModels()
+ }
+
+ private fun reloadModels() {
+ models.postValue(Tools.getInstalledModelsList(activity))
+ }
+
+
+ @Composable
+ fun Content() {
+ val modelsState = models.observeAsState(listOf())
+ val modelsPendingDownload = modelsPendingDownloadLD.observeAsState(mutableListOf())
+ val currentDownloadingModel = currentDownloadingModel.observeAsState()
+
+ LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
+ item {
+ currentDownloadingModel.value?.let { current ->
+ Card {
+ Row(
+ modifier = Modifier.padding(10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Text(text = current.info.locale.displayName, fontSize = 20.sp)
+ Text(text = current.info.url, fontSize = 12.sp)
+ val stateText = when (current.state) {
+ State.NONE -> "Unknown"
+ State.QUEUED -> "Pending"
+ State.DOWNLOAD_STARTED -> "Downloading"
+ State.DOWNLOAD_FINISHED -> "Download Finished"
+ State.UNZIP_STARTED -> "Unzipping"
+ State.UNZIP_FINISHED -> "Unzipping Finished"
+ State.FINISHED -> "Finished"
+ State.ERROR -> "Error"
+ State.CANCELED -> "Canceled"
+ }
+
+ Text(
+ text = "State: $stateText", fontSize = 14.sp
+ )
+ }
+ FilledIconButton(onClick = {
+ EventBus.getDefault()
+ .post(
+ CancelCurrent(current.info)
+ )
+ }, shape = ShapeDefaults.Medium) {
+ Icon(
+ imageVector = Icons.Default.Cancel,
+ contentDescription = null
+ )
+ }
+ }
+ LinearProgressIndicator(current.progress)
+ }
+ }
+ }
+ items(items = modelsPendingDownload.value) {
+ Card {
+ Row(
+ modifier = Modifier.padding(10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Text(text = it.locale.displayName, fontSize = 20.sp)
+ Text(text = it.url, fontSize = 12.sp)
+ Text(text = "State: Pending Download", fontSize = 14.sp)
+ }
+ FilledIconButton(onClick = {
+ EventBus.getDefault()
+ .post(
+ CancelPending(it)
+ )
+ }, shape = ShapeDefaults.Medium) {
+ Icon(imageVector = Icons.Default.Cancel, contentDescription = null)
+ }
+ }
+ }
+ }
+
+ items(items = modelsState.value) { lm ->
+ Card {
+ Row(
+ modifier = Modifier.padding(10.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Text(text = lm.locale.displayName ?: "null", fontSize = 20.sp)
+ Text(text = lm.path, fontSize = 12.sp)
+ }
+ FilledIconButton(onClick = {
+ Tools.deleteModel(lm, activity)
+ reloadModels()
+ }, shape = ShapeDefaults.Medium) {
+ Icon(imageVector = Icons.Default.Delete, contentDescription = null)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @OptIn(ExperimentalMaterial3Api::class)
+ @Composable
+ fun Fab() {
+ var showDownloadDialog by remember {
+ mutableStateOf(false)
+ }
+
+ Column(
+ verticalArrangement = Arrangement.spacedBy(10.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ FloatingActionButton(onClick = {
+ showDownloadDialog = true
+ }) {
+ Icon(imageVector = Icons.Default.Download, contentDescription = null)
+ }
+ }
+
+ if (showDownloadDialog) {
+ AlertDialog(onDismissRequest = {
+ showDownloadDialog = false
+ }, properties = DialogProperties(usePlatformDefaultWidth = false)) {
+ Box(
+ Modifier
+ .clip(RectangleShape)
+ .fillMaxWidth()
+ .background(Color.White)
+ .padding(10.dp)
+ ) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Text(text = "Download Model", fontSize = 30.sp)
+
+ LazyColumn(
+ verticalArrangement = Arrangement.spacedBy(10.dp),
+ modifier = Modifier.weight(1f)
+ ) {
+ items(items = ModelLink.values()) { ml ->
+ Card(onClick = {
+ showDownloadDialog = false
+ FileDownloader.downloadModel(ml, activity)
+ }, modifier = Modifier.fillMaxWidth()) {
+ Column(modifier = Modifier.padding(10.dp)) {
+ Text(text = ml.locale.displayName)
+ Text(text = ml.link, fontSize = 10.sp)
+ }
+ }
+ }
+ }
+ Box(modifier = Modifier.fillMaxWidth()) {
+ Button(onClick = {
+ showDownloadDialog = false
+ }, modifier = Modifier.align(Alignment.CenterEnd)) {
+ Text(text = "Cancel")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private fun updateCurrentDownloading(info: ModelInfo) {
+ val currentModel = currentDownloadingModel.value
+
+ if (currentModel == null) {
+ currentDownloadingModel.value = DownloadModelProgress(
+ info,
+ State.NONE,
+ 0f
+ )
+ } else if (currentModel.info != info) {
+ val newCurrent = modelsPendingDownload.find { it == currentModel.info }
+ if (newCurrent == null) {
+ currentDownloadingModel.value = DownloadModelProgress(
+ info,
+ State.NONE,
+ 0f
+ )
+ } else {
+ modelsPendingDownload.remove(newCurrent)
+ modelsPendingDownloadLD.postValue(modelsPendingDownload.toList())
+ currentDownloadingModel.value = DownloadModelProgress(
+ newCurrent,
+ State.NONE,
+ 0f
+ )
+ }
+ }
+ }
+
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onAddToDownload(info: ModelInfo) {
+ Log.d(TAG, "onAddToDownload($info)")
+
+ if (modelsPendingDownload.contains(info)) return
+ modelsPendingDownload.add(info)
+ modelsPendingDownloadLD.postValue(modelsPendingDownload.toList())
+ }
+
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onState(state: DownloadState) {
+ Log.d(TAG, "onState($state)")
+ when (state.state) {
+ State.QUEUED -> onAddToDownload(state.info)
+ State.DOWNLOAD_STARTED, State.NONE -> {
+ modelsPendingDownload.remove(state.info)
+ modelsPendingDownloadLD.postValue(modelsPendingDownload.toList())
+ updateCurrentDownloading(state.info)
+ val current = currentDownloadingModel.value!!
+ current.state = state.state
+ currentDownloadingModel.value = current
+ }
+
+ State.FINISHED -> {
+ currentDownloadingModel.value = null
+ reloadModels()
+ }
+
+ else -> {
+ updateCurrentDownloading(state.info)
+ val current = currentDownloadingModel.value!!
+ current.state = state.state
+ currentDownloadingModel.value = current
+ }
+ }
+ }
+
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onStatus(status: Status) {
+ Log.d(TAG, "onStatus($status)")
+ if (status.current != null) {
+ onState(DownloadState(status.current, status.state))
+ when (status.state) {
+ State.DOWNLOAD_STARTED -> onDownloadProgress(
+ DownloadProgress(
+ status.current,
+ status.downloadProgress
+ )
+ )
+
+ State.UNZIP_STARTED -> onUnzipProgress(
+ UnzipProgress(
+ status.current,
+ status.unzipProgress
+ )
+ )
+
+ else -> {}
+ }
+ }
+ for (modelInfo in status.queued) {
+ onState(DownloadState(modelInfo, State.QUEUED))
+ }
+ }
+
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onDownloadProgress(progress: DownloadProgress) {
+// Log.d(TAG, "onDownloadProgress($progress)")
+ updateCurrentDownloading(progress.info)
+ val current = currentDownloadingModel.value!!
+ current.progress = progress.progress
+ currentDownloadingModel.postValue(current)
+ }
+
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onUnzipProgress(progress: UnzipProgress) {
+// Log.d(TAG, "onUnzipProgress($progress)")
+ updateCurrentDownloading(progress.info)
+
+ val current = currentDownloadingModel.value!!
+ current.progress = progress.progress
+ currentDownloadingModel.postValue(current)
+ }
+
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onCancelFinished(event: CancelFinished) {
+ if (currentDownloadingModel.value?.info == event.info) {
+ currentDownloadingModel.value = null
+ }
+ }
+
+ @Subscribe(threadMode = ThreadMode.MAIN)
+ fun onDownloadError(error: DownloadError?) {
+// Toast.makeText(getContext(), error.message, Toast.LENGTH_SHORT).show();
+ }
+
+ fun onStart() {
+ EventBus.getDefault().register(this)
+ EventBus.getDefault().post(StatusQuery())
+ }
+
+ fun onStop() {
+ EventBus.getDefault().unregister(this)
+ }
+
+ fun onResume() {
+ reloadModels()
+// EventBus.getDefault().post(StatusQuery())
+ }
+
+ companion object {
+ private val TAG = "ModelsSettingUi"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/elishaazaria/sayboard/ui/UISettingsUi.kt b/app/src/main/java/com/elishaazaria/sayboard/ui/UISettingsUi.kt
new file mode 100644
index 0000000..e76fd7c
--- /dev/null
+++ b/app/src/main/java/com/elishaazaria/sayboard/ui/UISettingsUi.kt
@@ -0,0 +1,68 @@
+package com.elishaazaria.sayboard.ui
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import com.elishaazaria.sayboard.R
+import com.elishaazaria.sayboard.sayboardPreferenceModel
+import dev.patrickgold.jetpref.datastore.model.observeAsState
+import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
+import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
+import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
+import dev.patrickgold.jetpref.datastore.ui.ScrollablePreferenceLayout
+import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
+import dev.patrickgold.jetpref.material.ui.ExperimentalJetPrefMaterialUi
+
+@OptIn(ExperimentalJetPrefMaterialUi::class, ExperimentalJetPrefDatastoreUi::class)
+@Composable
+fun UISettingsUi() = ScrollablePreferenceLayout(sayboardPreferenceModel()) {
+ PreferenceGroup(title = stringResource(id = R.string.ui_light_theme_header)) {
+ SwitchPreference(
+ pref = prefs.uiDayForegroundMaterialYou,
+ title = stringResource(id = R.string.ui_light_theme_foreground_material_you_title)
+ )
+
+ ColorPickerPreference(
+ pref = prefs.uiDayForeground,
+ title = stringResource(id = R.string.ui_light_theme_foreground_color_title),
+ enabled = !prefs.uiDayForegroundMaterialYou.observeAsState().value
+ )
+
+ ColorPickerPreference(
+ pref = prefs.uiDayBackground,
+ title = stringResource(id = R.string.ui_light_theme_background_color_title)
+ )
+ }
+ PreferenceGroup(title = stringResource(id = R.string.ui_dark_theme_header)) {
+ SwitchPreference(
+ pref = prefs.uiNightForegroundMaterialYou,
+ title = stringResource(id = R.string.ui_dark_theme_foreground_material_you_title)
+ )
+
+ ColorPickerPreference(
+ pref = prefs.uiNightForeground,
+ title = stringResource(id = R.string.ui_dark_theme_foreground_color_title),
+ enabled = !prefs.uiNightForegroundMaterialYou.observeAsState().value
+ )
+
+ ColorPickerPreference(
+ pref = prefs.uiNightBackground,
+ title = stringResource(id = R.string.ui_dark_theme_background_color_title)
+ )
+ }
+ PreferenceGroup(title = stringResource(id = R.string.ui_keyboard_header)) {
+ DialogSliderPreference(
+ pref = prefs.uiKeyboardHeightPortrait,
+ title = stringResource(id = R.string.other_keyboard_height_portrait_title),
+ min = 0.01f,
+ max = 1f,
+ stepIncrement = 0.01f
+ )
+ DialogSliderPreference(
+ pref = prefs.uiKeyboardHeightLandscape,
+ title = stringResource(id = R.string.other_keyboard_height_landscape_title),
+ min = 0.01f,
+ max = 1f,
+ stepIncrement = 0.01f
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values/prefs.xml b/app/src/main/res/values/prefs.xml
new file mode 100644
index 0000000..09f6442
--- /dev/null
+++ b/app/src/main/res/values/prefs.xml
@@ -0,0 +1,6 @@
+
+
+ Never
+ When Listening
+ When Open
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8be1818..89fc31a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -80,8 +80,8 @@
Start listening immediately when the recognizer is ready.
Auto switch back
Automatically switch to previous keyboard whenever the keyboard is hidden.
- Weak ref to model (when closed)
- Allows the system to delete the model from RAM when the keyboard is closed if low on memory.
+ Weak ref to model (when closed)
+ Allows the system to delete the model from RAM when the keyboard is closed.
Model download
diff --git a/app/src/main/res/xml/logic_preferences.xml b/app/src/main/res/xml/logic_preferences.xml
index fd83237..e3388d9 100644
--- a/app/src/main/res/xml/logic_preferences.xml
+++ b/app/src/main/res/xml/logic_preferences.xml
@@ -21,7 +21,7 @@
+ android:summary="@string/other_weak_ref_to_model_summery"
+ android:title="@string/other_weak_ref_to_model_title" />
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index f8360ed..00b58a1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,5 +1,19 @@
-android.useAndroidX=true
-android.enableJetifier=true
+## For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+#
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+# Default value: -Xmx1024m -XX:MaxPermSize=256m
+# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
+#
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+#Thu Sep 28 14:52:36 IDT 2023
android.defaults.buildfeatures.buildconfig=true
-android.nonTransitiveRClass=false
+android.enableJetifier=true
android.nonFinalResIds=false
+android.nonTransitiveRClass=false
+android.useAndroidX=true
+org.gradle.jvmargs=-Xmx1536M