mirror of
https://gitlab.futo.org/keyboard/latinime.git
synced 2024-09-28 14:54:30 +01:00
Add text clipboard history
This commit is contained in:
parent
b935db017c
commit
657203e733
@ -5,6 +5,7 @@
|
||||
<string name="theme_switcher_action_title">Theme Switcher</string>
|
||||
<string name="emoji_action_title">Emojis</string>
|
||||
<string name="clipboard_action_title">Paste from Clipboard</string>
|
||||
<string name="clipboard_manager_action_title">Clipboard Manager</string>
|
||||
<string name="undo_action_title">Undo</string>
|
||||
<string name="redo_action_title">Redo</string>
|
||||
<string name="text_edit_action_title">Text Editor</string>
|
||||
|
@ -317,6 +317,8 @@ class LatinIME : InputMethodService(), LifecycleOwner, ViewModelStoreOwner, Save
|
||||
CrashLoggingApplication.logPreferences(it)
|
||||
}
|
||||
}
|
||||
|
||||
uixManager.onCreate()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.futo.inputmethod.latin.uix
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.view.View
|
||||
import android.view.inputmethod.InputConnection
|
||||
import androidx.annotation.DrawableRes
|
||||
@ -25,6 +26,7 @@ interface KeyboardManagerForAction {
|
||||
fun createInputTransaction(applySpaceIfNeeded: Boolean): ActionInputTransaction
|
||||
|
||||
fun typeText(v: String)
|
||||
fun typeUri(uri: Uri, mimeTypes: List<String>): Boolean
|
||||
fun backspace(amount: Int)
|
||||
|
||||
fun closeActionWindow()
|
||||
@ -68,6 +70,11 @@ interface PersistentActionState {
|
||||
suspend fun cleanUp()
|
||||
}
|
||||
|
||||
enum class PersistentStateInitialization {
|
||||
OnActionTrigger,
|
||||
OnKeyboardLoad
|
||||
}
|
||||
|
||||
data class Action(
|
||||
@DrawableRes val icon: Int,
|
||||
@StringRes val name: Int,
|
||||
@ -77,4 +84,5 @@ data class Action(
|
||||
val windowImpl: ((KeyboardManagerForAction, PersistentActionState?) -> ActionWindow)?,
|
||||
val simplePressImpl: ((KeyboardManagerForAction, PersistentActionState?) -> Unit)?,
|
||||
val persistentState: ((KeyboardManagerForAction) -> PersistentActionState)? = null,
|
||||
val persistentStateInitialization: PersistentStateInitialization = PersistentStateInitialization.OnActionTrigger
|
||||
)
|
||||
|
@ -1,15 +1,19 @@
|
||||
package org.futo.inputmethod.latin.uix
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ClipDescription
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.VibrationEffect
|
||||
import android.os.Vibrator
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.inputmethod.InlineSuggestionsResponse
|
||||
import android.view.inputmethod.InputConnection
|
||||
import android.view.inputmethod.InputContentInfo
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.fadeIn
|
||||
@ -60,6 +64,7 @@ import org.futo.inputmethod.latin.common.Constants
|
||||
import org.futo.inputmethod.latin.inputlogic.InputLogic
|
||||
import org.futo.inputmethod.latin.suggestions.SuggestionStripView
|
||||
import org.futo.inputmethod.latin.uix.actions.ActionRegistry
|
||||
import org.futo.inputmethod.latin.uix.actions.AllActions
|
||||
import org.futo.inputmethod.latin.uix.actions.EmojiAction
|
||||
import org.futo.inputmethod.latin.uix.settings.SettingsActivity
|
||||
import org.futo.inputmethod.latin.uix.theme.ThemeOption
|
||||
@ -142,6 +147,26 @@ class UixActionKeyboardManager(val uixManager: UixManager, val latinIME: LatinIM
|
||||
latinIME.latinIMELegacy.onTextInput(v)
|
||||
}
|
||||
|
||||
override fun typeUri(uri: Uri, mimeTypes: List<String>): Boolean {
|
||||
if(mimeTypes.isEmpty()) {
|
||||
Log.w("UixManager", "mimeTypes is empty")
|
||||
return false
|
||||
}
|
||||
|
||||
val description = ClipDescription("Pasted image", mimeTypes.toTypedArray())
|
||||
|
||||
val info = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
InputContentInfo(uri, description, null)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
|
||||
return latinIME.currentInputConnection?.commitContent(info, InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION, null) ?: run {
|
||||
Log.w("UixManager", "Current input connection is null")
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
override fun backspace(amount: Int) {
|
||||
latinIME.latinIMELegacy.onCodeInput(
|
||||
Constants.CODE_DELETE,
|
||||
@ -645,4 +670,12 @@ class UixManager(private val latinIME: LatinIME) {
|
||||
v!!.vibrate(50)
|
||||
}
|
||||
}
|
||||
|
||||
fun onCreate() {
|
||||
AllActions.forEach { action ->
|
||||
if(action.persistentStateInitialization == PersistentStateInitialization.OnKeyboardLoad) {
|
||||
persistentStates[action] = action.persistentState?.let { it(keyboardManagerForAction) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,384 @@
|
||||
package org.futo.inputmethod.latin.uix.actions
|
||||
|
||||
import android.content.ClipDescription
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateListOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.lifecycle.LifecycleCoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.futo.inputmethod.latin.R
|
||||
import org.futo.inputmethod.latin.uix.Action
|
||||
import org.futo.inputmethod.latin.uix.ActionWindow
|
||||
import org.futo.inputmethod.latin.uix.PersistentActionState
|
||||
import org.futo.inputmethod.latin.uix.PersistentStateInitialization
|
||||
import org.futo.inputmethod.latin.uix.SettingsKey
|
||||
import org.futo.inputmethod.latin.uix.getSettingBlocking
|
||||
import org.futo.inputmethod.latin.uix.settings.ScrollableList
|
||||
import org.futo.inputmethod.latin.uix.settings.pages.ParagraphText
|
||||
import org.futo.inputmethod.latin.uix.settings.pages.PaymentSurface
|
||||
import org.futo.inputmethod.latin.uix.settings.useDataStore
|
||||
import org.futo.inputmethod.latin.uix.theme.Typography
|
||||
import java.io.File
|
||||
|
||||
val ClipboardHistoryEnabled = SettingsKey(
|
||||
booleanPreferencesKey("enableClipboardHistory"),
|
||||
false
|
||||
)
|
||||
|
||||
object UriSerializer : KSerializer<Uri> {
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Uri", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Uri) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Uri {
|
||||
return Uri.parse(decoder.decodeString())
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class ClipboardEntry(
|
||||
val timestamp: Long,
|
||||
val pinned: Boolean,
|
||||
|
||||
val text: String?,
|
||||
|
||||
@Serializable(with = UriSerializer::class)
|
||||
val uri: Uri?,
|
||||
val mimeTypes: List<String>
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ClipboardEntryView(clipboardEntry: ClipboardEntry, onPaste: (ClipboardEntry) -> Unit, onRemove: (ClipboardEntry) -> Unit, onPin: (ClipboardEntry) -> Unit) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant,
|
||||
border = BorderStroke(2.dp, MaterialTheme.colorScheme.outlineVariant),
|
||||
modifier = Modifier.padding(2.dp),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
onClick = { onPaste(clipboardEntry) }
|
||||
) {
|
||||
Column {
|
||||
Row(modifier = Modifier.padding(0.dp)) {
|
||||
IconButton(onClick = {
|
||||
onPin(clipboardEntry)
|
||||
}, modifier = Modifier.size(32.dp)) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.unlock), contentDescription = "Pin",
|
||||
tint = if (clipboardEntry.pinned) {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant
|
||||
} else {
|
||||
MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.25f)
|
||||
}, modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1.0f))
|
||||
|
||||
IconButton(onClick = {
|
||||
onRemove(clipboardEntry)
|
||||
}, modifier = Modifier.size(32.dp)) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.close),
|
||||
contentDescription = "Close",
|
||||
tint = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Text(clipboardEntry.text ?: "", modifier = Modifier.padding(8.dp, 2.dp), style = Typography.bodySmall)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun ClipboardEntryViewPreview() {
|
||||
val sampleText = listOf("This is an entry", "Copying text a lot", "hunter2", "https://www.example.com/forum/viewpost/1234573193.html?parameter=1234")
|
||||
LazyVerticalStaggeredGrid(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
columns = StaggeredGridCells.Adaptive(160.dp),
|
||||
verticalItemSpacing = 4.dp,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
items(sampleText.size) {
|
||||
ClipboardEntryView(clipboardEntry = ClipboardEntry(0L, false, sampleText[it], null, listOf()), onPin = {}, onPaste = {}, onRemove = {})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ClipboardHistoryManager(val context: Context, val coroutineScope: LifecycleCoroutineScope) : PersistentActionState {
|
||||
private val clipboardManager = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
val clipboardHistory = mutableStateListOf(ClipboardEntry(
|
||||
timestamp = 0L,
|
||||
pinned = true,
|
||||
text = "Clipboard entries will appear here",
|
||||
uri = null,
|
||||
mimeTypes = listOf()
|
||||
))
|
||||
|
||||
init {
|
||||
coroutineScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
loadClipboard()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
clipboardManager.addPrimaryClipChangedListener {
|
||||
if(!context.getSettingBlocking(ClipboardHistoryEnabled)) return@addPrimaryClipChangedListener
|
||||
|
||||
val clip = clipboardManager.primaryClip
|
||||
|
||||
val text = clip?.getItemAt(0)?.coerceToText(context)?.toString()
|
||||
val uri = clip?.getItemAt(0)?.uri
|
||||
|
||||
val timestamp = clip?.description?.timestamp ?: System.currentTimeMillis()
|
||||
|
||||
val mimeTypes = List(clip?.description?.mimeTypeCount ?: 0) {
|
||||
clip?.description?.getMimeType(it)
|
||||
}.filterNotNull()
|
||||
|
||||
val isSensitive = clip?.description?.extras?.getBoolean(
|
||||
ClipDescription.EXTRA_IS_SENSITIVE, false) ?: false
|
||||
|
||||
// TODO: Support images and other non-text media
|
||||
if (text != null && uri == null && !isSensitive) {
|
||||
val isAlreadyPinned = clipboardHistory.firstOrNull {
|
||||
((it.text != null && it.text == text) || (it.uri != null && it.uri == uri)) && it.pinned
|
||||
}?.pinned ?: false
|
||||
|
||||
clipboardHistory.removeAll {
|
||||
(it.text != null && it.text == text) || (it.uri != null && it.uri == uri)
|
||||
}
|
||||
|
||||
val newEntry = ClipboardEntry(
|
||||
timestamp = timestamp,
|
||||
pinned = isAlreadyPinned,
|
||||
text = text,
|
||||
uri = uri,
|
||||
mimeTypes = mimeTypes
|
||||
)
|
||||
clipboardHistory.add(newEntry)
|
||||
|
||||
saveClipboard()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun pruneOldItems() {
|
||||
val maxDays = 3L
|
||||
val minimumTimestamp = System.currentTimeMillis() - (maxDays * 24L * 60L * 60L * 1000L)
|
||||
clipboardHistory.removeAll {
|
||||
(!it.pinned) && (it.timestamp < minimumTimestamp)
|
||||
}
|
||||
|
||||
|
||||
val maxItems = 25
|
||||
val numUnpinnedItems = clipboardHistory.filter { !it.pinned }.size
|
||||
|
||||
val numItemsToRemove = numUnpinnedItems - maxItems
|
||||
if(numItemsToRemove > 0) {
|
||||
for(i in 0 until numItemsToRemove) {
|
||||
val idx = clipboardHistory.indexOfFirst { !it.pinned }
|
||||
if(idx == -1) break
|
||||
clipboardHistory.removeAt(idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveClipboard() {
|
||||
coroutineScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
pruneOldItems()
|
||||
|
||||
val json = Json.encodeToString(clipboardHistory.toList())
|
||||
|
||||
val file = File(context.filesDir, "clipboard.json")
|
||||
file.writeText(json)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadClipboard() {
|
||||
try {
|
||||
val file = File(context.filesDir, "clipboard.json")
|
||||
|
||||
if(!context.getSettingBlocking(ClipboardHistoryEnabled)) {
|
||||
file.delete()
|
||||
}else if (file.exists()) {
|
||||
val reader = file.bufferedReader()
|
||||
val inputString = reader.use { it.readText() }
|
||||
|
||||
val data = Json.decodeFromString<List<ClipboardEntry>>(inputString)
|
||||
|
||||
clipboardHistory.clear()
|
||||
clipboardHistory.addAll(data)
|
||||
pruneOldItems()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
fun onPaste(item: ClipboardEntry) {
|
||||
clipboardHistory.remove(item)
|
||||
|
||||
clipboardHistory.add(
|
||||
ClipboardEntry(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
pinned = item.pinned,
|
||||
text = item.text,
|
||||
uri = item.uri,
|
||||
mimeTypes = item.mimeTypes
|
||||
)
|
||||
)
|
||||
|
||||
saveClipboard()
|
||||
}
|
||||
|
||||
fun onPin(item: ClipboardEntry) {
|
||||
clipboardHistory.remove(item)
|
||||
|
||||
clipboardHistory.add(
|
||||
ClipboardEntry(
|
||||
timestamp = System.currentTimeMillis(),
|
||||
pinned = !item.pinned,
|
||||
text = item.text,
|
||||
uri = item.uri,
|
||||
mimeTypes = item.mimeTypes
|
||||
)
|
||||
)
|
||||
|
||||
saveClipboard()
|
||||
}
|
||||
|
||||
fun onRemove(item: ClipboardEntry) {
|
||||
clipboardHistory.remove(item)
|
||||
saveClipboard()
|
||||
}
|
||||
|
||||
override suspend fun cleanUp() {
|
||||
saveClipboard()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
val ClipboardHistoryAction = Action(
|
||||
icon = R.drawable.clipboard,
|
||||
name = R.string.clipboard_manager_action_title,
|
||||
simplePressImpl = null,
|
||||
canShowKeyboard = true,
|
||||
persistentState = { manager ->
|
||||
ClipboardHistoryManager(manager.getContext(), manager.getLifecycleScope())
|
||||
},
|
||||
persistentStateInitialization = PersistentStateInitialization.OnKeyboardLoad,
|
||||
windowImpl = { manager, persistent ->
|
||||
val clipboardHistoryManager = persistent as ClipboardHistoryManager
|
||||
|
||||
clipboardHistoryManager.pruneOldItems()
|
||||
object : ActionWindow {
|
||||
@Composable
|
||||
override fun windowName(): String {
|
||||
return stringResource(R.string.clipboard_manager_action_title)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun WindowContents(keyboardShown: Boolean) {
|
||||
val clipboardHistory = useDataStore(ClipboardHistoryEnabled, blocking = true)
|
||||
if(!clipboardHistory.value) {
|
||||
ScrollableList {
|
||||
PaymentSurface(isPrimary = true, title = "Clipboard History Inactive") {
|
||||
ParagraphText("Clipboard history is not enabled. To save clipboard items, you can enable clipboard history. This will keep up to 25 items for 3 days unless pinned. Passwords and other items marked sensitive are excluded from history.")
|
||||
Button(onClick = {
|
||||
clipboardHistory.setValue(true)
|
||||
}, modifier = Modifier.padding(8.dp).fillMaxWidth()) {
|
||||
Text("Enable Clipboard History")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LazyVerticalStaggeredGrid(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
columns = StaggeredGridCells.Adaptive(180.dp),
|
||||
verticalItemSpacing = 4.dp,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
items(clipboardHistoryManager.clipboardHistory.size) { r_i ->
|
||||
val i = clipboardHistoryManager.clipboardHistory.size - r_i - 1
|
||||
val entry = clipboardHistoryManager.clipboardHistory[i]
|
||||
ClipboardEntryView(clipboardEntry = entry, onPaste = {
|
||||
if (it.uri != null) {
|
||||
if (!manager.typeUri(it.uri, it.mimeTypes)) {
|
||||
val toast = Toast.makeText(
|
||||
manager.getContext(),
|
||||
"App does not support image insertion",
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
toast.show()
|
||||
}
|
||||
} else if (it.text != null) {
|
||||
manager.typeText(it.text)
|
||||
}
|
||||
clipboardHistoryManager.onPaste(it)
|
||||
}, onRemove = {
|
||||
clipboardHistoryManager.onRemove(it)
|
||||
}, onPin = {
|
||||
clipboardHistoryManager.onPin(it)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
@ -20,7 +20,8 @@ val AllActions = listOf(
|
||||
RedoAction,
|
||||
VoiceInputAction,
|
||||
SystemVoiceInputAction,
|
||||
SwitchLanguageAction
|
||||
SwitchLanguageAction,
|
||||
ClipboardHistoryAction
|
||||
)
|
||||
|
||||
|
||||
@ -72,7 +73,8 @@ val DefaultActions = listOf(
|
||||
SettingsAction,
|
||||
ThemeAction,
|
||||
MemoryDebugAction,
|
||||
SwitchLanguageAction
|
||||
SwitchLanguageAction,
|
||||
ClipboardHistoryAction
|
||||
)
|
||||
|
||||
val DefaultActionsString = ActionRegistry.actionsToString(DefaultActions)
|
||||
|
@ -18,6 +18,7 @@ import org.futo.inputmethod.latin.settings.Settings
|
||||
import org.futo.inputmethod.latin.settings.Settings.PREF_VIBRATION_DURATION_SETTINGS
|
||||
import org.futo.inputmethod.latin.uix.SHOW_EMOJI_SUGGESTIONS
|
||||
import org.futo.inputmethod.latin.uix.SettingsKey
|
||||
import org.futo.inputmethod.latin.uix.actions.ClipboardHistoryEnabled
|
||||
import org.futo.inputmethod.latin.uix.settings.ScreenTitle
|
||||
import org.futo.inputmethod.latin.uix.settings.ScrollableList
|
||||
import org.futo.inputmethod.latin.uix.settings.SettingSlider
|
||||
@ -67,6 +68,12 @@ fun TypingScreen(navController: NavHostController = rememberNavController()) {
|
||||
key = Settings.PREF_ENABLE_NUMBER_ROW,
|
||||
default = false
|
||||
)
|
||||
|
||||
SettingToggleDataStore(
|
||||
title = "Clipboard History",
|
||||
setting = ClipboardHistoryEnabled
|
||||
)
|
||||
|
||||
SettingToggleSharedPrefs(
|
||||
title = "Emoji key",
|
||||
subtitle = "Show the emoji key on the bottom row",
|
||||
|
@ -6,20 +6,15 @@ import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowForward
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
@ -39,8 +34,8 @@ import org.futo.inputmethod.latin.uix.getSetting
|
||||
import org.futo.inputmethod.latin.uix.setSetting
|
||||
import org.futo.inputmethod.latin.uix.settings.SettingItem
|
||||
import org.futo.inputmethod.latin.uix.settings.pages.ParagraphText
|
||||
import org.futo.inputmethod.latin.uix.settings.pages.PaymentSurface
|
||||
import org.futo.inputmethod.latin.uix.settings.useDataStore
|
||||
import org.futo.inputmethod.latin.uix.theme.Typography
|
||||
|
||||
val LAST_UPDATE_CHECK_RESULT = stringPreferencesKey("last_update_check_result")
|
||||
val LAST_UPDATE_CHECK_FAILED = booleanPreferencesKey("last_update_check_failed")
|
||||
@ -142,55 +137,40 @@ val dismissedMigrateUpdateNotice = SettingsKey(
|
||||
@Preview
|
||||
fun ConditionalMigrateUpdateNotice() {
|
||||
val context = LocalContext.current
|
||||
val value = useDataStore(dismissedMigrateUpdateNotice)
|
||||
val value = useDataStore(dismissedMigrateUpdateNotice, blocking = true)
|
||||
if(!value.value) {
|
||||
Surface(
|
||||
color = MaterialTheme.colorScheme.surfaceVariant, modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(24.dp, 8.dp), shape = RoundedCornerShape(24.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(8.dp, 0.dp)) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Text(
|
||||
"Use F-Droid or Obtainium",
|
||||
modifier = Modifier.padding(8.dp),
|
||||
style = Typography.titleMedium,
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
)
|
||||
PaymentSurface(isPrimary = true, title = "Use F-Droid or Obtainium") {
|
||||
ParagraphText("The standalone APK has been updated to remove the network permission.")
|
||||
|
||||
ParagraphText("The standalone APK has been updated to remove the network permission.")
|
||||
ParagraphText("As a consequence, it can no longer offer automatic updates.")
|
||||
|
||||
ParagraphText("As a consequence, it can no longer offer automatic updates.")
|
||||
ParagraphText("If you are still using the apk, we recommend downloading the app from F-Droid, Obtainium or Play Store so that you receive updates.")
|
||||
|
||||
ParagraphText("If you are still using the apk, we recommend downloading the app from F-Droid, Obtainium or Play Store so that you receive updates.")
|
||||
ParagraphText("Visit keyboard.futo.org for download options.")
|
||||
|
||||
ParagraphText("Visit keyboard.futo.org for download options.")
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
) {
|
||||
|
||||
Box(modifier = Modifier.weight(1.0f)) {
|
||||
Button(onClick = {
|
||||
context.openURI("https://keyboard.futo.org/#downloads")
|
||||
}, modifier = Modifier.align(Alignment.Center)) {
|
||||
Text("Visit")
|
||||
}
|
||||
Box(modifier = Modifier.weight(1.0f)) {
|
||||
Button(onClick = {
|
||||
context.openURI("https://keyboard.futo.org/#downloads")
|
||||
}, modifier = Modifier.align(Alignment.Center)) {
|
||||
Text("Visit")
|
||||
}
|
||||
Box(modifier = Modifier.weight(1.0f)) {
|
||||
Button(
|
||||
onClick = { value.setValue(true) },
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondary,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondary
|
||||
), modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Text("Dismiss")
|
||||
}
|
||||
}
|
||||
Box(modifier = Modifier.weight(1.0f)) {
|
||||
Button(
|
||||
onClick = { value.setValue(true) },
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
containerColor = MaterialTheme.colorScheme.secondary,
|
||||
contentColor = MaterialTheme.colorScheme.onSecondary
|
||||
), modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Text("Dismiss")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user