Use dependency injection for most singletons

This commit is contained in:
MM20
2021-10-10 12:15:54 +02:00
parent 023bb2cbb1
commit 087d4fd455
144 changed files with 1789 additions and 1277 deletions
+2
View File
@@ -40,6 +40,8 @@ dependencies {
implementation(libs.androidx.core)
implementation(libs.androidx.appcompat)
implementation(libs.koin.android)
implementation(project(":search"))
implementation(project(":calendar"))
implementation(project(":database"))
@@ -2,34 +2,38 @@ package de.mm20.launcher2.favorites
import android.content.Context
import de.mm20.launcher2.database.entities.FavoritesItemEntity
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.Searchable
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.koin.core.parameter.parametersOf
data class FavoritesItem(
val key: String,
/**
* null if searchable could not be deserialized (i.e. the app has been uninstalled)
*/
val searchable: Searchable?,
var launchCount: Int,
var pinPosition: Int,
var hidden: Boolean
){
constructor(context: Context, entity: FavoritesItemEntity) : this(
key = entity.key,
searchable = SearchableDeserializer(context).deserialize(entity.serializedSearchable),
launchCount = entity.launchCount,
pinPosition = entity.pinPosition,
hidden = entity.hidden
)
val key: String,
/**
* null if searchable could not be deserialized (i.e. the app has been uninstalled)
*/
val searchable: Searchable?,
var launchCount: Int,
var pinPosition: Int,
var hidden: Boolean
) : KoinComponent {
private val serializer: SearchableSerializer by inject { parametersOf(searchable) }
fun toDatabaseEntity(): FavoritesItemEntity {
return FavoritesItemEntity(
key = key,
serializedSearchable = searchable?.let { "${SearchableDeserializer.getTypePrefix(it)}#${it.serialize()}" } ?: "",
hidden = hidden,
pinPosition = pinPosition,
launchCount = launchCount
key = key,
serializedSearchable = searchable?.let {
"${serializer.typePrefix}#${
serializer.serialize(
it
)
}"
} ?: "",
hidden = hidden,
pinPosition = pinPosition,
launchCount = launchCount
)
}
}
@@ -10,13 +10,16 @@ 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 org.koin.core.component.inject
import org.koin.core.parameter.parametersOf
import kotlin.math.max
import kotlin.math.min
class FavoritesRepository private constructor(private val context: Context) : BaseSearchableRepository() {
class FavoritesRepository(private val context: Context) : BaseSearchableRepository() {
private val scope = CoroutineScope(Job() + Dispatchers.Main)
@@ -30,6 +33,17 @@ class FavoritesRepository private constructor(private val context: Context) : Ba
val pinnedCalendarEvents = MediatorLiveData<List<CalendarEvent>>()
private fun fromDatabaseEntity(entity: FavoritesItemEntity): FavoritesItem {
val deserializer: SearchableDeserializer by inject { parametersOf(entity.serializedSearchable) }
return FavoritesItem(
key = entity.key,
searchable = deserializer.deserialize(entity.serializedSearchable.substringAfter("#")),
launchCount = entity.launchCount,
pinPosition = entity.pinPosition,
hidden = entity.hidden
)
}
private val reloadFavorites: (String) -> Unit = {
scope.launch {
if(!LauncherPreferences.instance.searchShowFavorites) {
@@ -41,7 +55,7 @@ class FavoritesRepository private constructor(private val context: Context) : Ba
val dao = AppDatabase.getInstance(context).searchDao()
val favItems = pinnedFavorites.value ?: emptyList()
favs.addAll(favItems.mapNotNull {
val item = FavoritesItem(context, it)
val item = fromDatabaseEntity(it)
if (item.searchable == null) {
dao.deleteByKey(item.key)
}
@@ -52,7 +66,7 @@ class FavoritesRepository private constructor(private val context: Context) : Ba
if(favItems.size < columns) favCount += columns
val autoFavs = dao.getAutoFavorites(favCount - favs.size)
favs.addAll(autoFavs.mapNotNull {
val item = FavoritesItem(context, it)
val item = fromDatabaseEntity(it)
if (item.searchable == null) {
dao.deleteByKey(item.key)
}
@@ -68,7 +82,7 @@ class FavoritesRepository private constructor(private val context: Context) : Ba
init {
val hidden = AppDatabase.getInstance(context).searchDao().getHiddenItems()
hiddenItems.addSource(hidden) { h ->
hiddenItems.value = h.mapNotNull { FavoritesItem(context, it).searchable }
hiddenItems.value = h.mapNotNull { fromDatabaseEntity(it).searchable }
}
favorites.addSource(pinnedFavorites) {
reloadFavorites("")
@@ -77,7 +91,7 @@ class FavoritesRepository private constructor(private val context: Context) : Ba
scope.launch {
val dao = AppDatabase.getInstance(context).searchDao()
pinnedCalendarEvents.value = it.filter { it.key.startsWith("calendar://") }.mapNotNull {
val item = FavoritesItem(context, it)
val item = fromDatabaseEntity(it)
if (item.searchable == null) {
withContext(Dispatchers.IO) { dao.deleteByKey(item.key) }
}
@@ -187,7 +201,7 @@ class FavoritesRepository private constructor(private val context: Context) : Ba
suspend fun getAllFavoriteItems(): List<FavoritesItem> {
return withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).searchDao().getAllFavoriteItems().mapNotNull {
FavoritesItem(context, it).takeIf { it.searchable != null }
fromDatabaseEntity(it).takeIf { it.searchable != null }
}
}
}
@@ -208,11 +222,4 @@ class FavoritesRepository private constructor(private val context: Context) : Ba
return favs
}
companion object {
private lateinit var instance: FavoritesRepository
fun getInstance(context: Context): FavoritesRepository {
if (!::instance.isInitialized) instance = FavoritesRepository(context.applicationContext)
return instance
}
}
}
@@ -1,58 +1,54 @@
package de.mm20.launcher2.favorites
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class FavoritesViewModel(app: Application) : AndroidViewModel(app) {
val repository = FavoritesRepository.getInstance(app)
class FavoritesViewModel(
private val favoritesRepository: FavoritesRepository
) : ViewModel() {
fun getTopFavorites(count: Int): LiveData<List<Searchable>> {
return repository.getTopFavorites(count)
return favoritesRepository.getTopFavorites(count)
}
fun getFavorites(columns: Int): LiveData<List<Searchable>> {
return repository.getFavorites(columns)
return favoritesRepository.getFavorites(columns)
}
fun pinItem(searchable: Searchable) {
repository.pinItem(searchable)
favoritesRepository.pinItem(searchable)
}
fun unpinItem(searchable: Searchable) {
repository.unpinItem(searchable)
favoritesRepository.unpinItem(searchable)
}
fun isPinned(searchable: Searchable): LiveData<Boolean> {
return repository.isPinned(searchable)
return favoritesRepository.isPinned(searchable)
}
fun isHidden(searchable: Searchable): LiveData<Boolean> {
return repository.isHidden(searchable)
return favoritesRepository.isHidden(searchable)
}
fun hideItem(searchable: Searchable) {
repository.hideItem(searchable)
favoritesRepository.hideItem(searchable)
}
fun unhideItem(searchable: Searchable) {
repository.unhideItem(searchable)
favoritesRepository.unhideItem(searchable)
}
suspend fun getAllFavoriteItems(): List<FavoritesItem> {
return repository.getAllFavoriteItems()
return favoritesRepository.getAllFavoriteItems()
}
fun saveFavorites(favorites: MutableList<FavoritesItem>) {
repository.saveFavorites(favorites)
favoritesRepository.saveFavorites(favorites)
}
val hiddenItems: LiveData<List<Searchable>> = repository.hiddenItems
val pinnedCalendarEvents: LiveData<List<CalendarEvent>> = repository.pinnedCalendarEvents
val hiddenItems: LiveData<List<Searchable>> = this.favoritesRepository.hiddenItems
val pinnedCalendarEvents: LiveData<List<CalendarEvent>> = this.favoritesRepository.pinnedCalendarEvents
}
@@ -0,0 +1,97 @@
package de.mm20.launcher2.favorites
import de.mm20.launcher2.calendar.CalendarEventDeserializer
import de.mm20.launcher2.calendar.CalendarEventSerializer
import de.mm20.launcher2.contacts.ContactDeserializer
import de.mm20.launcher2.contacts.ContactSerializer
import de.mm20.launcher2.files.*
import de.mm20.launcher2.search.NullDeserializer
import de.mm20.launcher2.search.data.*
import de.mm20.launcher2.websites.WebsiteDeserializer
import de.mm20.launcher2.websites.WebsiteSerializer
import de.mm20.launcher2.wikipedia.WikipediaDeserializer
import de.mm20.launcher2.wikipedia.WikipediaSerializer
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val favoritesModule = module {
factory { (searchable: Searchable) ->
if (searchable is LauncherApp) {
return@factory LauncherAppSerializer()
}
if (searchable is AppShortcut) {
return@factory AppShortcutSerializer()
}
if (searchable is CalendarEvent) {
return@factory CalendarEventSerializer()
}
if (searchable is Contact) {
return@factory ContactSerializer()
}
if (searchable is Wikipedia) {
return@factory WikipediaSerializer()
}
if (searchable is GDriveFile) {
return@factory GDriveFileSerializer()
}
if (searchable is OneDriveFile) {
return@factory OneDriveFileSerializer()
}
if (searchable is OwncloudFile) {
return@factory OwncloudFileSerializer()
}
if (searchable is NextcloudFile) {
return@factory NextcloudFileSerializer()
}
if (searchable is File) {
return@factory FileSerializer()
}
if (searchable is Website) {
return@factory WebsiteSerializer()
}
throw IllegalArgumentException("No known serializer exists for type ${searchable.javaClass.canonicalName}")
}
factory { (serialized: String) ->
val type = serialized.substringBefore("#")
if (type == "app") {
return@factory LauncherAppDeserializer(androidContext())
}
if (type == "shortcut") {
return@factory AppShortcutDeserializer(androidContext())
}
if (type == "calendar") {
return@factory CalendarEventDeserializer(androidContext())
}
if (type == "contact") {
return@factory ContactDeserializer(androidContext())
}
if (type == "wikipedia") {
return@factory WikipediaDeserializer()
}
if (type == "gdrive") {
return@factory GDriveFileDeserializer()
}
if (type == "onedrive") {
return@factory OneDriveFileDeserializer()
}
if (type == "nextcloud") {
return@factory NextcloudFileDeserializer()
}
if (type == "owncloud") {
return@factory OwncloudFileDeserializer()
}
if (type == "file") {
return@factory FileDeserializer(androidContext())
}
if (type == "website") {
return@factory WebsiteDeserializer()
}
return@factory NullDeserializer()
}
single { FavoritesRepository(androidContext()) }
viewModel { FavoritesViewModel(get()) }
}
@@ -1,47 +0,0 @@
package de.mm20.launcher2.favorites
import android.content.Context
import android.util.Log
import de.mm20.launcher2.search.data.*
class SearchableDeserializer(val context: Context) {
fun deserialize(serialized: String?): Searchable? {
val type = serialized?.substringBefore("#") ?: return null
val data = serialized.substringAfter("#")
return when (type) {
"app" -> LauncherApp.deserialize(context, data)
"shortcut" -> AppShortcut.deserialize(context, data)
"calculator" -> null
"calendar" -> CalendarEvent.deserialize(context, data)
"contact" -> Contact.deserialize(context, data)
"gdrive" -> GDriveFile.deserialize(data)
"owncloud" -> OwncloudFile.deserialize(data)
"nextcloud" -> NextcloudFile.deserialize(data)
"file" -> File.deserialize(context, data)
"onedrive" -> OneDriveFile.deserialize(data)
"websearch" -> null
"website" -> Website.deserialize(data)
"wikipedia" -> Wikipedia.deserialize(data)
else -> null
}
}
companion object {
fun getTypePrefix(searchable: Searchable): String {
return when(searchable) {
is Application -> "app"
is AppShortcut -> "shortcut"
is CalendarEvent -> "calendar"
is Contact -> "contact"
is GDriveFile -> "gdrive"
is OneDriveFile -> "onedrive"
is NextcloudFile -> "nextcloud"
is OwncloudFile -> "owncloud"
is File -> "file"
is Website -> "website"
is Wikipedia -> "wikipedia"
else -> ""
}
}
}
}