Refactor search

This commit is contained in:
MM20 2022-10-15 00:29:01 +02:00
parent 048c1e8cf9
commit 2157147caa
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
42 changed files with 540 additions and 517 deletions

View File

@ -46,7 +46,6 @@ dependencies {
implementation(libs.commons.text) implementation(libs.commons.text)
implementation(project(":base")) implementation(project(":base"))
implementation(project(":preferences"))
implementation(project(":ktx")) implementation(project(":ktx"))
implementation(project(":compat")) implementation(project(":compat"))

View File

@ -12,7 +12,6 @@ import android.os.Process
import android.os.UserHandle import android.os.UserHandle
import android.util.Log import android.util.Log
import de.mm20.launcher2.ktx.normalize import de.mm20.launcher2.ktx.normalize
import de.mm20.launcher2.search.SearchableRepository
import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.LauncherApp
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@ -22,9 +21,10 @@ import kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.FuzzyScore import org.apache.commons.text.similarity.FuzzyScore
import java.util.* import java.util.*
interface AppRepository: SearchableRepository<LauncherApp> { interface AppRepository {
fun getAllInstalledApps(): Flow<List<LauncherApp>> fun getAllInstalledApps(): Flow<List<LauncherApp>>
fun getSuspendedPackages(): Flow<List<String>> fun getSuspendedPackages(): Flow<List<String>>
fun search(query: String): Flow<ImmutableList<LauncherApp>>
} }
internal class AppRepositoryImpl( internal class AppRepositoryImpl(

View File

@ -46,7 +46,6 @@ dependencies {
implementation(project(":applications")) implementation(project(":applications"))
implementation(project(":permissions")) implementation(project(":permissions"))
implementation(project(":base")) implementation(project(":base"))
implementation(project(":preferences"))
implementation(project(":ktx")) implementation(project(":ktx"))
} }

View File

@ -3,7 +3,6 @@ package de.mm20.launcher2.appshortcuts
import android.content.Context import android.content.Context
import android.content.pm.LauncherActivityInfo import android.content.pm.LauncherActivityInfo
import android.content.pm.LauncherApps import android.content.pm.LauncherApps
import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo import android.content.pm.ShortcutInfo
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
@ -14,8 +13,6 @@ import androidx.core.content.getSystemService
import de.mm20.launcher2.ktx.normalize import de.mm20.launcher2.ktx.normalize
import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.SearchableRepository
import de.mm20.launcher2.search.data.AppShortcut import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.LauncherShortcut import de.mm20.launcher2.search.data.LauncherShortcut
@ -26,12 +23,19 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.apache.commons.text.similarity.FuzzyScore import org.apache.commons.text.similarity.FuzzyScore
import java.util.* import java.util.Locale
interface AppShortcutRepository: SearchableRepository<AppShortcut> { interface AppShortcutRepository {
fun search(query: String): Flow<ImmutableList<AppShortcut>>
suspend fun getShortcutsForActivity( suspend fun getShortcutsForActivity(
launcherActivityInfo: LauncherActivityInfo, launcherActivityInfo: LauncherActivityInfo,
count: Int = 5 count: Int = 5
@ -45,7 +49,6 @@ interface AppShortcutRepository: SearchableRepository<AppShortcut> {
internal class AppShortcutRepositoryImpl( internal class AppShortcutRepositoryImpl(
private val context: Context, private val context: Context,
private val permissionsManager: PermissionsManager, private val permissionsManager: PermissionsManager,
private val dataStore: LauncherDataStore,
) : AppShortcutRepository { ) : AppShortcutRepository {
private val scope = CoroutineScope(Dispatchers.Default + Job()) private val scope = CoroutineScope(Dispatchers.Default + Job())
@ -89,49 +92,44 @@ internal class AppShortcutRepositoryImpl(
send(persistentListOf()) send(persistentListOf())
return@withContext return@withContext
} }
dataStore.data.map { it.appShortcutSearch.enabled }.collectLatest { enabled ->
if (!enabled) {
send(persistentListOf())
return@collectLatest
}
shortcutChangeEmitter.collectLatest {
val launcherApps = shortcutChangeEmitter.collectLatest {
context.getSystemService<LauncherApps>() ?: return@collectLatest send( val launcherApps =
persistentListOf() context.getSystemService<LauncherApps>() ?: return@collectLatest send(
persistentListOf()
)
val shortcutQuery = LauncherApps.ShortcutQuery()
shortcutQuery.setQueryFlags(
LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED or
LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC or
LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST or
LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED or
LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
)
val shortcuts = launcherApps.getShortcuts(shortcutQuery, Process.myUserHandle())
?.filter {
if (it.longLabel != null) {
return@filter matches(it.longLabel.toString(), query)
}
if (it.shortLabel != null) {
return@filter matches(it.shortLabel.toString(), query)
}
return@filter false
} ?: emptyList()
val pm = context.packageManager
send(
shortcuts.mapNotNull {
LauncherShortcut(
context,
it
) )
}.toImmutableList()
val shortcutQuery = LauncherApps.ShortcutQuery() )
shortcutQuery.setQueryFlags(
LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED or
LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC or
LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST or
LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED or
LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
)
val shortcuts = launcherApps.getShortcuts(shortcutQuery, Process.myUserHandle())
?.filter {
if (it.longLabel != null) {
return@filter matches(it.longLabel.toString(), query)
}
if (it.shortLabel != null) {
return@filter matches(it.shortLabel.toString(), query)
}
return@filter false
} ?: emptyList()
val pm = context.packageManager
send(
shortcuts.mapNotNull {
LauncherShortcut(
context,
it
)
}.toImmutableList()
)
}
} }
} }
} }

View File

@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.dsl.module
val appShortcutsModule = module { val appShortcutsModule = module {
single<AppShortcutRepository> { AppShortcutRepositoryImpl(androidContext(), get(), get()) } single<AppShortcutRepository> { AppShortcutRepositoryImpl(androidContext(), get()) }
} }

View File

@ -9,6 +9,9 @@ import java.text.Collator
interface PinnableSearchable : Searchable, Comparable<PinnableSearchable> { interface PinnableSearchable : Searchable, Comparable<PinnableSearchable> {
val domain: String
val key: String
val label: String val label: String
val labelOverride: String? val labelOverride: String?
get() = null get() = null

View File

@ -1,6 +1,3 @@
package de.mm20.launcher2.search package de.mm20.launcher2.search
interface Searchable { interface Searchable
val domain: String
val key: String
}

View File

@ -1,8 +0,0 @@
package de.mm20.launcher2.search
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.Flow
interface SearchableRepository<T: Searchable> {
fun search(query: String): Flow<ImmutableList<T>>
}

View File

@ -45,7 +45,6 @@ dependencies {
implementation(libs.koin.android) implementation(libs.koin.android)
implementation(project(":preferences"))
implementation(project(":base")) implementation(project(":base"))
} }

View File

@ -1,15 +1,11 @@
package de.mm20.launcher2.calculator package de.mm20.launcher2.calculator
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.data.Calculator import de.mm20.launcher2.search.data.Calculator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import org.mariuszgromada.math.mxparser.Expression import org.mariuszgromada.math.mxparser.Expression
interface CalculatorRepository { interface CalculatorRepository {
@ -18,21 +14,14 @@ interface CalculatorRepository {
class CalculatorRepositoryImpl : CalculatorRepository, KoinComponent { class CalculatorRepositoryImpl : CalculatorRepository, KoinComponent {
private val dataStore: LauncherDataStore by inject()
override fun search(query: String): Flow<Calculator?> = channelFlow { override fun search(query: String): Flow<Calculator?> = channelFlow {
if (query.isBlank()) { if (query.isBlank()) {
send(null) send(null)
return@channelFlow return@channelFlow
} }
val searchCalculator = dataStore.data.map { it.calculatorSearch.enabled }
searchCalculator.collectLatest { send(queryCalculator(query))
if (it) {
send(queryCalculator(query))
} else {
send(null)
}
}
} }
private suspend fun queryCalculator(query: String): Calculator? { private suspend fun queryCalculator(query: String): Calculator? {
@ -43,18 +32,21 @@ class CalculatorRepositoryImpl : CalculatorRepository, KoinComponent {
} }
Calculator(term = query, solution = solution.toDouble()) Calculator(term = query, solution = solution.toDouble())
} }
query.matches(Regex("0b[01]+")) -> { query.matches(Regex("0b[01]+")) -> {
val solution = query.substring(2).toIntOrNull(2) ?: run { val solution = query.substring(2).toIntOrNull(2) ?: run {
return null return null
} }
Calculator(term = query, solution = solution.toDouble()) Calculator(term = query, solution = solution.toDouble())
} }
query.matches(Regex("0[0-7]+")) -> { query.matches(Regex("0[0-7]+")) -> {
val solution = query.substring(1).toIntOrNull(8) ?: run { val solution = query.substring(1).toIntOrNull(8) ?: run {
return null return null
} }
Calculator(term = query, solution = solution.toDouble()) Calculator(term = query, solution = solution.toDouble())
} }
else -> { else -> {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val exp = Expression(query) val exp = Expression(query)

View File

@ -11,12 +11,6 @@ data class Calculator(
val solution: Double val solution: Double
): Searchable { ): Searchable {
override val domain: String
get() = "calculator"
override val key: String
get() = "calculator://$term"
val formattedString: String val formattedString: String
val formattedBinaryString: String val formattedBinaryString: String
val formattedHexString: String val formattedHexString: String

View File

@ -42,7 +42,6 @@ dependencies {
implementation(libs.koin.android) implementation(libs.koin.android)
implementation(project(":preferences"))
implementation(project(":ktx")) implementation(project(":ktx"))
implementation(project(":base")) implementation(project(":base"))
implementation(project(":permissions")) implementation(project(":permissions"))

View File

@ -6,32 +6,36 @@ import android.provider.CalendarContract
import androidx.core.database.getStringOrNull import androidx.core.database.getStringOrNull
import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.SearchableRepository
import de.mm20.launcher2.search.data.CalendarEvent import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.data.UserCalendar import de.mm20.launcher2.search.data.UserCalendar
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import java.util.Calendar
import java.util.*
interface CalendarRepository: SearchableRepository<CalendarEvent> { interface CalendarRepository {
fun getUpcomingEvents(): Flow<List<CalendarEvent>>
fun search(query: String): Flow<ImmutableList<CalendarEvent>>
fun getUpcomingEvents(
excludeCalendars: List<Long>,
excludeAllDayEvents: Boolean
): Flow<List<CalendarEvent>>
suspend fun getCalendars(): List<UserCalendar> suspend fun getCalendars(): List<UserCalendar>
} }
internal class CalendarRepositoryImpl( internal class CalendarRepositoryImpl(
private val context: Context, private val context: Context,
) : CalendarRepository, KoinComponent { private val permissionsManager: PermissionsManager,
) : CalendarRepository {
private val dataStore: LauncherDataStore by inject()
private val permissionsManager: PermissionsManager by inject()
override fun search(query: String): Flow<ImmutableList<CalendarEvent>> { override fun search(query: String): Flow<ImmutableList<CalendarEvent>> {
if (query.isBlank() || query.length < 3) { if (query.isBlank() || query.length < 3) {
@ -41,10 +45,7 @@ internal class CalendarRepositoryImpl(
} }
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Calendar) val hasPermission = permissionsManager.hasPermission(PermissionGroup.Calendar)
val searchCalendar = dataStore.data.map { it.calendarSearch.enabled } return hasPermission.map {
return combine(hasPermission, searchCalendar) { permission, search ->
permission && search
}.map {
if (it) { if (it) {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
queryCalendarEvents( queryCalendarEvents(
@ -149,25 +150,26 @@ internal class CalendarRepositoryImpl(
return results return results
} }
override fun getUpcomingEvents(): Flow<List<CalendarEvent>> = channelFlow { override fun getUpcomingEvents(
excludeCalendars: List<Long>,
excludeAllDayEvents: Boolean,
): Flow<List<CalendarEvent>> = channelFlow {
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Calendar) val hasPermission = permissionsManager.hasPermission(PermissionGroup.Calendar)
hasPermission.collectLatest { hasPermission.collectLatest {
if (it) { if (it) {
dataStore.data.map { it.calendarWidget }.collectLatest { settings -> val now = System.currentTimeMillis()
val now = System.currentTimeMillis() val end = now + 14 * 24 * 60 * 60 * 1000L
val end = now + 14 * 24 * 60 * 60 * 1000L val events = withContext(Dispatchers.IO) {
val events = withContext(Dispatchers.IO) { queryCalendarEvents(
queryCalendarEvents( query = "",
query = "", intervalStart = now,
intervalStart = now, intervalEnd = end,
intervalEnd = end, limit = 700,
limit = 700, excludeAllDayEvents = excludeAllDayEvents,
excludeAllDayEvents = settings.hideAlldayEvents, excludeCalendars = excludeCalendars
excludeCalendars = settings.excludeCalendarsList )
)
}
send(events)
} }
send(events)
} else { } else {
send(emptyList()) send(emptyList())
} }

View File

@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.dsl.module
val calendarModule = module { val calendarModule = module {
single<CalendarRepository> { CalendarRepositoryImpl(androidContext()) } single<CalendarRepository> { CalendarRepositoryImpl(androidContext(), get()) }
} }

View File

@ -42,7 +42,6 @@ dependencies {
implementation(libs.koin.android) implementation(libs.koin.android)
implementation(project(":preferences"))
implementation(project(":ktx")) implementation(project(":ktx"))
implementation(project(":base")) implementation(project(":base"))
implementation(project(":permissions")) implementation(project(":permissions"))

View File

@ -4,8 +4,6 @@ import android.content.Context
import android.provider.ContactsContract import android.provider.ContactsContract
import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.SearchableRepository
import de.mm20.launcher2.search.data.Contact import de.mm20.launcher2.search.data.Contact
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -13,20 +11,17 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
interface ContactRepository: SearchableRepository<Contact> interface ContactRepository {
fun search(query: String): Flow<ImmutableList<Contact>>
}
internal class ContactRepositoryImpl( internal class ContactRepositoryImpl(
private val context: Context, private val context: Context,
) : ContactRepository, KoinComponent { private val permissionsManager: PermissionsManager
) : ContactRepository {
private val permissionsManager: PermissionsManager by inject()
private val dataStore: LauncherDataStore by inject()
override fun search(query: String): Flow<ImmutableList<Contact>> { override fun search(query: String): Flow<ImmutableList<Contact>> {
val searchContacts = dataStore.data.map { it.contactsSearch.enabled }
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Contacts) val hasPermission = permissionsManager.hasPermission(PermissionGroup.Contacts)
if (query.length < 3) { if (query.length < 3) {
@ -35,9 +30,7 @@ internal class ContactRepositoryImpl(
} }
} }
return combine(searchContacts, hasPermission) { search, permission -> return hasPermission.map {
search && permission
}.map {
if (it) { if (it) {
queryContacts(query) queryContacts(query)
} else { } else {

View File

@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.dsl.module
val contactsModule = module { val contactsModule = module {
single<ContactRepository> { ContactRepositoryImpl(androidContext()) } single<ContactRepository> { ContactRepositoryImpl(androidContext(), get()) }
} }

View File

@ -6,6 +6,9 @@ import de.mm20.launcher2.database.entities.CustomAttributeEntity
import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.ktx.jsonObjectOf import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.PinnableSearchable
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
@ -15,6 +18,9 @@ import org.json.JSONException
import java.io.File import java.io.File
interface CustomAttributesRepository { interface CustomAttributesRepository {
fun search(query: String): Flow<ImmutableList<PinnableSearchable>>
fun getCustomIcon(searchable: PinnableSearchable): Flow<CustomIcon?> fun getCustomIcon(searchable: PinnableSearchable): Flow<CustomIcon?>
fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?) fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?)
@ -25,8 +31,6 @@ interface CustomAttributesRepository {
fun setTags(searchable: PinnableSearchable, tags: List<String>) fun setTags(searchable: PinnableSearchable, tags: List<String>)
fun getTags(searchable: PinnableSearchable): Flow<List<String>> fun getTags(searchable: PinnableSearchable): Flow<List<String>>
suspend fun search(query: String): Flow<List<PinnableSearchable>>
suspend fun export(toDir: File) suspend fun export(toDir: File)
suspend fun import(fromDir: File) suspend fun import(fromDir: File)
@ -131,15 +135,15 @@ internal class CustomAttributesRepositoryImpl(
} }
} }
override suspend fun search(query: String): Flow<List<PinnableSearchable>> { override fun search(query: String): Flow<ImmutableList<PinnableSearchable>> {
if (query.isBlank()) { if (query.isBlank()) {
return flow { return flow {
emit(emptyList()) emit(persistentListOf())
} }
} }
val dao = appDatabase.customAttrsDao() val dao = appDatabase.customAttrsDao()
return dao.search("%$query%").map { return dao.search("%$query%").map {
favoritesRepository.getFromKeys(it) favoritesRepository.getFromKeys(it).toImmutableList()
} }
} }

View File

@ -1,25 +1,24 @@
package de.mm20.launcher2.ui.utils package de.mm20.launcher2.customattrs.utils
import de.mm20.launcher2.customattrs.CustomAttributesRepository import de.mm20.launcher2.customattrs.CustomAttributesRepository
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
fun <T : PinnableSearchable> Flow<List<T>>.withCustomLabels( fun <T: PinnableSearchable>Flow<List<T>>.withCustomLabels(
customAttributesRepository: CustomAttributesRepository, customAttributesRepository: CustomAttributesRepository,
): Flow<List<T>> = channelFlow { ): Flow<List<T>> = channelFlow {
this@withCustomLabels.collectLatest { items -> this@withCustomLabels.collectLatest { items ->
val customLabels = customAttributesRepository.getCustomLabels(items) val customLabels = customAttributesRepository.getCustomLabels(items)
customLabels.collectLatest { labels -> customLabels.collectLatest { labels ->
send(items.map { item -> send(items.map { item ->
val customLabel = labels.find { it.key == item.key } val customLabel = labels.find { it.key == item.key }
if (customLabel != null) { if (customLabel != null) {
item.overrideLabel(customLabel.label) as T item.overrideLabel(customLabel.label) as T
} else { } else {
item item
} }
}) })
} }
} }

View File

@ -16,13 +16,13 @@ The source code consists of a number of Gradle submodules which all depend on ea
- `:calculator`: Implements the calculator - `:calculator`: Implements the calculator
- `:calendar`: query calendar events for the calendar widget and calendar search - `:calendar`: query calendar events for the calendar widget and calendar search
- `:compat`: Compatibility helpers for old Android versions - `:compat`: Compatibility helpers for old Android versions
- `:contacts`: Contact search - `:contacts`: Query contacts on the device
- `:crashreporter`: Crash reporter; based on https://github.com/MindorksOpenSource/CrashReporter - `:crashreporter`: Crash reporter; based on https://github.com/MindorksOpenSource/CrashReporter
- `:currencies`: APIs to fetch currency conversion rates, used by `:unitconverter` - `:currencies`: APIs to fetch currency conversion rates, used by `:unitconverter`
- `:customattrs`: common (low-level) APIs to store per-app customizations (custom labels, custom icons, tags) - `:customattrs`: common (low-level) APIs to store per-app customizations (custom labels, custom icons, tags)
- `:database`: the launcher database, uses AndroidX Room - `:database`: the launcher database, uses AndroidX Room
- `:favorites`: Handles pinned, frequently used and hidden items and serialization / deserialization of items. Depends on most of the search modules (`:apps`, `:calendar`, `:contacts`, etc.) - `:favorites`: Handles pinned, frequently used and hidden items and serialization / deserialization of items. Depends on most of the search modules (`:apps`, `:calendar`, `:contacts`, etc.)
- `:files`: File search (local and cloud) - `:files`: Manage and find files (local and cloud)
- `:g-services`: Google APIs and Google sign-in; used by `:accounts` and `:files` - `:g-services`: Google APIs and Google sign-in; used by `:accounts` and `:files`
- `:i18n`: All resources that require localization. Mainly strings but can also be used for icon resources if they need localization. - `:i18n`: All resources that require localization. Mainly strings but can also be used for icon resources if they need localization.
- `:icons`: Used to retrieve icons for items. Handles icon packs, themed icons and also custom icons (on a higher level) - `:icons`: Used to retrieve icons for items. Handles icon packs, themed icons and also custom icons (on a higher level)
@ -35,7 +35,7 @@ The source code consists of a number of Gradle submodules which all depend on ea
- `:owncloud`: Owncloud APIs and Owncloud sign-in; used by `:accounts` and `:files` - `:owncloud`: Owncloud APIs and Owncloud sign-in; used by `:accounts` and `:files`
- `:permissions`: Request and observe permission status for this app - `:permissions`: Request and observe permission status for this app
- `:preferences`: Store user preferences; uses AndroidX Datastore - `:preferences`: Store user preferences; uses AndroidX Datastore
- `:search`: Base classes for search items and search item serialization. Also websearches. Does not contain the actual search which is split across several modules (`:applications`, `:calendar`, `:contacts`, `:files` and so on) - `:search`: The search. Also websearches.
- `:ui`: Contains almost the entire user interface (except for account sign-in UIs). Uses Jetpack Compose. - `:ui`: Contains almost the entire user interface (except for account sign-in UIs). Uses Jetpack Compose.
- `:unitconverter`: Unit and currency converter - `:unitconverter`: Unit and currency converter
- `:weather`: APIs to fetch weather data - `:weather`: APIs to fetch weather data

View File

@ -88,14 +88,12 @@ digraph {
":app" -> ":database" [style=dotted] ":app" -> ":database" [style=dotted]
":applications" -> ":applications" ":applications" -> ":applications"
":applications" -> ":base" [style=dotted] ":applications" -> ":base" [style=dotted]
":applications" -> ":preferences" [style=dotted]
":applications" -> ":ktx" [style=dotted] ":applications" -> ":ktx" [style=dotted]
":applications" -> ":compat" [style=dotted] ":applications" -> ":compat" [style=dotted]
":appshortcuts" -> ":appshortcuts" ":appshortcuts" -> ":appshortcuts"
":appshortcuts" -> ":applications" [style=dotted] ":appshortcuts" -> ":applications" [style=dotted]
":appshortcuts" -> ":permissions" [style=dotted] ":appshortcuts" -> ":permissions" [style=dotted]
":appshortcuts" -> ":base" [style=dotted] ":appshortcuts" -> ":base" [style=dotted]
":appshortcuts" -> ":preferences" [style=dotted]
":appshortcuts" -> ":ktx" [style=dotted] ":appshortcuts" -> ":ktx" [style=dotted]
":backup" -> ":backup" ":backup" -> ":backup"
":backup" -> ":favorites" [style=dotted] ":backup" -> ":favorites" [style=dotted]
@ -116,17 +114,14 @@ digraph {
":base" -> ":ktx" [style=dotted] ":base" -> ":ktx" [style=dotted]
":base" -> ":i18n" [style=dotted] ":base" -> ":i18n" [style=dotted]
":calculator" -> ":calculator" ":calculator" -> ":calculator"
":calculator" -> ":preferences" [style=dotted]
":calculator" -> ":base" [style=dotted] ":calculator" -> ":base" [style=dotted]
":calendar" -> ":calendar" ":calendar" -> ":calendar"
":calendar" -> ":preferences" [style=dotted]
":calendar" -> ":ktx" [style=dotted] ":calendar" -> ":ktx" [style=dotted]
":calendar" -> ":base" [style=dotted] ":calendar" -> ":base" [style=dotted]
":calendar" -> ":permissions" [style=dotted] ":calendar" -> ":permissions" [style=dotted]
":calendar" -> ":material-color-utilities" [style=dotted] ":calendar" -> ":material-color-utilities" [style=dotted]
":compat" -> ":compat" ":compat" -> ":compat"
":contacts" -> ":contacts" ":contacts" -> ":contacts"
":contacts" -> ":preferences" [style=dotted]
":contacts" -> ":ktx" [style=dotted] ":contacts" -> ":ktx" [style=dotted]
":contacts" -> ":base" [style=dotted] ":contacts" -> ":base" [style=dotted]
":contacts" -> ":permissions" [style=dotted] ":contacts" -> ":permissions" [style=dotted]
@ -161,7 +156,6 @@ digraph {
":favorites" -> ":badges" [style=dotted] ":favorites" -> ":badges" [style=dotted]
":favorites" -> ":crashreporter" [style=dotted] ":favorites" -> ":crashreporter" [style=dotted]
":files" -> ":files" ":files" -> ":files"
":files" -> ":preferences" [style=dotted]
":files" -> ":base" [style=dotted] ":files" -> ":base" [style=dotted]
":files" -> ":ktx" [style=dotted] ":files" -> ":ktx" [style=dotted]
":files" -> ":ms-services" [style=dotted] ":files" -> ":ms-services" [style=dotted]
@ -216,6 +210,16 @@ digraph {
":preferences" -> ":crashreporter" [style=dotted] ":preferences" -> ":crashreporter" [style=dotted]
":preferences" -> ":material-color-utilities" [style=dotted] ":preferences" -> ":material-color-utilities" [style=dotted]
":search" -> ":search" ":search" -> ":search"
":search" -> ":applications" [style=dotted]
":search" -> ":appshortcuts" [style=dotted]
":search" -> ":calculator" [style=dotted]
":search" -> ":calendar" [style=dotted]
":search" -> ":contacts" [style=dotted]
":search" -> ":files" [style=dotted]
":search" -> ":unitconverter" [style=dotted]
":search" -> ":websites" [style=dotted]
":search" -> ":wikipedia" [style=dotted]
":search" -> ":customattrs" [style=dotted]
":search" -> ":base" [style=dotted] ":search" -> ":base" [style=dotted]
":search" -> ":database" [style=dotted] ":search" -> ":database" [style=dotted]
":search" -> ":preferences" [style=dotted] ":search" -> ":preferences" [style=dotted]
@ -269,7 +273,6 @@ digraph {
":webdav" -> ":crashreporter" [style=dotted] ":webdav" -> ":crashreporter" [style=dotted]
":webdav" -> ":ktx" [style=dotted] ":webdav" -> ":ktx" [style=dotted]
":websites" -> ":websites" ":websites" -> ":websites"
":websites" -> ":preferences" [style=dotted]
":websites" -> ":base" [style=dotted] ":websites" -> ":base" [style=dotted]
":websites" -> ":ktx" [style=dotted] ":websites" -> ":ktx" [style=dotted]
":widgets" -> ":widgets" ":widgets" -> ":widgets"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -44,7 +44,6 @@ dependencies {
implementation(libs.koin.android) implementation(libs.koin.android)
implementation(project(":preferences"))
implementation(project(":base")) implementation(project(":base"))
implementation(project(":ktx")) implementation(project(":ktx"))
implementation(project(":ms-services")) implementation(project(":ms-services"))

View File

@ -1,12 +1,15 @@
package de.mm20.launcher2.files package de.mm20.launcher2.files
import android.content.Context import android.content.Context
import de.mm20.launcher2.files.providers.* import de.mm20.launcher2.files.providers.FileProvider
import de.mm20.launcher2.files.providers.GDriveFileProvider
import de.mm20.launcher2.files.providers.LocalFileProvider
import de.mm20.launcher2.files.providers.NextcloudFileProvider
import de.mm20.launcher2.files.providers.OneDriveFileProvider
import de.mm20.launcher2.files.providers.OwncloudFileProvider
import de.mm20.launcher2.nextcloud.NextcloudApiHelper import de.mm20.launcher2.nextcloud.NextcloudApiHelper
import de.mm20.launcher2.owncloud.OwncloudClient import de.mm20.launcher2.owncloud.OwncloudClient
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.SearchableRepository
import de.mm20.launcher2.search.data.File import de.mm20.launcher2.search.data.File
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -14,24 +17,30 @@ import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
interface FileRepository: SearchableRepository<File> { interface FileRepository {
fun search(
query: String,
local: Boolean = true,
gdrive: Boolean = true,
onedrive: Boolean = true,
nextcloud: Boolean = true,
owncloud: Boolean = true,
): Flow<ImmutableList<File>>
fun deleteFile(file: File) fun deleteFile(file: File)
} }
internal class FileRepositoryImpl( internal class FileRepositoryImpl(
private val context: Context, private val context: Context,
private val dataStore: LauncherDataStore,
private val permissionsManager: PermissionsManager, private val permissionsManager: PermissionsManager,
) : FileRepository { ) : FileRepository {
private val scope = CoroutineScope(Job() + Dispatchers.Default) private val scope = CoroutineScope(Job() + Dispatchers.Default)
private val providers = MutableStateFlow<List<FileProvider>>(emptyList())
private val nextcloudClient by lazy { private val nextcloudClient by lazy {
NextcloudApiHelper(context) NextcloudApiHelper(context)
} }
@ -39,46 +48,35 @@ internal class FileRepositoryImpl(
OwncloudClient(context) OwncloudClient(context)
} }
init { override fun search(
scope.launch { query: String,
dataStore.data.map { it.fileSearch }.distinctUntilChanged().collectLatest { local: Boolean,
val provs = mutableListOf<FileProvider>() gdrive: Boolean,
if (it.localFiles) { onedrive: Boolean,
provs += LocalFileProvider(context, permissionsManager) nextcloud: Boolean,
} owncloud: Boolean
if (it.nextcloud) { ) = channelFlow {
provs += NextcloudFileProvider(nextcloudClient)
}
if (it.owncloud) {
provs += OwncloudFileProvider(owncloudClient)
}
if (it.gdrive) {
provs += GDriveFileProvider(context)
}
if (it.onedrive) {
provs += OneDriveFileProvider(context)
}
providers.value = provs
}
}
}
override fun search(query: String): Flow<ImmutableList<File>> = channelFlow {
if (query.isBlank()) { if (query.isBlank()) {
send(persistentListOf()) send(persistentListOf())
return@channelFlow return@channelFlow
} }
providers.collectLatest { providers -> val providers = mutableListOf<FileProvider>()
if (providers.isEmpty()) {
send(persistentListOf()) if (local) providers.add(LocalFileProvider(context, permissionsManager))
return@collectLatest if (gdrive) providers.add(GDriveFileProvider(context))
} if (onedrive) providers.add(OneDriveFileProvider(context))
val results = mutableListOf<File>() if (nextcloud) providers.add(NextcloudFileProvider(nextcloudClient))
for (provider in providers) { if (owncloud) providers.add(OwncloudFileProvider(owncloudClient))
results.addAll(provider.search(query))
send(results.toImmutableList()) if (providers.isEmpty()) {
} send(persistentListOf())
return@channelFlow
}
val results = mutableListOf<File>()
for (provider in providers) {
results.addAll(provider.search(query))
send(results.toImmutableList())
} }
} }

View File

@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.dsl.module
val filesModule = module { val filesModule = module {
single<FileRepository> { FileRepositoryImpl(androidContext(), get(), get()) } single<FileRepository> { FileRepositoryImpl(androidContext(), get()) }
} }

View File

@ -47,6 +47,17 @@ dependencies {
implementation(libs.okhttp) implementation(libs.okhttp)
implementation(libs.coil.core) implementation(libs.coil.core)
implementation(project(":applications"))
implementation(project(":appshortcuts"))
implementation(project(":calculator"))
implementation(project(":calendar"))
implementation(project(":contacts"))
implementation(project(":files"))
implementation(project(":unitconverter"))
implementation(project(":websites"))
implementation(project(":wikipedia"))
implementation(project(":customattrs"))
implementation(project(":base")) implementation(project(":base"))
implementation(project(":database")) implementation(project(":database"))
implementation(project(":preferences")) implementation(project(":preferences"))

View File

@ -4,5 +4,19 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.dsl.module
val searchModule = module { val searchModule = module {
single<SearchService> {
SearchServiceImpl(
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
get(),
)
}
single<WebsearchRepository> { WebsearchRepositoryImpl(androidContext(), get()) } single<WebsearchRepository> { WebsearchRepositoryImpl(androidContext(), get()) }
} }

View File

@ -0,0 +1,243 @@
package de.mm20.launcher2.search
import de.mm20.launcher2.applications.AppRepository
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
import de.mm20.launcher2.calculator.CalculatorRepository
import de.mm20.launcher2.calendar.CalendarRepository
import de.mm20.launcher2.contacts.ContactRepository
import de.mm20.launcher2.customattrs.CustomAttributesRepository
import de.mm20.launcher2.customattrs.utils.withCustomLabels
import de.mm20.launcher2.files.FileRepository
import de.mm20.launcher2.preferences.Settings.AppShortcutSearchSettings
import de.mm20.launcher2.preferences.Settings.CalculatorSearchSettings
import de.mm20.launcher2.preferences.Settings.CalendarSearchSettings
import de.mm20.launcher2.preferences.Settings.ContactsSearchSettings
import de.mm20.launcher2.preferences.Settings.FilesSearchSettings
import de.mm20.launcher2.preferences.Settings.UnitConverterSearchSettings
import de.mm20.launcher2.preferences.Settings.WebsiteSearchSettings
import de.mm20.launcher2.preferences.Settings.WikipediaSearchSettings
import de.mm20.launcher2.search.data.AppShortcut
import de.mm20.launcher2.search.data.Calculator
import de.mm20.launcher2.search.data.CalendarEvent
import de.mm20.launcher2.search.data.Contact
import de.mm20.launcher2.search.data.File
import de.mm20.launcher2.search.data.GDriveFile
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.LocalFile
import de.mm20.launcher2.search.data.NextcloudFile
import de.mm20.launcher2.search.data.OneDriveFile
import de.mm20.launcher2.search.data.OwncloudFile
import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.search.data.Website
import de.mm20.launcher2.search.data.Wikipedia
import de.mm20.launcher2.unitconverter.UnitConverterRepository
import de.mm20.launcher2.websites.WebsiteRepository
import de.mm20.launcher2.wikipedia.WikipediaRepository
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
interface SearchService {
fun search(
query: String,
shortcuts: AppShortcutSearchSettings,
contacts: ContactsSearchSettings,
calendars: CalendarSearchSettings,
files: FilesSearchSettings,
calculator: CalculatorSearchSettings,
unitConverter: UnitConverterSearchSettings,
websites: WebsiteSearchSettings,
wikipedia: WikipediaSearchSettings,
): Flow<ImmutableList<Searchable>>
}
internal class SearchServiceImpl(
private val appRepository: AppRepository,
private val appShortcutRepository: AppShortcutRepository,
private val calendarRepository: CalendarRepository,
private val contactRepository: ContactRepository,
private val fileRepository: FileRepository,
private val wikipediaRepository: WikipediaRepository,
private val unitConverterRepository: UnitConverterRepository,
private val calculatorRepository: CalculatorRepository,
private val websiteRepository: WebsiteRepository,
private val customAttributesRepository: CustomAttributesRepository,
) : SearchService {
override fun search(
query: String,
shortcuts: AppShortcutSearchSettings,
contacts: ContactsSearchSettings,
calendars: CalendarSearchSettings,
files: FilesSearchSettings,
calculator: CalculatorSearchSettings,
unitConverter: UnitConverterSearchSettings,
websites: WebsiteSearchSettings,
wikipedia: WikipediaSearchSettings,
): Flow<ImmutableList<Searchable>> = channelFlow {
supervisorScope {
val results = MutableStateFlow(SearchResults())
launch {
appRepository.search(query)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
it.copy(apps = r)
}
}
}
if (shortcuts.enabled) {
launch {
appShortcutRepository.search(query)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
it.copy(shortcuts = r)
}
}
}
}
if (contacts.enabled) {
launch {
contactRepository.search(query)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
it.copy(contacts = r)
}
}
}
}
if (calendars.enabled) {
launch {
calendarRepository.search(query)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
it.copy(calendars = r)
}
}
}
}
if (calculator.enabled) {
launch {
calculatorRepository.search(query).collectLatest { r ->
results.update {
it.copy(calculators = r?.let { persistentListOf(it) }
?: persistentListOf())
}
}
}
}
if (unitConverter.enabled) {
launch {
unitConverterRepository.search(query, unitConverter.currencies).collectLatest { r ->
results.update {
it.copy(unitConverters = r?.let { persistentListOf(it) }
?: persistentListOf())
}
}
}
}
if (websites.enabled) {
launch {
websiteRepository.search(query)
.map { it?.let { listOf(it) } ?: listOf() }
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
it.copy(websites = r)
}
}
}
}
if (wikipedia.enabled) {
launch {
wikipediaRepository.search(query, loadImages = wikipedia.images)
.map { it?.let { listOf(it) } ?: listOf() }
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
it.copy(wikipedia = r)
}
}
}
}
if (files.localFiles || files.owncloud || files.onedrive || files.gdrive || files.nextcloud) {
launch {
fileRepository.search(
query,
local = files.localFiles,
nextcloud = files.nextcloud,
owncloud = files.owncloud,
onedrive = files.onedrive,
gdrive = files.gdrive,
)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
it.copy(files = r)
}
}
}
}
launch {
customAttributesRepository.search(query)
.withCustomLabels(customAttributesRepository)
.collectLatest { r ->
results.update {
it.copy(
other = r
.filter {
it is LauncherApp ||
shortcuts.enabled && it is AppShortcut ||
files.localFiles && it is LocalFile ||
files.nextcloud && it is NextcloudFile ||
files.owncloud && it is OwncloudFile ||
files.onedrive && it is OneDriveFile ||
files.gdrive && it is GDriveFile ||
wikipedia.enabled && it is Wikipedia ||
websites.enabled && it is Website ||
calendars.enabled && it is CalendarEvent ||
contacts.enabled && it is Contact
}.toImmutableList()
)
}
}
}
launch {
results
.map { it.toList().sortedBy { it as? PinnableSearchable }.toImmutableList() }
.collectLatest {
send(it)
}
}
}
}
}
internal data class SearchResults(
val apps: List<LauncherApp> = emptyList(),
val shortcuts: List<AppShortcut> = emptyList(),
val contacts: List<Contact> = emptyList(),
val calendars: List<CalendarEvent> = emptyList(),
val files: List<File> = emptyList(),
val calculators: List<Calculator> = emptyList(),
val unitConverters: List<UnitConverter> = emptyList(),
val websites: List<Website> = emptyList(),
val wikipedia: List<Wikipedia> = emptyList(),
val other: List<PinnableSearchable> = emptyList(),
) {
fun toList(): List<Searchable> {
return (apps + shortcuts + contacts + calendars + websites + wikipedia + other).distinctBy { it.key } + calculators+ unitConverters
}
}

View File

@ -3,12 +3,11 @@ package de.mm20.launcher2.ui.common
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.customattrs.CustomAttributesRepository import de.mm20.launcher2.customattrs.CustomAttributesRepository
import de.mm20.launcher2.customattrs.utils.withCustomLabels
import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.search.data.Tag import de.mm20.launcher2.search.data.Tag
import de.mm20.launcher2.ui.utils.withCustomLabels
import de.mm20.launcher2.widgets.WidgetRepository import de.mm20.launcher2.widgets.WidgetRepository
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -81,7 +80,7 @@ open class FavoritesVM : ViewModel(), KoinComponent {
customAttributesRepository customAttributesRepository
.getItemsForTag(tag) .getItemsForTag(tag)
.withCustomLabels(customAttributesRepository) .withCustomLabels(customAttributesRepository)
.map { it.sorted() } .map { it.sortedBy { it } }
} }
}.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 1) }.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 1)

View File

@ -91,10 +91,10 @@ fun SearchColumn(
val contacts by viewModel.contactResults.observeAsState(emptyList()) val contacts by viewModel.contactResults.observeAsState(emptyList())
val files by viewModel.fileResults.observeAsState(emptyList()) val files by viewModel.fileResults.observeAsState(emptyList())
val events by viewModel.calendarResults.observeAsState(emptyList()) val events by viewModel.calendarResults.observeAsState(emptyList())
val unitConverter by viewModel.unitConverterResult.observeAsState(null) val unitConverter by viewModel.unitConverterResults.observeAsState(emptyList())
val calculator by viewModel.calculatorResult.observeAsState(null) val calculator by viewModel.calculatorResults.observeAsState(emptyList())
val wikipedia by viewModel.wikipediaResult.observeAsState(null) val wikipedia by viewModel.wikipediaResults.observeAsState(emptyList())
val website by viewModel.websiteResult.observeAsState(null) val website by viewModel.websiteResults.observeAsState(emptyList())
val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true) val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true)
@ -276,14 +276,12 @@ fun SearchColumn(
reverse = reverse, reverse = reverse,
key = "shortcuts" key = "shortcuts"
) )
val uc = unitConverter for (conv in unitConverter) {
if (uc != null) {
SingleResult { SingleResult {
UnitConverterItem(unitConverter = uc) UnitConverterItem(unitConverter = conv)
} }
} }
val calc = calculator for (calc in calculator) {
if (calc != null) {
SingleResult { SingleResult {
CalculatorItem(calculator = calc) CalculatorItem(calculator = calc)
} }
@ -334,14 +332,12 @@ fun SearchColumn(
reverse = reverse, reverse = reverse,
key = "contacts" key = "contacts"
) )
val wiki = wikipedia for (wiki in wikipedia) {
if (wiki != null) {
SingleResult { SingleResult {
WikipediaItem(wikipedia = wiki) WikipediaItem(wikipedia = wiki)
} }
} }
val ws = website for (ws in website) {
if (ws != null) {
SingleResult { SingleResult {
WebsiteItem(website = ws) WebsiteItem(website = ws)
} }

View File

@ -1,29 +1,19 @@
package de.mm20.launcher2.ui.launcher.search package de.mm20.launcher2.ui.launcher.search
import android.util.Log
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.applications.AppRepository
import de.mm20.launcher2.appshortcuts.AppShortcutRepository
import de.mm20.launcher2.calculator.CalculatorRepository
import de.mm20.launcher2.calendar.CalendarRepository
import de.mm20.launcher2.contacts.ContactRepository
import de.mm20.launcher2.customattrs.CustomAttributesRepository import de.mm20.launcher2.customattrs.CustomAttributesRepository
import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.files.FileRepository
import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.PinnableSearchable import de.mm20.launcher2.search.PinnableSearchable
import de.mm20.launcher2.search.Searchable import de.mm20.launcher2.search.SearchService
import de.mm20.launcher2.search.WebsearchRepository
import de.mm20.launcher2.search.data.* import de.mm20.launcher2.search.data.*
import de.mm20.launcher2.ui.utils.withCustomLabels
import de.mm20.launcher2.unitconverter.UnitConverterRepository
import de.mm20.launcher2.websites.WebsiteRepository
import de.mm20.launcher2.wikipedia.WikipediaRepository
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -33,19 +23,9 @@ class SearchVM : ViewModel(), KoinComponent {
private val favoritesRepository: FavoritesRepository by inject() private val favoritesRepository: FavoritesRepository by inject()
private val permissionsManager: PermissionsManager by inject() private val permissionsManager: PermissionsManager by inject()
private val customAttributesRepository: CustomAttributesRepository by inject()
private val dataStore: LauncherDataStore by inject() private val dataStore: LauncherDataStore by inject()
private val calendarRepository: CalendarRepository by inject() private val searchService: SearchService by inject()
private val contactRepository: ContactRepository by inject()
private val appRepository: AppRepository by inject()
private val appShortcutRepository: AppShortcutRepository by inject()
private val wikipediaRepository: WikipediaRepository by inject()
private val unitConverterRepository: UnitConverterRepository by inject()
private val calculatorRepository: CalculatorRepository by inject()
private val websiteRepository: WebsiteRepository by inject()
private val fileRepository: FileRepository by inject()
private val websearchRepository: WebsearchRepository by inject()
val isSearching = MutableLiveData(false) val isSearching = MutableLiveData(false)
val searchQuery = MutableLiveData("") val searchQuery = MutableLiveData("")
@ -59,10 +39,10 @@ class SearchVM : ViewModel(), KoinComponent {
val fileResults = MutableLiveData<List<File>>(emptyList()) val fileResults = MutableLiveData<List<File>>(emptyList())
val contactResults = MutableLiveData<List<Contact>>(emptyList()) val contactResults = MutableLiveData<List<Contact>>(emptyList())
val calendarResults = MutableLiveData<List<CalendarEvent>>(emptyList()) val calendarResults = MutableLiveData<List<CalendarEvent>>(emptyList())
val wikipediaResult = MutableLiveData<Wikipedia?>(null) val wikipediaResults = MutableLiveData<List<Wikipedia>>(emptyList())
val websiteResult = MutableLiveData<Website?>(null) val websiteResults = MutableLiveData<List<Website>>(emptyList())
val calculatorResult = MutableLiveData<Calculator?>(null) val calculatorResults = MutableLiveData<List<Calculator>>(emptyList())
val unitConverterResult = MutableLiveData<UnitConverter?>(null) val unitConverterResults = MutableLiveData<List<UnitConverter>>(emptyList())
val websearchResults = MutableLiveData<List<Websearch>>(emptyList()) val websearchResults = MutableLiveData<List<Websearch>>(emptyList())
val hiddenResults = MutableLiveData<List<PinnableSearchable>>(emptyList()) val hiddenResults = MutableLiveData<List<PinnableSearchable>>(emptyList())
@ -84,8 +64,6 @@ class SearchVM : ViewModel(), KoinComponent {
isSearchEmpty.value = query.isEmpty() isSearchEmpty.value = query.isEmpty()
hiddenResults.value = emptyList() hiddenResults.value = emptyList()
val hiddenItems = MutableStateFlow(HiddenItemResults())
try { try {
searchJob?.cancel() searchJob?.cancel()
} catch (_: CancellationException) { } catch (_: CancellationException) {
@ -93,117 +71,64 @@ class SearchVM : ViewModel(), KoinComponent {
hideFavorites.postValue(query.isNotEmpty()) hideFavorites.postValue(query.isNotEmpty())
searchJob = viewModelScope.launch { searchJob = viewModelScope.launch {
isSearching.postValue(true) isSearching.postValue(true)
val customAttrResults = customAttributesRepository.search(query)
.combine(dataStore.data) { items, settings -> dataStore.data.collectLatest {
items.filter { searchService.search(
it is LauncherApp query,
|| it is Contact && settings.contactsSearch.enabled calculator = it.calculatorSearch,
|| it is CalendarEvent && settings.calendarSearch.enabled unitConverter = it.unitConverterSearch,
|| it is AppShortcut && settings.appShortcutSearch.enabled calendars = it.calendarSearch,
|| it is LocalFile && settings.fileSearch.localFiles contacts = it.contactsSearch,
|| it is GDriveFile && settings.fileSearch.gdrive files = it.fileSearch,
|| it is OneDriveFile && settings.fileSearch.onedrive shortcuts = it.appShortcutSearch,
} websites = it.websiteSearch,
} wikipedia = it.wikipediaSearch,
val jobs = mutableListOf<Deferred<Any>>() ).collectLatest { results ->
jobs += async(Dispatchers.Default) { hiddenItemKeys.collectLatest { hiddenKeys ->
appRepository val hidden = mutableListOf<PinnableSearchable>()
.search(query) val apps = mutableListOf<LauncherApp>()
.withCustomAttributeResults(customAttrResults) val workApps = mutableListOf<LauncherApp>()
.withCustomLabels(customAttributesRepository) val shortcuts = mutableListOf<AppShortcut>()
.sorted() val files = mutableListOf<File>()
.collectWithHiddenItems(hiddenItemKeys) { results, hidden -> val contacts = mutableListOf<Contact>()
val (work, personal) = results.partition { it is LauncherApp && !it.isMainProfile } val events = mutableListOf<CalendarEvent>()
appResults.postValue(personal) val unitConv = mutableListOf<UnitConverter>()
workAppResults.postValue(work) val calc = mutableListOf<Calculator>()
hiddenItems.update { val wikipedia = mutableListOf<Wikipedia>()
it.copy(apps = hidden) val website = mutableListOf<Website>()
for (r in results) {
when {
r is PinnableSearchable && hiddenKeys.contains(r.key) -> {
hidden.add(r)
}
r is LauncherApp && !r.isMainProfile -> workApps.add(r)
r is LauncherApp -> apps.add(r)
r is AppShortcut -> shortcuts.add(r)
r is File -> files.add(r)
r is Contact -> contacts.add(r)
r is CalendarEvent -> events.add(r)
r is UnitConverter -> unitConv.add(r)
r is Calculator -> calc.add(r)
r is Website -> website.add(r)
r is Wikipedia -> wikipedia.add(r)
}
} }
appResults.value = apps
workAppResults.value = workApps
appShortcutResults.value = shortcuts
fileResults.value = files
contactResults.value = contacts
calendarResults.value = events
wikipediaResults.value = wikipedia
websiteResults.value = website
calculatorResults.value = calc
unitConverterResults.value = unitConv
hiddenResults.value = hidden
} }
}
jobs += async(Dispatchers.Default) {
contactRepository
.search(query)
.withCustomAttributeResults(customAttrResults)
.withCustomLabels(customAttributesRepository)
.sorted()
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
contactResults.postValue(results)
hiddenItems.update {
it.copy(contacts = hidden)
}
}
}
jobs += async(Dispatchers.Default) {
calendarRepository
.search(query)
.withCustomAttributeResults(customAttrResults)
.withCustomLabels(customAttributesRepository)
.sorted()
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
calendarResults.postValue(results)
hiddenItems.update {
it.copy(calendarEvents = hidden)
}
}
}
jobs += async(Dispatchers.Default) {
wikipediaRepository.search(query).collectLatest {
wikipediaResult.postValue(it)
} }
} }
jobs += async(Dispatchers.Default) {
unitConverterRepository.search(query).collectLatest {
unitConverterResult.postValue(it)
}
}
jobs += async(Dispatchers.Default) {
calculatorRepository.search(query).collectLatest {
calculatorResult.postValue(it)
}
}
jobs += async(Dispatchers.Default) {
websiteRepository.search(query).collectLatest {
websiteResult.postValue(it)
}
}
jobs += async(Dispatchers.Default) {
fileRepository
.search(query)
.withCustomAttributeResults(customAttrResults)
.withCustomLabels(customAttributesRepository)
.sorted()
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
fileResults.postValue(results)
hiddenItems.update {
it.copy(files = hidden)
}
}
}
jobs += async(Dispatchers.Default) {
websearchRepository.search(query).collectLatest {
websearchResults.postValue(it)
}
}
jobs += async(Dispatchers.Default) {
appShortcutRepository
.search(query)
.withCustomAttributeResults(customAttrResults)
.withCustomLabels(customAttributesRepository)
.sorted()
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
appShortcutResults.postValue(results)
hiddenItems.update {
it.copy(appShortcuts = hidden)
}
}
}
launch(Dispatchers.Default) {
hiddenItems.collectLatest {
hiddenResults.postValue(it.joinToList())
}
}
jobs.map { it.await() }
isSearching.postValue(false) isSearching.postValue(false)
} }
} }

View File

@ -1,29 +0,0 @@
package de.mm20.launcher2.ui.launcher.search.calculator
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.launcher.search.SearchVM
@Composable
fun ColumnScope.CalculatorResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val calculator by viewModel.calculatorResult.observeAsState(null)
AnimatedVisibility(calculator != null) {
LauncherCard(
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
calculator?.let { CalculatorItem(calculator = it) }
}
}
}

View File

@ -1,29 +0,0 @@
package de.mm20.launcher2.ui.launcher.search.unitconverter
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.launcher.search.SearchVM
@Composable
fun ColumnScope.UnitConverterResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val unitConverter by viewModel.unitConverterResult.observeAsState(null)
AnimatedVisibility(unitConverter != null) {
LauncherCard(
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
unitConverter?.let { UnitConverterItem(unitConverter = it) }
}
}
}

View File

@ -1,29 +0,0 @@
package de.mm20.launcher2.ui.launcher.search.website
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.launcher.search.SearchVM
@Composable
fun ColumnScope.WebsiteResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val website by viewModel.websiteResult.observeAsState(null)
AnimatedVisibility(website != null) {
LauncherCard(
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
website?.let { WebsiteItem(website = it) }
}
}
}

View File

@ -1,29 +0,0 @@
package de.mm20.launcher2.ui.launcher.search.wikipedia
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.component.LauncherCard
import de.mm20.launcher2.ui.launcher.search.SearchVM
@Composable
fun ColumnScope.WikipediaResults(reverse: Boolean = false) {
val viewModel: SearchVM = viewModel()
val wikipedia by viewModel.wikipediaResult.observeAsState(null)
AnimatedVisibility(wikipedia != null) {
LauncherCard(
modifier = Modifier
.padding(bottom = if (reverse) 0.dp else 8.dp, top = if (reverse) 8.dp else 0.dp)
) {
wikipedia?.let { WikipediaItem(wikipedia = it) }
}
}
}

View File

@ -14,17 +14,22 @@ import de.mm20.launcher2.favorites.FavoritesRepository
import de.mm20.launcher2.ktx.tryStartActivity import de.mm20.launcher2.ktx.tryStartActivity
import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.data.CalendarEvent import de.mm20.launcher2.search.data.CalendarEvent
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
import java.lang.Integer.min import java.lang.Integer.min
import java.time.* import java.time.Instant
import java.util.* import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZoneId
import kotlin.math.max import kotlin.math.max
class CalendarWidgetVM : ViewModel(), KoinComponent { class CalendarWidgetVM : ViewModel(), KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val calendarRepository: CalendarRepository by inject() private val calendarRepository: CalendarRepository by inject()
private val favoritesRepository: FavoritesRepository by inject() private val favoritesRepository: FavoritesRepository by inject()
@ -144,10 +149,16 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
suspend fun onActive() { suspend fun onActive() {
selectDate(LocalDate.now()) selectDate(LocalDate.now())
calendarRepository.getUpcomingEvents().collectLatest { events -> dataStore.data.map { it.calendarWidget }.collectLatest { settings ->
favoritesRepository.getHiddenCalendarEventKeys().collectLatest { hidden -> calendarRepository.getUpcomingEvents(
upcomingEvents = events.filter { !hidden.contains(it.key) } excludeAllDayEvents = settings.hideAlldayEvents,
excludeCalendars = settings.excludeCalendarsList
).collectLatest { events ->
favoritesRepository.getHiddenCalendarEventKeys().collectLatest { hidden ->
upcomingEvents = events.filter { !hidden.contains(it.key) }
}
} }
} }
} }

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.search.data package de.mm20.launcher2.search.data
import de.mm20.launcher2.search.Searchable
import de.mm20.launcher2.unitconverter.Dimension import de.mm20.launcher2.unitconverter.Dimension
import de.mm20.launcher2.unitconverter.UnitValue import de.mm20.launcher2.unitconverter.UnitValue
@ -7,5 +8,4 @@ open class UnitConverter(
val dimension: Dimension, val dimension: Dimension,
val inputValue: UnitValue, val inputValue: UnitValue,
val values: List<UnitValue> val values: List<UnitValue>
) ): Searchable

View File

@ -1,7 +1,6 @@
package de.mm20.launcher2.unitconverter package de.mm20.launcher2.unitconverter
import android.content.Context import android.content.Context
import androidx.lifecycle.MutableLiveData
import de.mm20.launcher2.currencies.CurrencyRepository import de.mm20.launcher2.currencies.CurrencyRepository
import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.data.UnitConverter import de.mm20.launcher2.search.data.UnitConverter
@ -15,7 +14,7 @@ import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
interface UnitConverterRepository { interface UnitConverterRepository {
fun search(query: String): Flow<UnitConverter?> fun search(query: String, includeCurrencies: Boolean): Flow<UnitConverter?>
} }
internal class UnitConverterRepositoryImpl( internal class UnitConverterRepositoryImpl(
@ -26,8 +25,6 @@ internal class UnitConverterRepositoryImpl(
private val scope = CoroutineScope(Job() + Dispatchers.Default) private val scope = CoroutineScope(Job() + Dispatchers.Default)
val unitConverter = MutableLiveData<UnitConverter?>()
init { init {
scope.launch { scope.launch {
dataStore.data.map { it.unitConverterSearch }.distinctUntilChanged().collectLatest { dataStore.data.map { it.unitConverterSearch }.distinctUntilChanged().collectLatest {
@ -37,18 +34,12 @@ internal class UnitConverterRepositoryImpl(
} }
} }
override fun search(query: String): Flow<UnitConverter?> = channelFlow { override fun search(query: String, includeCurrencies: Boolean): Flow<UnitConverter?> = channelFlow {
if (query.isBlank()) { if (query.isBlank()) {
send(null) send(null)
return@channelFlow return@channelFlow
} }
dataStore.data.map { it.unitConverterSearch }.collectLatest { send(queryUnitConverter(query, includeCurrencies))
if (it.enabled) {
send(queryUnitConverter(query, it.currencies))
} else {
send(null)
}
}
} }
private suspend fun queryUnitConverter( private suspend fun queryUnitConverter(

View File

@ -50,7 +50,6 @@ dependencies {
implementation(libs.coil.core) implementation(libs.coil.core)
implementation(project(":preferences"))
implementation(project(":base")) implementation(project(":base"))
implementation(project(":ktx")) implementation(project(":ktx"))

View File

@ -3,13 +3,10 @@ package de.mm20.launcher2.websites
import android.content.Context import android.content.Context
import android.webkit.URLUtil import android.webkit.URLUtil
import androidx.core.graphics.toColorInt import androidx.core.graphics.toColorInt
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.data.Website import de.mm20.launcher2.search.data.Website
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -17,7 +14,6 @@ import okhttp3.Request
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.UncheckedIOException import org.jsoup.UncheckedIOException
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.io.IOException import java.io.IOException
import java.net.MalformedURLException import java.net.MalformedURLException
import java.net.URISyntaxException import java.net.URISyntaxException
@ -30,8 +26,6 @@ interface WebsiteRepository {
internal class WebsiteRepositoryImpl(val context: Context) : WebsiteRepository, KoinComponent { internal class WebsiteRepositoryImpl(val context: Context) : WebsiteRepository, KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val httpClient = OkHttpClient private val httpClient = OkHttpClient
.Builder() .Builder()
.connectTimeout(200, TimeUnit.MILLISECONDS) .connectTimeout(200, TimeUnit.MILLISECONDS)
@ -46,14 +40,8 @@ internal class WebsiteRepositoryImpl(val context: Context) : WebsiteRepository,
} }
if (query.isBlank()) return@channelFlow if (query.isBlank()) return@channelFlow
dataStore.data.map { it.websiteSearch.enabled }.collectLatest { val website = queryWebsite(query)
if(it) { send(website)
val website = queryWebsite(query)
send(website)
} else {
send(null)
}
}
} }

View File

@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module import org.koin.dsl.module
val wikipediaModule = module { val wikipediaModule = module {
single<WikipediaRepository> { WikipediaRepositoryImpl(androidContext()) } single<WikipediaRepository> { WikipediaRepositoryImpl(androidContext(), get()) }
} }

View File

@ -15,17 +15,16 @@ import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
interface WikipediaRepository { interface WikipediaRepository {
fun search(query: String): Flow<Wikipedia?> fun search(query: String, loadImages: Boolean = false): Flow<Wikipedia?>
} }
internal class WikipediaRepositoryImpl( internal class WikipediaRepositoryImpl(
private val context: Context private val context: Context,
private val dataStore: LauncherDataStore
) : WikipediaRepository, KoinComponent { ) : WikipediaRepository, KoinComponent {
private val scope = CoroutineScope(Job() + Dispatchers.Default) private val scope = CoroutineScope(Job() + Dispatchers.Default)
private val dataStore: LauncherDataStore by inject()
private val httpClient = OkHttpClient private val httpClient = OkHttpClient
.Builder() .Builder()
.connectTimeout(200, TimeUnit.MILLISECONDS) .connectTimeout(200, TimeUnit.MILLISECONDS)
@ -58,7 +57,7 @@ internal class WikipediaRepositoryImpl(
private lateinit var wikipediaService: WikipediaApi private lateinit var wikipediaService: WikipediaApi
override fun search(query: String): Flow<Wikipedia?> = channelFlow { override fun search(query: String, loadImages: Boolean): Flow<Wikipedia?> = channelFlow {
send(null) send(null)
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
httpClient.dispatcher.cancelAll() httpClient.dispatcher.cancelAll()
@ -67,13 +66,7 @@ internal class WikipediaRepositoryImpl(
if (!::wikipediaService.isInitialized) return@channelFlow if (!::wikipediaService.isInitialized) return@channelFlow
if (query.isBlank()) return@channelFlow if (query.isBlank()) return@channelFlow
dataStore.data.map { it.wikipediaSearch }.collectLatest { send(queryWikipedia(query, loadImages))
if (it.enabled) {
send(queryWikipedia(query, it.images))
} else {
send(null)
}
}
} }
private suspend fun queryWikipedia(query: String, loadImages: Boolean): Wikipedia? { private suspend fun queryWikipedia(query: String, loadImages: Boolean): Wikipedia? {