Compare commits

...

12 Commits

Author SHA1 Message Date
lm41
11a41f30e4 Temp 2025-08-02 00:27:30 +02:00
Patrick Goldinger
c035e19cfc Merge pull request #3000 from florisboard/feat/upgrade-to-jetpref-v3
Upgrade to JetPref v3
2025-08-01 16:58:14 +02:00
Patrick Goldinger
8d30403676 Upgrade Kotlin to 2.2.0 2025-08-01 16:47:43 +02:00
Patrick Goldinger
2886a24fcc Upgrade JetPref to 0.3.0-beta01 2025-08-01 16:22:20 +02:00
Patrick Goldinger
352c98205c Fix toasts being constructed on non-main threads 2025-07-31 13:03:13 +02:00
Patrick Goldinger
18c76b7195 Bump JetPref snapshot version 2025-07-30 02:14:49 +02:00
Patrick Goldinger
ea0712225a Fix backup and restore again 2025-07-30 01:35:51 +02:00
Patrick Goldinger
1132eaf59b Upgrade ThemeManager.kt to use Kotlin flows 2025-07-29 17:42:41 +02:00
Patrick Goldinger
cba85f1c0e Rework common collect(Latest)In functions 2025-07-29 16:41:36 +02:00
Patrick Goldinger
15dba9f33a Fix backup and restore functionality 2025-07-29 16:13:13 +02:00
Patrick Goldinger
3859528120 Try fix JvmTarget inconsistencies 2025-07-28 16:38:13 +02:00
Patrick Goldinger
046d253382 Upgrade to JetPref v3 2025-07-28 16:08:57 +02:00
72 changed files with 568 additions and 413 deletions

View File

@@ -206,6 +206,7 @@ dependencies {
implementation(libs.mikepenz.aboutlibraries.compose)
implementation(libs.patrickgold.compose.tooltip)
implementation(libs.patrickgold.jetpref.datastore.model)
ksp(libs.patrickgold.jetpref.datastore.model.processor)
implementation(libs.patrickgold.jetpref.datastore.ui)
implementation(libs.patrickgold.jetpref.material.ui)

View File

@@ -23,8 +23,10 @@ import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.Handler
import android.util.Log
import androidx.core.os.UserManagerCompat
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.clipboard.ClipboardManager
import dev.patrickgold.florisboard.ime.core.SubtypeManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
@@ -40,7 +42,11 @@ import dev.patrickgold.florisboard.lib.devtools.Flog
import dev.patrickgold.florisboard.lib.devtools.LogTopic
import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.runtime.initAndroid
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
import org.florisboard.lib.kotlin.tryOrNull
import org.florisboard.libnative.dummyAdd
@@ -63,8 +69,9 @@ class FlorisApplication : Application() {
}
}
private val prefs by florisPreferenceModel()
private val mainHandler by lazy { Handler(mainLooper) }
private val scope = CoroutineScope(Dispatchers.Default)
val preferenceStoreLoaded = MutableStateFlow(false)
val cacheManager = lazy { CacheManager(this) }
val clipboardManager = lazy { ClipboardManager(this) }
@@ -80,7 +87,6 @@ class FlorisApplication : Application() {
super.onCreate()
FlorisApplicationReference = WeakReference(this)
try {
JetPref.configure(saveIntervalMs = 500)
Flog.install(
context = this,
isFloggingEnabled = BuildConfig.DEBUG,
@@ -108,7 +114,14 @@ class FlorisApplication : Application() {
fun init() {
cacheDir?.deleteContentsRecursively()
prefs.initializeBlocking(this)
scope.launch {
val result = FlorisPreferenceStore.initAndroid(
context = this@FlorisApplication,
datastoreName = FlorisPreferenceModel.NAME,
)
Log.i("PREFS", result.toString())
preferenceStoreLoaded.value = true
}
extensionManager.value.init()
clipboardManager.value.initializeForContext(this)
DictionaryManager.init(this)

View File

@@ -75,8 +75,8 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import dev.patrickgold.florisboard.app.FlorisAppActivity
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.devtools.DevtoolsOverlay
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.ImeUiMode
import dev.patrickgold.florisboard.ime.clipboard.ClipboardInputLayout
import dev.patrickgold.florisboard.ime.core.SelectSubtypePanel
@@ -119,8 +119,9 @@ import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.isOrientationLandscape
import org.florisboard.lib.android.isOrientationPortrait
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.showShortToastSync
import org.florisboard.lib.android.systemServiceOrNull
import org.florisboard.lib.kotlin.collectLatestIn
import org.florisboard.lib.kotlin.collectIn
import org.florisboard.lib.snygg.ui.SnyggBox
import org.florisboard.lib.snygg.ui.SnyggButton
import org.florisboard.lib.snygg.ui.SnyggRow
@@ -246,12 +247,12 @@ class FlorisImeService : LifecycleInputMethodService() {
}
}
}
ims.showShortToast("Failed to find voice IME, do you have one installed?")
ims.showShortToastSync("Failed to find voice IME, do you have one installed?")
return false
}
}
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val editorInstance by editorInstance()
private val keyboardManager by keyboardManager()
private val nlpManager by nlpManager()
@@ -277,21 +278,21 @@ class FlorisImeService : LifecycleInputMethodService() {
super.onCreate()
FlorisImeServiceReference = WeakReference(this)
WindowCompat.setDecorFitsSystemWindows(window.window!!, false)
subtypeManager.activeSubtypeFlow.collectLatestIn(lifecycleScope) { subtype ->
subtypeManager.activeSubtypeFlow.collectIn(lifecycleScope) { subtype ->
val config = Configuration(resources.configuration)
if (prefs.localization.displayKeyboardLabelsInSubtypeLanguage.get()) {
config.setLocale(subtype.primaryLocale.base)
}
resourcesContext = createConfigurationContext(config)
}
prefs.localization.displayKeyboardLabelsInSubtypeLanguage.observeForever { shouldSync ->
prefs.localization.displayKeyboardLabelsInSubtypeLanguage.asFlow().collectIn(lifecycleScope) { shouldSync ->
val config = Configuration(resources.configuration)
if (shouldSync) {
config.setLocale(subtypeManager.activeSubtype.primaryLocale.base)
}
resourcesContext = createConfigurationContext(config)
}
prefs.physicalKeyboard.showOnScreenKeyboard.observeForever {
prefs.physicalKeyboard.showOnScreenKeyboard.asFlow().collectIn(lifecycleScope) {
updateInputViewShown()
}
@Suppress("DEPRECATION") // We do not retrieve the wallpaper but only listen to changes
@@ -408,7 +409,6 @@ class FlorisImeService : LifecycleInputMethodService() {
flogInfo(LogTopic.IMS_EVENTS)
}
isWindowShown = true
themeManager.updateActiveTheme()
inputFeedbackController.updateSystemPrefsState()
}

View File

@@ -20,7 +20,7 @@ import android.service.textservice.SpellCheckerService
import android.view.textservice.SentenceSuggestionsInfo
import android.view.textservice.SuggestionsInfo
import android.view.textservice.TextInfo
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
import dev.patrickgold.florisboard.ime.nlp.SpellingLanguageMode
@@ -33,7 +33,7 @@ import kotlinx.coroutines.runBlocking
import org.florisboard.lib.kotlin.map
class FlorisSpellCheckerService : SpellCheckerService() {
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val dictionaryManager get() = DictionaryManager.default()
private val nlpManager by nlpManager()
private val subtypeManager by subtypeManager()

View File

@@ -57,7 +57,8 @@ import dev.patrickgold.florisboard.lib.compose.ColorPreferenceSerializer
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
import dev.patrickgold.florisboard.lib.observeAsTransformingState
import dev.patrickgold.florisboard.lib.util.VersionName
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.annotations.Preferences
import dev.patrickgold.jetpref.datastore.jetprefDataStoreOf
import dev.patrickgold.jetpref.datastore.model.LocalTime
import dev.patrickgold.jetpref.datastore.model.PreferenceData
import dev.patrickgold.jetpref.datastore.model.PreferenceMigrationEntry
@@ -69,9 +70,14 @@ import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.isOrientationPortrait
import org.florisboard.lib.color.DEFAULT_GREEN
fun florisPreferenceModel() = JetPref.getOrCreatePreferenceModel(AppPrefs::class, ::AppPrefs)
val FlorisPreferenceStore = jetprefDataStoreOf(FlorisPreferenceModel::class)
@Preferences
abstract class FlorisPreferenceModel : PreferenceModel() {
companion object {
const val NAME = "florisboard-app-prefs"
}
class AppPrefs : PreferenceModel("florisboard-app-prefs") {
val clipboard = Clipboard()
inner class Clipboard {
val useInternalClipboard = boolean(
@@ -758,11 +764,11 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
},
serializer = ColorPreferenceSerializer,
)
val sunriseTime = time(
val sunriseTime = localTime(
key = "theme__sunrise_time",
default = LocalTime(6, 0),
)
val sunsetTime = time(
val sunsetTime = localTime(
key = "theme__sunset_time",
default = LocalTime(18, 0),
)

View File

@@ -39,12 +39,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController
import androidx.navigation.compose.rememberNavController
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.apptheme.FlorisAppTheme
import dev.patrickgold.florisboard.app.ext.ExtensionImportScreenType
import dev.patrickgold.florisboard.app.setup.NotificationPermissionState
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.lib.FlorisLocale
import dev.patrickgold.florisboard.lib.compose.LocalPreviewFieldController
@@ -59,6 +61,8 @@ import dev.patrickgold.jetpref.datastore.ui.ProvideDefaultDialogPrefStrings
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.hideAppIcon
import org.florisboard.lib.android.showAppIcon
import org.florisboard.lib.kotlin.collectIn
import java.util.concurrent.atomic.AtomicBoolean
enum class AppTheme(val id: String) {
AUTO("auto"),
@@ -73,7 +77,8 @@ val LocalNavController = staticCompositionLocalOf<NavController> {
}
class FlorisAppActivity : ComponentActivity() {
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val appContext by appContext()
private val cacheManager by cacheManager()
private var appTheme by mutableStateOf(AppTheme.AUTO)
private var showAppIcon = true
@@ -83,37 +88,37 @@ class FlorisAppActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Splash screen should be installed before calling super.onCreate()
installSplashScreen().apply {
setKeepOnScreenCondition { !prefs.datastoreReadyStatus.get() }
setKeepOnScreenCondition { !appContext.preferenceStoreLoaded.value }
}
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
prefs.other.settingsTheme.observe(this) {
prefs.other.settingsTheme.asFlow().collectIn(lifecycleScope) {
appTheme = it
}
prefs.other.settingsLanguage.observe(this) {
prefs.other.settingsLanguage.asFlow().collectIn(lifecycleScope) {
val config = Configuration(resources.configuration)
val locale = if (it == "auto") FlorisLocale.default() else FlorisLocale.fromTag(it)
config.setLocale(locale.base)
resourcesContext = createConfigurationContext(config)
}
if (AndroidVersion.ATMOST_API28_P) {
prefs.other.showAppIcon.observe(this) {
prefs.other.showAppIcon.asFlow().collectIn(lifecycleScope) {
showAppIcon = it
}
}
//Check if android 13+ is running and the NotificationPermission is not set
if (AndroidVersion.ATLEAST_API33_T &&
prefs.internal.notificationPermissionState.get() == NotificationPermissionState.NOT_SET
) {
// update pref value to show the setup screen again again
prefs.internal.isImeSetUp.set(false)
}
// We defer the setContent call until the datastore model is loaded, until then the splash screen stays drawn
prefs.datastoreReadyStatus.observe(this) { isModelLoaded ->
if (!isModelLoaded) return@observe
val isModelLoaded = AtomicBoolean(false)
appContext.preferenceStoreLoaded.collectIn(lifecycleScope) { loaded ->
if (!loaded || isModelLoaded.getAndSet(true)) return@collectIn
// Check if android 13+ is running and the NotificationPermission is not set
if (AndroidVersion.ATLEAST_API33_T &&
prefs.internal.notificationPermissionState.get() == NotificationPermissionState.NOT_SET
) {
// update pref value to show the setup screen again
prefs.internal.isImeSetUp.set(false)
}
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
setContent {
ProvideLocalizedResources(resourcesContext) {

View File

@@ -29,9 +29,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowCompat
import dev.patrickgold.florisboard.app.AppTheme
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.color.ColorMappings
/*private val AmoledDarkColorPalette = darkColorScheme(
@@ -79,7 +78,7 @@ fun getColorScheme(
context: Context,
theme: AppTheme,
): ColorScheme {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val accentColor by prefs.other.accentColor.observeAsState()
val isDark = isSystemInDarkTheme()

View File

@@ -37,6 +37,7 @@ import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
import java.util.Locale
@@ -66,9 +67,9 @@ fun AndroidLocalesScreen() = FlorisScreen {
out.appendLine()
}
}
context.showLongToast("Exported available system locales to \"${txtFile.path}\"")
context.showLongToastSync("Exported available system locales to \"${txtFile.path}\"")
} catch (e: Exception) {
context.showLongToast(
context.showLongToastSync(
R.string.error__snackbar_message_template,
"error_message" to e.message.toString(),
)

View File

@@ -29,7 +29,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
@@ -40,7 +39,7 @@ import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.editorInstance
import dev.patrickgold.florisboard.ime.keyboard.CachedLayout
@@ -64,7 +63,7 @@ private val DateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ss", FlorisLocale.
@Composable
fun DevtoolsOverlay(modifier: Modifier = Modifier) {
val context = LocalContext.current
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val keyboardManager by context.keyboardManager()
val themeManager by context.themeManager()
@@ -75,7 +74,7 @@ fun DevtoolsOverlay(modifier: Modifier = Modifier) {
val showInlineAutofillOverlay by prefs.devtools.showInlineAutofillOverlay.observeAsState()
val debugLayoutResult by keyboardManager.layoutManager.debugLayoutComputationResultFlow.collectAsState()
val themeInfo by themeManager.activeThemeInfo.observeAsState()
val themeInfo by themeManager.activeThemeInfo.collectAsState()
CompositionLocalProvider(
LocalContentColor provides Color.White,
@@ -97,7 +96,7 @@ fun DevtoolsOverlay(modifier: Modifier = Modifier) {
if (devtoolsEnabled && showInlineAutofillOverlay && AndroidVersion.ATLEAST_API30_R) {
DevtoolsInlineAutofillOverlay()
}
val loadFailure = themeInfo?.loadFailure
val loadFailure = themeInfo.loadFailure
if (loadFailure != null) {
DevtoolsStylesheetFailedToLoadOverlay(loadFailure)
}
@@ -163,13 +162,13 @@ private fun DevtoolsLastLayoutComputationOverlay(debugLayoutResult: DebugLayoutC
return@DevtoolsOverlayBox
}
DevtoolsSubGroup(title = "main") {
PrintResult(debugLayoutResult!!.main)
PrintResult(debugLayoutResult.main)
}
DevtoolsSubGroup(title = "mod") {
PrintResult(debugLayoutResult!!.mod)
PrintResult(debugLayoutResult.mod)
}
DevtoolsSubGroup(title = "ext") {
PrintResult(debugLayoutResult!!.ext)
PrintResult(debugLayoutResult.ext)
}
}
}

View File

@@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
@@ -38,7 +39,9 @@ import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import kotlinx.coroutines.launch
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.showLongToastSync
class DebugOnPurposeCrashException : Exception(
"Success! The app crashed purposely to display this beautiful screen we all love :)"
@@ -52,6 +55,7 @@ fun DevtoolsScreen() = FlorisScreen {
val context = LocalContext.current
val navController = LocalNavController.current
val extensionManager by context.extensionManager()
val scope = rememberCoroutineScope()
val (showDialog, setShowDialog) = remember { mutableStateOf(false) }
@@ -110,15 +114,17 @@ fun DevtoolsScreen() = FlorisScreen {
title = stringRes(R.string.devtools__reset_quick_actions_to_default__label),
summary = stringRes(R.string.devtools__reset_quick_actions_to_default__summary),
onClick = {
prefs.smartbar.actionArrangement.set(QuickActionArrangement.Default)
context.showLongToast(R.string.devtools__reset_quick_actions_to_default__toast_success)
scope.launch {
prefs.smartbar.actionArrangement.set(QuickActionArrangement.Default)
}
context.showLongToastSync(R.string.devtools__reset_quick_actions_to_default__toast_success)
},
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
title = stringRes(R.string.devtools__reset_flag__label, "flag_name" to "isImeSetUp"),
summary = stringRes(R.string.devtools__reset_flag_is_ime_set_up__summary),
onClick = { prefs.internal.isImeSetUp.set(false) },
onClick = { scope.launch { prefs.internal.isImeSetUp.set(false) } },
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
@@ -200,14 +206,14 @@ fun DevtoolsScreen() = FlorisScreen {
title = "keyboardExtensions",
summary = extensionManager.keyboardExtensions.internalModuleDir.absolutePath,
onClick = {
context.showLongToast(extensionManager.keyboardExtensions.internalModuleDir.absolutePath)
context.showLongToastSync(extensionManager.keyboardExtensions.internalModuleDir.absolutePath)
},
)
Preference(
title = "themes",
summary = extensionManager.themes.internalModuleDir.absolutePath,
onClick = {
context.showLongToast(extensionManager.themes.internalModuleDir.absolutePath)
context.showLongToastSync(extensionManager.themes.internalModuleDir.absolutePath)
},
)
}

View File

@@ -38,7 +38,7 @@ import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.sp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.lib.compose.FlorisButton
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
@@ -47,6 +47,7 @@ import dev.patrickgold.florisboard.lib.compose.florisScrollbar
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.devtools.Devtools
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.showShortToastSync
// TODO: This screen is just a quick thrown-together thing and needs further enhancing in the UI
@Composable
@@ -54,7 +55,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
title = stringRes(R.string.devtools__debuglog__title)
scrollable = false
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val clipboardManager by context.clipboardManager()
@@ -74,7 +75,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(debugLog!!.joinToString("\n"))
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
context.showShortToastSync(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
},
modifier = Modifier,
text = stringRes(R.string.devtools__debuglog__copy_log),
@@ -83,7 +84,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(formattedDebugLog!!.joinToString("\n"))
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
context.showShortToastSync(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
},
text = stringRes(R.string.devtools__debuglog__copy_for_github),
enabled = debugLog != null,

View File

@@ -59,7 +59,9 @@ import java.util.*
import org.florisboard.lib.android.query
import org.florisboard.lib.android.readToFile
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.showShortToastSync
import org.florisboard.lib.kotlin.io.parentDir
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
@@ -186,9 +188,9 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
allowOutsideDismissal = true,
onNeutral = {
if (file.delete()) {
context.showShortToast("Successfully deleted")
context.showShortToastSync("Successfully deleted")
} else {
context.showShortToast("Failed to delete")
context.showShortToastSync("Failed to delete")
}
dialogFile = null
version++
@@ -196,18 +198,18 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
onConfirm = {
val newFile = file.parentFile!!.subFile(fileNameInput).canonicalFile
if (newFile.parentFile != file.canonicalFile.parentFile) {
context.showLongToast("Invalid file name!")
context.showLongToastSync("Invalid file name!")
return@JetPrefAlertDialog
}
if (newFile.exists()) {
context.showShortToast("Filename already exists.")
context.showShortToastSync("Filename already exists.")
return@JetPrefAlertDialog
}
val success = file.renameTo(newFile)
if (success) {
context.showShortToast("Successfully renamed")
context.showShortToastSync("Successfully renamed")
} else {
context.showShortToast("Failed to rename the file.")
context.showShortToastSync("Failed to rename the file.")
}
dialogFile = null
version++
@@ -257,13 +259,13 @@ fun ExtensionEditFilesScreen(workspace: CacheManager.ExtEditorWorkspace<*>) = Fl
dir.mkdirs()
val file = dir.subFile(fileName)
if (file.parentDir != workspace.extDir.subDir(dest)) {
context.showShortToast("Invalid file name")
context.showShortToastSync("Invalid file name")
} else if (file.exists()) {
context.showShortToast("File already exists")
context.showShortToastSync("File already exists")
} else {
val tempFile = result.first
if (!tempFile.renameTo(file)) {
context.showShortToast("Failed to rename file")
context.showShortToastSync("Failed to rename file")
tempFile.delete()
}
currentImportDest = null

View File

@@ -90,6 +90,7 @@ import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
import java.util.*
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
@@ -287,7 +288,7 @@ private fun EditScreen(
stylesheetFile.writeText(stylesheet)
}.onFailure {
// TODO: better error handling
context.showLongToast(it.message.toString())
context.showLongToastSync(it.message.toString())
return
}
} else {
@@ -665,7 +666,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
when (createFrom) {
CreateFrom.EMPTY -> {
if (editor.themes.any { it.id == newId.trim() }) {
context.showLongToast("A theme with this ID already exists!")
context.showLongToastSync("A theme with this ID already exists!")
} else {
val componentEditor = ThemeExtensionComponentEditor(
id = newId.trim(),

View File

@@ -27,6 +27,7 @@ import org.florisboard.lib.android.showLongToast
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.ext.Extension
import dev.patrickgold.florisboard.lib.ext.ExtensionDefaults
import org.florisboard.lib.android.showLongToastSync
@Composable
fun ExtensionExportScreen(id: String) {
@@ -61,9 +62,9 @@ private fun ExportScreen(ext: Extension) = FlorisScreen {
return@rememberLauncherForActivityResult
}
runCatching { extensionManager.export(ext, uri) }.onSuccess {
context.showLongToast(R.string.ext__export__success)
context.showLongToastSync(R.string.ext__export__success)
}.onFailure { error ->
context.showLongToast(R.string.ext__export__failure, "error_message" to error.localizedMessage)
context.showLongToastSync(R.string.ext__export__failure, "error_message" to error.localizedMessage)
}
navController.popBackStack()
},

View File

@@ -61,6 +61,7 @@ import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.io.FileRegistry
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.kotlin.resultOk
enum class ExtensionImportScreenType(
@@ -188,10 +189,10 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
}
}.onSuccess {
workspace.close()
context.showLongToast(R.string.ext__import__success)
context.showLongToastSync(R.string.ext__import__success)
navController.popBackStack()
}.onFailure { error ->
context.showLongToast(R.string.ext__import__failure, "error_message" to error.localizedMessage)
context.showLongToastSync(R.string.ext__import__failure, "error_message" to error.localizedMessage)
}
}
}

View File

@@ -62,6 +62,7 @@ import dev.patrickgold.florisboard.lib.ext.Extension
import dev.patrickgold.florisboard.lib.ext.ExtensionMaintainer
import dev.patrickgold.florisboard.lib.ext.ExtensionMeta
import dev.patrickgold.florisboard.lib.io.FlorisRef
import org.florisboard.lib.android.showLongToastSync
@Composable
fun ExtensionViewScreen(id: String) {
@@ -202,7 +203,7 @@ private fun ViewScreen(ext: Extension) = FlorisScreen {
}.onSuccess {
navController.popBackStack()
}.onFailure { error ->
context.showLongToast(
context.showLongToastSync(
R.string.error__snackbar_message,
"error_message" to error.localizedMessage,
)

View File

@@ -32,6 +32,7 @@ import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -41,6 +42,8 @@ import androidx.core.app.ShareCompat
import androidx.core.content.FileProvider
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.clipboardManager
@@ -57,11 +60,14 @@ import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
import dev.patrickgold.florisboard.lib.io.FileRegistry
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.jetpref.datastore.jetprefDatastoreDir
import dev.patrickgold.jetpref.datastore.runtime.AndroidAppDataStorage
import dev.patrickgold.jetpref.datastore.runtime.FileBasedStorage
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import kotlinx.coroutines.launch
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.android.writeFromFile
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
@@ -136,6 +142,7 @@ fun BackupScreen() = FlorisScreen {
val navController = LocalNavController.current
val context = LocalContext.current
val cacheManager by context.cacheManager()
val scope = rememberCoroutineScope()
var backupDestination by remember { mutableStateOf(Backup.Destination.FILE_SYS) }
val backupFilesSelector = remember { Backup.FilesSelector() }
@@ -155,22 +162,24 @@ fun BackupScreen() = FlorisScreen {
context.contentResolver.writeFromFile(uri, backupWorkspace!!.zipFile)
backupWorkspace!!.close()
}.onSuccess {
context.showLongToast(R.string.backup_and_restore__back_up__success)
context.showLongToastSync(R.string.backup_and_restore__back_up__success)
navController.popBackStack()
}.onFailure { error ->
flogError { error.stackTraceToString() }
context.showLongToast(R.string.backup_and_restore__back_up__failure, "error_message" to error.message)
context.showLongToastSync(R.string.backup_and_restore__back_up__failure, "error_message" to error.message)
backupWorkspace = null
}
},
)
fun prepareBackupWorkspace() {
suspend fun prepareBackupWorkspace() {
val workspace = cacheManager.backupAndRestore.new()
if (backupFilesSelector.jetprefDatastore) {
context.jetprefDatastoreDir.let { dir ->
dir.copyRecursively(workspace.inputDir.subDir(dir.name))
}
val fileBasedStorage = workspace.inputDir
.subDir(AndroidAppDataStorage.JETPREF_DIR_NAME)
.subFile("${FlorisPreferenceModel.NAME}.${AndroidAppDataStorage.JETPREF_FILE_EXT}")
.let { FileBasedStorage(it.path) }
FlorisPreferenceStore.export(fileBasedStorage).getOrThrow()
}
val workspaceFilesDir = workspace.inputDir.subDir("files")
if (backupFilesSelector.imeKeyboard) {
@@ -225,7 +234,7 @@ fun BackupScreen() = FlorisScreen {
backupWorkspace = workspace
}
fun prepareAndPerformBackup() {
suspend fun prepareAndPerformBackup() {
runCatching {
if (backupWorkspace == null || backupWorkspace!!.isClosed()) {
prepareBackupWorkspace()
@@ -265,7 +274,7 @@ fun BackupScreen() = FlorisScreen {
)
ButtonBarButton(
onClick = {
prepareAndPerformBackup()
scope.launch { prepareAndPerformBackup() }
},
text = stringRes(R.string.action__back_up),
enabled = backupFilesSelector.atLeastOneSelected(),

View File

@@ -43,8 +43,9 @@ import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.cacheManager
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
@@ -60,13 +61,16 @@ import dev.patrickgold.florisboard.lib.compose.defaultFlorisOutlinedBox
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.ext.ExtensionManager
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.jetpref.datastore.JetPref
import dev.patrickgold.jetpref.datastore.runtime.AndroidAppDataStorage
import dev.patrickgold.jetpref.datastore.runtime.FileBasedStorage
import dev.patrickgold.jetpref.datastore.runtime.ImportStrategy
import dev.patrickgold.jetpref.datastore.ui.Preference
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.florisboard.lib.android.readToFile
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
import org.florisboard.lib.kotlin.io.readJson
import org.florisboard.lib.kotlin.io.subDir
@@ -79,11 +83,6 @@ object Restore {
const val MIN_VERSION_CODE = 64
const val PACKAGE_NAME = "dev.patrickgold.florisboard"
const val BACKUP_ARCHIVE_FILE_NAME = "backup.zip"
enum class Mode {
MERGE,
ERASE_AND_OVERWRITE;
}
}
@Composable
@@ -91,13 +90,12 @@ fun RestoreScreen() = FlorisScreen {
title = stringRes(R.string.backup_and_restore__restore__title)
previewFieldVisible = false
val prefs by florisPreferenceModel()
val navController = LocalNavController.current
val context = LocalContext.current
val cacheManager by context.cacheManager()
val restoreFilesSelector = remember { Backup.FilesSelector() }
var restoreMode by remember { mutableStateOf(Restore.Mode.MERGE) }
var importStrategy by remember { mutableStateOf(ImportStrategy.Merge) }
// TODO: rememberCoroutineScope() is unusable because it provides the scope in a cancelled state, which does
// not make sense at all. I suspect that this is a bug and once it is resolved we can use it here again.
val restoreScope = remember { CoroutineScope(Dispatchers.Main) }
@@ -138,7 +136,7 @@ fun RestoreScreen() = FlorisScreen {
}
restoreWorkspace = workspace
}.onFailure { error ->
context.showLongToast(
context.showLongToastSync(
R.string.backup_and_restore__restore__failure,
"error_message" to error.localizedMessage,
)
@@ -148,15 +146,13 @@ fun RestoreScreen() = FlorisScreen {
suspend fun performRestore() {
val workspace = restoreWorkspace!!
val shouldReset = restoreMode == Restore.Mode.ERASE_AND_OVERWRITE
val shouldReset = importStrategy == ImportStrategy.Erase
if (restoreFilesSelector.jetprefDatastore) {
val datastoreFile = workspace.outputDir
.subDir(JetPref.JETPREF_DIR_NAME)
.subFile("${prefs.name}.${JetPref.JETPREF_FILE_EXT}")
if (datastoreFile.exists()) {
prefs.datastorePersistenceHandler?.loadPrefs(datastoreFile, shouldReset)
prefs.datastorePersistenceHandler?.persistPrefs()
}
val fileBasedStorage = workspace.outputDir
.subDir(AndroidAppDataStorage.JETPREF_DIR_NAME)
.subFile("${FlorisPreferenceModel.NAME}.${AndroidAppDataStorage.JETPREF_FILE_EXT}")
.let { FileBasedStorage(it.path) }
FlorisPreferenceStore.import(importStrategy, fileBasedStorage).getOrThrow()
}
val workspaceFilesDir = workspace.outputDir.subDir("files")
if (restoreFilesSelector.imeKeyboard) {
@@ -275,16 +271,16 @@ fun RestoreScreen() = FlorisScreen {
) {
RadioListItem(
onClick = {
restoreMode = Restore.Mode.MERGE
importStrategy = ImportStrategy.Merge
},
selected = restoreMode == Restore.Mode.MERGE,
selected = importStrategy == ImportStrategy.Merge,
text = stringRes(R.string.backup_and_restore__restore__mode_merge),
)
RadioListItem(
onClick = {
restoreMode = Restore.Mode.ERASE_AND_OVERWRITE
importStrategy = ImportStrategy.Erase
},
selected = restoreMode == Restore.Mode.ERASE_AND_OVERWRITE,
selected = importStrategy == ImportStrategy.Erase,
text = stringRes(R.string.backup_and_restore__restore__mode_erase_and_overwrite),
)
}
@@ -293,7 +289,7 @@ fun RestoreScreen() = FlorisScreen {
runCatching {
restoreDataFromFileSystemLauncher.launch("*/*")
}.onFailure { error ->
context.showLongToast(
context.showLongToastSync(
R.string.backup_and_restore__restore__failure,
"error_message" to error.localizedMessage,
)

View File

@@ -68,6 +68,7 @@ import dev.patrickgold.jetpref.material.ui.JetPrefTextField
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.android.stringRes
private val AllLanguagesLocale = FlorisLocale.from(language = "zz")
@@ -143,16 +144,16 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDatabase()
}
if (db == null) {
context.showLongToast("Database handle is null, failed to import")
context.showLongToastSync("Database handle is null, failed to import")
return@rememberLauncherForActivityResult
}
runCatching {
db.importCombinedList(context, uri)
}.onSuccess {
buildUi()
context.showLongToast(R.string.settings__udm__dictionary_import_success)
context.showLongToastSync(R.string.settings__udm__dictionary_import_success)
}.onFailure { error ->
context.showLongToast("Error: ${error.localizedMessage}")
context.showLongToastSync("Error: ${error.localizedMessage}")
}
},
)
@@ -168,15 +169,15 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDatabase()
}
if (db == null) {
context.showLongToast("Database handle is null, failed to export")
context.showLongToastSync("Database handle is null, failed to export")
return@rememberLauncherForActivityResult
}
runCatching {
db.exportCombinedList(context, uri)
}.onSuccess {
context.showLongToast(R.string.settings__udm__dictionary_export_success)
context.showLongToastSync(R.string.settings__udm__dictionary_export_success)
}.onFailure { error ->
context.showLongToast("Error: ${error.localizedMessage}")
context.showLongToastSync("Error: ${error.localizedMessage}")
}
},
)

View File

@@ -41,10 +41,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.app.ext.ExtensionImportScreenType
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.nlp.LanguagePackComponent
import org.florisboard.lib.android.showLongToast
@@ -61,6 +61,7 @@ import dev.patrickgold.florisboard.lib.observeAsNonNullState
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import org.florisboard.lib.android.showLongToastSync
enum class LanguagePackManagerScreenAction(val id: String) {
MANAGE("manage-installed-language-packs");
@@ -75,7 +76,7 @@ fun LanguagePackManagerScreen(action: LanguagePackManagerScreenAction?) = Floris
else -> error("LanguagePack manager screen action must not be null")
})
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val navController = LocalNavController.current
val context = LocalContext.current
val extensionManager by context.extensionManager()
@@ -199,7 +200,7 @@ fun LanguagePackManagerScreen(action: LanguagePackManagerScreenAction?) = Floris
runCatching {
extensionManager.delete(languagePackExtToDelete!!)
}.onFailure { error ->
context.showLongToast(
context.showLongToastSync(
R.string.error__snackbar_message,
"error_message" to error.localizedMessage,
)

View File

@@ -44,8 +44,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.lib.FlorisLocale
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
@@ -61,7 +61,7 @@ fun SelectLocaleScreen() = FlorisScreen {
title = stringRes(R.string.settings__localization__subtype_select_locale)
scrollable = false
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val navController = LocalNavController.current
val displayLanguageNamesIn by prefs.localization.displayLanguageNamesIn.observeAsState()

View File

@@ -58,9 +58,9 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.Observer
import androidx.lifecycle.compose.LocalLifecycleOwner
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.core.SubtypeJsonConfig
@@ -86,7 +86,6 @@ import dev.patrickgold.florisboard.subtypeManager
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.material.ui.JetPrefAlertDialog
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import kotlinx.serialization.encodeToString
private val SelectComponentName = ExtensionComponentName("00", "00")
private val SelectNlpProviderId = SelectComponentName.toString()
@@ -186,7 +185,7 @@ fun SubtypeEditorScreen(id: Long?) = FlorisScreen {
val selectValue = stringRes(R.string.settings__localization__subtype_select_placeholder)
val selectListValues = remember(selectValue) { listOf(selectValue) }
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val navController = LocalNavController.current
val context = LocalContext.current
val configuration = LocalConfiguration.current

View File

@@ -27,8 +27,8 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistory
import dev.patrickgold.florisboard.ime.media.emoji.EmojiHistoryHelper
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSkinTone
@@ -52,7 +52,7 @@ fun MediaScreen() = FlorisScreen {
previewFieldVisible = true
iconSpaceReserved = true
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
var shouldDelete by remember { mutableStateOf<ShouldDelete?>(null) }
val scope = rememberCoroutineScope()

View File

@@ -68,7 +68,7 @@ fun SmartbarScreen() = FlorisScreen {
// TODO: schedule to remove this preference in the future, but keep it for now so users
// know why the setting is not available anymore. Also force enable it for UI display.
SideEffect {
prefs.smartbar.sharedActionsAutoExpandCollapse.set(true)
// prefs.smartbar.sharedActionsAutoExpandCollapse.set(true)
}
SwitchPreference(
prefs.smartbar.sharedActionsAutoExpandCollapse,

View File

@@ -96,6 +96,7 @@ import dev.patrickgold.jetpref.material.ui.JetPrefDropdown
import dev.patrickgold.jetpref.material.ui.JetPrefTextField
import dev.patrickgold.jetpref.material.ui.JetPrefTextFieldDefaults
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.showShortToastSync
import org.florisboard.lib.android.stringRes
import org.florisboard.lib.kotlin.curlyFormat
import org.florisboard.lib.snygg.SnyggAnnotationRule
@@ -406,7 +407,7 @@ private fun EditCodeValueDialog(
}
if (!isFlorisBoardEnabled || !isFlorisBoardSelected) {
lastRecordingToast?.cancel()
lastRecordingToast = context.showShortToast(
lastRecordingToast = context.showShortToastSync(
R.string.settings__theme_editor__code_recording_requires_default_ime_floris,
"app_name" to context.stringRes(R.string.floris_app_name),
)
@@ -432,12 +433,12 @@ private fun EditCodeValueDialog(
val defaultReceiver = keyboardManager.inputEventDispatcher.keyEventReceiver
keyboardManager.inputEventDispatcher.keyEventReceiver = receiver
lastRecordingToast?.cancel()
lastRecordingToast = context.showShortToast(R.string.settings__theme_editor__code_recording_started)
lastRecordingToast = context.showShortToastSync(R.string.settings__theme_editor__code_recording_started)
focusRequester.requestFocus()
onDispose {
keyboardManager.inputEventDispatcher.keyEventReceiver = defaultReceiver
lastRecordingToast?.cancel()
lastRecordingToast = context.showShortToast(R.string.settings__theme_editor__code_recording_stopped)
lastRecordingToast = context.showShortToastSync(R.string.settings__theme_editor__code_recording_stopped)
}
}
}

View File

@@ -20,8 +20,8 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.runtime.Composable
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.PreferenceLayout
@@ -37,7 +37,7 @@ fun FineTuneDialog(onDismiss: () -> Unit) {
onDismiss = onDismiss,
contentPadding = FineTuneContentPadding,
) {
PreferenceLayout(florisPreferenceModel(), iconSpaceReserved = false) {
PreferenceLayout(FlorisPreferenceStore, iconSpaceReserved = false) {
ListPreference(
listPref = prefs.theme.editorLevel,
title = stringRes(R.string.settings__theme_editor__fine_tune__level),

View File

@@ -30,7 +30,6 @@ import androidx.compose.material.icons.automirrored.filled.FormatAlignLeft
import androidx.compose.material.icons.automirrored.filled.FormatAlignRight
import androidx.compose.material.icons.automirrored.filled.WrapText
import androidx.compose.material.icons.filled.AttachFile
import androidx.compose.material.icons.filled.CheckBox
import androidx.compose.material.icons.filled.CheckBoxOutlineBlank
import androidx.compose.material.icons.filled.FontDownload
import androidx.compose.material.icons.filled.FormatAlignCenter
@@ -57,7 +56,7 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.snygg.value.SnyggCutCornerDpShapeValue
import org.florisboard.lib.snygg.value.SnyggDefinedVarValue
@@ -121,7 +120,7 @@ internal fun SnyggValueIcon(
modifier: Modifier = Modifier,
spec: SnyggValueIcon.Spec = SnyggValueIcon.Normal,
) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val accentColor by prefs.theme.accentColor.observeAsState()

View File

@@ -70,9 +70,9 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.apptheme.Shapes
import dev.patrickgold.florisboard.app.ext.ExtensionComponentView
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponentEditor
@@ -100,6 +100,7 @@ import dev.patrickgold.jetpref.material.ui.JetPrefTextField
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.kotlin.io.subFile
import org.florisboard.lib.snygg.SnyggAnnotationRule
import org.florisboard.lib.snygg.SnyggElementRule
@@ -145,7 +146,7 @@ fun ThemeEditorScreen(
title = stringRes(R.string.ext__editor__edit_component__title_theme)
scrollable = false
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val focusManager = LocalFocusManager.current
val themeManager by context.themeManager()
@@ -309,14 +310,14 @@ fun ThemeEditorScreen(
}
DisposableEffect(workspace.version) {
themeManager.previewThemeInfo = ThemeManager.ThemeInfo.DEFAULT.copy(
themeManager.previewThemeInfo.value = ThemeManager.ThemeInfo.DEFAULT.copy(
name = extPreviewTheme(System.currentTimeMillis().toString()),
config = editor.build(),
stylesheet = stylesheetEditor.build(),
loadedDir = workspace.extDir,
)
onDispose {
themeManager.previewThemeInfo = null
themeManager.previewThemeInfo.value = null
}
}
@@ -633,7 +634,7 @@ private fun ComponentMetaEditorDialog(
if (!allFieldsValid) {
showValidationErrors = true
} else if (id != editor.id && (workspace.editor as? ThemeExtensionEditor)?.themes?.find { it.id == id.trim() } != null) {
context.showLongToast("A theme with this ID already exists!")
context.showLongToastSync("A theme with this ID already exists!")
} else {
workspace.update {
editor.id = id.trim()

View File

@@ -29,10 +29,11 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
@@ -45,6 +46,7 @@ import dev.patrickgold.florisboard.lib.observeAsNonNullState
import dev.patrickgold.florisboard.themeManager
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.material.ui.JetPrefListItem
import kotlinx.coroutines.launch
enum class ThemeManagerScreenAction(val id: String) {
SELECT_DAY("select-day"),
@@ -60,10 +62,11 @@ fun ThemeManagerScreen(action: ThemeManagerScreenAction?) = FlorisScreen {
})
previewFieldVisible = true
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val extensionManager by context.extensionManager()
val themeManager by context.themeManager()
val scope = rememberCoroutineScope()
val indexedThemeExtensions by extensionManager.themes.observeAsNonNullState()
val extGroupedThemes = remember(indexedThemeExtensions) {
@@ -83,7 +86,7 @@ fun ThemeManagerScreen(action: ThemeManagerScreenAction?) = FlorisScreen {
val extComponentName = ExtensionComponentName(extId, componentId)
when (action) {
ThemeManagerScreenAction.SELECT_DAY,
ThemeManagerScreenAction.SELECT_NIGHT -> {
ThemeManagerScreenAction.SELECT_NIGHT -> scope.launch {
getThemeIdPref().set(extComponentName)
}
}
@@ -96,9 +99,9 @@ fun ThemeManagerScreen(action: ThemeManagerScreenAction?) = FlorisScreen {
content {
DisposableEffect(activeThemeId) {
themeManager.previewThemeId = activeThemeId
themeManager.previewThemeId.value = activeThemeId
onDispose {
themeManager.previewThemeId = null
themeManager.previewThemeId.value = null
}
}
val grayColor = LocalContentColor.current.copy(alpha = 0.56f)

View File

@@ -24,8 +24,8 @@ import androidx.compose.material.icons.filled.DarkMode
import androidx.compose.material.icons.filled.LightMode
import androidx.compose.material.icons.filled.WbTwilight
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
@@ -59,8 +59,8 @@ fun ThemeScreen() = FlorisScreen {
@Composable
fun ThemeManager.getThemeLabel(id: ExtensionComponentName): String {
val configs by indexedThemeConfigs.observeAsState()
configs?.get(id)?.let { return it.label }
val configs by indexedThemeConfigs.collectAsState()
configs[id]?.let { return it.label }
return id.toString()
}

View File

@@ -33,6 +33,7 @@ import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -40,11 +41,11 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.AppPrefs
import dev.patrickgold.florisboard.app.FlorisAppActivity
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.lib.compose.FlorisBulletSpacer
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.FlorisScreenScope
@@ -57,10 +58,11 @@ import dev.patrickgold.florisboard.lib.util.launchActivity
import dev.patrickgold.florisboard.lib.util.launchUrl
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.PreferenceUiScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.florisboard.lib.android.AndroidVersion
@Composable
fun SetupScreen() = FlorisScreen {
title = stringRes(R.string.setup__title)
@@ -70,7 +72,8 @@ fun SetupScreen() = FlorisScreen {
val navController = LocalNavController.current
val context = LocalContext.current
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val scope = rememberCoroutineScope()
val isFlorisBoardEnabled by InputMethodUtils.observeIsFlorisboardEnabled(foregroundOnly = true)
val isFlorisBoardSelected by InputMethodUtils.observeIsFlorisboardSelected(foregroundOnly = true)
@@ -78,10 +81,12 @@ fun SetupScreen() = FlorisScreen {
val requestNotification =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
prefs.internal.notificationPermissionState.set(NotificationPermissionState.GRANTED)
} else {
prefs.internal.notificationPermissionState.set(NotificationPermissionState.DENIED)
scope.launch {
if (isGranted) {
prefs.internal.notificationPermissionState.set(NotificationPermissionState.GRANTED)
} else {
prefs.internal.notificationPermissionState.set(NotificationPermissionState.DENIED)
}
}
}
@@ -91,7 +96,8 @@ fun SetupScreen() = FlorisScreen {
context,
navController,
requestNotification,
hasNotificationPermission
hasNotificationPermission,
scope,
)
}
@@ -103,6 +109,7 @@ private fun FlorisScreenScope.content(
navController: NavController,
requestNotification: ManagedActivityResultLauncher<String, Boolean>,
hasNotificationPermission: NotificationPermissionState,
scope: CoroutineScope,
) {
val stepState = rememberSaveable(saver = FlorisStepState.Saver) {
@@ -158,7 +165,7 @@ private fun FlorisScreenScope.content(
Spacer(modifier = Modifier.height(16.dp))
},
steps = steps(
context, navController, requestNotification
context, navController, requestNotification, scope
),
footer = {
footer(context)
@@ -189,10 +196,11 @@ private fun footer(context: Context) {
}
@Composable
private fun PreferenceUiScope<AppPrefs>.steps(
private fun PreferenceUiScope<FlorisPreferenceModel>.steps(
context: Context,
navController: NavController,
requestNotification: ManagedActivityResultLauncher<String, Boolean>,
scope: CoroutineScope,
): List<FlorisStep> {
return listOfNotNull(
@@ -232,7 +240,7 @@ private fun PreferenceUiScope<AppPrefs>.steps(
StepText(stringRes(R.string.setup__finish_up__description_p1))
StepText(stringRes(R.string.setup__finish_up__description_p2))
StepButton(label = stringRes(R.string.setup__finish_up__finish_btn)) {
this@steps.prefs.internal.isImeSetUp.set(true)
scope.launch { this@steps.prefs.internal.isImeSetUp.set(true) }
navController.navigate(Routes.Settings.Home) {
popUpTo(Routes.Setup.Screen) {
inclusive = true

View File

@@ -74,6 +74,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableStateSetOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -87,7 +88,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.ime.ImeUiMode
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
@@ -112,9 +113,11 @@ import dev.patrickgold.florisboard.lib.observeAsTransformingState
import dev.patrickgold.florisboard.lib.util.NetworkUtils
import dev.patrickgold.jetpref.datastore.model.observeAsState
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.florisboard.lib.android.AndroidKeyguardManager
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.showShortToastSync
import org.florisboard.lib.android.systemService
import org.florisboard.lib.snygg.SnyggQueryAttributes
import org.florisboard.lib.snygg.ui.SnyggBox
@@ -136,7 +139,8 @@ const val CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO: Int = 0
fun ClipboardInputLayout(
modifier: Modifier = Modifier,
) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val scope = rememberCoroutineScope()
val context = LocalContext.current
val clipboardManager by context.clipboardManager()
val keyboardManager by context.keyboardManager()
@@ -203,7 +207,7 @@ fun ClipboardInputLayout(
)
SnyggIconButton(
elementName = FlorisImeUi.ClipboardHeaderButton.elementName,
onClick = { prefs.clipboard.historyEnabled.set(!historyEnabled) },
onClick = { scope.launch { prefs.clipboard.historyEnabled.set(!historyEnabled) } },
modifier = sizeModifier.autoMirrorForRtl(),
enabled = !deviceLocked && !isPopupSurfaceActive(),
) {
@@ -583,7 +587,7 @@ fun ClipboardInputLayout(
attributes = mapOf("action" to "yes"),
onClick = {
clipboardManager.clearHistory()
context.showShortToast(R.string.clipboard__cleared_history)
context.showShortToastSync(R.string.clipboard__cleared_history)
showClearAllHistory = false
isFilterRowShown = false
},
@@ -629,7 +633,7 @@ fun ClipboardInputLayout(
text = stringRes(R.string.clipboard__disabled__message),
)
SnyggButton(FlorisImeUi.ClipboardHistoryDisabledButton.elementName,
onClick = { prefs.clipboard.historyEnabled.set(true) },
onClick = { scope.launch { prefs.clipboard.historyEnabled.set(true) } },
modifier = Modifier.align(Alignment.End),
) {
SnyggText(

View File

@@ -17,11 +17,10 @@
package dev.patrickgold.florisboard.ime.clipboard
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.editorInstance
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardHistoryDao
@@ -44,6 +43,7 @@ import org.florisboard.lib.android.AndroidClipboardManager
import org.florisboard.lib.android.AndroidClipboardManager_OnPrimaryClipChangedListener
import org.florisboard.lib.android.setOrClearPrimaryClip
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.showShortToastSync
import org.florisboard.lib.android.systemService
import org.florisboard.lib.kotlin.tryOrNull
import java.io.Closeable
@@ -91,7 +91,7 @@ class ClipboardManager(
}
}
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val appContext by context.appContext()
private val editorInstance by context.editorInstance()
private val systemClipboardManager = context.systemService(AndroidClipboardManager::class)
@@ -355,7 +355,7 @@ class ClipboardManager(
val editorInstance by appContext.editorInstance()
editorInstance.commitClipboardItem(item).also { result ->
if (!result) {
appContext.showShortToast("Failed to paste item.")
appContext.showShortToastSync("Failed to paste item.")
}
}
}

View File

@@ -47,8 +47,8 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.apptheme.FlorisAppTheme
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.lib.compose.ProvideLocalizedResources
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.model.observeAsState
@@ -135,7 +135,7 @@ class FlorisCopyToClipboardActivity : ComponentActivity() {
@Composable
private fun Content() {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
ProvideLocalizedResources(this, forceLayoutDirection = LayoutDirection.Ltr) {
val theme by prefs.other.settingsTheme.observeAsState()
FlorisAppTheme(theme) {

View File

@@ -17,15 +17,18 @@
package dev.patrickgold.florisboard.ime.core
import android.content.Context
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.keyboard.CurrencySet
import dev.patrickgold.florisboard.keyboardManager
import dev.patrickgold.florisboard.lib.FlorisLocale
import dev.patrickgold.florisboard.lib.devtools.flogDebug
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.serialization.encodeToString
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import org.florisboard.lib.kotlin.collectLatestIn
val SubtypeJsonConfig = Json {
encodeDefaults = true
@@ -38,8 +41,9 @@ val SubtypeJsonConfig = Json {
* helper methods for the in-keyboard language switch process.
*/
class SubtypeManager(context: Context) {
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val keyboardManager by context.keyboardManager()
private val scope = CoroutineScope(Dispatchers.Default)
private val _subtypesFlow = MutableStateFlow(listOf<Subtype>())
val subtypesFlow = _subtypesFlow.asStateFlow()
@@ -54,7 +58,7 @@ class SubtypeManager(context: Context) {
private set(v) { _activeSubtypeFlow.value = v }
init {
prefs.localization.subtypes.observeForever { listRaw ->
prefs.localization.subtypes.asFlow().collectLatestIn(scope) { listRaw ->
flogDebug { listRaw }
val list = if (listRaw.isNotBlank()) {
SubtypeJsonConfig.decodeFromString<List<Subtype>>(listRaw)
@@ -66,7 +70,7 @@ class SubtypeManager(context: Context) {
}
}
private fun persistNewSubtypeList(list: List<Subtype>) {
private fun persistNewSubtypeList(list: List<Subtype>) = scope.launch {
val listRaw = SubtypeJsonConfig.encodeToString(list)
prefs.localization.subtypes.set(listRaw)
}
@@ -78,7 +82,7 @@ class SubtypeManager(context: Context) {
* @return The active subtype or null, if the subtype list is empty or no new active subtype
* could be determined.
*/
private fun evaluateActiveSubtype(list: List<Subtype>) {
private fun evaluateActiveSubtype(list: List<Subtype>) = scope.launch {
val activeSubtypeId = prefs.localization.activeSubtypeId.get()
val subtype = list.find { it.id == activeSubtypeId } ?: list.firstOrNull() ?: Subtype.DEFAULT
if (subtype.id != activeSubtypeId) {
@@ -184,7 +188,7 @@ class SubtypeManager(context: Context) {
/**
* Switch to the previous subtype in the subtype list if possible.
*/
fun switchToPrevSubtype() {
fun switchToPrevSubtype() = scope.launch {
val subtypeList = subtypes
val cachedActiveSubtype = activeSubtype
var triggerNextSubtype = false
@@ -207,7 +211,7 @@ class SubtypeManager(context: Context) {
/**
* Switch to the next subtype in the subtype list if possible.
*/
fun switchToNextSubtype() {
fun switchToNextSubtype() = scope.launch {
val subtypeList = subtypes
val cachedActiveSubtype = activeSubtype
var triggerNextSubtype = false
@@ -227,7 +231,7 @@ class SubtypeManager(context: Context) {
activeSubtype = newActiveSubtype
}
fun switchToSubtypeById(id: Long) {
fun switchToSubtypeById(id: Long) = scope.launch {
if (subtypes.any { it.id == id }) {
activeSubtype = getSubtypeById(id)!!
prefs.localization.activeSubtypeId.set(id)

View File

@@ -18,7 +18,7 @@ package dev.patrickgold.florisboard.ime.dictionary
import android.content.Context
import androidx.room.Room
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.nlp.SuggestionCandidate
import dev.patrickgold.florisboard.ime.nlp.WordSuggestionCandidate
import dev.patrickgold.florisboard.lib.FlorisLocale
@@ -29,7 +29,7 @@ import java.lang.ref.WeakReference
*/
class DictionaryManager private constructor(context: Context) {
private val applicationContext: WeakReference<Context> = WeakReference(context.applicationContext ?: context)
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private var florisUserDictionaryDatabase: FlorisUserDictionaryDatabase? = null
private var systemUserDictionaryDatabase: SystemUserDictionaryDatabase? = null

View File

@@ -23,7 +23,7 @@ import android.view.KeyEvent
import androidx.core.view.inputmethod.InputConnectionCompat
import androidx.core.view.inputmethod.InputContentInfoCompat
import dev.patrickgold.florisboard.FlorisImeService
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
@@ -43,13 +43,14 @@ import dev.patrickgold.florisboard.subtypeManager
import java.util.concurrent.atomic.AtomicInteger
import kotlinx.coroutines.runBlocking
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.showShortToastSync
class EditorInstance(context: Context) : AbstractEditorInstance(context) {
companion object {
private const val SPACE = " "
}
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val appContext by context.appContext()
private val clipboardManager by context.clipboardManager()
private val keyboardManager by context.keyboardManager()
@@ -393,7 +394,7 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
if (text != null) {
clipboardManager.addNewPlaintext(text.toString())
} else {
appContext.showShortToast("Failed to retrieve selected text requested to cut: Eiter selection state is invalid or an error occurred within the input connection.")
appContext.showShortToastSync("Failed to retrieve selected text requested to cut: Eiter selection state is invalid or an error occurred within the input connection.")
}
return deleteBackwards()
}
@@ -411,7 +412,7 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
if (text != null) {
clipboardManager.addNewPlaintext(text.toString())
} else {
appContext.showShortToast("Failed to retrieve selected text requested to copy: Eiter selection state is invalid or an error occurred within the input connection.")
appContext.showShortToastSync("Failed to retrieve selected text requested to copy: Eiter selection state is invalid or an error occurred within the input connection.")
}
val activeSelection = activeContent.selection
return setSelection(activeSelection.end, activeSelection.end)
@@ -428,7 +429,7 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
phantomSpace.setInactive()
return commitClipboardItem(clipboardManager.primaryClip).also { result ->
if (!result) {
appContext.showShortToast("Failed to paste item.")
appContext.showShortToastSync("Failed to paste item.")
}
}
}

View File

@@ -21,7 +21,7 @@ import android.view.ViewConfiguration
import androidx.collection.SparseArrayCompat
import androidx.collection.isNotEmpty
import androidx.collection.set
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.keyboard.KeyData
import dev.patrickgold.florisboard.ime.text.gestures.SwipeAction
import dev.patrickgold.florisboard.ime.text.key.KeyCode
@@ -48,7 +48,7 @@ class InputEventDispatcher private constructor(private val repeatableKeyCodes: I
fun new(repeatableKeyCodes: IntArray = intArrayOf()) = InputEventDispatcher(repeatableKeyCodes.clone())
}
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
private val pressedKeys = guardedByLock { SparseArrayCompat<PressedKeyInfo>() }

View File

@@ -21,7 +21,7 @@ import android.media.AudioManager
import android.provider.Settings
import android.view.HapticFeedbackConstants
import androidx.compose.runtime.staticCompositionLocalOf
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.keyboard.KeyData
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
@@ -46,7 +46,7 @@ class InputFeedbackController private constructor(private val ims: InputMethodSe
fun new(ims: InputMethodService) = InputFeedbackController(ims)
}
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val audioManager = ims.systemServiceOrNull(AudioManager::class)
private val vibrator = ims.systemVibratorOrNull()

View File

@@ -36,8 +36,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.core.view.WindowInsetsCompat
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyboard
@@ -80,7 +79,7 @@ object FlorisImeSizing {
@Composable
fun smartbarUiHeight(): Dp {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val smartbarEnabled by prefs.smartbar.enabled.observeAsState()
val smartbarLayout by prefs.smartbar.layout.observeAsState()
val extendedActionsExpanded by prefs.smartbar.extendedActionsExpanded.observeAsState()
@@ -113,7 +112,7 @@ object FlorisImeSizing {
@Composable
fun ProvideKeyboardRowBaseHeight(content: @Composable () -> Unit) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val resources = LocalContext.current.resources
val configuration = LocalConfiguration.current

View File

@@ -26,7 +26,7 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.MutableLiveData
import dev.patrickgold.florisboard.FlorisImeService
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.editorInstance
@@ -74,7 +74,9 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.florisboard.lib.android.AndroidKeyguardManager
import org.florisboard.lib.android.showLongToast
import org.florisboard.lib.android.showLongToastSync
import org.florisboard.lib.android.showShortToast
import org.florisboard.lib.android.showShortToastSync
import org.florisboard.lib.android.systemService
import org.florisboard.lib.kotlin.collectIn
import org.florisboard.lib.kotlin.collectLatestIn
@@ -83,7 +85,7 @@ import java.util.concurrent.atomic.AtomicInteger
private val DoubleSpacePeriodMatcher = """([^.!?‽\s]\s)""".toRegex()
class KeyboardManager(context: Context) : InputKeyEventReceiver {
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val appContext by context.appContext()
private val clipboardManager by context.clipboardManager()
private val editorInstance by context.editorInstance()
@@ -129,21 +131,21 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
keyboardCache.clear()
}
}
prefs.keyboard.numberRow.observeForever {
prefs.keyboard.numberRow.asFlow().collectLatestIn(scope) {
updateActiveEvaluators {
keyboardCache.clear(KeyboardMode.CHARACTERS)
}
}
prefs.keyboard.hintedNumberRowEnabled.observeForever {
prefs.keyboard.hintedNumberRowEnabled.asFlow().collectLatestIn(scope) {
updateActiveEvaluators()
}
prefs.keyboard.hintedSymbolsEnabled.observeForever {
prefs.keyboard.hintedSymbolsEnabled.asFlow().collectLatestIn(scope) {
updateActiveEvaluators()
}
prefs.keyboard.utilityKeyEnabled.observeForever {
prefs.keyboard.utilityKeyEnabled.asFlow().collectLatestIn(scope) {
updateActiveEvaluators()
}
prefs.keyboard.utilityKeyAction.observeForever {
prefs.keyboard.utilityKeyAction.asFlow().collectLatestIn(scope) {
updateActiveEvaluators()
}
activeState.collectLatestIn(scope) {
@@ -164,10 +166,10 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
editorInstance.activeContentFlow.collectIn(scope) { content ->
resetSuggestions(content)
}
prefs.devtools.enabled.observeForever {
prefs.devtools.enabled.asFlow().collectLatestIn(scope) {
reevaluateDebugFlags()
}
prefs.devtools.showDragAndDropHelpers.observeForever {
prefs.devtools.showDragAndDropHelpers.asFlow().collectLatestIn(scope) {
reevaluateDebugFlags()
}
}
@@ -237,7 +239,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
return subtypeManager.subtypes.size > 1
}
fun toggleOneHandedMode() {
suspend fun toggleOneHandedMode() {
prefs.keyboard.oneHandedModeEnabled.set(!prefs.keyboard.oneHandedModeEnabled.get())
}
@@ -580,7 +582,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
/**
* Handles a [KeyCode.TOGGLE_INCOGNITO_MODE] event.
*/
private fun handleToggleIncognitoMode() {
private suspend fun handleToggleIncognitoMode() {
prefs.suggestion.forceIncognitoModeFromDynamic.set(!prefs.suggestion.forceIncognitoModeFromDynamic.get())
val newState = !activeState.isIncognitoMode
activeState.isIncognitoMode = newState
@@ -606,7 +608,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
private fun handleToggleAutocorrect() {
lastToastReference.get()?.cancel()
lastToastReference = WeakReference(
appContext.showLongToast("Autocorrect toggle is a placeholder and not yet implemented")
appContext.showLongToastSync("Autocorrect toggle is a placeholder and not yet implemented")
)
}
@@ -716,14 +718,14 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
clipboardManager.primaryClip?.let { clipboardManager.deleteClip(it) }
}
clipboardManager.updatePrimaryClip(null)
appContext.showShortToast(R.string.clipboard__cleared_primary_clip)
appContext.showShortToastSync(R.string.clipboard__cleared_primary_clip)
}
KeyCode.TOGGLE_COMPACT_LAYOUT -> toggleOneHandedMode()
KeyCode.COMPACT_LAYOUT_TO_LEFT -> {
KeyCode.TOGGLE_COMPACT_LAYOUT -> scope.launch { toggleOneHandedMode() }
KeyCode.COMPACT_LAYOUT_TO_LEFT -> scope.launch {
prefs.keyboard.oneHandedMode.set(OneHandedMode.START)
toggleOneHandedMode()
}
KeyCode.COMPACT_LAYOUT_TO_RIGHT -> {
KeyCode.COMPACT_LAYOUT_TO_RIGHT -> scope.launch {
prefs.keyboard.oneHandedMode.set(OneHandedMode.END)
toggleOneHandedMode()
}
@@ -753,7 +755,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
}
KeyCode.SYSTEM_PREV_INPUT_METHOD -> FlorisImeService.switchToPrevInputMethod()
KeyCode.SYSTEM_NEXT_INPUT_METHOD -> FlorisImeService.switchToNextInputMethod()
KeyCode.TOGGLE_SMARTBAR_VISIBILITY -> {
KeyCode.TOGGLE_SMARTBAR_VISIBILITY -> scope.launch {
prefs.smartbar.enabled.let { it.set(!it.get()) }
}
KeyCode.TOGGLE_ACTIONS_OVERFLOW -> {
@@ -762,7 +764,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
KeyCode.TOGGLE_ACTIONS_EDITOR -> {
activeState.isActionsEditorVisible = !activeState.isActionsEditorVisible
}
KeyCode.TOGGLE_INCOGNITO_MODE -> handleToggleIncognitoMode()
KeyCode.TOGGLE_INCOGNITO_MODE -> scope.launch { handleToggleIncognitoMode() }
KeyCode.TOGGLE_AUTOCORRECT -> handleToggleAutocorrect()
KeyCode.UNDO -> editorInstance.performUndo()
KeyCode.VIEW_CHARACTERS -> activeState.keyboardMode = KeyboardMode.CHARACTERS

View File

@@ -17,7 +17,7 @@
package dev.patrickgold.florisboard.ime.keyboard
import android.content.Context
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.core.Subtype
@@ -78,7 +78,7 @@ data class DebugLayoutComputationResult(
* Class which manages layout loading and caching.
*/
class LayoutManager(context: Context) {
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val appContext by context.appContext()
private val extensionManager by context.extensionManager()
private val keyboardManager by context.keyboardManager()

View File

@@ -16,13 +16,12 @@
package dev.patrickgold.florisboard.ime.media.emoji
import dev.patrickgold.florisboard.app.AppPrefs
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.jetpref.datastore.model.PreferenceSerializer
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@Serializable
@@ -76,7 +75,7 @@ data class EmojiHistory(
object EmojiHistoryHelper {
private var emojiGuard = Mutex(locked = false)
suspend fun markEmojiUsed(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
suspend fun markEmojiUsed(prefs: FlorisPreferenceModel, emoji: Emoji): Unit = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}
@@ -121,7 +120,7 @@ object EmojiHistoryHelper {
)
}
suspend fun pinEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
suspend fun pinEmoji(prefs: FlorisPreferenceModel, emoji: Emoji): Unit = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}
@@ -138,7 +137,7 @@ object EmojiHistoryHelper {
prefs.emoji.historyData.set(dataMut.build())
}
suspend fun unpinEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
suspend fun unpinEmoji(prefs: FlorisPreferenceModel, emoji: Emoji): Unit = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}
@@ -155,7 +154,7 @@ object EmojiHistoryHelper {
prefs.emoji.historyData.set(dataMut.build())
}
suspend fun moveEmoji(prefs: AppPrefs, emoji: Emoji, offset: Int) = emojiGuard.withLock {
suspend fun moveEmoji(prefs: FlorisPreferenceModel, emoji: Emoji, offset: Int): Unit = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get() || offset == 0) {
return
}
@@ -175,7 +174,7 @@ object EmojiHistoryHelper {
prefs.emoji.historyData.set(dataMut.build())
}
suspend fun removeEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
suspend fun removeEmoji(prefs: FlorisPreferenceModel, emoji: Emoji): Unit = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}
@@ -195,7 +194,7 @@ object EmojiHistoryHelper {
prefs.emoji.historyData.set(dataMut.build())
}
suspend fun deleteHistory(prefs: AppPrefs) = emojiGuard.withLock {
suspend fun deleteHistory(prefs: FlorisPreferenceModel): Unit = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}
@@ -203,7 +202,7 @@ object EmojiHistoryHelper {
prefs.emoji.historyData.set(EmojiHistory(pinned = dataMut.pinned, listOf()))
}
suspend fun deletePinned(prefs: AppPrefs) = emojiGuard.withLock {
suspend fun deletePinned(prefs: FlorisPreferenceModel): Unit = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}

View File

@@ -25,7 +25,6 @@ import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
@@ -46,8 +45,6 @@ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.PushPin
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow
import androidx.compose.material3.TabRowDefaults
@@ -83,7 +80,7 @@ import androidx.compose.ui.window.Popup
import androidx.emoji2.text.EmojiCompat
import androidx.emoji2.widget.EmojiTextView
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.editorInstance
import dev.patrickgold.florisboard.ime.input.LocalInputFeedbackController
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
@@ -133,7 +130,7 @@ fun EmojiPaletteView(
fullEmojiMappings: EmojiData,
modifier: Modifier = Modifier,
) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val editorInstance by context.editorInstance()
val keyboardManager by context.keyboardManager()
@@ -497,7 +494,7 @@ private fun EmojiHistoryPopup(
onHistoryAction: () -> Unit,
onDismiss: () -> Unit,
) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val scope = rememberCoroutineScope()
val emojiKeyHeight = FlorisImeSizing.smartbarHeight
val context = LocalContext.current

View File

@@ -20,7 +20,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.util.stream.Collectors
import android.content.Context
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.editor.EditorContent
import dev.patrickgold.florisboard.ime.nlp.EmojiSuggestionCandidate
@@ -41,7 +41,7 @@ import io.github.reactivecircus.cache4k.Cache
class EmojiSuggestionProvider(private val context: Context) : SuggestionProvider {
override val providerId = "org.florisboard.nlp.providers.emoji"
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val lettersRegex = "^[A-Za-z]*$".toRegex()
private val cachedEmojiMappings = Cache.Builder().build<FlorisLocale, EmojiDataBySkinTone>()

View File

@@ -20,7 +20,7 @@ import android.content.Context
import android.os.SystemClock
import android.util.LruCache
import androidx.lifecycle.MutableLiveData
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.editorInstance
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
@@ -43,8 +43,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.florisboard.lib.kotlin.collectLatestIn
import org.florisboard.lib.kotlin.guardedByLock
import org.florisboard.lib.kotlin.collectLatestIn
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.properties.Delegates
@@ -54,7 +54,7 @@ private const val BLANK_STR_PATTERN = "^\\s*$"
class NlpManager(context: Context) {
private val blankStrRegex = Regex(BLANK_STR_PATTERN)
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val clipboardManager by context.clipboardManager()
private val editorInstance by context.editorInstance()
private val keyboardManager by context.keyboardManager()
@@ -93,13 +93,13 @@ class NlpManager(context: Context) {
clipboardManager.primaryClipFlow.collectLatestIn(scope) {
assembleCandidates()
}
prefs.suggestion.enabled.observeForever {
prefs.suggestion.enabled.asFlow().collectLatestIn(scope) {
assembleCandidates()
}
prefs.clipboard.suggestionEnabled.observeForever {
prefs.clipboard.suggestionEnabled.asFlow().collectLatestIn(scope) {
assembleCandidates()
}
prefs.emoji.suggestionEnabled.observeForever {
prefs.emoji.suggestionEnabled.asFlow().collectLatestIn(scope) {
assembleCandidates()
}
subtypeManager.activeSubtypeFlow.collectLatestIn(scope) { subtype ->
@@ -317,8 +317,10 @@ class NlpManager(context: Context) {
}*/
val isSelection = editorInstance.activeContent.selection.isSelectionMode
val isExpanded = list1.isNullOrEmpty() && list2.isNullOrEmpty() || isSelection
prefs.smartbar.sharedActionsExpandWithAnimation.set(false)
prefs.smartbar.sharedActionsExpanded.set(isExpanded)
scope.launch {
prefs.smartbar.sharedActionsExpandWithAnimation.set(false)
prefs.smartbar.sharedActionsExpanded.set(isExpanded)
}
}
fun addToDebugOverlay(word: String, info: SpellingResult) {

View File

@@ -26,14 +26,16 @@ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.ZoomOutMap
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.input.LocalInputFeedbackController
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.lib.compose.stringRes
import kotlinx.coroutines.launch
import org.florisboard.lib.snygg.ui.SnyggColumn
import org.florisboard.lib.snygg.ui.SnyggIcon
import org.florisboard.lib.snygg.ui.SnyggIconButton
@@ -44,7 +46,8 @@ fun RowScope.OneHandedPanel(
panelSide: OneHandedMode,
weight: Float,
) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val scope = rememberCoroutineScope()
val inputFeedbackController = LocalInputFeedbackController.current
SnyggColumn(
@@ -58,8 +61,10 @@ fun RowScope.OneHandedPanel(
SnyggIconButton(
FlorisImeUi.OneHandedPanelButton.elementName,
onClick = {
inputFeedbackController.keyPress()
prefs.keyboard.oneHandedModeEnabled.set(false)
scope.launch {
inputFeedbackController.keyPress()
prefs.keyboard.oneHandedModeEnabled.set(false)
}
},
modifier = Modifier
.fillMaxWidth()
@@ -74,8 +79,10 @@ fun RowScope.OneHandedPanel(
SnyggIconButton(
FlorisImeUi.OneHandedPanelButton.elementName,
onClick = {
inputFeedbackController.keyPress()
prefs.keyboard.oneHandedMode.set(panelSide)
scope.launch {
inputFeedbackController.keyPress()
prefs.keyboard.oneHandedMode.set(panelSide)
}
},
modifier = Modifier
.weight(1f)

View File

@@ -28,6 +28,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.zIndex
import dev.patrickgold.florisboard.ime.keyboard.Key
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import org.florisboard.lib.snygg.SnyggQueryAttributes
@@ -58,7 +59,7 @@ fun PopupBaseBox(
.align(Alignment.TopCenter),
) {
SnyggText(
modifier = Modifier.align(Alignment.Center),
modifier = Modifier.align(Alignment.Center).zIndex(100f),
text = label,
)
}

View File

@@ -31,6 +31,7 @@ import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.zIndex
import dev.patrickgold.florisboard.ime.keyboard.ComputingEvaluator
import dev.patrickgold.florisboard.ime.keyboard.DefaultComputingEvaluator
import dev.patrickgold.florisboard.ime.keyboard.Key
@@ -38,6 +39,7 @@ import dev.patrickgold.florisboard.ime.keyboard.KeyData
import dev.patrickgold.florisboard.ime.keyboard.computeImageVector
import dev.patrickgold.florisboard.ime.keyboard.computeLabel
import dev.patrickgold.florisboard.ime.media.emoji.EmojiSet
import dev.patrickgold.florisboard.ime.smartbar.Temp
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.key.KeyHintConfiguration
import dev.patrickgold.florisboard.ime.text.keyboard.TextKey
@@ -452,11 +454,12 @@ class PopupUiController(
FlorisImeUi.Attr.Mode to evaluator.keyboard.mode.toString(),
FlorisImeUi.Attr.ShiftState to evaluator.state.inputShiftState.toString(),
)
Temp = !(baseRenderInfo != null || extRenderInfo != null)
baseRenderInfo?.let { renderInfo ->
PopupBaseBox(
modifier = Modifier
.requiredSize(renderInfo.bounds.size.toDpSize())
.absoluteOffset { renderInfo.bounds.topLeft.toIntOffset() },
.absoluteOffset { renderInfo.bounds.topLeft.toIntOffset() }.zIndex(100f),
attributes = attributes,
key = renderInfo.key,
shouldIndicateExtendedPopups = renderInfo.shouldIndicateExtendedPopups && extRenderInfo == null,

View File

@@ -20,7 +20,6 @@ import androidx.compose.foundation.gestures.awaitEachGesture
import androidx.compose.foundation.gestures.awaitFirstDown
import androidx.compose.foundation.gestures.waitForUpOrCancellation
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.width
@@ -40,7 +39,7 @@ import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.nlp.ClipboardSuggestionCandidate
import dev.patrickgold.florisboard.ime.nlp.SuggestionCandidate
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
@@ -62,7 +61,7 @@ val CandidatesRowScrollbarHeight = 2.dp
@Composable
fun CandidatesRow(modifier: Modifier = Modifier) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val keyboardManager by context.keyboardManager()
val nlpManager by context.nlpManager()

View File

@@ -16,8 +16,11 @@
package dev.patrickgold.florisboard.ime.smartbar
import android.graphics.PixelFormat
import android.os.Build
import android.view.SurfaceView
import androidx.annotation.RequiresApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.rememberScrollState
@@ -32,16 +35,18 @@ import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.view.forEach
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofillSuggestion
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.lib.compose.florisHorizontalScroll
import dev.patrickgold.florisboard.lib.toIntOffset
import org.florisboard.lib.snygg.SnyggPropertySet
import org.florisboard.lib.snygg.SnyggSinglePropertySet
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
var CachedInlineSuggestionsChipStyleSet: SnyggSinglePropertySet? = null
var Temp: Boolean = false
@Composable
fun InlineSuggestionsStyleCache() {
val chipStyleSet = rememberSnyggThemeQuery(FlorisImeUi.InlineAutofillChip.elementName)
@@ -59,13 +64,16 @@ fun InlineSuggestionsUi(
val scrollState = rememberScrollState()
val almostEmptyRect = remember { android.graphics.Rect(0, 0, 1, 1) }
val backgroundColor = rememberSnyggThemeQuery(FlorisImeUi.SmartbarCandidatesRow.elementName).background()
Row(
modifier
.fillMaxSize()
.florisHorizontalScroll(
state = scrollState,
scrollbarHeight = CandidatesRowScrollbarHeight,
),
)
.background(backgroundColor),
) {
val xMin = scrollState.value
val xMax = scrollState.value + scrollState.viewportSize
@@ -73,6 +81,16 @@ fun InlineSuggestionsUi(
if (inlineSuggestion.view == null) {
continue
}
//inlineSuggestion.view.background = ColorDrawable(backgroundColor.toArgb())
inlineSuggestion.view.forEach {
with (it as SurfaceView) {
//this.setBackgroundColor(backgroundColor.toArgb())
setZOrderOnTop(false)
holder.setFormat(PixelFormat.OPAQUE)
}
}
var chipPos by remember { mutableStateOf(IntOffset.Zero) }
AndroidView(
modifier = Modifier.onGloballyPositioned { chipPos = it.positionInParent().toIntOffset() },

View File

@@ -24,7 +24,6 @@ import androidx.compose.animation.core.tween
import androidx.compose.animation.core.updateTransition
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.absoluteOffset
@@ -43,6 +42,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@@ -52,7 +52,7 @@ import androidx.compose.ui.graphics.isUnspecified
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofill
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionButton
@@ -65,6 +65,7 @@ import dev.patrickgold.florisboard.lib.compose.verticalTween
import dev.patrickgold.florisboard.nlpManager
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.vectorResource
import kotlinx.coroutines.launch
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.snygg.ui.SnyggBox
import org.florisboard.lib.snygg.ui.SnyggColumn
@@ -89,7 +90,7 @@ private val NoAnimationTween = tween<Float>(0)
@Composable
fun Smartbar() {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val smartbarEnabled by prefs.smartbar.enabled.observeAsState()
val extendedActionsPlacement by prefs.smartbar.extendedActionsPlacement.observeAsState()
@@ -138,10 +139,12 @@ fun Smartbar() {
@Composable
private fun SmartbarMainRow(modifier: Modifier = Modifier) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val keyboardManager by context.keyboardManager()
val activeEvaluator by keyboardManager.activeEvaluator.collectAsState()
val nlpManager by context.nlpManager()
val scope = rememberCoroutineScope()
val inlineSuggestions by NlpInlineAutofill.suggestions.collectAsState()
LaunchedEffect(inlineSuggestions) {
@@ -164,7 +167,9 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
if (/* was */ sharedActionsExpanded) {
keyboardManager.activeState.isActionsOverflowVisible = false
}
prefs.smartbar.sharedActionsExpanded.set(!sharedActionsExpanded)
scope.launch {
prefs.smartbar.sharedActionsExpanded.set(!sharedActionsExpanded)
}
},
modifier = Modifier.sizeIn(maxHeight = FlorisImeSizing.smartbarHeight).aspectRatio(1f)
) {
@@ -244,7 +249,9 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
if (/* was */ extendedActionsExpanded) {
keyboardManager.activeState.isActionsOverflowVisible = false
}
prefs.smartbar.extendedActionsExpanded.set(!extendedActionsExpanded)
scope.launch {
prefs.smartbar.extendedActionsExpanded.set(!extendedActionsExpanded)
}
},
modifier = Modifier.sizeIn(maxHeight = FlorisImeSizing.smartbarHeight).aspectRatio(1f)
) {
@@ -304,7 +311,9 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
SideEffect {
if (!shouldAnimate) {
prefs.smartbar.sharedActionsExpandWithAnimation.set(true)
scope.launch {
prefs.smartbar.sharedActionsExpandWithAnimation.set(true)
}
}
}
@@ -359,7 +368,7 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
@Composable
private fun SmartbarSecondaryRow(modifier: Modifier = Modifier) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val smartbarLayout by prefs.smartbar.layout.observeAsState()
val secondaryRowStyle = rememberSnyggThemeQuery(FlorisImeUi.SmartbarExtendedActionsRow.elementName)
val windowStyle = rememberSnyggThemeQuery(FlorisImeUi.Window.elementName)

View File

@@ -25,7 +25,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.safeDrawingPadding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
@@ -40,6 +39,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.runtime.toMutableStateList
import androidx.compose.ui.Alignment
@@ -52,7 +52,7 @@ import androidx.compose.ui.unit.IntSize
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
import dev.patrickgold.florisboard.ime.text.key.KeyCode
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
@@ -60,8 +60,8 @@ import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.keyboardManager
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.toIntOffset
import kotlinx.coroutines.launch
import org.florisboard.lib.snygg.ui.SnyggBox
import org.florisboard.lib.snygg.ui.SnyggButton
import org.florisboard.lib.snygg.ui.SnyggColumn
import org.florisboard.lib.snygg.ui.SnyggIcon
import org.florisboard.lib.snygg.ui.SnyggIconButton
@@ -74,8 +74,9 @@ private val DragMarkerAction = QuickAction.InsertKey(TextKeyData(code = KeyCode.
@Composable
fun QuickActionsEditorPanel() {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val scope = rememberCoroutineScope()
val keyboardManager by context.keyboardManager()
// We get the current arrangement once and do not observe on purpose
@@ -235,7 +236,9 @@ fun QuickActionsEditorPanel() {
dynamicActions.filter { it != NoopAction && it != DragMarkerAction },
hiddenActions.filter { it != NoopAction && it != DragMarkerAction },
)
prefs.smartbar.actionArrangement.set(newActionArrangement)
scope.launch {
prefs.smartbar.actionArrangement.set(newActionArrangement)
}
if (keyboardManager.activeState.isActionsEditorVisible) {
keyboardManager.activeState.isActionsEditorVisible = false
}

View File

@@ -30,7 +30,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.keyboardManager
@@ -42,7 +42,7 @@ import org.florisboard.lib.snygg.ui.SnyggText
@Composable
fun QuickActionsOverflowPanel() {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val keyboardManager by context.keyboardManager()

View File

@@ -16,6 +16,7 @@
package dev.patrickgold.florisboard.ime.smartbar.quickaction
import android.annotation.SuppressLint
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxSize
@@ -28,7 +29,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
import dev.patrickgold.florisboard.keyboardManager
@@ -37,12 +38,13 @@ import org.florisboard.lib.snygg.ui.SnyggRow
internal val ToggleOverflowPanelAction = QuickAction.InsertKey(TextKeyData.TOGGLE_ACTIONS_OVERFLOW)
@SuppressLint("UnusedBoxWithConstraintsScope")
@Composable
fun QuickActionsRow(
elementName: String,
modifier: Modifier = Modifier,
) = with(LocalDensity.current) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val keyboardManager by context.keyboardManager()

View File

@@ -31,7 +31,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.LayoutDirection
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.smartbar.IncognitoDisplayMode
import dev.patrickgold.florisboard.ime.smartbar.InlineSuggestionsStyleCache
import dev.patrickgold.florisboard.ime.smartbar.Smartbar
@@ -49,7 +49,7 @@ fun TextInputLayout(
val context = LocalContext.current
val keyboardManager by context.keyboardManager()
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val state by keyboardManager.activeState.collectAsState()
val evaluator by keyboardManager.activeEvaluator.collectAsState()

View File

@@ -17,7 +17,7 @@
package dev.patrickgold.florisboard.ime.text.gestures
import android.content.Context
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.nlp.WordSuggestionCandidate
import dev.patrickgold.florisboard.ime.text.keyboard.TextKey
import dev.patrickgold.florisboard.keyboardManager
@@ -39,7 +39,7 @@ class GlideTypingManager(context: Context) : GlideTypingGesture.Listener {
private const val MAX_SUGGESTION_COUNT = 8
}
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val keyboardManager by context.keyboardManager()
private val nlpManager by context.nlpManager()
private val subtypeManager by context.subtypeManager()

View File

@@ -19,7 +19,7 @@ package dev.patrickgold.florisboard.ime.text.gestures
import android.view.MotionEvent
import android.view.VelocityTracker
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.lib.Pointer
import dev.patrickgold.florisboard.lib.PointerMap
import dev.patrickgold.florisboard.lib.devtools.LogTopic
@@ -39,7 +39,7 @@ abstract class SwipeGesture {
* @property listener The listener to report detected swipes to.
*/
class Detector(private val listener: Listener) {
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
var isEnabled: Boolean = true
private var pointerMap: PointerMap<GesturePointer> = PointerMap { GesturePointer() }

View File

@@ -16,7 +16,7 @@
package dev.patrickgold.florisboard.ime.text.keyboard
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.ime.keyboard.AbstractKeyData
import dev.patrickgold.florisboard.ime.keyboard.ComputingEvaluator
import dev.patrickgold.florisboard.ime.keyboard.Key
@@ -246,7 +246,7 @@ class TextKey(override val data: AbstractKeyData) : Key(data) {
else -> null
}
} else if (!data.isSpaceKey() || data.type == KeyType.NUMERIC) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
computedPopups.getPopupKeys(prefs.keyboard.keyHintConfiguration()).hint.let { hintData ->
if (hintData?.isSpaceKey() == false) {
hintedLabel = hintData.asString(isForDisplay = true)

View File

@@ -56,7 +56,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.toSize
import dev.patrickgold.florisboard.FlorisImeService
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.editorInstance
import dev.patrickgold.florisboard.glideTypingManager
import dev.patrickgold.florisboard.ime.input.InputEventDispatcher
@@ -104,7 +104,7 @@ fun TextKeyboardLayout(
evaluator: ComputingEvaluator,
isPreview: Boolean = false,
): Unit = with(LocalDensity.current) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val context = LocalContext.current
val configuration = LocalConfiguration.current
val glideTypingManager by context.glideTypingManager()
@@ -132,7 +132,7 @@ fun TextKeyboardLayout(
controller.onTouchEventInternal(event)
controller.popupUiController.hide()
event.recycle()
} catch (e: Throwable) {
} catch (_: Throwable) {
// Ignore
}
}
@@ -337,7 +337,7 @@ private fun TextKeyButton(
key.label?.let { label ->
var customLabel = label
if (key.computedData.code == KeyCode.SPACE) {
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val spaceBarMode by prefs.keyboard.spaceBarMode.observeAsState()
when (spaceBarMode) {
SpaceBarMode.NOTHING -> return@let
@@ -385,7 +385,7 @@ private fun TextKeyButton(
private class TextKeyboardLayoutController(
context: Context,
) : SwipeGesture.Listener, GlideTypingGesture.Listener {
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val editorInstance by context.editorInstance()
private val keyboardManager by context.keyboardManager()

View File

@@ -27,13 +27,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.staticCompositionLocalOf
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.TextStyle
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.keyboardManager
import dev.patrickgold.florisboard.lib.observeAsNonNullState
import dev.patrickgold.florisboard.themeManager
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.snygg.SnyggAttributes
import org.florisboard.lib.snygg.SnyggQueryAttributes
import org.florisboard.lib.snygg.ui.ProvideSnyggTheme
import org.florisboard.lib.snygg.ui.rememberSnyggTheme
@@ -52,10 +49,10 @@ fun FlorisImeTheme(content: @Composable () -> Unit) {
val keyboardManager by context.keyboardManager()
val themeManager by context.themeManager()
val prefs by florisPreferenceModel()
val prefs by FlorisPreferenceStore
val accentColor by prefs.theme.accentColor.observeAsState()
val activeThemeInfo by themeManager.activeThemeInfo.observeAsNonNullState()
val activeThemeInfo by themeManager.activeThemeInfo.collectAsState()
val activeConfig = remember(activeThemeInfo) { activeThemeInfo.config }
val activeStyle = remember(activeThemeInfo) { activeThemeInfo.stylesheet }

View File

@@ -39,10 +39,8 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.smartbar.CachedInlineSuggestionsChipStyleSet
@@ -52,70 +50,67 @@ import dev.patrickgold.florisboard.lib.ext.ExtensionMeta
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.florisboard.lib.util.TimeUtils.javaLocalTime
import dev.patrickgold.florisboard.lib.util.ViewUtils
import java.time.LocalTime
import java.util.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.florisboard.lib.kotlin.collectIn
import org.florisboard.lib.kotlin.io.FsDir
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
import org.florisboard.lib.snygg.SnyggStylesheet
import org.florisboard.lib.snygg.value.SnyggStaticColorValue
import kotlin.properties.Delegates
import java.time.LocalTime
import java.util.*
/**
* Core class which manages the keyboard theme. Note, that this does not affect the UI theme of the
* Settings Activities.
*/
class ThemeManager(context: Context) {
private val prefs by florisPreferenceModel()
private val prefs by FlorisPreferenceStore
private val appContext by context.appContext()
private val extensionManager by context.extensionManager()
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
private val _indexedThemeConfigs = MutableLiveData(mapOf<ExtensionComponentName, ThemeExtensionComponent>())
val indexedThemeConfigs: LiveData<Map<ExtensionComponentName, ThemeExtensionComponent>> get() = _indexedThemeConfigs
var previewThemeId: ExtensionComponentName? by Delegates.observable(null) { _, _, _ ->
updateActiveTheme()
}
var previewThemeInfo: ThemeInfo? by Delegates.observable(null) { _, _, _ ->
updateActiveTheme()
}
private val _indexedThemeConfigs = MutableStateFlow(mapOf<ExtensionComponentName, ThemeExtensionComponent>())
val indexedThemeConfigs get() = _indexedThemeConfigs.asStateFlow()
val previewThemeId = MutableStateFlow<ExtensionComponentName?>(null)
val previewThemeInfo = MutableStateFlow<ThemeInfo?>(null)
val wallpaperChangedCounter = MutableStateFlow(0)
private val cachedThemeInfos = mutableListOf<ThemeInfo>()
private val activeThemeGuard = Mutex(locked = false)
private val _activeThemeInfo = MutableLiveData(ThemeInfo.DEFAULT)
val activeThemeInfo: LiveData<ThemeInfo> get() = _activeThemeInfo
private val _activeThemeInfo = MutableStateFlow(ThemeInfo.DEFAULT)
val activeThemeInfo get() = _activeThemeInfo.asStateFlow()
init {
extensionManager.themes.observeForever { themeExtensions ->
val map = buildMap {
_indexedThemeConfigs.value = buildMap {
for (themeExtension in themeExtensions) {
for (themeComponent in themeExtension.themes) {
put(ExtensionComponentName(themeExtension.meta.id, themeComponent.id), themeComponent)
}
}
}
_indexedThemeConfigs.postValue(map)
}
indexedThemeConfigs.observeForever {
updateActiveTheme {
cachedThemeInfos.clear()
}
indexedThemeConfigs.collectIn(scope) {
updateActiveTheme { cachedThemeInfos.clear() }
}
prefs.theme.mode.observeForever {
updateActiveTheme()
}
prefs.theme.dayThemeId.observeForever {
updateActiveTheme()
}
prefs.theme.nightThemeId.observeForever {
combine(
prefs.theme.mode.asFlow(),
prefs.theme.dayThemeId.asFlow(),
prefs.theme.nightThemeId.asFlow(),
previewThemeId,
previewThemeInfo,
wallpaperChangedCounter,
) {}.collectIn(scope) {
updateActiveTheme()
}
}
@@ -124,56 +119,54 @@ class ThemeManager(context: Context) {
* Updates the current theme ref and loads the corresponding theme, as well as notifies all
* callback receivers about the new theme.
*/
fun updateActiveTheme(action: () -> Unit = { }) = scope.launch {
activeThemeGuard.withLock {
action()
previewThemeInfo?.let { previewThemeInfo ->
_activeThemeInfo.postValue(previewThemeInfo)
return@withLock
}
val activeName = evaluateActiveThemeName()
val cachedInfo = cachedThemeInfos.find { it.name == activeName }
if (cachedInfo != null) {
_activeThemeInfo.postValue(cachedInfo)
return@withLock
}
val themeExt = extensionManager.getExtensionById(activeName.extensionId) as? ThemeExtension
val themeExtRef = themeExt?.sourceRef
if (themeExtRef == null) {
return@withLock
}
val themeConfig = themeExt.themes.find { it.id == activeName.componentId }
if (themeConfig == null) {
return@withLock
}
// TODO: loaded dir is implemented already...
// TODO: this leaks the loaded dir, but at least the state is not kaputt from compose viewpoint
val loadedDir = appContext.cacheDir.subDir("loaded").subDir(UUID.randomUUID().toString())
runCatching {
loadedDir.mkdirs()
loadedDir.deleteContentsRecursively()
ZipUtils.unzip(appContext, themeExtRef, loadedDir).getOrThrow()
flogInfo { "Loaded extension ${themeExt.meta.id} into $loadedDir" }
val stylesheetFile = loadedDir.subFile(themeConfig.stylesheetPath())
val stylesheetJson = stylesheetFile.readText()
SnyggStylesheet.fromJson(stylesheetJson).getOrThrow()
}.fold(
onSuccess = { newStylesheet ->
val newInfo = ThemeInfo(activeName, themeConfig, newStylesheet, loadedDir, null)
cachedThemeInfos.add(newInfo)
_activeThemeInfo.postValue(newInfo)
},
onFailure = { cause ->
_activeThemeInfo.postValue(ThemeInfo.DEFAULT.copy(
loadFailure = LoadFailure(themeExt.meta, themeConfig, cause)
))
},
)
suspend fun updateActiveTheme(action: () -> Unit = { }) = activeThemeGuard.withLock {
action()
previewThemeInfo.value?.let { previewThemeInfo ->
_activeThemeInfo.value = previewThemeInfo
return@withLock
}
val activeName = evaluateActiveThemeName()
val cachedInfo = cachedThemeInfos.find { it.name == activeName }
if (cachedInfo != null) {
_activeThemeInfo.value = cachedInfo
return@withLock
}
val themeExt = extensionManager.getExtensionById(activeName.extensionId) as? ThemeExtension
val themeExtRef = themeExt?.sourceRef
if (themeExtRef == null) {
return@withLock
}
val themeConfig = themeExt.themes.find { it.id == activeName.componentId }
if (themeConfig == null) {
return@withLock
}
// TODO: loaded dir is implemented already...
// TODO: this leaks the loaded dir, but at least the state is not kaputt from compose viewpoint
val loadedDir = appContext.cacheDir.subDir("loaded").subDir(UUID.randomUUID().toString())
runCatching {
loadedDir.mkdirs()
loadedDir.deleteContentsRecursively()
ZipUtils.unzip(appContext, themeExtRef, loadedDir).getOrThrow()
flogInfo { "Loaded extension ${themeExt.meta.id} into $loadedDir" }
val stylesheetFile = loadedDir.subFile(themeConfig.stylesheetPath())
val stylesheetJson = stylesheetFile.readText()
SnyggStylesheet.fromJson(stylesheetJson).getOrThrow()
}.fold(
onSuccess = { newStylesheet ->
val newInfo = ThemeInfo(activeName, themeConfig, newStylesheet, loadedDir, null)
cachedThemeInfos.add(newInfo)
_activeThemeInfo.value = newInfo
},
onFailure = { cause ->
_activeThemeInfo.value = ThemeInfo.DEFAULT.copy(
loadFailure = LoadFailure(themeExt.meta, themeConfig, cause)
)
},
)
}
private fun evaluateActiveThemeName(): ExtensionComponentName {
previewThemeId?.let { return it }
previewThemeId.value?.let { return it }
return when (prefs.theme.mode.get()) {
ThemeMode.ALWAYS_DAY -> {
prefs.theme.dayThemeId.get()
@@ -202,10 +195,9 @@ class ThemeManager(context: Context) {
}
/**
* Creates a new inline suggestion UI bundle based on the attributes of the given [style].
* Creates a new inline suggestion UI bundle.
*
* @param context The context of the parent view/controller.
* @param style The style set which is responsible for styling the chips.
*
* @return A bundle containing all necessary attributes for the inline suggestion views to properly display.
*/

View File

@@ -21,6 +21,7 @@ import android.content.Context
import android.content.Intent
import dev.patrickgold.florisboard.lib.devtools.flogDebug
import dev.patrickgold.florisboard.themeManager
import kotlinx.coroutines.flow.update
class WallpaperChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
@@ -29,7 +30,8 @@ class WallpaperChangeReceiver : BroadcastReceiver() {
@Suppress("DEPRECATION") // We do not retrieve the wallpaper but only listen to changes
if (intent.action == Intent.ACTION_WALLPAPER_CHANGED) {
flogDebug { "Wallpaper changed" }
context.themeManager().value.updateActiveTheme()
val themeManager by context.themeManager()
themeManager.wallpaperChangedCounter.update { it + 1 }
}
}
}

View File

@@ -16,11 +16,13 @@
package dev.patrickgold.florisboard.lib
import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisallowComposableCalls
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SnapshotMutationPolicy
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.structuralEqualityPolicy
@@ -28,21 +30,16 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.compose.LocalLifecycleOwner
import dev.patrickgold.jetpref.datastore.model.PreferenceData
import dev.patrickgold.jetpref.datastore.model.PreferenceObserver
import kotlinx.coroutines.flow.map
@SuppressLint("StateFlowValueCalledInComposition")
@Composable
inline fun <V : Any, R : Any> PreferenceData<V>.observeAsTransformingState(
policy: SnapshotMutationPolicy<R> = structuralEqualityPolicy(),
crossinline transform: @DisallowComposableCalls (V) -> R,
): State<R> {
val lifecycleOwner = LocalLifecycleOwner.current
val state = remember(key) { mutableStateOf(transform(get()), policy) }
DisposableEffect(this, lifecycleOwner) {
val observer = PreferenceObserver<V> { newValue -> state.value = transform(newValue) }
observe(lifecycleOwner, observer)
onDispose { removeObserver(observer) }
return asFlow().let { flow ->
flow.map { transform(it) }.collectAsState(transform(flow.value))
}
return state
}
@Composable

View File

@@ -37,9 +37,9 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.app.AppPrefs
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.jetpref.datastore.ui.PreferenceLayout
import dev.patrickgold.jetpref.datastore.ui.PreferenceUiContent
import org.florisboard.lib.android.AndroidVersion
@@ -53,7 +53,7 @@ fun FlorisScreen(builder: @Composable FlorisScreenScope.() -> Unit) {
typealias FlorisScreenActions = @Composable RowScope.() -> Unit
typealias FlorisScreenBottomBar = @Composable () -> Unit
typealias FlorisScreenContent = PreferenceUiContent<AppPrefs>
typealias FlorisScreenContent = PreferenceUiContent<FlorisPreferenceModel>
typealias FlorisScreenFab = @Composable () -> Unit
typealias FlorisScreenNavigationIcon = @Composable () -> Unit
@@ -152,7 +152,7 @@ private class FlorisScreenScopeImpl : FlorisScreenScope {
Modifier
}
PreferenceLayout(
florisPreferenceModel(),
FlorisPreferenceStore,
modifier = Modifier
.padding(innerPadding)
.fillMaxWidth()

View File

@@ -56,6 +56,7 @@ import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import org.florisboard.lib.android.showShortToast
import dev.patrickgold.florisboard.lib.util.InputMethodUtils
import org.florisboard.lib.android.showShortToastSync
private const val AnimationDuration = 200
@@ -115,7 +116,7 @@ fun PreviewKeyboardField(
Row {
IconButton(onClick = {
if (!InputMethodUtils.showImePicker(context)) {
context.showShortToast("Error: InputMethodManager service not available!")
context.showShortToastSync("Error: InputMethodManager service not available!")
}
}) {
Icon(

View File

@@ -30,8 +30,8 @@ import android.widget.Toolbar
import androidx.activity.ComponentActivity
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.AppPrefs
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import org.florisboard.lib.android.stringRes
import dev.patrickgold.florisboard.lib.devtools.Devtools
import dev.patrickgold.florisboard.lib.devtools.LogTopic
@@ -39,14 +39,14 @@ import dev.patrickgold.florisboard.lib.devtools.flogWarning
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
private class SafePreferenceInstanceWrapper : ReadOnlyProperty<Any?, AppPrefs?> {
private class SafePreferenceInstanceWrapper : ReadOnlyProperty<Any?, FlorisPreferenceModel?> {
val cachedPreferenceModel = try {
florisPreferenceModel()
FlorisPreferenceStore
} catch (_: Throwable) {
null
}
override fun getValue(thisRef: Any?, property: KProperty<*>): AppPrefs? {
override fun getValue(thisRef: Any?, property: KProperty<*>): FlorisPreferenceModel? {
return cachedPreferenceModel?.getValue(thisRef, property)
}
}

View File

@@ -22,7 +22,7 @@ import android.os.Build
import android.os.Debug
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.AppPrefs
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.lib.titlecase
import dev.patrickgold.florisboard.lib.util.TimeUtils
@@ -35,7 +35,7 @@ import java.io.InputStreamReader
@Suppress("MemberVisibilityCanBePrivate")
object Devtools {
fun generateDebugLog(context: Context, prefs: AppPrefs? = null, includeLogcat: Boolean = false): String {
fun generateDebugLog(context: Context, prefs: FlorisPreferenceModel? = null, includeLogcat: Boolean = false): String {
return buildString {
append(generateDebugLogHeader(context, prefs))
if (includeLogcat) {
@@ -45,7 +45,7 @@ object Devtools {
}
}
fun generateDebugLogHeader(context: Context, prefs: AppPrefs? = null): String {
fun generateDebugLogHeader(context: Context, prefs: FlorisPreferenceModel? = null): String {
return buildString {
append(generateSystemInfoLog(context))
appendLine()
@@ -61,7 +61,7 @@ object Devtools {
}
}
fun generateDebugLogForGithub(context: Context, prefs: AppPrefs? = null, includeLogcat: Boolean = false): String {
fun generateDebugLogForGithub(context: Context, prefs: FlorisPreferenceModel? = null, includeLogcat: Boolean = false): String {
return buildString {
appendLine("<details>")
appendLine("<summary>Detailed info (Debug log header)</summary>")
@@ -113,7 +113,7 @@ object Devtools {
}
}
fun generateFeatureConfigLog(prefs: AppPrefs, withTitle: Boolean = true): String {
fun generateFeatureConfigLog(prefs: FlorisPreferenceModel, withTitle: Boolean = true): String {
return buildString {
if (withTitle) appendLine("======= FEATURE CONFIG =======")
append("Smartbar enabled : ").appendLine(prefs.smartbar.enabled.get())

View File

@@ -17,7 +17,7 @@
package dev.patrickgold.florisboard.lib.util
import android.content.Context
import dev.patrickgold.florisboard.app.AppPrefs
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
object AppVersionUtils {
private fun getRawVersionName(context: Context): String {
@@ -28,7 +28,7 @@ object AppVersionUtils {
}
}
fun shouldShowChangelog(context: Context, prefs: AppPrefs): Boolean {
fun shouldShowChangelog(context: Context, prefs: FlorisPreferenceModel): Boolean {
val installVersion =
VersionName.fromString(prefs.internal.versionOnInstall.get()) ?: VersionName.DEFAULT
val lastChangelogVersion =
@@ -39,14 +39,14 @@ object AppVersionUtils {
return lastChangelogVersion < currentVersion && installVersion != currentVersion
}
fun updateVersionOnInstallAndLastUse(context: Context, prefs: AppPrefs) {
suspend fun updateVersionOnInstallAndLastUse(context: Context, prefs: FlorisPreferenceModel) {
if (prefs.internal.versionOnInstall.get() == VersionName.DEFAULT_RAW) {
prefs.internal.versionOnInstall.set(getRawVersionName(context))
}
prefs.internal.versionLastUse.set(getRawVersionName(context))
}
fun updateVersionLastChangelog(context: Context, prefs: AppPrefs) {
suspend fun updateVersionLastChangelog(context: Context, prefs: FlorisPreferenceModel) {
prefs.internal.versionLastChangelog.set(getRawVersionName(context))
}
}

View File

@@ -1,6 +1,6 @@
[versions]
# Main
android-gradle-plugin = "8.9.3"
android-gradle-plugin = "8.10.1"
androidx-activity = "1.10.1"
androidx-autofill = "1.1.0"
androidx-collection = "1.5.0"
@@ -11,16 +11,16 @@ androidx-emoji2 = "1.5.0"
androidx-exifinterface = "1.4.1"
androidx-navigation = "2.9.0"
androidx-profileinstaller = "1.4.1"
androidx-room = "2.6.1"
androidx-room = "2.7.2"
cache4k = "0.7.0"
coil = "3.2.0"
kotlin = "2.1.20"
kotlin = "2.2.0"
kotlinx-coroutines = "1.10.2"
kotlinx-serialization-json = "1.8.1"
ksp = "2.1.20-1.0.32"
kotlinx-serialization-json = "1.9.0"
ksp = "2.2.0-2.0.2"
mikepenz-aboutlibraries = "12.1.2"
patrickgold-compose-tooltip = "0.2.0-rc02"
patrickgold-jetpref = "0.2.0-rc04"
patrickgold-jetpref = "0.3.0-beta01"
# Testing
androidx-benchmark = "1.3.4"
@@ -63,6 +63,7 @@ mikepenz-aboutlibraries-core = { module = "com.mikepenz:aboutlibraries-core", ve
mikepenz-aboutlibraries-compose = { module = "com.mikepenz:aboutlibraries-compose-m3", version.ref = "mikepenz-aboutlibraries" }
patrickgold-compose-tooltip = { module = "dev.patrickgold.compose.tooltip:tooltip", version.ref = "patrickgold-compose-tooltip" }
patrickgold-jetpref-datastore-model = { module = "dev.patrickgold.jetpref:jetpref-datastore-model", version.ref = "patrickgold-jetpref" }
patrickgold-jetpref-datastore-model-processor = { module = "dev.patrickgold.jetpref:jetpref-datastore-model-processor", version.ref = "patrickgold-jetpref" }
patrickgold-jetpref-datastore-ui = { module = "dev.patrickgold.jetpref:jetpref-datastore-ui", version.ref = "patrickgold-jetpref" }
patrickgold-jetpref-material-ui = { module = "dev.patrickgold.jetpref:jetpref-material-ui", version.ref = "patrickgold-jetpref" }

View File

@@ -19,6 +19,9 @@ package org.florisboard.lib.android
import android.content.Context
import android.widget.Toast
import androidx.annotation.StringRes
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.florisboard.lib.kotlin.CurlyArg
/**
@@ -26,8 +29,8 @@ import org.florisboard.lib.kotlin.CurlyArg
*
* @param text The text to show in the toast popup.
*/
fun Context.showShortToast(text: String): Toast {
return Toast.makeText(this, text, Toast.LENGTH_SHORT).also { it.show() }
suspend fun Context.showShortToast(text: String): Toast = withContext(Dispatchers.Main.immediate) {
Toast.makeText(this@showShortToast, text, Toast.LENGTH_SHORT).also { it.show() }
}
/**
@@ -35,9 +38,9 @@ fun Context.showShortToast(text: String): Toast {
*
* @param id The string resource id of the text to display. Must not be 0.
*/
fun Context.showShortToast(@StringRes id: Int): Toast {
suspend fun Context.showShortToast(@StringRes id: Int): Toast {
val text = this.stringRes(id)
return Toast.makeText(this, text, Toast.LENGTH_SHORT).also { it.show() }
return showShortToast(text)
}
/**
@@ -47,9 +50,9 @@ fun Context.showShortToast(@StringRes id: Int): Toast {
* @param id The string resource id of the text to display. Must not be 0.
* @param args The curly arguments which will be filled into the string template identified by [id].
*/
fun Context.showShortToast(@StringRes id: Int, vararg args: CurlyArg): Toast {
suspend fun Context.showShortToast(@StringRes id: Int, vararg args: CurlyArg): Toast {
val text = this.stringRes(id, *args)
return Toast.makeText(this, text, Toast.LENGTH_SHORT).also { it.show() }
return showShortToast(text)
}
/**
@@ -57,8 +60,8 @@ fun Context.showShortToast(@StringRes id: Int, vararg args: CurlyArg): Toast {
*
* @param text The text to show in the toast popup.
*/
fun Context.showLongToast(text: String): Toast {
return Toast.makeText(this, text, Toast.LENGTH_LONG).also { it.show() }
suspend fun Context.showLongToast(text: String): Toast = withContext(Dispatchers.Main.immediate) {
Toast.makeText(this@showLongToast, text, Toast.LENGTH_LONG).also { it.show() }
}
/**
@@ -66,9 +69,9 @@ fun Context.showLongToast(text: String): Toast {
*
* @param id The string resource id of the text to display. Must not be 0.
*/
fun Context.showLongToast(@StringRes id: Int): Toast {
suspend fun Context.showLongToast(@StringRes id: Int): Toast {
val text = this.stringRes(id)
return Toast.makeText(this, text, Toast.LENGTH_LONG).also { it.show() }
return showLongToast(text)
}
/**
@@ -78,7 +81,63 @@ fun Context.showLongToast(@StringRes id: Int): Toast {
* @param id The string resource id of the text to display. Must not be 0.
* @param args The curly arguments which will be filled into the string template identified by [id].
*/
fun Context.showLongToast(@StringRes id: Int, vararg args: CurlyArg): Toast {
suspend fun Context.showLongToast(@StringRes id: Int, vararg args: CurlyArg): Toast {
val text = this.stringRes(id, *args)
return Toast.makeText(this, text, Toast.LENGTH_LONG).also { it.show() }
return showLongToast(text)
}
// These wrappers are temporary, but needed.
// Gradually in the future all event logic will be suspendable, then these wrappers will not be needed anymore.
// DO NOT USE THESE IN SUSPENDABLE CONTEXTS, THIS CAUSES ISSUES
@Deprecated(
"Use suspend showShortToast instead",
ReplaceWith("showShortToast(text)")
)
fun Context.showShortToastSync(text: String): Toast = runBlocking {
showShortToast(text)
}
@Deprecated(
"Use suspend showShortToast instead",
ReplaceWith("showShortToast(id)")
)
fun Context.showShortToastSync(@StringRes id: Int): Toast = runBlocking {
showShortToast(id)
}
@Deprecated(
"Use suspend showShortToast instead",
ReplaceWith("showShortToast(id, *args)")
)
fun Context.showShortToastSync(@StringRes id: Int, vararg args: CurlyArg): Toast = runBlocking {
showShortToast(id, *args)
}
@Deprecated(
"Use suspend showLongToast instead",
ReplaceWith("showLongToast(text)")
)
fun Context.showLongToastSync(text: String): Toast = runBlocking {
showLongToast(text)
}
@Deprecated(
"Use suspend showLongToast instead",
ReplaceWith("showLongToast(id)")
)
fun Context.showLongToastSync(@StringRes id: Int): Toast = runBlocking {
showLongToast(id)
}
@Deprecated(
"Use suspend showLongToast instead",
ReplaceWith("showLongToast(id, *args)")
)
fun Context.showLongToastSync(@StringRes id: Int, vararg args: CurlyArg): Toast = runBlocking {
showLongToast(id, *args)
}

View File

@@ -19,13 +19,14 @@ package org.florisboard.lib.kotlin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
fun <T> Flow<T>.collectIn(scope: CoroutineScope, collector: FlowCollector<T>) {
scope.launch { this@collectIn.collect(collector) }
scope.launch { collect(collector) }
}
fun <T> Flow<T>.collectLatestIn(scope: CoroutineScope, action: suspend (value: T) -> Unit) {
scope.launch { this@collectLatestIn.collectLatest(action) }
fun <T> StateFlow<T>.collectLatestIn(scope: CoroutineScope, action: suspend (value: T) -> Unit) {
scope.launch { collectLatest(action) }
}