Refactor search
This commit is contained in:
parent
048c1e8cf9
commit
2157147caa
@ -46,7 +46,6 @@ dependencies {
|
||||
implementation(libs.commons.text)
|
||||
|
||||
implementation(project(":base"))
|
||||
implementation(project(":preferences"))
|
||||
implementation(project(":ktx"))
|
||||
implementation(project(":compat"))
|
||||
|
||||
|
||||
@ -12,7 +12,6 @@ import android.os.Process
|
||||
import android.os.UserHandle
|
||||
import android.util.Log
|
||||
import de.mm20.launcher2.ktx.normalize
|
||||
import de.mm20.launcher2.search.SearchableRepository
|
||||
import de.mm20.launcher2.search.data.LauncherApp
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
@ -22,9 +21,10 @@ import kotlinx.coroutines.withContext
|
||||
import org.apache.commons.text.similarity.FuzzyScore
|
||||
import java.util.*
|
||||
|
||||
interface AppRepository: SearchableRepository<LauncherApp> {
|
||||
interface AppRepository {
|
||||
fun getAllInstalledApps(): Flow<List<LauncherApp>>
|
||||
fun getSuspendedPackages(): Flow<List<String>>
|
||||
fun search(query: String): Flow<ImmutableList<LauncherApp>>
|
||||
}
|
||||
|
||||
internal class AppRepositoryImpl(
|
||||
|
||||
@ -46,7 +46,6 @@ dependencies {
|
||||
implementation(project(":applications"))
|
||||
implementation(project(":permissions"))
|
||||
implementation(project(":base"))
|
||||
implementation(project(":preferences"))
|
||||
implementation(project(":ktx"))
|
||||
|
||||
}
|
||||
@ -3,7 +3,6 @@ package de.mm20.launcher2.appshortcuts
|
||||
import android.content.Context
|
||||
import android.content.pm.LauncherActivityInfo
|
||||
import android.content.pm.LauncherApps
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.ShortcutInfo
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
@ -14,8 +13,6 @@ import androidx.core.content.getSystemService
|
||||
import de.mm20.launcher2.ktx.normalize
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
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.LauncherApp
|
||||
import de.mm20.launcher2.search.data.LauncherShortcut
|
||||
@ -26,12 +23,19 @@ import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
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 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(
|
||||
launcherActivityInfo: LauncherActivityInfo,
|
||||
count: Int = 5
|
||||
@ -45,7 +49,6 @@ interface AppShortcutRepository: SearchableRepository<AppShortcut> {
|
||||
internal class AppShortcutRepositoryImpl(
|
||||
private val context: Context,
|
||||
private val permissionsManager: PermissionsManager,
|
||||
private val dataStore: LauncherDataStore,
|
||||
) : AppShortcutRepository {
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.Default + Job())
|
||||
@ -89,49 +92,44 @@ internal class AppShortcutRepositoryImpl(
|
||||
send(persistentListOf())
|
||||
return@withContext
|
||||
}
|
||||
dataStore.data.map { it.appShortcutSearch.enabled }.collectLatest { enabled ->
|
||||
if (!enabled) {
|
||||
send(persistentListOf())
|
||||
return@collectLatest
|
||||
}
|
||||
|
||||
shortcutChangeEmitter.collectLatest {
|
||||
val launcherApps =
|
||||
context.getSystemService<LauncherApps>() ?: return@collectLatest send(
|
||||
persistentListOf()
|
||||
|
||||
shortcutChangeEmitter.collectLatest {
|
||||
val launcherApps =
|
||||
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
|
||||
)
|
||||
|
||||
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()
|
||||
)
|
||||
}
|
||||
}.toImmutableList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val appShortcutsModule = module {
|
||||
single<AppShortcutRepository> { AppShortcutRepositoryImpl(androidContext(), get(), get()) }
|
||||
single<AppShortcutRepository> { AppShortcutRepositoryImpl(androidContext(), get()) }
|
||||
}
|
||||
@ -9,6 +9,9 @@ import java.text.Collator
|
||||
|
||||
interface PinnableSearchable : Searchable, Comparable<PinnableSearchable> {
|
||||
|
||||
val domain: String
|
||||
val key: String
|
||||
|
||||
val label: String
|
||||
val labelOverride: String?
|
||||
get() = null
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
package de.mm20.launcher2.search
|
||||
|
||||
interface Searchable {
|
||||
val domain: String
|
||||
val key: String
|
||||
}
|
||||
interface Searchable
|
||||
@ -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>>
|
||||
}
|
||||
@ -45,7 +45,6 @@ dependencies {
|
||||
|
||||
implementation(libs.koin.android)
|
||||
|
||||
implementation(project(":preferences"))
|
||||
implementation(project(":base"))
|
||||
|
||||
}
|
||||
@ -1,15 +1,11 @@
|
||||
package de.mm20.launcher2.calculator
|
||||
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.search.data.Calculator
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import org.mariuszgromada.math.mxparser.Expression
|
||||
|
||||
interface CalculatorRepository {
|
||||
@ -18,21 +14,14 @@ interface CalculatorRepository {
|
||||
|
||||
class CalculatorRepositoryImpl : CalculatorRepository, KoinComponent {
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
override fun search(query: String): Flow<Calculator?> = channelFlow {
|
||||
if (query.isBlank()) {
|
||||
send(null)
|
||||
return@channelFlow
|
||||
}
|
||||
val searchCalculator = dataStore.data.map { it.calculatorSearch.enabled }
|
||||
searchCalculator.collectLatest {
|
||||
if (it) {
|
||||
send(queryCalculator(query))
|
||||
} else {
|
||||
send(null)
|
||||
}
|
||||
}
|
||||
|
||||
send(queryCalculator(query))
|
||||
}
|
||||
|
||||
private suspend fun queryCalculator(query: String): Calculator? {
|
||||
@ -43,18 +32,21 @@ class CalculatorRepositoryImpl : CalculatorRepository, KoinComponent {
|
||||
}
|
||||
Calculator(term = query, solution = solution.toDouble())
|
||||
}
|
||||
|
||||
query.matches(Regex("0b[01]+")) -> {
|
||||
val solution = query.substring(2).toIntOrNull(2) ?: run {
|
||||
return null
|
||||
}
|
||||
Calculator(term = query, solution = solution.toDouble())
|
||||
}
|
||||
|
||||
query.matches(Regex("0[0-7]+")) -> {
|
||||
val solution = query.substring(1).toIntOrNull(8) ?: run {
|
||||
return null
|
||||
}
|
||||
Calculator(term = query, solution = solution.toDouble())
|
||||
}
|
||||
|
||||
else -> {
|
||||
withContext(Dispatchers.IO) {
|
||||
val exp = Expression(query)
|
||||
|
||||
@ -11,12 +11,6 @@ data class Calculator(
|
||||
val solution: Double
|
||||
): Searchable {
|
||||
|
||||
override val domain: String
|
||||
get() = "calculator"
|
||||
|
||||
override val key: String
|
||||
get() = "calculator://$term"
|
||||
|
||||
val formattedString: String
|
||||
val formattedBinaryString: String
|
||||
val formattedHexString: String
|
||||
|
||||
@ -42,7 +42,6 @@ dependencies {
|
||||
|
||||
implementation(libs.koin.android)
|
||||
|
||||
implementation(project(":preferences"))
|
||||
implementation(project(":ktx"))
|
||||
implementation(project(":base"))
|
||||
implementation(project(":permissions"))
|
||||
|
||||
@ -6,32 +6,36 @@ import android.provider.CalendarContract
|
||||
import androidx.core.database.getStringOrNull
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
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.UserCalendar
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
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 org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.util.*
|
||||
import java.util.Calendar
|
||||
|
||||
interface CalendarRepository: SearchableRepository<CalendarEvent> {
|
||||
fun getUpcomingEvents(): Flow<List<CalendarEvent>>
|
||||
interface CalendarRepository {
|
||||
|
||||
fun search(query: String): Flow<ImmutableList<CalendarEvent>>
|
||||
fun getUpcomingEvents(
|
||||
excludeCalendars: List<Long>,
|
||||
excludeAllDayEvents: Boolean
|
||||
): Flow<List<CalendarEvent>>
|
||||
|
||||
suspend fun getCalendars(): List<UserCalendar>
|
||||
}
|
||||
|
||||
internal class CalendarRepositoryImpl(
|
||||
private val context: Context,
|
||||
) : CalendarRepository, KoinComponent {
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val permissionsManager: PermissionsManager,
|
||||
) : CalendarRepository {
|
||||
|
||||
override fun search(query: String): Flow<ImmutableList<CalendarEvent>> {
|
||||
if (query.isBlank() || query.length < 3) {
|
||||
@ -41,10 +45,7 @@ internal class CalendarRepositoryImpl(
|
||||
}
|
||||
|
||||
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Calendar)
|
||||
val searchCalendar = dataStore.data.map { it.calendarSearch.enabled }
|
||||
return combine(hasPermission, searchCalendar) { permission, search ->
|
||||
permission && search
|
||||
}.map {
|
||||
return hasPermission.map {
|
||||
if (it) {
|
||||
val now = System.currentTimeMillis()
|
||||
queryCalendarEvents(
|
||||
@ -149,25 +150,26 @@ internal class CalendarRepositoryImpl(
|
||||
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)
|
||||
hasPermission.collectLatest {
|
||||
if (it) {
|
||||
dataStore.data.map { it.calendarWidget }.collectLatest { settings ->
|
||||
val now = System.currentTimeMillis()
|
||||
val end = now + 14 * 24 * 60 * 60 * 1000L
|
||||
val events = withContext(Dispatchers.IO) {
|
||||
queryCalendarEvents(
|
||||
query = "",
|
||||
intervalStart = now,
|
||||
intervalEnd = end,
|
||||
limit = 700,
|
||||
excludeAllDayEvents = settings.hideAlldayEvents,
|
||||
excludeCalendars = settings.excludeCalendarsList
|
||||
)
|
||||
}
|
||||
send(events)
|
||||
val now = System.currentTimeMillis()
|
||||
val end = now + 14 * 24 * 60 * 60 * 1000L
|
||||
val events = withContext(Dispatchers.IO) {
|
||||
queryCalendarEvents(
|
||||
query = "",
|
||||
intervalStart = now,
|
||||
intervalEnd = end,
|
||||
limit = 700,
|
||||
excludeAllDayEvents = excludeAllDayEvents,
|
||||
excludeCalendars = excludeCalendars
|
||||
)
|
||||
}
|
||||
send(events)
|
||||
} else {
|
||||
send(emptyList())
|
||||
}
|
||||
|
||||
@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val calendarModule = module {
|
||||
single<CalendarRepository> { CalendarRepositoryImpl(androidContext()) }
|
||||
single<CalendarRepository> { CalendarRepositoryImpl(androidContext(), get()) }
|
||||
}
|
||||
@ -42,7 +42,6 @@ dependencies {
|
||||
|
||||
implementation(libs.koin.android)
|
||||
|
||||
implementation(project(":preferences"))
|
||||
implementation(project(":ktx"))
|
||||
implementation(project(":base"))
|
||||
implementation(project(":permissions"))
|
||||
|
||||
@ -4,8 +4,6 @@ import android.content.Context
|
||||
import android.provider.ContactsContract
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
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 kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@ -13,20 +11,17 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
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(
|
||||
private val context: Context,
|
||||
) : ContactRepository, KoinComponent {
|
||||
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val permissionsManager: PermissionsManager
|
||||
) : ContactRepository {
|
||||
|
||||
override fun search(query: String): Flow<ImmutableList<Contact>> {
|
||||
val searchContacts = dataStore.data.map { it.contactsSearch.enabled }
|
||||
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Contacts)
|
||||
|
||||
if (query.length < 3) {
|
||||
@ -35,9 +30,7 @@ internal class ContactRepositoryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
return combine(searchContacts, hasPermission) { search, permission ->
|
||||
search && permission
|
||||
}.map {
|
||||
return hasPermission.map {
|
||||
if (it) {
|
||||
queryContacts(query)
|
||||
} else {
|
||||
|
||||
@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val contactsModule = module {
|
||||
single<ContactRepository> { ContactRepositoryImpl(androidContext()) }
|
||||
single<ContactRepository> { ContactRepositoryImpl(androidContext(), get()) }
|
||||
}
|
||||
@ -6,6 +6,9 @@ import de.mm20.launcher2.database.entities.CustomAttributeEntity
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.ktx.jsonObjectOf
|
||||
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.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
@ -15,6 +18,9 @@ import org.json.JSONException
|
||||
import java.io.File
|
||||
|
||||
interface CustomAttributesRepository {
|
||||
|
||||
fun search(query: String): Flow<ImmutableList<PinnableSearchable>>
|
||||
|
||||
fun getCustomIcon(searchable: PinnableSearchable): Flow<CustomIcon?>
|
||||
fun setCustomIcon(searchable: PinnableSearchable, icon: CustomIcon?)
|
||||
|
||||
@ -25,8 +31,6 @@ interface CustomAttributesRepository {
|
||||
fun setTags(searchable: PinnableSearchable, tags: List<String>)
|
||||
fun getTags(searchable: PinnableSearchable): Flow<List<String>>
|
||||
|
||||
suspend fun search(query: String): Flow<List<PinnableSearchable>>
|
||||
|
||||
suspend fun export(toDir: 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()) {
|
||||
return flow {
|
||||
emit(emptyList())
|
||||
emit(persistentListOf())
|
||||
}
|
||||
}
|
||||
val dao = appDatabase.customAttrsDao()
|
||||
return dao.search("%$query%").map {
|
||||
favoritesRepository.getFromKeys(it)
|
||||
favoritesRepository.getFromKeys(it).toImmutableList()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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.search.PinnableSearchable
|
||||
import de.mm20.launcher2.search.Searchable
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
|
||||
fun <T : PinnableSearchable> Flow<List<T>>.withCustomLabels(
|
||||
fun <T: PinnableSearchable>Flow<List<T>>.withCustomLabels(
|
||||
customAttributesRepository: CustomAttributesRepository,
|
||||
): Flow<List<T>> = channelFlow {
|
||||
this@withCustomLabels.collectLatest { items ->
|
||||
val customLabels = customAttributesRepository.getCustomLabels(items)
|
||||
customLabels.collectLatest { labels ->
|
||||
send(items.map { item ->
|
||||
val customLabel = labels.find { it.key == item.key }
|
||||
if (customLabel != null) {
|
||||
item.overrideLabel(customLabel.label) as T
|
||||
} else {
|
||||
item
|
||||
}
|
||||
val customLabel = labels.find { it.key == item.key }
|
||||
if (customLabel != null) {
|
||||
item.overrideLabel(customLabel.label) as T
|
||||
} else {
|
||||
item
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -16,13 +16,13 @@ The source code consists of a number of Gradle submodules which all depend on ea
|
||||
- `:calculator`: Implements the calculator
|
||||
- `:calendar`: query calendar events for the calendar widget and calendar search
|
||||
- `: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
|
||||
- `: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)
|
||||
- `: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.)
|
||||
- `: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`
|
||||
- `: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)
|
||||
@ -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`
|
||||
- `:permissions`: Request and observe permission status for this app
|
||||
- `: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.
|
||||
- `:unitconverter`: Unit and currency converter
|
||||
- `:weather`: APIs to fetch weather data
|
||||
|
||||
17
docs/static/img/dependency-graph.dot
vendored
17
docs/static/img/dependency-graph.dot
vendored
@ -88,14 +88,12 @@ digraph {
|
||||
":app" -> ":database" [style=dotted]
|
||||
":applications" -> ":applications"
|
||||
":applications" -> ":base" [style=dotted]
|
||||
":applications" -> ":preferences" [style=dotted]
|
||||
":applications" -> ":ktx" [style=dotted]
|
||||
":applications" -> ":compat" [style=dotted]
|
||||
":appshortcuts" -> ":appshortcuts"
|
||||
":appshortcuts" -> ":applications" [style=dotted]
|
||||
":appshortcuts" -> ":permissions" [style=dotted]
|
||||
":appshortcuts" -> ":base" [style=dotted]
|
||||
":appshortcuts" -> ":preferences" [style=dotted]
|
||||
":appshortcuts" -> ":ktx" [style=dotted]
|
||||
":backup" -> ":backup"
|
||||
":backup" -> ":favorites" [style=dotted]
|
||||
@ -116,17 +114,14 @@ digraph {
|
||||
":base" -> ":ktx" [style=dotted]
|
||||
":base" -> ":i18n" [style=dotted]
|
||||
":calculator" -> ":calculator"
|
||||
":calculator" -> ":preferences" [style=dotted]
|
||||
":calculator" -> ":base" [style=dotted]
|
||||
":calendar" -> ":calendar"
|
||||
":calendar" -> ":preferences" [style=dotted]
|
||||
":calendar" -> ":ktx" [style=dotted]
|
||||
":calendar" -> ":base" [style=dotted]
|
||||
":calendar" -> ":permissions" [style=dotted]
|
||||
":calendar" -> ":material-color-utilities" [style=dotted]
|
||||
":compat" -> ":compat"
|
||||
":contacts" -> ":contacts"
|
||||
":contacts" -> ":preferences" [style=dotted]
|
||||
":contacts" -> ":ktx" [style=dotted]
|
||||
":contacts" -> ":base" [style=dotted]
|
||||
":contacts" -> ":permissions" [style=dotted]
|
||||
@ -161,7 +156,6 @@ digraph {
|
||||
":favorites" -> ":badges" [style=dotted]
|
||||
":favorites" -> ":crashreporter" [style=dotted]
|
||||
":files" -> ":files"
|
||||
":files" -> ":preferences" [style=dotted]
|
||||
":files" -> ":base" [style=dotted]
|
||||
":files" -> ":ktx" [style=dotted]
|
||||
":files" -> ":ms-services" [style=dotted]
|
||||
@ -216,6 +210,16 @@ digraph {
|
||||
":preferences" -> ":crashreporter" [style=dotted]
|
||||
":preferences" -> ":material-color-utilities" [style=dotted]
|
||||
":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" -> ":database" [style=dotted]
|
||||
":search" -> ":preferences" [style=dotted]
|
||||
@ -269,7 +273,6 @@ digraph {
|
||||
":webdav" -> ":crashreporter" [style=dotted]
|
||||
":webdav" -> ":ktx" [style=dotted]
|
||||
":websites" -> ":websites"
|
||||
":websites" -> ":preferences" [style=dotted]
|
||||
":websites" -> ":base" [style=dotted]
|
||||
":websites" -> ":ktx" [style=dotted]
|
||||
":widgets" -> ":widgets"
|
||||
|
||||
BIN
docs/static/img/dependency-graph.dot.png
vendored
BIN
docs/static/img/dependency-graph.dot.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
@ -44,7 +44,6 @@ dependencies {
|
||||
|
||||
implementation(libs.koin.android)
|
||||
|
||||
implementation(project(":preferences"))
|
||||
implementation(project(":base"))
|
||||
implementation(project(":ktx"))
|
||||
implementation(project(":ms-services"))
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
package de.mm20.launcher2.files
|
||||
|
||||
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.owncloud.OwncloudClient
|
||||
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 kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@ -14,24 +17,30 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
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)
|
||||
}
|
||||
|
||||
internal class FileRepositoryImpl(
|
||||
private val context: Context,
|
||||
private val dataStore: LauncherDataStore,
|
||||
private val permissionsManager: PermissionsManager,
|
||||
) : FileRepository {
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
|
||||
|
||||
private val providers = MutableStateFlow<List<FileProvider>>(emptyList())
|
||||
|
||||
private val nextcloudClient by lazy {
|
||||
NextcloudApiHelper(context)
|
||||
}
|
||||
@ -39,46 +48,35 @@ internal class FileRepositoryImpl(
|
||||
OwncloudClient(context)
|
||||
}
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
dataStore.data.map { it.fileSearch }.distinctUntilChanged().collectLatest {
|
||||
val provs = mutableListOf<FileProvider>()
|
||||
if (it.localFiles) {
|
||||
provs += LocalFileProvider(context, permissionsManager)
|
||||
}
|
||||
if (it.nextcloud) {
|
||||
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 {
|
||||
override fun search(
|
||||
query: String,
|
||||
local: Boolean,
|
||||
gdrive: Boolean,
|
||||
onedrive: Boolean,
|
||||
nextcloud: Boolean,
|
||||
owncloud: Boolean
|
||||
) = channelFlow {
|
||||
if (query.isBlank()) {
|
||||
send(persistentListOf())
|
||||
return@channelFlow
|
||||
}
|
||||
|
||||
providers.collectLatest { providers ->
|
||||
if (providers.isEmpty()) {
|
||||
send(persistentListOf())
|
||||
return@collectLatest
|
||||
}
|
||||
val results = mutableListOf<File>()
|
||||
for (provider in providers) {
|
||||
results.addAll(provider.search(query))
|
||||
send(results.toImmutableList())
|
||||
}
|
||||
val providers = mutableListOf<FileProvider>()
|
||||
|
||||
if (local) providers.add(LocalFileProvider(context, permissionsManager))
|
||||
if (gdrive) providers.add(GDriveFileProvider(context))
|
||||
if (onedrive) providers.add(OneDriveFileProvider(context))
|
||||
if (nextcloud) providers.add(NextcloudFileProvider(nextcloudClient))
|
||||
if (owncloud) providers.add(OwncloudFileProvider(owncloudClient))
|
||||
|
||||
if (providers.isEmpty()) {
|
||||
send(persistentListOf())
|
||||
return@channelFlow
|
||||
}
|
||||
val results = mutableListOf<File>()
|
||||
for (provider in providers) {
|
||||
results.addAll(provider.search(query))
|
||||
send(results.toImmutableList())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val filesModule = module {
|
||||
single<FileRepository> { FileRepositoryImpl(androidContext(), get(), get()) }
|
||||
single<FileRepository> { FileRepositoryImpl(androidContext(), get()) }
|
||||
}
|
||||
@ -47,6 +47,17 @@ dependencies {
|
||||
implementation(libs.okhttp)
|
||||
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(":database"))
|
||||
implementation(project(":preferences"))
|
||||
|
||||
@ -4,5 +4,19 @@ import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val searchModule = module {
|
||||
single<SearchService> {
|
||||
SearchServiceImpl(
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
get(),
|
||||
)
|
||||
}
|
||||
single<WebsearchRepository> { WebsearchRepositoryImpl(androidContext(), get()) }
|
||||
}
|
||||
243
search/src/main/java/de/mm20/launcher2/search/SearchService.kt
Normal file
243
search/src/main/java/de/mm20/launcher2/search/SearchService.kt
Normal 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
|
||||
}
|
||||
}
|
||||
@ -3,12 +3,11 @@ package de.mm20.launcher2.ui.common
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.customattrs.CustomAttributesRepository
|
||||
import de.mm20.launcher2.customattrs.utils.withCustomLabels
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.search.PinnableSearchable
|
||||
import de.mm20.launcher2.search.Searchable
|
||||
import de.mm20.launcher2.search.data.Tag
|
||||
import de.mm20.launcher2.ui.utils.withCustomLabels
|
||||
import de.mm20.launcher2.widgets.WidgetRepository
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
@ -81,7 +80,7 @@ open class FavoritesVM : ViewModel(), KoinComponent {
|
||||
customAttributesRepository
|
||||
.getItemsForTag(tag)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
.map { it.sorted() }
|
||||
.map { it.sortedBy { it } }
|
||||
}
|
||||
}.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 1)
|
||||
|
||||
|
||||
@ -91,10 +91,10 @@ fun SearchColumn(
|
||||
val contacts by viewModel.contactResults.observeAsState(emptyList())
|
||||
val files by viewModel.fileResults.observeAsState(emptyList())
|
||||
val events by viewModel.calendarResults.observeAsState(emptyList())
|
||||
val unitConverter by viewModel.unitConverterResult.observeAsState(null)
|
||||
val calculator by viewModel.calculatorResult.observeAsState(null)
|
||||
val wikipedia by viewModel.wikipediaResult.observeAsState(null)
|
||||
val website by viewModel.websiteResult.observeAsState(null)
|
||||
val unitConverter by viewModel.unitConverterResults.observeAsState(emptyList())
|
||||
val calculator by viewModel.calculatorResults.observeAsState(emptyList())
|
||||
val wikipedia by viewModel.wikipediaResults.observeAsState(emptyList())
|
||||
val website by viewModel.websiteResults.observeAsState(emptyList())
|
||||
|
||||
val isSearchEmpty by viewModel.isSearchEmpty.observeAsState(true)
|
||||
|
||||
@ -276,14 +276,12 @@ fun SearchColumn(
|
||||
reverse = reverse,
|
||||
key = "shortcuts"
|
||||
)
|
||||
val uc = unitConverter
|
||||
if (uc != null) {
|
||||
for (conv in unitConverter) {
|
||||
SingleResult {
|
||||
UnitConverterItem(unitConverter = uc)
|
||||
UnitConverterItem(unitConverter = conv)
|
||||
}
|
||||
}
|
||||
val calc = calculator
|
||||
if (calc != null) {
|
||||
for (calc in calculator) {
|
||||
SingleResult {
|
||||
CalculatorItem(calculator = calc)
|
||||
}
|
||||
@ -334,14 +332,12 @@ fun SearchColumn(
|
||||
reverse = reverse,
|
||||
key = "contacts"
|
||||
)
|
||||
val wiki = wikipedia
|
||||
if (wiki != null) {
|
||||
for (wiki in wikipedia) {
|
||||
SingleResult {
|
||||
WikipediaItem(wikipedia = wiki)
|
||||
}
|
||||
}
|
||||
val ws = website
|
||||
if (ws != null) {
|
||||
for (ws in website) {
|
||||
SingleResult {
|
||||
WebsiteItem(website = ws)
|
||||
}
|
||||
|
||||
@ -1,29 +1,19 @@
|
||||
package de.mm20.launcher2.ui.launcher.search
|
||||
|
||||
import android.util.Log
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
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.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.files.FileRepository
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.search.PinnableSearchable
|
||||
import de.mm20.launcher2.search.Searchable
|
||||
import de.mm20.launcher2.search.WebsearchRepository
|
||||
import de.mm20.launcher2.search.SearchService
|
||||
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.flow.*
|
||||
import org.koin.core.component.KoinComponent
|
||||
@ -33,19 +23,9 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
private val customAttributesRepository: CustomAttributesRepository by inject()
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
private val calendarRepository: CalendarRepository 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()
|
||||
private val searchService: SearchService by inject()
|
||||
|
||||
val isSearching = MutableLiveData(false)
|
||||
val searchQuery = MutableLiveData("")
|
||||
@ -59,10 +39,10 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
val fileResults = MutableLiveData<List<File>>(emptyList())
|
||||
val contactResults = MutableLiveData<List<Contact>>(emptyList())
|
||||
val calendarResults = MutableLiveData<List<CalendarEvent>>(emptyList())
|
||||
val wikipediaResult = MutableLiveData<Wikipedia?>(null)
|
||||
val websiteResult = MutableLiveData<Website?>(null)
|
||||
val calculatorResult = MutableLiveData<Calculator?>(null)
|
||||
val unitConverterResult = MutableLiveData<UnitConverter?>(null)
|
||||
val wikipediaResults = MutableLiveData<List<Wikipedia>>(emptyList())
|
||||
val websiteResults = MutableLiveData<List<Website>>(emptyList())
|
||||
val calculatorResults = MutableLiveData<List<Calculator>>(emptyList())
|
||||
val unitConverterResults = MutableLiveData<List<UnitConverter>>(emptyList())
|
||||
val websearchResults = MutableLiveData<List<Websearch>>(emptyList())
|
||||
|
||||
val hiddenResults = MutableLiveData<List<PinnableSearchable>>(emptyList())
|
||||
@ -84,8 +64,6 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
isSearchEmpty.value = query.isEmpty()
|
||||
hiddenResults.value = emptyList()
|
||||
|
||||
val hiddenItems = MutableStateFlow(HiddenItemResults())
|
||||
|
||||
try {
|
||||
searchJob?.cancel()
|
||||
} catch (_: CancellationException) {
|
||||
@ -93,117 +71,64 @@ class SearchVM : ViewModel(), KoinComponent {
|
||||
hideFavorites.postValue(query.isNotEmpty())
|
||||
searchJob = viewModelScope.launch {
|
||||
isSearching.postValue(true)
|
||||
val customAttrResults = customAttributesRepository.search(query)
|
||||
.combine(dataStore.data) { items, settings ->
|
||||
items.filter {
|
||||
it is LauncherApp
|
||||
|| it is Contact && settings.contactsSearch.enabled
|
||||
|| it is CalendarEvent && settings.calendarSearch.enabled
|
||||
|| it is AppShortcut && settings.appShortcutSearch.enabled
|
||||
|| it is LocalFile && settings.fileSearch.localFiles
|
||||
|| it is GDriveFile && settings.fileSearch.gdrive
|
||||
|| it is OneDriveFile && settings.fileSearch.onedrive
|
||||
}
|
||||
}
|
||||
val jobs = mutableListOf<Deferred<Any>>()
|
||||
jobs += async(Dispatchers.Default) {
|
||||
appRepository
|
||||
.search(query)
|
||||
.withCustomAttributeResults(customAttrResults)
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
.sorted()
|
||||
.collectWithHiddenItems(hiddenItemKeys) { results, hidden ->
|
||||
val (work, personal) = results.partition { it is LauncherApp && !it.isMainProfile }
|
||||
appResults.postValue(personal)
|
||||
workAppResults.postValue(work)
|
||||
hiddenItems.update {
|
||||
it.copy(apps = hidden)
|
||||
|
||||
dataStore.data.collectLatest {
|
||||
searchService.search(
|
||||
query,
|
||||
calculator = it.calculatorSearch,
|
||||
unitConverter = it.unitConverterSearch,
|
||||
calendars = it.calendarSearch,
|
||||
contacts = it.contactsSearch,
|
||||
files = it.fileSearch,
|
||||
shortcuts = it.appShortcutSearch,
|
||||
websites = it.websiteSearch,
|
||||
wikipedia = it.wikipediaSearch,
|
||||
).collectLatest { results ->
|
||||
hiddenItemKeys.collectLatest { hiddenKeys ->
|
||||
val hidden = mutableListOf<PinnableSearchable>()
|
||||
val apps = mutableListOf<LauncherApp>()
|
||||
val workApps = mutableListOf<LauncherApp>()
|
||||
val shortcuts = mutableListOf<AppShortcut>()
|
||||
val files = mutableListOf<File>()
|
||||
val contacts = mutableListOf<Contact>()
|
||||
val events = mutableListOf<CalendarEvent>()
|
||||
val unitConv = mutableListOf<UnitConverter>()
|
||||
val calc = mutableListOf<Calculator>()
|
||||
val wikipedia = mutableListOf<Wikipedia>()
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -14,17 +14,22 @@ import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.lang.Integer.min
|
||||
import java.time.*
|
||||
import java.util.*
|
||||
import java.time.Instant
|
||||
import java.time.LocalDate
|
||||
import java.time.OffsetDateTime
|
||||
import java.time.ZoneId
|
||||
import kotlin.math.max
|
||||
|
||||
class CalendarWidgetVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val calendarRepository: CalendarRepository by inject()
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
|
||||
@ -144,10 +149,16 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
||||
|
||||
suspend fun onActive() {
|
||||
selectDate(LocalDate.now())
|
||||
calendarRepository.getUpcomingEvents().collectLatest { events ->
|
||||
favoritesRepository.getHiddenCalendarEventKeys().collectLatest { hidden ->
|
||||
upcomingEvents = events.filter { !hidden.contains(it.key) }
|
||||
dataStore.data.map { it.calendarWidget }.collectLatest { settings ->
|
||||
calendarRepository.getUpcomingEvents(
|
||||
excludeAllDayEvents = settings.hideAlldayEvents,
|
||||
excludeCalendars = settings.excludeCalendarsList
|
||||
).collectLatest { events ->
|
||||
favoritesRepository.getHiddenCalendarEventKeys().collectLatest { hidden ->
|
||||
upcomingEvents = events.filter { !hidden.contains(it.key) }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package de.mm20.launcher2.search.data
|
||||
|
||||
import de.mm20.launcher2.search.Searchable
|
||||
import de.mm20.launcher2.unitconverter.Dimension
|
||||
import de.mm20.launcher2.unitconverter.UnitValue
|
||||
|
||||
@ -7,5 +8,4 @@ open class UnitConverter(
|
||||
val dimension: Dimension,
|
||||
val inputValue: UnitValue,
|
||||
val values: List<UnitValue>
|
||||
)
|
||||
|
||||
): Searchable
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
package de.mm20.launcher2.unitconverter
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import de.mm20.launcher2.currencies.CurrencyRepository
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.search.data.UnitConverter
|
||||
@ -15,7 +14,7 @@ import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
interface UnitConverterRepository {
|
||||
fun search(query: String): Flow<UnitConverter?>
|
||||
fun search(query: String, includeCurrencies: Boolean): Flow<UnitConverter?>
|
||||
}
|
||||
|
||||
internal class UnitConverterRepositoryImpl(
|
||||
@ -26,8 +25,6 @@ internal class UnitConverterRepositoryImpl(
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
|
||||
val unitConverter = MutableLiveData<UnitConverter?>()
|
||||
|
||||
init {
|
||||
scope.launch {
|
||||
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()) {
|
||||
send(null)
|
||||
return@channelFlow
|
||||
}
|
||||
dataStore.data.map { it.unitConverterSearch }.collectLatest {
|
||||
if (it.enabled) {
|
||||
send(queryUnitConverter(query, it.currencies))
|
||||
} else {
|
||||
send(null)
|
||||
}
|
||||
}
|
||||
send(queryUnitConverter(query, includeCurrencies))
|
||||
}
|
||||
|
||||
private suspend fun queryUnitConverter(
|
||||
|
||||
@ -50,7 +50,6 @@ dependencies {
|
||||
|
||||
implementation(libs.coil.core)
|
||||
|
||||
implementation(project(":preferences"))
|
||||
implementation(project(":base"))
|
||||
implementation(project(":ktx"))
|
||||
|
||||
|
||||
@ -3,13 +3,10 @@ package de.mm20.launcher2.websites
|
||||
import android.content.Context
|
||||
import android.webkit.URLUtil
|
||||
import androidx.core.graphics.toColorInt
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.search.data.Website
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.OkHttpClient
|
||||
@ -17,7 +14,6 @@ import okhttp3.Request
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.UncheckedIOException
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.io.IOException
|
||||
import java.net.MalformedURLException
|
||||
import java.net.URISyntaxException
|
||||
@ -30,8 +26,6 @@ interface WebsiteRepository {
|
||||
|
||||
internal class WebsiteRepositoryImpl(val context: Context) : WebsiteRepository, KoinComponent {
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
private val httpClient = OkHttpClient
|
||||
.Builder()
|
||||
.connectTimeout(200, TimeUnit.MILLISECONDS)
|
||||
@ -46,14 +40,8 @@ internal class WebsiteRepositoryImpl(val context: Context) : WebsiteRepository,
|
||||
}
|
||||
if (query.isBlank()) return@channelFlow
|
||||
|
||||
dataStore.data.map { it.websiteSearch.enabled }.collectLatest {
|
||||
if(it) {
|
||||
val website = queryWebsite(query)
|
||||
send(website)
|
||||
} else {
|
||||
send(null)
|
||||
}
|
||||
}
|
||||
val website = queryWebsite(query)
|
||||
send(website)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -4,5 +4,5 @@ import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
|
||||
val wikipediaModule = module {
|
||||
single<WikipediaRepository> { WikipediaRepositoryImpl(androidContext()) }
|
||||
single<WikipediaRepository> { WikipediaRepositoryImpl(androidContext(), get()) }
|
||||
}
|
||||
@ -15,17 +15,16 @@ import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
interface WikipediaRepository {
|
||||
fun search(query: String): Flow<Wikipedia?>
|
||||
fun search(query: String, loadImages: Boolean = false): Flow<Wikipedia?>
|
||||
}
|
||||
|
||||
internal class WikipediaRepositoryImpl(
|
||||
private val context: Context
|
||||
private val context: Context,
|
||||
private val dataStore: LauncherDataStore
|
||||
) : WikipediaRepository, KoinComponent {
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
private val httpClient = OkHttpClient
|
||||
.Builder()
|
||||
.connectTimeout(200, TimeUnit.MILLISECONDS)
|
||||
@ -58,7 +57,7 @@ internal class WikipediaRepositoryImpl(
|
||||
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)
|
||||
withContext(Dispatchers.IO) {
|
||||
httpClient.dispatcher.cancelAll()
|
||||
@ -67,13 +66,7 @@ internal class WikipediaRepositoryImpl(
|
||||
if (!::wikipediaService.isInitialized) return@channelFlow
|
||||
if (query.isBlank()) return@channelFlow
|
||||
|
||||
dataStore.data.map { it.wikipediaSearch }.collectLatest {
|
||||
if (it.enabled) {
|
||||
send(queryWikipedia(query, it.images))
|
||||
} else {
|
||||
send(null)
|
||||
}
|
||||
}
|
||||
send(queryWikipedia(query, loadImages))
|
||||
}
|
||||
|
||||
private suspend fun queryWikipedia(query: String, loadImages: Boolean): Wikipedia? {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user