Add Tasks integration settings screen

This commit is contained in:
MM20 2025-04-27 14:24:30 +02:00
parent c7acd7ad24
commit 28b33398cd
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
7 changed files with 198 additions and 3 deletions

View File

@ -69,6 +69,7 @@ import de.mm20.launcher2.ui.settings.plugins.PluginsSettingsScreen
import de.mm20.launcher2.ui.settings.search.SearchSettingsScreen
import de.mm20.launcher2.ui.settings.searchactions.SearchActionsSettingsScreen
import de.mm20.launcher2.ui.settings.tags.TagsSettingsScreen
import de.mm20.launcher2.ui.settings.tasks.TasksIntegrationSettingsScreen
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterHelpSettingsScreen
import de.mm20.launcher2.ui.settings.unitconverter.UnitConverterSettingsScreen
import de.mm20.launcher2.ui.settings.weather.WeatherIntegrationSettingsScreen
@ -240,6 +241,9 @@ class SettingsActivity : BaseActivity() {
composable("settings/integrations/owncloud") {
OwncloudSettingsScreen()
}
composable("settings/integrations/tasks") {
TasksIntegrationSettingsScreen()
}
composable("settings/plugins") {
PluginsSettingsScreen()
}

View File

@ -1,6 +1,7 @@
package de.mm20.launcher2.ui.settings.integrations
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.rounded.LightMode
import androidx.compose.material.icons.rounded.PlayCircleOutline
import androidx.compose.runtime.Composable
@ -49,6 +50,13 @@ fun IntegrationsSettingsScreen() {
navController?.navigate("settings/integrations/owncloud")
}
)
Preference(
title = stringResource(R.string.preference_tasks_integration),
icon = Icons.Default.Check,
onClick = {
navController?.navigate("settings/integrations/tasks")
}
)
}
}
}

View File

@ -63,7 +63,7 @@ fun SearchSettingsScreen() {
val hasCalendarPlugins by remember { derivedStateOf { plugins?.any { it.plugin.type == PluginType.Calendar } } }
val hasLocationPlugins by remember { derivedStateOf { plugins?.any { it.plugin.type == PluginType.LocationSearch } } }
val hasContactPlugins by remember { derivedStateOf { plugins?.any { it.plugin.type == PluginType.ContactSearch } } }
val isTasksAppInstalled by viewModel.isTasksAppInstalled.collectAsStateWithLifecycle()
val hasAppShortcutsPermission by viewModel.hasAppShortcutPermission.collectAsStateWithLifecycle(null)
val hasContactsPermission by viewModel.hasContactsPermission.collectAsStateWithLifecycle(null)
@ -145,7 +145,7 @@ fun SearchSettingsScreen() {
)
}
if (hasCalendarPlugins != false) {
if (hasCalendarPlugins != false || isTasksAppInstalled != false) {
Preference(
title = stringResource(R.string.preference_search_calendar),
summary = stringResource(R.string.preference_search_calendar_summary),

View File

@ -1,8 +1,10 @@
package de.mm20.launcher2.ui.settings.search
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.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.plugins.PluginService
@ -18,6 +20,7 @@ import de.mm20.launcher2.preferences.search.WikipediaSearchSettings
import de.mm20.launcher2.preferences.ui.SearchUiSettings
import de.mm20.launcher2.search.SearchFilters
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -34,6 +37,8 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
private val locationSearchSettings: LocationSearchSettings by inject()
private val searchFilterSettings: SearchFilterSettings by inject()
private val appRepository: AppRepository by inject()
private val pluginService: PluginService by inject()
private val permissionsManager: PermissionsManager by inject()
@ -159,4 +164,8 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
val plugins = pluginService.getPluginsWithState(enabled = true)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
val isTasksAppInstalled = appRepository.findOne("org.tasks", Process.myUserHandle())
.map { it != null }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
}

View File

@ -0,0 +1,104 @@
package de.mm20.launcher2.ui.settings.tasks
import androidx.activity.compose.LocalActivity
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.OpenInNew
import androidx.compose.material.icons.rounded.CheckCircle
import androidx.compose.material.icons.rounded.Info
import androidx.compose.material.icons.rounded.TaskAlt
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.ui.component.Banner
import de.mm20.launcher2.ui.component.MissingPermissionBanner
import de.mm20.launcher2.ui.component.preferences.Preference
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
import de.mm20.launcher2.ui.component.preferences.PreferenceWithSwitch
import de.mm20.launcher2.ui.locals.LocalNavController
@Composable
fun TasksIntegrationSettingsScreen() {
val viewModel: TasksSettingsScreenVM = viewModel()
val activity = LocalActivity.current
val navController = LocalNavController.current
val isTasksInstalled by viewModel.isTasksAppInstalled.collectAsStateWithLifecycle(null)
val hasTasksPermission by viewModel.hasTasksPermission.collectAsStateWithLifecycle(null)
val isTasksSearchEnabled by viewModel.isTasksSearchEnabled.collectAsStateWithLifecycle(false)
PreferenceScreen(
title = stringResource(R.string.preference_tasks_integration)
) {
if (isTasksInstalled == false) {
item {
Banner(
text = stringResource(
R.string.preference_tasks_integration_description,
stringResource(R.string.app_name)
),
icon = Icons.Rounded.Info,
modifier = Modifier.padding(16.dp),
primaryAction = {
Button(onClick = {
viewModel.downloadTasksApp(activity as AppCompatActivity)
}) {
Text(stringResource(R.string.action_install))
}
}
)
}
}
if (isTasksInstalled == true) {
item {
PreferenceCategory {
if (hasTasksPermission == false) {
MissingPermissionBanner(
modifier = Modifier.padding(16.dp),
text = stringResource(R.string.missing_permission_tasks_integration),
onClick = {
viewModel.requestTasksPermission(activity as AppCompatActivity)
}
)
}
if (hasTasksPermission == true) {
Banner(
text = stringResource(R.string.preference_tasks_integration_ready),
icon = Icons.Rounded.CheckCircle,
modifier = Modifier.padding(16.dp),
)
}
PreferenceWithSwitch(
icon = Icons.Rounded.TaskAlt,
title = stringResource(R.string.preference_search_tasks),
summary = stringResource(R.string.preference_search_tasks_summary),
switchValue = isTasksSearchEnabled == true && hasTasksPermission == true,
onSwitchChanged = {
viewModel.setTasksSearchEnabled(it)
},
enabled = hasTasksPermission == true,
onClick = {
navController?.navigate("settings/search/calendar/tasks.org")
}
)
Preference(
title = "Open Tasks app",
icon = Icons.AutoMirrored.Rounded.OpenInNew,
onClick = {
viewModel.launchTasksApp(activity as AppCompatActivity)
}
)
}
}
}
}
}

View File

@ -0,0 +1,64 @@
package de.mm20.launcher2.ui.settings.tasks
import android.content.Intent
import android.os.Process
import androidx.appcompat.app.AppCompatActivity
import androidx.core.net.toUri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.applications.AppRepository
import de.mm20.launcher2.icons.IconService
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.search.CalendarSearchSettings
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.launch
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
class TasksSettingsScreenVM : ViewModel(), KoinComponent {
private val appRepository: AppRepository by inject()
private val permissionsManager: PermissionsManager by inject()
private val iconService: IconService by inject()
private val calendarSearchSettings: CalendarSearchSettings by inject()
val tasksApp = appRepository.findOne("org.tasks", Process.myUserHandle())
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
val isTasksAppInstalled = tasksApp
.map { it != null }
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
val hasTasksPermission = permissionsManager.hasPermission(PermissionGroup.Tasks)
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
fun requestTasksPermission(activity: AppCompatActivity) {
permissionsManager.requestPermission(activity, PermissionGroup.Tasks)
}
fun downloadTasksApp(activity: AppCompatActivity) {
activity.startActivity(
Intent(Intent.ACTION_VIEW).apply {
data = "https://tasks.org/".toUri()
}
)
}
fun launchTasksApp(activity: AppCompatActivity) {
viewModelScope.launch {
tasksApp.first()?.launch(activity, null)
}
}
val isTasksSearchEnabled = calendarSearchSettings.isProviderEnabled("tasks.org")
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 1)
fun setTasksSearchEnabled(enabled: Boolean) {
calendarSearchSettings.setProviderEnabled("tasks.org", enabled)
}
}

View File

@ -37,6 +37,7 @@
<string name="action_done">Done</string>
<string name="action_more_actions">More actions</string>
<string name="action_clear">Clear</string>
<string name="action_install">Install</string>
<!-- Delete something (a file or a web search shortcut) -->
<string name="menu_delete">Delete</string>
<string name="menu_remove">Remove</string>
@ -389,6 +390,7 @@
<!-- Missing calendar permission in search settings screen -->
<string name="missing_permission_calendar_search_settings">Calendar permission is required to search calendar</string>
<string name="missing_permission_tasks_search_settings">Tasks permission is required to search tasks</string>
<string name="missing_permission_tasks_integration">Tasks permission is required to use the Tasks integration</string>
<!-- Missing calendar permission in calendar widget settings screen -->
<string name="missing_permission_calendar_widget_settings">This widget requires calendar permission</string>
<string name="widget_config_calendar_missing_calendars_hint">Can\'t find your calendars?</string>
@ -610,7 +612,7 @@
<string name="preference_search_calendar_summary">Search upcoming appointments and events</string>
<string name="preference_search_local_calendar_summary">Search calendars on this device</string>
<string name="preference_search_tasks">Tasks</string>
<string name="preference_search_tasks_summary">Search tasks in the Tasks.org app</string>
<string name="preference_search_tasks_summary">Search tasks in the Tasks app</string>
<string name="preference_search_appshortcuts">App shortcuts</string>
<string name="preference_search_appshortcuts_summary">Search app shortcuts</string>
<string name="preference_search_calculator">Calculator</string>
@ -665,6 +667,10 @@
<string name="preference_screen_icons_summary">Grid, icon size, icon packs, badges</string>
<string name="preference_weather_integration">Weather</string>
<string name="preference_media_integration">Media control</string>
<string name="preference_tasks_integration">Tasks</string>
<!-- %1$s: app name (Kvaesitso) -->
<string name="preference_tasks_integration_description">Tasks is a free and open source to-do list and reminders app. If installed, %1$s can display and search your tasks from the Tasks app.</string>
<string name="preference_tasks_integration_ready">Tasks integration is fully set up and ready to use.</string>
<string name="preference_contacts_call_on_tap">Tap to call</string>
<string name="preference_contacts_call_on_tap_summary">Immediately start a call when tapping a phone number</string>
<!-- Used in an info banner if a specific feature requires a Nextcloud account -->