mirror of https://github.com/tasks/tasks
Prompt for sync at install time
parent
267ebfe86e
commit
0dea530c50
@ -0,0 +1,90 @@
|
||||
package org.tasks.caldav
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import org.tasks.R
|
||||
import org.tasks.analytics.Constants
|
||||
import org.tasks.data.UUIDHelper
|
||||
import org.tasks.data.entity.CaldavAccount
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LocalAccountSettingsActivity : BaseCaldavAccountSettingsActivity(), Toolbar.OnMenuItemClickListener {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding.userLayout.visibility = View.GONE
|
||||
binding.passwordLayout.visibility = View.GONE
|
||||
binding.urlLayout.visibility = View.GONE
|
||||
binding.serverSelector.visibility = View.GONE
|
||||
}
|
||||
|
||||
override fun hasChanges() = newName != caldavAccount!!.name
|
||||
|
||||
override fun save() = lifecycleScope.launch {
|
||||
if (newName.isBlank()) {
|
||||
binding.nameLayout.error = getString(R.string.name_cannot_be_empty)
|
||||
return@launch
|
||||
}
|
||||
updateAccount()
|
||||
}
|
||||
|
||||
private suspend fun addAccount() {
|
||||
caldavDao.insert(
|
||||
CaldavAccount(
|
||||
name = newName,
|
||||
uuid = UUIDHelper.newUUID(),
|
||||
)
|
||||
)
|
||||
firebase.logEvent(
|
||||
R.string.event_sync_add_account,
|
||||
R.string.param_type to Constants.SYNC_TYPE_LOCAL
|
||||
)
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
||||
override suspend fun updateAccount() {
|
||||
caldavAccount!!.name = newName
|
||||
caldavDao.update(caldavAccount!!)
|
||||
setResult(Activity.RESULT_OK)
|
||||
finish()
|
||||
}
|
||||
|
||||
override suspend fun addAccount(url: String, username: String, password: String) {
|
||||
addAccount()
|
||||
}
|
||||
|
||||
override suspend fun updateAccount(url: String, username: String, password: String) {
|
||||
updateAccount()
|
||||
}
|
||||
|
||||
override suspend fun removeAccountPrompt() {
|
||||
val countTasks = caldavAccount?.uuid?.let { caldavDao.countTasks(it) } ?: 0
|
||||
val countString = resources.getQuantityString(R.plurals.task_count, countTasks, countTasks)
|
||||
dialogBuilder
|
||||
.newDialog()
|
||||
.setTitle(
|
||||
R.string.delete_tag_confirmation,
|
||||
caldavAccount?.name?.takeIf { it.isNotBlank() } ?: getString(R.string.local_lists)
|
||||
)
|
||||
.apply {
|
||||
if (countTasks > 0) {
|
||||
setMessage(R.string.delete_tasks_warning, countString)
|
||||
} else {
|
||||
setMessage(R.string.logout_warning)
|
||||
}
|
||||
}
|
||||
.setPositiveButton(R.string.delete) { _, _ -> lifecycleScope.launch { removeAccount() } }
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
override val newPassword: String? = null
|
||||
|
||||
override val helpUrl = R.string.url_caldav
|
||||
}
|
||||
@ -0,0 +1,348 @@
|
||||
package org.tasks.compose.accounts
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.outlined.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.outlined.HelpOutline
|
||||
import androidx.compose.material.icons.outlined.Backup
|
||||
import androidx.compose.material.icons.outlined.CloudOff
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CardDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedCard
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TopAppBar
|
||||
import androidx.compose.material3.TopAppBarDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.PreviewFontScale
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewScreenSizes
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.tasks.R
|
||||
import org.tasks.sync.AddAccountDialog.Platform
|
||||
import org.tasks.themes.TasksTheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun AddAccountScreen(
|
||||
gettingStarted: Boolean,
|
||||
hasTasksAccount: Boolean,
|
||||
hasPro: Boolean,
|
||||
onBack: () -> Unit,
|
||||
signIn: (Platform) -> Unit,
|
||||
openUrl: (Platform) -> Unit,
|
||||
onImportBackup: () -> Unit,
|
||||
) {
|
||||
BackHandler {
|
||||
if (!gettingStarted) {
|
||||
onBack()
|
||||
}
|
||||
}
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
colors = TopAppBarDefaults.topAppBarColors(),
|
||||
navigationIcon = {
|
||||
if (!gettingStarted) {
|
||||
IconButton(onClick = onBack) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Outlined.ArrowBack,
|
||||
contentDescription = stringResource(R.string.back),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
title = {
|
||||
Text(
|
||||
text = if (gettingStarted) {
|
||||
stringResource(R.string.sign_in)
|
||||
} else {
|
||||
stringResource(R.string.add_account)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
.padding(vertical = 16.dp)
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState()),
|
||||
) {
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
maxItemsInEachRow = 5
|
||||
) {
|
||||
if (gettingStarted) {
|
||||
ActionCard(
|
||||
title = R.string.backup_BAc_import,
|
||||
icon = Icons.Outlined.Backup,
|
||||
onClick = onImportBackup,
|
||||
isOutlined = true
|
||||
)
|
||||
|
||||
ActionCard(
|
||||
title = R.string.continue_without_sync,
|
||||
icon = Icons.Outlined.CloudOff,
|
||||
onClick = { signIn(Platform.LOCAL) },
|
||||
isOutlined = true
|
||||
)
|
||||
}
|
||||
if (!hasTasksAccount) {
|
||||
AccountTypeCard(
|
||||
title = R.string.tasks_org,
|
||||
cost = R.string.cost_more_money,
|
||||
icon = R.drawable.ic_round_icon,
|
||||
onClick = { signIn(Platform.TASKS_ORG) }
|
||||
)
|
||||
}
|
||||
|
||||
AccountTypeCard(
|
||||
title = R.string.microsoft,
|
||||
cost = if (hasPro) null else R.string.cost_free,
|
||||
icon = R.drawable.ic_microsoft_tasks,
|
||||
onClick = { signIn(Platform.MICROSOFT) }
|
||||
)
|
||||
|
||||
AccountTypeCard(
|
||||
title = R.string.gtasks_GPr_header,
|
||||
cost = if (hasPro) null else R.string.cost_free,
|
||||
icon = R.drawable.ic_google,
|
||||
onClick = { signIn(Platform.GOOGLE_TASKS) }
|
||||
)
|
||||
|
||||
AccountTypeCard(
|
||||
title = R.string.davx5,
|
||||
cost = if (hasPro) null else R.string.cost_money,
|
||||
icon = R.drawable.ic_davx5_icon_green_bg,
|
||||
onClick = { openUrl(Platform.DAVX5) }
|
||||
)
|
||||
|
||||
AccountTypeCard(
|
||||
title = R.string.caldav,
|
||||
cost = if (hasPro) null else R.string.cost_money,
|
||||
icon = R.drawable.ic_webdav_logo,
|
||||
tint = MaterialTheme.colorScheme.onSurface.copy(alpha = .8f),
|
||||
onClick = { signIn(Platform.CALDAV) }
|
||||
)
|
||||
|
||||
AccountTypeCard(
|
||||
title = R.string.etesync,
|
||||
cost = if (hasPro) null else R.string.cost_money,
|
||||
icon = R.drawable.ic_etesync,
|
||||
onClick = { signIn(Platform.ETESYNC) }
|
||||
)
|
||||
|
||||
AccountTypeCard(
|
||||
title = R.string.decsync,
|
||||
cost = if (hasPro) null else R.string.cost_money,
|
||||
icon = R.drawable.ic_decsync,
|
||||
onClick = { openUrl(Platform.DECSYNC_CC) }
|
||||
)
|
||||
|
||||
if (gettingStarted) {
|
||||
ActionCard(
|
||||
title = R.string.help_me_choose,
|
||||
icon = Icons.AutoMirrored.Outlined.HelpOutline,
|
||||
onClick = { openUrl(Platform.LOCAL) },
|
||||
isOutlined = true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AccountTypeCard(
|
||||
@StringRes title: Int,
|
||||
@StringRes cost: Int? = null,
|
||||
@DrawableRes icon: Int,
|
||||
tint: Color? = null,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.width(108.dp),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||
onClick = onClick
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(12.dp).fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = icon),
|
||||
contentDescription = stringResource(id = title),
|
||||
tint = tint ?: Color.Unspecified,
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
append(stringResource(id = title))
|
||||
cost?.let {
|
||||
append("\n")
|
||||
withStyle(
|
||||
style = SpanStyle(
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontSize = MaterialTheme.typography.labelSmall.fontSize
|
||||
)
|
||||
) {
|
||||
append(stringResource(id = it))
|
||||
}
|
||||
}
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
minLines = 3,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ActionCard(
|
||||
@StringRes title: Int,
|
||||
icon: ImageVector,
|
||||
onClick: () -> Unit,
|
||||
isOutlined: Boolean = false
|
||||
) {
|
||||
if (isOutlined) {
|
||||
OutlinedCard(
|
||||
modifier = Modifier
|
||||
.width(108.dp),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
onClick = onClick
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = stringResource(id = title),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = title),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
minLines = 3,
|
||||
maxLines = 3,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.width(150.dp),
|
||||
shape = MaterialTheme.shapes.medium,
|
||||
elevation = CardDefaults.cardElevation(defaultElevation = 2.dp),
|
||||
onClick = onClick
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = stringResource(id = title),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.size(40.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Text(
|
||||
text = stringResource(id = title),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
textAlign = TextAlign.Center,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@PreviewScreenSizes
|
||||
@PreviewFontScale
|
||||
@Composable
|
||||
fun GettingStartedPreview() {
|
||||
TasksTheme {
|
||||
AddAccountScreen(
|
||||
gettingStarted = true,
|
||||
hasTasksAccount = false,
|
||||
hasPro = false,
|
||||
onBack = {},
|
||||
signIn = {},
|
||||
openUrl = {},
|
||||
onImportBackup = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
fun AddAccountPreview() {
|
||||
TasksTheme {
|
||||
AddAccountScreen(
|
||||
gettingStarted = false,
|
||||
hasTasksAccount = false,
|
||||
hasPro = false,
|
||||
onBack = {},
|
||||
signIn = {},
|
||||
openUrl = {},
|
||||
onImportBackup = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
package org.tasks.compose.accounts
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.tasks.R
|
||||
import org.tasks.data.dao.CaldavDao
|
||||
import org.tasks.data.newLocalAccount
|
||||
import org.tasks.extensions.Context.openUri
|
||||
import org.tasks.sync.AddAccountDialog
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class AddAccountViewModel @Inject constructor(
|
||||
private val caldavDao: CaldavDao,
|
||||
) : ViewModel() {
|
||||
fun createLocalAccount() = viewModelScope.launch {
|
||||
caldavDao.newLocalAccount()
|
||||
}
|
||||
|
||||
fun openUrl(context: Context, platform: AddAccountDialog.Platform) {
|
||||
val url = when (platform) {
|
||||
AddAccountDialog.Platform.DAVX5 -> R.string.url_davx5
|
||||
AddAccountDialog.Platform.DECSYNC_CC -> R.string.url_decsync
|
||||
AddAccountDialog.Platform.LOCAL -> R.string.help_url_sync
|
||||
else -> return
|
||||
}
|
||||
context.openUri(context.getString(url))
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue