Add tasks.org integration
This commit is contained in:
@@ -30,6 +30,7 @@
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="org.tasks.permission.READ_TASKS" />
|
||||
|
||||
<application
|
||||
android:name=".LauncherApplication"
|
||||
|
||||
@@ -12,7 +12,16 @@ import de.mm20.launcher2.searchable.PinnedLevel
|
||||
import de.mm20.launcher2.services.favorites.FavoritesService
|
||||
import de.mm20.launcher2.widgets.CalendarWidget
|
||||
import de.mm20.launcher2.widgets.WidgetRepository
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@@ -25,7 +34,8 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
||||
|
||||
val selectedTag = MutableStateFlow<String?>(null)
|
||||
|
||||
val showEditButton = settings.showEditButton.stateIn(viewModelScope, SharingStarted.Lazily, false)
|
||||
val showEditButton =
|
||||
settings.showEditButton.stateIn(viewModelScope, SharingStarted.Lazily, false)
|
||||
abstract val tagsExpanded: Flow<Boolean>
|
||||
abstract val compactTags: Flow<Boolean>
|
||||
|
||||
@@ -46,34 +56,44 @@ abstract class FavoritesVM : ViewModel(), KoinComponent {
|
||||
) { (a, b) -> a as Boolean to b as FavoritesSettingsData }
|
||||
.transformLatest {
|
||||
|
||||
val columns = it.second.columns
|
||||
val excludeCalendar = it.first
|
||||
val includeFrequentlyUsed = it.second.frequentlyUsed
|
||||
val frequentlyUsedRows = it.second.frequentlyUsedRows
|
||||
val columns = it.second.columns
|
||||
val excludeCalendar = it.first
|
||||
val includeFrequentlyUsed = it.second.frequentlyUsed
|
||||
val frequentlyUsedRows = it.second.frequentlyUsedRows
|
||||
|
||||
val pinned = favoritesService.getFavorites(
|
||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag", "plugin.calendar") else listOf("tag"),
|
||||
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||
limit = 10 * columns,
|
||||
)
|
||||
if (includeFrequentlyUsed) {
|
||||
emitAll(pinned.flatMapLatest { pinned ->
|
||||
favoritesService.getFavorites(
|
||||
excludeTypes = if (excludeCalendar) listOf("calendar", "tag", "plugin.calendar") else listOf("tag"),
|
||||
maxPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||
minPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||
limit = frequentlyUsedRows * columns - pinned.size % columns,
|
||||
).map {
|
||||
pinned + it
|
||||
}
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
})
|
||||
} else {
|
||||
emitAll(
|
||||
pinned.withCustomLabels(customAttributesRepository)
|
||||
val pinned = favoritesService.getFavorites(
|
||||
excludeTypes = if (excludeCalendar) listOf(
|
||||
"calendar",
|
||||
"tasks.org",
|
||||
"tag",
|
||||
"plugin.calendar"
|
||||
) else listOf("tag"),
|
||||
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||
limit = 10 * columns,
|
||||
)
|
||||
if (includeFrequentlyUsed) {
|
||||
emitAll(pinned.flatMapLatest { pinned ->
|
||||
favoritesService.getFavorites(
|
||||
excludeTypes = if (excludeCalendar) listOf(
|
||||
"calendar",
|
||||
"tasks.org",
|
||||
"tag",
|
||||
"plugin.calendar"
|
||||
) else listOf("tag"),
|
||||
maxPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||
minPinnedLevel = PinnedLevel.FrequentlyUsed,
|
||||
limit = frequentlyUsedRows * columns - pinned.size % columns,
|
||||
).map {
|
||||
pinned + it
|
||||
}
|
||||
.withCustomLabels(customAttributesRepository)
|
||||
})
|
||||
} else {
|
||||
emitAll(
|
||||
pinned.withCustomLabels(customAttributesRepository)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
customAttributesRepository
|
||||
.getItemsForTag(tag)
|
||||
|
||||
@@ -554,6 +554,7 @@ fun ColumnScope.ConfigureCalendarWidget(
|
||||
for (group in groups) {
|
||||
val pluginName = remember(plugins, group.key) {
|
||||
if (group.key == "local") context.getString(R.string.preference_calendar_calendars)
|
||||
else if (group.key == "tasks.org") context.getString(R.string.preference_calendar_tasks)
|
||||
else plugins.find { it.authority == group.key }?.label
|
||||
}
|
||||
if (pluginName != null) {
|
||||
|
||||
+2
-2
@@ -43,7 +43,7 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
||||
val calendarEvents = mutableStateOf<List<CalendarEvent>>(emptyList())
|
||||
val pinnedCalendarEvents =
|
||||
favoritesService.getFavorites(
|
||||
includeTypes = listOf("calendar", "plugin.calendar"),
|
||||
includeTypes = listOf("calendar", "tasks.org", "plugin.calendar"),
|
||||
minPinnedLevel = PinnedLevel.AutomaticallySorted,
|
||||
).stateIn(viewModelScope, SharingStarted.WhileSubscribed(), emptyList())
|
||||
val nextEvents = mutableStateOf<List<CalendarEvent>>(emptyList())
|
||||
@@ -172,7 +172,7 @@ class CalendarWidgetVM : ViewModel(), KoinComponent {
|
||||
excludeCalendars = config.excludedCalendarIds ?: config.legacyExcludedCalendarIds?.map { "local:$it" } ?: emptyList(),
|
||||
).collectLatest { events ->
|
||||
searchableRepository.getKeys(
|
||||
includeTypes = listOf("calendar", "plugin.calendar"),
|
||||
includeTypes = listOf("calendar", "tasks.org", "plugin.calendar"),
|
||||
maxVisibility = VisibilityLevel.SearchOnly,
|
||||
limit = 9999,
|
||||
).collectLatest { hidden ->
|
||||
|
||||
+3
-1
@@ -46,7 +46,7 @@ fun CalendarProviderSettingsScreen(providerId: String) {
|
||||
|
||||
val pluginState by viewModel.pluginState.collectAsStateWithLifecycle(null)
|
||||
|
||||
val providerAvailable = providerId == "local" || pluginState != null
|
||||
val providerAvailable = providerId == "local" || providerId == "tasks.org" || pluginState != null
|
||||
|
||||
PreferenceScreen(
|
||||
title = pluginState?.plugin?.label ?: stringResource(R.string.preference_search_calendar)
|
||||
@@ -59,9 +59,11 @@ fun CalendarProviderSettingsScreen(providerId: String) {
|
||||
SwitchPreference(
|
||||
title =
|
||||
if (providerId == "local") stringResource(R.string.preference_search_calendar)
|
||||
else if (providerId == "tasks.org") stringResource(R.string.preference_search_tasks)
|
||||
else pluginState?.plugin?.label ?: "",
|
||||
summary =
|
||||
if (providerId == "local") stringResource(R.string.preference_search_local_calendar_summary)
|
||||
else if (providerId == "tasks.org") stringResource(R.string.preference_search_tasks_summary)
|
||||
else (pluginState?.state as? PluginState.Ready)?.text
|
||||
?: pluginState?.plugin?.description,
|
||||
value = enabled && (pluginState == null || pluginState?.state is PluginState.Ready),
|
||||
|
||||
+29
-4
@@ -11,9 +11,6 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -39,7 +36,12 @@ fun CalendarSearchSettingsScreen() {
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val hasCalendarPermission by viewModel.hasCalendarPermission.collectAsState(null)
|
||||
val plugins by viewModel.availablePlugins.collectAsStateWithLifecycle(emptyList(), minActiveState = Lifecycle.State.RESUMED)
|
||||
val hasTasksPermission by viewModel.hasTasksPermission.collectAsState(null)
|
||||
val isTasksAppInstalled by viewModel.isTasksAppInstalled.collectAsStateWithLifecycle(false)
|
||||
val plugins by viewModel.availablePlugins.collectAsStateWithLifecycle(
|
||||
emptyList(),
|
||||
minActiveState = Lifecycle.State.RESUMED
|
||||
)
|
||||
val enabledProviders by viewModel.enabledProviders.collectAsState(emptySet())
|
||||
|
||||
PreferenceScreen(title = stringResource(R.string.preference_search_calendar)) {
|
||||
@@ -66,6 +68,29 @@ fun CalendarSearchSettingsScreen() {
|
||||
navController?.navigate("settings/search/calendar/local")
|
||||
}
|
||||
)
|
||||
if (isTasksAppInstalled) {
|
||||
AnimatedVisibility(hasTasksPermission == false) {
|
||||
MissingPermissionBanner(
|
||||
text = stringResource(R.string.missing_permission_tasks_search_settings),
|
||||
onClick = {
|
||||
viewModel.requestTasksPermission(context as AppCompatActivity)
|
||||
},
|
||||
modifier = Modifier.padding(16.dp)
|
||||
)
|
||||
}
|
||||
PreferenceWithSwitch(
|
||||
title = stringResource(R.string.preference_search_tasks),
|
||||
summary = stringResource(R.string.preference_search_tasks_summary),
|
||||
switchValue = enabledProviders.contains("tasks.org") && hasTasksPermission == true,
|
||||
onSwitchChanged = {
|
||||
viewModel.setProviderEnabled("tasks.org", it)
|
||||
},
|
||||
enabled = hasTasksPermission == true,
|
||||
onClick = {
|
||||
navController?.navigate("settings/search/calendar/tasks.org")
|
||||
}
|
||||
)
|
||||
}
|
||||
for (plugin in plugins) {
|
||||
val state = plugin.state
|
||||
if (state is PluginState.SetupRequired) {
|
||||
|
||||
+15
-5
@@ -1,7 +1,10 @@
|
||||
package de.mm20.launcher2.ui.settings.calendarsearch
|
||||
|
||||
import android.os.Process
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.applications.AppRepository
|
||||
import de.mm20.launcher2.calendar.CalendarRepository
|
||||
import de.mm20.launcher2.calendar.providers.CalendarList
|
||||
import de.mm20.launcher2.permissions.PermissionGroup
|
||||
@@ -11,16 +14,25 @@ import de.mm20.launcher2.plugin.PluginType
|
||||
import de.mm20.launcher2.plugins.PluginService
|
||||
import de.mm20.launcher2.preferences.search.CalendarSearchSettings
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.shareIn
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class CalendarSearchSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
private val settings: CalendarSearchSettings by inject()
|
||||
private val calendarRepository: CalendarRepository by inject()
|
||||
private val appRepository: AppRepository by inject()
|
||||
private val pluginService: PluginService by inject()
|
||||
private val permissionsManager: PermissionsManager by inject()
|
||||
|
||||
val hasCalendarPermission = permissionsManager.hasPermission(PermissionGroup.Calendar)
|
||||
val hasTasksPermission = permissionsManager.hasPermission(PermissionGroup.Tasks)
|
||||
|
||||
val isTasksAppInstalled = appRepository.findOne("org.tasks", Process.myUserHandle())
|
||||
.map { it != null }
|
||||
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
|
||||
|
||||
val availablePlugins = pluginService.getPluginsWithState(
|
||||
type = PluginType.Calendar,
|
||||
@@ -37,10 +49,8 @@ class CalendarSearchSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
permissionsManager.requestPermission(activity, PermissionGroup.Calendar)
|
||||
}
|
||||
|
||||
val calendarLists = calendarRepository.getCalendars()
|
||||
|
||||
val excludedCalendars = settings.excludedCalendars
|
||||
fun setCalendarExcluded(calendarId: String, excluded: Boolean) {
|
||||
settings.setCalendarExcluded(calendarId, excluded)
|
||||
fun requestTasksPermission(activity: AppCompatActivity) {
|
||||
permissionsManager.requestPermission(activity, PermissionGroup.Tasks)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -111,7 +111,7 @@ fun SearchSettingsScreen() {
|
||||
}
|
||||
)
|
||||
|
||||
if (hasLocationPlugins != false) {
|
||||
if (hasContactPlugins != false) {
|
||||
Preference(
|
||||
title = stringResource(R.string.preference_search_contacts),
|
||||
summary = stringResource(R.string.preference_search_contacts_summary),
|
||||
|
||||
Reference in New Issue
Block a user