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

View File

@ -1,6 +1,7 @@
package de.mm20.launcher2.ui.settings.integrations package de.mm20.launcher2.ui.settings.integrations
import androidx.compose.material.icons.Icons 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.LightMode
import androidx.compose.material.icons.rounded.PlayCircleOutline import androidx.compose.material.icons.rounded.PlayCircleOutline
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -49,6 +50,13 @@ fun IntegrationsSettingsScreen() {
navController?.navigate("settings/integrations/owncloud") 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 hasCalendarPlugins by remember { derivedStateOf { plugins?.any { it.plugin.type == PluginType.Calendar } } }
val hasLocationPlugins by remember { derivedStateOf { plugins?.any { it.plugin.type == PluginType.LocationSearch } } } 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 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 hasAppShortcutsPermission by viewModel.hasAppShortcutPermission.collectAsStateWithLifecycle(null)
val hasContactsPermission by viewModel.hasContactsPermission.collectAsStateWithLifecycle(null) val hasContactsPermission by viewModel.hasContactsPermission.collectAsStateWithLifecycle(null)
@ -145,7 +145,7 @@ fun SearchSettingsScreen() {
) )
} }
if (hasCalendarPlugins != false) { if (hasCalendarPlugins != false || isTasksAppInstalled != false) {
Preference( Preference(
title = stringResource(R.string.preference_search_calendar), title = stringResource(R.string.preference_search_calendar),
summary = stringResource(R.string.preference_search_calendar_summary), summary = stringResource(R.string.preference_search_calendar_summary),

View File

@ -1,8 +1,10 @@
package de.mm20.launcher2.ui.settings.search package de.mm20.launcher2.ui.settings.search
import android.os.Process
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import de.mm20.launcher2.applications.AppRepository
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.plugins.PluginService 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.preferences.ui.SearchUiSettings
import de.mm20.launcher2.search.SearchFilters import de.mm20.launcher2.search.SearchFilters
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
@ -34,6 +37,8 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
private val locationSearchSettings: LocationSearchSettings by inject() private val locationSearchSettings: LocationSearchSettings by inject()
private val searchFilterSettings: SearchFilterSettings by inject() private val searchFilterSettings: SearchFilterSettings by inject()
private val appRepository: AppRepository by inject()
private val pluginService: PluginService by inject() private val pluginService: PluginService by inject()
private val permissionsManager: PermissionsManager by inject() private val permissionsManager: PermissionsManager by inject()
@ -159,4 +164,8 @@ class SearchSettingsScreenVM : ViewModel(), KoinComponent {
val plugins = pluginService.getPluginsWithState(enabled = true) val plugins = pluginService.getPluginsWithState(enabled = true)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null) .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_done">Done</string>
<string name="action_more_actions">More actions</string> <string name="action_more_actions">More actions</string>
<string name="action_clear">Clear</string> <string name="action_clear">Clear</string>
<string name="action_install">Install</string>
<!-- Delete something (a file or a web search shortcut) --> <!-- Delete something (a file or a web search shortcut) -->
<string name="menu_delete">Delete</string> <string name="menu_delete">Delete</string>
<string name="menu_remove">Remove</string> <string name="menu_remove">Remove</string>
@ -389,6 +390,7 @@
<!-- Missing calendar permission in search settings screen --> <!-- 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_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_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 --> <!-- Missing calendar permission in calendar widget settings screen -->
<string name="missing_permission_calendar_widget_settings">This widget requires calendar permission</string> <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> <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_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_local_calendar_summary">Search calendars on this device</string>
<string name="preference_search_tasks">Tasks</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">App shortcuts</string>
<string name="preference_search_appshortcuts_summary">Search app shortcuts</string> <string name="preference_search_appshortcuts_summary">Search app shortcuts</string>
<string name="preference_search_calculator">Calculator</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_screen_icons_summary">Grid, icon size, icon packs, badges</string>
<string name="preference_weather_integration">Weather</string> <string name="preference_weather_integration">Weather</string>
<string name="preference_media_integration">Media control</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">Tap to call</string>
<string name="preference_contacts_call_on_tap_summary">Immediately start a call when tapping a phone number</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 --> <!-- Used in an info banner if a specific feature requires a Nextcloud account -->