Compare commits

...

6 Commits

Author SHA1 Message Date
Patrick Goldinger
ddc4f7f1ba Release v0.4.2 2024-10-17 19:12:13 +02:00
Patrick Goldinger
afea8c721f Merge branch 'main' into release/0.4 2024-10-17 19:10:49 +02:00
florisboard-bot
7dedfd4f7a Update translations from Crowdin 2024-10-17 19:07:32 +02:00
Patrick Goldinger
ef37194900 Disable compose strong skipping mode (#2637) 2024-10-17 16:51:02 +02:00
Lars Mühlbauer
58134b1ceb Add fix for sensitive clipboard suggestions (#2635) 2024-10-16 23:17:54 +02:00
Lars Mühlbauer
53cfbad404 Clipboard History enhancements (#2631)
* Hide sensitive clip data in clipboard history

* Add is remoteDevice flag

* Do not link password length to displayed characters

* Add backspace in clipboard history (#2615)

* Use ClipboardItem level function for the obfuscation of the text

* Move the backspace button to the header bar

* Adjust innerHeight to match the full layout

* Use KeyboardLikeButton instead of FlorisIconButtonWithInnerPadding
2024-10-14 19:31:37 +02:00
14 changed files with 198 additions and 19 deletions

View File

@@ -14,6 +14,7 @@
* limitations under the License.
*/
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
import java.io.ByteArrayOutputStream
plugins {
@@ -161,6 +162,12 @@ android {
}
}
composeCompiler {
// DO NOT ENABLE STRONG SKIPPING! This project currently relies on
// recomposition on parent state change to update the UI correctly.
featureFlags.add(ComposeFeatureFlag.StrongSkipping.disabled())
}
tasks.withType<Test> {
useJUnitPlatform()
}

View File

@@ -0,0 +1,92 @@
{
"formatVersion": 1,
"database": {
"version": 3,
"identityHash": "282a1b421e498fd0e21c055b6a4315e0",
"entities": [
{
"tableName": "clipboard_history",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `text` TEXT, `uri` TEXT, `creationTimestampMs` INTEGER NOT NULL, `isPinned` INTEGER NOT NULL, `mimeTypes` TEXT NOT NULL, `isSensitive` INTEGER NOT NULL, `isRemoteDevice` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
"columnName": "_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "type",
"columnName": "type",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "uri",
"columnName": "uri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "creationTimestampMs",
"columnName": "creationTimestampMs",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isPinned",
"columnName": "isPinned",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mimeTypes",
"columnName": "mimeTypes",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isSensitive",
"columnName": "isSensitive",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isRemoteDevice",
"columnName": "isRemoteDevice",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": true,
"columnNames": [
"_id"
]
},
"indices": [
{
"name": "index_clipboard_history__id",
"unique": false,
"columnNames": [
"_id"
],
"orders": [],
"createSql": "CREATE INDEX IF NOT EXISTS `index_clipboard_history__id` ON `${TABLE_NAME}` (`_id`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '282a1b421e498fd0e21c055b6a4315e0')"
]
}
}

View File

@@ -48,6 +48,7 @@ import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.outlined.Backspace
import androidx.compose.material.icons.filled.ClearAll
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.ToggleOff
@@ -87,6 +88,8 @@ import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardFileStorage
import dev.patrickgold.florisboard.ime.clipboard.provider.ClipboardItem
import dev.patrickgold.florisboard.ime.clipboard.provider.ItemType
import dev.patrickgold.florisboard.ime.keyboard.FlorisImeSizing
import dev.patrickgold.florisboard.ime.media.KeyboardLikeButton
import dev.patrickgold.florisboard.ime.text.keyboard.TextKeyData
import dev.patrickgold.florisboard.ime.theme.FlorisImeTheme
import dev.patrickgold.florisboard.ime.theme.FlorisImeUi
import dev.patrickgold.florisboard.keyboardManager
@@ -193,6 +196,13 @@ fun ClipboardInputLayout(
iconColor = headerStyle.foreground.solidColor(context),
enabled = !deviceLocked && historyEnabled && !isPopupSurfaceActive(),
)
KeyboardLikeButton(
inputEventDispatcher = keyboardManager.inputEventDispatcher,
keyData = TextKeyData.DELETE,
element = FlorisImeUi.ClipboardHeader,
) {
Icon(Icons.AutoMirrored.Outlined.Backspace, null)
}
}
}
@@ -307,7 +317,7 @@ fun ClipboardInputLayout(
.fillMaxWidth()
.run { if (contentScrollInsteadOfClip) this.florisVerticalScroll() else this }
.padding(ItemPadding),
text = text,
text = item.displayText(),
style = TextStyle(textDirection = TextDirection.ContentOrLtr),
color = style.foreground.solidColor(context),
fontSize = style.fontSize.spSize(),
@@ -577,7 +587,7 @@ fun ClipboardInputLayout(
Column(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight(),
.height(FlorisImeSizing.imeUiHeight()),
) {
HeaderRow()
if (deviceLocked) {

View File

@@ -17,6 +17,8 @@
package dev.patrickgold.florisboard.ime.clipboard.provider
import android.content.ClipData
import android.content.ClipDescription.EXTRA_IS_REMOTE_DEVICE
import android.content.ClipDescription.EXTRA_IS_SENSITIVE
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
@@ -24,6 +26,8 @@ import android.net.Uri
import android.provider.BaseColumns
import android.provider.MediaStore.Images.Media
import android.provider.OpenableColumns
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import androidx.core.database.getStringOrNull
import androidx.lifecycle.LiveData
import androidx.room.ColumnInfo
@@ -39,9 +43,14 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverter
import androidx.room.TypeConverters
import androidx.room.Update
import dev.patrickgold.florisboard.R
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.Serializable
import org.florisboard.lib.android.AndroidVersion
import org.florisboard.lib.android.UriSerializer
import org.florisboard.lib.android.query
import kotlinx.serialization.Serializable
import org.florisboard.lib.android.stringRes
import org.florisboard.lib.kotlin.tryOrNull
private const val CLIPBOARD_HISTORY_TABLE = "clipboard_history"
@@ -67,7 +76,7 @@ enum class ItemType(val value: Int) {
*/
@Serializable
@Entity(tableName = CLIPBOARD_HISTORY_TABLE)
data class ClipboardItem(
data class ClipboardItem @OptIn(ExperimentalSerializationApi::class) constructor(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = BaseColumns._ID, index = true)
var id: Long = 0,
@@ -78,6 +87,10 @@ data class ClipboardItem(
val creationTimestampMs: Long,
val isPinned: Boolean,
val mimeTypes: Array<String>,
@EncodeDefault
val isSensitive: Boolean = false,
@EncodeDefault
val isRemoteDevice: Boolean = false,
) {
companion object {
/**
@@ -113,6 +126,18 @@ data class ClipboardItem(
else -> ItemType.TEXT
}
val isSensitive = if (AndroidVersion.ATLEAST_API33_T) {
data.description?.extras?.getBoolean(EXTRA_IS_SENSITIVE) ?: false
} else {
false
}
val isRemoteDevice = if (AndroidVersion.ATLEAST_API34_U) {
data.description?.extras?.getBoolean(EXTRA_IS_REMOTE_DEVICE) ?: false
} else {
false
}
val uri = if (type == ItemType.IMAGE || type == ItemType.VIDEO) {
if (dataItem.uri.authority == ClipboardMediaProvider.AUTHORITY || !cloneUri) {
dataItem.uri
@@ -151,7 +176,21 @@ data class ClipboardItem(
}
}
return ClipboardItem(0, type, text, uri, System.currentTimeMillis(), false, mimeTypes)
return ClipboardItem(0, type, text, uri, System.currentTimeMillis(), false, mimeTypes, isSensitive, isRemoteDevice)
}
}
@Composable
inline fun displayText(): String {
val context = LocalContext.current
return displayText(context)
}
fun displayText(context: Context): String {
return if (isSensitive) {
context.stringRes(R.string.clipboard__sensitive_clip_content)
} else {
stringRepresentation()
}
}
@@ -293,7 +332,7 @@ interface ClipboardHistoryDao {
fun deleteAllUnpinned()
}
@Database(entities = [ClipboardItem::class], version = 2)
@Database(entities = [ClipboardItem::class], version = 3)
@TypeConverters(Converters::class)
abstract class ClipboardHistoryDatabase : RoomDatabase() {
abstract fun clipboardItemDao(): ClipboardHistoryDao

View File

@@ -113,12 +113,13 @@ internal fun KeyboardLikeButton(
modifier: Modifier = Modifier,
inputEventDispatcher: InputEventDispatcher,
keyData: KeyData,
element: String = FlorisImeUi.EmojiKey,
content: @Composable RowScope.() -> Unit,
) {
val inputFeedbackController = LocalInputFeedbackController.current
var isPressed by remember { mutableStateOf(false) }
val keyStyle = FlorisImeTheme.style.get(
element = FlorisImeUi.EmojiKey,
element = element,
code = keyData.code,
isPressed = isPressed,
)

View File

@@ -61,7 +61,7 @@ class NlpManager(context: Context) {
private val subtypeManager by context.subtypeManager()
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
private val clipboardSuggestionProvider = ClipboardSuggestionProvider()
private val clipboardSuggestionProvider = ClipboardSuggestionProvider(context)
private val emojiSuggestionProvider = EmojiSuggestionProvider(context)
private val providers = guardedByLock {
mapOf(
@@ -349,7 +349,7 @@ class NlpManager(context: Context) {
}
}
inner class ClipboardSuggestionProvider internal constructor() : SuggestionProvider {
inner class ClipboardSuggestionProvider internal constructor(private val context: Context) : SuggestionProvider {
private var lastClipboardItemId: Long = -1
override val providerId = "org.florisboard.nlp.providers.clipboard"
@@ -378,7 +378,10 @@ class NlpManager(context: Context) {
return buildList {
val now = System.currentTimeMillis()
if ((now - currentItem.creationTimestampMs) < prefs.suggestion.clipboardContentTimeout.get() * 1000) {
add(ClipboardSuggestionCandidate(currentItem, sourceProvider = this@ClipboardSuggestionProvider))
add(ClipboardSuggestionCandidate(currentItem, sourceProvider = this@ClipboardSuggestionProvider, context = context))
if (currentItem.isSensitive) {
return@buildList
}
if (currentItem.type == ItemType.TEXT) {
val text = currentItem.stringRepresentation()
val matches = buildList {
@@ -402,6 +405,7 @@ class NlpManager(context: Context) {
}
),
sourceProvider = this@ClipboardSuggestionProvider,
context = context,
))
}
}

View File

@@ -16,6 +16,7 @@
package dev.patrickgold.florisboard.ime.nlp
import android.content.Context
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.Assignment
import androidx.compose.material.icons.filled.Email
@@ -123,8 +124,9 @@ data class WordSuggestionCandidate(
data class ClipboardSuggestionCandidate(
val clipboardItem: ClipboardItem,
override val sourceProvider: SuggestionProvider?,
val context: Context,
) : SuggestionCandidate {
override val text: CharSequence = clipboardItem.stringRepresentation()
override val text: CharSequence = clipboardItem.displayText(context)
override val secondaryText: CharSequence? = null

View File

@@ -316,7 +316,7 @@ fun TextKeyboardLayout(
val debugShowTouchBoundaries by prefs.devtools.showKeyTouchBoundaries.observeAsState()
for (textKey in keyboard.keys()) {
TextKeyButton(
textKey, desiredKey, evaluator, fontSizeMultiplier, isSmartbarKeyboard,
textKey, evaluator, fontSizeMultiplier, isSmartbarKeyboard,
debugShowTouchBoundaries,
)
}
@@ -336,7 +336,6 @@ fun TextKeyboardLayout(
@Composable
private fun TextKeyButton(
key: TextKey,
desiredKey: TextKey,
evaluator: ComputingEvaluator,
fontSizeMultiplier: Float,
isSmartbarKey: Boolean,
@@ -359,9 +358,7 @@ private fun TextKeyButton(
KeyCode.VIEW_NUMERIC_ADVANCED -> 0.55f
else -> 1.0f
}
val size = remember(desiredKey) {
key.visibleBounds.size.toDpSize()
}
val size = key.visibleBounds.size.toDpSize()
Box(
modifier = Modifier
.requiredSize(size)

View File

@@ -15,6 +15,7 @@
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Kaomoji</string>
<string name="prefs__media__emoji_preferred_skin_tone">لون البشرة المفضل للرموز التعبيرية</string>
<string name="prefs__media__emoji_preferred_hair_style">تصفيفة الشعر الرموز التعبيرية المفضلة</string>
<string name="prefs__media__emoji_history_enabled" comment="Preference title">Activar l\'historial de fustaxes</string>
<!-- Emoji strings -->
<string name="emoji__category__smileys_emotion" comment="Emoji category name">Sorrises y fustaxes</string>
<string name="emoji__category__people_body" comment="Emoji category name">Persones y cuerpu</string>

View File

@@ -582,6 +582,8 @@
<string name="devtools__show_input_state_overlay__summary" comment="Summary of Show input cache overlay in Devtools">Zobrazí aktuální stav vstupu pro ladění</string>
<string name="devtools__show_spelling_overlay__label" comment="Label of Show spelling overlay in Devtools">Zobrazit překrytí s pravopisem</string>
<string name="devtools__show_spelling_overlay__summary" comment="Summary of Show spelling overlay in Devtools">Zobrazí aktuální výsledky pravopisu pro ladění</string>
<string name="devtools__show_inline_autofill_overlay__label">Zobrazit překrytí automatického vyplňování na řádku</string>
<string name="devtools__show_inline_autofill_overlay__summary">Zobrazí aktuální výsledky automatického vyplňování na řádku pro ladění</string>
<string name="devtools__show_key_touch_boundaries__label" comment="Label of Show key touch boundaries in Devtools">Zobrazit hranice dotyku kláves</string>
<string name="devtools__show_key_touch_boundaries__summary" comment="Summary of Show key touch boundaries in Devtools">Zobrazit červené ohraničení hranic dotyku kláves</string>
<string name="devtools__show_drag_and_drop_helpers__label" comment="Label of Show drag and drop helpers in Devtools">Zobrazit pomocníky drag&amp;drop</string>

View File

@@ -5,7 +5,7 @@
<string name="key__phone_wait" comment="Label for the Wait key in the telephone keyboard layout">Ожидание</string>
<string name="key_popup__threedots_alt" comment="Content description for the three-dots icon in a key popup">Значок троеточия. Если отображается, показывает, что можно использовать больше знаков при долгом нажатии.</string>
<!-- One-handed strings -->
<string name="one_handed__close_btn_content_description" comment="Content description for the one-handed close button">Закрыть режим одной руки</string>
<string name="one_handed__close_btn_content_description" comment="Content description for the one-handed close button">Закрыть режим одной руки.</string>
<string name="one_handed__move_start_btn_content_description" comment="Content description for the one-handed move to left button">Переместить клавиатуру влево.</string>
<string name="one_handed__move_end_btn_content_description" comment="Content description for the one-handed move to right button">Переместить клавиатуру вправо.</string>
<!-- Media strings -->
@@ -15,6 +15,12 @@
<string name="media__tab__kaomoji" comment="Tab description for kaomoji in the media UI">Каомодзи</string>
<string name="prefs__media__emoji_preferred_skin_tone">Предпочтительный цвет кожи эмодзи</string>
<string name="prefs__media__emoji_preferred_hair_style">Предпочтительная прическа эмодзи</string>
<string name="prefs__media__emoji_history__title" comment="Preference group title">История эмодзи</string>
<string name="prefs__media__emoji_history_enabled" comment="Preference title">Включить историю эмодзи</string>
<string name="prefs__media__emoji_history_enabled__summary" comment="Preference summary">Сохраняйте недавно использованные эмодзи для быстрого доступа</string>
<string name="prefs__media__emoji_history_pinned_update_strategy" comment="Preference title">Обновление истории (Закрепленного)</string>
<string name="prefs__media__emoji_history_recent_update_strategy" comment="Preference title">Обновление истории (Недавнего)</string>
<string name="prefs__media__emoji_history_max_size">Максимум элементов, которые можно сохранить</string>
<string name="prefs__media__emoji_suggestion__title" comment="Preference group title">Подсказки смайликов</string>
<string name="prefs__media__emoji_suggestion_enabled" comment="Preference title">Включить подсказки смайликов</string>
<string name="prefs__media__emoji_suggestion_enabled__summary" comment="Preference summary">Предлагать смайлики при наборе текста</string>
@@ -34,6 +40,11 @@
<string name="emoji__category__objects" comment="Emoji category name">Объекты</string>
<string name="emoji__category__symbols" comment="Emoji category name">Символы</string>
<string name="emoji__category__flags" comment="Emoji category name">Флаги</string>
<string name="emoji__history__empty_message" comment="Message if the emoji history is empty">Недавно использованные эмодзи не найдены. Как только вы начнете использовать эмодзи, они автоматически будут появляться здесь.</string>
<string name="emoji__history__usage_tip" comment="Feature discoverability for actions of emoji history">Совет: Долго нажимайте на эмодзи в истории эмодзи, чтобы закрепить или удалить их!</string>
<string name="emoji__history__removal_success_message" comment="Toast message if user has used the delete action on an emoji in the emoji history">Удаление {emoji} из истории эмодзи</string>
<string name="emoji__history__pinned">Закреплено</string>
<string name="emoji__history__recent">Недавние</string>
<!-- Quick action strings -->
<string name="quick_action__arrow_up" maxLength="12">В начало</string>
<string name="quick_action__arrow_up__tooltip">Переместить курсор в начало</string>
@@ -569,6 +580,7 @@
<string name="devtools__show_input_state_overlay__summary" comment="Summary of Show input cache overlay in Devtools">Показывать наложением текущее состояние ввода для отладки</string>
<string name="devtools__show_spelling_overlay__label" comment="Label of Show spelling overlay in Devtools">Показывать орфографию наложением</string>
<string name="devtools__show_spelling_overlay__summary" comment="Summary of Show spelling overlay in Devtools">Показывать наложением текущие результаты проверки орфографии для отладки</string>
<string name="devtools__show_inline_autofill_overlay__summary">Отображает текущие результаты автозаполнения строки для отладки</string>
<string name="devtools__show_key_touch_boundaries__label" comment="Label of Show key touch boundaries in Devtools">Показывать границы нажатия клавиш</string>
<string name="devtools__show_key_touch_boundaries__summary" comment="Summary of Show key touch boundaries in Devtools">Обводить границы нажатия клавиш красным контуром</string>
<string name="devtools__show_drag_and_drop_helpers__label" comment="Label of Show drag and drop helpers in Devtools">Показывать вспомогательные элементы перетаскивания</string>
@@ -747,6 +759,12 @@
<string name="enum__display_language_names_in__system_locale__description" comment="Enum value description">Подписи в приложении и интерфейсе клавиатуры указаны на языке, используемом в системе по умолчанию</string>
<string name="enum__display_language_names_in__native_locale" comment="Enum value label">В исходном виде</string>
<string name="enum__display_language_names_in__native_locale__description" comment="Enum value description">Подписи в приложении и интерфейсе клавиатуры приводятся на родных языках</string>
<string name="enum__emoji_history_update_strategy__auto_sort_prepend__description" comment="Enum value description">Автоматическое изменение порядка расположения эмодзи в зависимости от их использования. Новые эмодзи добавляются в начало.</string>
<string name="enum__emoji_history_update_strategy__auto_sort_append__description" comment="Enum value description">Автоматическое изменение порядка расположения эмодзи в зависимости от их использования. Новые эмодзи добавляются в конец.</string>
<string name="enum__emoji_history_update_strategy__manual_sort_prepend__description" comment="Enum value description">Не происходит автоматической перестановки эмодзи в зависимости от их использования.
Новые эмодзи добавляются в начало.</string>
<string name="enum__emoji_history_update_strategy__manual_sort_append__description" comment="Enum value description">Не происходит автоматической перестановки эмодзи в зависимости от их использования.
Новые эмодзи добавляются в конец.</string>
<string name="enum__emoji_skin_tone__default" comment="Enum value label">Цвет кожи {emoji} по умолчанию</string>
<string name="enum__emoji_skin_tone__light_skin_tone" comment="Enum value label">Светлый цвет кожи {emoji}</string>
<string name="enum__emoji_skin_tone__medium_light_skin_tone" comment="Enum value label">Светловатый цвет кожи {emoji}</string>
@@ -758,6 +776,8 @@
<string name="enum__emoji_hair_style__curly_hair" comment="Enum value label">{emoji} Вьющиеся волосы</string>
<string name="enum__emoji_hair_style__white_hair" comment="Enum value label">{emoji} Светлые волосы</string>
<string name="enum__emoji_hair_style__bald" comment="Enum value label">{emoji} Без волос</string>
<string name="enum__emoji_suggestion_type__leading_colon__description" comment="Keep the :emoji_name while translating, this is a syntax guide">Предлагайте эмодзи, используя синтаксис :emoji_name</string>
<string name="enum__emoji_suggestion_type__inline_text__description">Предлагает эмодзи, просто набрав название эмодзи в виде слова</string>
<string name="enum__extended_actions_placement__above_candidates" comment="Enum value label">Вышестоящие предложение</string>
<string name="enum__extended_actions_placement__above_candidates__description" comment="Enum value description">Размещает строку расширенных действий между пользовательским интерфейсом приложения и строкой предложений</string>
<string name="enum__extended_actions_placement__below_candidates" comment="Enum value label">Нижестоящие предложение</string>

View File

@@ -581,6 +581,8 @@
<string name="devtools__show_input_state_overlay__summary" comment="Summary of Show input cache overlay in Devtools">Накладає поточний стан входу для налагодження</string>
<string name="devtools__show_spelling_overlay__label" comment="Label of Show spelling overlay in Devtools">Показати правопис накладанням</string>
<string name="devtools__show_spelling_overlay__summary" comment="Summary of Show spelling overlay in Devtools">Накладає поточні результати правопису для налагодження</string>
<string name="devtools__show_inline_autofill_overlay__label">Показати вбудоване накладання автозаповнення</string>
<string name="devtools__show_inline_autofill_overlay__summary">Накладає поточні результати автозаповнення для налагодження</string>
<string name="devtools__show_key_touch_boundaries__label" comment="Label of Show key touch boundaries in Devtools">Показати межі дотику клавіш</string>
<string name="devtools__show_key_touch_boundaries__summary" comment="Summary of Show key touch boundaries in Devtools">Обводить червоним кольором межі дотику клавіш</string>
<string name="devtools__show_drag_and_drop_helpers__label" comment="Label of Show drag and drop helpers in Devtools">Показати помічників перетягування</string>

View File

@@ -23,6 +23,8 @@
<string name="key__view_keshida" translatable="false">"یــــ"</string>
<string name="key__dotted_circle" translatable="false">&#9676;</string>
<string name="clipboard__sensitive_clip_content" translatable="false">************</string>
<!-- Media strings -->
<string name="media__tab__emoticons_label" translatable="false">;-)</string>
<string name="media__tab__kaomoji_label" translatable="false">(^-^*)/</string>

View File

@@ -13,5 +13,5 @@ projectCompileSdk=34
projectBuildToolsVersion=34.0.0
projectNdkVersion=26.1.10909125
projectVersionCode=99
projectVersionName=0.4.1
projectVersionCode=100
projectVersionName=0.4.2