Refactor search
This commit is contained in:
parent
048c1e8cf9
commit
2157147caa
@ -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"))
|
||||||
|
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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"))
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()) }
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
|||||||
@ -1,6 +1,3 @@
|
|||||||
package de.mm20.launcher2.search
|
package de.mm20.launcher2.search
|
||||||
|
|
||||||
interface Searchable {
|
interface Searchable
|
||||||
val domain: String
|
|
||||||
val key: String
|
|
||||||
}
|
|
||||||
@ -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(libs.koin.android)
|
||||||
|
|
||||||
implementation(project(":preferences"))
|
|
||||||
implementation(project(":base"))
|
implementation(project(":base"))
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -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)
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"))
|
||||||
|
|||||||
@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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()) }
|
||||||
}
|
}
|
||||||
@ -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"))
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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()) }
|
||||||
}
|
}
|
||||||
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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
|
||||||
|
|||||||
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]
|
":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"
|
||||||
|
|||||||
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(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"))
|
||||||
|
|||||||
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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()) }
|
||||||
}
|
}
|
||||||
@ -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"))
|
||||||
|
|||||||
@ -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()) }
|
||||||
}
|
}
|
||||||
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.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)
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.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) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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"))
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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()) }
|
||||||
}
|
}
|
||||||
@ -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? {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user