Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow changing toolbar icon and codes #1110

Merged
merged 14 commits into from
Sep 23, 2024
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ❤

Expand Down
7 changes: 4 additions & 3 deletions app/src/main/java/helium314/keyboard/keyboard/Key.java
Original file line number Diff line number Diff line change
Expand Up @@ -938,11 +938,12 @@ public final boolean isAccentColored() {
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() {
Expand Down

Large diffs are not rendered by default.

16 changes: 16 additions & 0 deletions app/src/main/java/helium314/keyboard/latin/Settings.kt
Original file line number Diff line number Diff line change
@@ -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 }
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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())
Expand Down
22 changes: 22 additions & 0 deletions app/src/main/java/helium314/keyboard/latin/settings/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@
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 {
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -234,6 +242,8 @@ public void onSharedPreferenceChanged(final SharedPreferences prefs, final Strin
Log.w(TAG, "onSharedPreferenceChanged called before loadSettings.");
return;
}
mCustomToolbarLongpressCodes = null;
mCustomToolbarKeyCodes = null;
loadSettings(mContext, mSettingsValues.mLocale, mSettingsValues.mInputAttributes);
StatsUtils.onLoadSettings(mSettingsValues);
} finally {
Expand Down Expand Up @@ -706,4 +716,16 @@ public String getInLocale(@StringRes final int resId, final Locale locale) {
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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public SuggestionStripView(final Context context, final AttributeSet attrs, fina
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);

Expand Down
Loading