Compare commits

..

21 Commits
v0.5.0 ... main

Author SHA1 Message Date
e2b361ac3d Add Android.bp for in-line building in aosp
Some checks failed
FlorisBoard CI / build (push) Has been cancelled
2025-11-03 00:22:30 -08:00
Patrick Goldinger
5562d49546 Merge pull request #3107 from florisboard/feat/clipboard-history-rework
Clipboard history rework
2025-09-30 21:19:29 +02:00
Patrick Goldinger
5891c53cf6 Fix paste icon not consistent with Smartbar 2025-09-30 01:08:57 +02:00
Patrick Goldinger
5466d00037 Add fine-grained clipboard sync behavior options (#811) 2025-09-30 00:44:24 +02:00
Patrick Goldinger
8a06d764bb Fix clear primary clip removes from history if pinned (#2089) 2025-09-29 23:16:31 +02:00
Patrick Goldinger
a800c7b230 Fix clipboard timestamp not shown for large item popups (#2941) 2025-09-29 22:31:36 +02:00
Patrick Goldinger
67e99aeca3 Add clipboard history hide on event controls (#1098 and #2168)
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-09-29 22:19:53 +02:00
Patrick Goldinger
ac14d92192 Tidy up code in clipboard manager and history
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-09-29 21:43:12 +02:00
Patrick Goldinger
3392f8f212 Rename clipboard prefs to improve consistency
Co-authored-by: Lars Mühlbauer <lm41@lm41.xyz>
2025-09-29 21:10:44 +02:00
Lars Mühlbauer
8c7cafad61 Use different icon for paste action (#3106) 2025-09-29 20:42:57 +02:00
Shunnuo
721b25c349 Adding Korean Phonetic layout (#3079)
* korean

* duplicated

* KR

* KR
2025-09-10 12:10:15 +02:00
florisboard-bot
96e3af9c16 Update translations from Crowdin 2025-09-09 22:21:11 +02:00
Pelle
fa79fa3849 Add the swedish version of the dvorak layout (svorak) (#3080)
Signed-off-by: Pelle Windestam <pelle@windestam.se>
2025-09-09 21:32:56 +02:00
Ahmed Nagi
f646825e9c Add Arabic question mark to ~right popup mappings (#3088)
* Add Arabic question mark to ~right popup mappings

* Update ar.json

fix: add exclamation mark "!"

* Update ar.json

---------

Co-authored-by: Ahmed Nagi <144544047+Ahmed-Nagi1@users.noreply.github.com>
2025-09-09 21:22:24 +02:00
lnfinitesimal
0a299e1b04 Add superscript minus (⁻) and plus (⁺) symbols (#3039)
Complements superscript numbers. For mathematical and chemical notation (e.g., 10⁻³, Na⁺, Ca²⁺).
2025-09-09 21:15:39 +02:00
Patrick Goldinger
6417cf5958 Fix background not rendering on Android 8/9 (#3076) 2025-09-08 01:33:47 +02:00
Patrick Goldinger
234580fd48 Rework clipboard delete button to respect active filters (#3075) 2025-09-02 23:31:52 +02:00
Patrick Goldinger
fe015c549c Add IzzySoft badge for stable track to readme (#3073) 2025-09-02 17:23:33 +02:00
Patrick Goldinger
253ee969eb Update early-beta to beta (#3072) 2025-09-02 01:20:00 +02:00
Patrick Goldinger
8fa986ca76 Fix theme updates not properly propagating after save (#3052) 2025-08-19 22:34:03 +02:00
Lars Mühlbauer
640a1c56cc Update bug_report.yml (#3047)
* Update bug_report.yml

* Update .github/ISSUE_TEMPLATE/bug_report.yml

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>

---------

Co-authored-by: Patrick Goldinger <patrick@patrickgold.dev>
2025-08-18 12:36:16 +02:00
43 changed files with 602 additions and 243 deletions

View File

@@ -44,7 +44,8 @@ body:
label: Install Source
options:
- Google PlayStore
- F-Droid
- F-Droid (F-Droid Main)
- F-Droid (IzzyOnDroid)
- GitHub
validations:
required: true

39
Android.bp Normal file
View File

@@ -0,0 +1,39 @@
android_app {
name: "FlorisBoard",
srcs: [
"app/src/main/kotlin/**/*.kt",
"app/src/main/java/**/*.java",
],
resource_dirs: ["app/src/main/res"],
manifest: "app/src/main/AndroidManifest.xml",
certificate: "platform",
system_ext_specific: true,
platform_apis: true,
optimize: {
enabled: true,
},
overrides: ["FlorisBoard"],
static_libs: [
"androidx.core_core",
"androidx.emoji2_emoji2",
"androidx.emoji2_emoji2-views",
"androidx.startup_startup",
"androidx.appcompat_appcompat",
"androidx.preference_preference",
"androidx.recyclerview_recyclerview",
"androidx.constraintlayout_constraintlayout",
],
required: ["android.permission.VIBRATE"],
optional: ["android.permission.POST_NOTIFICATIONS"],
allow_backup: true,
backup_config: "res/xml/backup_rules.xml",
full_backup_content: "res/xml/backup_rules.xml",
enable_on_back_invoked_callback: true,
profileable: true,
package: "dev.patrickgold.florisboard",
privileged: false,
dex_preopt: {
enabled: false,
},
asset_dirs: ["app/src/main/assets"],
}

View File

@@ -5,7 +5,7 @@ src=".github/repo_icon.png" alt="App icon">
**FlorisBoard** is a free and open-source keyboard for Android 8.0+
devices. It aims at being modern, user-friendly and customizable while
fully respecting your privacy. Currently in early-beta state.
fully respecting your privacy. Currently in beta state.
<table>
<tr>
@@ -26,7 +26,10 @@ fully respecting your privacy. Currently in early-beta state.
</tr>
<tr>
<td valign="top">
<p><a href="https://f-droid.org/packages/dev.patrickgold.florisboard"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge"></a></p>
<p>
<a href="https://apt.izzysoft.de/fdroid/index/apk/dev.patrickgold.florisboard"><img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png" height="64" alt="IzzySoft repo badge"></a>
<a href="https://f-droid.org/packages/dev.patrickgold.florisboard"><img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" height="64" alt="F-Droid badge"></a>
</p>
<p>
**Google Play**: Join the [FlorisBoard Test Group](https://groups.google.com/g/florisboard-closed-beta-test), then visit the [testing page](https://play.google.com/apps/testing/dev.patrickgold.florisboard). Once joined and installed, updates will be delivered like for any other app. ([Store entry](https://play.google.com/store/apps/details?id=dev.patrickgold.florisboard))

View File

@@ -157,6 +157,13 @@
"direction": "ltr",
"modifier": "org.florisboard.layouts:dvorak"
},
{
"id": "dvorak_se",
"label": "Dvorak (SE)",
"authors": [ "iceaway" ],
"direction": "ltr",
"modifier": "org.florisboard.layouts:dvorak_se"
},
{
"id": "esperanto",
"label": "Esperanto",
@@ -267,6 +274,12 @@
"authors": [ "patrickgold", "Hayleia" ],
"direction": "ltr"
},
{
"id": "korean_phonetic",
"label": "South Korean Phonetic",
"authors": [ "Shunnuo" ],
"direction": "ltr"
},
{
"id": "kurdish",
"label": "کوردی (قوەرتی نوێ)",
@@ -510,6 +523,12 @@
"authors": [ "msrd0" ],
"direction": "ltr"
},
{
"id": "dvorak_se",
"label": "Dvorak (SE)",
"authors": [ "iceaway" ],
"direction": "ltr"
},
{
"id": "hebrew",
"label": "עברית",

View File

@@ -0,0 +1,37 @@
[
[
{ "$": "auto_text_key", "code": 229, "label": "å" },
{ "$": "auto_text_key", "code": 228, "label": "ä" },
{ "$": "auto_text_key", "code": 246, "label": "ö" },
{ "$": "auto_text_key", "code": 112, "label": "p" },
{ "$": "auto_text_key", "code": 121, "label": "y" },
{ "$": "auto_text_key", "code": 102, "label": "f" },
{ "$": "auto_text_key", "code": 103, "label": "g" },
{ "$": "auto_text_key", "code": 99, "label": "c" },
{ "$": "auto_text_key", "code": 114, "label": "r" },
{ "$": "auto_text_key", "code": 108, "label": "l" }
],
[
{ "$": "auto_text_key", "code": 97, "label": "a" },
{ "$": "auto_text_key", "code": 111, "label": "o" },
{ "$": "auto_text_key", "code": 101, "label": "e" },
{ "$": "auto_text_key", "code": 117, "label": "u" },
{ "$": "auto_text_key", "code": 105, "label": "i" },
{ "$": "auto_text_key", "code": 100, "label": "d" },
{ "$": "auto_text_key", "code": 104, "label": "h" },
{ "$": "auto_text_key", "code": 116, "label": "t" },
{ "$": "auto_text_key", "code": 110, "label": "n" },
{ "$": "auto_text_key", "code": 115, "label": "s" }
],
[
{ "$": "auto_text_key", "code": 113, "label": "q" },
{ "$": "auto_text_key", "code": 106, "label": "j" },
{ "$": "auto_text_key", "code": 107, "label": "k" },
{ "$": "auto_text_key", "code": 120, "label": "x" },
{ "$": "auto_text_key", "code": 98, "label": "b" },
{ "$": "auto_text_key", "code": 109, "label": "m" },
{ "$": "auto_text_key", "code": 119, "label": "w" },
{ "$": "auto_text_key", "code": 118, "label": "v" },
{ "$": "auto_text_key", "code": 122, "label": "z" }
]
]

View File

@@ -0,0 +1,55 @@
[
[
{ "$": "auto_text_key", "code": 12615, "label": "ㅇ"},
{ "$": "auto_text_key", "code": 12641, "label": "ㅡ"},
{ "$": "case_selector",
"lower": { "code": 12628, "label": "ㅔ" },
"upper": { "code": 12630, "label": "ㅖ" }
},
{ "$": "auto_text_key", "code": 12601, "label": "ㄹ"},
{ "$": "auto_text_key", "code": 12620, "label": "ㅌ"},
{ "$": "case_selector",
"lower": { "code": 12624, "label": "ㅐ" },
"upper": { "code": 12626, "label": "ㅒ" }
},
{ "$": "auto_text_key", "code": 12636, "label": "ㅜ"},
{ "$": "auto_text_key", "code": 12643, "label": "ㅣ"},
{ "$": "auto_text_key", "code": 12631, "label": "ㅗ"},
{ "$": "auto_text_key", "code": 12621, "label": "ㅍ"}
],
[
{ "$": "auto_text_key", "code": 12623, "label": "ㅏ"},
{ "$": "case_selector",
"lower": { "code": 12613, "label": "ㅅ" },
"upper": { "code": 12614, "label": "ㅆ" }
},
{ "$": "case_selector",
"lower": { "code": 12599, "label": "ㄷ" },
"upper": { "code": 12600, "label": "ㄸ" }
},
{ "$": "auto_text_key", "code": 12625, "label": "ㅑ"},
{ "$": "case_selector",
"lower": { "code": 12593, "label": "ㄱ" },
"upper": { "code": 12594, "label": "ㄲ" }
},
{ "$": "auto_text_key", "code": 12622, "label": "ㅎ"},
{ "$": "case_selector",
"lower": { "code": 12616, "label": "ㅈ" },
"upper": { "code": 12617, "label": "ㅉ" }
},
{ "$": "auto_text_key", "code": 12619, "label": "ㅋ"},
{ "$": "auto_text_key", "code": 12635, "label": "ㅛ"}
],
[
{ "$": "auto_text_key", "code": 12629, "label": "ㅕ"},
{ "$": "auto_text_key", "code": 12640, "label": "ㅠ"},
{ "$": "auto_text_key", "code": 12618, "label": "ㅊ"},
{ "$": "auto_text_key", "code": 12627, "label": "ㅓ"},
{ "$": "case_selector",
"lower": { "code": 12610, "label": "ㅂ" },
"upper": { "code": 12611, "label": "ㅃ" }
},
{ "$": "auto_text_key", "code": 12596, "label": "ㄴ"},
{ "$": "auto_text_key", "code": 12609, "label": "ㅁ"}
]
]

View File

@@ -0,0 +1,16 @@
[
[
{ "code": -11, "label": "shift", "type": "modifier" },
{ "code": 0, "type": "placeholder" },
{ "code": -7, "label": "delete", "type": "enter_editing" }
],
[
{ "code": -202, "label": "view_symbols", "type": "system_gui" },
{ "$": "auto_text_key", "code": 44, "label": "," },
{ "code": -227, "label": "language_switch", "type": "system_gui" },
{ "code": -212, "label": "ime_ui_mode_media", "type": "system_gui" },
{ "code": 32, "label": "space" },
{ "$": "auto_text_key", "code": 46, "label": "." },
{ "code": 10, "label": "enter", "groupId": 3, "type": "enter_editing" }
]
]

View File

@@ -23,13 +23,17 @@
{ "code": 45, "label": "-", "popup": {
"main": { "code": 95, "label": "_" },
"relevant": [
{ "code": 8315, "label": "⁻" },
{ "code": 8212, "label": "—" },
{ "code": 8211, "label": "" },
{ "code": 183, "label": "·" }
]
} },
{ "code": 43, "label": "+", "popup": {
"main": { "code": 177, "label": "±" }
"main": { "code": 177, "label": "±" },
"relevant": [
{ "code": 8314, "label": "⁺" }
]
} },
{ "$": "layout_direction_selector",
"ltr": { "code": 40, "label": "(", "popup": {

View File

@@ -59,6 +59,7 @@
{ "id": "ja-JP-jis", "authors": [ "waelwindows" ] },
{ "id": "kab", "authors": [ "yanis867" ] },
{ "id": "ko", "authors": [ "patrickgold", "Hayleia" ] },
{ "id": "ko-KR", "authors": [ "Shunnuo" ] },
{ "id": "ku", "authors": [ "GoRaN" ] },
{ "id": "lt", "authors": [ "patrickgold" ] },
{ "id": "lv", "authors": [ "patrickgold", "eandersons" ] },
@@ -606,6 +607,15 @@
"characters": "org.florisboard.layouts:korean"
}
},
{
"languageTag": "ko-KR",
"composer": "org.florisboard.composers:hangul-unicode",
"currencySet": "org.florisboard.currencysets:south_korean_won",
"popupMapping": "org.florisboard.localization:ko",
"preferred": {
"characters": "org.florisboard.layouts:korean_phonetic"
}
},
{
"languageTag": "lt-LT",
"composer": "org.florisboard.composers:appender",

View File

@@ -70,8 +70,9 @@
]
},
"~right": {
"main": { "code": 1611, "label": "ً" },
"main": { "code": 1567, "label": "؟" },
"relevant": [
{ "code": 1611, "label": "ً" },
{ "code": 1622, "label": "ٖ" },
{ "code": 1648, "label": "ٰ" },
{ "code": 1619, "label": "ٓ" },

View File

@@ -360,7 +360,9 @@ class FlorisImeService : LifecycleInputMethodService() {
if (info == null) return
val editorInfo = FlorisEditorInfo.wrap(info)
activeState.batchEdit {
activeState.imeUiMode = ImeUiMode.TEXT
if (activeState.imeUiMode != ImeUiMode.CLIPBOARD || prefs.clipboard.historyHideOnNextTextField.get()) {
activeState.imeUiMode = ImeUiMode.TEXT
}
activeState.isSelectionMode = editorInfo.initialSelection.isSelectionMode
editorInstance.handleStartInputView(editorInfo, isRestart = restarting)
}
@@ -428,6 +430,7 @@ class FlorisImeService : LifecycleInputMethodService() {
}
isWindowShown = false
activeState.batchEdit {
activeState.imeUiMode = ImeUiMode.TEXT
activeState.isActionsOverflowVisible = false
activeState.isActionsEditorVisible = false
}
@@ -626,14 +629,19 @@ class FlorisImeService : LifecycleInputMethodService() {
clickAndSemanticsModifier = Modifier
// Do not remove below line or touch input may get stuck
.pointerInteropFilter { false },
supportsBackgroundImage = false,
supportsBackgroundImage = !AndroidVersion.ATLEAST_API30_R,
allowClip = false,
) {
SnyggSurfaceView(
elementName = FlorisImeUi.Window.elementName,
attributes = attributes,
modifier = Modifier.matchParentSize(),
)
// The SurfaceView is used to render the background image under inline-autofill chips. These are only
// available on Android >=11, and SurfaceView causes trouble on Android 8/9, thus we render the image
// in the SurfaceView for Android >=11, and in the Compose View Tree for Android <=10.
if (AndroidVersion.ATLEAST_API30_R) {
SnyggSurfaceView(
elementName = FlorisImeUi.Window.elementName,
attributes = attributes,
modifier = Modifier.matchParentSize(),
)
}
val configuration = LocalConfiguration.current
val bottomOffset by if (configuration.isOrientationPortrait()) {
prefs.keyboard.bottomOffsetPortrait

View File

@@ -25,6 +25,7 @@ import dev.patrickgold.florisboard.app.settings.theme.DisplayKbdAfterDialogs
import dev.patrickgold.florisboard.app.settings.theme.SnyggLevel
import dev.patrickgold.florisboard.app.setup.NotificationPermissionState
import dev.patrickgold.florisboard.ime.clipboard.CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO
import dev.patrickgold.florisboard.ime.clipboard.ClipboardSyncBehavior
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.core.Subtype
import dev.patrickgold.florisboard.ime.input.CapitalizationBehavior
@@ -63,6 +64,7 @@ import dev.patrickgold.jetpref.datastore.model.LocalTime
import dev.patrickgold.jetpref.datastore.model.PreferenceData
import dev.patrickgold.jetpref.datastore.model.PreferenceMigrationEntry
import dev.patrickgold.jetpref.datastore.model.PreferenceModel
import dev.patrickgold.jetpref.datastore.model.PreferenceType
import dev.patrickgold.jetpref.datastore.model.observeAsState
import dev.patrickgold.jetpref.material.ui.ColorRepresentation
import kotlinx.serialization.json.Json
@@ -84,62 +86,13 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
key = "clipboard__use_internal_clipboard",
default = false,
)
val syncToFloris = boolean(
val syncToFloris = enum(
key = "clipboard__sync_to_floris",
default = true,
default = ClipboardSyncBehavior.ALL_EVENTS,
)
val syncToSystem = boolean(
val syncToSystem = enum(
key = "clipboard__sync_to_system",
default = false,
)
val historyEnabled = boolean(
key = "clipboard__history_enabled",
default = false,
)
val numHistoryGridColumnsPortrait = int(
key = "clipboard__num_history_grid_columns_portrait",
default = CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO,
)
val numHistoryGridColumnsLandscape = int(
key = "clipboard__num_history_grid_columns_landscape",
default = CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO,
)
@Composable
fun numHistoryGridColumns(): PreferenceData<Int> {
val configuration = LocalConfiguration.current
return if (configuration.isOrientationPortrait()) {
numHistoryGridColumnsPortrait
} else {
numHistoryGridColumnsLandscape
}
}
val cleanUpOld = boolean(
key = "clipboard__clean_up_old",
default = false,
)
val cleanUpAfter = int(
key = "clipboard__clean_up_after",
default = 20,
)
val autoCleanSensitive = boolean(
key = "clipboard__auto_clean_sensitive",
default = false,
)
val autoCleanSensitiveAfter = int(
key = "clipboard__auto_clean_sensitive_after",
default = 20,
)
val limitHistorySize = boolean(
key = "clipboard__limit_history_size",
default = true,
)
val maxHistorySize = int(
key = "clipboard__max_history_size",
default = 20,
)
val clearPrimaryClipDeletesLastItem = boolean(
key = "clipboard__clear_primary_clip_deletes_last_item",
default = true,
default = ClipboardSyncBehavior.NO_EVENTS,
)
val suggestionEnabled = boolean(
key = "clipboard__suggestion_enabled",
@@ -149,6 +102,63 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
key = "clipboard__suggestion_timeout",
default = 60,
)
val historyEnabled = boolean(
key = "clipboard__history_enabled",
default = false,
)
val historyNumGridColumnsPortrait = int(
key = "clipboard__history_num_grid_columns_portrait",
default = CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO,
)
val historyNumGridColumnsLandscape = int(
key = "clipboard__history_num_grid_columns_landscape",
default = CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO,
)
@Composable
fun historyNumGridColumns(): PreferenceData<Int> {
val configuration = LocalConfiguration.current
return if (configuration.isOrientationPortrait()) {
historyNumGridColumnsPortrait
} else {
historyNumGridColumnsLandscape
}
}
val historyAutoCleanOldEnabled = boolean(
key = "clipboard__history_auto_clean_old_enabled",
default = false,
)
val historyAutoCleanOldAfter = int(
key = "clipboard__history_auto_clean_old_after",
default = 20,
)
val historyAutoCleanSensitiveEnabled = boolean(
key = "clipboard__history_auto_clean_sensitive_enabled",
default = false,
)
val historyAutoCleanSensitiveAfter = int(
key = "clipboard__history_auto_clean_sensitive_after",
default = 20,
)
val historySizeLimitEnabled = boolean(
key = "clipboard__history_size_limit_enabled",
default = true,
)
val historySizeLimit = int(
key = "clipboard__history_size_limit",
default = 20,
)
val historyHideOnPaste = boolean(
key = "clipboard__history_hide_on_paste",
default = false,
)
val historyHideOnNextTextField = boolean(
key = "clipboard__history_hide_on_next_text_field",
default = true,
)
val clearPrimaryClipAffectsHistoryIfUnpinned = boolean(
key = "clipboard__clear_primary_clip_affects_history_if_unpinned",
default = true,
)
}
val correction = Correction()
@@ -882,6 +892,44 @@ abstract class FlorisPreferenceModel : PreferenceModel() {
)
}
// Migrate clipboard history pref names
// Keep migration rules until: 0.7 dev cycle
"clipboard__sync_to_floris", "clipboard__sync_to_system" -> {
entry.transform(
type = PreferenceType.string(),
rawValue = when (entry.rawValue) {
"true" -> ClipboardSyncBehavior.ALL_EVENTS
else -> ClipboardSyncBehavior.NO_EVENTS
}.name,
)
}
"clipboard__num_history_grid_columns_portrait" -> {
entry.transform(key = "clipboard__history_num_grid_columns_portrait")
}
"clipboard__num_history_grid_columns_landscape" -> {
entry.transform(key = "clipboard__history_num_grid_columns_landscape")
}
"clipboard__clean_up_old" -> {
entry.transform(key = "clipboard__history_auto_clean_old_enabled")
}
"clipboard__clean_up_after" -> {
entry.transform(key = "clipboard__history_auto_clean_old_after")
}
"clipboard__auto_clean_sensitive" -> {
entry.transform(key = "clipboard__history_auto_clean_sensitive_enabled")
}
"clipboard__auto_clean_sensitive_after" -> {
entry.transform(key = "clipboard__history_auto_clean_sensitive_after")
}
"clipboard__limit_history_size" -> {
entry.transform(key = "clipboard__history_size_limit_enabled")
}
"clipboard__max_history_size" -> {
entry.transform(key = "clipboard__history_size_limit")
}
"clipboard__clear_primary_clip_deletes_last_item" -> {
entry.transform(key = "clipboard__clear_primary_clip_affects_history_if_unpinned")
}
// Default: keep entry
else -> entry.keepAsIs()

View File

@@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.settings.theme.DisplayKbdAfterDialogs
import dev.patrickgold.florisboard.app.settings.theme.SnyggLevel
import dev.patrickgold.florisboard.ime.clipboard.ClipboardSyncBehavior
import dev.patrickgold.florisboard.ime.core.DisplayLanguageNamesIn
import dev.patrickgold.florisboard.ime.input.CapitalizationBehavior
import dev.patrickgold.florisboard.ime.input.HapticVibrationMode
@@ -104,6 +105,30 @@ private val ENUM_DISPLAY_ENTRIES = mapOf<Pair<KClass<*>, String>, @Composable ()
)
}
},
ClipboardSyncBehavior::class to DEFAULT to {
listPrefEntries {
entry(
key = ClipboardSyncBehavior.NO_EVENTS,
label = stringRes(R.string.enum__clipboard_sync_behavior__no_events),
description = stringRes(R.string.enum__clipboard_sync_behavior__no_events__description),
)
entry(
key = ClipboardSyncBehavior.ONLY_CLEAR_EVENTS,
label = stringRes(R.string.enum__clipboard_sync_behavior__only_clear_events),
description = stringRes(R.string.enum__clipboard_sync_behavior__only_clear_events__description),
)
entry(
key = ClipboardSyncBehavior.ONLY_SET_EVENTS,
label = stringRes(R.string.enum__clipboard_sync_behavior__only_set_events),
description = stringRes(R.string.enum__clipboard_sync_behavior__only_set_events__description),
)
entry(
key = ClipboardSyncBehavior.ALL_EVENTS,
label = stringRes(R.string.enum__clipboard_sync_behavior__all_events),
description = stringRes(R.string.enum__clipboard_sync_behavior__all_events__description),
)
}
},
ColorRepresentation::class to DEFAULT to {
listPrefEntries {
entry(

View File

@@ -628,7 +628,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
for (theme in editor.themes) {
put(ExtensionComponentName(extId, theme.id), theme)
}
for ((componentName, theme) in themeManager.indexedThemeConfigs.value ?: emptyMap()) {
for ((componentName, theme) in themeManager.indexedThemeConfigs.value.first) {
if (componentName.extensionId != extId) {
put(componentName, theme)
}
@@ -710,7 +710,7 @@ private fun <T : ExtensionComponent> CreateComponentScreen(
}
editor.themes.add(componentEditor)
} else {
val component = themeManager.indexedThemeConfigs.value?.get(componentName) ?: return
val component = themeManager.indexedThemeConfigs.value.first.get(componentName) ?: return
val componentEditor = (component as? ThemeExtensionComponentImpl)?.edit() ?: return
componentEditor.id = componentId
componentEditor.stylesheetPath = ""

View File

@@ -194,7 +194,8 @@ fun BackupScreen() = FlorisScreen {
}
if (backupFilesSelector.provideClipboardItems()) {
val clipboardHistory = context.clipboardManager().value.history().all
val clipboardManager by context.clipboardManager()
val clipboardHistory = clipboardManager.currentHistory.all
val clipboardFilesDir = workspace.inputDir.subDir("clipboard")
clipboardFilesDir.mkdir()
if (backupFilesSelector.clipboardTextItems) {

View File

@@ -18,10 +18,13 @@ package dev.patrickgold.florisboard.app.settings.clipboard
import androidx.compose.runtime.Composable
import dev.patrickgold.florisboard.R
import dev.patrickgold.florisboard.app.enumDisplayEntriesOf
import dev.patrickgold.florisboard.ime.clipboard.CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO
import dev.patrickgold.florisboard.ime.clipboard.ClipboardSyncBehavior
import dev.patrickgold.florisboard.lib.compose.FlorisScreen
import dev.patrickgold.jetpref.datastore.ui.DialogSliderPreference
import dev.patrickgold.jetpref.datastore.ui.ExperimentalJetPrefDatastoreUi
import dev.patrickgold.jetpref.datastore.ui.ListPreference
import dev.patrickgold.jetpref.datastore.ui.PreferenceGroup
import dev.patrickgold.jetpref.datastore.ui.SwitchPreference
import org.florisboard.lib.android.AndroidVersion
@@ -40,16 +43,16 @@ fun ClipboardScreen() = FlorisScreen {
title = stringRes(R.string.pref__clipboard__use_internal_clipboard__label),
summary = stringRes(R.string.pref__clipboard__use_internal_clipboard__summary),
)
SwitchPreference(
ListPreference(
prefs.clipboard.syncToFloris,
title = stringRes(R.string.pref__clipboard__sync_from_system_clipboard__label),
summary = stringRes(R.string.pref__clipboard__sync_from_system_clipboard__summary),
entries = enumDisplayEntriesOf(ClipboardSyncBehavior::class),
enabledIf = { prefs.clipboard.useInternalClipboard isEqualTo true },
)
SwitchPreference(
ListPreference(
prefs.clipboard.syncToSystem,
title = stringRes(R.string.pref__clipboard__sync_to_system_clipboard__label),
summary = stringRes(R.string.pref__clipboard__sync_to_system_clipboard__summary),
entries = enumDisplayEntriesOf(ClipboardSyncBehavior::class),
enabledIf = { prefs.clipboard.useInternalClipboard isEqualTo true },
)
@@ -77,8 +80,8 @@ fun ClipboardScreen() = FlorisScreen {
summary = stringRes(R.string.pref__clipboard__enable_clipboard_history__summary),
)
DialogSliderPreference(
primaryPref = prefs.clipboard.numHistoryGridColumnsPortrait,
secondaryPref = prefs.clipboard.numHistoryGridColumnsLandscape,
primaryPref = prefs.clipboard.historyNumGridColumnsPortrait,
secondaryPref = prefs.clipboard.historyNumGridColumnsLandscape,
title = stringRes(R.string.pref__clipboard__num_history_grid_columns__label),
primaryLabel = stringRes(R.string.screen_orientation__portrait),
secondaryLabel = stringRes(R.string.screen_orientation__landscape),
@@ -95,53 +98,65 @@ fun ClipboardScreen() = FlorisScreen {
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
)
SwitchPreference(
prefs.clipboard.cleanUpOld,
prefs.clipboard.historyAutoCleanOldEnabled,
title = stringRes(R.string.pref__clipboard__clean_up_old__label),
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
)
DialogSliderPreference(
prefs.clipboard.cleanUpAfter,
prefs.clipboard.historyAutoCleanOldAfter,
title = stringRes(R.string.pref__clipboard__clean_up_after__label),
valueLabel = { pluralsRes(R.plurals.unit__minutes__written, it, "v" to it) },
min = 0,
max = 120,
stepIncrement = 5,
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.cleanUpOld isEqualTo true },
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.historyAutoCleanOldEnabled isEqualTo true },
)
SwitchPreference(
prefs.clipboard.autoCleanSensitive,
prefs.clipboard.historyAutoCleanSensitiveEnabled,
title = stringRes(R.string.pref__clipboard__auto_clean_sensitive__label),
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
visibleIf = { AndroidVersion.ATLEAST_API33_T },
)
DialogSliderPreference(
prefs.clipboard.autoCleanSensitiveAfter,
prefs.clipboard.historyAutoCleanSensitiveAfter,
title = stringRes(R.string.pref__clipboard__auto_clean_sensitive_after__label),
valueLabel = { pluralsRes(R.plurals.unit__seconds__written, it, "v" to it) },
min = 0,
max = 300,
stepIncrement = 10,
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.autoCleanSensitive isEqualTo true },
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.historyAutoCleanSensitiveEnabled isEqualTo true },
visibleIf = { AndroidVersion.ATLEAST_API33_T },
)
SwitchPreference(
prefs.clipboard.limitHistorySize,
prefs.clipboard.historySizeLimitEnabled,
title = stringRes(R.string.pref__clipboard__limit_history_size__label),
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
)
DialogSliderPreference(
prefs.clipboard.maxHistorySize,
prefs.clipboard.historySizeLimit,
title = stringRes(R.string.pref__clipboard__max_history_size__label),
valueLabel = { pluralsRes(R.plurals.unit__items__written, it, "v" to it) },
min = 5,
max = 100,
stepIncrement = 5,
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.limitHistorySize isEqualTo true },
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true && prefs.clipboard.historySizeLimitEnabled isEqualTo true },
)
SwitchPreference(
prefs.clipboard.historyHideOnPaste,
title = stringRes(R.string.pref__clipboard__history_hide_on_paste__label),
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true }
)
SwitchPreference(
prefs.clipboard.clearPrimaryClipDeletesLastItem,
title = stringRes(R.string.pref__clipboard__clear_primary_clip_deletes_last_item__label),
summary = stringRes(R.string.pref__clipboard__clear_primary_clip_deletes_last_item__summary),
prefs.clipboard.historyHideOnNextTextField,
title = stringRes(R.string.pref__clipboard__history_hide_on_next_text_field__label),
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true }
)
SwitchPreference(
prefs.clipboard.clearPrimaryClipAffectsHistoryIfUnpinned,
title = stringRes(R.string.pref__clipboard__clear_primary_clip_affects_history_if_unpinned__label),
summary = stringRes(R.string.pref__clipboard__clear_primary_clip_affects_history_if_unpinned__summary),
enabledIf = { prefs.clipboard.historyEnabled isEqualTo true },
)
}

View File

@@ -60,7 +60,7 @@ fun ThemeScreen() = FlorisScreen {
@Composable
fun ThemeManager.getThemeLabel(id: ExtensionComponentName): String {
val configs by indexedThemeConfigs.collectAsState()
configs[id]?.let { return it.label }
configs.first[id]?.let { return it.label }
return id.toString()
}

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.clipboard
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
data class ClipboardHistory(val all: List<ClipboardItem>) {
companion object {
private const val RECENT_TIMESPAN_MS = 300_000 // 300 sec = 5 min
val EMPTY = ClipboardHistory(emptyList())
}
private val now = System.currentTimeMillis()
val pinned = all.filter { it.isPinned }
val unpinned = all.filter { !it.isPinned }
val recent = unpinned.filter { (now - it.creationTimestampMs) < RECENT_TIMESPAN_MS }
val other = unpinned.filter { (now - it.creationTimestampMs) >= RECENT_TIMESPAN_MS }
}

View File

@@ -63,13 +63,14 @@ import androidx.compose.material.icons.filled.TextFields
import androidx.compose.material.icons.filled.ToggleOff
import androidx.compose.material.icons.filled.ToggleOn
import androidx.compose.material.icons.filled.Videocam
import androidx.compose.material.icons.outlined.ContentPaste
import androidx.compose.material.icons.outlined.ContentPasteGo
import androidx.compose.material.icons.outlined.Email
import androidx.compose.material.icons.outlined.PushPin
import androidx.compose.material3.Icon
import androidx.compose.material3.ripple
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.mutableStateSetOf
@@ -102,7 +103,6 @@ import dev.patrickgold.florisboard.ime.smartbar.VerticalExitTransition
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.keyboardManager
import dev.patrickgold.florisboard.lib.observeAsNonNullState
import dev.patrickgold.florisboard.lib.observeAsTransformingState
import dev.patrickgold.florisboard.lib.util.NetworkUtils
import dev.patrickgold.jetpref.datastore.model.observeAsState
@@ -111,7 +111,6 @@ 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.compose.LocalLocalizedDateTimeFormatter
@@ -148,23 +147,23 @@ fun ClipboardInputLayout(
val deviceLocked = androidKeyguardManager.let { it.isDeviceLocked || it.isKeyguardLocked }
val historyEnabled by prefs.clipboard.historyEnabled.observeAsState()
val unfilteredHistory by clipboardManager.history.observeAsNonNullState()
var isFilterRowShown by remember { mutableStateOf(false) }
val activeFilterTypes = remember { mutableStateSetOf<ItemType>() }
val history = remember(unfilteredHistory, activeFilterTypes.toSet()) {
val unfilteredHistory by clipboardManager.historyFlow.collectAsState()
val filteredHistory = remember(unfilteredHistory, activeFilterTypes.toSet()) {
if (activeFilterTypes.isEmpty()) {
unfilteredHistory
} else {
unfilteredHistory.all
.filter { activeFilterTypes.contains(it.type) }
.let { ClipboardManager.ClipboardHistory(it) }
.let { ClipboardHistory(it) }
}
}
val gridState = rememberLazyStaggeredGridState()
var popupItem by remember(history) { mutableStateOf<ClipboardItem?>(null) }
var popupItem by remember(filteredHistory) { mutableStateOf<ClipboardItem?>(null) }
var showClearAllHistory by remember { mutableStateOf(false) }
fun isPopupSurfaceActive() = popupItem != null || showClearAllHistory
@@ -223,7 +222,7 @@ fun ClipboardInputLayout(
elementName = FlorisImeUi.ClipboardHeaderButton.elementName,
onClick = { showClearAllHistory = true },
modifier = sizeModifier.autoMirrorForRtl(),
enabled = !deviceLocked && historyEnabled && unfilteredHistory.all.isNotEmpty() && !isPopupSurfaceActive(),
enabled = !deviceLocked && historyEnabled && filteredHistory.all.isNotEmpty() && !isPopupSurfaceActive(),
) {
SnyggIcon(
imageVector = Icons.Default.DeleteSweep,
@@ -371,7 +370,7 @@ fun ClipboardInputLayout(
modifier = Modifier.fillMaxSize(),
) {
val historyAlpha by animateFloatAsState(targetValue = if (isPopupSurfaceActive()) 0.12f else 1f)
val staggeredGridCells by prefs.clipboard.numHistoryGridColumns()
val staggeredGridCells by prefs.clipboard.historyNumGridColumns()
.observeAsTransformingState { numGridColumns ->
if (numGridColumns == CLIPBOARD_HISTORY_NUM_GRID_COLUMNS_AUTO) {
StaggeredGridCells.Adaptive(160.dp)
@@ -468,17 +467,17 @@ fun ClipboardInputLayout(
columns = staggeredGridCells,
) {
clipboardItems(
items = history.pinned,
items = filteredHistory.pinned,
key = "pinned-header",
title = R.string.clipboard__group_pinned,
)
clipboardItems(
items = history.recent,
items = filteredHistory.recent,
key = "recent-header",
title = R.string.clipboard__group_recent,
)
clipboardItems(
items = history.other,
items = filteredHistory.other,
key = "other-header",
title = R.string.clipboard__group_other,
)
@@ -499,7 +498,9 @@ fun ClipboardInputLayout(
SnyggColumn(modifier = Modifier.weight(0.5f)) {
ClipItemView(
elementName = FlorisImeUi.ClipboardItemPopup.elementName,
modifier = Modifier.widthIn(max = ItemWidth),
modifier = Modifier
.widthIn(max = ItemWidth)
.weight(1f, fill = false),
item = popupItem!!,
contentScrollInsteadOfClip = true,
)
@@ -532,11 +533,11 @@ fun ClipboardInputLayout(
icon = Icons.Default.Delete,
text = stringRes(R.string.clip__delete_item),
) {
clipboardManager.deleteClip(popupItem!!)
clipboardManager.deleteClip(popupItem!!, onlyIfUnpinned = false)
popupItem = null
}
PopupAction(
icon = Icons.Outlined.ContentPaste,
icon = Icons.Outlined.ContentPasteGo,
text = stringRes(R.string.clip__paste_item),
) {
clipboardManager.pasteItem(popupItem!!)
@@ -567,7 +568,13 @@ fun ClipboardInputLayout(
) {
SnyggText(
elementName = FlorisImeUi.ClipboardClearAllDialogMessage.elementName,
text = stringRes(R.string.clipboard__confirm_clear_history__message),
text = stringRes(
if (isFilterRowShown) {
R.string.clipboard__confirm_clear_filtered_history__message
} else {
R.string.clipboard__confirm_clear_unfiltered_history__message
}
),
)
SnyggRow(FlorisImeUi.ClipboardClearAllDialogButtons.elementName) {
Spacer(modifier = Modifier.weight(1f))
@@ -586,10 +593,9 @@ fun ClipboardInputLayout(
elementName = FlorisImeUi.ClipboardClearAllDialogButton.elementName,
attributes = mapOf("action" to "yes"),
onClick = {
clipboardManager.clearHistory()
clipboardManager.clearExactHistory(filteredHistory.unpinned)
context.showShortToastSync(R.string.clipboard__cleared_history)
showClearAllHistory = false
isFilterRowShown = false
},
) {
SnyggText(
@@ -670,7 +676,7 @@ fun ClipboardInputLayout(
HistoryLockedView()
} else {
if (historyEnabled) {
if (history.all.isNotEmpty() || !activeFilterTypes.isEmpty()) {
if (filteredHistory.all.isNotEmpty() || !activeFilterTypes.isEmpty()) {
HistoryMainView()
} else {
HistoryEmptyView()

View File

@@ -18,8 +18,6 @@ package dev.patrickgold.florisboard.ime.clipboard
import android.content.ClipData
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.editorInstance
@@ -41,8 +39,8 @@ import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.florisboard.lib.android.AndroidClipboardManager
import org.florisboard.lib.android.AndroidClipboardManager_OnPrimaryClipChangedListener
import org.florisboard.lib.android.clearPrimaryClipAnyApi
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
@@ -62,7 +60,6 @@ class ClipboardManager(
companion object {
// 1 minute
private const val INTERVAL = 60 * 1000L
private const val RECENT_TIMESPAN_MS = 300_000 // 300 sec = 5 min
/**
* Taken from ClipboardDescription.java from the AOSP
@@ -101,8 +98,10 @@ class ClipboardManager(
private var clipHistoryDb: ClipboardHistoryDatabase? = null
private val clipHistoryDao: ClipboardHistoryDao? get() = clipHistoryDb?.clipboardItemDao()
private val _history = MutableLiveData(ClipboardHistory.Empty)
val history: LiveData<ClipboardHistory> get() = _history
private val _historyFlow = MutableStateFlow(ClipboardHistory.EMPTY)
val historyFlow = _historyFlow.asStateFlow()
val currentHistory: ClipboardHistory
get() = historyFlow.value
private val primaryClipLastFromCallbackGuard = Mutex(locked = false)
private var primaryClipLastFromCallback: ClipData? = null
@@ -119,7 +118,7 @@ class ClipboardManager(
cleanUpJob = ioScope.launch {
while (isActive) {
delay(INTERVAL)
enforceExpiryDate(history())
enforceExpiryDate(currentHistory)
}
}
}
@@ -141,20 +140,21 @@ class ClipboardManager(
val itemsSorted = items.sortedByDescending { it.creationTimestampMs }
val clipHistory = ClipboardHistory(itemsSorted)
enforceHistoryLimit(clipHistory)
_history.postValue(clipHistory)
_historyFlow.value = clipHistory
}
fun history(): ClipboardHistory = history.value!!
/**
* Sets the current primary clip without updating the internal clipboard history.
*/
fun updatePrimaryClip(item: ClipboardItem?) {
primaryClip = item
if (prefs.clipboard.useInternalClipboard.get()) {
// Purposely do not sync to system if disabled in prefs
if (prefs.clipboard.syncToSystem.get()) {
systemClipboardManager.setOrClearPrimaryClip(item?.toClipData(appContext))
val syncBehavior = prefs.clipboard.syncToSystem.get()
val clipData = item?.toClipData(appContext)
if (clipData != null && syncBehavior.shouldSyncSet) {
systemClipboardManager.setPrimaryClip(clipData)
} else if (clipData == null && syncBehavior.shouldSyncClear) {
systemClipboardManager.clearPrimaryClipAnyApi()
}
} else {
systemClipboardManager.setOrClearPrimaryClip(item?.toClipData(appContext))
@@ -165,7 +165,8 @@ class ClipboardManager(
* Called by system clipboard when the system primary clip has changed.
*/
override fun onPrimaryClipChanged() {
if (!prefs.clipboard.useInternalClipboard.get() || prefs.clipboard.syncToFloris.get()) {
val syncBehavior = prefs.clipboard.syncToFloris.get()
if (!prefs.clipboard.useInternalClipboard.get() || syncBehavior != ClipboardSyncBehavior.NO_EVENTS) {
val systemPrimaryClip = systemClipboardManager.primaryClip
ioScope.launch {
val isDuplicate: Boolean
@@ -173,7 +174,7 @@ class ClipboardManager(
val a = primaryClipLastFromCallback?.getItemAt(0)
val b = systemPrimaryClip?.getItemAt(0)
isDuplicate = when {
a === b || a == null && b == null -> true
a === b -> true
a == null || b == null -> false
else -> a.text == b.text && a.uri == b.uri
}
@@ -184,12 +185,20 @@ class ClipboardManager(
val internalPrimaryClip = primaryClip
if (systemPrimaryClip == null) {
primaryClip = null
if (syncBehavior.shouldSyncClear) {
primaryClip = null
}
return@launch
}
if (systemPrimaryClip.getItemAt(0).let { it.text == null && it.uri == null }) {
primaryClip = null
if (syncBehavior.shouldSyncClear) {
primaryClip = null
}
return@launch
}
if (!syncBehavior.shouldSyncSet) {
return@launch
}
@@ -224,7 +233,7 @@ class ClipboardManager(
*/
private fun insertOrMoveBeginning(newItem: ClipboardItem) {
if (prefs.clipboard.historyEnabled.get()) {
val historyElement = history().all.firstOrNull { it.type == ItemType.TEXT && it.text == newItem.text }
val historyElement = currentHistory.all.firstOrNull { it.type == ItemType.TEXT && it.text == newItem.text }
if (historyElement != null) {
moveToTheBeginning(
oldItem = historyElement,
@@ -241,9 +250,9 @@ class ClipboardManager(
}
private fun enforceHistoryLimit(clipHistory: ClipboardHistory) {
if (prefs.clipboard.limitHistorySize.get()) {
if (prefs.clipboard.historySizeLimitEnabled.get()) {
val nonPinnedItems = clipHistory.recent + clipHistory.other
val nToRemove = nonPinnedItems.size - prefs.clipboard.maxHistorySize.get()
val nToRemove = nonPinnedItems.size - prefs.clipboard.historySizeLimit.get()
if (nToRemove > 0) {
val itemsToRemove = nonPinnedItems.asReversed().filterIndexed { n, _ -> n < nToRemove }
ioScope.launch {
@@ -255,14 +264,14 @@ class ClipboardManager(
private fun enforceExpiryDate(clipHistory: ClipboardHistory) {
val itemsToRemove = mutableSetOf<ClipboardItem>()
if (prefs.clipboard.cleanUpOld.get()) {
if (prefs.clipboard.historyAutoCleanOldEnabled.get()) {
val nonPinnedItems = clipHistory.recent + clipHistory.other
val expiryTime = System.currentTimeMillis() - (prefs.clipboard.cleanUpAfter.get() * 60 * 1000)
val expiryTime = System.currentTimeMillis() - (prefs.clipboard.historyAutoCleanOldAfter.get() * 60 * 1000)
itemsToRemove.addAll(nonPinnedItems.filter { it.creationTimestampMs < expiryTime })
}
if (prefs.clipboard.autoCleanSensitive.get()) {
if (prefs.clipboard.historyAutoCleanSensitiveEnabled.get()) {
val sensitiveData = clipHistory.all.filter { it.isSensitive }
val expiryTime = System.currentTimeMillis() - (prefs.clipboard.autoCleanSensitiveAfter.get() * 1000)
val expiryTime = System.currentTimeMillis() - (prefs.clipboard.historyAutoCleanSensitiveAfter.get() * 1000)
itemsToRemove.addAll(sensitiveData.filter { it.creationTimestampMs < expiryTime })
}
if (itemsToRemove.isNotEmpty()) {
@@ -274,7 +283,7 @@ class ClipboardManager(
private fun moveToTheBeginning(oldItem: ClipboardItem, newItem: ClipboardItem) {
ioScope.launch {
clipHistoryDao?.delete(oldItem)
clipHistoryDao?.delete(oldItem.id)
clipHistoryDao?.insert(newItem)
}
}
@@ -286,12 +295,21 @@ class ClipboardManager(
}
}
fun clearExactHistory(items: List<ClipboardItem>) {
ioScope.launch {
for (item in items) {
item.close(appContext)
}
clipHistoryDao?.delete(items)
}
}
/**
* Clears all unpinned items from the clipboard history
*/
fun clearHistory() {
ioScope.launch {
for (item in history().all) {
for (item in currentHistory.all) {
item.close(appContext)
}
clipHistoryDao?.deleteAllUnpinned()
@@ -303,7 +321,7 @@ class ClipboardManager(
*/
fun clearFullHistory() {
ioScope.launch {
for (item in history().all) {
for (item in currentHistory.all) {
item.close(appContext)
}
clipHistoryDao?.deleteAll()
@@ -318,18 +336,22 @@ class ClipboardManager(
*/
fun restoreHistory(items: List<ClipboardItem>) {
ioScope.launch {
val currentHistory = this@ClipboardManager.history().all
val currentHistory = currentHistory.all
for (item in items) {
if (!currentHistory.map { it.copy(id = 0) }.contains(item.copy(id = 0))) {
this@ClipboardManager.insertClip(item.copy(id = 0))
insertClip(item.copy(id = 0))
}
}
}
}
fun deleteClip(item: ClipboardItem) {
fun deleteClip(item: ClipboardItem, onlyIfUnpinned: Boolean) {
ioScope.launch {
clipHistoryDao?.delete(item)
if (onlyIfUnpinned) {
clipHistoryDao?.deleteIfUnpinned(item.id)
} else {
clipHistoryDao?.delete(item.id)
}
tryOrNull {
val uri = item.uri
if (uri != null) {
@@ -382,16 +404,4 @@ class ClipboardManager(
systemClipboardManager.removePrimaryClipChangedListener(this)
cleanUpJob.cancel()
}
class ClipboardHistory(val all: List<ClipboardItem>) {
companion object {
val Empty = ClipboardHistory(emptyList())
}
private val now = System.currentTimeMillis()
val pinned = all.filter { it.isPinned }
val recent = all.filter { !it.isPinned && (now - it.creationTimestampMs < RECENT_TIMESPAN_MS) }
val other = all.filter { !it.isPinned && (now - it.creationTimestampMs >= RECENT_TIMESPAN_MS) }
}
}

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2025 The FlorisBoard Contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package dev.patrickgold.florisboard.ime.clipboard
enum class ClipboardSyncBehavior(val shouldSyncSet: Boolean, val shouldSyncClear: Boolean) {
NO_EVENTS(shouldSyncSet = false, shouldSyncClear = false),
ONLY_CLEAR_EVENTS(shouldSyncSet = false, shouldSyncClear = true),
ONLY_SET_EVENTS(shouldSyncSet = true, shouldSyncClear = false),
ALL_EVENTS(shouldSyncSet = true, shouldSyncClear = true);
}

View File

@@ -55,6 +55,7 @@ import org.florisboard.lib.android.UriSerializer
import org.florisboard.lib.android.query
import org.florisboard.lib.android.stringRes
import org.florisboard.lib.kotlin.tryOrNull
import androidx.core.net.toUri
private const val CLIPBOARD_HISTORY_TABLE = "clipboard_history"
private const val CLIPBOARD_FILES_TABLE = "clipboard_files"
@@ -89,7 +90,7 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
val uri: Uri?,
val creationTimestampMs: Long,
val isPinned: Boolean,
val mimeTypes: Array<String>,
val mimeTypes: List<String>,
@EncodeDefault
@ColumnInfo(name = "is_sensitive", defaultValue = "0")
val isSensitive: Boolean = false,
@@ -101,7 +102,7 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
/**
* So that every item doesn't have to allocate its own array.
*/
private val TEXT_PLAIN = arrayOf("text/plain")
private val TEXT_PLAIN = listOf("text/plain")
private val MEDIA_PROJECTION = arrayOf(OpenableColumns.DISPLAY_NAME)
const val FLORIS_CLIP_LABEL = "florisboard/clipboard_item"
@@ -177,7 +178,7 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
val mimeTypes = when (type) {
ItemType.TEXT -> TEXT_PLAIN
ItemType.IMAGE, ItemType.VIDEO -> {
Array(data.description.mimeTypeCount) { data.description.getMimeType(it) }
List(data.description.mimeTypeCount) { data.description.getMimeType(it) }
}
}
@@ -230,34 +231,6 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ClipboardItem
if (id != other.id) return false
if (type != other.type) return false
if (text != other.text) return false
if (uri != other.uri) return false
if (creationTimestampMs != other.creationTimestampMs) return false
if (!mimeTypes.contentEquals(other.mimeTypes)) return false
if (isSensitive != other.isSensitive) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + type.hashCode()
result = 31 * result + (text?.hashCode() ?: 0)
result = 31 * result + (uri?.hashCode() ?: 0)
result = 31 * result + creationTimestampMs.hashCode()
result = 31 * result + mimeTypes.contentHashCode()
result = 31 * result + isSensitive.hashCode()
return result
}
fun stringRepresentation(): String {
return when {
text != null -> text
@@ -270,7 +243,7 @@ data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor
class Converters {
@TypeConverter
fun uriFromString(value: String?): Uri? {
return Uri.parse(value)
return value?.toUri()
}
@TypeConverter
@@ -293,13 +266,13 @@ class Converters {
* DOES NOT USE A GENERALIZED FORMAT.
*/
@TypeConverter
fun mimeTypesToString(mimeTypes: Array<String>): String {
fun mimeTypesToString(mimeTypes: List<String>): String {
return mimeTypes.joinToString(",")
}
@TypeConverter
fun stringToMimeTypes(value: String): Array<String> {
return value.split(",").toTypedArray()
fun stringToMimeTypes(value: String): List<String> {
return value.split(",")
}
}
@@ -320,12 +293,12 @@ interface ClipboardHistoryDao {
@Update
fun update(items: List<ClipboardItem>)
@Delete
fun delete(item: ClipboardItem)
@Query("DELETE FROM $CLIPBOARD_HISTORY_TABLE WHERE ${BaseColumns._ID} = :id")
fun delete(id: Long)
@Query("DELETE FROM $CLIPBOARD_HISTORY_TABLE WHERE ${BaseColumns._ID} = :id AND not isPinned ")
fun deleteIfUnpinned(id: Long)
@Delete
fun delete(items: List<ClipboardItem>)
@@ -382,30 +355,8 @@ data class ClipboardFileInfo(
@ColumnInfo(name=OpenableColumns.DISPLAY_NAME) val displayName: String,
@ColumnInfo(name=OpenableColumns.SIZE) val size: Long,
@ColumnInfo(name=Media.ORIENTATION) val orientation: Int,
val mimeTypes: Array<String>,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as ClipboardFileInfo
if (id != other.id) return false
if (displayName != other.displayName) return false
if (size != other.size) return false
if (!mimeTypes.contentEquals(other.mimeTypes)) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + displayName.hashCode()
result = 31 * result + size.hashCode()
result = 31 * result + mimeTypes.contentHashCode()
return result
}
}
val mimeTypes: List<String>,
)
@Dao
interface ClipboardFilesDao {

View File

@@ -35,6 +35,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import org.florisboard.lib.kotlin.tryOrNull
import androidx.core.net.toUri
/**
* Allows apps to access images and videos on the clipboard.
@@ -49,8 +50,8 @@ class ClipboardMediaProvider : ContentProvider() {
companion object {
const val AUTHORITY = "${BuildConfig.APPLICATION_ID}.provider.clipboard"
val IMAGE_CLIPS_URI: Uri = Uri.parse("content://$AUTHORITY/clips/images")
val VIDEO_CLIPS_URI: Uri = Uri.parse("content://$AUTHORITY/clips/videos")
val IMAGE_CLIPS_URI: Uri = "content://$AUTHORITY/clips/images".toUri()
val VIDEO_CLIPS_URI: Uri = "content://$AUTHORITY/clips/videos".toUri()
private const val IMAGE_CLIP_ITEM = 0
private const val IMAGE_CLIPS_TABLE = 1
@@ -118,7 +119,7 @@ class ClipboardMediaProvider : ContentProvider() {
override fun getStreamTypes(uri: Uri, mimeTypeFilter: String): Array<String>? {
return when (Matcher.match(uri)) {
IMAGE_CLIP_ITEM, VIDEO_CLIP_ITEM -> {
cachedFileInfos.getOrDefault(ContentUris.parseId(uri), null)?.mimeTypes
cachedFileInfos.getOrDefault(ContentUris.parseId(uri), null)?.mimeTypes?.toTypedArray()
}
else -> null
}
@@ -152,7 +153,7 @@ class ClipboardMediaProvider : ContentProvider() {
}
val id = ClipboardFileStorage.cloneUri(context!!, mediaUri)
val size = ClipboardFileStorage.getFileForId(context!!, id).length()
val mimeTypes = values.getAsString(Columns.MimeTypes).split(",").toTypedArray()
val mimeTypes = values.getAsString(Columns.MimeTypes).split(",")
val displayName = values.getAsString(OpenableColumns.DISPLAY_NAME)
val fileInfo = ClipboardFileInfo(id, displayName, size, rotation, mimeTypes)
cachedFileInfos[id] = fileInfo

View File

@@ -26,6 +26,7 @@ import dev.patrickgold.florisboard.FlorisImeService
import dev.patrickgold.florisboard.app.FlorisPreferenceStore
import dev.patrickgold.florisboard.appContext
import dev.patrickgold.florisboard.clipboardManager
import dev.patrickgold.florisboard.ime.ImeUiMode
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
@@ -317,7 +318,7 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
if (!file.exists()) return false
val inputContentInfo = InputContentInfoCompat(
item.uri,
ClipDescription("clipboard media file", mimeTypes),
ClipDescription("clipboard media file", mimeTypes.toTypedArray()),
null,
)
val ic = currentInputConnection() ?: return false
@@ -325,6 +326,10 @@ class EditorInstance(context: Context) : AbstractEditorInstance(context) {
val flags = InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION
InputConnectionCompat.commitContent(ic, activeInfo.base, inputContentInfo, flags, null)
}
}.also {
if (prefs.clipboard.historyHideOnPaste.get()) {
keyboardManager.activeState.imeUiMode = ImeUiMode.TEXT
}
}
}

View File

@@ -19,10 +19,8 @@ package dev.patrickgold.florisboard.ime.keyboard
import android.content.Context
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowRightAlt
import androidx.compose.material.icons.automirrored.filled.Backspace
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.automirrored.filled.KeyboardBackspace
import androidx.compose.material.icons.automirrored.filled.KeyboardReturn
import androidx.compose.material.icons.automirrored.filled.Redo
import androidx.compose.material.icons.automirrored.filled.Send
@@ -32,7 +30,7 @@ import androidx.compose.material.icons.automirrored.outlined.Backspace
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.ContentCopy
import androidx.compose.material.icons.filled.ContentCut
import androidx.compose.material.icons.filled.ContentPaste
import androidx.compose.material.icons.filled.ContentPasteGo
import androidx.compose.material.icons.filled.DeleteSweep
import androidx.compose.material.icons.filled.Done
import androidx.compose.material.icons.filled.FontDownload
@@ -200,7 +198,7 @@ fun ComputingEvaluator.computeImageVector(data: KeyData): ImageVector? {
Icons.Default.ContentCut
}
KeyCode.CLIPBOARD_PASTE -> {
Icons.Default.ContentPaste
Icons.Default.ContentPasteGo
}
KeyCode.CLIPBOARD_SELECT_ALL -> {
Icons.Default.SelectAll

View File

@@ -717,8 +717,8 @@ class KeyboardManager(context: Context) : InputKeyEventReceiver {
KeyCode.CLIPBOARD_CLEAR_HISTORY -> clipboardManager.clearHistory()
KeyCode.CLIPBOARD_CLEAR_FULL_HISTORY -> clipboardManager.clearFullHistory()
KeyCode.CLIPBOARD_CLEAR_PRIMARY_CLIP -> {
if (prefs.clipboard.clearPrimaryClipDeletesLastItem.get()) {
clipboardManager.primaryClip?.let { clipboardManager.deleteClip(it) }
if (prefs.clipboard.clearPrimaryClipAffectsHistoryIfUnpinned.get()) {
clipboardManager.primaryClip?.let { clipboardManager.deleteClip(it, onlyIfUnpinned = true) }
}
clipboardManager.updatePrimaryClip(null)
appContext.showShortToastSync(R.string.clipboard__cleared_primary_clip)

View File

@@ -67,6 +67,7 @@ import org.florisboard.lib.kotlin.io.subDir
import org.florisboard.lib.kotlin.io.subFile
import org.florisboard.lib.snygg.SnyggStylesheet
import org.florisboard.lib.snygg.value.SnyggStaticColorValue
import java.util.concurrent.atomic.AtomicInteger
/**
* Core class which manages the keyboard theme. Note, that this does not affect the UI theme of the
@@ -79,8 +80,10 @@ class ThemeManager(context: Context) {
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
private val _indexedThemeConfigs = MutableStateFlow(mapOf<ExtensionComponentName, ThemeExtensionComponent>())
private val _indexedThemeConfigs = MutableStateFlow(mapOf<ExtensionComponentName, ThemeExtensionComponent>() to 0)
val indexedThemeConfigs get() = _indexedThemeConfigs.asStateFlow()
private val indexedThemeConfigVersion = AtomicInteger(0)
val previewThemeId = MutableStateFlow<ExtensionComponentName?>(null)
val previewThemeInfo = MutableStateFlow<ThemeInfo?>(null)
val configurationChangeCounter = MutableStateFlow(0)
@@ -92,13 +95,14 @@ class ThemeManager(context: Context) {
init {
extensionManager.themes.observeForever { themeExtensions ->
val version = indexedThemeConfigVersion.incrementAndGet()
_indexedThemeConfigs.value = buildMap {
for (themeExtension in themeExtensions) {
for (themeComponent in themeExtension.themes) {
put(ExtensionComponentName(themeExtension.meta.id, themeComponent.id), themeComponent)
}
}
}
} to version
}
indexedThemeConfigs.collectIn(scope) {
updateActiveTheme { cachedThemeInfos.clear() }

View File

@@ -635,7 +635,8 @@
<string name="clipboard__cleared_primary_clip">Текущото съдържание на междинната памет е изчистено</string>
<string name="clipboard__cleared_history">Историята е изчистена</string>
<string name="clipboard__cleared_full_history">Цялата история е изчистена</string>
<string name="clipboard__confirm_clear_history__message">Желаете ли историята на междинната памет да бъде изчистена? Всички елементи, с изключение на закачените ще бъдат премахнати без значение на използвания филтър.</string>
<string name="clipboard__confirm_clear_unfiltered_history__message">Потвърждавате ли изчистване на междинната памет? Всички филтрирани елементи без закачените ще бъдат премахнати.</string>
<string name="clipboard__confirm_clear_filtered_history__message">Потвърждавате ли изчистване на междинната памет? Всички елементи без закачените ще бъдат премахнати.</string>
<string name="settings__clipboard__title">Междинна памет</string>
<string name="pref__clipboard__use_internal_clipboard__label">Междинна памет на приложението</string>
<string name="pref__clipboard__use_internal_clipboard__summary">Използва се междинната памет на приложението вместо системната</string>

View File

@@ -637,7 +637,8 @@
<string name="clipboard__cleared_primary_clip">Primární zkopírovaná položka vymazána</string>
<string name="clipboard__cleared_history">Historie vymazána</string>
<string name="clipboard__cleared_full_history">Celá historie vymazána</string>
<string name="clipboard__confirm_clear_history__message">Opravdu chcete vymazat historii schránky? Smažete tím všechny položky kromě připnutých, nezávisle na aktivních filtrech.</string>
<string name="clipboard__confirm_clear_unfiltered_history__message">Opravdu chcete vymazat celou vaší historii schránky? Toto ovlivní všechny položky, kromě připnutých.</string>
<string name="clipboard__confirm_clear_filtered_history__message">Opravdu chcete vymazat vaší historii schránky? Toto ovlivní všechny aktuálně vyfiltrované položky, kromě připnutých.</string>
<string name="settings__clipboard__title">Schránka</string>
<string name="pref__clipboard__use_internal_clipboard__label">Použít interní schránku</string>
<string name="pref__clipboard__use_internal_clipboard__summary">Použít interní schránku místo systémové schránky</string>

View File

@@ -631,7 +631,8 @@
<string name="clipboard__cleared_primary_clip">Clip principal borrado</string>
<string name="clipboard__cleared_history">Historial borrado</string>
<string name="clipboard__cleared_full_history">Historial completo borrado</string>
<string name="clipboard__confirm_clear_history__message">¿Estás seguro de que quieres limpiar el historial del portapapeles? Esto limpiará todos los ítems excepto los fijados, sin tener en cuenta los filtros activos.</string>
<string name="clipboard__confirm_clear_unfiltered_history__message">¿Estás seguro de que quieres borrar todo el historial del portapapeles? Esto afectará a todos los elementos, excepto los fijados.</string>
<string name="clipboard__confirm_clear_filtered_history__message">¿Estás seguro de que quieres borrar el historial del portapapeles? Esto afectará a todos los elementos filtrados actualmente, excepto los fijados.</string>
<string name="settings__clipboard__title">Portapapeles</string>
<string name="pref__clipboard__use_internal_clipboard__label">Usar portapapeles interno</string>
<string name="pref__clipboard__use_internal_clipboard__summary">Usar portapapeles interno en vez del portapapeles del sistema</string>

View File

@@ -633,7 +633,6 @@
<string name="clipboard__cleared_primary_clip">Elsődleges vágólap törölve</string>
<string name="clipboard__cleared_history">Előzmények törölve</string>
<string name="clipboard__cleared_full_history">Az összes előzmény törölve</string>
<string name="clipboard__confirm_clear_history__message">Biztosan törölni szeretné a vágólap előzményeit? Ez az összes elemet törli, kivéve a kitűzötteket, függetlenül az aktív szűrőktől.</string>
<string name="settings__clipboard__title">Vágólap</string>
<string name="pref__clipboard__use_internal_clipboard__label">Belső vágólap használata</string>
<string name="pref__clipboard__use_internal_clipboard__summary">Belső vágólap használata a rendszer vágólapja helyett</string>

View File

@@ -632,7 +632,6 @@ Klik di sini untuk menyelesaikan masalah ini.</string>
<string name="clipboard__cleared_primary_clip">Klip utama dibersihkan</string>
<string name="clipboard__cleared_history">Riwayat dibersihkan</string>
<string name="clipboard__cleared_full_history">Seluruh riwayat dibersihkan</string>
<string name="clipboard__confirm_clear_history__message">Apakah Anda yakin ingin menghapus riwayat papan klip Anda? Ini akan menghapus semua item kecuali yang telah disematkan, terlepas dari filter yang aktif.</string>
<string name="settings__clipboard__title">Papan klip</string>
<string name="pref__clipboard__use_internal_clipboard__label">Gunakan papan klip internal</string>
<string name="pref__clipboard__use_internal_clipboard__summary">Gunakan papan klip internal daripada papan klip sistem</string>

View File

@@ -153,6 +153,7 @@
<string name="pref__theme__sunset_time__label" comment="Label of the sunset time preference">Ora del tramonto</string>
<string name="pref__theme__day" comment="Label of the day group (day means light theme)">Tema giorno</string>
<string name="pref__theme__night" comment="Label of the night group (night means dark theme)">Tema scuro</string>
<string name="pref__theme__theme_accent_color__label" comment="Label of accent color preference in Theme"> Colore dell\'accentMateria</string>
<string name="settings__theme_manager__title_manage" comment="Title of the theme manager screen for managing installed and custom themes">Gestisci temi installati</string>
<string name="pref__theme__source_assets" comment="Label for the theme source field">Risorse dell\'app FlorisBoard</string>
<string name="pref__theme__source_internal" comment="Label for the theme source field">Memoria interna</string>
@@ -531,7 +532,7 @@
<string name="clipboard__cleared_primary_clip">Clip principale cancellata</string>
<string name="clipboard__cleared_history">Cronologia cancellata</string>
<string name="clipboard__cleared_full_history">Tutta la cronologia cancellata</string>
<string name="clipboard__confirm_clear_history__message">Sei sicuro di voler cancellare la cronologia degli appunti? Questa azione cancellerà tutti gli appunti tranne quelli spuntati, indipendentemente dai filtri attivi.</string>
<string name="clipboard__confirm_clear_unfiltered_history__message">Vuoi cancellare lo storico della clipboard? Questa azione cancellerà tutto lo storico tranne i messaggi con spunta.</string>
<string name="settings__clipboard__title">Appunti</string>
<string name="pref__clipboard__use_internal_clipboard__label">Usa gli appunti integrati</string>
<string name="pref__clipboard__use_internal_clipboard__summary">Usa gli appunti integrati invece degli appunti di sistema</string>

View File

@@ -603,7 +603,8 @@
<string name="clipboard__cleared_primary_clip">Galvenais vienums notīrīts</string>
<string name="clipboard__cleared_history">Vēsture notīrīta</string>
<string name="clipboard__cleared_full_history">Visa vēsture notīrīta</string>
<string name="clipboard__confirm_clear_history__message">Vai tiešām iztīrīt starpliktuves vēsturi? Tas notīrīs visus vienumus (izņemot piespraustos) neatkarīgi no spēkā esošajiem atlasītājiem.</string>
<string name="clipboard__confirm_clear_unfiltered_history__message">Vai tiešām iztīrīt visu starpliktuves vēseturi? Tas ietekmēs visus vienums, izņemot piespraustos.</string>
<string name="clipboard__confirm_clear_filtered_history__message">Vai tiešām iztīrīt starpliktuves vēseturi? Tas ietekmēs visus pašreiz atsijātos vienumus, izņemot piespraustos.</string>
<string name="settings__clipboard__title">Starpliktuve</string>
<string name="pref__clipboard__use_internal_clipboard__label">Izmantot iekšējo starpliktuvi</string>
<string name="pref__clipboard__use_internal_clipboard__summary">Izmantot iekšējo, nevis sistēmas starpliktuvi</string>

View File

@@ -639,7 +639,8 @@
<string name="clipboard__cleared_primary_clip">Wyczyść główny schowek</string>
<string name="clipboard__cleared_history">Historia została usunięta</string>
<string name="clipboard__cleared_full_history">Cała historia została usunięta</string>
<string name="clipboard__confirm_clear_history__message">Czy na pewno chcesz wyczyścić historię schowka? Spowoduje to wyczyszczenie wszystkich elementów z wyjątkiem przypiętych, niezależnie od aktywnych filtrów.</string>
<string name="clipboard__confirm_clear_unfiltered_history__message">Czy na pewno chcesz wyczyścić całą historię schowka? Spowoduje to usunięcie wszystkich elementów z wyjątkiem przypiętych.</string>
<string name="clipboard__confirm_clear_filtered_history__message">Czy na pewno chcesz wyczyścić historię schowka? Spowoduje to usunięcie wszystkich obecnie filtrowanych elementów, z wyjątkiem przypiętych.</string>
<string name="settings__clipboard__title">Schowek</string>
<string name="pref__clipboard__use_internal_clipboard__label">Użyj wewnętrznego schowka</string>
<string name="pref__clipboard__use_internal_clipboard__summary">Użyj wewnętrznego schowka zamiast systemowego schowka</string>

View File

@@ -643,6 +643,8 @@
<string name="ext__editor__metadata__message_invalid">Неправильные метаданные для этого расширения, проверьте редактор метаданных для получения подробной информации!</string>
<string name="ext__editor__dependencies__title">Управление зависимостями</string>
<string name="ext__editor__files__title">Управление файлами архивов</string>
<string name="ext__editor__files__type_fonts">Шрифты</string>
<string name="ext__editor__files__type_images">Изображения</string>
<string name="ext__editor__create_component__title">Создать компонент</string>
<string name="ext__editor__create_component__title_theme">Создать тему</string>
<string name="ext__editor__create_component__from_empty">Пустой</string>

View File

@@ -636,7 +636,8 @@
<string name="clipboard__cleared_primary_clip">Birincil klip temizlendi</string>
<string name="clipboard__cleared_history">Geçmiş temizlendi</string>
<string name="clipboard__cleared_full_history">Tüm geçmiş temizlendi</string>
<string name="clipboard__confirm_clear_history__message">Pano geçmişinizi temizlemek istediğinizden emin misiniz? Bu, aktif filtrelerden bağımsız olarak sabitlenmiş ögeler dışındaki tüm ögeleri temizleyecektir.</string>
<string name="clipboard__confirm_clear_unfiltered_history__message">Tüm pano geçmişinizi temizlemek istediğinizden emin misiniz? Bu işlem, sabitlenmiş olanlar hariç tüm öğeleri etkileyecektir.</string>
<string name="clipboard__confirm_clear_filtered_history__message">Pano geçmişinizi temizlemek istediğinizden emin misiniz? Bu işlem, sabitlenmiş olanlar hariç, şu anda filtrelenmiş olan tüm öğeleri etkileyecektir.</string>
<string name="settings__clipboard__title">Pano</string>
<string name="pref__clipboard__use_internal_clipboard__label">Dahili panoyu kullan</string>
<string name="pref__clipboard__use_internal_clipboard__summary">Sistem panosu yerine dahili bir pano kullan</string>

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<!-- One-handed strings -->
<!-- Media strings -->
<!-- Emoji strings -->
<!-- Quick action strings -->
<!-- Incognito mode strings -->
<!-- Settings UI strings -->
<!-- Smartbar strings -->
<!-- Typing strings -->
<!-- About UI strings -->
<!-- Setup UI strings -->
<!-- Physical keyboard -->
<!-- Back up & Restore -->
<!-- Crash Dialog strings -->
<!-- Clipboard strings -->
<!-- Devtools strings -->
<!-- Extension strings -->
<!-- Action strings -->
<!-- Error strings (generic) -->
<!-- General strings -->
<!-- Screen orientation strings -->
<!-- State strings -->
<!-- Enum label and description strings -->
<!-- Unit strings (symbols) -->
<!-- Unit strings (written words) -->
</resources>

View File

@@ -695,14 +695,13 @@
<string name="clipboard__cleared_primary_clip">Cleared primary clip</string>
<string name="clipboard__cleared_history">Cleared history</string>
<string name="clipboard__cleared_full_history">Cleared full history</string>
<string name="clipboard__confirm_clear_history__message">Are you sure you want to clear your clipboard history? This will clear all items except pinned ones, regardless of active filters.</string>
<string name="clipboard__confirm_clear_unfiltered_history__message">Are you sure you want to clear your full clipboard history? This will affect all items, except pinned ones.</string>
<string name="clipboard__confirm_clear_filtered_history__message">Are you sure you want to clear your clipboard history? This will affect all currently filtered items, except pinned ones.</string>
<string name="settings__clipboard__title">Clipboard</string>
<string name="pref__clipboard__use_internal_clipboard__label">Use internal clipboard</string>
<string name="pref__clipboard__use_internal_clipboard__summary">Use an internal clipboard instead of the system clipboard</string>
<string name="pref__clipboard__sync_from_system_clipboard__label">Sync from system clipboard</string>
<string name="pref__clipboard__sync_from_system_clipboard__summary">System clipboard updates also update Floris clipboard</string>
<string name="pref__clipboard__sync_to_system_clipboard__label">Sync to system clipboard</string>
<string name="pref__clipboard__sync_to_system_clipboard__summary">Floris clipboard updates also update system clipboard</string>
<string name="pref__clipboard__group_clipboard_suggestion__label">Clipboard suggestions</string>
<string name="pref__clipboard__suggestion_enabled__label" comment="Preference title">Clipboard content suggestions</string>
<string name="pref__clipboard__suggestion_enabled__summary" comment="Preference summary">Suggest previously copied clipboard content</string>
@@ -718,8 +717,10 @@
<string name="pref__clipboard__auto_clean_sensitive_after__label">Auto clean sensitive items after</string>
<string name="pref__clipboard__limit_history_size__label">Limit history size</string>
<string name="pref__clipboard__max_history_size__label">Max history size</string>
<string name="pref__clipboard__clear_primary_clip_deletes_last_item__label">Clear primary clip affects history</string>
<string name="pref__clipboard__clear_primary_clip_deletes_last_item__summary">Clearing the primary clip also deletes the latest history entry</string>
<string name="pref__clipboard__history_hide_on_paste__label">Hide history on paste</string>
<string name="pref__clipboard__history_hide_on_next_text_field__label">Hide history on next text field</string>
<string name="pref__clipboard__clear_primary_clip_affects_history_if_unpinned__label">Clear primary clip affects history</string>
<string name="pref__clipboard__clear_primary_clip_affects_history_if_unpinned__summary">Clearing the primary clip also removes it from the history, if not pinned</string>
<string name="send_to_clipboard__unknown_error">An unknown error occurred. Please try again!</string>
<string name="send_to_clipboard__type_not_supported_error">This media type is not supported.</string>
<string name="send_to_clipboard__description__copied_image_to_clipboard">Copied below image to clipboard.</string>
@@ -927,6 +928,15 @@
<string name="enum__capitalization_behavior__capslock_by_double_tap" comment="Enum value label">Enable Capslock by double tapping shift</string>
<string name="enum__capitalization_behavior__capslock_by_cycle" comment="Enum value label">Switch to the next capitalization step each time the shift key is pressed</string>
<string name="enum__clipboard_sync_behavior__no_events" comment="Enum value label">No events</string>
<string name="enum__clipboard_sync_behavior__no_events__description" comment="Enum value description">No events are synced in this direction. In this mode, sync excludes text content.</string>
<string name="enum__clipboard_sync_behavior__only_clear_events" comment="Enum value label">Only clear events</string>
<string name="enum__clipboard_sync_behavior__only_clear_events__description" comment="Enum value description">Only events clearing clip data are synced in this direction. In this mode, sync excludes text content.</string>
<string name="enum__clipboard_sync_behavior__only_set_events" comment="Enum value label">Only set events</string>
<string name="enum__clipboard_sync_behavior__only_set_events__description" comment="Enum value description">Only events setting clip data are synced in this direction. In this mode, sync includes text content.</string>
<string name="enum__clipboard_sync_behavior__all_events" comment="Enum value label">All events</string>
<string name="enum__clipboard_sync_behavior__all_events__description" comment="Enum value description">Both events setting and clearing clip data are synced in this direction. In this mode, sync includes text content.</string>
<string name="enum__color_representation__hex" comment="Enum value label">Hexadecimal</string>
<string name="enum__color_representation__rgb" comment="Enum value label">Red Green Blue</string>
<string name="enum__color_representation__hsv" comment="Enum value label">Hue Saturation Value</string>

View File

@@ -1,5 +1,5 @@
<p><i>FlorisBoard</i> is an open-source keyboard aimed at providing you with an easy way to type while respecting your privacy.</p>
<p><b>Note:</b> This project is currently in early-beta stage. If you want to see a feature being implemented or want to report a bug, please visit this project's repository (linked in the end of the description) on GitHub and file an issue. This helps making FlorisBoard even better! Thank you!</p>
<p><b>Note:</b> This project is currently in beta stage. If you want to see a feature being implemented or want to report a bug, please visit this project's repository (linked in the end of the description) on GitHub and file an issue. This helps making FlorisBoard even better! Thank you!</p>
<p><b>Currently implemented and fully working features:</b></p>
<ul>
<li>Huge variety of Latin keyboard layouts</li>

View File

@@ -1 +1 @@
An open-source keyboard which respects your privacy. Currently in early-beta.
An open-source keyboard which respects your privacy. Currently in beta.

View File

@@ -1,5 +1,5 @@
<p><i>FlorisBoard</i> is an open-source keyboard aimed at providing you with an easy way to type while respecting your privacy.</p>
<p><b>Note:</b> This project is currently in early-beta stage. If you want to see a feature being implemented or want to report a bug, please visit this project's repository (linked in the end of the description) on GitHub and file an issue. This helps making FlorisBoard even better! Thank you!</p>
<p><b>Note:</b> This project is currently in beta stage. If you want to see a feature being implemented or want to report a bug, please visit this project's repository (linked in the end of the description) on GitHub and file an issue. This helps making FlorisBoard even better! Thank you!</p>
<p><b>Currently implemented and fully working features:</b></p>
<ul>
<li>Huge variety of Latin keyboard layouts</li>

View File

@@ -11,5 +11,5 @@ projectMinSdk=26
projectTargetSdk=35
projectCompileSdk=35
projectVersionCode=115
projectVersionName=0.5.0
projectVersionCode=114
projectVersionName=0.5.0-rc02