Minor classes refactoring

This commit is contained in:
Ricki Hirner 2024-03-09 17:42:38 +01:00
parent 77ab1801fa
commit 3edcc02a21
7 changed files with 635 additions and 675 deletions

View file

@ -28,6 +28,7 @@ import at.bitfire.davdroid.resource.TaskUtils
import at.bitfire.davdroid.settings.Settings
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.ui.intro.BatteryOptimizationsPage
import at.bitfire.davdroid.ui.intro.OpenSourcePage
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineScope
@ -273,8 +274,8 @@ class AppSettingsActivity: AppCompatActivity() {
private fun resetHints() {
settings.remove(BatteryOptimizationsPage.Model.HINT_BATTERY_OPTIMIZATIONS)
settings.remove(BatteryOptimizationsPage.Model.HINT_AUTOSTART_PERMISSION)
settings.remove(TasksModel.HINT_OPENTASKS_NOT_INSTALLED)
//settings.remove(OpenSourceFragment.Model.SETTING_NEXT_DONATION_POPUP)
settings.remove(TasksActivity.Model.HINT_OPENTASKS_NOT_INSTALLED)
settings.remove(OpenSourcePage.Model.SETTING_NEXT_DONATION_POPUP)
Snackbar.make(requireView(), R.string.app_settings_reset_hints_success, Snackbar.LENGTH_LONG).show()
}

View file

@ -4,23 +4,65 @@
package at.bitfire.davdroid.ui
import android.Manifest
import android.app.Application
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.annotation.MainThread
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.viewModels
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewmodel.compose.viewModel
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.PackageChangedReceiver
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.ui.widget.CardWithImage
import at.bitfire.davdroid.util.PermissionUtils
import at.bitfire.ical4android.TaskProvider
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.accompanist.themeadapter.material.MdcTheme
import java.util.logging.Level
class PermissionsActivity: AppCompatActivity() {
val model by viewModels<PermissionsFragment.Model>()
val model by viewModels<Model>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MdcTheme {
PermissionsFragmentContent(model)
PermissionsContent(model)
}
}
}
@ -30,4 +72,271 @@ class PermissionsActivity: AppCompatActivity() {
model.checkPermissions()
}
class Model(app: Application): AndroidViewModel(app) {
val needKeepPermissions = MutableLiveData<Boolean>()
val openTasksAvailable = MutableLiveData<Boolean>()
val tasksOrgAvailable = MutableLiveData<Boolean>()
val jtxAvailable = MutableLiveData<Boolean>()
private val tasksWatcher = object: PackageChangedReceiver(app) {
@MainThread
override fun onReceive(context: Context?, intent: Intent?) {
checkPermissions()
}
}
init {
checkPermissions()
}
override fun onCleared() {
tasksWatcher.close()
}
@MainThread
fun checkPermissions() {
val pm = getApplication<Application>().packageManager
// auto-reset permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
needKeepPermissions.value = pm.isAutoRevokeWhitelisted
}
openTasksAvailable.value = pm.resolveContentProvider(TaskProvider.ProviderName.OpenTasks.authority, 0) != null
tasksOrgAvailable.value = pm.resolveContentProvider(TaskProvider.ProviderName.TasksOrg.authority, 0) != null
jtxAvailable.value = pm.resolveContentProvider(TaskProvider.ProviderName.JtxBoard.authority, 0) != null
}
}
}
@Composable
fun PermissionsContent(model: PermissionsActivity.Model = viewModel()) {
val context = LocalContext.current
val keepPermissions by model.needKeepPermissions.observeAsState()
val openTasksAvailable by model.openTasksAvailable.observeAsState()
val tasksOrgAvailable by model.tasksOrgAvailable.observeAsState()
val jtxAvailable by model.jtxAvailable.observeAsState()
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
PermissionsCard(
keepPermissions,
onKeepPermissionsRequested = {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val intent = Intent(
Intent.ACTION_AUTO_REVOKE_PERMISSIONS,
Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
)
try {
context.startActivity(intent)
Toast.makeText(context, R.string.permissions_autoreset_instruction, Toast.LENGTH_LONG).show()
} catch (e: Exception) {
Logger.log.log(Level.WARNING, "Couldn't start Keep Permissions activity", e)
}
}
},
openTasksAvailable,
tasksOrgAvailable,
jtxAvailable
)
}
}
@Composable
@OptIn(ExperimentalPermissionsApi::class)
fun PermissionSwitchRow(
text: String,
permissions: List<String>,
summaryWhenGranted: String,
summaryWhenNotGranted: String,
modifier: Modifier = Modifier,
fontWeight: FontWeight = FontWeight.Normal
) {
val state = rememberMultiplePermissionsState(permissions = permissions.toList())
PermissionSwitchRow(
text = text,
fontWeight = fontWeight,
summaryWhenGranted = summaryWhenGranted,
summaryWhenNotGranted = summaryWhenNotGranted,
allPermissionsGranted = state.allPermissionsGranted,
onLaunchRequest = state::launchMultiplePermissionRequest,
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun PermissionSwitchRow_Preview() {
PermissionSwitchRow(
text = "Contacts",
allPermissionsGranted = false,
summaryWhenGranted = "Granted",
summaryWhenNotGranted = "Not granted",
onLaunchRequest = {}
)
}
@Composable
fun PermissionSwitchRow(
text: String,
allPermissionsGranted: Boolean,
summaryWhenGranted: String,
summaryWhenNotGranted: String,
modifier: Modifier = Modifier,
fontWeight: FontWeight = FontWeight.Normal,
onLaunchRequest: () -> Unit
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = text,
modifier = Modifier.fillMaxWidth(),
fontWeight = fontWeight,
style = MaterialTheme.typography.body1
)
Text(
text = if (allPermissionsGranted) summaryWhenGranted else summaryWhenNotGranted,
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.body2
)
}
Switch(
checked = allPermissionsGranted,
enabled = !allPermissionsGranted,
onCheckedChange = { checked ->
if (checked) {
onLaunchRequest()
}
}
)
}
}
@Composable
fun PermissionsCard(
keepPermissions: Boolean?,
onKeepPermissionsRequested: () -> Unit,
openTasksAvailable: Boolean?,
tasksOrgAvailable: Boolean?,
jtxAvailable: Boolean?
) {
val context = LocalContext.current
CardWithImage(
title = stringResource(R.string.permissions_title),
message = stringResource(
R.string.permissions_text,
stringResource(R.string.app_name)
),
image = painterResource(R.drawable.intro_permissions),
modifier = Modifier.padding(8.dp)
) {
if (keepPermissions != null) {
PermissionSwitchRow(
text = stringResource(R.string.permissions_autoreset_title),
summaryWhenGranted = stringResource(R.string.permissions_autoreset_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_autoreset_status_off),
allPermissionsGranted = keepPermissions,
onLaunchRequest = onKeepPermissionsRequested,
modifier = Modifier.padding(vertical = 4.dp)
)
}
val allPermissions = mutableListOf<String>()
allPermissions.addAll(PermissionUtils.CONTACT_PERMISSIONS)
allPermissions.addAll(PermissionUtils.CALENDAR_PERMISSIONS)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
allPermissions += Manifest.permission.POST_NOTIFICATIONS
if (openTasksAvailable == true)
allPermissions.addAll(TaskProvider.PERMISSIONS_OPENTASKS)
if (tasksOrgAvailable == true)
allPermissions.addAll(TaskProvider.PERMISSIONS_TASKS_ORG)
if (jtxAvailable == true)
allPermissions.addAll(TaskProvider.PERMISSIONS_JTX)
PermissionSwitchRow(
text = stringResource(R.string.permissions_all_title),
permissions = allPermissions,
summaryWhenGranted = stringResource(R.string.permissions_all_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_all_status_off),
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(vertical = 4.dp)
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
PermissionSwitchRow(
text = stringResource(R.string.permissions_notification_title),
summaryWhenGranted = stringResource(R.string.permissions_notification_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_notification_status_off),
permissions = listOf(Manifest.permission.POST_NOTIFICATIONS),
modifier = Modifier.padding(vertical = 4.dp)
)
PermissionSwitchRow(
text = stringResource(R.string.permissions_calendar_title),
summaryWhenGranted = stringResource(R.string.permissions_calendar_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_calendar_status_off),
permissions = PermissionUtils.CALENDAR_PERMISSIONS.toList(),
modifier = Modifier.padding(vertical = 4.dp)
)
PermissionSwitchRow(
text = stringResource(R.string.permissions_contacts_title),
summaryWhenGranted = stringResource(R.string.permissions_contacts_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_contacts_status_off),
permissions = PermissionUtils.CONTACT_PERMISSIONS.toList(),
modifier = Modifier.padding(vertical = 4.dp)
)
if (jtxAvailable == true)
PermissionSwitchRow(
text = stringResource(R.string.permissions_jtx_title),
summaryWhenGranted = stringResource(R.string.permissions_tasks_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_tasks_status_off),
permissions = TaskProvider.PERMISSIONS_JTX.toList(),
modifier = Modifier.padding(vertical = 4.dp)
)
if (openTasksAvailable == true)
PermissionSwitchRow(
text = stringResource(R.string.permissions_opentasks_title),
summaryWhenGranted = stringResource(R.string.permissions_tasks_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_tasks_status_off),
permissions = TaskProvider.PERMISSIONS_OPENTASKS.toList(),
modifier = Modifier.padding(vertical = 4.dp)
)
if (tasksOrgAvailable == true)
PermissionSwitchRow(
text = stringResource(R.string.permissions_tasksorg_title),
summaryWhenGranted = stringResource(R.string.permissions_tasks_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_tasks_status_off),
permissions = TaskProvider.PERMISSIONS_TASKS_ORG.toList(),
modifier = Modifier.padding(vertical = 4.dp)
)
Text(
text = stringResource(R.string.permissions_app_settings_hint),
style = MaterialTheme.typography.body1,
modifier = Modifier.padding(top = 24.dp)
)
OutlinedButton(
modifier = Modifier.padding(top = 8.dp),
onClick = { PermissionUtils.showAppSettings(context) }
) {
Text(stringResource(R.string.permissions_app_settings).uppercase())
}
}
}

View file

@ -1,347 +0,0 @@
/***************************************************************************************************
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
**************************************************************************************************/
package at.bitfire.davdroid.ui
import android.Manifest
import android.app.Application
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.annotation.MainThread
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Switch
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewmodel.compose.viewModel
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.PackageChangedReceiver
import at.bitfire.davdroid.R
import at.bitfire.davdroid.log.Logger
import at.bitfire.davdroid.ui.widget.CardWithImage
import at.bitfire.davdroid.util.PermissionUtils
import at.bitfire.davdroid.util.PermissionUtils.CALENDAR_PERMISSIONS
import at.bitfire.davdroid.util.PermissionUtils.CONTACT_PERMISSIONS
import at.bitfire.ical4android.TaskProvider
import at.bitfire.ical4android.TaskProvider.ProviderName
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.accompanist.themeadapter.material.MdcTheme
import java.util.logging.Level
class PermissionsFragment: Fragment() {
val model by viewModels<Model>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return ComposeView(requireContext()).apply {
setContent {
MdcTheme {
PermissionsFragmentContent(model)
}
}
}
}
override fun onResume() {
super.onResume()
model.checkPermissions()
}
class Model(app: Application): AndroidViewModel(app) {
val needKeepPermissions = MutableLiveData<Boolean>()
val openTasksAvailable = MutableLiveData<Boolean>()
val tasksOrgAvailable = MutableLiveData<Boolean>()
val jtxAvailable = MutableLiveData<Boolean>()
private val tasksWatcher = object: PackageChangedReceiver(app) {
@MainThread
override fun onReceive(context: Context?, intent: Intent?) {
checkPermissions()
}
}
init {
checkPermissions()
}
override fun onCleared() {
tasksWatcher.close()
}
@MainThread
fun checkPermissions() {
val pm = getApplication<Application>().packageManager
// auto-reset permissions
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
needKeepPermissions.value = pm.isAutoRevokeWhitelisted
}
openTasksAvailable.value = pm.resolveContentProvider(ProviderName.OpenTasks.authority, 0) != null
tasksOrgAvailable.value = pm.resolveContentProvider(ProviderName.TasksOrg.authority, 0) != null
jtxAvailable.value = pm.resolveContentProvider(ProviderName.JtxBoard.authority, 0) != null
}
}
}
@Composable
fun PermissionsFragmentContent(model: PermissionsFragment.Model = viewModel()) {
val context = LocalContext.current
val keepPermissions by model.needKeepPermissions.observeAsState()
val openTasksAvailable by model.openTasksAvailable.observeAsState()
val tasksOrgAvailable by model.tasksOrgAvailable.observeAsState()
val jtxAvailable by model.jtxAvailable.observeAsState()
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
PermissionsCard(
keepPermissions,
onKeepPermissionsRequested = {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val intent = Intent(
Intent.ACTION_AUTO_REVOKE_PERMISSIONS,
Uri.fromParts("package", BuildConfig.APPLICATION_ID, null)
)
try {
context.startActivity(intent)
Toast.makeText(context, R.string.permissions_autoreset_instruction, Toast.LENGTH_LONG).show()
} catch (e: Exception) {
Logger.log.log(Level.WARNING, "Couldn't start Keep Permissions activity", e)
}
}
},
openTasksAvailable,
tasksOrgAvailable,
jtxAvailable
)
}
}
@Composable
@OptIn(ExperimentalPermissionsApi::class)
fun PermissionSwitchRow(
text: String,
permissions: List<String>,
summaryWhenGranted: String,
summaryWhenNotGranted: String,
modifier: Modifier = Modifier,
fontWeight: FontWeight = FontWeight.Normal
) {
val state = rememberMultiplePermissionsState(permissions = permissions.toList())
PermissionSwitchRow(
text = text,
fontWeight = fontWeight,
summaryWhenGranted = summaryWhenGranted,
summaryWhenNotGranted = summaryWhenNotGranted,
allPermissionsGranted = state.allPermissionsGranted,
onLaunchRequest = state::launchMultiplePermissionRequest,
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun PermissionSwitchRow_Preview() {
PermissionSwitchRow(
text = "Contacts",
allPermissionsGranted = false,
summaryWhenGranted = "Granted",
summaryWhenNotGranted = "Not granted",
onLaunchRequest = {}
)
}
@Composable
fun PermissionSwitchRow(
text: String,
allPermissionsGranted: Boolean,
summaryWhenGranted: String,
summaryWhenNotGranted: String,
modifier: Modifier = Modifier,
fontWeight: FontWeight = FontWeight.Normal,
onLaunchRequest: () -> Unit
) {
Row(
modifier = modifier,
verticalAlignment = Alignment.CenterVertically
) {
Column(
modifier = Modifier.weight(1f)
) {
Text(
text = text,
modifier = Modifier.fillMaxWidth(),
fontWeight = fontWeight,
style = MaterialTheme.typography.body1
)
Text(
text = if (allPermissionsGranted) summaryWhenGranted else summaryWhenNotGranted,
modifier = Modifier.fillMaxWidth(),
style = MaterialTheme.typography.body2
)
}
Switch(
checked = allPermissionsGranted,
enabled = !allPermissionsGranted,
onCheckedChange = { checked ->
if (checked) {
onLaunchRequest()
}
}
)
}
}
@Composable
fun PermissionsCard(
keepPermissions: Boolean?,
onKeepPermissionsRequested: () -> Unit,
openTasksAvailable: Boolean?,
tasksOrgAvailable: Boolean?,
jtxAvailable: Boolean?
) {
val context = LocalContext.current
CardWithImage(
title = stringResource(R.string.permissions_title),
message = stringResource(
R.string.permissions_text,
stringResource(R.string.app_name)
),
image = painterResource(R.drawable.intro_permissions),
modifier = Modifier.padding(8.dp)
) {
if (keepPermissions != null) {
PermissionSwitchRow(
text = stringResource(R.string.permissions_autoreset_title),
summaryWhenGranted = stringResource(R.string.permissions_autoreset_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_autoreset_status_off),
allPermissionsGranted = keepPermissions,
onLaunchRequest = onKeepPermissionsRequested,
modifier = Modifier.padding(vertical = 4.dp)
)
}
val allPermissions = mutableListOf<String>()
allPermissions.addAll(CONTACT_PERMISSIONS)
allPermissions.addAll(CALENDAR_PERMISSIONS)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
allPermissions += Manifest.permission.POST_NOTIFICATIONS
if (openTasksAvailable == true)
allPermissions.addAll(TaskProvider.PERMISSIONS_OPENTASKS)
if (tasksOrgAvailable == true)
allPermissions.addAll(TaskProvider.PERMISSIONS_TASKS_ORG)
if (jtxAvailable == true)
allPermissions.addAll(TaskProvider.PERMISSIONS_JTX)
PermissionSwitchRow(
text = stringResource(R.string.permissions_all_title),
permissions = allPermissions,
summaryWhenGranted = stringResource(R.string.permissions_all_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_all_status_off),
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(vertical = 4.dp)
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
PermissionSwitchRow(
text = stringResource(R.string.permissions_notification_title),
summaryWhenGranted = stringResource(R.string.permissions_notification_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_notification_status_off),
permissions = listOf(Manifest.permission.POST_NOTIFICATIONS),
modifier = Modifier.padding(vertical = 4.dp)
)
PermissionSwitchRow(
text = stringResource(R.string.permissions_calendar_title),
summaryWhenGranted = stringResource(R.string.permissions_calendar_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_calendar_status_off),
permissions = CALENDAR_PERMISSIONS.toList(),
modifier = Modifier.padding(vertical = 4.dp)
)
PermissionSwitchRow(
text = stringResource(R.string.permissions_contacts_title),
summaryWhenGranted = stringResource(R.string.permissions_contacts_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_contacts_status_off),
permissions = CONTACT_PERMISSIONS.toList(),
modifier = Modifier.padding(vertical = 4.dp)
)
if (jtxAvailable == true)
PermissionSwitchRow(
text = stringResource(R.string.permissions_jtx_title),
summaryWhenGranted = stringResource(R.string.permissions_tasks_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_tasks_status_off),
permissions = TaskProvider.PERMISSIONS_JTX.toList(),
modifier = Modifier.padding(vertical = 4.dp)
)
if (openTasksAvailable == true)
PermissionSwitchRow(
text = stringResource(R.string.permissions_opentasks_title),
summaryWhenGranted = stringResource(R.string.permissions_tasks_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_tasks_status_off),
permissions = TaskProvider.PERMISSIONS_OPENTASKS.toList(),
modifier = Modifier.padding(vertical = 4.dp)
)
if (tasksOrgAvailable == true)
PermissionSwitchRow(
text = stringResource(R.string.permissions_tasksorg_title),
summaryWhenGranted = stringResource(R.string.permissions_tasks_status_on),
summaryWhenNotGranted = stringResource(R.string.permissions_tasks_status_off),
permissions = TaskProvider.PERMISSIONS_TASKS_ORG.toList(),
modifier = Modifier.padding(vertical = 4.dp)
)
Text(
text = stringResource(R.string.permissions_app_settings_hint),
style = MaterialTheme.typography.body1,
modifier = Modifier.padding(top = 24.dp)
)
OutlinedButton(
modifier = Modifier.padding(top = 8.dp),
onClick = { PermissionUtils.showAppSettings(context) }
) {
Text(stringResource(R.string.permissions_app_settings).uppercase())
}
}
}

View file

@ -4,16 +4,68 @@
package at.bitfire.davdroid.ui
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.annotation.AnyThread
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Checkbox
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.SnackbarDuration
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.unit.dp
import androidx.core.text.HtmlCompat
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.viewmodel.compose.viewModel
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.PackageChangedReceiver
import at.bitfire.davdroid.R
import at.bitfire.davdroid.resource.TaskUtils
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.ui.UiUtils.toAnnotatedString
import at.bitfire.davdroid.ui.widget.CardWithImage
import at.bitfire.davdroid.ui.widget.RadioWithSwitch
import at.bitfire.ical4android.TaskProvider
import com.google.accompanist.themeadapter.material.MdcTheme
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class TasksActivity: AppCompatActivity() {
val model: TasksModel by viewModels()
val model: Model by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -24,4 +76,266 @@ class TasksActivity: AppCompatActivity() {
}
}
}
@HiltViewModel
class Model @Inject constructor(
application: Application,
val settings: SettingsManager
) : AndroidViewModel(application), SettingsManager.OnChangeListener {
companion object {
/**
* Whether this fragment (which asks for OpenTasks installation) shall be shown.
* If this setting is true or null/not set, the notice shall be shown. Only if this
* setting is false, the notice shall not be shown.
*/
const val HINT_OPENTASKS_NOT_INSTALLED = "hint_OpenTasksNotInstalled"
}
val currentProvider = MutableLiveData<TaskProvider.ProviderName>()
val openTasksInstalled = MutableLiveData<Boolean>()
val openTasksRequested = MutableLiveData<Boolean>()
val openTasksSelected = MutableLiveData<Boolean>()
val tasksOrgInstalled = MutableLiveData<Boolean>()
val tasksOrgRequested = MutableLiveData<Boolean>()
val tasksOrgSelected = MutableLiveData<Boolean>()
val jtxInstalled = MutableLiveData<Boolean>()
val jtxRequested = MutableLiveData<Boolean>()
val jtxSelected = MutableLiveData<Boolean>()
private val tasksWatcher = object: PackageChangedReceiver(application) {
override fun onReceive(context: Context?, intent: Intent?) {
checkInstalled()
}
}
val dontShow = MutableLiveData(
settings.getBooleanOrNull(HINT_OPENTASKS_NOT_INSTALLED) == false
)
private val dontShowObserver = Observer<Boolean> { value ->
if (value)
settings.putBoolean(HINT_OPENTASKS_NOT_INSTALLED, false)
else
settings.remove(HINT_OPENTASKS_NOT_INSTALLED)
}
init {
checkInstalled()
settings.addOnChangeListener(this)
dontShow.observeForever(dontShowObserver)
}
override fun onCleared() {
settings.removeOnChangeListener(this)
tasksWatcher.close()
dontShow.removeObserver(dontShowObserver)
}
@AnyThread
fun checkInstalled() {
val taskProvider = TaskUtils.currentProvider(getApplication())
currentProvider.postValue(taskProvider)
val openTasks = isInstalled(TaskProvider.ProviderName.OpenTasks.packageName)
openTasksInstalled.postValue(openTasks)
openTasksRequested.postValue(openTasks)
openTasksSelected.postValue(taskProvider == TaskProvider.ProviderName.OpenTasks)
val tasksOrg = isInstalled(TaskProvider.ProviderName.TasksOrg.packageName)
tasksOrgInstalled.postValue(tasksOrg)
tasksOrgRequested.postValue(tasksOrg)
tasksOrgSelected.postValue(taskProvider == TaskProvider.ProviderName.TasksOrg)
val jtxBoard = isInstalled(TaskProvider.ProviderName.JtxBoard.packageName)
jtxInstalled.postValue(jtxBoard)
jtxRequested.postValue(jtxBoard)
jtxSelected.postValue(taskProvider == TaskProvider.ProviderName.JtxBoard)
}
private fun isInstalled(packageName: String): Boolean =
try {
getApplication<Application>().packageManager.getPackageInfo(packageName, 0)
true
} catch (e: PackageManager.NameNotFoundException) {
false
}
fun selectPreferredProvider(provider: TaskProvider.ProviderName) {
// Changes preferred task app setting, so onSettingsChanged() will be called
TaskUtils.setPreferredProvider(getApplication(), provider)
}
override fun onSettingsChanged() {
checkInstalled()
}
}
}
@OptIn(ExperimentalTextApi::class)
@Composable
fun TasksCard(
modifier: Modifier = Modifier,
model: TasksActivity.Model = viewModel()
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
val jtxInstalled by model.jtxInstalled.observeAsState(initial = false)
val jtxSelected by model.jtxSelected.observeAsState(initial = false)
val jtxRequested by model.jtxRequested.observeAsState(initial = false)
val tasksOrgInstalled by model.tasksOrgInstalled.observeAsState(initial = false)
val tasksOrgSelected by model.tasksOrgSelected.observeAsState(initial = false)
val tasksOrgRequested by model.tasksOrgRequested.observeAsState(initial = false)
val openTasksInstalled by model.openTasksInstalled.observeAsState(initial = false)
val openTasksSelected by model.openTasksSelected.observeAsState(initial = false)
val openTasksRequested by model.openTasksRequested.observeAsState(initial = false)
val dontShow by model.dontShow.observeAsState(initial = false)
fun installApp(packageName: String) {
val uri = Uri.parse("market://details?id=$packageName&referrer=" +
Uri.encode("utm_source=" + BuildConfig.APPLICATION_ID))
val intent = Intent(Intent.ACTION_VIEW, uri)
if (intent.resolveActivity(context.packageManager) != null)
context.startActivity(intent)
else
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = context.getString(R.string.intro_tasks_no_app_store),
duration = SnackbarDuration.Long
)
}
}
fun onProviderSelected(provider: TaskProvider.ProviderName) {
if (model.currentProvider.value != provider)
model.selectPreferredProvider(provider)
}
Scaffold(
modifier = modifier,
snackbarHost = { SnackbarHost(snackbarHostState) }
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxHeight()
.padding(paddingValues)
.verticalScroll(rememberScrollState())
) {
CardWithImage(
image = painterResource(R.drawable.intro_tasks),
imageAlignment = BiasAlignment(0f, .1f),
title = stringResource(R.string.intro_tasks_title),
message = stringResource(R.string.intro_tasks_text1),
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(top = 16.dp)
) {
RadioWithSwitch(
title = stringResource(R.string.intro_tasks_jtx),
summary = {
Text(stringResource(R.string.intro_tasks_jtx_info))
},
isSelected = jtxSelected,
isToggled = jtxRequested,
enabled = jtxInstalled,
onSelected = { onProviderSelected(TaskProvider.ProviderName.JtxBoard) },
onToggled = { toggled ->
if (toggled) installApp(TaskProvider.ProviderName.JtxBoard.packageName)
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
)
RadioWithSwitch(
title = stringResource(R.string.intro_tasks_tasks_org),
summary = {
val summary = HtmlCompat.fromHtml(
stringResource(R.string.intro_tasks_tasks_org_info),
HtmlCompat.FROM_HTML_MODE_COMPACT
).toAnnotatedString()
ClickableText(
text = summary,
onClick = { index ->
// Get the tapped position, and check if there's any link
summary.getUrlAnnotations(index, index).firstOrNull()?.item?.url?.let { url ->
UiUtils.launchUri(context, Uri.parse(url))
}
}
)
},
isSelected = tasksOrgSelected,
isToggled = tasksOrgRequested,
enabled = tasksOrgInstalled,
onSelected = { onProviderSelected(TaskProvider.ProviderName.TasksOrg) },
onToggled = { toggled ->
if (toggled) installApp(TaskProvider.ProviderName.TasksOrg.packageName)
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
)
RadioWithSwitch(
title = stringResource(R.string.intro_tasks_opentasks),
summary = {
Text(stringResource(R.string.intro_tasks_opentasks_info))
},
isSelected = openTasksSelected,
isToggled = openTasksRequested,
enabled = openTasksInstalled,
onSelected = { onProviderSelected(TaskProvider.ProviderName.OpenTasks) },
onToggled = { toggled ->
if (toggled) installApp(TaskProvider.ProviderName.OpenTasks.packageName)
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
) {
Checkbox(
checked = dontShow,
onCheckedChange = { model.dontShow.value = it }
)
Text(
text = stringResource(R.string.intro_tasks_dont_show),
modifier = Modifier
.fillMaxWidth()
.clickable { model.dontShow.value = !dontShow }
)
}
}
Text(
text = stringResource(
R.string.intro_leave_unchecked,
stringResource(R.string.app_settings_reset_hints)
),
style = MaterialTheme.typography.caption,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 8.dp)
)
}
}
}

View file

@ -1,317 +0,0 @@
/***************************************************************************************************
* Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details.
**************************************************************************************************/
package at.bitfire.davdroid.ui
import android.app.Application
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.annotation.AnyThread
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Checkbox
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.SnackbarDuration
import androidx.compose.material.SnackbarHost
import androidx.compose.material.SnackbarHostState
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.ExperimentalTextApi
import androidx.compose.ui.unit.dp
import androidx.core.text.HtmlCompat
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.viewmodel.compose.viewModel
import at.bitfire.davdroid.BuildConfig
import at.bitfire.davdroid.PackageChangedReceiver
import at.bitfire.davdroid.R
import at.bitfire.davdroid.resource.TaskUtils
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.ui.UiUtils.toAnnotatedString
import at.bitfire.davdroid.ui.widget.CardWithImage
import at.bitfire.davdroid.ui.widget.RadioWithSwitch
import at.bitfire.ical4android.TaskProvider.ProviderName
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class TasksModel @Inject constructor(
application: Application,
val settings: SettingsManager
) : AndroidViewModel(application), SettingsManager.OnChangeListener {
companion object {
/**
* Whether this fragment (which asks for OpenTasks installation) shall be shown.
* If this setting is true or null/not set, the notice shall be shown. Only if this
* setting is false, the notice shall not be shown.
*/
const val HINT_OPENTASKS_NOT_INSTALLED = "hint_OpenTasksNotInstalled"
}
val currentProvider = MutableLiveData<ProviderName>()
val openTasksInstalled = MutableLiveData<Boolean>()
val openTasksRequested = MutableLiveData<Boolean>()
val openTasksSelected = MutableLiveData<Boolean>()
val tasksOrgInstalled = MutableLiveData<Boolean>()
val tasksOrgRequested = MutableLiveData<Boolean>()
val tasksOrgSelected = MutableLiveData<Boolean>()
val jtxInstalled = MutableLiveData<Boolean>()
val jtxRequested = MutableLiveData<Boolean>()
val jtxSelected = MutableLiveData<Boolean>()
private val tasksWatcher = object: PackageChangedReceiver(application) {
override fun onReceive(context: Context?, intent: Intent?) {
checkInstalled()
}
}
val dontShow = MutableLiveData(
settings.getBooleanOrNull(HINT_OPENTASKS_NOT_INSTALLED) == false
)
private val dontShowObserver = Observer<Boolean> { value ->
if (value)
settings.putBoolean(HINT_OPENTASKS_NOT_INSTALLED, false)
else
settings.remove(HINT_OPENTASKS_NOT_INSTALLED)
}
init {
checkInstalled()
settings.addOnChangeListener(this)
dontShow.observeForever(dontShowObserver)
}
override fun onCleared() {
settings.removeOnChangeListener(this)
tasksWatcher.close()
dontShow.removeObserver(dontShowObserver)
}
@AnyThread
fun checkInstalled() {
val taskProvider = TaskUtils.currentProvider(getApplication())
currentProvider.postValue(taskProvider)
val openTasks = isInstalled(ProviderName.OpenTasks.packageName)
openTasksInstalled.postValue(openTasks)
openTasksRequested.postValue(openTasks)
openTasksSelected.postValue(taskProvider == ProviderName.OpenTasks)
val tasksOrg = isInstalled(ProviderName.TasksOrg.packageName)
tasksOrgInstalled.postValue(tasksOrg)
tasksOrgRequested.postValue(tasksOrg)
tasksOrgSelected.postValue(taskProvider == ProviderName.TasksOrg)
val jtxBoard = isInstalled(ProviderName.JtxBoard.packageName)
jtxInstalled.postValue(jtxBoard)
jtxRequested.postValue(jtxBoard)
jtxSelected.postValue(taskProvider == ProviderName.JtxBoard)
}
private fun isInstalled(packageName: String): Boolean =
try {
getApplication<Application>().packageManager.getPackageInfo(packageName, 0)
true
} catch (e: PackageManager.NameNotFoundException) {
false
}
fun selectPreferredProvider(provider: ProviderName) {
// Changes preferred task app setting, so onSettingsChanged() will be called
TaskUtils.setPreferredProvider(getApplication(), provider)
}
override fun onSettingsChanged() {
checkInstalled()
}
}
@OptIn(ExperimentalTextApi::class)
@Composable
fun TasksCard(
modifier: Modifier = Modifier,
model: TasksModel = viewModel()
) {
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
val jtxInstalled by model.jtxInstalled.observeAsState(initial = false)
val jtxSelected by model.jtxSelected.observeAsState(initial = false)
val jtxRequested by model.jtxRequested.observeAsState(initial = false)
val tasksOrgInstalled by model.tasksOrgInstalled.observeAsState(initial = false)
val tasksOrgSelected by model.tasksOrgSelected.observeAsState(initial = false)
val tasksOrgRequested by model.tasksOrgRequested.observeAsState(initial = false)
val openTasksInstalled by model.openTasksInstalled.observeAsState(initial = false)
val openTasksSelected by model.openTasksSelected.observeAsState(initial = false)
val openTasksRequested by model.openTasksRequested.observeAsState(initial = false)
val dontShow by model.dontShow.observeAsState(initial = false)
fun installApp(packageName: String) {
val uri = Uri.parse("market://details?id=$packageName&referrer=" +
Uri.encode("utm_source=" + BuildConfig.APPLICATION_ID))
val intent = Intent(Intent.ACTION_VIEW, uri)
if (intent.resolveActivity(context.packageManager) != null)
context.startActivity(intent)
else
coroutineScope.launch {
snackbarHostState.showSnackbar(
message = context.getString(R.string.intro_tasks_no_app_store),
duration = SnackbarDuration.Long
)
}
}
fun onProviderSelected(provider: ProviderName) {
if (model.currentProvider.value != provider)
model.selectPreferredProvider(provider)
}
Scaffold(
modifier = modifier,
snackbarHost = { SnackbarHost(snackbarHostState) }
) { paddingValues ->
Column(
modifier = Modifier
.fillMaxHeight()
.padding(paddingValues)
.verticalScroll(rememberScrollState())
) {
CardWithImage(
image = painterResource(R.drawable.intro_tasks),
imageAlignment = BiasAlignment(0f, .1f),
title = stringResource(R.string.intro_tasks_title),
message = stringResource(R.string.intro_tasks_text1),
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(top = 16.dp)
) {
RadioWithSwitch(
title = stringResource(R.string.intro_tasks_jtx),
summary = {
Text(stringResource(R.string.intro_tasks_jtx_info))
},
isSelected = jtxSelected,
isToggled = jtxRequested,
enabled = jtxInstalled,
onSelected = { onProviderSelected(ProviderName.JtxBoard) },
onToggled = { toggled ->
if (toggled) installApp(ProviderName.JtxBoard.packageName)
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
)
RadioWithSwitch(
title = stringResource(R.string.intro_tasks_tasks_org),
summary = {
val summary = HtmlCompat.fromHtml(
stringResource(R.string.intro_tasks_tasks_org_info),
HtmlCompat.FROM_HTML_MODE_COMPACT
).toAnnotatedString()
ClickableText(
text = summary,
onClick = { index ->
// Get the tapped position, and check if there's any link
summary.getUrlAnnotations(index, index).firstOrNull()?.item?.url?.let { url ->
UiUtils.launchUri(context, Uri.parse(url))
}
}
)
},
isSelected = tasksOrgSelected,
isToggled = tasksOrgRequested,
enabled = tasksOrgInstalled,
onSelected = { onProviderSelected(ProviderName.TasksOrg) },
onToggled = { toggled ->
if (toggled) installApp(ProviderName.TasksOrg.packageName)
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
)
RadioWithSwitch(
title = stringResource(R.string.intro_tasks_opentasks),
summary = {
Text(stringResource(R.string.intro_tasks_opentasks_info))
},
isSelected = openTasksSelected,
isToggled = openTasksRequested,
enabled = openTasksInstalled,
onSelected = { onProviderSelected(ProviderName.OpenTasks) },
onToggled = { toggled ->
if (toggled) installApp(ProviderName.OpenTasks.packageName)
},
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp)
) {
Checkbox(
checked = dontShow,
onCheckedChange = { model.dontShow.value = it }
)
Text(
text = stringResource(R.string.intro_tasks_dont_show),
modifier = Modifier
.fillMaxWidth()
.clickable { model.dontShow.value = !dontShow }
)
}
}
Text(
text = stringResource(
R.string.intro_leave_unchecked,
stringResource(R.string.app_settings_reset_hints)
),
style = MaterialTheme.typography.caption,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 12.dp, vertical = 8.dp)
)
}
}
}

View file

@ -6,7 +6,7 @@ package at.bitfire.davdroid.ui.intro
import android.app.Application
import androidx.compose.runtime.Composable
import at.bitfire.davdroid.ui.PermissionsFragmentContent
import at.bitfire.davdroid.ui.PermissionsContent
import at.bitfire.davdroid.util.PermissionUtils
import at.bitfire.davdroid.util.PermissionUtils.CALENDAR_PERMISSIONS
import at.bitfire.davdroid.util.PermissionUtils.CONTACT_PERMISSIONS
@ -28,7 +28,7 @@ class PermissionsIntroPage: IntroPage {
@Composable
override fun ComposePage() {
PermissionsFragmentContent()
PermissionsContent()
}
}

View file

@ -9,8 +9,8 @@ import androidx.compose.runtime.Composable
import androidx.lifecycle.viewmodel.compose.viewModel
import at.bitfire.davdroid.resource.TaskUtils
import at.bitfire.davdroid.settings.SettingsManager
import at.bitfire.davdroid.ui.TasksActivity
import at.bitfire.davdroid.ui.TasksCard
import at.bitfire.davdroid.ui.TasksModel
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
@ -27,7 +27,7 @@ class TasksIntroPage : IntroPage {
override fun getShowPolicy(application: Application): IntroPage.ShowPolicy {
val settingsManager = EntryPointAccessors.fromApplication(application, TasksIntroPageEntryPoint::class.java).settingsManager()
return if (!TaskUtils.isAvailable(application) && settingsManager.getBooleanOrNull(TasksModel.HINT_OPENTASKS_NOT_INSTALLED) != false)
return if (!TaskUtils.isAvailable(application) && settingsManager.getBooleanOrNull(TasksActivity.Model.HINT_OPENTASKS_NOT_INSTALLED) != false)
IntroPage.ShowPolicy.SHOW_ALWAYS
else
IntroPage.ShowPolicy.DONT_SHOW
@ -35,7 +35,7 @@ class TasksIntroPage : IntroPage {
@Composable
override fun ComposePage() {
TasksCard(model = viewModel<TasksModel>())
TasksCard(model = viewModel<TasksActivity.Model>())
}
}