Compare commits

..

35 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
Lars Mühlbauer
c55a87862f Remove enforcement for navigationBarContrast (#2987)
* Remove enforcement for navigationBarContrast

* Use luminance for color contrast detection

* Always use light icons when background image is present
2025-07-22 01:10:29 +02:00
Patrick Goldinger
ee08c58db2 Release v0.5.0-beta02 2025-07-18 15:59:23 +02:00
Patrick Goldinger
05d4a5cf62 Update README.md (#2984)
Updates the milestone version numbers
2025-07-18 15:56:38 +02:00
florisboard-bot
c5c8f7a4c3 Update translations from Crowdin 2025-07-18 15:44:11 +02:00
Patrick Goldinger
ce3aee93d6 Merge pull request #2719 from florisboard/reproducible-builds
Add support for reproducible builds
2025-07-18 15:25:00 +02:00
Patrick Goldinger
96340f7277 Remove old flake build config 2025-07-16 23:11:28 +02:00
Patrick Goldinger
d0dcf5be38 Improve repr_build assemble.sh script 2025-07-16 23:03:09 +02:00
Patrick Goldinger
15450a760e Add ability for APK/AAB signing in repr_build setup 2025-07-16 22:28:14 +02:00
Patrick Goldinger
de0027d87e Add Gradle cache sharing & Add sha256sum generation 2025-07-16 20:24:03 +02:00
Patrick Goldinger
c7e83fca21 Add option to pass custom output directory 2025-07-16 15:29:11 +02:00
Lars Mühlbauer
115dc5c42a Add support for physical keyboard settings (#2981) 2025-07-16 14:41:49 +02:00
Patrick Goldinger
c78cf84d6c Fix Android SDK location and rename to repr_build 2025-07-16 12:58:03 +02:00
Lars Mühlbauer
5e59f144dd Update ROADMAP.md (#2980)
* Update ROADMAP.md

This commit updated the roadmap to include the latest changes,
especially that the 0.5 update was split and word suggestions
are pushed down to 0.6.

* Add time base theme switching to ROADMAP

* Mark spaces in uri bug as fixed in ROADMAP

* Fix typos
2025-07-16 00:55:59 +02:00
Patrick Goldinger
564c075763 Fix files with spaces causing UI issues (#2982) 2025-07-16 00:31:49 +02:00
Lars Mühlbauer
86365d393b Re-add time based theme switching (#2977)
* Add prefs for time based theme switching

* Implement time based theme switching

* Update to jetpref 0.2.0-rc04

* Remove hardcoded strings
2025-07-15 23:56:44 +02:00
Patrick Goldinger
45a0c9ef63 Upgrade gradle/actions/wrapper-validation to v4 2025-07-15 23:16:42 +02:00
Patrick Goldinger
25da6be1a4 Upgrade CMake to 4.0.2 2025-07-15 23:15:01 +02:00
Patrick Goldinger
976afc2e51 Fiz containerization failing for aab builds 2025-07-15 22:47:39 +02:00
Patrick Goldinger
d994da8c97 Rework containerization and add track logic 2025-07-15 22:30:31 +02:00
Patrick Goldinger
40a22a762a Add containerization config for building the project 2025-07-15 16:53:18 +02:00
Patrick Goldinger
264b9ff98b Add -Wl,--build-id=none linker option
Required for reproducible builds to work
2025-07-15 16:52:54 +02:00
Patrick Goldinger
f1b7ddedb8 Add new tools version catalog 2025-07-15 16:51:53 +02:00
Lars Mühlbauer
b1665f61e5 Remove checks for API 26 as this is the min API (#2972) 2025-07-15 11:49:29 +02:00
145 changed files with 2230 additions and 1091 deletions

11
.dockerignore Normal file
View File

@@ -0,0 +1,11 @@
**/.*/
!.git/
**/build/
**/dist/
**/out/
**/target/
utils/
!utils/repr_build/scripts/
.env
gradlew.bat
local.properties

1
.envrc
View File

@@ -1 +0,0 @@
use_flake

View File

@@ -25,14 +25,14 @@ jobs:
with:
submodules: recursive
- uses: gradle/actions/wrapper-validation@v3
- uses: gradle/actions/wrapper-validation@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: temurin
- name: Set up CMake and Ninja
uses: lukka/get-cmake@latest
uses: lukka/get-cmake@v4.0.2
- name: Build with Gradle
run: ./gradlew clean assembleDebug
- uses: actions/upload-artifact@v4

View File

@@ -64,7 +64,7 @@ fully respecting your privacy. Currently in early-beta state.
</tr>
</table>
Beginning with v0.6.0 FlorisBoard will enter the public beta on Google Play.
Beginning with v0.7 FlorisBoard will enter the public beta on Google Play.
## Highlighted features
- Integrated clipboard manager / history
@@ -74,7 +74,7 @@ Beginning with v0.6.0 FlorisBoard will enter the public beta on Google Play.
> [!IMPORTANT]
> Word suggestions/spell checking are not included in the current releases
> and are a major goal for the v0.5 milestone.
> and are a major goal for the v0.6 milestone.
Feature roadmap: See [ROADMAP.md](ROADMAP.md)

View File

@@ -6,34 +6,49 @@ Each major milestone has associated alpha/beta releases, so if you are intereste
## 0.5 (currently in development)
- [x] Theme rework part II / Snygg v2
- See https://github.com/florisboard/florisboard/pull/2855
> [!NOTE]
> The milestone 0.5 was split, thus the word suggestions now come with version 0.6. The old version 0.6 has been moved down and is now 0.7. The time it takes to implement word suggestions will not change, but we can now release the new theme editor earlier, which would otherwise lie dormant.
- [ ] Theme rework part II / Snygg v2
- [x] See https://github.com/florisboard/florisboard/pull/2855
- [x] Spaces in URI bug (See https://github.com/florisboard/florisboard/issues/2898)
- [ ] Rework cache manager (See https://github.com/florisboard/florisboard/issues/2870)
- [x] Re-add time based theme switching (See https://github.com/florisboard/florisboard/pull/2977)
- [ ] Add support for any remaining new features introduced with Android 13 / 14
- [ ] Proper physical keyboard support (See https://github.com/florisboard/florisboard/issues/2815)
- [x] Raise minimum required Android version from Android 7 (SDK level 24) to Android 8 (SDK level 26)
## 0.6
- [ ] Implement predictive text support / spell checking
- [ ] Add new extension type: Language Pack
- Basically groups all locale-relevant data (predictive base model, emoji suggestion data, ...)
in a dynamically importable extension file
- [ ] Add support for any remaining new features introduced with Android 13 / 14
- [x] Raise minimum required Android version from Android 7 (SDK level 24) to Android 8 (SDK level 26)
- Basically groups all locale-relevant data (predictive base model, emoji suggestion data, ...)
in a dynamically importable extension file
## k3lp
> [!NOTE]
> The development of k3lp is not tied to a florisboard version and takes place on [codeberg.org](https://codeberg.org/k3lp/k3lp) simultaneously.
- [ ] New keyboard layout engine + file syntax based on the upcoming Unicode Keyboard v3 standard
- [ ] Add Tablet mode / Optimizations for landscape input based on new keyboard layout engine
- [ ] Not bound to a specific FlorisBoard version
## 0.6
## 0.7+
> [!NOTE]
> From 0.6 onwards we plan to have more stable 0.X releases but with at most one large feature per release, thus having a much quicker iteration of new features on the stable track, which is a benefit for everyone involved.
- [ ] Add floating keyboard mode
- [ ] New text processing logic
- [ ] Complete rework of the Emoji panel
- Emoji search
- Fully scrollable emoji list (soft category borders)
- Side scrollable emoji list (swipe for next category)
- More granular theming options
- Layout customization (e.g. placement of category buttons)
- Maybe: consider upgrading to emoji2 for better unified system-wide emoji styles
- [ ] Emoji search
- [ ] Fully scrollable emoji list (soft category borders)
- [ ] Side scrollable emoji list (swipe for next category)
- [ ] More granular theming options
- [ ] Layout customization (e.g. placement of category buttons)
- [ ] Maybe: consider upgrading to emoji2 for better unified system-wide emoji styles
- [ ] Reimplementation of glide typing with the new layout engine and predictive text core
- [ ] Prepare FlorisBoard repository and app store presence for public beta release on Google Play (will go live with stable 0.6)
- [ ] Prepare FlorisBoard repository and app store presence for public beta release on Google Play (will go live with stable 0.7)
- [ ] Rework branding images and texts of FlorisBoard for the app stores
- [ ] Focus on stability and experience improvements of the app and keyboard
- [ ] Add support for new features introduced with Android 15 / 16

View File

@@ -28,8 +28,6 @@ plugins {
val projectMinSdk: String by project
val projectTargetSdk: String by project
val projectCompileSdk: String by project
val projectBuildToolsVersion: String by project
val projectNdkVersion: String by project
val projectVersionCode: String by project
val projectVersionName: String by project
val projectVersionNameSuffix = projectVersionName.substringAfter("-", "").let { suffix ->
@@ -43,8 +41,8 @@ val projectVersionNameSuffix = projectVersionName.substringAfter("-", "").let {
android {
namespace = "dev.patrickgold.florisboard"
compileSdk = projectCompileSdk.toInt()
buildToolsVersion = projectBuildToolsVersion
ndkVersion = projectNdkVersion
buildToolsVersion = tools.versions.buildTools.get()
ndkVersion = tools.versions.ndk.get()
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
@@ -208,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,20 +278,23 @@ 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.asFlow().collectIn(lifecycleScope) {
updateInputViewShown()
}
@Suppress("DEPRECATION") // We do not retrieve the wallpaper but only listen to changes
registerReceiver(wallpaperChangeReceiver, IntentFilter(Intent.ACTION_WALLPAPER_CHANGED))
}
@@ -356,6 +360,13 @@ class FlorisImeService : LifecycleInputMethodService() {
}
}
override fun onEvaluateInputViewShown(): Boolean {
val config = resources.configuration
return super.onEvaluateInputViewShown()
|| config.keyboard == Configuration.KEYBOARD_NOKEYS
|| prefs.physicalKeyboard.showOnScreenKeyboard.get()
}
override fun onUpdateSelection(
oldSelStart: Int,
oldSelEnd: Int,
@@ -398,7 +409,6 @@ class FlorisImeService : LifecycleInputMethodService() {
flogInfo(LogTopic.IMS_EVENTS)
}
isWindowShown = true
themeManager.updateActiveTheme()
inputFeedbackController.updateSystemPrefsState()
}
@@ -759,7 +769,8 @@ class FlorisImeService : LifecycleInputMethodService() {
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
) {
SnyggBox(FlorisImeUi.ExtractedLandscapeInputLayout.elementName,
SnyggBox(
elementName = FlorisImeUi.ExtractedLandscapeInputLayout.elementName,
modifier = Modifier
.fillMaxHeight()
.weight(1f),

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,9 @@ 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
import dev.patrickgold.jetpref.datastore.model.PreferenceModel
@@ -68,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(
@@ -638,6 +645,14 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
)
}
val physicalKeyboard = PhysicalKeyboard()
inner class PhysicalKeyboard {
val showOnScreenKeyboard = boolean(
key = "physical_keyboard__show_on_screen_keyboard",
default = false,
)
}
val smartbar = Smartbar()
inner class Smartbar {
val enabled = boolean(
@@ -749,14 +764,14 @@ class AppPrefs : PreferenceModel("florisboard-app-prefs") {
},
serializer = ColorPreferenceSerializer,
)
//val sunriseTime = localTime(
// key = "theme__sunrise_time",
// default = LocalTime.of(6, 0),
//)
//val sunsetTime = localTime(
// key = "theme__sunset_time",
// default = LocalTime.of(18, 0),
//)
val sunriseTime = localTime(
key = "theme__sunrise_time",
default = LocalTime(6, 0),
)
val sunsetTime = localTime(
key = "theme__sunset_time",
default = LocalTime(18, 0),
)
val editorColorRepresentation = enum(
key = "theme__editor_color_representation",
default = ColorRepresentation.HEX,

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

@@ -47,8 +47,9 @@ import dev.patrickgold.florisboard.app.settings.HomeScreen
import dev.patrickgold.florisboard.app.settings.about.AboutScreen
import dev.patrickgold.florisboard.app.settings.about.ProjectLicenseScreen
import dev.patrickgold.florisboard.app.settings.about.ThirdPartyLicensesScreen
import dev.patrickgold.florisboard.app.settings.advanced.OtherScreen
import dev.patrickgold.florisboard.app.settings.advanced.BackupScreen
import dev.patrickgold.florisboard.app.settings.advanced.OtherScreen
import dev.patrickgold.florisboard.app.settings.advanced.PhysicalKeyboardScreen
import dev.patrickgold.florisboard.app.settings.advanced.RestoreScreen
import dev.patrickgold.florisboard.app.settings.clipboard.ClipboardScreen
import dev.patrickgold.florisboard.app.settings.dictionary.DictionaryScreen
@@ -111,6 +112,7 @@ object Routes {
const val Media = "settings/media"
const val Other = "settings/other"
const val PhysicalKeyboard = "settings/other/physical-keyboard"
const val Backup = "settings/other/backup"
const val Restore = "settings/other/restore"
@@ -240,6 +242,7 @@ object Routes {
composableWithDeepLink(Settings.Media) { MediaScreen() }
composableWithDeepLink(Settings.Other) { OtherScreen() }
composableWithDeepLink(Settings.PhysicalKeyboard) { PhysicalKeyboardScreen() }
composableWithDeepLink(Settings.Backup) { BackupScreen() }
composableWithDeepLink(Settings.Restore) { RestoreScreen() }

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

@@ -19,7 +19,6 @@ package dev.patrickgold.florisboard.app.settings
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Assignment
import androidx.compose.material.icons.filled.Adb
import androidx.compose.material.icons.filled.Extension
import androidx.compose.material.icons.filled.Gesture
import androidx.compose.material.icons.filled.Language

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

@@ -45,6 +45,7 @@ import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import dev.patrickgold.jetpref.datastore.ui.isMaterialYou
import dev.patrickgold.jetpref.datastore.ui.listPrefEntries
import dev.patrickgold.jetpref.datastore.ui.vectorResource
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.color.ColorMappings
@@ -154,6 +155,11 @@ fun OtherScreen() = FlorisScreen {
},
enabledIf = { AndroidVersion.ATMOST_API28_P },
)
Preference(
icon = vectorResource(R.drawable.ic_keyboard_keys),
title = stringRes(R.string.physical_keyboard__title),
onClick = { navController.navigate(Routes.Settings.PhysicalKeyboard) },
)
Preference(
icon = Icons.Default.Adb,
title = stringRes(R.string.devtools__title),

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.app.settings.advanced
import android.content.Intent
import android.content.res.Configuration
import android.provider.Settings
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
@Composable
fun PhysicalKeyboardScreen() = FlorisScreen {
title = stringRes(R.string.physical_keyboard__title)
val context = LocalContext.current
val physicalKeyboardAttached by remember {
mutableStateOf(context.resources.configuration.keyboard != Configuration.KEYBOARD_NOKEYS)
}
val activityForResult = rememberLauncherForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { }
content {
if (physicalKeyboardAttached) {
Preference(
title = stringRes(R.string.physical_keyboard__system_settings__title),
summary = stringRes(R.string.physical_keyboard__system_settings__summary),
onClick = {
activityForResult.launch(Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS))
}
)
} else {
Preference(
title = stringRes(R.string.physical_keyboard__system_settings__title),
summary = stringRes(R.string.physical_keyboard__system_settings__summary_not_attached),
)
}
SwitchPreference(
pref = prefs.physicalKeyboard.showOnScreenKeyboard,
title = stringRes(R.string.physical_keyboard__show_on_screen_keyboard__title),
summary = stringRes(R.string.physical_keyboard__show_on_screen_keyboard__summary),
)
}
}

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

@@ -20,11 +20,8 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
import dev.patrickgold.florisboard.ime.input.InputFeedbackActivationMode
import dev.patrickgold.florisboard.ime.input.HapticVibrationMode
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.systemVibratorOrNull
import org.florisboard.lib.android.vibrate
import dev.patrickgold.florisboard.ime.input.InputFeedbackActivationMode
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
@@ -32,6 +29,8 @@ import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import org.florisboard.lib.android.systemVibratorOrNull
import org.florisboard.lib.android.vibrate
@OptIn(ExperimentalJetPrefDatastoreUi::class)
@Composable
@@ -155,7 +154,7 @@ fun InputFeedbackScreen() = FlorisScreen {
prefs.inputFeedback.hapticEnabled isEqualTo true &&
prefs.inputFeedback.hapticVibrationMode isEqualTo HapticVibrationMode.USE_VIBRATOR_DIRECTLY &&
vibrator != null && vibrator.hasVibrator() &&
AndroidVersion.ATLEAST_API26_O && vibrator.hasAmplitudeControl()
vibrator.hasAmplitudeControl()
},
)
SwitchPreference(

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

@@ -16,6 +16,7 @@
package dev.patrickgold.florisboard.app.settings.theme
import android.net.Uri
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.background
import androidx.compose.foundation.border
@@ -655,7 +656,7 @@ private fun PropertyValueEditor(
JetPrefListItem(
modifier = Modifier.clickable {
val relPath = file.path.removePrefix(workspace.extDir.path)
inputStr = "flex:$relPath"
inputStr = "flex:" + Uri.encode(relPath, "/")
onValueChange(SnyggUriValue(inputStr))
showSelectFileDialog = false
},

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

@@ -16,19 +16,18 @@
package dev.patrickgold.florisboard.app.settings.theme
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Brightness2
import androidx.compose.material.icons.filled.BrightnessAuto
import androidx.compose.material.icons.filled.ColorLens
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.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
@@ -37,7 +36,6 @@ import dev.patrickgold.florisboard.app.ext.AddonManagementReferenceBox
import dev.patrickgold.florisboard.app.ext.ExtensionListScreenType
import dev.patrickgold.florisboard.ime.theme.ThemeManager
import dev.patrickgold.florisboard.ime.theme.ThemeMode
import dev.patrickgold.florisboard.lib.compose.FlorisInfoCard
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.florisboard.lib.compose.stringRes
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
@@ -45,6 +43,7 @@ import dev.patrickgold.florisboard.themeManager
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.datastore.ui.ColorPickerPreference
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.LocalTimePickerPreference
import dev.patrickgold.jetpref.datastore.ui.Preference
import dev.patrickgold.jetpref.datastore.ui.isMaterialYou
import org.florisboard.lib.color.ColorMappings
@@ -60,41 +59,21 @@ 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()
}
content {
val themeMode by prefs.theme.mode.observeAsState()
val dayThemeId by prefs.theme.dayThemeId.observeAsState()
val nightThemeId by prefs.theme.nightThemeId.observeAsState()
/*Card(modifier = Modifier.padding(8.dp)) {
Column(modifier = Modifier.padding(8.dp)) {
Text("If you want to give feedback on the new stylesheet editor and theme engine, please do so in below linked feedback thread:\n")
Button(onClick = {
context.launchUrl("https://github.com/florisboard/florisboard/discussions/1531")
}) {
Text("Open Feedback Thread")
}
}
}*/
ListPreference(
prefs.theme.mode,
icon = Icons.Default.BrightnessAuto,
title = stringRes(R.string.pref__theme__mode__label),
entries = enumDisplayEntriesOf(ThemeMode::class),
)
if (themeMode == ThemeMode.FOLLOW_TIME) {
FlorisInfoCard(
modifier = Modifier.padding(8.dp),
text = """
The theme mode "Follow time" is not available in this beta release.
""".trimIndent()
)
}
Preference(
icon = Icons.Default.LightMode,
title = stringRes(R.string.pref__theme__day),
@@ -113,6 +92,18 @@ fun ThemeScreen() = FlorisScreen {
navController.navigate(Routes.Settings.ThemeManager(ThemeManagerScreenAction.SELECT_NIGHT))
},
)
LocalTimePickerPreference(
pref = prefs.theme.sunriseTime,
title = stringRes(R.string.pref__theme__sunrise_time__label),
icon = Icons.Default.WbTwilight,
enabledIf = { prefs.theme.mode isEqualTo ThemeMode.FOLLOW_TIME },
)
LocalTimePickerPreference(
pref = prefs.theme.sunsetTime,
title = stringRes(R.string.pref__theme__sunset_time__label),
icon = Icons.Default.Brightness2,
enabledIf = { prefs.theme.mode isEqualTo ThemeMode.FOLLOW_TIME },
)
ColorPickerPreference(
pref = prefs.theme.accentColor,
title = stringRes(R.string.pref__theme__theme_accent_color__label),

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
@@ -50,70 +48,69 @@ import dev.patrickgold.florisboard.lib.devtools.flogInfo
import dev.patrickgold.florisboard.lib.ext.ExtensionComponentName
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 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 java.util.UUID
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()
}
}
@@ -122,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()
@@ -187,27 +182,22 @@ class ThemeManager(context: Context) {
prefs.theme.dayThemeId.get()
}
ThemeMode.FOLLOW_TIME -> {
//if (AndroidVersion.ATLEAST_API26_O) {
// val current = LocalTime.now()
// val sunrise = prefs.theme.sunriseTime.get()
// val sunset = prefs.theme.sunsetTime.get()
// if (current in sunrise..sunset) {
// prefs.theme.dayThemeId.get()
// } else {
// prefs.theme.nightThemeId.get()
// }
//} else {
val current = LocalTime.now()
val sunrise = prefs.theme.sunriseTime.get().javaLocalTime
val sunset = prefs.theme.sunsetTime.get().javaLocalTime
if (current in sunrise..sunset) {
prefs.theme.dayThemeId.get()
} else {
prefs.theme.nightThemeId.get()
//}
}
}
}
}
/**
* 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

@@ -23,22 +23,35 @@ import android.inputmethodservice.InputMethodService
import android.view.Window
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.graphics.luminance
import androidx.compose.ui.platform.LocalView
import androidx.core.view.WindowInsetsControllerCompat
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
import org.florisboard.lib.snygg.ui.uriOrNull
@Composable
fun SystemUiIme() {
val useDarkIcons = !FlorisImeTheme.config.isNightTheme
val backgroundQuery = rememberSnyggThemeQuery(FlorisImeUi.Window.elementName)
val backgroundColor = backgroundQuery.background()
val backgroundImage = backgroundQuery.backgroundImage.uriOrNull()
val hasBackgroundImage = backgroundImage != null
val useDarkIcons = if (backgroundImage == null) {
backgroundColor.luminance() >= 0.5
} else {
false
}
val view = LocalView.current
val window = view.context.findWindow()!!
val windowInsetsController = WindowInsetsControllerCompat(window, view)
LaunchedEffect(useDarkIcons) {
LaunchedEffect(useDarkIcons, hasBackgroundImage) {
windowInsetsController.isAppearanceLightNavigationBars = useDarkIcons
if (AndroidVersion.ATLEAST_API29_Q) {
window.isNavigationBarContrastEnforced = true
window.isNavigationBarContrastEnforced = hasBackgroundImage
}
}
}

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

@@ -35,12 +35,11 @@ import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.lib.devtools.LogTopic
import dev.patrickgold.florisboard.lib.devtools.flogError
import dev.patrickgold.florisboard.lib.devtools.flogInfo
import org.florisboard.lib.android.AndroidVersion
import java.lang.ref.WeakReference
import org.florisboard.lib.kotlin.io.FsDir
import org.florisboard.lib.kotlin.io.FsFile
import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
import java.lang.ref.WeakReference
import kotlin.system.exitProcess
/**
@@ -113,7 +112,7 @@ abstract class CrashUtility private constructor() {
application.registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
savedInstanceState: Bundle?,
) {
if (activity !is CrashDialogActivity) {
lastActivityCreated = WeakReference(activity)
@@ -125,28 +124,26 @@ abstract class CrashUtility private constructor() {
override fun onActivityStopped(activity: Activity) {}
override fun onActivitySaveInstanceState(
activity: Activity,
outState: Bundle
outState: Bundle,
) {}
override fun onActivityDestroyed(activity: Activity) {}
})
if (AndroidVersion.ATLEAST_API26_O) {
try {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
if (notificationManager != null && notificationManager is NotificationManager) {
val notificationChannel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
context.resources.getString(R.string.crash_notification_channel__title),
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(notificationChannel)
}
flogInfo(LogTopic.CRASH_UTILITY) {
"Successfully created crash handler notification channel!"
}
} catch (e: Exception) {
flogError(LogTopic.CRASH_UTILITY) {
"Failed to create crash handler notification channel due to an unspecified error:\n$e"
}
try {
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
if (notificationManager != null && notificationManager is NotificationManager) {
val notificationChannel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
context.resources.getString(R.string.crash_notification_channel__title),
NotificationManager.IMPORTANCE_HIGH
)
notificationManager.createNotificationChannel(notificationChannel)
}
flogInfo(LogTopic.CRASH_UTILITY) {
"Successfully created crash handler notification channel!"
}
} catch (e: Exception) {
flogError(LogTopic.CRASH_UTILITY) {
"Failed to create crash handler notification channel due to an unspecified error:\n$e"
}
}
} else {
@@ -274,14 +271,7 @@ abstract class CrashUtility private constructor() {
context ?: return
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE)
if (notificationManager != null && notificationManager is NotificationManager) {
val notificationBuilder = if (AndroidVersion.ATLEAST_API26_O) {
Notification.Builder(context.applicationContext, NOTIFICATION_CHANNEL_ID)
} else {
@Suppress("DEPRECATION")
Notification.Builder(context.applicationContext).apply {
setPriority(Notification.PRIORITY_MAX)
}
}
val notificationBuilder = Notification.Builder(context.applicationContext, NOTIFICATION_CHANNEL_ID)
val crashDialogIntent = Intent(context, CrashDialogActivity::class.java)
val notification = notificationBuilder.run {
setContentTitle(title)
@@ -369,7 +359,7 @@ abstract class CrashUtility private constructor() {
*/
data class Stacktrace(
val name: String,
val details: String
val details: String,
)
/**

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

@@ -17,10 +17,8 @@
package dev.patrickgold.florisboard.lib.util
import android.icu.text.SimpleDateFormat
import android.icu.util.Calendar
import android.icu.util.TimeZone
import dev.patrickgold.florisboard.lib.FlorisLocale
import org.florisboard.lib.android.AndroidVersion
import dev.patrickgold.jetpref.datastore.model.LocalTime
import java.time.Instant
import java.time.format.DateTimeFormatter
@@ -28,10 +26,9 @@ object TimeUtils {
private val ISO_INSTANT = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", FlorisLocale.ENGLISH.base)
fun currentUtcTimestamp(): CharSequence {
return if (AndroidVersion.ATLEAST_API26_O) {
DateTimeFormatter.ISO_INSTANT.format(Instant.now())
} else {
ISO_INSTANT.format(Calendar.getInstance(TimeZone.GMT_ZONE, FlorisLocale.ENGLISH.base))
}
return DateTimeFormatter.ISO_INSTANT.format(Instant.now())
}
val LocalTime.javaLocalTime: java.time.LocalTime
get() = java.time.LocalTime.of(hour, minute)
}

View File

@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#000000"
android:pathData="M280,680L280,600L680,600L680,680L280,680ZM120,520L120,440L200,440L200,520L120,520ZM280,520L280,440L360,440L360,520L280,520ZM440,520L440,440L520,440L520,520L440,520ZM600,520L600,440L680,440L680,520L600,520ZM760,520L760,440L840,440L840,520L760,520ZM120,360L120,280L200,280L200,360L120,360ZM280,360L280,280L360,280L360,360L280,360ZM440,360L440,280L520,280L520,360L440,360ZM600,360L600,280L680,280L680,360L600,360ZM760,360L760,280L840,280L840,360L760,360Z"/>
</vector>

View File

@@ -72,6 +72,8 @@
<string name="quick_action__ime_ui_mode_clipboard__tooltip">فتح سجل الحافظة</string>
<string name="quick_action__ime_ui_mode_media" maxLength="12">رمز تعبيري</string>
<string name="quick_action__ime_ui_mode_media__tooltip">فتح نافذة الرموز التعبيرية</string>
<string name="quick_action__language_switch" maxLength="12">تبديل اللغة</string>
<string name="quick_action__language_switch__tooltip">تنفيذ تبديل اللغة</string>
<string name="quick_action__settings" maxLength="12">الإعدادات</string>
<string name="quick_action__settings__tooltip">فتح الإعدادات</string>
<string name="quick_action__undo" maxLength="12">تراجع</string>
@@ -459,6 +461,7 @@
<string name="setup__finish_up__description_p1">تم تمكين{app_name} في النظام وجاهز للتخصيص بأسلوبك.</string>
<string name="setup__finish_up__description_p2">إذا واجهت أي مشاكل أو أخطاء أو أعطال أو اذا كنت ترغب فقط في تقديم اقتراح ، تحقق من مستودع المشروع من شاشة \"حول التطبيق\"!</string>
<string name="setup__finish_up__finish_btn">أبدأ بالتخصيص</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<string name="backup_and_restore__title">النسخ الإحتياطي و الإستعادة</string>
<string name="backup_and_restore__back_up__title">النسخ الاحتياطي للبيانات</string>

View File

@@ -188,6 +188,7 @@
<string name="setup__finish_up__description_p1">{app_name} ta activáu nel sistema y yá pues personalizalu.</string>
<string name="setup__finish_up__description_p2">Si atopes cualesquier problema, fallu, casque o namás quier facer dalguna suxerencia, consulta\'l depósitu del proyeutu na pantalla «Tocante a».</string>
<string name="setup__finish_up__finish_btn">Comenzar a personalizar</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<string name="backup_and_restore__back_up__destination_file_sys">Sistema de ficheros llocal</string>
<string name="backup_and_restore__back_up__destination_share_intent">Aplicación de terceros pel menú de compartición</string>

View File

@@ -557,6 +557,13 @@
<string name="setup__finish_up__description_p1">Клавииатурата на {app_name} вече е включена в системата и готова да я настроите според вашите предпочитения.</string>
<string name="setup__finish_up__description_p2">Акосе сблъскате с проблеми, дефекти, сривове или просто искате да направите предложение, разгледайте хранилището на проекта от екрана Относно!</string>
<string name="setup__finish_up__finish_btn">Започнете да настройвате</string>
<!-- Physical keyboard -->
<string name="physical_keyboard__title">Физическа клавиатура</string>
<string name="physical_keyboard__system_settings__title">Системни настройки на физическа клавиатура</string>
<string name="physical_keyboard__system_settings__summary">Подредби, клав. комбинации и специални клавиши</string>
<string name="physical_keyboard__system_settings__summary_not_attached">Достъпно само при включена клавиатура</string>
<string name="physical_keyboard__show_on_screen_keyboard__title">Екранна клавиатура</string>
<string name="physical_keyboard__show_on_screen_keyboard__summary">Показване на екранна клавиатура при използване на физкческа клавиатура</string>
<!-- Back up & Restore -->
<string name="backup_and_restore__title">Резервно копие и възстановяване</string>
<string name="backup_and_restore__back_up__title">Резервно копие на данни</string>

View File

@@ -57,6 +57,7 @@
<string name="about__view_source_code" comment="Label of View source code button in About">Izvorni kod</string>
<!-- Setup UI strings -->
<string name="setup__title" comment="Title of Setup">Dobrodošli!</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<!-- Crash Dialog strings -->
<string name="crash_dialog__close" comment="Label of Close button in crash dialog">Zatvori</string>

View File

@@ -481,6 +481,7 @@
<string name="setup__finish_up__description_p1">{app_name} s\'habilitat al sistema i està preparat perquè el personalitzis.</string>
<string name="setup__finish_up__description_p2">Si trobes cap incidència, error, fallada o simplement vols suggerir alguna cosa, només has d\'anar al repositori del projecte des de la pantalla Quant a!</string>
<string name="setup__finish_up__finish_btn">Comença a personalitzar</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<string name="backup_and_restore__title">Còpia de seguretat &amp; Restauració</string>
<string name="backup_and_restore__back_up__title">Còpia de seguretat de les dades</string>

View File

@@ -423,6 +423,7 @@
<string name="setup__finish_up__description_p1">{app_name} ئێستا چالاکە بۆ بەکارهێنان و نووسین لەسەر مۆبایلەکەت.</string>
<string name="setup__finish_up__description_p2">لەکاتی بوونی هەر پرسیار و سەرنج و پێشنیارێک ئەتوانن سەردانی بەشی تایبەتی یارمەتیدان بکەن!</string>
<string name="setup__finish_up__finish_btn">ڕێکخستنەکانی تەختەکلیل</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<string name="backup_and_restore__title">پاشەکەوتکردن و گێڕانەوە</string>
<string name="backup_and_restore__back_up__title">پاشەکەوتکردنی داتاکان</string>

View File

@@ -559,6 +559,13 @@
<string name="setup__finish_up__description_p1">{app_name} je nyní povolena ve vašem systému a je připravena na přizpůsobení.</string>
<string name="setup__finish_up__description_p2">Pokud objevíte jakékoli problémy, chyby, pády nebo prostě jen budete chtít podat návrh, podívejte se na repozitář projektu na obrazovce O aplikaci!</string>
<string name="setup__finish_up__finish_btn">Začít přizpůsobovat</string>
<!-- Physical keyboard -->
<string name="physical_keyboard__title">Fyzická klávesnice</string>
<string name="physical_keyboard__system_settings__title">Systémová nastavení fyzické klávesnice</string>
<string name="physical_keyboard__system_settings__summary">Rozložení, klávesové zkratky a modifikátory</string>
<string name="physical_keyboard__system_settings__summary_not_attached">Dostupné pouze při připojené klávesnici</string>
<string name="physical_keyboard__show_on_screen_keyboard__title">Zobrazit klávesnici na obrazovce</string>
<string name="physical_keyboard__show_on_screen_keyboard__summary">Zobrazí klávesnici na obrazovce při používání fyzické klávesnice</string>
<!-- Back up & Restore -->
<string name="backup_and_restore__title">Záloha a obnovení</string>
<string name="backup_and_restore__back_up__title">Zálohovat data</string>

View File

@@ -252,6 +252,7 @@
<string name="setup__enable_ime__title">Aktivér {app_name}</string>
<string name="setup__select_ime__title">Vælg {app_name}</string>
<string name="setup__select_ime__switch_keyboard_btn">Skift tastatur</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">FlorisBoard fejlrapport</string>

View File

@@ -482,6 +482,7 @@
<string name="setup__finish_up__description_p1">{app_name} ist nun im System aktiviert und bereit von dir angepasst zu werden.</string>
<string name="setup__finish_up__description_p2">Falls dir irgendwelche Probleme, Bugs oder Abstürze begegnen, oder du einfach einen Vorschlag machen möchtest, besuche einfach die GitHub Seite des Projekts (zu finden im About \"Über die App\" Screen)!</string>
<string name="setup__finish_up__finish_btn">Anpassung beginnen</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<string name="backup_and_restore__title">Sichern &amp; Wiederherstellen</string>
<string name="backup_and_restore__back_up__title">Daten sichern</string>

View File

@@ -226,6 +226,7 @@
<string name="setup__finish_up__description_p1">{app_name} είναι τώρα ενεργοποιημένο στο σύστημα και έτοιμο να προσαρμοστεί από εσάς.</string>
<string name="setup__finish_up__description_p2">Εάν αντιμετωπίσετε τυχόν θέματα, προβλήματα, σφάλματα ή απλώς θέλετε να κάνετε μία πρόταση, ρίξτε μία ματιά στο αποθετήριο από το πεδίο σχετικά με!</string>
<string name="setup__finish_up__finish_btn">Ξεκινήστε την προσαρμογή</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<string name="backup_and_restore__restore__title">Επαναφορά δεδομένων</string>
<string name="backup_and_restore__restore__mode">Λειτουργία επαναφοράς</string>

View File

@@ -388,6 +388,7 @@
<string name="setup__grant_notification_permission__btn">Permesi</string>
<string name="setup__finish_up__title">Plenumi</string>
<string name="setup__finish_up__finish_btn">Komenci agordi</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<string name="backup_and_restore__title">Savkopiado &amp; Restaŭrigo</string>
<string name="backup_and_restore__back_up__title">Savi datumojn</string>

View File

@@ -554,6 +554,13 @@
<string name="setup__finish_up__description_p1">{app_name} ahora está habilitado en su sistema y listo para ser personalizado.</string>
<string name="setup__finish_up__description_p2">Si encuentra algún problema, errores, fallos o sólo desea hacer alguna sugerencia, ¡revise el repositorio del proyecto en el menú de \"Acerca de\"!</string>
<string name="setup__finish_up__finish_btn">Empiece a personalizar</string>
<!-- Physical keyboard -->
<string name="physical_keyboard__title">Teclado físico</string>
<string name="physical_keyboard__system_settings__title">Configuración del teclado físico del sistema</string>
<string name="physical_keyboard__system_settings__summary">Distribuciones, atajos de teclado y teclas modificadoras</string>
<string name="physical_keyboard__system_settings__summary_not_attached">Solo disponible cuando el teclado está conectado</string>
<string name="physical_keyboard__show_on_screen_keyboard__title">Mostrar teclado en pantalla</string>
<string name="physical_keyboard__show_on_screen_keyboard__summary">Mostrar el teclado en pantalla mientras se utiliza el teclado físico</string>
<!-- Back up & Restore -->
<string name="backup_and_restore__title">Respaldar &amp; Restaurar</string>
<string name="backup_and_restore__back_up__title">Respaldar datos</string>

View File

@@ -0,0 +1,100 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="app_name">FlorisBoard</string>
<string name="key__phone_pause" comment="Label for the Pause key in the telephone keyboard layout">Paus</string>
<string name="key__phone_wait" comment="Label for the Wait key in the telephone keyboard layout">Oota</string>
<string name="key_popup__threedots_alt" comment="Content description for the three-dots icon in a key popup">Kolme punktiga ikoon. Kui nähtav, siis saab pikemal vajutusel kasutada rohkem tähti.</string>
<!-- One-handed strings -->
<string name="one_handed__close_btn_content_description" comment="Content description for the one-handed close button">Sule ühe käe režiim.</string>
<string name="one_handed__move_start_btn_content_description" comment="Content description for the one-handed move to left button">Liiguta klaviatuur vasakule.</string>
<string name="one_handed__move_end_btn_content_description" comment="Content description for the one-handed move to right button">Liiguta klaviatuur paremale.</string>
<!-- Media strings -->
<string name="settings__media__title">Emojid</string>
<string name="media__tab__emojis" comment="Tab description for emojis in the media UI">Emojid</string>
<string name="media__tab__emoticons" comment="Tab description for emoticons in the media UI">Emootikonid</string>
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Kaomoji</string>
<string name="prefs__media__emoji_preferred_skin_tone">Eelistatud emoji nahavärv</string>
<string name="prefs__media__emoji_preferred_hair_style">Eelistatud emoji soeng</string>
<string name="prefs__media__emoji_history__title" comment="Preference group title">Emojide ajalugu</string>
<string name="prefs__media__emoji_history_enabled" comment="Preference title">Luba emojide ajalugu</string>
<string name="prefs__media__emoji_history_enabled__summary" comment="Preference summary">Säilita hijuti kasutatud emojid kiireks juurdepääsuks</string>
<string name="prefs__media__emoji_history_pinned_update_strategy" comment="Preference title">Uuendusmeetod (Kinnitatud)</string>
<string name="prefs__media__emoji_history_recent_update_strategy" comment="Preference title">Uuendusmeetod (Hiljutine)</string>
<string name="prefs__media__emoji_history_max_size">Maksimum säilitatud emojide arv</string>
<string name="prefs__media__emoji_history_pinned_reset">Lähtesta kinnitatud emojid</string>
<string name="prefs__media__emoji_history_reset">Lähtesta hiljutised emojid</string>
<string name="prefs__media__emoji_suggestion__title" comment="Preference group title">Emojide soovitused</string>
<string name="prefs__media__emoji_suggestion_enabled" comment="Preference title">Luba emojide soovitused</string>
<string name="prefs__media__emoji_suggestion_enabled__summary" comment="Preference summary">Kirjutamisel emojide soovitamine</string>
<string name="prefs__media__emoji_suggestion_type" comment="Preference title">Käivitusmeetod</string>
<string name="prefs__media__emoji_suggestion_update_history" comment="Preference title">UUenda emojide ajalugu</string>
<string name="prefs__media__emoji_suggestion_update_history__summary" comment="Preference summary">Soovitatud emojide kasutamine lisab need emojidde ajalukku</string>
<string name="prefs__media__emoji_suggestion_candidate_show_name" comment="Preference title">Näita emoji nime</string>
<string name="prefs__media__emoji_suggestion_candidate_show_name__summary" comment="Preference summary">Soovitatud emoji kõrval nime kuvamine</string>
<string name="prefs__media__emoji_suggestion_query_min_length" comment="Preference title">Miinimum päringu pikkus</string>
<string name="prefs__media__emoji_suggestion_candidate_max_count" comment="Preference title">Maksimum kandidaatide arv</string>
<!-- Emoji strings -->
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Naerunäod &amp; Emotsioonid</string>
<string name="emoji__category__people_body" comment="Emoji category name">Inimesed &amp; Keha</string>
<string name="emoji__category__animals_nature" comment="Emoji category name">Loomad &amp; Loodus</string>
<string name="emoji__category__food_drink" comment="Emoji category name">Toit &amp; Jook</string>
<string name="emoji__category__travel_places" comment="Emoji category name">Reisimine &amp; Kohad</string>
<string name="emoji__category__activities" comment="Emoji category name">Tegevused</string>
<string name="emoji__category__objects" comment="Emoji category name">Objektid</string>
<string name="emoji__category__symbols" comment="Emoji category name">Sümbolid</string>
<string name="emoji__category__flags" comment="Emoji category name">Lipud</string>
<string name="emoji__history__empty_message" comment="Message if the emoji history is empty">Hiljuti kasutatud emojisid ei leitud. Kui hakkad emojisid sisestama, siis need ilmuvad automaatselt siia</string>
<string name="emoji__history__phone_locked_message" comment="Message to show if phone is locked">Et emojide ajaloole ligi pääseda, palun avage oma seade lukust.</string>
<string name="emoji__history__usage_tip" comment="Feature discoverability for actions of emoji history">Hea näpunäide: Vajuta pikalt emojide ajaloos nende peale, et neid kinnitada või eemaldada!</string>
<string name="emoji__history__removal_success_message" comment="Toast message if user has used the delete action on an emoji in the emoji history">{emoji} on emojide ajaloost eemaldatud</string>
<string name="emoji__history__pinned">Kinnitatud</string>
<string name="emoji__history__recent">Hiljutine</string>
<!-- Quick action strings -->
<string name="quick_action__arrow_up" maxLength="12">Nool üles</string>
<string name="quick_action__arrow_up__tooltip">Soorita nool üles</string>
<string name="quick_action__arrow_down" maxLength="12">Nool alla</string>
<string name="quick_action__arrow_down__tooltip">Soorita nool alla</string>
<string name="quick_action__arrow_left__tooltip">Soorita nool vasakule</string>
<string name="quick_action__arrow_right__tooltip">Soorita nool paremale</string>
<string name="quick_action__clipboard_clear_primary_clip__tooltip">Soorita põhilõikelaua tühjendus</string>
<string name="quick_action__clipboard_copy" maxLength="12">Kopeeri</string>
<string name="quick_action__clipboard_copy__tooltip">Soorita lõikelaual kopeeri</string>
<string name="quick_action__clipboard_cut" maxLength="12">Lõika</string>
<string name="quick_action__clipboard_cut__tooltip">Soorita lõikelaual lõika</string>
<string name="quick_action__clipboard_paste" maxLength="12">Kleebi</string>
<string name="quick_action__clipboard_paste__tooltip">Soorita lõikelaual kleebi</string>
<string name="quick_action__clipboard_select_all" maxLength="12">Vali kõik</string>
<string name="quick_action__clipboard_select_all__tooltip">Soorita lõikelaual vali kõik</string>
<string name="quick_action__ime_ui_mode_clipboard" maxLength="12">Lõikelaud</string>
<string name="quick_action__ime_ui_mode_clipboard__tooltip">Ava lõikelaua ajalugu</string>
<string name="quick_action__ime_ui_mode_media" maxLength="12">Emoji</string>
<string name="quick_action__ime_ui_mode_media__tooltip">Ava emojide paneel</string>
<string name="quick_action__language_switch" maxLength="12">Vaheta keelt</string>
<string name="quick_action__language_switch__tooltip">Soorita keelevahetus</string>
<string name="quick_action__settings" maxLength="12">Sätted</string>
<string name="quick_action__settings__tooltip">Ava sätted</string>
<string name="quick_action__undo" maxLength="12">Võta tagasi</string>
<string name="quick_action__undo__tooltip">Võta viimane sisestus tagasi</string>
<string name="quick_action__redo" maxLength="12">Tee uuesti</string>
<string name="quick_action__redo__tooltip">Tee viimane sisestus uuesti</string>
<!-- Incognito mode strings -->
<!-- Settings UI strings -->
<!-- Smartbar strings -->
<!-- Typing strings -->
<!-- About UI strings -->
<!-- Setup UI strings -->
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<!-- Crash Dialog strings -->
<!-- Clipboard strings -->
<!-- Devtools strings -->
<!-- Extension strings -->
<!-- Action strings -->
<!-- Error strings (generic) -->
<!-- General strings -->
<!-- Screen orientation strings -->
<!-- State strings -->
<!-- Enum label and description strings -->
<!-- Unit strings (symbols) -->
<!-- Unit strings (written words) -->
</resources>

View File

@@ -244,6 +244,7 @@
<!-- Setup UI strings -->
<string name="setup__title" comment="Title of Setup">خوش آمدید!</string>
<string name="setup__footer__privacy_policy" comment="Privacy policy label for URL">سیاست حفظ حریم خصوصی</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">گزارش خطای فلوریس بورد</string>

View File

@@ -281,6 +281,7 @@
<string name="setup__enable_ime__open_settings_btn">Avaa järjestelmän asetukset</string>
<string name="setup__select_ime__title">Valitse {app_name}</string>
<string name="setup__select_ime__switch_keyboard_btn">Vaihda näppäimistöä</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<!-- Crash Dialog strings -->
<string name="crash_dialog__title" comment="Title of crash dialog">FlorisBoardin virheraportti</string>

View File

@@ -225,6 +225,10 @@
<string name="snygg__rule_element__clipboard_item">Élément du presse-papiers</string>
<string name="snygg__rule_element__clipboard_item_description">Description de lélément du presse-papiers</string>
<string name="snygg__rule_element__clipboard_item_popup">Pop-up d\'élément du presse-papiers</string>
<string name="snygg__rule_element__clipboard_item_timestamp">Horodatage des éléments du presse-papier</string>
<string name="snygg__rule_element__clipboard_history_disabled_title">Désactiver l\'historique</string>
<string name="snygg__rule_element__clipboard_history_disabled_message">L\'historique du presse-papier va être désactivé</string>
<string name="snygg__rule_element__clipboard_history_disabled_button">Confirmer</string>
<string name="snygg__rule_element__extracted_landscape_input_layout">Disposition en mode paysage</string>
<string name="snygg__rule_element__extracted_landscape_input_field">Champ d\'entrée en mode paysage</string>
<string name="snygg__rule_element__extracted_landscape_input_action">Action d\'entrée en mode paysage</string>
@@ -502,6 +506,7 @@
<string name="setup__finish_up__description_p1">{app_name} est maintenant activé dans le système et prêt à être personnalisé par vous.</string>
<string name="setup__finish_up__description_p2">Si vous rencontrez des problèmes, des bogues, des pannes ou si vous voulez simplement faire une suggestion, consultez le dépôt du projet à partir de l\'écran \" à propos \" !</string>
<string name="setup__finish_up__finish_btn">Commencer à personnaliser</string>
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<string name="backup_and_restore__title">Sauvegarder &amp; Restaurer</string>
<string name="backup_and_restore__back_up__title">Sauvegarder les données</string>
@@ -784,9 +789,9 @@
<string name="enum__display_kbd_after_dialogs__remember" comment="Enum value label">Se souvenir du dernier état</string>
<string name="enum__display_kbd_after_dialogs__remember__description" comment="Enum value description">N\'affiche le clavier qu\'après la fermeture d\'une boîte de dialogue d\'éditeur s\'il était auparavant visible</string>
<string name="enum__display_language_names_in__system_locale" comment="Enum value label">Langage du système</string>
<string name="enum__display_language_names_in__system_locale__description" comment="Enum value description">Les noms de langue dans l\'application et l\'interface utilisateur du clavier sont affichés dans les paramètres régionaux définis pour l\'ensemble de l\'appareil</string>
<string name="enum__display_language_names_in__system_locale__description" comment="Enum value description">Les noms de langues dans l\'app et l\'interface clavier sont affichés dans la langue définie pour l\'appareil</string>
<string name="enum__display_language_names_in__native_locale" comment="Enum value label">Langage natif</string>
<string name="enum__display_language_names_in__native_locale__description" comment="Enum value description">Les noms de langue dans l\'application et l\'interface utilisateur du clavier sont affichés dans les paramètres régionaux référencés par eux-mêmes</string>
<string name="enum__display_language_names_in__native_locale__description" comment="Enum value description">Les noms de langue dans l\'app et l\'interface clavier sont affichés dans chaque langue</string>
<string name="enum__emoji_history_update_strategy__auto_sort_prepend" comment="Enum value label">Tri automatique (ajout au début)</string>
<string name="enum__emoji_history_update_strategy__auto_sort_prepend__description" comment="Enum value description">Réordonner automatiquement les émojis en fonction de votre usage. Les nouveaux émojis sont ajoutés au début.</string>
<string name="enum__emoji_history_update_strategy__auto_sort_append" comment="Enum value label">Tri automatique (ajout à la fin)</string>
@@ -869,7 +874,7 @@
<string name="enum__space_bar_mode__current_language" comment="Enum value label">Langue actuelle</string>
<string name="enum__space_bar_mode__space_bar_key" comment="Enum value label"></string>
<string name="enum__spelling_language_mode__use_system_languages" comment="Enum value label">Utiliser la langue du système</string>
<string name="enum__spelling_language_mode__use_keyboard_subtypes" comment="Enum value label">Utiliser les sous-types du clavier</string>
<string name="enum__spelling_language_mode__use_keyboard_subtypes" comment="Enum value label">Utiliser la langue du clavier</string>
<string name="enum__swipe_action__no_action" comment="Enum value label">Aucune action</string>
<string name="enum__swipe_action__cycle_to_previous_keyboard_mode" comment="Enum value label">Passer au mode de clavier précédent</string>
<string name="enum__swipe_action__cycle_to_next_keyboard_mode" comment="Enum value label">Passer au mode de clavier suivant</string>

Some files were not shown because too many files have changed in this diff Show More