mirror of
https://github.com/Helium314/HeliBoard.git
synced 2025-06-23 17:40:54 +00:00
store pinned clips in shared prferences instead of separate file
This commit is contained in:
parent
aad8c68bf9
commit
2dc9b12b13
4 changed files with 58 additions and 135 deletions
|
@ -2,14 +2,32 @@
|
|||
|
||||
package org.dslul.openboard.inputmethod.latin
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
@Serializable
|
||||
data class ClipboardHistoryEntry (
|
||||
var timeStamp: Long,
|
||||
@Serializable(with = CharSequenceStringSerializer::class)
|
||||
val content: CharSequence,
|
||||
var isPinned: Boolean = false
|
||||
) : Comparable<ClipboardHistoryEntry> {
|
||||
|
||||
override fun compareTo(other: ClipboardHistoryEntry): Int {
|
||||
val result = other.isPinned.compareTo(isPinned)
|
||||
return if (result != 0) result else other.timeStamp.compareTo(timeStamp)
|
||||
}
|
||||
}
|
||||
|
||||
class CharSequenceStringSerializer : KSerializer<CharSequence> {
|
||||
override val descriptor = PrimitiveSerialDescriptor("CharSequence", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: CharSequence) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder) = decoder.decodeString()
|
||||
}
|
|
@ -5,29 +5,26 @@ package org.dslul.openboard.inputmethod.latin
|
|||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.text.TextUtils
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import org.dslul.openboard.inputmethod.compat.ClipboardManagerCompat
|
||||
import org.dslul.openboard.inputmethod.latin.utils.JsonUtils
|
||||
import java.io.File
|
||||
import java.lang.Exception
|
||||
import java.util.*
|
||||
import org.dslul.openboard.inputmethod.latin.settings.Settings
|
||||
import org.dslul.openboard.inputmethod.latin.utils.DeviceProtectedUtils
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class ClipboardHistoryManager(
|
||||
private val latinIME: LatinIME
|
||||
) : ClipboardManager.OnPrimaryClipChangedListener {
|
||||
|
||||
private lateinit var pinnedHistoryClipsFile: File
|
||||
private lateinit var clipboardManager: ClipboardManager
|
||||
private val historyEntries: MutableList<ClipboardHistoryEntry>
|
||||
private val historyEntries: MutableList<ClipboardHistoryEntry> = ArrayList()
|
||||
private var onHistoryChangeListener: OnHistoryChangeListener? = null
|
||||
|
||||
fun onCreate() {
|
||||
pinnedHistoryClipsFile = File(latinIME.filesDir, PINNED_CLIPS_DATA_FILE_NAME)
|
||||
clipboardManager = latinIME.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
fetchPrimaryClip()
|
||||
clipboardManager.addPrimaryClipChangedListener(this)
|
||||
startLoadPinnedClipsFromDisk()
|
||||
loadPinnedClips()
|
||||
}
|
||||
|
||||
fun onPinnedClipsAvailable(pinnedClips: List<ClipboardHistoryEntry>) {
|
||||
|
@ -82,7 +79,7 @@ class ClipboardHistoryManager(
|
|||
sortHistoryEntries()
|
||||
val to = historyEntries.indexOf(historyEntry)
|
||||
onHistoryChangeListener?.onClipboardHistoryEntryMoved(from, to)
|
||||
startSavePinnedClipsToDisk()
|
||||
savePinnedClips()
|
||||
}
|
||||
|
||||
fun clearHistory() {
|
||||
|
@ -127,57 +124,16 @@ class ClipboardHistoryManager(
|
|||
return clipData.getItemAt(0)?.coerceToText(latinIME) ?: ""
|
||||
}
|
||||
|
||||
private fun startLoadPinnedClipsFromDisk() {
|
||||
object : Thread("$TAG-load") {
|
||||
override fun run() {
|
||||
loadFromDisk()
|
||||
}
|
||||
}.start()
|
||||
private fun loadPinnedClips() {
|
||||
val pinnedClipString = Settings.readPinnedClipString(DeviceProtectedUtils.getSharedPreferences(latinIME))
|
||||
if (pinnedClipString.isEmpty()) return
|
||||
val pinnedClips: List<ClipboardHistoryEntry> = Json.decodeFromString(pinnedClipString)
|
||||
latinIME.mHandler.postUpdateClipboardPinnedClips(pinnedClips)
|
||||
}
|
||||
|
||||
private fun loadFromDisk() {
|
||||
// Debugging
|
||||
if (pinnedHistoryClipsFile.exists() && !pinnedHistoryClipsFile.canRead()) {
|
||||
Log.w(TAG, "Attempt to read pinned clips file $pinnedHistoryClipsFile without permission")
|
||||
}
|
||||
var list = emptyList<ClipboardHistoryEntry>()
|
||||
try {
|
||||
if (pinnedHistoryClipsFile.exists()) {
|
||||
val bytes = Base64.decode(pinnedHistoryClipsFile.readText(), Base64.DEFAULT)
|
||||
list = JsonUtils.jsonBytesToHistoryEntryList(bytes)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't retrieve $pinnedHistoryClipsFile content", e)
|
||||
}
|
||||
latinIME.mHandler.postUpdateClipboardPinnedClips(list)
|
||||
}
|
||||
|
||||
private fun startSavePinnedClipsToDisk() {
|
||||
val localCopy = historyEntries.filter { it.isPinned }.map { it.copy() }
|
||||
object : Thread("$TAG-save") {
|
||||
override fun run() {
|
||||
saveToDisk(localCopy)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun saveToDisk(list: List<ClipboardHistoryEntry>) {
|
||||
// Debugging
|
||||
if (pinnedHistoryClipsFile.exists() && !pinnedHistoryClipsFile.canWrite()) {
|
||||
Log.w(TAG, "Attempt to write pinned clips file $pinnedHistoryClipsFile without permission")
|
||||
}
|
||||
try {
|
||||
pinnedHistoryClipsFile.createNewFile()
|
||||
val jsonStr = JsonUtils.historyEntryListToJsonStr(list)
|
||||
if (!TextUtils.isEmpty(jsonStr)) {
|
||||
val rawText = Base64.encodeToString(jsonStr.encodeToByteArray(), Base64.DEFAULT)
|
||||
pinnedHistoryClipsFile.writeText(rawText)
|
||||
} else {
|
||||
pinnedHistoryClipsFile.writeText("")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Couldn't write to $pinnedHistoryClipsFile", e)
|
||||
}
|
||||
private fun savePinnedClips() {
|
||||
val pinnedClips = Json.encodeToString(historyEntries.filter { it.isPinned })
|
||||
Settings.writePinnedClipString(DeviceProtectedUtils.getSharedPreferences(latinIME), pinnedClips)
|
||||
}
|
||||
|
||||
interface OnHistoryChangeListener {
|
||||
|
@ -185,13 +141,4 @@ class ClipboardHistoryManager(
|
|||
fun onClipboardHistoryEntriesRemoved(pos: Int, count: Int)
|
||||
fun onClipboardHistoryEntryMoved(from: Int, to: Int)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PINNED_CLIPS_DATA_FILE_NAME = "pinned_clips.data"
|
||||
const val TAG = "ClipboardHistoryManager"
|
||||
}
|
||||
|
||||
init {
|
||||
historyEntries = LinkedList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ import org.dslul.openboard.inputmethod.latin.utils.StatsUtils;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
@ -133,6 +134,7 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_ID = "last_shown_emoji_category_id";
|
||||
public static final String PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID = "last_shown_emoji_category_page_id";
|
||||
|
||||
public static final String PREF_PINNED_CLIPS = "pinned_clips";
|
||||
// used as a workaround against keyboard not showing edited theme in ColorsSettingsFragment
|
||||
public static final String PREF_FORCE_OPPOSITE_THEME = "force_opposite_theme";
|
||||
|
||||
|
@ -147,6 +149,16 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
|
||||
private static final Settings sInstance = new Settings();
|
||||
|
||||
// preferences that are not used in SettingsValues
|
||||
private static final HashSet<String> dontReloadOnChanged = new HashSet<>() {{
|
||||
add(PREF_FORCE_OPPOSITE_THEME);
|
||||
add(PREF_PINNED_CLIPS);
|
||||
add(PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID);
|
||||
add(PREF_LAST_SHOWN_EMOJI_CATEGORY_ID);
|
||||
add(PREF_EMOJI_RECENT_KEYS);
|
||||
add(PREF_DONT_SHOW_MISSING_DICTIONARY_DIALOG);
|
||||
}};
|
||||
|
||||
public static Settings getInstance() {
|
||||
return sInstance;
|
||||
}
|
||||
|
@ -172,6 +184,8 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
|
||||
if (dontReloadOnChanged.contains(key))
|
||||
return;
|
||||
mSettingsValuesLock.lock();
|
||||
try {
|
||||
if (mSettingsValues == null) {
|
||||
|
@ -425,6 +439,14 @@ public final class Settings implements SharedPreferences.OnSharedPreferenceChang
|
|||
return prefs.getInt(PREF_LAST_SHOWN_EMOJI_CATEGORY_PAGE_ID, defValue);
|
||||
}
|
||||
|
||||
public static String readPinnedClipString(final SharedPreferences prefs) {
|
||||
return prefs.getString(PREF_PINNED_CLIPS, "");
|
||||
}
|
||||
|
||||
public static void writePinnedClipString(final SharedPreferences prefs, final String clips) {
|
||||
prefs.edit().putString(PREF_PINNED_CLIPS, clips).apply();
|
||||
}
|
||||
|
||||
public static List<String> readPinnedKeys(final SharedPreferences prefs) {
|
||||
final String pinnedKeysString = prefs.getString(Settings.PREF_PINNED_KEYS, "");
|
||||
if (pinnedKeysString.isEmpty())
|
||||
|
|
|
@ -6,20 +6,15 @@
|
|||
|
||||
package org.dslul.openboard.inputmethod.latin.utils;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.JsonReader;
|
||||
import android.util.JsonWriter;
|
||||
import android.util.Log;
|
||||
import org.dslul.openboard.inputmethod.latin.ClipboardHistoryEntry;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
|
@ -28,8 +23,6 @@ public final class JsonUtils {
|
|||
|
||||
private static final String INTEGER_CLASS_NAME = Integer.class.getSimpleName();
|
||||
private static final String STRING_CLASS_NAME = String.class.getSimpleName();
|
||||
private static final String CLIPBOARD_HISTORY_ENTRY_ID_KEY = "id";
|
||||
private static final String CLIPBOARD_HISTORY_ENTRY_CONTENT_KEY = "content";
|
||||
|
||||
private static final String EMPTY_STRING = "";
|
||||
|
||||
|
@ -88,63 +81,6 @@ public final class JsonUtils {
|
|||
return EMPTY_STRING;
|
||||
}
|
||||
|
||||
public static List<ClipboardHistoryEntry> jsonBytesToHistoryEntryList(final byte[] bytes) {
|
||||
final ArrayList<ClipboardHistoryEntry> list = new ArrayList<>();
|
||||
final JsonReader reader = new JsonReader(new InputStreamReader(new ByteArrayInputStream(bytes)));
|
||||
try {
|
||||
reader.beginArray();
|
||||
while (reader.hasNext()) {
|
||||
reader.beginObject();
|
||||
long id = 0;
|
||||
String content = EMPTY_STRING;
|
||||
while (reader.hasNext()) {
|
||||
final String name = reader.nextName();
|
||||
if (name.equals(CLIPBOARD_HISTORY_ENTRY_ID_KEY)) {
|
||||
id = reader.nextLong();
|
||||
} else if (name.equals(CLIPBOARD_HISTORY_ENTRY_CONTENT_KEY)) {
|
||||
content = reader.nextString();
|
||||
} else {
|
||||
Log.w(TAG, "Invalid name: " + name);
|
||||
reader.skipValue();
|
||||
}
|
||||
}
|
||||
if (id > 0 && !TextUtils.isEmpty(content)) {
|
||||
list.add(new ClipboardHistoryEntry(id, content, true));
|
||||
}
|
||||
reader.endObject();
|
||||
}
|
||||
reader.endArray();
|
||||
return list;
|
||||
} catch (final IOException e) {
|
||||
} finally {
|
||||
close(reader);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public static String historyEntryListToJsonStr(final Collection<ClipboardHistoryEntry> entries) {
|
||||
if (entries == null || entries.isEmpty()) {
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
final StringWriter sw = new StringWriter();
|
||||
final JsonWriter writer = new JsonWriter(sw);
|
||||
try {
|
||||
writer.beginArray();
|
||||
for (final ClipboardHistoryEntry e : entries) {
|
||||
writer.beginObject();
|
||||
writer.name(CLIPBOARD_HISTORY_ENTRY_ID_KEY).value(e.getTimeStamp());
|
||||
writer.name(CLIPBOARD_HISTORY_ENTRY_CONTENT_KEY).value(e.getContent().toString());
|
||||
writer.endObject();
|
||||
}
|
||||
writer.endArray();
|
||||
return sw.toString();
|
||||
} catch (final IOException e) {
|
||||
} finally {
|
||||
close(writer);
|
||||
}
|
||||
return EMPTY_STRING;
|
||||
}
|
||||
|
||||
private static void close(final Closeable closeable) {
|
||||
try {
|
||||
if (closeable != null) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue