Migrate search settings
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
package de.mm20.launcher2.calendar
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.provider.CalendarContract
|
||||
import androidx.core.database.getStringOrNull
|
||||
import de.mm20.launcher2.hiddenitems.HiddenItemsRepository
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
@@ -9,6 +15,10 @@ import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.channels.trySendBlocking
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import org.koin.core.component.inject
|
||||
import java.util.*
|
||||
|
||||
interface CalendarRepository {
|
||||
fun search(query: String): Flow<List<CalendarEvent>>
|
||||
@@ -19,34 +29,131 @@ interface CalendarRepository {
|
||||
class CalendarRepositoryImpl(
|
||||
private val context: Context,
|
||||
hiddenItemsRepository: HiddenItemsRepository
|
||||
) : CalendarRepository {
|
||||
) : CalendarRepository, KoinComponent {
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
|
||||
private val hiddenItems = hiddenItemsRepository.hiddenItemsKeys
|
||||
|
||||
override fun search(query: String): Flow<List<CalendarEvent>> = channelFlow {
|
||||
if (query.isBlank()) {
|
||||
if (query.isBlank() || query.length < 3) {
|
||||
send(emptyList())
|
||||
return@channelFlow
|
||||
}
|
||||
val events = withContext(Dispatchers.IO) {
|
||||
val now = System.currentTimeMillis()
|
||||
CalendarEvent.search(
|
||||
context,
|
||||
query,
|
||||
intervalStart = now,
|
||||
intervalEnd = now + 14 * 24 * 60 * 60 * 1000L,
|
||||
)
|
||||
}
|
||||
|
||||
hiddenItems.collectLatest { hiddenItems ->
|
||||
val calendarResults = withContext(Dispatchers.IO) {
|
||||
events.filter { !hiddenItems.contains(it.key) }
|
||||
val hasPermission = permissionsManager.hasPermission(PermissionGroup.Calendar)
|
||||
val searchCalendar = dataStore.data.map { it.calendarSearch.enabled }
|
||||
combine(hasPermission, searchCalendar) { permission, search ->
|
||||
permission && search
|
||||
}.map {
|
||||
if (it) {
|
||||
val now = System.currentTimeMillis()
|
||||
queryCalendarEvents(
|
||||
query,
|
||||
intervalStart = now,
|
||||
intervalEnd = now + 14 * 24 * 60 * 60 * 1000L,
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
send(calendarResults)
|
||||
}.flatMapLatest { events ->
|
||||
hiddenItems.map { hidden ->
|
||||
events.filter { !hidden.contains(it.key) }
|
||||
}
|
||||
}.collectLatest {
|
||||
send(it)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private suspend fun queryCalendarEvents(
|
||||
query: String,
|
||||
intervalStart: Long,
|
||||
intervalEnd: Long,
|
||||
limit: Int = 10,
|
||||
excludeAllDayEvents: Boolean = false,
|
||||
excludeCalendars: List<Long> = emptyList(),
|
||||
): List<CalendarEvent> {
|
||||
val results = withContext(Dispatchers.IO) {
|
||||
val results = mutableListOf<CalendarEvent>()
|
||||
val builder = CalendarContract.Instances.CONTENT_URI.buildUpon()
|
||||
ContentUris.appendId(builder, intervalStart)
|
||||
ContentUris.appendId(builder, intervalEnd)
|
||||
val uri = builder.build()
|
||||
val projection = arrayOf(
|
||||
CalendarContract.Instances.EVENT_ID,
|
||||
CalendarContract.Instances.TITLE,
|
||||
CalendarContract.Instances.BEGIN,
|
||||
CalendarContract.Instances.END,
|
||||
CalendarContract.Instances.ALL_DAY,
|
||||
CalendarContract.Instances.DISPLAY_COLOR,
|
||||
CalendarContract.Instances.EVENT_LOCATION,
|
||||
CalendarContract.Instances.CALENDAR_ID,
|
||||
CalendarContract.Instances.DESCRIPTION
|
||||
)
|
||||
val selection = mutableListOf<String>()
|
||||
if (query.isNotEmpty()) selection.add("${CalendarContract.Instances.TITLE} LIKE ?")
|
||||
if (excludeCalendars.isNotEmpty()) selection.add("${CalendarContract.Instances.CALENDAR_ID} NOT IN (${excludeCalendars.joinToString()})")
|
||||
if (excludeAllDayEvents) selection.add("${CalendarContract.Instances.ALL_DAY} = 0")
|
||||
val selArgs = if (query.isBlank()) null else arrayOf("%$query%")
|
||||
val sort =
|
||||
"${CalendarContract.Instances.BEGIN} ASC" + if (limit > -1) " LIMIT $limit" else ""
|
||||
val cursor = context.contentResolver.query(
|
||||
uri,
|
||||
projection,
|
||||
selection.joinToString(separator = " AND "),
|
||||
selArgs,
|
||||
sort
|
||||
) ?: return@withContext mutableListOf()
|
||||
val proj = arrayOf(
|
||||
CalendarContract.Attendees.EVENT_ID,
|
||||
CalendarContract.Attendees.ATTENDEE_NAME,
|
||||
CalendarContract.Attendees.ATTENDEE_EMAIL
|
||||
)
|
||||
val s = "${CalendarContract.Attendees.ATTENDEE_NAME} COLLATE NOCASE ASC"
|
||||
while (cursor.moveToNext()) {
|
||||
val sel = "${CalendarContract.Attendees.EVENT_ID} = ${cursor.getLong(0)}"
|
||||
val cur = context.contentResolver.query(
|
||||
CalendarContract.Attendees.CONTENT_URI,
|
||||
proj, sel, null, s
|
||||
) ?: return@withContext mutableListOf()
|
||||
val attendees = mutableListOf<String>()
|
||||
while (cur.moveToNext()) {
|
||||
attendees.add(cur.getString(1).takeUnless { it.isNullOrBlank() }
|
||||
?: cur.getString(2))
|
||||
}
|
||||
cur.close()
|
||||
val allday = cursor.getInt(4) > 0
|
||||
val begin = cursor.getLong(2)
|
||||
|
||||
val tzOffset = if (allday) {
|
||||
Calendar.getInstance().timeZone.getOffset(begin)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
val event = CalendarEvent(
|
||||
label = cursor.getString(1) ?: "",
|
||||
id = cursor.getLong(0),
|
||||
color = cursor.getInt(5),
|
||||
startTime = begin - tzOffset,
|
||||
endTime = cursor.getLong(3) - tzOffset - if (allday) 1 else 0,
|
||||
allDay = allday,
|
||||
location = cursor.getString(6) ?: "",
|
||||
attendees = attendees,
|
||||
description = cursor.getStringOrNull(8)
|
||||
?: "",
|
||||
calendar = cursor.getLong(7)
|
||||
)
|
||||
results.add(event)
|
||||
}
|
||||
cursor.close()
|
||||
return@withContext results
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
override fun getUpcomingEvents(): Flow<List<CalendarEvent>> = channelFlow {
|
||||
val unselectedCalendars = callbackFlow {
|
||||
val unregister =
|
||||
@@ -70,23 +177,26 @@ class CalendarRepositoryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
merge(unselectedCalendars, hideAlldayEvents, hiddenItems).collectLatest {
|
||||
val now = System.currentTimeMillis()
|
||||
val end = now + 14 * 24 * 60 * 60 * 1000L
|
||||
val events = withContext(Dispatchers.IO) {
|
||||
CalendarEvent.search(
|
||||
context = context,
|
||||
query = "",
|
||||
intervalStart = now,
|
||||
intervalEnd = end,
|
||||
limit = 700,
|
||||
hideAllDayEvents = LauncherPreferences.instance.calendarHideAllday,
|
||||
unselectedCalendars = LauncherPreferences.instance.unselectedCalendars
|
||||
).filter {
|
||||
!hiddenItems.value.contains(it.key)
|
||||
hideAlldayEvents.collectLatest { hideAllday ->
|
||||
unselectedCalendars.collectLatest { unselected ->
|
||||
hiddenItems.collectLatest { hidden ->
|
||||
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 = hideAllday,
|
||||
excludeCalendars = unselected
|
||||
).filter {
|
||||
!hiddenItems.value.contains(it.key)
|
||||
}
|
||||
}
|
||||
send(events)
|
||||
}
|
||||
}
|
||||
send(events)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -80,99 +80,6 @@ class CalendarEvent(
|
||||
}
|
||||
|
||||
companion object: KoinComponent {
|
||||
fun search(
|
||||
context: Context,
|
||||
query: String,
|
||||
intervalStart: Long,
|
||||
intervalEnd: Long,
|
||||
limit: Int = 10,
|
||||
hideAllDayEvents: Boolean = false,
|
||||
unselectedCalendars: List<Long> = emptyList(),
|
||||
hiddenEvents: List<Long> = emptyList()
|
||||
): List<CalendarEvent> {
|
||||
val permissionsManager: PermissionsManager = get()
|
||||
val results = mutableListOf<CalendarEvent>()
|
||||
if (!query.isEmpty() && query.length < 3) return results
|
||||
if (!LauncherPreferences.instance.searchCalendars) return listOf()
|
||||
if (!permissionsManager.checkPermissionOnce(PermissionGroup.Calendar)) {
|
||||
return emptyList()
|
||||
}
|
||||
val builder = CalendarContract.Instances.CONTENT_URI.buildUpon()
|
||||
ContentUris.appendId(builder, intervalStart)
|
||||
ContentUris.appendId(builder, intervalEnd)
|
||||
val uri = builder.build()
|
||||
val projection = arrayOf(
|
||||
CalendarContract.Instances.EVENT_ID,
|
||||
CalendarContract.Instances.TITLE,
|
||||
CalendarContract.Instances.BEGIN,
|
||||
CalendarContract.Instances.END,
|
||||
CalendarContract.Instances.ALL_DAY,
|
||||
CalendarContract.Instances.DISPLAY_COLOR,
|
||||
CalendarContract.Instances.EVENT_LOCATION,
|
||||
CalendarContract.Instances.CALENDAR_ID,
|
||||
CalendarContract.Instances.DESCRIPTION
|
||||
)
|
||||
val selection = mutableListOf<String>()
|
||||
if (query.isNotEmpty()) selection.add("${CalendarContract.Instances.TITLE} LIKE ?")
|
||||
if (hiddenEvents.isNotEmpty()) selection.add("${CalendarContract.Instances.EVENT_ID} NOT IN (${hiddenEvents.joinToString()})")
|
||||
if (unselectedCalendars.isNotEmpty()) selection.add("${CalendarContract.Instances.CALENDAR_ID} NOT IN (${unselectedCalendars.joinToString()})")
|
||||
if (hideAllDayEvents) selection.add("${CalendarContract.Instances.ALL_DAY} = 0")
|
||||
val selArgs = if (query.isBlank()) null else arrayOf("%$query%")
|
||||
val sort =
|
||||
"${CalendarContract.Instances.BEGIN} ASC" + if (limit > -1) " LIMIT $limit" else ""
|
||||
val cursor = context.contentResolver.query(
|
||||
uri,
|
||||
projection,
|
||||
selection.joinToString(separator = " AND "),
|
||||
selArgs,
|
||||
sort
|
||||
)
|
||||
?: return mutableListOf()
|
||||
val proj = arrayOf(
|
||||
CalendarContract.Attendees.EVENT_ID,
|
||||
CalendarContract.Attendees.ATTENDEE_NAME,
|
||||
CalendarContract.Attendees.ATTENDEE_EMAIL
|
||||
)
|
||||
val s = "${CalendarContract.Attendees.ATTENDEE_NAME} COLLATE NOCASE ASC"
|
||||
while (cursor.moveToNext()) {
|
||||
val sel = "${CalendarContract.Attendees.EVENT_ID} = ${cursor.getLong(0)}"
|
||||
val cur = context.contentResolver.query(
|
||||
CalendarContract.Attendees.CONTENT_URI,
|
||||
proj, sel, null, s
|
||||
) ?: return mutableListOf()
|
||||
val attendees = mutableListOf<String>()
|
||||
while (cur.moveToNext()) {
|
||||
attendees.add(cur.getString(1).takeUnless { it.isNullOrBlank() }
|
||||
?: cur.getString(2))
|
||||
}
|
||||
cur.close()
|
||||
val allday = cursor.getInt(4) > 0
|
||||
val begin = cursor.getLong(2)
|
||||
|
||||
val tzOffset = if (allday) {
|
||||
Calendar.getInstance().timeZone.getOffset(begin)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
val event = CalendarEvent(
|
||||
label = cursor.getString(1) ?: "",
|
||||
id = cursor.getLong(0),
|
||||
color = cursor.getInt(5),
|
||||
startTime = begin - tzOffset,
|
||||
endTime = cursor.getLong(3) - tzOffset - if (allday) 1 else 0,
|
||||
allDay = allday,
|
||||
location = cursor.getString(6) ?: "",
|
||||
attendees = attendees,
|
||||
description = cursor.getStringOrNull(8)
|
||||
?: "",
|
||||
calendar = cursor.getLong(7)
|
||||
)
|
||||
results.add(event)
|
||||
}
|
||||
cursor.close()
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
fun getCalendars(context: Context): List<UserCalendar> {
|
||||
val calendars = mutableListOf<UserCalendar>()
|
||||
|
||||
Reference in New Issue
Block a user