From 0d365d55c5ea39bc1f77ee79f1ab17b366579369 Mon Sep 17 00:00:00 2001 From: rebel onion <87634197+rebelonion@users.noreply.github.com> Date: Fri, 3 Jan 2025 09:54:29 -0600 Subject: [PATCH] fix: ordering of search history --- .../dantotsu/media/SearchHistoryAdapter.kt | 58 ++++++++++++------- .../dantotsu/settings/saving/PrefManager.kt | 14 +++-- .../dantotsu/settings/saving/Preferences.kt | 15 ++--- .../saving/SharedPreferenceLiveData.kt | 39 +++++++++++++ 4 files changed, 95 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt b/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt index 70619036..f6d46583 100644 --- a/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt +++ b/app/src/main/java/ani/dantotsu/media/SearchHistoryAdapter.kt @@ -10,52 +10,70 @@ import ani.dantotsu.R import ani.dantotsu.connections.anilist.AnilistSearch.SearchType import ani.dantotsu.databinding.ItemSearchHistoryBinding import ani.dantotsu.settings.saving.PrefManager -import ani.dantotsu.settings.saving.PrefManager.asLiveStringSet +import ani.dantotsu.settings.saving.PrefManager.asLiveClass import ani.dantotsu.settings.saving.PrefName -import ani.dantotsu.settings.saving.SharedPreferenceStringSetLiveData +import ani.dantotsu.settings.saving.SharedPreferenceClassLiveData +import java.io.Serializable + +data class SearchHistory(val search: String, val time: Long) : Serializable { + companion object { + private const val serialVersionUID = 1L + } +} class SearchHistoryAdapter(type: SearchType, private val searchClicked: (String) -> Unit) : ListAdapter( DIFF_CALLBACK_INSTALLED ) { - private var searchHistoryLiveData: SharedPreferenceStringSetLiveData? = null - private var searchHistory: MutableSet? = null + private var searchHistoryLiveData: SharedPreferenceClassLiveData>? = null + private var searchHistory: MutableList? = null private var historyType: PrefName = when (type) { - SearchType.ANIME -> PrefName.AnimeSearchHistory - SearchType.MANGA -> PrefName.MangaSearchHistory - SearchType.CHARACTER -> PrefName.CharacterSearchHistory - SearchType.STAFF -> PrefName.StaffSearchHistory - SearchType.STUDIO -> PrefName.StudioSearchHistory - SearchType.USER -> PrefName.UserSearchHistory + SearchType.ANIME -> PrefName.SortedAnimeSH + SearchType.MANGA -> PrefName.SortedMangaSH + SearchType.CHARACTER -> PrefName.SortedCharacterSH + SearchType.STAFF -> PrefName.SortedStaffSH + SearchType.STUDIO -> PrefName.SortedStudioSH + SearchType.USER -> PrefName.SortedUserSH } + private fun MutableList?.sorted(): List? = + this?.sortedByDescending { it.time }?.map { it.search } + init { searchHistoryLiveData = - PrefManager.getLiveVal(historyType, mutableSetOf()).asLiveStringSet() - searchHistoryLiveData?.observeForever { - searchHistory = it.toMutableSet() - submitList(searchHistory?.toList()) + PrefManager.getLiveVal(historyType, mutableListOf()).asLiveClass() + searchHistoryLiveData?.observeForever { data -> + searchHistory = data.toMutableList() + submitList(searchHistory?.sorted()) } } fun remove(item: String) { - searchHistory?.remove(item) + searchHistory?.let { list -> + list.removeAll { it.search == item } + } PrefManager.setVal(historyType, searchHistory) - submitList(searchHistory?.toList()) + submitList(searchHistory?.sorted()) } fun add(item: String) { - if (searchHistory?.contains(item) == true || item.isBlank()) return + val maxSize = 25 + if (searchHistory?.any { it.search == item } == true || item.isBlank()) return if (PrefManager.getVal(PrefName.Incognito)) return - searchHistory?.add(item) - submitList(searchHistory?.toList()) + searchHistory?.add(SearchHistory(item, System.currentTimeMillis())) + if ((searchHistory?.size ?: 0) > maxSize) { + searchHistory?.removeAt( + searchHistory?.sorted()?.lastIndex ?: 0 + ) + } + submitList(searchHistory?.sorted()) PrefManager.setVal(historyType, searchHistory) } fun clearHistory() { searchHistory?.clear() PrefManager.setVal(historyType, searchHistory) - submitList(searchHistory?.toList()) + submitList(searchHistory?.sorted()) } override fun onCreateViewHolder( diff --git a/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt b/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt index c0833ee2..c7e9b880 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/PrefManager.kt @@ -255,9 +255,6 @@ object PrefManager { return allEntries } - - - @Suppress("UNCHECKED_CAST") fun getLiveVal(prefName: PrefName, default: T): SharedPreferenceLiveData { val pref = getPrefLocation(prefName.data.prefLocation) @@ -298,7 +295,11 @@ object PrefManager { default as Set ) as SharedPreferenceLiveData - else -> throw IllegalArgumentException("Type not supported") + else -> SharedPreferenceClassLiveData( + pref, + prefName.name, + default + ) } } @@ -326,6 +327,11 @@ object PrefManager { this as? SharedPreferenceStringSetLiveData ?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData>") + @Suppress("UNCHECKED_CAST") + inline fun SharedPreferenceLiveData<*>.asLiveClass(): SharedPreferenceClassLiveData = + this as? SharedPreferenceClassLiveData + ?: throw ClassCastException("Cannot cast to SharedPreferenceLiveData") + fun getAnimeDownloadPreferences(): SharedPreferences = animeDownloadsPreferences!! //needs to be used externally diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt index 03958657..5be84d99 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt @@ -3,12 +3,13 @@ package ani.dantotsu.settings.saving import android.graphics.Color import ani.dantotsu.connections.comments.AuthResponse import ani.dantotsu.connections.mal.MAL +import ani.dantotsu.media.SearchHistory import ani.dantotsu.notifications.comment.CommentStore import ani.dantotsu.notifications.subscription.SubscriptionStore import ani.dantotsu.settings.saving.internal.Location import ani.dantotsu.settings.saving.internal.Pref -enum class PrefName(val data: Pref) { //TODO: Split this into multiple files +enum class PrefName(val data: Pref) { //General SharedUserID(Pref(Location.General, Boolean::class, true)), OfflineView(Pref(Location.General, Int::class, 0)), @@ -34,13 +35,13 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files MangaExtensionRepos(Pref(Location.General, Set::class, setOf())), NovelExtensionRepos(Pref(Location.General, Set::class, setOf())), AnimeSourcesOrder(Pref(Location.General, List::class, listOf())), - AnimeSearchHistory(Pref(Location.General, Set::class, setOf())), MangaSourcesOrder(Pref(Location.General, List::class, listOf())), - MangaSearchHistory(Pref(Location.General, Set::class, setOf())), - CharacterSearchHistory(Pref(Location.General, Set::class, setOf())), - StaffSearchHistory(Pref(Location.General, Set::class, setOf())), - StudioSearchHistory(Pref(Location.General, Set::class, setOf())), - UserSearchHistory(Pref(Location.General, Set::class, setOf())), + SortedAnimeSH(Pref(Location.General, List::class, listOf())), + SortedMangaSH(Pref(Location.General, List::class, listOf())), + SortedCharacterSH(Pref(Location.General, List::class, listOf())), + SortedStaffSH(Pref(Location.General, List::class, listOf())), + SortedStudioSH(Pref(Location.General, List::class, listOf())), + SortedUserSH(Pref(Location.General, List::class, listOf())), NovelSourcesOrder(Pref(Location.General, List::class, listOf())), CommentNotificationInterval(Pref(Location.General, Int::class, 0)), AnilistNotificationInterval(Pref(Location.General, Int::class, 3)), diff --git a/app/src/main/java/ani/dantotsu/settings/saving/SharedPreferenceLiveData.kt b/app/src/main/java/ani/dantotsu/settings/saving/SharedPreferenceLiveData.kt index 562abbc6..83eedef2 100644 --- a/app/src/main/java/ani/dantotsu/settings/saving/SharedPreferenceLiveData.kt +++ b/app/src/main/java/ani/dantotsu/settings/saving/SharedPreferenceLiveData.kt @@ -1,7 +1,11 @@ package ani.dantotsu.settings.saving import android.content.SharedPreferences +import android.util.Base64 import androidx.lifecycle.LiveData +import ani.dantotsu.util.Logger +import java.io.ByteArrayInputStream +import java.io.ObjectInputStream abstract class SharedPreferenceLiveData( val sharedPrefs: SharedPreferences, @@ -78,6 +82,41 @@ class SharedPreferenceStringSetLiveData( sharedPrefs.getStringSet(key, defValue)?.toSet() ?: defValue } +@Suppress("UNCHECKED_CAST") +class SharedPreferenceClassLiveData( + sharedPrefs: SharedPreferences, + key: String, + defValue: T +) : SharedPreferenceLiveData(sharedPrefs, key, defValue) { + override fun getValueFromPreferences(key: String, defValue: T): T { + return try { + val serialized = sharedPrefs.getString(key, null) + if (serialized != null) { + val data = Base64.decode(serialized, Base64.DEFAULT) + val bis = ByteArrayInputStream(data) + val ois = ObjectInputStream(bis) + val obj = ois.readObject() as T + obj + } else { + Logger.log("Serialized data is null (key: $key)") + defValue + } + } catch (e: java.io.InvalidClassException) { + Logger.log(e) + try { + sharedPrefs.edit().remove(key).apply() + defValue + } catch (e: Exception) { + Logger.log(e) + defValue + } + } catch (e: Exception) { + Logger.log(e) + defValue + } + } +} + @Suppress("unused") fun SharedPreferences.intLiveData(key: String, defValue: Int): SharedPreferenceLiveData { return SharedPreferenceIntLiveData(this, key, defValue)