Allow changing toolbar icon and codes (#1110)

This commit is contained in:
Helium314 2024-09-23 20:45:23 +02:00 committed by GitHub
parent 7d1cf0dd63
commit 05523ecd30
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 433 additions and 89 deletions

View file

@ -46,7 +46,7 @@ Does not use internet permission, and thus is 100% offline.
<li>Backup and restore your settings and learned word / history data</li>
</ul>
For more information about the app and features, please visit the [wiki](https://github.com/Helium314/HeliBoard/wiki)
For FAQ and more information about the app and features, please visit the [wiki](https://github.com/Helium314/HeliBoard/wiki)
# Contributing ❤

View file

@ -938,11 +938,12 @@ public class Key implements Comparable<Key> {
final String iconName = getIconName();
if (iconName == null) return false;
// todo: other way of identifying the color?
// if yes, NAME_CLIPBOARD_ACTION_KEY and NAME_CLIPBOARD_NORMAL_KEY could be merged
// this should be done differently, as users can set any icon now
// how is the background drawable selected? can we use the same way?
return iconName.equals(KeyboardIconsSet.NAME_NEXT_KEY)
|| iconName.equals(KeyboardIconsSet.NAME_PREVIOUS_KEY)
|| iconName.equals(KeyboardIconsSet.NAME_CLIPBOARD_ACTION_KEY)
|| iconName.equals(KeyboardIconsSet.NAME_EMOJI_ACTION_KEY);
|| iconName.equals("clipboard_action_key")
|| iconName.equals("emoji_action_key");
}
public boolean hasFunctionalBackground() {

View file

@ -6,6 +6,7 @@ import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat
import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.latin.R
import helium314.keyboard.latin.customIconIds
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.DeviceProtectedUtils
import helium314.keyboard.latin.utils.Log
@ -13,17 +14,20 @@ import helium314.keyboard.latin.utils.ToolbarKey
import java.util.Locale
class KeyboardIconsSet private constructor() {
private var iconIds = emptyMap<String, Int>()
var iconIds = emptyMap<String, Int>()
private set
private val iconsByName = HashMap<String, Drawable>(80)
fun loadIcons(context: Context) {
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
val iconStyle = prefs.getString(Settings.PREF_ICON_STYLE, KeyboardTheme.STYLE_MATERIAL)
val ids = when (iconStyle) {
val defaultIds = when (iconStyle) {
KeyboardTheme.STYLE_HOLO -> keyboardIconsHolo
KeyboardTheme.STYLE_ROUNDED -> keyboardIconsRounded
else -> keyboardIconsMaterial
}
val overrideIds = customIconIds(context, prefs)
val ids = if (overrideIds.isEmpty()) defaultIds else defaultIds + overrideIds
if (ids == iconIds) return
iconIds = ids
iconsByName.clear()
@ -38,13 +42,15 @@ class KeyboardIconsSet private constructor() {
}
}
fun getIconDrawable(name: String?): Drawable? = iconsByName[name?.lowercase(Locale.US)]
/** gets drawable from resources, with mutate (might be necessary to avoid coloring issues...) */
fun getNewDrawable(name: String?, context: Context): Drawable? =
iconIds[name?.lowercase(Locale.US)]?.let { ContextCompat.getDrawable(context, it)?.mutate() }
fun getIconDrawable(name: String?): Drawable? = name?.lowercase(Locale.US)?.let {
iconsByName[it] ?: iconsByName[alternativeNames[it]]
}
/** gets drawable from resources, with mutate (might be necessary to avoid coloring issues...) */
fun getNewDrawable(name: String?, context: Context): Drawable? = name?.lowercase(Locale.US)?.let { name ->
(iconIds[name] ?: iconIds[alternativeNames[name]])?.let { ContextCompat.getDrawable(context, it)?.mutate() }
}
// sometimes there are 2 names for the same icon for historic reasons,
// and removing needs to be handled with care to not break custom themes
companion object {
private val TAG = KeyboardIconsSet::class.simpleName
const val PREFIX_ICON = "!icon/"
@ -53,7 +59,6 @@ class KeyboardIconsSet private constructor() {
const val NAME_SHIFT_KEY_SHIFTED = "shift_key_shifted"
const val NAME_SHIFT_KEY_LOCKED = "shift_key_locked"
const val NAME_DELETE_KEY = "delete_key"
const val NAME_SETTINGS_KEY = "settings_key"
const val NAME_SPACE_KEY = "space_key"
const val NAME_SPACE_KEY_FOR_NUMBER_LAYOUT = "space_key_for_number_layout"
const val NAME_ENTER_KEY = "enter_key"
@ -64,64 +69,37 @@ class KeyboardIconsSet private constructor() {
const val NAME_DONE_KEY = "done_key"
const val NAME_PREVIOUS_KEY = "previous_key"
const val NAME_TAB_KEY = "tab_key"
const val NAME_SHORTCUT_KEY = "shortcut_key"
const val NAME_INCOGNITO_KEY = "incognito_key"
const val NAME_SHORTCUT_KEY_DISABLED = "shortcut_key_disabled"
const val NAME_LANGUAGE_SWITCH_KEY = "language_switch_key"
const val NAME_ZWNJ_KEY = "zwnj_key"
const val NAME_ZWJ_KEY = "zwj_key"
const val NAME_EMOJI_ACTION_KEY = "emoji_action_key"
const val NAME_EMOJI_NORMAL_KEY = "emoji_normal_key"
const val NAME_CLIPBOARD_ACTION_KEY = "clipboard_action_key"
const val NAME_CLIPBOARD_NORMAL_KEY = "clipboard_normal_key"
const val NAME_CLEAR_CLIPBOARD_KEY = "clear_clipboard_key"
const val NAME_CUT_KEY = "cut_key"
const val NAME_START_ONEHANDED_KEY = "start_onehanded_mode_key"
const val NAME_STOP_ONEHANDED_KEY = "stop_onehanded_mode_key"
const val NAME_SWITCH_ONEHANDED_KEY = "switch_onehanded_key"
const val NAME_RESIZE_ONEHANDED_KEY = "resize_onehanded_key"
const val NAME_TOOLBAR_KEY = "toolbar_key"
const val NAME_BIN = "bin"
/*
private val styleableIdByName = hashMapOf(
NAME_SHIFT_KEY to R.styleable.Keyboard_iconShiftKey,
NAME_DELETE_KEY to R.styleable.Keyboard_iconDeleteKey,
NAME_SETTINGS_KEY to R.styleable.Keyboard_iconSettingsKey,
NAME_SPACE_KEY to R.styleable.Keyboard_iconSpaceKey,
NAME_ENTER_KEY to R.styleable.Keyboard_iconEnterKey,
NAME_GO_KEY to R.styleable.Keyboard_iconGoKey,
NAME_SEARCH_KEY to R.styleable.Keyboard_iconSearchKey,
NAME_SEND_KEY to R.styleable.Keyboard_iconSendKey,
NAME_NEXT_KEY to R.styleable.Keyboard_iconNextKey,
NAME_DONE_KEY to R.styleable.Keyboard_iconDoneKey,
NAME_PREVIOUS_KEY to R.styleable.Keyboard_iconPreviousKey,
NAME_TAB_KEY to R.styleable.Keyboard_iconTabKey,
NAME_SHORTCUT_KEY to R.styleable.Keyboard_iconShortcutKey,
NAME_INCOGNITO_KEY to R.styleable.Keyboard_iconIncognitoKey,
NAME_SPACE_KEY_FOR_NUMBER_LAYOUT to R.styleable.Keyboard_iconSpaceKeyForNumberLayout,
NAME_SHIFT_KEY_SHIFTED to R.styleable.Keyboard_iconShiftKeyShifted,
NAME_SHIFT_KEY_LOCKED to R.styleable.Keyboard_iconShiftKeyLocked,
NAME_SHORTCUT_KEY_DISABLED to R.styleable.Keyboard_iconShortcutKeyDisabled,
NAME_LANGUAGE_SWITCH_KEY to R.styleable.Keyboard_iconLanguageSwitchKey,
NAME_ZWNJ_KEY to R.styleable.Keyboard_iconZwnjKey,
NAME_ZWJ_KEY to R.styleable.Keyboard_iconZwjKey,
NAME_EMOJI_ACTION_KEY to R.styleable.Keyboard_iconEmojiActionKey,
NAME_EMOJI_NORMAL_KEY to R.styleable.Keyboard_iconEmojiNormalKey,
NAME_CLIPBOARD_ACTION_KEY to R.styleable.Keyboard_iconClipboardActionKey,
NAME_CLIPBOARD_NORMAL_KEY to R.styleable.Keyboard_iconClipboardNormalKey,
NAME_CLEAR_CLIPBOARD_KEY to R.styleable.Keyboard_iconClearClipboardKey,
NAME_CUT_KEY to R.styleable.Keyboard_iconCutKey,
NAME_START_ONEHANDED_KEY to R.styleable.Keyboard_iconStartOneHandedMode,
NAME_STOP_ONEHANDED_KEY to R.styleable.Keyboard_iconStopOneHandedMode,
NAME_SWITCH_ONEHANDED_KEY to R.styleable.Keyboard_iconSwitchOneHandedMode,
).apply { ToolbarKey.entries.forEach { put(it.name.lowercase(Locale.US), getStyleableIconId(it)) } }
*/
// names used in the past, and we can't just delete them because they might still be in use in some layouts
// (also some of them are in use for internal layouts, but there we could just remove them...)
private val alternativeNames = hashMapOf(
"clear_clipboard_key" to ToolbarKey.CLEAR_CLIPBOARD.name.lowercase(Locale.US),
"shortcut_key" to ToolbarKey.VOICE.name.lowercase(Locale.US),
"emoji_action_key" to ToolbarKey.EMOJI.name.lowercase(Locale.US),
"emoji_normal_key" to ToolbarKey.EMOJI.name.lowercase(Locale.US),
"clipboard_action_key" to ToolbarKey.CLIPBOARD.name.lowercase(Locale.US),
"clipboard_normal_key" to ToolbarKey.CLIPBOARD.name.lowercase(Locale.US),
"cut_key" to ToolbarKey.CUT.name.lowercase(Locale.US),
"incognito_key" to ToolbarKey.INCOGNITO.name.lowercase(Locale.US),
"settings_key" to ToolbarKey.SETTINGS.name.lowercase(Locale.US),
"start_onehanded_mode_key" to ToolbarKey.ONE_HANDED.name.lowercase(Locale.US),
)
// todo: incognito and force incognito should not be the same? or not the same as toolbar key?
private val keyboardIconsHolo by lazy { hashMapOf(
NAME_SHIFT_KEY to R.drawable.sym_keyboard_shift_holo,
NAME_SHIFT_KEY_SHIFTED to R.drawable.sym_keyboard_shifted_holo,
NAME_SHIFT_KEY_LOCKED to R.drawable.sym_keyboard_shift_lock_holo,
NAME_DELETE_KEY to R.drawable.sym_keyboard_delete_holo,
NAME_SETTINGS_KEY to R.drawable.sym_keyboard_settings_holo,
// NAME_SPACE_KEY to null,
NAME_ENTER_KEY to R.drawable.sym_keyboard_return_holo,
// NAME_GO_KEY to null,
@ -131,20 +109,11 @@ class KeyboardIconsSet private constructor() {
// NAME_NEXT_KEY to null,
// NAME_PREVIOUS_KEY to null,
NAME_TAB_KEY to R.drawable.sym_keyboard_tab_holo,
NAME_INCOGNITO_KEY to R.drawable.sym_keyboard_incognito_holo,
NAME_SPACE_KEY_FOR_NUMBER_LAYOUT to R.drawable.sym_keyboard_space_holo,
NAME_SHORTCUT_KEY to R.drawable.sym_keyboard_voice_holo,
NAME_SHORTCUT_KEY_DISABLED to R.drawable.sym_keyboard_voice_off_holo,
NAME_LANGUAGE_SWITCH_KEY to R.drawable.sym_keyboard_language_switch,
NAME_ZWNJ_KEY to R.drawable.sym_keyboard_zwnj_holo,
NAME_ZWJ_KEY to R.drawable.sym_keyboard_zwj_holo,
NAME_EMOJI_ACTION_KEY to R.drawable.sym_keyboard_smiley_holo,
NAME_EMOJI_NORMAL_KEY to R.drawable.sym_keyboard_smiley_holo,
NAME_CLIPBOARD_ACTION_KEY to R.drawable.sym_keyboard_clipboard_holo,
NAME_CLIPBOARD_NORMAL_KEY to R.drawable.sym_keyboard_clipboard_holo,
NAME_CLEAR_CLIPBOARD_KEY to R.drawable.sym_keyboard_clear_clipboard_holo,
NAME_CUT_KEY to R.drawable.sym_keyboard_cut,
NAME_START_ONEHANDED_KEY to R.drawable.sym_keyboard_start_onehanded_holo,
NAME_STOP_ONEHANDED_KEY to R.drawable.sym_keyboard_stop_onehanded_holo,
NAME_SWITCH_ONEHANDED_KEY to R.drawable.ic_arrow_left,
NAME_RESIZE_ONEHANDED_KEY to R.drawable.ic_arrow_horizontal,
@ -191,7 +160,6 @@ class KeyboardIconsSet private constructor() {
NAME_SHIFT_KEY_SHIFTED to R.drawable.sym_keyboard_shift_lxx,
NAME_SHIFT_KEY_LOCKED to R.drawable.sym_keyboard_shift_lock_lxx,
NAME_DELETE_KEY to R.drawable.sym_keyboard_delete_lxx,
NAME_SETTINGS_KEY to R.drawable.sym_keyboard_settings_lxx,
// NAME_SPACE_KEY to null,
NAME_ENTER_KEY to R.drawable.sym_keyboard_return_lxx,
NAME_GO_KEY to R.drawable.sym_keyboard_go_lxx,
@ -201,20 +169,11 @@ class KeyboardIconsSet private constructor() {
NAME_NEXT_KEY to R.drawable.ic_arrow_right,
NAME_PREVIOUS_KEY to R.drawable.ic_arrow_left,
NAME_TAB_KEY to R.drawable.sym_keyboard_tab_lxx,
NAME_INCOGNITO_KEY to R.drawable.sym_keyboard_incognito_lxx,
NAME_SPACE_KEY_FOR_NUMBER_LAYOUT to R.drawable.sym_keyboard_space_lxx,
NAME_SHORTCUT_KEY to R.drawable.sym_keyboard_voice_lxx,
NAME_SHORTCUT_KEY_DISABLED to R.drawable.sym_keyboard_voice_off_lxx,
NAME_LANGUAGE_SWITCH_KEY to R.drawable.sym_keyboard_language_switch_lxx,
NAME_ZWNJ_KEY to R.drawable.sym_keyboard_zwnj_lxx,
NAME_ZWJ_KEY to R.drawable.sym_keyboard_zwj_lxx,
NAME_EMOJI_ACTION_KEY to R.drawable.sym_keyboard_smiley_lxx,
NAME_EMOJI_NORMAL_KEY to R.drawable.sym_keyboard_smiley_lxx,
NAME_CLIPBOARD_ACTION_KEY to R.drawable.sym_keyboard_clipboard_lxx,
NAME_CLIPBOARD_NORMAL_KEY to R.drawable.sym_keyboard_clipboard_lxx,
NAME_CLEAR_CLIPBOARD_KEY to R.drawable.sym_keyboard_clear_clipboard_lxx,
NAME_CUT_KEY to R.drawable.sym_keyboard_cut,
NAME_START_ONEHANDED_KEY to R.drawable.sym_keyboard_start_onehanded_lxx,
NAME_STOP_ONEHANDED_KEY to R.drawable.sym_keyboard_stop_onehanded_lxx,
NAME_SWITCH_ONEHANDED_KEY to R.drawable.ic_arrow_left,
NAME_RESIZE_ONEHANDED_KEY to R.drawable.ic_arrow_horizontal,
@ -261,7 +220,6 @@ class KeyboardIconsSet private constructor() {
NAME_SHIFT_KEY_SHIFTED to R.drawable.sym_keyboard_shift_rounded,
NAME_SHIFT_KEY_LOCKED to R.drawable.sym_keyboard_shift_lock_rounded,
NAME_DELETE_KEY to R.drawable.sym_keyboard_delete_rounded,
NAME_SETTINGS_KEY to R.drawable.sym_keyboard_settings_rounded,
// NAME_SPACE_KEY to null,
NAME_ENTER_KEY to R.drawable.sym_keyboard_return_rounded,
NAME_GO_KEY to R.drawable.sym_keyboard_go_rounded,
@ -271,20 +229,11 @@ class KeyboardIconsSet private constructor() {
NAME_NEXT_KEY to R.drawable.ic_arrow_right_rounded,
NAME_PREVIOUS_KEY to R.drawable.ic_arrow_left_rounded,
NAME_TAB_KEY to R.drawable.sym_keyboard_tab_rounded,
NAME_INCOGNITO_KEY to R.drawable.sym_keyboard_incognito_lxx,
NAME_SPACE_KEY_FOR_NUMBER_LAYOUT to R.drawable.sym_keyboard_space_rounded,
NAME_SHORTCUT_KEY to R.drawable.sym_keyboard_voice_rounded,
NAME_SHORTCUT_KEY_DISABLED to R.drawable.sym_keyboard_voice_off_rounded,
NAME_LANGUAGE_SWITCH_KEY to R.drawable.sym_keyboard_language_switch_lxx,
NAME_ZWNJ_KEY to R.drawable.sym_keyboard_zwnj_lxx,
NAME_ZWJ_KEY to R.drawable.sym_keyboard_zwj_lxx,
NAME_EMOJI_ACTION_KEY to R.drawable.sym_keyboard_smiley_rounded,
NAME_EMOJI_NORMAL_KEY to R.drawable.sym_keyboard_smiley_rounded,
NAME_CLIPBOARD_ACTION_KEY to R.drawable.sym_keyboard_clipboard_rounded,
NAME_CLIPBOARD_NORMAL_KEY to R.drawable.sym_keyboard_clipboard_rounded,
NAME_CLEAR_CLIPBOARD_KEY to R.drawable.sym_keyboard_clear_clipboard_rounded,
NAME_CUT_KEY to R.drawable.sym_keyboard_cut_rounded,
NAME_START_ONEHANDED_KEY to R.drawable.sym_keyboard_start_onehanded_rounded,
NAME_STOP_ONEHANDED_KEY to R.drawable.sym_keyboard_stop_onehanded_rounded,
NAME_SWITCH_ONEHANDED_KEY to R.drawable.ic_arrow_left_rounded,
NAME_RESIZE_ONEHANDED_KEY to R.drawable.ic_arrow_horizontal_rounded,
@ -326,6 +275,19 @@ class KeyboardIconsSet private constructor() {
}
} }
fun getAllIcons(context: Context): Map<String, List<Int>> {
// currently active style first
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
val iconStyle = prefs.getString(Settings.PREF_ICON_STYLE, KeyboardTheme.STYLE_MATERIAL)
return keyboardIconsMaterial.entries.associate { (name, id) ->
name to when (iconStyle) {
KeyboardTheme.STYLE_HOLO -> listOfNotNull(keyboardIconsHolo[name], keyboardIconsRounded[name], id)
KeyboardTheme.STYLE_ROUNDED -> listOfNotNull(keyboardIconsRounded[name], id, keyboardIconsHolo[name])
else -> listOfNotNull(id, keyboardIconsRounded[name], keyboardIconsHolo[name])
}
}
}
val instance = KeyboardIconsSet()
}
}

View file

@ -0,0 +1,16 @@
package helium314.keyboard.latin
import android.content.Context
import android.content.SharedPreferences
import helium314.keyboard.latin.settings.Settings
import kotlinx.serialization.json.Json
fun customIconNames(prefs: SharedPreferences) = runCatching {
Json.decodeFromString<Map<String, String>>(prefs.getString(Settings.PREF_CUSTOM_ICON_NAMES, "")!!)
}.getOrElse { emptyMap() }
fun customIconIds(context: Context, prefs: SharedPreferences) = customIconNames(prefs)
.mapNotNull { entry ->
val id = runCatching { context.resources.getIdentifier(entry.value, "drawable", context.packageName) }.getOrNull()
id?.let { entry.key to it }
}

View file

@ -10,18 +10,37 @@ import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ScrollView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.util.TypedValueCompat
import androidx.core.view.forEach
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.TwoStatePreference
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import helium314.keyboard.keyboard.KeyboardSwitcher
import helium314.keyboard.keyboard.KeyboardTheme
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
import helium314.keyboard.latin.R
import helium314.keyboard.latin.common.FileUtils
import helium314.keyboard.latin.customIconNames
import helium314.keyboard.latin.databinding.ReorderDialogItemBinding
import helium314.keyboard.latin.utils.ResourceUtils
import helium314.keyboard.latin.utils.getStringResourceOrName
import helium314.keyboard.latin.utils.infoDialog
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.lang.Float.max
import java.lang.Float.min
import java.util.*
@ -73,6 +92,7 @@ class AppearanceSettingsFragment : SubScreenFragment() {
}
}
findPreference<Preference>("custom_background_image")?.setOnPreferenceClickListener { onClickLoadImage() }
findPreference<Preference>(Settings.PREF_CUSTOM_ICON_NAMES)?.setOnPreferenceClickListener { onClickCustomizeIcons() }
}
override fun onPause() {
@ -179,6 +199,111 @@ class AppearanceSettingsFragment : SubScreenFragment() {
userColorsPrefNight?.isVisible = dayNightPref?.isChecked == true && colorsNightPref?.value == KeyboardTheme.THEME_USER_NIGHT
}
// performance is not good, but not bad enough to justify work
private fun onClickCustomizeIcons(): Boolean {
val ctx = requireContext()
val padding = ResourceUtils.toPx(8, ctx.resources)
val ll = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(padding, 3 * padding, padding, padding)
}
val d = AlertDialog.Builder(ctx)
.setTitle(R.string.customize_icons)
.setView(ScrollView(context).apply { addView(ll) })
.setPositiveButton(R.string.dialog_close, null)
.create()
val cf = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(ContextCompat.getColor(ctx, R.color.foreground), BlendModeCompat.SRC_IN)
val icons = KeyboardIconsSet.getAllIcons(ctx)
icons.keys.forEach { iconName ->
val b = ReorderDialogItemBinding.inflate(LayoutInflater.from(ctx), ll, true)
b.reorderItemIcon.setImageDrawable(KeyboardIconsSet.instance.getNewDrawable(iconName, ctx))
b.reorderItemIcon.colorFilter = cf
b.reorderItemIcon.isVisible = true
b.reorderItemName.text = iconName.getStringResourceOrName("", ctx)
if (b.reorderItemName.text == iconName)
b.reorderItemName.text = iconName.getStringResourceOrName("label_", ctx)
b.root.setOnClickListener {
customizeIcon(iconName)
d.dismiss()
}
b.reorderItemSwitch.isGone = true
b.reorderItemDragIndicator.isGone = true
}
d.show()
return true
}
// todo: icon size is an important difference between holo and others, but really awful to work with
// scaling the intrinsic icon width may look awful depending on display density
private fun customizeIcon(iconName: String) {
val ctx = requireContext()
val rv = RecyclerView(ctx)
rv.layoutManager = GridLayoutManager(ctx, 6)
val padding = ResourceUtils.toPx(6, resources)
rv.setPadding(padding, 3 * padding, padding, padding)
val icons = KeyboardIconsSet.getAllIcons(ctx)
val iconsList = icons[iconName].orEmpty().toSet().toMutableList()
val iconsSet = icons.values.flatten().toMutableSet()
iconsSet.removeAll(iconsList)
iconsList.addAll(iconsSet)
val foregroundColor = ContextCompat.getColor(ctx, R.color.foreground)
val iconColorFilter = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(foregroundColor, BlendModeCompat.SRC_IN)
var currentIconId = KeyboardIconsSet.instance.iconIds[iconName]
val adapter = object : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val v = ImageView(ctx)
v.colorFilter = iconColorFilter
v.setPadding(padding, padding, padding, padding)
return object : RecyclerView.ViewHolder(v) { }
}
override fun getItemCount(): Int = iconsList.size
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
val icon = ContextCompat.getDrawable(ctx, iconsList[position])?.mutate()
val imageView = viewHolder.itemView as? ImageView
imageView?.setImageDrawable(icon)
if (iconsList[position] == currentIconId) imageView?.setColorFilter(R.color.accent)
else imageView?.colorFilter = iconColorFilter
viewHolder.itemView.setOnClickListener { v ->
rv.forEach { (it as? ImageView)?.colorFilter = iconColorFilter }
(v as? ImageView)?.setColorFilter(R.color.accent)
currentIconId = iconsList[position]
}
}
}
rv.adapter = adapter
val title = iconName.getStringResourceOrName("", ctx).takeUnless { it == iconName }
?: iconName.getStringResourceOrName("label_", ctx)
val builder = AlertDialog.Builder(ctx)
.setTitle(title)
.setView(rv)
.setPositiveButton(android.R.string.ok) { _, _ ->
runCatching {
val icons2 = customIconNames(sharedPreferences).toMutableMap()
icons2[iconName] = currentIconId?.let { resources.getResourceEntryName(it) } ?: return@runCatching
sharedPreferences.edit().putString(Settings.PREF_CUSTOM_ICON_NAMES, Json.encodeToString(icons2)).apply()
KeyboardIconsSet.instance.loadIcons(ctx)
}
onClickCustomizeIcons()
}
.setNegativeButton(android.R.string.cancel) { _, _ -> onClickCustomizeIcons() }
if (customIconNames(sharedPreferences).contains(iconName))
builder.setNeutralButton(R.string.button_default) { _, _ ->
runCatching {
val icons2 = customIconNames(sharedPreferences).toMutableMap()
icons2.remove(iconName)
sharedPreferences.edit().putString(Settings.PREF_CUSTOM_ICON_NAMES, Json.encodeToString(icons2)).apply()
KeyboardIconsSet.instance.loadIcons(ctx)
}
onClickCustomizeIcons()
}
builder.show()
}
private fun onClickLoadImage(): Boolean {
if (Settings.readDayNightPref(sharedPreferences, resources)) {
AlertDialog.Builder(requireContext())

View file

@ -44,12 +44,15 @@ import helium314.keyboard.latin.utils.ResourceUtils;
import helium314.keyboard.latin.utils.RunInLocaleKt;
import helium314.keyboard.latin.utils.StatsUtils;
import helium314.keyboard.latin.utils.SubtypeSettingsKt;
import helium314.keyboard.latin.utils.ToolbarKey;
import helium314.keyboard.latin.utils.ToolbarUtilsKt;
import java.io.File;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
public final class Settings implements SharedPreferences.OnSharedPreferenceChangeListener {
@ -79,6 +82,9 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public static final String PREF_COLOR_BACKGROUND_SUFFIX = "background";
public static final String PREF_AUTO_USER_COLOR_SUFFIX = "_auto";
public static final String PREF_ALL_COLORS_SUFFIX = "all_colors";
public static final String PREF_CUSTOM_ICON_NAMES = "custom_icon_names";
public static final String PREF_TOOLBAR_CUSTOM_KEY_CODES = "toolbar_custom_key_codes";
public static final String PREF_TOOLBAR_CUSTOM_LONGPRESS_CODES = "toolbar_custom_longpress_codes";
public static final String PREF_AUTO_CAP = "auto_cap";
public static final String PREF_VIBRATE_ON = "vibrate_on";
@ -187,6 +193,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
// static cache for background images to avoid potentially slow reload on every settings reload
private static Drawable sCachedBackgroundDay;
private static Drawable sCachedBackgroundNight;
private Map<String, Integer> mCustomToolbarKeyCodes = null;
private Map<String, Integer> mCustomToolbarLongpressCodes = null;
private static final Settings sInstance = new Settings();
@ -234,6 +242,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
Log.w(TAG, "onSharedPreferenceChanged called before loadSettings.");
return;
}
mCustomToolbarLongpressCodes = null;
mCustomToolbarKeyCodes = null;
loadSettings(mContext, mSettingsValues.mLocale, mSettingsValues.mInputAttributes);
StatsUtils.onLoadSettings(mSettingsValues);
} finally {
@ -706,4 +716,16 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
public String readCustomCurrencyKey() {
return mPrefs.getString(PREF_CUSTOM_CURRENCY_KEY, "");
}
public Integer getCustomToolbarKeyCode(ToolbarKey key) {
if (mCustomToolbarKeyCodes == null)
mCustomToolbarKeyCodes = ToolbarUtilsKt.readCustomKeyCodes(mPrefs);
return mCustomToolbarKeyCodes.get(key.name());
}
public Integer getCustomToolbarLongpressCode(ToolbarKey key) {
if (mCustomToolbarLongpressCodes == null)
mCustomToolbarLongpressCodes = ToolbarUtilsKt.readCustomLongpressCodes(mPrefs);
return mCustomToolbarLongpressCodes.get(key.name());
}
}

View file

@ -10,6 +10,7 @@ import helium314.keyboard.latin.utils.defaultClipboardToolbarPref
import helium314.keyboard.latin.utils.defaultPinnedToolbarPref
import helium314.keyboard.latin.utils.defaultToolbarPref
import helium314.keyboard.latin.utils.reorderDialog
import helium314.keyboard.latin.utils.toolbarKeysCustomizer
class ToolbarSettingsFragment : SubScreenFragment() {
private var reloadKeyboard = false
@ -44,6 +45,11 @@ class ToolbarSettingsFragment : SubScreenFragment() {
) { iconsSet.getNewDrawable(it, requireContext()) }
true
}
findPreference<Preference>("customize_key_codes")?.onPreferenceClickListener =
Preference.OnPreferenceClickListener {
toolbarKeysCustomizer(requireContext())
true
}
}
override fun onPause() {

View file

@ -182,7 +182,7 @@ public final class SuggestionStripView extends RelativeLayout implements OnClick
mMoreSuggestionsSlidingDetector = new GestureDetector(context, mMoreSuggestionsSlidingListener);
final KeyboardIconsSet iconsSet = KeyboardIconsSet.Companion.getInstance();
mIncognitoIcon = iconsSet.getNewDrawable(KeyboardIconsSet.NAME_INCOGNITO_KEY, context);
mIncognitoIcon = iconsSet.getNewDrawable(ToolbarKey.INCOGNITO.name(), context);
mToolbarArrowIcon = iconsSet.getNewDrawable(KeyboardIconsSet.NAME_TOOLBAR_KEY, context);
mBinIcon = iconsSet.getNewDrawable(KeyboardIconsSet.NAME_BIN, context);

View file

@ -1,16 +1,33 @@
// SPDX-License-Identifier: GPL-3.0-only
package helium314.keyboard.latin.utils
import android.annotation.SuppressLint
import android.content.Context
import android.content.DialogInterface
import android.content.SharedPreferences
import android.view.LayoutInflater
import android.widget.EditText
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.ScrollView
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.core.content.edit
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.doAfterTextChanged
import helium314.keyboard.keyboard.internal.KeyboardIconsSet
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode
import helium314.keyboard.keyboard.internal.keyboard_parser.floris.KeyCode.checkAndConvertCode
import helium314.keyboard.latin.R
import helium314.keyboard.latin.databinding.ReorderDialogItemBinding
import helium314.keyboard.latin.settings.Settings
import helium314.keyboard.latin.utils.ToolbarKey.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.util.EnumMap
import java.util.Locale
@ -31,7 +48,7 @@ fun createToolbarKey(context: Context, iconsSet: KeyboardIconsSet, key: ToolbarK
return button
}
fun getCodeForToolbarKey(key: ToolbarKey) = when (key) {
fun getCodeForToolbarKey(key: ToolbarKey) = Settings.getInstance().getCustomToolbarKeyCode(key) ?: when (key) {
VOICE -> KeyCode.VOICE_INPUT
CLIPBOARD -> KeyCode.CLIPBOARD
NUMPAD -> KeyCode.NUMPAD
@ -63,7 +80,7 @@ fun getCodeForToolbarKey(key: ToolbarKey) = when (key) {
PAGE_END -> KeyCode.MOVE_END_OF_PAGE
}
fun getCodeForToolbarKeyLongClick(key: ToolbarKey) = when (key) {
fun getCodeForToolbarKeyLongClick(key: ToolbarKey) = Settings.getInstance().getCustomToolbarLongpressCode(key) ?: when (key) {
CLIPBOARD -> KeyCode.CLIPBOARD_PASTE
UNDO -> KeyCode.REDO
REDO -> KeyCode.UNDO
@ -175,3 +192,109 @@ private fun getEnabledToolbarKeys(prefs: SharedPreferences, pref: String, defaul
} else null
}
}
fun toolbarKeysCustomizer(context: Context) {
val padding = ResourceUtils.toPx(8, context.resources)
val ll = LinearLayout(context).apply {
orientation = LinearLayout.VERTICAL
setPadding(3 * padding, padding, padding, padding)
}
val dialog = AlertDialog.Builder(context)
.setTitle(R.string.customize_toolbar_key_codes)
.setView(ScrollView(context).apply { addView(ll) })
.setPositiveButton(R.string.dialog_close, null)
.create()
val cf = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(ContextCompat.getColor(context, R.color.foreground), BlendModeCompat.SRC_IN)
ToolbarKey.entries.forEach { key ->
val binding = ReorderDialogItemBinding.inflate(LayoutInflater.from(context), ll, true)
binding.reorderItemIcon.setImageDrawable(KeyboardIconsSet.instance.getNewDrawable(key.name, context))
binding.reorderItemIcon.colorFilter = cf
binding.reorderItemIcon.isVisible = true
binding.reorderItemName.text = key.name.lowercase().getStringResourceOrName("", context)
binding.root.setOnClickListener {
toolbarKeyCustomizer(context, key)
dialog.dismiss()
}
binding.reorderItemSwitch.isGone = true
binding.reorderItemDragIndicator.isGone = true
}
dialog.show()
}
@SuppressLint("SetTextI18n")
private fun toolbarKeyCustomizer(context: Context, key: ToolbarKey) {
val layout = LayoutInflater.from(context).inflate(R.layout.toolbar_key_customizer, null)
val prefs = DeviceProtectedUtils.getSharedPreferences(context)
var keyCode: String? = null
var longpressCode: String? = null
val builder = AlertDialog.Builder(context)
.setTitle(key.name.lowercase().getStringResourceOrName("", context))
.setView(ScrollView(context).apply { addView(layout) })
.setPositiveButton(android.R.string.ok) { _, _ ->
val newKeyCode = runCatching { keyCode?.toIntOrNull()?.checkAndConvertCode() }.getOrNull()?.takeIf { it < Char.MAX_VALUE.code }
val newLongpressCode = runCatching { longpressCode?.toIntOrNull()?.checkAndConvertCode() }.getOrNull()?.takeIf { it < Char.MAX_VALUE.code }
if (newKeyCode != null)
writeCustomKeyCodes(prefs, readCustomKeyCodes(prefs) + (key.name to newKeyCode))
if (newLongpressCode != null)
writeCustomLongpressCodes(prefs, readCustomLongpressCodes(prefs) + (key.name to newLongpressCode))
toolbarKeysCustomizer(context)
}
.setNegativeButton(android.R.string.cancel) { _, _ -> toolbarKeysCustomizer(context) }
if (readCustomKeyCodes(prefs).containsKey(key.name) || readCustomLongpressCodes(prefs).containsKey(key.name))
builder.setNeutralButton(R.string.button_default) { _, _ ->
val keys = readCustomKeyCodes(prefs).toMutableMap()
keys.remove(key.name)
prefs.edit().putString(Settings.PREF_TOOLBAR_CUSTOM_KEY_CODES, Json.encodeToString(keys)).apply()
val longpressKeys = readCustomLongpressCodes(prefs).toMutableMap()
longpressKeys.remove(key.name)
prefs.edit().putString(Settings.PREF_TOOLBAR_CUSTOM_LONGPRESS_CODES, Json.encodeToString(longpressKeys)).apply()
toolbarKeysCustomizer(context)
}
val dialog = builder.create()
fun checkOk() {
val keyOk = keyCode == null
|| runCatching { keyCode?.toIntOrNull()?.let { it.checkAndConvertCode() <= Char.MAX_VALUE.code } }.getOrNull() ?: false
val longPressOk = longpressCode == null
|| runCatching { longpressCode?.toIntOrNull()?.let { it.checkAndConvertCode() <= Char.MAX_VALUE.code } }.getOrNull() ?: false
dialog.getButton(DialogInterface.BUTTON_POSITIVE)?.isEnabled = keyOk && longPressOk
}
layout.findViewById<EditText>(R.id.toolbar_key_code)?.apply {
setText(getCodeForToolbarKey(key).toString())
doAfterTextChanged {
keyCode = it?.toString()
checkOk()
}
}
layout.findViewById<EditText>(R.id.toolbar_key_longpress_code)?.apply {
setText(getCodeForToolbarKeyLongClick(key).toString())
doAfterTextChanged {
longpressCode = it?.toString()
checkOk()
}
}
dialog.show()
}
fun readCustomKeyCodes(prefs: SharedPreferences) = prefs.getString(Settings.PREF_TOOLBAR_CUSTOM_KEY_CODES, "")!!
.split(";").associate {
val code = runCatching { it.substringAfter(",").toIntOrNull()?.checkAndConvertCode() }.getOrNull()
it.substringBefore(",") to code
}
fun readCustomLongpressCodes(prefs: SharedPreferences) = prefs.getString(Settings.PREF_TOOLBAR_CUSTOM_LONGPRESS_CODES, "")!!
.split(";").associate {
val code = runCatching { it.substringAfter(",").toIntOrNull()?.checkAndConvertCode() }.getOrNull()
it.substringBefore(",") to code
}
private fun writeCustomKeyCodes(prefs: SharedPreferences, codes: Map<String, Int?>) {
val string = codes.mapNotNull { entry -> entry.value?.let { "${entry.key},$it" } }.joinToString(";")
prefs.edit().putString(Settings.PREF_TOOLBAR_CUSTOM_KEY_CODES, string).apply()
}
private fun writeCustomLongpressCodes(prefs: SharedPreferences, codes: Map<String, Int?>) {
val string = codes.mapNotNull { entry -> entry.value?.let { "${entry.key},$it" } }.joinToString(";")
prefs.edit().putString(Settings.PREF_TOOLBAR_CUSTOM_LONGPRESS_CODES, string).apply()
}

View file

@ -31,6 +31,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<ImageView
android:id="@+id/reorder_item_drag_indicator"
android:paddingEnd="10dp"
android:src="@drawable/ic_drag_indicator"
app:tint="@color/foreground_weak"

View file

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingHorizontal="10dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/PreferenceTitleText"
android:text="@string/key_code" />
<EditText
android:id="@+id/toolbar_key_code"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="number"
android:layout_weight="1" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
style="@style/PreferenceTitleText"
android:text="@string/long_press_code" />
<EditText
android:id="@+id/toolbar_key_longpress_code"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:inputType="number"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>

View file

@ -881,6 +881,36 @@ New dictionary:
<string name="label_pause_key" tools:keep="@string/label_pause_key">Pause</string>
<!-- Label for "Wait" key of phone number keyboard. Must be short to fit on key. 5 chars or less is preferable. [CHAR LIMIT=7]-->
<string name="label_wait_key" tools:keep="@string/label_wait_key">Wait</string>
<!-- Label for enter key, currently used only for showing the icon name -->
<string name="label_enter_key" tools:keep="@string/label_enter_key">Enter</string>
<!-- Label for tabulator key, currently used only for showing the icon name -->
<string name="label_tab_key" tools:keep="@string/label_tab_key">Tab</string>
<!-- Label for delete key, currently used only for showing the icon name -->
<string name="label_delete_key" tools:keep="@string/label_delete_key">Delete</string>
<!-- Label for shift key, currently used only for showing the icon name -->
<string name="label_shift_key" tools:keep="@string/label_shift_key">Shift</string>
<!-- Label for shift key when active, currently used only for showing the icon name -->
<string name="label_shift_key_shifted" tools:keep="@string/label_shift_key_shifted">Shift (shifted)</string>
<!-- Label for shift key when locked (caps lock mode), currently used only for showing the icon name -->
<string name="label_shift_key_locked" tools:keep="@string/label_shift_key_locked">Caps lock</string>
<!-- Label for the short space key (in number layouts), currently used only for showing the icon name -->
<string name="label_space_key_for_number_layout" tools:keep="@string/label_space_key_for_number_layout">Space (number layout)</string>
<!-- Label for key to stop onehanded mode, currently used only for showing the icon name -->
<string name="label_stop_onehanded_mode_key" tools:keep="@string/label_stop_onehanded_mode_key">End one-handed mode</string>
<!-- Label for key to resize onehanded mode, currently used only for showing the icon name -->
<string name="label_resize_onehanded_key" tools:keep="@string/label_resize_onehanded_key">Resize one-handed mode</string>
<!-- Label for key to switch side of onehanded mode, currently used only for showing the icon name -->
<string name="label_switch_onehanded_key" tools:keep="@string/label_switch_onehanded_key">Switch one-handed mode side</string>
<!-- Label for voice key when disabled, currently used only for showing the icon name -->
<string name="label_shortcut_key_disabled" tools:keep="@string/label_shortcut_key_disabled">Voice input disabled</string>
<!-- Label for toolbar key, currently used only for showing the icon name -->
<string name="label_toolbar_key" tools:keep="@string/label_toolbar_key">Show / hide toolbar</string>
<!-- Label for language switch key, currently used only for showing the icon name -->
<string name="label_language_switch_key" tools:keep="@string/label_language_switch_key">@string/show_language_switch_key</string>
<!-- Label for zero-width joiner key, currently used only for showing the icon name -->
<string name="label_zwj_key" tools:keep="@string/label_zwj_key">Zero-width joiner</string>
<!-- Label for zero-width non-joiner key, currently used only for showing the icon name -->
<string name="label_zwnj_key" tools:keep="@string/label_zwnj_key">Zero-width non-joiner</string>
<!-- Title of the setting for horizontal spacebar swipe gesture -->
<string name="show_horizontal_space_swipe">Horizontal spacebar swipe gesture</string>
<!-- Title of the setting for vertical spacebar swipe gesture -->
@ -895,6 +925,12 @@ New dictionary:
<string name="var_toolbar_direction">Variable toolbar direction</string>
<!-- Description of the variable toolbar direction setting -->
<string name="var_toolbar_direction_summary">Reverse direction when a right-to-left keyboard subtype is selected</string>
<!-- Title of the setting to customize toolbar key codes -->
<string name="customize_toolbar_key_codes">Customize toolbar key codes</string>
<!-- Text for showing / setting key code -->
<string name="key_code">Key code</string>
<!-- Text for showing / setting long press code -->
<string name="long_press_code">Long press code</string>
<!-- Title of the setting for showing the toolbar automatically -->
<string name="auto_show_toolbar">Auto show toolbar</string>
<!-- Description of the setting for showing the toolbar automatically -->
@ -905,4 +941,6 @@ New dictionary:
<string name="auto_hide_toolbar_summary">Hide the toolbar when suggestions become available</string>
<!-- Toast message shown when content is copied to the clipboard -->
<string name="toast_msg_clipboard_copy">Content copied</string>
<!-- Title of the setting to customize icons for keyboard and toolbar keys -->
<string name="customize_icons">Customize icons</string>
</resources>

View file

@ -25,6 +25,10 @@
android:defaultValue="Material"
latin:singleLineTitle="false" />
<Preference
android:key="custom_icon_names"
android:title="@string/customize_icons" />
<ListPreference
android:key="theme_colors"
android:title="@string/theme_colors"

View file

@ -21,6 +21,10 @@
android:key="clipboard_toolbar_keys"
android:title="@string/clipboard_toolbar_keys" />
<Preference
android:key="customize_key_codes"
android:title="@string/customize_toolbar_key_codes" />
<SwitchPreference
android:key="quick_pin_toolbar_keys"
android:title="@string/quick_pin_toolbar_keys"