Compare commits

..

5 Commits

Author SHA1 Message Date
Patrick Goldinger
9c1bfb60ba Add initial flesttools impl 2025-07-18 16:40:15 +02:00
Patrick Goldinger
76702d6d68 Add initial flest draft impl 2025-07-18 16:40:15 +02:00
Patrick Goldinger
28df80a1df Rework native textutils 2025-07-18 16:40:15 +02:00
Patrick Goldinger
56a81f86a3 Add initial textutils crate 2025-07-18 16:40:14 +02:00
Patrick Goldinger
ab65aac74a Add VSCode setup script 2025-07-18 16:40:14 +02:00
98 changed files with 4397 additions and 586 deletions

View File

@@ -206,7 +206,6 @@ 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,10 +23,8 @@ 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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.clipboard.ClipboardManager
import dev.patrickgold.florisboard.ime.core.SubtypeManager
import dev.patrickgold.florisboard.ime.dictionary.DictionaryManager
@@ -42,11 +40,7 @@ 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.runtime.initAndroid
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import dev.patrickgold.jetpref.datastore.JetPref
import org.florisboard.lib.kotlin.io.deleteContentsRecursively
import org.florisboard.lib.kotlin.tryOrNull
import org.florisboard.libnative.dummyAdd
@@ -69,9 +63,8 @@ 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) }
@@ -87,6 +80,7 @@ class FlorisApplication : Application() {
super.onCreate()
FlorisApplicationReference = WeakReference(this)
try {
JetPref.configure(saveIntervalMs = 500)
Flog.install(
context = this,
isFloggingEnabled = BuildConfig.DEBUG,
@@ -114,14 +108,7 @@ class FlorisApplication : Application() {
fun init() {
cacheDir?.deleteContentsRecursively()
scope.launch {
val result = FlorisPreferenceStore.initAndroid(
context = this@FlorisApplication,
datastoreName = FlorisPreferenceModel.NAME,
)
Log.i("PREFS", result.toString())
preferenceStoreLoaded.value = true
}
prefs.initializeBlocking(this)
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,9 +119,8 @@ 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.collectIn
import org.florisboard.lib.kotlin.collectLatestIn
import org.florisboard.lib.snygg.ui.SnyggBox
import org.florisboard.lib.snygg.ui.SnyggButton
import org.florisboard.lib.snygg.ui.SnyggRow
@@ -247,12 +246,12 @@ class FlorisImeService : LifecycleInputMethodService() {
}
}
}
ims.showShortToastSync("Failed to find voice IME, do you have one installed?")
ims.showShortToast("Failed to find voice IME, do you have one installed?")
return false
}
}
private val prefs by FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val editorInstance by editorInstance()
private val keyboardManager by keyboardManager()
private val nlpManager by nlpManager()
@@ -278,21 +277,21 @@ class FlorisImeService : LifecycleInputMethodService() {
super.onCreate()
FlorisImeServiceReference = WeakReference(this)
WindowCompat.setDecorFitsSystemWindows(window.window!!, false)
subtypeManager.activeSubtypeFlow.collectIn(lifecycleScope) { subtype ->
subtypeManager.activeSubtypeFlow.collectLatestIn(lifecycleScope) { subtype ->
val config = Configuration(resources.configuration)
if (prefs.localization.displayKeyboardLabelsInSubtypeLanguage.get()) {
config.setLocale(subtype.primaryLocale.base)
}
resourcesContext = createConfigurationContext(config)
}
prefs.localization.displayKeyboardLabelsInSubtypeLanguage.asFlow().collectIn(lifecycleScope) { shouldSync ->
prefs.localization.displayKeyboardLabelsInSubtypeLanguage.observeForever { shouldSync ->
val config = Configuration(resources.configuration)
if (shouldSync) {
config.setLocale(subtypeManager.activeSubtype.primaryLocale.base)
}
resourcesContext = createConfigurationContext(config)
}
prefs.physicalKeyboard.showOnScreenKeyboard.asFlow().collectIn(lifecycleScope) {
prefs.physicalKeyboard.showOnScreenKeyboard.observeForever {
updateInputViewShown()
}
@Suppress("DEPRECATION") // We do not retrieve the wallpaper but only listen to changes
@@ -409,6 +408,7 @@ class FlorisImeService : LifecycleInputMethodService() {
flogInfo(LogTopic.IMS_EVENTS)
}
isWindowShown = true
themeManager.updateActiveTheme()
inputFeedbackController.updateSystemPrefsState()
}

View File

@@ -20,7 +20,7 @@ import android.service.textservice.SpellCheckerService
import android.view.textservice.SentenceSuggestionsInfo
import android.view.textservice.SuggestionsInfo
import android.view.textservice.TextInfo
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val dictionaryManager get() = DictionaryManager.default()
private val nlpManager by nlpManager()
private val subtypeManager by subtypeManager()

View File

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

View File

@@ -39,14 +39,12 @@ 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
@@ -61,8 +59,6 @@ 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"),
@@ -77,8 +73,7 @@ val LocalNavController = staticCompositionLocalOf<NavController> {
}
class FlorisAppActivity : ComponentActivity() {
private val prefs by FlorisPreferenceStore
private val appContext by appContext()
private val prefs by florisPreferenceModel()
private val cacheManager by cacheManager()
private var appTheme by mutableStateOf(AppTheme.AUTO)
private var showAppIcon = true
@@ -88,37 +83,37 @@ class FlorisAppActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// Splash screen should be installed before calling super.onCreate()
installSplashScreen().apply {
setKeepOnScreenCondition { !appContext.preferenceStoreLoaded.value }
setKeepOnScreenCondition { !prefs.datastoreReadyStatus.get() }
}
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
prefs.other.settingsTheme.asFlow().collectIn(lifecycleScope) {
prefs.other.settingsTheme.observe(this) {
appTheme = it
}
prefs.other.settingsLanguage.asFlow().collectIn(lifecycleScope) {
prefs.other.settingsLanguage.observe(this) {
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.asFlow().collectIn(lifecycleScope) {
prefs.other.showAppIcon.observe(this) {
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
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)
}
prefs.datastoreReadyStatus.observe(this) { isModelLoaded ->
if (!isModelLoaded) return@observe
AppVersionUtils.updateVersionOnInstallAndLastUse(this, prefs)
setContent {
ProvideLocalizedResources(resourcesContext) {

View File

@@ -29,8 +29,9 @@ 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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.color.ColorMappings
/*private val AmoledDarkColorPalette = darkColorScheme(
@@ -78,7 +79,7 @@ fun getColorScheme(
context: Context,
theme: AppTheme,
): ColorScheme {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val accentColor by prefs.other.accentColor.observeAsState()
val isDark = isSystemInDarkTheme()

View File

@@ -37,7 +37,6 @@ 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
@@ -67,9 +66,9 @@ fun AndroidLocalesScreen() = FlorisScreen {
out.appendLine()
}
}
context.showLongToastSync("Exported available system locales to \"${txtFile.path}\"")
context.showLongToast("Exported available system locales to \"${txtFile.path}\"")
} catch (e: Exception) {
context.showLongToastSync(
context.showLongToast(
R.string.error__snackbar_message_template,
"error_message" to e.message.toString(),
)

View File

@@ -29,6 +29,7 @@ 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
@@ -39,7 +40,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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.editorInstance
import dev.patrickgold.florisboard.ime.keyboard.CachedLayout
@@ -63,7 +64,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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
val keyboardManager by context.keyboardManager()
val themeManager by context.themeManager()
@@ -74,7 +75,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.collectAsState()
val themeInfo by themeManager.activeThemeInfo.observeAsState()
CompositionLocalProvider(
LocalContentColor provides Color.White,
@@ -96,7 +97,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)
}
@@ -162,13 +163,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,7 +20,6 @@ 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
@@ -39,9 +38,7 @@ 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 :)"
@@ -55,7 +52,6 @@ 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) }
@@ -114,17 +110,15 @@ 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 = {
scope.launch {
prefs.smartbar.actionArrangement.set(QuickActionArrangement.Default)
}
context.showLongToastSync(R.string.devtools__reset_quick_actions_to_default__toast_success)
prefs.smartbar.actionArrangement.set(QuickActionArrangement.Default)
context.showLongToast(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 = { scope.launch { prefs.internal.isImeSetUp.set(false) } },
onClick = { prefs.internal.isImeSetUp.set(false) },
enabledIf = { prefs.devtools.enabled isEqualTo true },
)
Preference(
@@ -206,14 +200,14 @@ fun DevtoolsScreen() = FlorisScreen {
title = "keyboardExtensions",
summary = extensionManager.keyboardExtensions.internalModuleDir.absolutePath,
onClick = {
context.showLongToastSync(extensionManager.keyboardExtensions.internalModuleDir.absolutePath)
context.showLongToast(extensionManager.keyboardExtensions.internalModuleDir.absolutePath)
},
)
Preference(
title = "themes",
summary = extensionManager.themes.internalModuleDir.absolutePath,
onClick = {
context.showLongToastSync(extensionManager.themes.internalModuleDir.absolutePath)
context.showLongToast(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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.lib.compose.FlorisButton
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
@@ -47,7 +47,6 @@ 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
@@ -55,7 +54,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
title = stringRes(R.string.devtools__debuglog__title)
scrollable = false
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val context = LocalContext.current
val clipboardManager by context.clipboardManager()
@@ -75,7 +74,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(debugLog!!.joinToString("\n"))
context.showShortToastSync(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
},
modifier = Modifier,
text = stringRes(R.string.devtools__debuglog__copy_log),
@@ -84,7 +83,7 @@ fun ExportDebugLogScreen() = FlorisScreen {
FlorisButton(
onClick = {
clipboardManager.addNewPlaintext(formattedDebugLog!!.joinToString("\n"))
context.showShortToastSync(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
context.showShortToast(context.getString(R.string.devtools__debuglog__copied_to_clipboard))
},
text = stringRes(R.string.devtools__debuglog__copy_for_github),
enabled = debugLog != null,

View File

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

View File

@@ -90,7 +90,6 @@ 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
@@ -288,7 +287,7 @@ private fun EditScreen(
stylesheetFile.writeText(stylesheet)
}.onFailure {
// TODO: better error handling
context.showLongToastSync(it.message.toString())
context.showLongToast(it.message.toString())
return
}
} else {
@@ -666,7 +665,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
when (createFrom) {
CreateFrom.EMPTY -> {
if (editor.themes.any { it.id == newId.trim() }) {
context.showLongToastSync("A theme with this ID already exists!")
context.showLongToast("A theme with this ID already exists!")
} else {
val componentEditor = ThemeExtensionComponentEditor(
id = newId.trim(),

View File

@@ -27,7 +27,6 @@ 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) {
@@ -62,9 +61,9 @@ private fun ExportScreen(ext: Extension) = FlorisScreen {
return@rememberLauncherForActivityResult
}
runCatching { extensionManager.export(ext, uri) }.onSuccess {
context.showLongToastSync(R.string.ext__export__success)
context.showLongToast(R.string.ext__export__success)
}.onFailure { error ->
context.showLongToastSync(R.string.ext__export__failure, "error_message" to error.localizedMessage)
context.showLongToast(R.string.ext__export__failure, "error_message" to error.localizedMessage)
}
navController.popBackStack()
},

View File

@@ -61,7 +61,6 @@ 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(
@@ -189,10 +188,10 @@ fun ExtensionImportScreen(type: ExtensionImportScreenType, initUuid: String?) =
}
}.onSuccess {
workspace.close()
context.showLongToastSync(R.string.ext__import__success)
context.showLongToast(R.string.ext__import__success)
navController.popBackStack()
}.onFailure { error ->
context.showLongToastSync(R.string.ext__import__failure, "error_message" to error.localizedMessage)
context.showLongToast(R.string.ext__import__failure, "error_message" to error.localizedMessage)
}
}
}

View File

@@ -62,7 +62,6 @@ 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) {
@@ -203,7 +202,7 @@ private fun ViewScreen(ext: Extension) = FlorisScreen {
}.onSuccess {
navController.popBackStack()
}.onFailure { error ->
context.showLongToastSync(
context.showLongToast(
R.string.error__snackbar_message,
"error_message" to error.localizedMessage,
)

View File

@@ -32,7 +32,6 @@ 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
@@ -42,8 +41,6 @@ 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
@@ -60,14 +57,11 @@ 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.runtime.AndroidAppDataStorage
import dev.patrickgold.jetpref.datastore.runtime.FileBasedStorage
import dev.patrickgold.jetpref.datastore.jetprefDatastoreDir
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
@@ -142,7 +136,6 @@ 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() }
@@ -162,24 +155,22 @@ fun BackupScreen() = FlorisScreen {
context.contentResolver.writeFromFile(uri, backupWorkspace!!.zipFile)
backupWorkspace!!.close()
}.onSuccess {
context.showLongToastSync(R.string.backup_and_restore__back_up__success)
context.showLongToast(R.string.backup_and_restore__back_up__success)
navController.popBackStack()
}.onFailure { error ->
flogError { error.stackTraceToString() }
context.showLongToastSync(R.string.backup_and_restore__back_up__failure, "error_message" to error.message)
context.showLongToast(R.string.backup_and_restore__back_up__failure, "error_message" to error.message)
backupWorkspace = null
}
},
)
suspend fun prepareBackupWorkspace() {
fun prepareBackupWorkspace() {
val workspace = cacheManager.backupAndRestore.new()
if (backupFilesSelector.jetprefDatastore) {
val fileBasedStorage = workspace.inputDir
.subDir(AndroidAppDataStorage.JETPREF_DIR_NAME)
.subFile("${FlorisPreferenceModel.NAME}.${AndroidAppDataStorage.JETPREF_FILE_EXT}")
.let { FileBasedStorage(it.path) }
FlorisPreferenceStore.export(fileBasedStorage).getOrThrow()
context.jetprefDatastoreDir.let { dir ->
dir.copyRecursively(workspace.inputDir.subDir(dir.name))
}
}
val workspaceFilesDir = workspace.inputDir.subDir("files")
if (backupFilesSelector.imeKeyboard) {
@@ -234,7 +225,7 @@ fun BackupScreen() = FlorisScreen {
backupWorkspace = workspace
}
suspend fun prepareAndPerformBackup() {
fun prepareAndPerformBackup() {
runCatching {
if (backupWorkspace == null || backupWorkspace!!.isClosed()) {
prepareBackupWorkspace()
@@ -274,7 +265,7 @@ fun BackupScreen() = FlorisScreen {
)
ButtonBarButton(
onClick = {
scope.launch { prepareAndPerformBackup() }
prepareAndPerformBackup()
},
text = stringRes(R.string.action__back_up),
enabled = backupFilesSelector.atLeastOneSelected(),

View File

@@ -43,9 +43,8 @@ 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
@@ -61,16 +60,13 @@ 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.runtime.AndroidAppDataStorage
import dev.patrickgold.jetpref.datastore.runtime.FileBasedStorage
import dev.patrickgold.jetpref.datastore.runtime.ImportStrategy
import dev.patrickgold.jetpref.datastore.JetPref
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
@@ -83,6 +79,11 @@ 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
@@ -90,12 +91,13 @@ 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 importStrategy by remember { mutableStateOf(ImportStrategy.Merge) }
var restoreMode by remember { mutableStateOf(Restore.Mode.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) }
@@ -136,7 +138,7 @@ fun RestoreScreen() = FlorisScreen {
}
restoreWorkspace = workspace
}.onFailure { error ->
context.showLongToastSync(
context.showLongToast(
R.string.backup_and_restore__restore__failure,
"error_message" to error.localizedMessage,
)
@@ -146,13 +148,15 @@ fun RestoreScreen() = FlorisScreen {
suspend fun performRestore() {
val workspace = restoreWorkspace!!
val shouldReset = importStrategy == ImportStrategy.Erase
val shouldReset = restoreMode == Restore.Mode.ERASE_AND_OVERWRITE
if (restoreFilesSelector.jetprefDatastore) {
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 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 workspaceFilesDir = workspace.outputDir.subDir("files")
if (restoreFilesSelector.imeKeyboard) {
@@ -271,16 +275,16 @@ fun RestoreScreen() = FlorisScreen {
) {
RadioListItem(
onClick = {
importStrategy = ImportStrategy.Merge
restoreMode = Restore.Mode.MERGE
},
selected = importStrategy == ImportStrategy.Merge,
selected = restoreMode == Restore.Mode.MERGE,
text = stringRes(R.string.backup_and_restore__restore__mode_merge),
)
RadioListItem(
onClick = {
importStrategy = ImportStrategy.Erase
restoreMode = Restore.Mode.ERASE_AND_OVERWRITE
},
selected = importStrategy == ImportStrategy.Erase,
selected = restoreMode == Restore.Mode.ERASE_AND_OVERWRITE,
text = stringRes(R.string.backup_and_restore__restore__mode_erase_and_overwrite),
)
}
@@ -289,7 +293,7 @@ fun RestoreScreen() = FlorisScreen {
runCatching {
restoreDataFromFileSystemLauncher.launch("*/*")
}.onFailure { error ->
context.showLongToastSync(
context.showLongToast(
R.string.backup_and_restore__restore__failure,
"error_message" to error.localizedMessage,
)

View File

@@ -68,7 +68,6 @@ 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")
@@ -144,16 +143,16 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDatabase()
}
if (db == null) {
context.showLongToastSync("Database handle is null, failed to import")
context.showLongToast("Database handle is null, failed to import")
return@rememberLauncherForActivityResult
}
runCatching {
db.importCombinedList(context, uri)
}.onSuccess {
buildUi()
context.showLongToastSync(R.string.settings__udm__dictionary_import_success)
context.showLongToast(R.string.settings__udm__dictionary_import_success)
}.onFailure { error ->
context.showLongToastSync("Error: ${error.localizedMessage}")
context.showLongToast("Error: ${error.localizedMessage}")
}
},
)
@@ -169,15 +168,15 @@ fun UserDictionaryScreen(type: UserDictionaryType) = FlorisScreen {
UserDictionaryType.SYSTEM -> dictionaryManager.systemUserDictionaryDatabase()
}
if (db == null) {
context.showLongToastSync("Database handle is null, failed to export")
context.showLongToast("Database handle is null, failed to export")
return@rememberLauncherForActivityResult
}
runCatching {
db.exportCombinedList(context, uri)
}.onSuccess {
context.showLongToastSync(R.string.settings__udm__dictionary_export_success)
context.showLongToast(R.string.settings__udm__dictionary_export_success)
}.onFailure { error ->
context.showLongToastSync("Error: ${error.localizedMessage}")
context.showLongToast("Error: ${error.localizedMessage}")
}
},
)

View File

@@ -41,10 +41,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.LocalNavController
import dev.patrickgold.florisboard.app.Routes
import dev.patrickgold.florisboard.app.ext.ExtensionImportScreenType
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.nlp.LanguagePackComponent
import org.florisboard.lib.android.showLongToast
@@ -61,7 +61,6 @@ 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");
@@ -76,7 +75,7 @@ fun LanguagePackManagerScreen(action: LanguagePackManagerScreenAction?) = Floris
else -> error("LanguagePack manager screen action must not be null")
})
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val navController = LocalNavController.current
val context = LocalContext.current
val extensionManager by context.extensionManager()
@@ -200,7 +199,7 @@ fun LanguagePackManagerScreen(action: LanguagePackManagerScreenAction?) = Floris
runCatching {
extensionManager.delete(languagePackExtToDelete!!)
}.onFailure { error ->
context.showLongToastSync(
context.showLongToast(
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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
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,6 +86,7 @@ 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()
@@ -185,7 +186,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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
var shouldDelete by remember { mutableStateOf<ShouldDelete?>(null) }
val scope = rememberCoroutineScope()

View File

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

View File

@@ -96,7 +96,6 @@ 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
@@ -407,7 +406,7 @@ private fun EditCodeValueDialog(
}
if (!isFlorisBoardEnabled || !isFlorisBoardSelected) {
lastRecordingToast?.cancel()
lastRecordingToast = context.showShortToastSync(
lastRecordingToast = context.showShortToast(
R.string.settings__theme_editor__code_recording_requires_default_ime_floris,
"app_name" to context.stringRes(R.string.floris_app_name),
)
@@ -433,12 +432,12 @@ private fun EditCodeValueDialog(
val defaultReceiver = keyboardManager.inputEventDispatcher.keyEventReceiver
keyboardManager.inputEventDispatcher.keyEventReceiver = receiver
lastRecordingToast?.cancel()
lastRecordingToast = context.showShortToastSync(R.string.settings__theme_editor__code_recording_started)
lastRecordingToast = context.showShortToast(R.string.settings__theme_editor__code_recording_started)
focusRequester.requestFocus()
onDispose {
keyboardManager.inputEventDispatcher.keyEventReceiver = defaultReceiver
lastRecordingToast?.cancel()
lastRecordingToast = context.showShortToastSync(R.string.settings__theme_editor__code_recording_stopped)
lastRecordingToast = context.showShortToast(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(FlorisPreferenceStore, iconSpaceReserved = false) {
PreferenceLayout(florisPreferenceModel(), iconSpaceReserved = false) {
ListPreference(
listPref = prefs.theme.editorLevel,
title = stringRes(R.string.settings__theme_editor__fine_tune__level),

View File

@@ -30,6 +30,7 @@ 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
@@ -56,7 +57,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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.jetpref.datastore.model.observeAsState
import org.florisboard.lib.snygg.value.SnyggCutCornerDpShapeValue
import org.florisboard.lib.snygg.value.SnyggDefinedVarValue
@@ -120,7 +121,7 @@ internal fun SnyggValueIcon(
modifier: Modifier = Modifier,
spec: SnyggValueIcon.Spec = SnyggValueIcon.Normal,
) {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
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,7 +100,6 @@ 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
@@ -146,7 +145,7 @@ fun ThemeEditorScreen(
title = stringRes(R.string.ext__editor__edit_component__title_theme)
scrollable = false
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val context = LocalContext.current
val focusManager = LocalFocusManager.current
val themeManager by context.themeManager()
@@ -310,14 +309,14 @@ fun ThemeEditorScreen(
}
DisposableEffect(workspace.version) {
themeManager.previewThemeInfo.value = ThemeManager.ThemeInfo.DEFAULT.copy(
themeManager.previewThemeInfo = ThemeManager.ThemeInfo.DEFAULT.copy(
name = extPreviewTheme(System.currentTimeMillis().toString()),
config = editor.build(),
stylesheet = stylesheetEditor.build(),
loadedDir = workspace.extDir,
)
onDispose {
themeManager.previewThemeInfo.value = null
themeManager.previewThemeInfo = null
}
}
@@ -634,7 +633,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.showLongToastSync("A theme with this ID already exists!")
context.showLongToast("A theme with this ID already exists!")
} else {
workspace.update {
editor.id = id.trim()

View File

@@ -29,11 +29,10 @@ 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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.theme.ThemeExtensionComponent
import dev.patrickgold.florisboard.lib.compose.FlorisOutlinedBox
@@ -46,7 +45,6 @@ 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"),
@@ -62,11 +60,10 @@ fun ThemeManagerScreen(action: ThemeManagerScreenAction?) = FlorisScreen {
})
previewFieldVisible = true
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
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) {
@@ -86,7 +83,7 @@ fun ThemeManagerScreen(action: ThemeManagerScreenAction?) = FlorisScreen {
val extComponentName = ExtensionComponentName(extId, componentId)
when (action) {
ThemeManagerScreenAction.SELECT_DAY,
ThemeManagerScreenAction.SELECT_NIGHT -> scope.launch {
ThemeManagerScreenAction.SELECT_NIGHT -> {
getThemeIdPref().set(extComponentName)
}
}
@@ -99,9 +96,9 @@ fun ThemeManagerScreen(action: ThemeManagerScreenAction?) = FlorisScreen {
content {
DisposableEffect(activeThemeId) {
themeManager.previewThemeId.value = activeThemeId
themeManager.previewThemeId = activeThemeId
onDispose {
themeManager.previewThemeId.value = null
themeManager.previewThemeId = null
}
}
val grayColor = LocalContentColor.current.copy(alpha = 0.56f)

View File

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

View File

@@ -33,7 +33,6 @@ 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
@@ -41,11 +40,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
@@ -58,11 +57,10 @@ 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)
@@ -72,8 +70,7 @@ fun SetupScreen() = FlorisScreen {
val navController = LocalNavController.current
val context = LocalContext.current
val prefs by FlorisPreferenceStore
val scope = rememberCoroutineScope()
val prefs by florisPreferenceModel()
val isFlorisBoardEnabled by InputMethodUtils.observeIsFlorisboardEnabled(foregroundOnly = true)
val isFlorisBoardSelected by InputMethodUtils.observeIsFlorisboardSelected(foregroundOnly = true)
@@ -81,12 +78,10 @@ fun SetupScreen() = FlorisScreen {
val requestNotification =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
scope.launch {
if (isGranted) {
prefs.internal.notificationPermissionState.set(NotificationPermissionState.GRANTED)
} else {
prefs.internal.notificationPermissionState.set(NotificationPermissionState.DENIED)
}
if (isGranted) {
prefs.internal.notificationPermissionState.set(NotificationPermissionState.GRANTED)
} else {
prefs.internal.notificationPermissionState.set(NotificationPermissionState.DENIED)
}
}
@@ -96,8 +91,7 @@ fun SetupScreen() = FlorisScreen {
context,
navController,
requestNotification,
hasNotificationPermission,
scope,
hasNotificationPermission
)
}
@@ -109,7 +103,6 @@ private fun FlorisScreenScope.content(
navController: NavController,
requestNotification: ManagedActivityResultLauncher<String, Boolean>,
hasNotificationPermission: NotificationPermissionState,
scope: CoroutineScope,
) {
val stepState = rememberSaveable(saver = FlorisStepState.Saver) {
@@ -165,7 +158,7 @@ private fun FlorisScreenScope.content(
Spacer(modifier = Modifier.height(16.dp))
},
steps = steps(
context, navController, requestNotification, scope
context, navController, requestNotification
),
footer = {
footer(context)
@@ -196,11 +189,10 @@ private fun footer(context: Context) {
}
@Composable
private fun PreferenceUiScope<FlorisPreferenceModel>.steps(
private fun PreferenceUiScope<AppPrefs>.steps(
context: Context,
navController: NavController,
requestNotification: ManagedActivityResultLauncher<String, Boolean>,
scope: CoroutineScope,
): List<FlorisStep> {
return listOfNotNull(
@@ -240,7 +232,7 @@ private fun PreferenceUiScope<FlorisPreferenceModel>.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)) {
scope.launch { this@steps.prefs.internal.isImeSetUp.set(true) }
this@steps.prefs.internal.isImeSetUp.set(true)
navController.navigate(Routes.Settings.Home) {
popUpTo(Routes.Setup.Screen) {
inclusive = true

View File

@@ -74,7 +74,6 @@ 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
@@ -88,7 +87,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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.ime.ImeUiMode
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
@@ -113,11 +112,9 @@ 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
@@ -139,8 +136,7 @@ const val CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO: Int = 0
fun ClipboardInputLayout(
modifier: Modifier = Modifier,
) {
val prefs by FlorisPreferenceStore
val scope = rememberCoroutineScope()
val prefs by florisPreferenceModel()
val context = LocalContext.current
val clipboardManager by context.clipboardManager()
val keyboardManager by context.keyboardManager()
@@ -207,7 +203,7 @@ fun ClipboardInputLayout(
)
SnyggIconButton(
elementName = FlorisImeUi.ClipboardHeaderButton.elementName,
onClick = { scope.launch { prefs.clipboard.historyEnabled.set(!historyEnabled) } },
onClick = { prefs.clipboard.historyEnabled.set(!historyEnabled) },
modifier = sizeModifier.autoMirrorForRtl(),
enabled = !deviceLocked && !isPopupSurfaceActive(),
) {
@@ -587,7 +583,7 @@ fun ClipboardInputLayout(
attributes = mapOf("action" to "yes"),
onClick = {
clipboardManager.clearHistory()
context.showShortToastSync(R.string.clipboard__cleared_history)
context.showShortToast(R.string.clipboard__cleared_history)
showClearAllHistory = false
isFilterRowShown = false
},
@@ -633,7 +629,7 @@ fun ClipboardInputLayout(
text = stringRes(R.string.clipboard__disabled__message),
)
SnyggButton(FlorisImeUi.ClipboardHistoryDisabledButton.elementName,
onClick = { scope.launch { prefs.clipboard.historyEnabled.set(true) } },
onClick = { prefs.clipboard.historyEnabled.set(true) },
modifier = Modifier.align(Alignment.End),
) {
SnyggText(

View File

@@ -17,10 +17,11 @@
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.editorInstance
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardHistoryDao
@@ -43,7 +44,6 @@ 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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
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.showShortToastSync("Failed to paste item.")
appContext.showShortToast("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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
ProvideLocalizedResources(this, forceLayoutDirection = LayoutDirection.Ltr) {
val theme by prefs.other.settingsTheme.observeAsState()
FlorisAppTheme(theme) {

View File

@@ -17,18 +17,15 @@
package dev.patrickgold.florisboard.ime.core
import android.content.Context
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.florisboard.lib.kotlin.collectLatestIn
val SubtypeJsonConfig = Json {
encodeDefaults = true
@@ -41,9 +38,8 @@ val SubtypeJsonConfig = Json {
* helper methods for the in-keyboard language switch process.
*/
class SubtypeManager(context: Context) {
private val prefs by FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val keyboardManager by context.keyboardManager()
private val scope = CoroutineScope(Dispatchers.Default)
private val _subtypesFlow = MutableStateFlow(listOf<Subtype>())
val subtypesFlow = _subtypesFlow.asStateFlow()
@@ -58,7 +54,7 @@ class SubtypeManager(context: Context) {
private set(v) { _activeSubtypeFlow.value = v }
init {
prefs.localization.subtypes.asFlow().collectLatestIn(scope) { listRaw ->
prefs.localization.subtypes.observeForever { listRaw ->
flogDebug { listRaw }
val list = if (listRaw.isNotBlank()) {
SubtypeJsonConfig.decodeFromString<List<Subtype>>(listRaw)
@@ -70,7 +66,7 @@ class SubtypeManager(context: Context) {
}
}
private fun persistNewSubtypeList(list: List<Subtype>) = scope.launch {
private fun persistNewSubtypeList(list: List<Subtype>) {
val listRaw = SubtypeJsonConfig.encodeToString(list)
prefs.localization.subtypes.set(listRaw)
}
@@ -82,7 +78,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>) = scope.launch {
private fun evaluateActiveSubtype(list: List<Subtype>) {
val activeSubtypeId = prefs.localization.activeSubtypeId.get()
val subtype = list.find { it.id == activeSubtypeId } ?: list.firstOrNull() ?: Subtype.DEFAULT
if (subtype.id != activeSubtypeId) {
@@ -188,7 +184,7 @@ class SubtypeManager(context: Context) {
/**
* Switch to the previous subtype in the subtype list if possible.
*/
fun switchToPrevSubtype() = scope.launch {
fun switchToPrevSubtype() {
val subtypeList = subtypes
val cachedActiveSubtype = activeSubtype
var triggerNextSubtype = false
@@ -211,7 +207,7 @@ class SubtypeManager(context: Context) {
/**
* Switch to the next subtype in the subtype list if possible.
*/
fun switchToNextSubtype() = scope.launch {
fun switchToNextSubtype() {
val subtypeList = subtypes
val cachedActiveSubtype = activeSubtype
var triggerNextSubtype = false
@@ -231,7 +227,7 @@ class SubtypeManager(context: Context) {
activeSubtype = newActiveSubtype
}
fun switchToSubtypeById(id: Long) = scope.launch {
fun switchToSubtypeById(id: Long) {
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
@@ -43,14 +43,13 @@ 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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val appContext by context.appContext()
private val clipboardManager by context.clipboardManager()
private val keyboardManager by context.keyboardManager()
@@ -394,7 +393,7 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
if (text != null) {
clipboardManager.addNewPlaintext(text.toString())
} else {
appContext.showShortToastSync("Failed to retrieve selected text requested to cut: Eiter selection state is invalid or an error occurred within the input connection.")
appContext.showShortToast("Failed to retrieve selected text requested to cut: Eiter selection state is invalid or an error occurred within the input connection.")
}
return deleteBackwards()
}
@@ -412,7 +411,7 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
if (text != null) {
clipboardManager.addNewPlaintext(text.toString())
} else {
appContext.showShortToastSync("Failed to retrieve selected text requested to copy: Eiter selection state is invalid or an error occurred within the input connection.")
appContext.showShortToast("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)
@@ -429,7 +428,7 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
phantomSpace.setInactive()
return commitClipboardItem(clipboardManager.primaryClip).also { result ->
if (!result) {
appContext.showShortToastSync("Failed to paste item.")
appContext.showShortToast("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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val audioManager = ims.systemServiceOrNull(AudioManager::class)
private val vibrator = ims.systemVibratorOrNull()

View File

@@ -36,7 +36,8 @@ 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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.onehanded.OneHandedMode
import dev.patrickgold.florisboard.ime.smartbar.ExtendedActionsPlacement
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyboard
@@ -79,7 +80,7 @@ object FlorisImeSizing {
@Composable
fun smartbarUiHeight(): Dp {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val smartbarEnabled by prefs.smartbar.enabled.observeAsState()
val smartbarLayout by prefs.smartbar.layout.observeAsState()
val extendedActionsExpanded by prefs.smartbar.extendedActionsExpanded.observeAsState()
@@ -112,7 +113,7 @@ object FlorisImeSizing {
@Composable
fun ProvideKeyboardRowBaseHeight(content: @Composable () -> Unit) {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.editorInstance
@@ -74,9 +74,7 @@ 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
@@ -85,7 +83,7 @@ import java.util.concurrent.atomic.AtomicInteger
private val DoubleSpacePeriodMatcher = """([^.!?‽\s]\s)""".toRegex()
class KeyboardManager(context: Context) : InputKeyEventReceiver {
private val prefs by FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val appContext by context.appContext()
private val clipboardManager by context.clipboardManager()
private val editorInstance by context.editorInstance()
@@ -131,21 +129,21 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
keyboardCache.clear()
}
}
prefs.keyboard.numberRow.asFlow().collectLatestIn(scope) {
prefs.keyboard.numberRow.observeForever {
updateActiveEvaluators {
keyboardCache.clear(KeyboardMode.CHARACTERS)
}
}
prefs.keyboard.hintedNumberRowEnabled.asFlow().collectLatestIn(scope) {
prefs.keyboard.hintedNumberRowEnabled.observeForever {
updateActiveEvaluators()
}
prefs.keyboard.hintedSymbolsEnabled.asFlow().collectLatestIn(scope) {
prefs.keyboard.hintedSymbolsEnabled.observeForever {
updateActiveEvaluators()
}
prefs.keyboard.utilityKeyEnabled.asFlow().collectLatestIn(scope) {
prefs.keyboard.utilityKeyEnabled.observeForever {
updateActiveEvaluators()
}
prefs.keyboard.utilityKeyAction.asFlow().collectLatestIn(scope) {
prefs.keyboard.utilityKeyAction.observeForever {
updateActiveEvaluators()
}
activeState.collectLatestIn(scope) {
@@ -166,10 +164,10 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
editorInstance.activeContentFlow.collectIn(scope) { content ->
resetSuggestions(content)
}
prefs.devtools.enabled.asFlow().collectLatestIn(scope) {
prefs.devtools.enabled.observeForever {
reevaluateDebugFlags()
}
prefs.devtools.showDragAndDropHelpers.asFlow().collectLatestIn(scope) {
prefs.devtools.showDragAndDropHelpers.observeForever {
reevaluateDebugFlags()
}
}
@@ -239,7 +237,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
return subtypeManager.subtypes.size > 1
}
suspend fun toggleOneHandedMode() {
fun toggleOneHandedMode() {
prefs.keyboard.oneHandedModeEnabled.set(!prefs.keyboard.oneHandedModeEnabled.get())
}
@@ -582,7 +580,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
/**
* Handles a [KeyCode.TOGGLE_INCOGNITO_MODE] event.
*/
private suspend fun handleToggleIncognitoMode() {
private fun handleToggleIncognitoMode() {
prefs.suggestion.forceIncognitoModeFromDynamic.set(!prefs.suggestion.forceIncognitoModeFromDynamic.get())
val newState = !activeState.isIncognitoMode
activeState.isIncognitoMode = newState
@@ -608,7 +606,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
private fun handleToggleAutocorrect() {
lastToastReference.get()?.cancel()
lastToastReference = WeakReference(
appContext.showLongToastSync("Autocorrect toggle is a placeholder and not yet implemented")
appContext.showLongToast("Autocorrect toggle is a placeholder and not yet implemented")
)
}
@@ -718,14 +716,14 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
clipboardManager.primaryClip?.let { clipboardManager.deleteClip(it) }
}
clipboardManager.updatePrimaryClip(null)
appContext.showShortToastSync(R.string.clipboard__cleared_primary_clip)
appContext.showShortToast(R.string.clipboard__cleared_primary_clip)
}
KeyCode.TOGGLE_COMPACT_LAYOUT -> scope.launch { toggleOneHandedMode() }
KeyCode.COMPACT_LAYOUT_TO_LEFT -> scope.launch {
KeyCode.TOGGLE_COMPACT_LAYOUT -> toggleOneHandedMode()
KeyCode.COMPACT_LAYOUT_TO_LEFT -> {
prefs.keyboard.oneHandedMode.set(OneHandedMode.START)
toggleOneHandedMode()
}
KeyCode.COMPACT_LAYOUT_TO_RIGHT -> scope.launch {
KeyCode.COMPACT_LAYOUT_TO_RIGHT -> {
prefs.keyboard.oneHandedMode.set(OneHandedMode.END)
toggleOneHandedMode()
}
@@ -755,7 +753,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
}
KeyCode.SYSTEM_PREV_INPUT_METHOD -> FlorisImeService.switchToPrevInputMethod()
KeyCode.SYSTEM_NEXT_INPUT_METHOD -> FlorisImeService.switchToNextInputMethod()
KeyCode.TOGGLE_SMARTBAR_VISIBILITY -> scope.launch {
KeyCode.TOGGLE_SMARTBAR_VISIBILITY -> {
prefs.smartbar.enabled.let { it.set(!it.get()) }
}
KeyCode.TOGGLE_ACTIONS_OVERFLOW -> {
@@ -764,7 +762,7 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
KeyCode.TOGGLE_ACTIONS_EDITOR -> {
activeState.isActionsEditorVisible = !activeState.isActionsEditorVisible
}
KeyCode.TOGGLE_INCOGNITO_MODE -> scope.launch { handleToggleIncognitoMode() }
KeyCode.TOGGLE_INCOGNITO_MODE -> 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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val appContext by context.appContext()
private val extensionManager by context.extensionManager()
private val keyboardManager by context.keyboardManager()

View File

@@ -16,12 +16,13 @@
package dev.patrickgold.florisboard.ime.media.emoji
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.AppPrefs
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
@@ -75,7 +76,7 @@ data class EmojiHistory(
object EmojiHistoryHelper {
private var emojiGuard = Mutex(locked = false)
suspend fun markEmojiUsed(prefs: FlorisPreferenceModel, emoji: Emoji): Unit = emojiGuard.withLock {
suspend fun markEmojiUsed(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}
@@ -120,7 +121,7 @@ object EmojiHistoryHelper {
)
}
suspend fun pinEmoji(prefs: FlorisPreferenceModel, emoji: Emoji): Unit = emojiGuard.withLock {
suspend fun pinEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}
@@ -137,7 +138,7 @@ object EmojiHistoryHelper {
prefs.emoji.historyData.set(dataMut.build())
}
suspend fun unpinEmoji(prefs: FlorisPreferenceModel, emoji: Emoji): Unit = emojiGuard.withLock {
suspend fun unpinEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}
@@ -154,7 +155,7 @@ object EmojiHistoryHelper {
prefs.emoji.historyData.set(dataMut.build())
}
suspend fun moveEmoji(prefs: FlorisPreferenceModel, emoji: Emoji, offset: Int): Unit = emojiGuard.withLock {
suspend fun moveEmoji(prefs: AppPrefs, emoji: Emoji, offset: Int) = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get() || offset == 0) {
return
}
@@ -174,7 +175,7 @@ object EmojiHistoryHelper {
prefs.emoji.historyData.set(dataMut.build())
}
suspend fun removeEmoji(prefs: FlorisPreferenceModel, emoji: Emoji): Unit = emojiGuard.withLock {
suspend fun removeEmoji(prefs: AppPrefs, emoji: Emoji) = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}
@@ -194,7 +195,7 @@ object EmojiHistoryHelper {
prefs.emoji.historyData.set(dataMut.build())
}
suspend fun deleteHistory(prefs: FlorisPreferenceModel): Unit = emojiGuard.withLock {
suspend fun deleteHistory(prefs: AppPrefs) = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}
@@ -202,7 +203,7 @@ object EmojiHistoryHelper {
prefs.emoji.historyData.set(EmojiHistory(pinned = dataMut.pinned, listOf()))
}
suspend fun deletePinned(prefs: FlorisPreferenceModel): Unit = emojiGuard.withLock {
suspend fun deletePinned(prefs: AppPrefs) = emojiGuard.withLock {
if (!prefs.emoji.historyEnabled.get()) {
return
}

View File

@@ -25,6 +25,7 @@ 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
@@ -45,6 +46,8 @@ 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
@@ -80,7 +83,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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.editorInstance
import dev.patrickgold.florisboard.ime.input.LocalInputFeedbackController
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
@@ -130,7 +133,7 @@ fun EmojiPaletteView(
fullEmojiMappings: EmojiData,
modifier: Modifier = Modifier,
) {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val context = LocalContext.current
val editorInstance by context.editorInstance()
val keyboardManager by context.keyboardManager()
@@ -494,7 +497,7 @@ private fun EmojiHistoryPopup(
onHistoryAction: () -> Unit,
onDismiss: () -> Unit,
) {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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.guardedByLock
import org.florisboard.lib.kotlin.collectLatestIn
import org.florisboard.lib.kotlin.guardedByLock
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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
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.asFlow().collectLatestIn(scope) {
prefs.suggestion.enabled.observeForever {
assembleCandidates()
}
prefs.clipboard.suggestionEnabled.asFlow().collectLatestIn(scope) {
prefs.clipboard.suggestionEnabled.observeForever {
assembleCandidates()
}
prefs.emoji.suggestionEnabled.asFlow().collectLatestIn(scope) {
prefs.emoji.suggestionEnabled.observeForever {
assembleCandidates()
}
subtypeManager.activeSubtypeFlow.collectLatestIn(scope) { subtype ->
@@ -317,10 +317,8 @@ class NlpManager(context: Context) {
}*/
val isSelection = editorInstance.activeContent.selection.isSelectionMode
val isExpanded = list1.isNullOrEmpty() && list2.isNullOrEmpty() || isSelection
scope.launch {
prefs.smartbar.sharedActionsExpandWithAnimation.set(false)
prefs.smartbar.sharedActionsExpanded.set(isExpanded)
}
prefs.smartbar.sharedActionsExpandWithAnimation.set(false)
prefs.smartbar.sharedActionsExpanded.set(isExpanded)
}
fun addToDebugOverlay(word: String, info: SpellingResult) {

View File

@@ -26,16 +26,14 @@ 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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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
@@ -46,8 +44,7 @@ fun RowScope.OneHandedPanel(
panelSide: OneHandedMode,
weight: Float,
) {
val prefs by FlorisPreferenceStore
val scope = rememberCoroutineScope()
val prefs by florisPreferenceModel()
val inputFeedbackController = LocalInputFeedbackController.current
SnyggColumn(
@@ -61,10 +58,8 @@ fun RowScope.OneHandedPanel(
SnyggIconButton(
FlorisImeUi.OneHandedPanelButton.elementName,
onClick = {
scope.launch {
inputFeedbackController.keyPress()
prefs.keyboard.oneHandedModeEnabled.set(false)
}
inputFeedbackController.keyPress()
prefs.keyboard.oneHandedModeEnabled.set(false)
},
modifier = Modifier
.fillMaxWidth()
@@ -79,10 +74,8 @@ fun RowScope.OneHandedPanel(
SnyggIconButton(
FlorisImeUi.OneHandedPanelButton.elementName,
onClick = {
scope.launch {
inputFeedbackController.keyPress()
prefs.keyboard.oneHandedMode.set(panelSide)
}
inputFeedbackController.keyPress()
prefs.keyboard.oneHandedMode.set(panelSide)
},
modifier = Modifier
.weight(1f)

View File

@@ -28,7 +28,6 @@ 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
@@ -59,7 +58,7 @@ fun PopupBaseBox(
.align(Alignment.TopCenter),
) {
SnyggText(
modifier = Modifier.align(Alignment.Center).zIndex(100f),
modifier = Modifier.align(Alignment.Center),
text = label,
)
}

View File

@@ -31,7 +31,6 @@ 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
@@ -39,7 +38,6 @@ 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
@@ -454,12 +452,11 @@ 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() }.zIndex(100f),
.absoluteOffset { renderInfo.bounds.topLeft.toIntOffset() },
attributes = attributes,
key = renderInfo.key,
shouldIndicateExtendedPopups = renderInfo.shouldIndicateExtendedPopups && extRenderInfo == null,

View File

@@ -20,6 +20,7 @@ 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
@@ -39,7 +40,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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.nlp.ClipboardSuggestionCandidate
import dev.patrickgold.florisboard.ime.nlp.SuggestionCandidate
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
@@ -61,7 +62,7 @@ val CandidatesRowScrollbarHeight = 2.dp
@Composable
fun CandidatesRow(modifier: Modifier = Modifier) {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val context = LocalContext.current
val keyboardManager by context.keyboardManager()
val nlpManager by context.nlpManager()

View File

@@ -16,11 +16,8 @@
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
@@ -35,18 +32,16 @@ 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)
@@ -64,16 +59,13 @@ 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
@@ -81,16 +73,6 @@ 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,6 +24,7 @@ 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
@@ -42,7 +43,6 @@ 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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
import dev.patrickgold.florisboard.ime.nlp.NlpInlineAutofill
import dev.patrickgold.florisboard.ime.smartbar.quickaction.QuickActionButton
@@ -65,7 +65,6 @@ 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
@@ -90,7 +89,7 @@ private val NoAnimationTween = tween<Float>(0)
@Composable
fun Smartbar() {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val smartbarEnabled by prefs.smartbar.enabled.observeAsState()
val extendedActionsPlacement by prefs.smartbar.extendedActionsPlacement.observeAsState()
@@ -139,12 +138,10 @@ fun Smartbar() {
@Composable
private fun SmartbarMainRow(modifier: Modifier = Modifier) {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
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) {
@@ -167,9 +164,7 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
if (/* was */ sharedActionsExpanded) {
keyboardManager.activeState.isActionsOverflowVisible = false
}
scope.launch {
prefs.smartbar.sharedActionsExpanded.set(!sharedActionsExpanded)
}
prefs.smartbar.sharedActionsExpanded.set(!sharedActionsExpanded)
},
modifier = Modifier.sizeIn(maxHeight = FlorisImeSizing.smartbarHeight).aspectRatio(1f)
) {
@@ -249,9 +244,7 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
if (/* was */ extendedActionsExpanded) {
keyboardManager.activeState.isActionsOverflowVisible = false
}
scope.launch {
prefs.smartbar.extendedActionsExpanded.set(!extendedActionsExpanded)
}
prefs.smartbar.extendedActionsExpanded.set(!extendedActionsExpanded)
},
modifier = Modifier.sizeIn(maxHeight = FlorisImeSizing.smartbarHeight).aspectRatio(1f)
) {
@@ -311,9 +304,7 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
SideEffect {
if (!shouldAnimate) {
scope.launch {
prefs.smartbar.sharedActionsExpandWithAnimation.set(true)
}
prefs.smartbar.sharedActionsExpandWithAnimation.set(true)
}
}
@@ -368,7 +359,7 @@ private fun SmartbarMainRow(modifier: Modifier = Modifier) {
@Composable
private fun SmartbarSecondaryRow(modifier: Modifier = Modifier) {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val smartbarLayout by prefs.smartbar.layout.observeAsState()
val secondaryRowStyle = rememberSnyggThemeQuery(FlorisImeUi.SmartbarExtendedActionsRow.elementName)
val windowStyle = rememberSnyggThemeQuery(FlorisImeUi.Window.elementName)

View File

@@ -25,6 +25,7 @@ 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
@@ -39,7 +40,6 @@ 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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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,9 +74,8 @@ private val DragMarkerAction = QuickAction.InsertKey(TextKeyData(code = KeyCode.
@Composable
fun QuickActionsEditorPanel() {
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val context = LocalContext.current
val scope = rememberCoroutineScope()
val keyboardManager by context.keyboardManager()
// We get the current arrangement once and do not observe on purpose
@@ -236,9 +235,7 @@ fun QuickActionsEditorPanel() {
dynamicActions.filter { it != NoopAction && it != DragMarkerAction },
hiddenActions.filter { it != NoopAction && it != DragMarkerAction },
)
scope.launch {
prefs.smartbar.actionArrangement.set(newActionArrangement)
}
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
val context = LocalContext.current
val keyboardManager by context.keyboardManager()

View File

@@ -16,7 +16,6 @@
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
@@ -29,7 +28,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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.ime.smartbar.SmartbarLayout
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
import dev.patrickgold.florisboard.keyboardManager
@@ -38,13 +37,12 @@ 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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
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 (_: Throwable) {
} catch (e: 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 FlorisPreferenceStore
val prefs by florisPreferenceModel()
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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val editorInstance by context.editorInstance()
private val keyboardManager by context.keyboardManager()

View File

@@ -27,10 +27,13 @@ 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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
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
@@ -49,10 +52,10 @@ fun FlorisImeTheme(content: @Composable () -> Unit) {
val keyboardManager by context.keyboardManager()
val themeManager by context.themeManager()
val prefs by FlorisPreferenceStore
val prefs by florisPreferenceModel()
val accentColor by prefs.theme.accentColor.observeAsState()
val activeThemeInfo by themeManager.activeThemeInfo.collectAsState()
val activeThemeInfo by themeManager.activeThemeInfo.observeAsNonNullState()
val activeConfig = remember(activeThemeInfo) { activeThemeInfo.config }
val activeStyle = remember(activeThemeInfo) { activeThemeInfo.stylesheet }

View File

@@ -39,8 +39,10 @@ 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.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.florisPreferenceModel
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.extensionManager
import dev.patrickgold.florisboard.ime.smartbar.CachedInlineSuggestionsChipStyleSet
@@ -50,67 +52,70 @@ import dev.patrickgold.florisboard.lib.ext.ExtensionMeta
import dev.patrickgold.florisboard.lib.io.ZipUtils
import dev.patrickgold.florisboard.lib.util.TimeUtils.javaLocalTime
import dev.patrickgold.florisboard.lib.util.ViewUtils
import java.time.LocalTime
import java.util.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
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.time.LocalTime
import java.util.*
import kotlin.properties.Delegates
/**
* 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 FlorisPreferenceStore
private val prefs by florisPreferenceModel()
private val appContext by context.appContext()
private val extensionManager by context.extensionManager()
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
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 _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 cachedThemeInfos = mutableListOf<ThemeInfo>()
private val activeThemeGuard = Mutex(locked = false)
private val _activeThemeInfo = MutableStateFlow(ThemeInfo.DEFAULT)
val activeThemeInfo get() = _activeThemeInfo.asStateFlow()
private val _activeThemeInfo = MutableLiveData(ThemeInfo.DEFAULT)
val activeThemeInfo: LiveData<ThemeInfo> get() = _activeThemeInfo
init {
extensionManager.themes.observeForever { themeExtensions ->
_indexedThemeConfigs.value = buildMap {
val map = buildMap {
for (themeExtension in themeExtensions) {
for (themeComponent in themeExtension.themes) {
put(ExtensionComponentName(themeExtension.meta.id, themeComponent.id), themeComponent)
}
}
}
_indexedThemeConfigs.postValue(map)
}
indexedThemeConfigs.collectIn(scope) {
updateActiveTheme { cachedThemeInfos.clear() }
indexedThemeConfigs.observeForever {
updateActiveTheme {
cachedThemeInfos.clear()
}
}
combine(
prefs.theme.mode.asFlow(),
prefs.theme.dayThemeId.asFlow(),
prefs.theme.nightThemeId.asFlow(),
previewThemeId,
previewThemeInfo,
wallpaperChangedCounter,
) {}.collectIn(scope) {
prefs.theme.mode.observeForever {
updateActiveTheme()
}
prefs.theme.dayThemeId.observeForever {
updateActiveTheme()
}
prefs.theme.nightThemeId.observeForever {
updateActiveTheme()
}
}
@@ -119,54 +124,56 @@ 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.
*/
suspend fun updateActiveTheme(action: () -> Unit = { }) = activeThemeGuard.withLock {
action()
previewThemeInfo.value?.let { previewThemeInfo ->
_activeThemeInfo.value = previewThemeInfo
return@withLock
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)
))
},
)
}
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.value?.let { return it }
previewThemeId?.let { return it }
return when (prefs.theme.mode.get()) {
ThemeMode.ALWAYS_DAY -> {
prefs.theme.dayThemeId.get()
@@ -195,9 +202,10 @@ class ThemeManager(context: Context) {
}
/**
* Creates a new inline suggestion UI bundle.
* Creates a new inline suggestion UI bundle based on the attributes of the given [style].
*
* @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,7 +21,6 @@ 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?) {
@@ -30,8 +29,7 @@ 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" }
val themeManager by context.themeManager()
themeManager.wallpaperChangedCounter.update { it + 1 }
context.themeManager().value.updateActiveTheme()
}
}
}

View File

@@ -16,13 +16,11 @@
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
@@ -30,16 +28,21 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.compose.LocalLifecycleOwner
import dev.patrickgold.jetpref.datastore.model.PreferenceData
import kotlinx.coroutines.flow.map
import dev.patrickgold.jetpref.datastore.model.PreferenceObserver
@SuppressLint("StateFlowValueCalledInComposition")
@Composable
inline fun <V : Any, R : Any> PreferenceData<V>.observeAsTransformingState(
policy: SnapshotMutationPolicy<R> = structuralEqualityPolicy(),
crossinline transform: @DisallowComposableCalls (V) -> R,
): State<R> {
return asFlow().let { flow ->
flow.map { transform(it) }.collectAsState(transform(flow.value))
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 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.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.AppPrefs
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<FlorisPreferenceModel>
typealias FlorisScreenContent = PreferenceUiContent<AppPrefs>
typealias FlorisScreenFab = @Composable () -> Unit
typealias FlorisScreenNavigationIcon = @Composable () -> Unit
@@ -152,7 +152,7 @@ private class FlorisScreenScopeImpl : FlorisScreenScope {
Modifier
}
PreferenceLayout(
FlorisPreferenceStore,
florisPreferenceModel(),
modifier = Modifier
.padding(innerPadding)
.fillMaxWidth()

View File

@@ -56,7 +56,6 @@ 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
@@ -116,7 +115,7 @@ fun PreviewKeyboardField(
Row {
IconButton(onClick = {
if (!InputMethodUtils.showImePicker(context)) {
context.showShortToastSync("Error: InputMethodManager service not available!")
context.showShortToast("Error: InputMethodManager service not available!")
}
}) {
Icon(

View File

@@ -23,35 +23,22 @@ 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.FlorisImeUi
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.snygg.ui.rememberSnyggThemeQuery
import org.florisboard.lib.snygg.ui.uriOrNull
@Composable
fun SystemUiIme() {
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 useDarkIcons = !FlorisImeTheme.config.isNightTheme
val view = LocalView.current
val window = view.context.findWindow()!!
val windowInsetsController = WindowInsetsControllerCompat(window, view)
LaunchedEffect(useDarkIcons, hasBackgroundImage) {
LaunchedEffect(useDarkIcons) {
windowInsetsController.isAppearanceLightNavigationBars = useDarkIcons
if (AndroidVersion.ATLEAST_API29_Q) {
window.isNavigationBarContrastEnforced = hasBackgroundImage
window.isNavigationBarContrastEnforced = true
}
}
}

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.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.app.AppPrefs
import dev.patrickgold.florisboard.app.florisPreferenceModel
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?, FlorisPreferenceModel?> {
private class SafePreferenceInstanceWrapper : ReadOnlyProperty<Any?, AppPrefs?> {
val cachedPreferenceModel = try {
FlorisPreferenceStore
florisPreferenceModel()
} catch (_: Throwable) {
null
}
override fun getValue(thisRef: Any?, property: KProperty<*>): FlorisPreferenceModel? {
override fun getValue(thisRef: Any?, property: KProperty<*>): AppPrefs? {
return cachedPreferenceModel?.getValue(thisRef, property)
}
}

View File

@@ -22,7 +22,7 @@ import android.os.Build
import android.os.Debug
import dev.patrickgold.florisboard.BuildConfig
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.AppPrefs
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: FlorisPreferenceModel? = null, includeLogcat: Boolean = false): String {
fun generateDebugLog(context: Context, prefs: AppPrefs? = null, includeLogcat: Boolean = false): String {
return buildString {
append(generateDebugLogHeader(context, prefs))
if (includeLogcat) {
@@ -45,7 +45,7 @@ object Devtools {
}
}
fun generateDebugLogHeader(context: Context, prefs: FlorisPreferenceModel? = null): String {
fun generateDebugLogHeader(context: Context, prefs: AppPrefs? = null): String {
return buildString {
append(generateSystemInfoLog(context))
appendLine()
@@ -61,7 +61,7 @@ object Devtools {
}
}
fun generateDebugLogForGithub(context: Context, prefs: FlorisPreferenceModel? = null, includeLogcat: Boolean = false): String {
fun generateDebugLogForGithub(context: Context, prefs: AppPrefs? = 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: FlorisPreferenceModel, withTitle: Boolean = true): String {
fun generateFeatureConfigLog(prefs: AppPrefs, 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.FlorisPreferenceModel
import dev.patrickgold.florisboard.app.AppPrefs
object AppVersionUtils {
private fun getRawVersionName(context: Context): String {
@@ -28,7 +28,7 @@ object AppVersionUtils {
}
}
fun shouldShowChangelog(context: Context, prefs: FlorisPreferenceModel): Boolean {
fun shouldShowChangelog(context: Context, prefs: AppPrefs): Boolean {
val installVersion =
VersionName.fromString(prefs.internal.versionOnInstall.get()) ?: VersionName.DEFAULT
val lastChangelogVersion =
@@ -39,14 +39,14 @@ object AppVersionUtils {
return lastChangelogVersion < currentVersion && installVersion != currentVersion
}
suspend fun updateVersionOnInstallAndLastUse(context: Context, prefs: FlorisPreferenceModel) {
fun updateVersionOnInstallAndLastUse(context: Context, prefs: AppPrefs) {
if (prefs.internal.versionOnInstall.get() == VersionName.DEFAULT_RAW) {
prefs.internal.versionOnInstall.set(getRawVersionName(context))
}
prefs.internal.versionLastUse.set(getRawVersionName(context))
}
suspend fun updateVersionLastChangelog(context: Context, prefs: FlorisPreferenceModel) {
fun updateVersionLastChangelog(context: Context, prefs: AppPrefs) {
prefs.internal.versionLastChangelog.set(getRawVersionName(context))
}
}

View File

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

View File

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

View File

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

View File

@@ -48,6 +48,7 @@ import androidx.compose.ui.unit.takeOrElse
import kotlinx.coroutines.runBlocking
import org.florisboard.lib.color.ColorMappings
import org.florisboard.lib.snygg.CompiledFontFamilyData
import org.florisboard.lib.snygg.SnyggAttributes
import org.florisboard.lib.snygg.SnyggQueryAttributes
import org.florisboard.lib.snygg.SnyggRule
import org.florisboard.lib.snygg.SnyggSelector
@@ -363,7 +364,7 @@ internal fun SnyggValue.dpSize(default: Dp = Dp.Unspecified): Dp {
}
}
fun SnyggValue.uriOrNull(): String? {
internal fun SnyggValue.uriOrNull(): String? {
return when (this) {
is SnyggUriValue -> this.uri
else -> null

828
libnative/flest/Cargo.lock generated Normal file
View File

@@ -0,0 +1,828 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "calendrical_calculations"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cec493b209a1b81fa32312d7ceca1b547d341c7b5f16a3edbf32b1d8b455bbdf"
dependencies = [
"core_maths",
"displaydoc",
]
[[package]]
name = "core_maths"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3"
dependencies = [
"libm",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "fixed_decimal"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0febbeb1118a9ecdee6e4520ead6b54882e843dd0592ad233247dbee84c53db8"
dependencies = [
"displaydoc",
"smallvec",
"writeable",
]
[[package]]
name = "flest"
version = "0.1.0"
dependencies = [
"textutils",
]
[[package]]
name = "icu"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff5e3018d703f168b00dcefa540a65f1bbc50754ae32f3f5f0e43fe5ee51502"
dependencies = [
"icu_calendar",
"icu_casemap",
"icu_collator",
"icu_collections",
"icu_datetime",
"icu_decimal",
"icu_experimental",
"icu_list",
"icu_locid",
"icu_locid_transform",
"icu_normalizer",
"icu_plurals",
"icu_properties",
"icu_provider",
"icu_segmenter",
"icu_timezone",
]
[[package]]
name = "icu_calendar"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7265b2137f9a36f7634a308d91f984574bbdba8cfd95ceffe1c345552275a8ff"
dependencies = [
"calendrical_calculations",
"displaydoc",
"icu_calendar_data",
"icu_locid",
"icu_locid_transform",
"icu_provider",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_calendar_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0"
[[package]]
name = "icu_casemap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff0c8ae9f8d31b12e27fc385ff9ab1f3cd9b17417c665c49e4ec958c37da75f"
dependencies = [
"displaydoc",
"icu_casemap_data",
"icu_collections",
"icu_locid",
"icu_properties",
"icu_provider",
"writeable",
"zerovec",
]
[[package]]
name = "icu_casemap_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d57966d5ab748f74513be4046867f9a20e801e2775d41f91d04a0f560b61f08"
[[package]]
name = "icu_collator"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d370371887d31d56f361c3eaa15743e54f13bc677059c9191c77e099ed6966b2"
dependencies = [
"displaydoc",
"icu_collator_data",
"icu_collections",
"icu_locid_transform",
"icu_normalizer",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_collator_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee3f88741364b7d6269cce6827a3e6a8a2cf408a78f766c9224ab479d5e4ae5"
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_datetime"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d115efb85e08df3fd77e77f52e7e087545a783fffba8be80bfa2102f306b1780"
dependencies = [
"displaydoc",
"either",
"fixed_decimal",
"icu_calendar",
"icu_datetime_data",
"icu_decimal",
"icu_locid",
"icu_locid_transform",
"icu_plurals",
"icu_provider",
"icu_timezone",
"smallvec",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_datetime_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ba7e7f7a01269b9afb0a39eff4f8676f693b55f509b3120e43a0350a9f88bea"
[[package]]
name = "icu_decimal"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb8fd98f86ec0448d85e1edf8884e4e318bb2e121bd733ec929a05c0a5e8b0eb"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_decimal_data",
"icu_locid_transform",
"icu_provider",
"writeable",
]
[[package]]
name = "icu_decimal_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d424c994071c6f5644f999925fc868c85fec82295326e75ad5017bc94b41523"
[[package]]
name = "icu_experimental"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "844ad7b682a165c758065d694bc4d74ac67f176da1c499a04d85d492c0f193b7"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_collections",
"icu_decimal",
"icu_experimental_data",
"icu_locid",
"icu_locid_transform",
"icu_normalizer",
"icu_pattern",
"icu_plurals",
"icu_properties",
"icu_provider",
"litemap",
"num-bigint",
"num-rational",
"num-traits",
"smallvec",
"tinystr",
"writeable",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_experimental_data"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c178b9a34083fca5bd70d61f647575335e9c197d0f30c38e8ccd187babc69d0"
[[package]]
name = "icu_list"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfeda1d7775b6548edd4e8b7562304a559a91ed56ab56e18961a053f367c365"
dependencies = [
"displaydoc",
"icu_list_data",
"icu_locid_transform",
"icu_provider",
"regex-automata 0.2.0",
"writeable",
]
[[package]]
name = "icu_list_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1825170d2c6679cb20dbd96a589d034e49f698aed9a2ef4fafc9a0101ed298f"
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_pattern"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f36aafd098d6717de34e668a8120822275c1fba22b936e757b7de8a2fd7e4"
dependencies = [
"displaydoc",
"either",
"writeable",
"yoke",
"zerofrom",
]
[[package]]
name = "icu_plurals"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a70e7c025dbd5c501b0a5c188cd11666a424f0dadcd4f0a95b7dafde3b114"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_locid_transform",
"icu_plurals_data",
"icu_provider",
"zerovec",
]
[[package]]
name = "icu_plurals_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3e8f775b215d45838814a090a2227247a7431d74e9156407d9c37f6ef0f208"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "icu_segmenter"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de"
dependencies = [
"core_maths",
"displaydoc",
"icu_collections",
"icu_locid",
"icu_provider",
"icu_segmenter_data",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_segmenter_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df"
[[package]]
name = "icu_timezone"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa91ba6a585939a020c787235daa8aee856d9bceebd6355e283c0c310bc6de96"
dependencies = [
"displaydoc",
"icu_calendar",
"icu_provider",
"icu_timezone_data",
"tinystr",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_timezone_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c588878c508a3e2ace333b3c50296053e6483c6a7541251b546cc59dcd6ced8e"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "linkify"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780"
dependencies = [
"memchr",
]
[[package]]
name = "litemap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "proc-macro2"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.8",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782"
dependencies = [
"memchr",
]
[[package]]
name = "regex-automata"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "serde"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "2.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "textutils"
version = "0.1.0"
dependencies = [
"icu",
"icu_segmenter",
"itertools",
"lazy_static",
"linkify",
"once_cell",
"regex",
"unicase",
"unicode-normalization",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "unicase"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
dependencies = [
"either",
]
[[package]]
name = "yoke"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@@ -0,0 +1,7 @@
[package]
name = "flest"
version = "0.1.0"
edition = "2021"
[dependencies]
textutils = { path = "../textutils" }

View File

@@ -0,0 +1,125 @@
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Candidate {
pub text: String,
pub secondary_text: Option<String>,
pub confidence: u8,
}
impl PartialOrd for Candidate {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
// Reverse ordering
other.confidence.partial_cmp(&self.confidence)
}
}
impl Ord for Candidate {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// Reverse ordering
other.confidence.cmp(&self.confidence)
}
}
pub struct CandidateQueue {
entries: Vec<Candidate>,
capacity: usize,
}
impl CandidateQueue {
pub fn with_capacity(capacity: usize) -> Self {
let capacity = capacity.max(1);
CandidateQueue {
entries: Vec::with_capacity(capacity),
capacity,
}
}
pub fn push(&mut self, text: String, confidence: f64) {
if confidence.is_nan() {
return;
}
let confidence = confidence.clamp(0.0, 1.0);
let confidence = ((u8::MAX as f64) * confidence) as u8;
let entry = Candidate { text, secondary_text: None, confidence };
if self.entries.is_empty() {
self.entries.push(entry);
return;
}
let existing_entry = self.entries.iter_mut().find(|it| it.text == entry.text);
if let Some(entry) = existing_entry {
entry.confidence = entry.confidence.max(confidence);
} else {
if self.entries.len() < self.capacity {
self.entries.push(entry);
} else {
let last = &mut self.entries[self.capacity - 1];
if last.confidence < entry.confidence {
*last = entry;
}
}
}
self.entries.sort();
}
pub fn into_sorted_vec(self) -> Vec<Candidate> {
self.entries
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic_insertions() {
let mut queue = CandidateQueue::with_capacity(3);
queue.push("foo".to_string(), 0.5);
queue.push("bar".to_string(), 0.7);
queue.push("baz".to_string(), 0.6);
queue.push("qux".to_string(), 0.8);
queue.push("quux".to_string(), 0.9);
let vec = queue.into_sorted_vec();
assert_eq!(vec.len(), 3);
assert_eq!(vec[0].text, "quux");
assert_eq!(vec[1].text, "qux");
assert_eq!(vec[2].text, "bar");
}
#[test]
fn basic_insertions_with_duplicates() {
let mut queue = CandidateQueue::with_capacity(3);
queue.push("foo".to_string(), 0.5);
queue.push("bar".to_string(), 0.7);
queue.push("baz".to_string(), 0.6);
queue.push("qux".to_string(), 0.8);
queue.push("quux".to_string(), 0.9);
queue.push("quux".to_string(), 0.9);
let vec = queue.into_sorted_vec();
assert_eq!(vec.len(), 3);
assert_eq!(vec[0].text, "quux");
assert_eq!(vec[1].text, "qux");
assert_eq!(vec[2].text, "bar");
}
#[test]
fn empty_candidate_set() {
let queue = CandidateQueue::with_capacity(3);
let vec = queue.into_sorted_vec();
assert_eq!(vec.len(), 0);
}
#[test]
fn nan_confidence_insertions() {
let mut queue = CandidateQueue::with_capacity(3);
queue.push("foo".to_string(), 0.5);
queue.push("bar".to_string(), f64::NAN);
queue.push("baz".to_string(), 0.6);
let vec = queue.into_sorted_vec();
assert_eq!(vec.len(), 2);
assert_eq!(vec[0].text, "baz");
assert_eq!(vec[1].text, "foo");
}
}

View File

@@ -0,0 +1,148 @@
use core::fmt;
use std::collections::HashMap;
pub const TOKEN_SEPARATOR: char = '\u{00}';
#[derive(Default)]
pub struct DynTrieNode<T> {
children: HashMap<char, DynTrieNode<T>>,
pub value: Option<T>,
}
impl<T: Default> DynTrieNode<T> {
pub fn new() -> Self {
DynTrieNode {
children: HashMap::new(),
value: None,
}
}
#[inline]
pub fn traverse(&self, c: char) -> Option<&DynTrieNode<T>> {
return self.children.get(&c);
}
#[inline]
pub fn traverse_mut(&mut self, c: char) -> Option<&mut DynTrieNode<T>> {
return self.children.get_mut(&c);
}
#[inline]
pub fn traverse_or_insert(&mut self, c: char) -> &mut DynTrieNode<T> {
return self.children.entry(c).or_insert_with(|| DynTrieNode::default());
}
pub fn get(&self, token: &Vec<char>) -> Option<&DynTrieNode<T>> {
let mut node = self;
for c in token {
node = node.traverse(*c)?;
}
return Some(node);
}
pub fn get_mut(&mut self, token: &Vec<char>) -> Option<&mut DynTrieNode<T>> {
let mut node = self;
for c in token {
node = node.traverse_mut(*c)?;
}
return Some(node);
}
pub fn get_or_insert(&mut self, token: &Vec<char>) -> &mut DynTrieNode<T> {
let mut node = self;
for c in token {
node = node.traverse_or_insert(*c);
}
if node.value.is_none() {
node.value = Some(T::default());
}
return node;
}
pub fn get_ngram(&self, ngram: &[Vec<char>]) -> Option<&DynTrieNode<T>> {
let mut node = self;
for (i, token) in ngram.iter().enumerate() {
if i > 0 {
node = node.traverse(TOKEN_SEPARATOR)?;
}
node = node.get(token)?;
}
return Some(node);
}
pub fn get_ngram_mut(&mut self, ngram: &[Vec<char>]) -> Option<&mut DynTrieNode<T>> {
let mut node = self;
for (i, token) in ngram.iter().enumerate() {
if i > 0 {
node = node.traverse_mut(TOKEN_SEPARATOR)?;
}
node = node.get_mut(token)?;
}
return Some(node);
}
pub fn get_ngram_or_insert(&mut self, ngram: &[Vec<char>]) -> &mut DynTrieNode<T> {
let mut node = self;
for (i, token) in ngram.iter().enumerate() {
if i > 0 {
node = node.traverse_or_insert(TOKEN_SEPARATOR);
}
node = node.get_or_insert(token);
}
return node;
}
pub fn for_each<F>(&self, f: &F) where F: Fn(&Vec<char>, &DynTrieNode<T>) {
let mut token = Vec::with_capacity(32);
self.for_each_recursive(&mut token, f);
}
fn for_each_recursive<F>(&self, token: &mut Vec<char>, f: &F) where F: Fn(&Vec<char>, &DynTrieNode<T>) {
if self.value.is_some() {
f(token, self);
}
for (c, child) in &self.children {
if *c == TOKEN_SEPARATOR {
continue;
}
token.push(*c);
child.for_each_recursive(token, f);
token.pop();
}
}
pub fn for_each_fnmut<F>(&self, f: &mut F) where F: FnMut(&Vec<char>, &DynTrieNode<T>) {
let mut token = Vec::with_capacity(32);
self.for_each_recursive_fnmut(&mut token, f);
}
fn for_each_recursive_fnmut<F>(&self, token: &mut Vec<char>, f: &mut F) where F: FnMut(&Vec<char>, &DynTrieNode<T>) {
if self.value.is_some() {
f(token, self);
}
for (c, child) in &self.children {
if *c == TOKEN_SEPARATOR {
continue;
}
token.push(*c);
child.for_each_recursive_fnmut(token, f);
token.pop();
}
}
}
impl<T: Default + fmt::Debug> DynTrieNode<T> {
pub fn debug_pretty_print(&self) {
self.debug_pretty_print_recursive(0);
}
fn debug_pretty_print_recursive(&self, depth: usize) {
for (c, child) in &self.children {
for _ in 0..depth {
print!(" ");
}
println!("{:?}: {:?}", c, child.value);
child.debug_pretty_print_recursive(depth + 1);
}
}
}

View File

@@ -0,0 +1,4 @@
mod candidates;
mod dyntrie;
pub mod model;
mod types;

View File

@@ -0,0 +1,68 @@
mod prediction;
mod serialization;
mod spellcheck;
mod training;
mod version;
pub use prediction::*;
pub use serialization::*;
pub use spellcheck::*;
pub use training::*;
pub use version::*;
use crate::dyntrie::DynTrieNode;
#[derive(Default, Debug)]
pub struct NgramData {
time: u64,
count: u64,
is_offensive: bool,
// flag should only be set for 1st level words!!
is_dictionary_word: bool,
}
pub struct NgramModelMeta {
version: NgramModelVersion,
global_time: u64,
global_count: u64,
pub sentence_token: String,
}
pub struct NgramModelOptions {
pub max_candidates: usize,
pub max_ngram_size: usize,
pub allow_offensive: bool,
}
pub struct NgramModel {
pub trie_root: DynTrieNode<NgramData>,
pub meta: NgramModelMeta,
pub options: NgramModelOptions,
}
impl NgramModel {
pub fn new() -> Self {
NgramModel {
trie_root: DynTrieNode::new(),
meta: NgramModelMeta {
version: NgramModelVersion::latest(),
global_time: 0,
global_count: 0,
sentence_token: "\u{1e}".to_owned(),
},
options: NgramModelOptions {
max_candidates: 5,
max_ngram_size: 3,
allow_offensive: false,
},
}
}
}
impl NgramModelMeta {
fn update_and_get_time(&mut self) -> u64 {
self.global_time += 1;
self.global_count += 1;
return self.global_time;
}
}

View File

@@ -0,0 +1,149 @@
use textutils::{fuzzy, normalization::StringNormalizationHelpers};
use crate::{candidates::{Candidate, CandidateQueue}, dyntrie::TOKEN_SEPARATOR};
use super::NgramModel;
impl NgramModel {
pub fn predict(&self, partial_sentence: &Vec<&str>) -> Vec<Candidate> {
if partial_sentence.is_empty() {
return vec![];
}
let mut partial_sentence_nfd = Vec::with_capacity(partial_sentence.len() + 1);
partial_sentence_nfd.push(self.meta.sentence_token.to_nfd_chars());
for word in partial_sentence {
partial_sentence_nfd.push(word.to_nfd_chars());
}
let curr_word_nfd = &partial_sentence_nfd.last().unwrap();
let history_nfd = &partial_sentence_nfd[..partial_sentence_nfd.len() - 1];
if curr_word_nfd.is_empty() {
return self.predict_next_word(history_nfd);
}
return self.predict_curr_word(curr_word_nfd, history_nfd);
}
fn predict_next_word(&self, history_nfd: &[Vec<char>]) -> Vec<Candidate> {
let mut candidate_queue = CandidateQueue::with_capacity(self.options.max_candidates);
let max_history_depth = (self.options.max_ngram_size - 1).min(history_nfd.len());
let tmax = self.meta.global_time;
let tmin = if tmax >= 300 { tmax - 300 } else { 0 };
let cmax = self.meta.global_count;
let cmin = 0;
self.trie_root.for_each_fnmut(&mut |word, word_node| {
let node = word_node.traverse(TOKEN_SEPARATOR);
if node.is_none() {
return;
}
let value = word_node.value.as_ref().unwrap();
let time_conf = norm_weight(value.time, tmin, tmax);
let count_conf = norm_weight(value.count, cmin, cmax);
let mut hist_node = node.unwrap();
for hist_index in 0..max_history_depth {
let hist_word = &history_nfd[history_nfd.len() - hist_index - 1];
// TODO: instead of get use fuzzy get with:
// case-insentive match and accent-insensitive match
let hist_node_opt = hist_node.get(hist_word);
if hist_node_opt.is_none() {
return;
}
hist_node = hist_node_opt.unwrap();
let hist_value = hist_node.value.as_ref();
if hist_value.is_none() {
return;
}
let hist_value = hist_value.unwrap();
let hist_time_conf = norm_weight(hist_value.time, tmin, tmax);
let hist_count_conf = norm_weight(hist_value.count, cmin, cmax);
let hist_conf = calc_confidence(hist_time_conf, hist_count_conf, 1.0);
let conf = calc_confidence(time_conf, count_conf, hist_conf);
candidate_queue.push(word.iter().collect(), conf);
let hist_node_opt = hist_node.traverse(TOKEN_SEPARATOR);
if hist_node_opt.is_none() {
return;
}
hist_node = hist_node_opt.unwrap();
}
});
return candidate_queue.into_sorted_vec();
}
fn predict_curr_word(&self, curr_word_nfd: &Vec<char>, history_nfd: &[Vec<char>]) -> Vec<Candidate> {
let mut candidate_queue = CandidateQueue::with_capacity(self.options.max_candidates);
let max_history_depth = (self.options.max_ngram_size - 1).min(history_nfd.len());
let tmax = self.meta.global_time;
let tmin = if tmax >= 300 { tmax - 300 } else { 0 };
let cmax = self.meta.global_count;
let cmin = 0;
// TODO: implement fuzzy_for_each_fnmut
self.trie_root.for_each_fnmut(&mut |word, word_node| {
// TODO: the fuzzy matcher needs to be written completely froms cratch, return a FuzzyResult instead of f64
if fuzzy::str_match_live(word, curr_word_nfd) < 0.5 {
return;
}
let node = word_node.traverse(TOKEN_SEPARATOR);
if node.is_none() {
return;
}
let value = word_node.value.as_ref().unwrap();
let time_conf = norm_weight(value.time, tmin, tmax);
let count_conf = norm_weight(value.count, cmin, cmax);
let mut hist_node = node.unwrap();
for hist_index in 0..max_history_depth {
let hist_word = &history_nfd[history_nfd.len() - hist_index - 1];
// TODO: instead of get use fuzzy get with:
// case-insentive match and accent-insensitive match
let hist_node_opt = hist_node.get(hist_word);
if hist_node_opt.is_none() {
return;
}
hist_node = hist_node_opt.unwrap();
let hist_value = hist_node.value.as_ref();
if hist_value.is_none() {
return;
}
let hist_value = hist_value.unwrap();
let hist_time_conf = norm_weight(hist_value.time, tmin, tmax);
let hist_count_conf = norm_weight(hist_value.count, cmin, cmax);
let hist_conf = calc_confidence(hist_time_conf, hist_count_conf, 1.0);
let conf = calc_confidence(time_conf, count_conf, hist_conf);
candidate_queue.push(word.iter().collect(), conf);
let hist_node_opt = hist_node.traverse(TOKEN_SEPARATOR);
if hist_node_opt.is_none() {
return;
}
hist_node = hist_node_opt.unwrap();
}
});
return candidate_queue.into_sorted_vec();
}
}
fn calc_confidence(time_conf: f64, count_conf: f64, hist_conf: f64) -> f64 {
println!("time_conf: {}, count_conf: {}, hist_conf: {}", time_conf, count_conf, hist_conf);
// TODO: count_conf is messed up
return 0.45 * time_conf + 0.10 * count_conf + 0.45 * hist_conf;
}
fn norm_weight(x: u64, xmin: u64, xmax: u64) -> f64 {
if x <= xmin {
return 0.0;
}
if x >= xmax {
return 1.0;
}
let xnorm = (x - xmin) as f64 / (xmax - xmin) as f64;
return 2.0 * xnorm - xnorm.powi(2);
}

View File

@@ -0,0 +1,19 @@
use crate::types::FlestResult;
use super::NgramModel;
impl NgramModel {
pub fn from_file(path: &str) -> FlestResult<Self> {
let mut model = NgramModel::new();
model.load_from_file(path)?;
return Ok(model);
}
pub fn load_from_file(&mut self, path: &str) -> FlestResult<()> {
todo!()
}
pub fn persist_to_file(&self, path: &str) -> FlestResult<()> {
todo!()
}
}

View File

@@ -0,0 +1,13 @@
use crate::candidates::Candidate;
use super::NgramModel;
impl NgramModel {
fn spell(&self, curr_word: &str, history: &Vec<&str>) -> Vec<Candidate> {
todo!()
}
fn spell_sentence(&self, sentence: &Vec<&str>) -> Vec<Option<Vec<Candidate>>> {
todo!()
}
}

View File

@@ -0,0 +1,41 @@
use textutils::normalization::StringNormalizationHelpers;
use super::NgramModel;
impl NgramModel {
pub fn train_from_sentence(&mut self, sentence: &Vec<&str>) {
let mut sentence_nfd = Vec::with_capacity(sentence.len() + 1);
sentence_nfd.push(self.meta.sentence_token.to_nfd_chars());
for word in sentence {
sentence_nfd.push(word.to_nfd_chars());
}
for sent_i in 0..sentence_nfd.len() {
for ngram_i in 0..=(self.options.max_ngram_size - 1).clamp(0, sent_i) {
let i = sent_i - ngram_i;
assert!(i < sentence_nfd.len()); // catch overflow issues
let ngram = &sentence_nfd[i..=sent_i].iter().rev().cloned().collect::<Vec<_>>();
let node = self.trie_root.get_ngram_or_insert(ngram);
let data = node.value.as_mut().unwrap();
data.time = self.meta.update_and_get_time();
data.count += 1;
}
}
}
pub fn train_from_tokens(&mut self, tokens: &Vec<&str>) {
for n in 1..=self.options.max_ngram_size {
if n > tokens.len() {
continue;
}
for i in 0..tokens.len() - n + 1 {
let ngram = &tokens[i..(i + n)];
let ngram = ngram.iter().rev().map(|&x| x.to_nfd_chars()).collect::<Vec<_>>();
let node = self.trie_root.get_ngram_or_insert(ngram.as_slice());
let data = node.value.as_mut().unwrap();
data.time = self.meta.update_and_get_time();
data.count += 1;
}
}
}
}

View File

@@ -0,0 +1,70 @@
use core::fmt;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct NgramModelVersion {
major: u8,
minor: u8,
}
#[macro_export]
macro_rules! ngram_model_version {
($major:expr, $minor:expr) => {
NgramModelVersion { major: $major, minor: $minor }
};
($major:expr) => {
NgramModelVersion { major: $major, minor: 0 }
};
}
#[allow(non_upper_case_globals)]
impl NgramModelVersion {
pub const vDEV: NgramModelVersion = ngram_model_version!(0, 0);
pub const v0_1: NgramModelVersion = ngram_model_version!(0, 1);
pub fn latest() -> NgramModelVersion {
NgramModelVersion::v0_1
}
}
impl fmt::Display for NgramModelVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.major == 0 && self.minor == 0 {
write!(f, "vDEV")
} else {
write!(f, "v{}.{}", self.major, self.minor)
}
}
}
impl fmt::Debug for NgramModelVersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (0x{:02x}{:02x})", self, self.major, self.minor)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn display() {
assert_eq!(format!("{}", ngram_model_version!(0, 1)), "v0.1");
assert_eq!(format!("{:?}", ngram_model_version!(0, 1)), "v0.1 (0x0001)");
assert_eq!(format!("{}", ngram_model_version!(1, 0)), "v1.0");
assert_eq!(format!("{:?}", ngram_model_version!(1, 0)), "v1.0 (0x0100)");
}
#[test]
fn equality() {
assert_eq!(ngram_model_version!(0, 1), ngram_model_version!(0, 1));
assert_eq!(ngram_model_version!(1, 0), ngram_model_version!(1, 0));
assert_ne!(ngram_model_version!(0, 1), ngram_model_version!(1, 0));
}
#[test]
fn comparison() {
assert!(ngram_model_version!(0, 1) > ngram_model_version!(0, 0));
assert!(ngram_model_version!(1, 0) > ngram_model_version!(0, 1));
assert!(ngram_model_version!(1, 0) > ngram_model_version!(0, 42));
}
}

View File

@@ -0,0 +1,3 @@
use std::error::Error;
pub type FlestResult<T> = Result<T, Box<dyn Error>>;

821
libnative/textutils/Cargo.lock generated Normal file
View File

@@ -0,0 +1,821 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "calendrical_calculations"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cec493b209a1b81fa32312d7ceca1b547d341c7b5f16a3edbf32b1d8b455bbdf"
dependencies = [
"core_maths",
"displaydoc",
]
[[package]]
name = "core_maths"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3"
dependencies = [
"libm",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "fixed_decimal"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0febbeb1118a9ecdee6e4520ead6b54882e843dd0592ad233247dbee84c53db8"
dependencies = [
"displaydoc",
"smallvec",
"writeable",
]
[[package]]
name = "icu"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff5e3018d703f168b00dcefa540a65f1bbc50754ae32f3f5f0e43fe5ee51502"
dependencies = [
"icu_calendar",
"icu_casemap",
"icu_collator",
"icu_collections",
"icu_datetime",
"icu_decimal",
"icu_experimental",
"icu_list",
"icu_locid",
"icu_locid_transform",
"icu_normalizer",
"icu_plurals",
"icu_properties",
"icu_provider",
"icu_segmenter",
"icu_timezone",
]
[[package]]
name = "icu_calendar"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7265b2137f9a36f7634a308d91f984574bbdba8cfd95ceffe1c345552275a8ff"
dependencies = [
"calendrical_calculations",
"displaydoc",
"icu_calendar_data",
"icu_locid",
"icu_locid_transform",
"icu_provider",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_calendar_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0"
[[package]]
name = "icu_casemap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff0c8ae9f8d31b12e27fc385ff9ab1f3cd9b17417c665c49e4ec958c37da75f"
dependencies = [
"displaydoc",
"icu_casemap_data",
"icu_collections",
"icu_locid",
"icu_properties",
"icu_provider",
"writeable",
"zerovec",
]
[[package]]
name = "icu_casemap_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d57966d5ab748f74513be4046867f9a20e801e2775d41f91d04a0f560b61f08"
[[package]]
name = "icu_collator"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d370371887d31d56f361c3eaa15743e54f13bc677059c9191c77e099ed6966b2"
dependencies = [
"displaydoc",
"icu_collator_data",
"icu_collections",
"icu_locid_transform",
"icu_normalizer",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_collator_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee3f88741364b7d6269cce6827a3e6a8a2cf408a78f766c9224ab479d5e4ae5"
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_datetime"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d115efb85e08df3fd77e77f52e7e087545a783fffba8be80bfa2102f306b1780"
dependencies = [
"displaydoc",
"either",
"fixed_decimal",
"icu_calendar",
"icu_datetime_data",
"icu_decimal",
"icu_locid",
"icu_locid_transform",
"icu_plurals",
"icu_provider",
"icu_timezone",
"smallvec",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_datetime_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ba7e7f7a01269b9afb0a39eff4f8676f693b55f509b3120e43a0350a9f88bea"
[[package]]
name = "icu_decimal"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb8fd98f86ec0448d85e1edf8884e4e318bb2e121bd733ec929a05c0a5e8b0eb"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_decimal_data",
"icu_locid_transform",
"icu_provider",
"writeable",
]
[[package]]
name = "icu_decimal_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d424c994071c6f5644f999925fc868c85fec82295326e75ad5017bc94b41523"
[[package]]
name = "icu_experimental"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "844ad7b682a165c758065d694bc4d74ac67f176da1c499a04d85d492c0f193b7"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_collections",
"icu_decimal",
"icu_experimental_data",
"icu_locid",
"icu_locid_transform",
"icu_normalizer",
"icu_pattern",
"icu_plurals",
"icu_properties",
"icu_provider",
"litemap",
"num-bigint",
"num-rational",
"num-traits",
"smallvec",
"tinystr",
"writeable",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_experimental_data"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c178b9a34083fca5bd70d61f647575335e9c197d0f30c38e8ccd187babc69d0"
[[package]]
name = "icu_list"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfeda1d7775b6548edd4e8b7562304a559a91ed56ab56e18961a053f367c365"
dependencies = [
"displaydoc",
"icu_list_data",
"icu_locid_transform",
"icu_provider",
"regex-automata 0.2.0",
"writeable",
]
[[package]]
name = "icu_list_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1825170d2c6679cb20dbd96a589d034e49f698aed9a2ef4fafc9a0101ed298f"
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_pattern"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f36aafd098d6717de34e668a8120822275c1fba22b936e757b7de8a2fd7e4"
dependencies = [
"displaydoc",
"either",
"writeable",
"yoke",
"zerofrom",
]
[[package]]
name = "icu_plurals"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a70e7c025dbd5c501b0a5c188cd11666a424f0dadcd4f0a95b7dafde3b114"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_locid_transform",
"icu_plurals_data",
"icu_provider",
"zerovec",
]
[[package]]
name = "icu_plurals_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3e8f775b215d45838814a090a2227247a7431d74e9156407d9c37f6ef0f208"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "icu_segmenter"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de"
dependencies = [
"core_maths",
"displaydoc",
"icu_collections",
"icu_locid",
"icu_provider",
"icu_segmenter_data",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_segmenter_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df"
[[package]]
name = "icu_timezone"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa91ba6a585939a020c787235daa8aee856d9bceebd6355e283c0c310bc6de96"
dependencies = [
"displaydoc",
"icu_calendar",
"icu_provider",
"icu_timezone_data",
"tinystr",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_timezone_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c588878c508a3e2ace333b3c50296053e6483c6a7541251b546cc59dcd6ced8e"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "linkify"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780"
dependencies = [
"memchr",
]
[[package]]
name = "litemap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "proc-macro2"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.8",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782"
dependencies = [
"memchr",
]
[[package]]
name = "regex-automata"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "serde"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "2.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "textutils"
version = "0.1.0"
dependencies = [
"icu",
"icu_segmenter",
"itertools",
"lazy_static",
"linkify",
"once_cell",
"regex",
"unicase",
"unicode-normalization",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "unicase"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
dependencies = [
"either",
]
[[package]]
name = "yoke"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@@ -0,0 +1,17 @@
[package]
name = "textutils"
version = "0.1.0"
edition = "2021"
[dependencies]
icu = { version = "1.5.0", features = [
"compiled_data"
] }
icu_segmenter = "1.5.0"
itertools = "0.13.0"
lazy_static = "1.5.0"
linkify = "0.10.0"
once_cell = "1.20.2"
regex = "1.11.1"
unicase = "2.8.0"
unicode-normalization = "0.1.24"

View File

@@ -0,0 +1,20 @@
use lazy_static::lazy_static;
use linkify::{self, LinkFinder};
use regex::Regex;
lazy_static! {
static ref LINK_FINDER: LinkFinder = LinkFinder::new();
static ref REDDIT_REGEX: Regex = Regex::new(r"\/?(r\/[a-zA-Z0-9_]{3}[a-zA-Z0-9_]{0,18}|u\/[a-zA-Z0-9_-]{3}[a-zA-Z0-9_-]{0,17})").unwrap();
}
pub fn preprocess_auto(text: &str) -> String {
let mut cleaned_text = String::new();
let mut begin_cleaned_index = 0;
for span in LINK_FINDER.links(text) {
cleaned_text.push_str(&text[begin_cleaned_index..span.start()]);
begin_cleaned_index = span.end();
}
cleaned_text.push_str(&text[begin_cleaned_index..]);
cleaned_text = REDDIT_REGEX.replace_all(&cleaned_text, "").to_string();
return cleaned_text;
}

View File

@@ -0,0 +1,292 @@
use icu::properties::sets;
const NULLCHAR: char = '\0';
#[derive(PartialEq, Eq)]
pub enum FuzzyMatchStrategy {
MaxScore,
ScoreLhs,
ScoreRhs,
}
fn str_match_impl(word1: &[char], word2: &[char], strategy: FuzzyMatchStrategy) -> f64 {
let len1: usize = word1.len();
let len2: usize = word2.len();
let mut score1: f64 = 0.0;
let mut score2: f64 = 0.0;
let mut penalty: f64 = 0.0;
let mut last_penalty_awarded: f64 = 0.0;
let mut i1: usize = 0;
let mut i2: usize = 0;
let mut last_ch1: char = NULLCHAR;
let mut last_ch2: char = NULLCHAR;
fn next(word: &[char], i: &mut usize) -> char {
let mut ch: char;
loop {
ch = *word.get(*i).unwrap_or(&NULLCHAR);
if ch == NULLCHAR || !sets::diacritic().contains(ch) {
break;
}
*i += 1;
}
return ch;
}
while i1 < len1 && strategy != FuzzyMatchStrategy::ScoreRhs || i2 < len2 && strategy != FuzzyMatchStrategy::ScoreLhs {
let ch1 = next(word1, &mut i1);
let ch2 = next(word2, &mut i2);
if ch1 == NULLCHAR && ch2 == NULLCHAR {
break;
}
let mut ch_not_null = NULLCHAR;
if ch1 != NULLCHAR {
score1 += 1.0;
ch_not_null = ch1;
}
if ch2 != NULLCHAR {
score2 += 1.0;
ch_not_null = ch2;
}
if ch1 == NULLCHAR || ch2 == NULLCHAR {
if !sets::diacritic().contains(ch_not_null) {
penalty += 1.0;
}
i1 += 1;
i2 += 1;
continue;
}
if ch1 == ch2 {
// no penalty
} else if ch1.to_lowercase().eq(ch2.to_lowercase()) {
penalty += 0.1;
} else if ch1 == last_ch2 && ch2 == last_ch1 {
// transposition
// reduce penalty for transpositions
penalty -= 0.5 * last_penalty_awarded;
} else {
last_penalty_awarded = 1.0;
if last_ch1 == NULLCHAR && last_ch2 == NULLCHAR {
last_penalty_awarded += 1.0;
}
penalty += last_penalty_awarded;
}
i1 += 1;
i2 += 1;
last_ch1 = ch1;
last_ch2 = ch2;
}
let mut score = match strategy {
FuzzyMatchStrategy::ScoreLhs => score1,
FuzzyMatchStrategy::ScoreRhs => score2,
FuzzyMatchStrategy::MaxScore => f64::max(score1, score2),
};
if score == 0.0 {
// both strings essentially empty, thus they match
return 1.0;
}
score = 1.0 - penalty / score;
return f64::max(0.0, score);
}
#[inline]
pub fn str_match(word1: &[char], word2: &[char]) -> f64 {
return str_match_impl(word1, word2, FuzzyMatchStrategy::MaxScore);
}
#[inline]
pub fn str_match_live(base_word: &[char], curr_user_word: &[char]) -> f64 {
return str_match_impl(base_word, curr_user_word, FuzzyMatchStrategy::ScoreRhs);
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use std::vec;
use crate::normalization::StringNormalizationHelpers;
use super::*;
#[test]
fn ascii_basic_match() {
let abc = "abc".to_nfd_chars();
let result = str_match(&abc, &abc);
assert_eq!(result, 1.0);
}
#[test]
fn ascii_basic_mismatch() {
let abc = "abc".to_nfd_chars();
let def = "def".to_nfd_chars();
let result = str_match(&abc, &def);
assert_eq!(result, 0.0);
}
#[test]
fn ascii_casing_diff_one_char() {
let a = "a".to_nfd_chars();
let A = "A".to_nfd_chars();
let result = str_match(&a, &A);
assert_eq!(result, 0.9);
}
#[test]
fn ascii_casing_diff_multiple_chars() {
let abc = "abc".to_nfd_chars();
let ABC = "ABC".to_nfd_chars();
let result = str_match(&abc, &ABC);
assert_eq!(result, 0.9);
}
#[test]
fn diacritic_basic_match_lowercase() {
let ae = "ä".to_nfd_chars();
let result = str_match(&ae, &ae);
assert_eq!(result, 1.0);
}
#[test]
fn diacritic_basic_match_uppercase() {
let AE = "Ä".to_nfd_chars();
let result = str_match(&AE, &AE);
assert_eq!(result, 1.0);
}
#[test]
fn diacritic_basic_mismatch_lowercase() {
let ae = "ä".to_nfd_chars();
let oe = "ö".to_nfd_chars();
let result = str_match(&ae, &oe);
assert_eq!(result, 0.0);
}
#[test]
fn diacritic_basic_mismatch_uppercase() {
let AE = "Ä".to_nfd_chars();
let OE = "Ö".to_nfd_chars();
let result = str_match(&AE, &OE);
assert_eq!(result, 0.0);
}
#[test]
fn diacritic_casing_and_accent_diff() {
let ae = "ä".to_nfd_chars();
let AE = "Ä".to_nfd_chars();
let a: Vec<char> = "a".to_nfd_chars();
let A = "A".to_nfd_chars();
let result = str_match(&ae, &AE);
assert_eq!(result, 0.9);
let result = str_match(&ae, &A);
assert_eq!(result, 0.9);
let result = str_match(&AE, &a);
assert_eq!(result, 0.9);
let result = str_match(&ae, &a);
assert_eq!(result, 1.0);
let result = str_match(&AE, &A);
assert_eq!(result, 1.0);
}
#[test]
fn empty_match() {
let empty = "".to_nfd_chars();
let result = str_match(&empty, &empty);
assert_eq!(result, 1.0);
}
#[test]
fn transposition_basic_1_start() {
let str1 = "abxx".to_nfd_chars();
let str2 = "baxx".to_nfd_chars();
let result = str_match(&str1, &str2);
assert_eq!(result, 0.75);
}
#[test]
fn transposition_basic_2_middle() {
let str1 = "xabx".to_nfd_chars();
let str2 = "xbax".to_nfd_chars();
let result = str_match(&str1, &str2);
assert_eq!(result, 0.875);
}
#[test]
fn transposition_basic_3_end() {
let str1 = "xxab".to_nfd_chars();
let str2 = "xxba".to_nfd_chars();
let result = str_match(&str1, &str2);
assert_eq!(result, 0.875);
}
#[test]
fn transposition_diactric_1_start() {
let str1 = "äbxx".to_nfd_chars();
let str2 = "bäxx".to_nfd_chars();
let result = str_match(&str1, &str2);
assert_eq!(result, 0.75);
}
#[test]
fn transposition_diactric_2_middle() {
let str1 = "xäbx".to_nfd_chars();
let str2 = "xbäx".to_nfd_chars();
let result = str_match(&str1, &str2);
assert_eq!(result, 0.875);
}
#[test]
fn transposition_diactric_3_end() {
let str1 = "xxäb".to_nfd_chars();
let str2 = "xxbä".to_nfd_chars();
let result = str_match(&str1, &str2);
assert_eq!(result, 0.875);
}
#[test]
fn unicode_normalization_basic_mismatch() {
let ae_nfd = "ä".to_nfd_chars();
let ae_nfc = "ä".to_nfc_chars();
let result = str_match(&ae_nfd, &ae_nfc);
assert_eq!(result, 0.0);
}
#[test]
fn words_english_many() {
let words = vec![
("hello", "hello", 1.0),
("hello", "hallo", 0.8),
("hello", "helo", 0.6),
];
for (word1, word2, expected_score) in words {
let result = str_match(&word1.to_nfd_chars(), &word2.to_nfd_chars());
assert_eq!(result, expected_score, "Mismatch for words '{}' and '{}'", word1, word2);
}
}
}

View File

@@ -0,0 +1,54 @@
pub mod filter;
pub mod fuzzy;
pub mod normalization;
pub mod properties;
pub mod segment;
#[cfg(test)]
mod tests {
use filter::preprocess_auto;
use icu_segmenter::{SentenceSegmenter, WordSegmenter};
use segment::{split_sentences, split_words};
use super::*;
#[test]
fn segment_sentences_simple() {
let text = "Hello, world! How are you? I'm fine.";
let segmenter = SentenceSegmenter::new();
let sentences = split_sentences(text, &segmenter);
assert_eq!(&sentences, &["Hello, world!", "How are you?", "I'm fine."]);
}
#[test]
fn segment_words_simple() {
let text = "Hello, world! How are you? I'm fine.";
let segmenter = WordSegmenter::new_auto();
let words = split_words(text, &segmenter);
assert_eq!(&words, &["Hello", "world", "How", "are", "you", "I'm", "fine"]);
}
#[test]
fn preprocess_auto_simple() {
let text = "Hello, world! How are you? I'm fine. https://example.com and more";
let cleaned_text = preprocess_auto(text);
assert_eq!(&cleaned_text, "Hello, world! How are you? I'm fine. and more");
}
#[test]
fn preprocess_reddit_ids() {
let text = "have a look at r/cats, user u/example posed a cute cat in there";
let cleaned_text = preprocess_auto(text);
assert_eq!(&cleaned_text, "have a look at , user posed a cute cat in there");
}
#[test]
fn preprocess_url_markdown() {
let text = "You can find an example [in the documentation](https://example.com) or on GitHub";
let cleaned_text = preprocess_auto(text);
assert_eq!(&cleaned_text, "You can find an example [in the documentation]() or on GitHub");
let segmenter = WordSegmenter::new_auto();
let words = split_words(&cleaned_text, &segmenter);
assert_eq!(&words, &["You", "can", "find", "an", "example", "in", "the", "documentation", "or", "on", "GitHub"]);
}
}

View File

@@ -0,0 +1,61 @@
use unicode_normalization::UnicodeNormalization;
pub trait StringNormalizationHelpers {
fn to_nfd_chars(&self) -> Vec<char>;
fn to_nfd_string(&self) -> String;
fn to_nfc_chars(&self) -> Vec<char>;
fn to_nfc_string(&self) -> String;
fn to_nfkd_chars(&self) -> Vec<char>;
fn to_nfkd_string(&self) -> String;
fn to_nfkc_chars(&self) -> Vec<char>;
fn to_nfkc_string(&self) -> String;
}
impl StringNormalizationHelpers for str {
#[inline]
fn to_nfd_chars(&self) -> Vec<char> {
self.nfd().collect()
}
#[inline]
fn to_nfd_string(&self) -> String {
self.nfd().collect()
}
#[inline]
fn to_nfc_chars(&self) -> Vec<char> {
self.nfc().collect()
}
#[inline]
fn to_nfc_string(&self) -> String {
self.nfc().collect()
}
#[inline]
fn to_nfkd_chars(&self) -> Vec<char> {
self.nfkd().collect()
}
#[inline]
fn to_nfkd_string(&self) -> String {
self.nfkd().collect()
}
#[inline]
fn to_nfkc_chars(&self) -> Vec<char> {
self.nfkc().collect()
}
#[inline]
fn to_nfkc_string(&self) -> String {
self.nfkc().collect()
}
}

View File

@@ -0,0 +1,33 @@
use icu::properties::sets::CodePointSetDataBorrowed;
pub use icu::properties::sets;
pub trait CodePointSetDataExt {
fn debug_print(&self);
fn debug_print_based(&self, base: char);
}
impl <'a> CodePointSetDataExt for CodePointSetDataBorrowed<'a> {
fn debug_print(&self) {
debug_print_impl(&self, None);
}
fn debug_print_based(&self, base: char) {
debug_print_impl(&self, Some(base));
}
}
fn debug_print_impl(set: &CodePointSetDataBorrowed, base: Option<char>) {
for range in set.iter_ranges() {
print!("{:#x}..={:#x}", range.start(), range.end());
for codepoint in range {
if let Some(base) = base {
print!(" {}{}", base, char::from_u32(codepoint).unwrap());
} else {
print!(" {}", char::from_u32(codepoint).unwrap());
}
}
println!();
}
}

View File

@@ -0,0 +1,63 @@
use icu_segmenter::{GraphemeClusterSegmenter, SentenceSegmenter, WordSegmenter};
use itertools::Itertools;
pub struct IcuSegmenterCache {
sentence_segmenter: SentenceSegmenter,
word_segmenter: WordSegmenter,
grapheme_cluster_segmenter: GraphemeClusterSegmenter,
}
impl IcuSegmenterCache {
pub fn new_auto() -> Self {
let sentence_segmenter = SentenceSegmenter::new();
let word_segmenter = WordSegmenter::new_auto();
let grapheme_cluster_segmenter = GraphemeClusterSegmenter::new();
return Self {
sentence_segmenter,
word_segmenter,
grapheme_cluster_segmenter,
};
}
pub fn split_sentences<'t>(&self, text: &'t str) -> Vec<&'t str> {
return split_sentences(text, &self.sentence_segmenter);
}
pub fn split_words<'t>(&self, text: &'t str) -> Vec<&'t str> {
return split_words(text, &self.word_segmenter);
}
pub fn split_grapheme_clusters<'t>(&self, text: &'t str) -> Vec<&'t str> {
return split_grapheme_clusters(text, &self.grapheme_cluster_segmenter);
}
}
pub fn split_sentences<'t>(text: &'t str, segmenter: &SentenceSegmenter) -> Vec<&'t str> {
let sentences: Vec<&str> = segmenter
.segment_str(text)
.tuple_windows()
.map(|(i, j)| text[i..j].trim())
.filter(|sentence| !sentence.is_empty())
.collect();
return sentences;
}
pub fn split_words<'t>(text: &'t str, segmenter: &WordSegmenter) -> Vec<&'t str> {
let words: Vec<&str> = segmenter
.segment_str(text)
.iter_with_word_type()
.tuple_windows()
.filter(|(_, (_, segment_type))| segment_type.is_word_like())
.map(|((i, _), (j, _))| &text[i..j])
.collect();
return words;
}
pub fn split_grapheme_clusters<'t>(text: &'t str, segmenter: &GraphemeClusterSegmenter) -> Vec<&'t str> {
let grapheme_clusters: Vec<&str> = segmenter
.segment_str(text)
.tuple_windows()
.map(|(i, j)| &text[i..j])
.collect();
return grapheme_clusters;
}

961
utils/flesttools/Cargo.lock generated Normal file
View File

@@ -0,0 +1,961 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "calendrical_calculations"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cec493b209a1b81fa32312d7ceca1b547d341c7b5f16a3edbf32b1d8b455bbdf"
dependencies = [
"core_maths",
"displaydoc",
]
[[package]]
name = "cc"
version = "1.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b9470d453346108f93a59222a9a1a5724db32d0a4727b7ab7ace4b4d822dc9"
dependencies = [
"shlex",
]
[[package]]
name = "core_maths"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3b02505ccb8c50b0aa21ace0fc08c3e53adebd4e58caa18a36152803c7709a3"
dependencies = [
"libm",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "fixed_decimal"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0febbeb1118a9ecdee6e4520ead6b54882e843dd0592ad233247dbee84c53db8"
dependencies = [
"displaydoc",
"smallvec",
"writeable",
]
[[package]]
name = "flest"
version = "0.1.0"
dependencies = [
"textutils",
]
[[package]]
name = "flesttools"
version = "0.1.0"
dependencies = [
"flest",
"pancurses",
"serde",
"serde_json",
"textutils",
]
[[package]]
name = "icu"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff5e3018d703f168b00dcefa540a65f1bbc50754ae32f3f5f0e43fe5ee51502"
dependencies = [
"icu_calendar",
"icu_casemap",
"icu_collator",
"icu_collections",
"icu_datetime",
"icu_decimal",
"icu_experimental",
"icu_list",
"icu_locid",
"icu_locid_transform",
"icu_normalizer",
"icu_plurals",
"icu_properties",
"icu_provider",
"icu_segmenter",
"icu_timezone",
]
[[package]]
name = "icu_calendar"
version = "1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7265b2137f9a36f7634a308d91f984574bbdba8cfd95ceffe1c345552275a8ff"
dependencies = [
"calendrical_calculations",
"displaydoc",
"icu_calendar_data",
"icu_locid",
"icu_locid_transform",
"icu_provider",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_calendar_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e009b7f0151ee6fb28c40b1283594397e0b7183820793e9ace3dcd13db126d0"
[[package]]
name = "icu_casemap"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ff0c8ae9f8d31b12e27fc385ff9ab1f3cd9b17417c665c49e4ec958c37da75f"
dependencies = [
"displaydoc",
"icu_casemap_data",
"icu_collections",
"icu_locid",
"icu_properties",
"icu_provider",
"writeable",
"zerovec",
]
[[package]]
name = "icu_casemap_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d57966d5ab748f74513be4046867f9a20e801e2775d41f91d04a0f560b61f08"
[[package]]
name = "icu_collator"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d370371887d31d56f361c3eaa15743e54f13bc677059c9191c77e099ed6966b2"
dependencies = [
"displaydoc",
"icu_collator_data",
"icu_collections",
"icu_locid_transform",
"icu_normalizer",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_collator_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee3f88741364b7d6269cce6827a3e6a8a2cf408a78f766c9224ab479d5e4ae5"
[[package]]
name = "icu_collections"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_datetime"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d115efb85e08df3fd77e77f52e7e087545a783fffba8be80bfa2102f306b1780"
dependencies = [
"displaydoc",
"either",
"fixed_decimal",
"icu_calendar",
"icu_datetime_data",
"icu_decimal",
"icu_locid",
"icu_locid_transform",
"icu_plurals",
"icu_provider",
"icu_timezone",
"smallvec",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_datetime_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ba7e7f7a01269b9afb0a39eff4f8676f693b55f509b3120e43a0350a9f88bea"
[[package]]
name = "icu_decimal"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb8fd98f86ec0448d85e1edf8884e4e318bb2e121bd733ec929a05c0a5e8b0eb"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_decimal_data",
"icu_locid_transform",
"icu_provider",
"writeable",
]
[[package]]
name = "icu_decimal_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d424c994071c6f5644f999925fc868c85fec82295326e75ad5017bc94b41523"
[[package]]
name = "icu_experimental"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "844ad7b682a165c758065d694bc4d74ac67f176da1c499a04d85d492c0f193b7"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_collections",
"icu_decimal",
"icu_experimental_data",
"icu_locid",
"icu_locid_transform",
"icu_normalizer",
"icu_pattern",
"icu_plurals",
"icu_properties",
"icu_provider",
"litemap",
"num-bigint",
"num-rational",
"num-traits",
"smallvec",
"tinystr",
"writeable",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_experimental_data"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c178b9a34083fca5bd70d61f647575335e9c197d0f30c38e8ccd187babc69d0"
[[package]]
name = "icu_list"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbfeda1d7775b6548edd4e8b7562304a559a91ed56ab56e18961a053f367c365"
dependencies = [
"displaydoc",
"icu_list_data",
"icu_locid_transform",
"icu_provider",
"regex-automata 0.2.0",
"writeable",
]
[[package]]
name = "icu_list_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1825170d2c6679cb20dbd96a589d034e49f698aed9a2ef4fafc9a0101ed298f"
[[package]]
name = "icu_locid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_locid_transform"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
dependencies = [
"displaydoc",
"icu_locid",
"icu_locid_transform_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_locid_transform_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
[[package]]
name = "icu_normalizer"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
dependencies = [
"displaydoc",
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"utf16_iter",
"utf8_iter",
"write16",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
[[package]]
name = "icu_pattern"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7f36aafd098d6717de34e668a8120822275c1fba22b936e757b7de8a2fd7e4"
dependencies = [
"displaydoc",
"either",
"writeable",
"yoke",
"zerofrom",
]
[[package]]
name = "icu_plurals"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba5a70e7c025dbd5c501b0a5c188cd11666a424f0dadcd4f0a95b7dafde3b114"
dependencies = [
"displaydoc",
"fixed_decimal",
"icu_locid_transform",
"icu_plurals_data",
"icu_provider",
"zerovec",
]
[[package]]
name = "icu_plurals_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3e8f775b215d45838814a090a2227247a7431d74e9156407d9c37f6ef0f208"
[[package]]
name = "icu_properties"
version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
dependencies = [
"displaydoc",
"icu_collections",
"icu_locid_transform",
"icu_properties_data",
"icu_provider",
"tinystr",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
[[package]]
name = "icu_provider"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
dependencies = [
"displaydoc",
"icu_locid",
"icu_provider_macros",
"stable_deref_trait",
"tinystr",
"writeable",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_provider_macros"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "icu_segmenter"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a717725612346ffc2d7b42c94b820db6908048f39434504cb130e8b46256b0de"
dependencies = [
"core_maths",
"displaydoc",
"icu_collections",
"icu_locid",
"icu_provider",
"icu_segmenter_data",
"utf8_iter",
"zerovec",
]
[[package]]
name = "icu_segmenter_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f739ee737260d955e330bc83fdeaaf1631f7fb7ed218761d3c04bb13bb7d79df"
[[package]]
name = "icu_timezone"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa91ba6a585939a020c787235daa8aee856d9bceebd6355e283c0c310bc6de96"
dependencies = [
"displaydoc",
"icu_calendar",
"icu_provider",
"icu_timezone_data",
"tinystr",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_timezone_data"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c588878c508a3e2ace333b3c50296053e6483c6a7541251b546cc59dcd6ced8e"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "libm"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
[[package]]
name = "linkify"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1dfa36d52c581e9ec783a7ce2a5e0143da6237be5811a0b3153fedfdbe9f780"
dependencies = [
"memchr",
]
[[package]]
name = "litemap"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704"
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "ncurses"
version = "5.101.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e2c5d34d72657dc4b638a1c25d40aae81e4f1c699062f72f467237920752032"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
dependencies = [
"num-integer",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
dependencies = [
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
dependencies = [
"num-bigint",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "pancurses"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0352975c36cbacb9ee99bfb709b9db818bed43af57751797f8633649759d13db"
dependencies = [
"libc",
"log",
"ncurses",
"pdcurses-sys",
"winreg",
]
[[package]]
name = "pdcurses-sys"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "084dd22796ff60f1225d4eb6329f33afaf4c85419d51d440ab6b8c6f4529166b"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "pkg-config"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "proc-macro2"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.8",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9368763f5a9b804326f3af749e16f9abf378d227bcdee7634b13d8f17793782"
dependencies = [
"memchr",
]
[[package]]
name = "regex-automata"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "serde"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "stable_deref_trait"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
[[package]]
name = "syn"
version = "2.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "synstructure"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "textutils"
version = "0.1.0"
dependencies = [
"icu",
"icu_segmenter",
"itertools",
"lazy_static",
"linkify",
"once_cell",
"regex",
"unicase",
"unicode-normalization",
]
[[package]]
name = "tinystr"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tinyvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "unicase"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "unicode-normalization"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
dependencies = [
"tinyvec",
]
[[package]]
name = "utf16_iter"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winreg"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a27a759395c1195c4cc5cda607ef6f8f6498f64e78f7900f5de0a127a424704a"
dependencies = [
"winapi",
]
[[package]]
name = "write16"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
[[package]]
name = "writeable"
version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
dependencies = [
"either",
]
[[package]]
name = "yoke"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5b1314b079b0930c31e3af543d8ee1757b1951ae1e1565ec704403a7240ca5"
dependencies = [
"serde",
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28cc31741b18cb6f1d5ff12f5b7523e3d6eb0852bbbad19d73905511d9849b95"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea7b4a3637ea8669cedf0f1fd5c286a17f3de97b8dd5a70a6c167a1730e63a5"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerotrie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb594dd55d87335c5f60177cee24f19457a5ec10a065e0a3014722ad252d0a1f"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@@ -0,0 +1,11 @@
[package]
name = "flesttools"
version = "0.1.0"
edition = "2021"
[dependencies]
flest = { path = "../../libnative/flest" }
textutils = { path = "../../libnative/textutils" }
pancurses = { version = "0.17.0", features = ["wide"] }
serde = "1.0.203"
serde_json = "1.0.120"

View File

@@ -0,0 +1,143 @@
use flest::model::NgramModel;
use pancurses::Input;
use textutils::filter::preprocess_auto;
use textutils::segment::IcuSegmenterCache;
use std::env;
use std::fs;
use std::io::BufRead;
use std::io::BufReader;
const TOKEN_SENTENCE_SEPARATOR: &str = "\\sep";
fn tokenize_text(text: &str) -> Vec<&str> {
let segmenters = IcuSegmenterCache::new_auto();
let sentences = segmenters.split_sentences(text);
let mut tokens: Vec<&str> = Vec::new();
tokens.push(TOKEN_SENTENCE_SEPARATOR);
for sentence in sentences {
let words = segmenters.split_words(sentence);
for word in words {
tokens.push(word);
}
tokens.push(TOKEN_SENTENCE_SEPARATOR);
}
//println!("Tokens: {:?}", tokens);
return tokens;
}
fn train_model(text: &str, model: &mut NgramModel) {
let text = preprocess_auto(text);
let text = text.trim();
if text.is_empty() {
return;
}
let tokens = tokenize_text(&text);
//println!("Tokens: {:?}", tokens);
model.train_from_tokens(&tokens);
}
fn train_from_plain_text(path: &str, model: &mut NgramModel) {
let text = fs::read_to_string(path).expect("Failed to read file");
train_model(&text, model);
}
fn train_from_reddit_comments(path: &str, model: &mut NgramModel) {
let file = fs::File::open(path).expect("Failed to open file");
let reader = BufReader::new(file);
let mut line_count = 0;
for line in reader.lines() {
if let Ok(line) = line {
let json: serde_json::Value = serde_json::from_str(&line).expect("Failed to parse JSON");
if let Some(author) = json.get("author").and_then(|it| it.as_str()) {
if author == "AutoModerator" {
continue;
}
}
if let Some(body) = json.get("body").and_then(|it| it.as_str()) {
train_model(body, model);
}
}
line_count += 1;
if line_count > 10000 {
break;
}
}
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() != 2 {
eprintln!("Usage: {} <file_path>", args[0]);
return;
}
let path = &args[1];
let mut model = NgramModel::new();
model.meta.sentence_token = TOKEN_SENTENCE_SEPARATOR.to_owned();
if path.ends_with(".reddit.jsonl") {
train_from_reddit_comments(path, &mut model);
} else {
train_from_plain_text(path, &mut model);
}
//model.trie_root.debug_pretty_print();
//return;
let window = pancurses::initscr();
let mut input_text = String::new();
pancurses::noecho();
window.keypad(true);
loop {
let mut words: Vec<&str> = input_text.split_whitespace().collect();
words.insert(0, TOKEN_SENTENCE_SEPARATOR);
if input_text.ends_with(' ') || words.last() == Some(&TOKEN_SENTENCE_SEPARATOR) {
words.push("");
}
let predictions = model.predict(&words);
window.clear();
window.addstr("N-gram model debug frontend\n");
window.addstr(" demo tokenizer only supports single-line sentence in input text!\n\n");
window.addstr(format!("enter text: {}\n", input_text));
window.addstr(format!("detected words: {:?}\n\n", words));
window.addstr("predictions:\n");
for (i, candidate) in predictions.iter().enumerate() {
if i == 0 && candidate.confidence > (0.9 * 255.0) as u8 {
window.attron(pancurses::A_BOLD);
}
window.addstr(format!(" {}. {} (c={:.2})\n", i + 1, candidate.text, candidate.confidence));
if i == 0 && candidate.confidence > (0.9 * 255.0) as u8 {
window.attroff(pancurses::A_BOLD);
}
}
if predictions.is_empty() {
window.addstr(" (none)\n");
}
window.mv(3, 12 + input_text.len() as i32);
window.refresh();
match window.getch().unwrap() {
Input::KeyF10 => {
break
}
Input::KeyBackspace => {
input_text.pop();
}
Input::Character('\n') => {
train_model(&input_text, &mut model)
}
Input::Character(ch) => {
input_text.push(ch)
}
_ => { () }
}
}
pancurses::endwin();
}

27
utils/setup_vscode_dev_env.sh Executable file
View File

@@ -0,0 +1,27 @@
#!/bin/bash
WORKSPACE_ROOT_DIR="$(realpath "$(dirname "$0")/..")"
VSCODE_DIR="$WORKSPACE_ROOT_DIR/.vscode"
VSCODE_SETTINGS_JSON_PATH="$VSCODE_DIR/settings.json"
if [ "$WORKSPACE_ROOT_DIR" != "$(pwd)" ]; then
echo "Not executing this script from workspace root dir!"
exit 1
fi
if [ ! -d "$VSCODE_DIR" ]; then
mkdir "$VSCODE_DIR"
fi
echo -en "{\n" > "$VSCODE_SETTINGS_JSON_PATH"
# <rust-analyzer>
rust_project_paths="$(find "$WORKSPACE_ROOT_DIR" -type f -name "Cargo.toml")"
echo -en " \"rust-analyzer.linkedProjects\": [\n" >> "$VSCODE_SETTINGS_JSON_PATH"
for rust_project_path in $rust_project_paths; do
echo -en " \"$rust_project_path\",\n" >> "$VSCODE_SETTINGS_JSON_PATH"
done
echo -en " ],\n" >> "$VSCODE_SETTINGS_JSON_PATH"
# </rust-analyzer>
echo -en "}\n" >> "$VSCODE_SETTINGS_JSON_PATH"