Refactor search, favorites and calendar widget

This commit is contained in:
MM20
2021-12-31 20:16:08 +01:00
parent 0193d4e495
commit a2ccef64ce
81 changed files with 1335 additions and 1671 deletions
@@ -1,38 +1,183 @@
package de.mm20.launcher2.favorites
import android.content.Context
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.database.entities.FavoritesItemEntity
import de.mm20.launcher2.ktx.ceilToInt
import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.search.BaseSearchableRepository
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.channels.trySendBlocking
import kotlinx.coroutines.flow.*
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.component.inject
import org.koin.core.parameter.parametersOf
import kotlin.math.max
import kotlin.math.min
class FavoritesRepository(private val context: Context) : BaseSearchableRepository() {
interface FavoritesRepository {
fun getFavorites(): Flow<List<Searchable>>
fun getPinnedCalendarEvents(): Flow<List<Searchable>>
fun isPinned(searchable: Searchable): Flow<Boolean>
fun pinItem(searchable: Searchable)
fun unpinItem(searchable: Searchable)
fun isHidden(searchable: Searchable): Flow<Boolean>
fun hideItem(searchable: Searchable)
fun unhideItem(searchable: Searchable)
fun incrementLaunchCounter(searchable: Searchable)
suspend fun getAllFavoriteItems(): List<FavoritesItem>
fun saveFavorites(favorites: List<FavoritesItem>)
fun getHiddenItems(): Flow<List<Searchable>>
}
private val scope = CoroutineScope(Job() + Dispatchers.Main)
class FavoritesRepositoryImpl(
private val context: Context,
private val database: AppDatabase
) : FavoritesRepository, KoinComponent {
private val favorites = MediatorLiveData<List<Searchable>>()
private val favoriteItems: LiveData<List<FavoritesItemEntity>> = MutableLiveData()
private val scope = CoroutineScope(Dispatchers.Main + Job())
val hiddenItems = MediatorLiveData<List<Searchable>>()
override fun getFavorites(): Flow<List<Searchable>> = channelFlow {
private val pinnedFavorites = AppDatabase.getInstance(context).searchDao().getFavorites()
withContext(Dispatchers.IO) {
val gridColumns = callbackFlow {
send(LauncherPreferences.instance.gridColumnCount)
val unregister =
LauncherPreferences.instance.doOnPreferenceChange("grid_column_count") {
trySendBlocking(LauncherPreferences.instance.gridColumnCount)
}
awaitClose {
unregister()
}
}
val dao = database.searchDao()
val pinnedFavorites = dao.getFavorites().map {
it.mapNotNull {
val item = fromDatabaseEntity(it).searchable
if (item == null) {
dao.deleteByKey(it.key)
}
return@mapNotNull item
}
}
pinnedFavorites.collectLatest { pinned ->
gridColumns.collectLatest { columns ->
var favCount = (pinned.size.toDouble() / columns).ceilToInt() * columns
if (pinned.size < columns) favCount += columns
val autoFavs = dao.getAutoFavorites(favCount - pinned.size).mapNotNull {
val item = fromDatabaseEntity(it).searchable
if (item == null) {
dao.deleteByKey(it.key)
}
return@mapNotNull item
}
send(pinned + autoFavs)
}
}
}
}
override fun getPinnedCalendarEvents(): Flow<List<CalendarEvent>> {
return database.searchDao().getPinnedCalendarEvents().map {
it.mapNotNull { fromDatabaseEntity(it).searchable as? CalendarEvent }
}
}
override fun isPinned(searchable: Searchable): Flow<Boolean> {
return AppDatabase.getInstance(context).searchDao().isPinned(searchable.key)
}
override fun pinItem(searchable: Searchable) {
scope.launch {
withContext(Dispatchers.IO) {
val dao = AppDatabase.getInstance(context).searchDao()
val databaseItem = dao.getFavorite(searchable.key)
val favoritesItem = FavoritesItem(
key = searchable.key,
searchable = searchable,
launchCount = databaseItem?.launchCount ?: 0,
pinPosition = 1,
hidden = false
)
dao.insertReplaceExisting(favoritesItem.toDatabaseEntity())
}
}
}
override fun unpinItem(searchable: Searchable) {
scope.launch {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().unpinFavorite(searchable.key)
}
}
}
override fun isHidden(searchable: Searchable): Flow<Boolean> {
return AppDatabase.getInstance(context).searchDao().isHidden(searchable.key)
}
override fun hideItem(searchable: Searchable) {
scope.launch {
withContext(Dispatchers.IO) {
val dao = AppDatabase.getInstance(context).searchDao()
val databaseItem = dao.getFavorite(searchable.key)
val favoritesItem = FavoritesItem(
key = searchable.key,
searchable = searchable,
launchCount = databaseItem?.launchCount ?: 0,
pinPosition = 0,
hidden = true
)
dao.insertReplaceExisting(favoritesItem.toDatabaseEntity())
}
}
}
override fun unhideItem(searchable: Searchable) {
scope.launch {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().unhideItem(searchable.key)
}
}
}
override fun incrementLaunchCounter(searchable: Searchable) {
scope.launch {
withContext(Dispatchers.IO) {
val item = FavoritesItem(searchable.key, searchable, 0, 0, false)
AppDatabase.getInstance(context).searchDao()
.incrementLaunchCount(item.toDatabaseEntity())
}
}
}
override suspend fun getAllFavoriteItems(): List<FavoritesItem> {
return withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().getAllFavoriteItems().mapNotNull {
fromDatabaseEntity(it).takeIf { it.searchable != null }
}
}
}
override fun saveFavorites(favorites: List<FavoritesItem>) {
scope.launch {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao()
.saveFavorites(favorites.map { it.toDatabaseEntity() })
}
}
}
override fun getHiddenItems(): Flow<List<Searchable>> {
return database.searchDao().getHiddenItems().map {
it.mapNotNull { fromDatabaseEntity(it).searchable }
}
}
val pinnedCalendarEvents = MediatorLiveData<List<CalendarEvent>>()
private fun fromDatabaseEntity(entity: FavoritesItemEntity): FavoritesItem {
val deserializer: SearchableDeserializer = get { parametersOf(entity.serializedSearchable) }
@@ -44,183 +189,4 @@ class FavoritesRepository(private val context: Context) : BaseSearchableReposito
hidden = entity.hidden
)
}
private val reloadFavorites: (String) -> Unit = {
scope.launch {
if(!LauncherPreferences.instance.searchShowFavorites) {
favorites.value = emptyList()
return@launch
}
val favs = mutableListOf<Searchable>()
withContext(Dispatchers.IO) {
val dao = AppDatabase.getInstance(context).searchDao()
val favItems = pinnedFavorites.value ?: emptyList()
favs.addAll(favItems.mapNotNull {
val item = fromDatabaseEntity(it)
if (item.searchable == null) {
dao.deleteByKey(item.key)
}
if (item.searchable is CalendarEvent) return@mapNotNull null
item.searchable
})
var favCount = (favs.size.toDouble() / columns).ceilToInt() * columns
if(favItems.size < columns) favCount += columns
val autoFavs = dao.getAutoFavorites(favCount - favs.size)
favs.addAll(autoFavs.mapNotNull {
val item = fromDatabaseEntity(it)
if (item.searchable == null) {
dao.deleteByKey(item.key)
}
item.searchable
})
}
favorites.value = favs
}
}
private var columns = 1
init {
val hidden = AppDatabase.getInstance(context).searchDao().getHiddenItems()
hiddenItems.addSource(hidden) { h ->
hiddenItems.value = h.mapNotNull { fromDatabaseEntity(it).searchable }
}
favorites.addSource(pinnedFavorites) {
reloadFavorites("")
}
pinnedCalendarEvents.addSource(pinnedFavorites) {
scope.launch {
val dao = AppDatabase.getInstance(context).searchDao()
pinnedCalendarEvents.value = it.filter { it.key.startsWith("calendar://") }.mapNotNull {
val item = fromDatabaseEntity(it)
if (item.searchable == null) {
withContext(Dispatchers.IO) { dao.deleteByKey(item.key) }
}
item.searchable as? CalendarEvent
}
}
}
LauncherPreferences.instance.doOnPreferenceChange(
"search_show_favorites",
"search_auto_add_favorites",
action = reloadFavorites
)
}
fun isHidden(searchable: Searchable): LiveData<Boolean> {
return AppDatabase.getInstance(context).searchDao().isHidden(searchable.key)
}
fun getFavorites(columns: Int): LiveData<List<Searchable>> {
if (columns != this.columns) {
this.columns = columns
reloadFavorites("")
}
return favorites
}
override suspend fun search(query: String) {
if (query.isEmpty()) {
reloadFavorites("")
} else {
favorites.value = emptyList()
}
}
fun isPinned(searchable: Searchable): LiveData<Boolean> {
return AppDatabase.getInstance(context).searchDao().isPinned(searchable.key)
}
fun pinItem(searchable: Searchable) {
scope.launch {
withContext(Dispatchers.IO) {
val dao = AppDatabase.getInstance(context).searchDao()
val databaseItem = dao.getFavorite(searchable.key)
val favoritesItem = FavoritesItem(
key = searchable.key,
searchable = searchable,
launchCount = databaseItem?.launchCount ?: 0,
pinPosition = 1,
hidden = false
)
dao.insertReplaceExisting(favoritesItem.toDatabaseEntity())
}
}
}
fun unpinItem(searchable: Searchable) {
scope.launch {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().unpinFavorite(searchable.key)
}
}
}
fun hideItem(searchable: Searchable) {
scope.launch {
withContext(Dispatchers.IO) {
val dao = AppDatabase.getInstance(context).searchDao()
val databaseItem = dao.getFavorite(searchable.key)
val favoritesItem = FavoritesItem(
key = searchable.key,
searchable = searchable,
launchCount = databaseItem?.launchCount ?: 0,
pinPosition = 0,
hidden = true
)
dao.insertReplaceExisting(favoritesItem.toDatabaseEntity())
}
}
}
fun unhideItem(searchable: Searchable) {
scope.launch {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().unhideItem(searchable.key)
}
}
}
fun deleteItem(key: String) {
scope.launch {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().deleteByKey(key)
}
}
}
fun incrementLaunchCount(searchable: Searchable) {
scope.launch {
withContext(Dispatchers.IO) {
val item = FavoritesItem(searchable.key, searchable, 0, 0, false)
AppDatabase.getInstance(context).searchDao().incrementLaunchCount(item.toDatabaseEntity())
}
}
}
suspend fun getAllFavoriteItems(): List<FavoritesItem> {
return withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().getAllFavoriteItems().mapNotNull {
fromDatabaseEntity(it).takeIf { it.searchable != null }
}
}
}
fun saveFavorites(favorites: MutableList<FavoritesItem>) {
scope.launch {
withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().saveFavorites(favorites.map { it.toDatabaseEntity() })
}
}
}
fun getTopFavorites(count: Int): LiveData<List<Searchable>> {
val favs = MediatorLiveData<List<Searchable>>()
favs.addSource(favorites) {
favs.value = it.subList(0, min(count, it.size))
}
return favs
}
}
@@ -1,54 +0,0 @@
package de.mm20.launcher2.favorites
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.data.Searchable
class FavoritesViewModel(
private val favoritesRepository: FavoritesRepository
) : ViewModel() {
fun getTopFavorites(count: Int): LiveData<List<Searchable>> {
return favoritesRepository.getTopFavorites(count)
}
fun getFavorites(columns: Int): LiveData<List<Searchable>> {
return favoritesRepository.getFavorites(columns)
}
fun pinItem(searchable: Searchable) {
favoritesRepository.pinItem(searchable)
}
fun unpinItem(searchable: Searchable) {
favoritesRepository.unpinItem(searchable)
}
fun isPinned(searchable: Searchable): LiveData<Boolean> {
return favoritesRepository.isPinned(searchable)
}
fun isHidden(searchable: Searchable): LiveData<Boolean> {
return favoritesRepository.isHidden(searchable)
}
fun hideItem(searchable: Searchable) {
favoritesRepository.hideItem(searchable)
}
fun unhideItem(searchable: Searchable) {
favoritesRepository.unhideItem(searchable)
}
suspend fun getAllFavoriteItems(): List<FavoritesItem> {
return favoritesRepository.getAllFavoriteItems()
}
fun saveFavorites(favorites: MutableList<FavoritesItem>) {
favoritesRepository.saveFavorites(favorites)
}
val hiddenItems: LiveData<List<Searchable>> = this.favoritesRepository.hiddenItems
val pinnedCalendarEvents: LiveData<List<CalendarEvent>> = this.favoritesRepository.pinnedCalendarEvents
}
@@ -91,7 +91,5 @@ val favoritesModule = module {
return@factory NullDeserializer()
}
single { FavoritesRepository(androidContext()) }
viewModel { FavoritesViewModel(get()) }
single<FavoritesRepository> { FavoritesRepositoryImpl(androidContext(), get()) }
}