From 660e831725460721c6701561a409325a5e066ff6 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Wed, 14 Jan 2026 20:22:04 -0600 Subject: [PATCH] New WelcomeScreen with consent disclosure --- .../java/org/tasks/analytics/Firebase.kt | 3 + .../java/org/tasks/analytics/Firebase.kt | 9 + .../todoroo/astrid/activity/MainActivity.kt | 110 ++++++-- .../java/org/tasks/auth/SignInActivity.kt | 57 ++--- .../java/org/tasks/auth/SignInViewModel.kt | 18 +- .../org/tasks/caldav/CaldavClientProvider.kt | 14 +- .../org/tasks/caldav/CaldavSynchronizer.kt | 2 +- .../java/org/tasks/caldav/TasksBasicAuth.kt | 7 +- .../java/org/tasks/compose/Destinations.kt | 5 +- .../java/org/tasks/compose/SignInDialog.kt | 48 ---- .../java/org/tasks/compose/TosUpdateDialog.kt | 75 ++++++ .../java/org/tasks/compose/WelcomeScreen.kt | 237 ++++++++++++++++++ .../compose/accounts/AddAccountScreen.kt | 159 +----------- app/src/main/java/org/tasks/jobs/SyncWork.kt | 13 +- .../preferences/fragments/HelpAndFeedback.kt | 1 + .../preferences/fragments/TasksAccount.kt | 29 +++ app/src/main/res/values-ar/strings.xml | 2 - app/src/main/res/values-ast/strings.xml | 1 - app/src/main/res/values-bg-rBG/strings.xml | 2 - app/src/main/res/values-ca/strings.xml | 2 - app/src/main/res/values-cs/strings.xml | 2 - app/src/main/res/values-da/strings.xml | 2 - app/src/main/res/values-de/strings.xml | 2 - app/src/main/res/values-el/strings.xml | 2 - app/src/main/res/values-eo/strings.xml | 2 - app/src/main/res/values-es/strings.xml | 2 - app/src/main/res/values-et/strings.xml | 2 - app/src/main/res/values-eu/strings.xml | 1 - app/src/main/res/values-fi/strings.xml | 2 - app/src/main/res/values-fr/strings.xml | 2 - app/src/main/res/values-gl/strings.xml | 2 - app/src/main/res/values-he/strings.xml | 2 - app/src/main/res/values-hr/strings.xml | 2 - app/src/main/res/values-hu/strings.xml | 2 - app/src/main/res/values-it/strings.xml | 2 - app/src/main/res/values-ja/strings.xml | 2 - app/src/main/res/values-ko/strings.xml | 2 - app/src/main/res/values-lt/strings.xml | 1 - app/src/main/res/values-nb/strings.xml | 1 - app/src/main/res/values-nl/strings.xml | 2 - app/src/main/res/values-pl/strings.xml | 2 - app/src/main/res/values-pt-rBR/strings.xml | 2 - app/src/main/res/values-pt/strings.xml | 2 - app/src/main/res/values-ro/strings.xml | 2 - app/src/main/res/values-ru/strings.xml | 2 - app/src/main/res/values-sk/strings.xml | 1 - app/src/main/res/values-sr/strings.xml | 2 - app/src/main/res/values-sv/strings.xml | 2 - app/src/main/res/values-ta/strings.xml | 2 - app/src/main/res/values-tr/strings.xml | 2 - app/src/main/res/values-uk/strings.xml | 2 - app/src/main/res/values-vi/strings.xml | 1 - app/src/main/res/values-zh-rCN/strings.xml | 2 - app/src/main/res/values-zh-rTW/strings.xml | 1 - app/src/main/res/values/keys.xml | 6 + app/src/main/res/values/strings.xml | 15 +- app/src/main/res/xml/help_and_feedback.xml | 12 +- .../org/tasks/data/entity/CaldavAccount.kt | 5 + .../org/tasks/preferences/TasksPreferences.kt | 2 + 59 files changed, 549 insertions(+), 347 deletions(-) create mode 100644 app/src/main/java/org/tasks/compose/TosUpdateDialog.kt create mode 100644 app/src/main/java/org/tasks/compose/WelcomeScreen.kt diff --git a/app/src/generic/java/org/tasks/analytics/Firebase.kt b/app/src/generic/java/org/tasks/analytics/Firebase.kt index 5faf48d60..e9f6932ce 100644 --- a/app/src/generic/java/org/tasks/analytics/Firebase.kt +++ b/app/src/generic/java/org/tasks/analytics/Firebase.kt @@ -37,4 +37,7 @@ class Firebase @Inject constructor( private fun days(default: Long): Long = TimeUnit.DAYS.toMillis(default) + + fun getTosVersion(): Int = + context.resources.getInteger(R.integer.default_tos_version) } diff --git a/app/src/googleplay/java/org/tasks/analytics/Firebase.kt b/app/src/googleplay/java/org/tasks/analytics/Firebase.kt index badfc66ac..470ef1e0e 100644 --- a/app/src/googleplay/java/org/tasks/analytics/Firebase.kt +++ b/app/src/googleplay/java/org/tasks/analytics/Firebase.kt @@ -109,4 +109,13 @@ class Firebase @Inject constructor( private fun days(key: String, default: Long): Long = TimeUnit.DAYS.toMillis(remoteConfig?.getLong(key) ?: default) + + fun getTosVersion(): Int { + val default = context.resources.getInteger(R.integer.default_tos_version) + return remoteConfig + ?.getLong(context.getString(R.string.remote_config_tos_version)) + ?.toInt() + ?.takeIf { it >= default } + ?: default + } } \ No newline at end of file diff --git a/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt b/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt index 50fd828da..b51bf15fd 100644 --- a/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt +++ b/app/src/main/java/com/todoroo/astrid/activity/MainActivity.kt @@ -28,6 +28,7 @@ import androidx.compose.material3.adaptive.layout.calculatePaneScaffoldDirective import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.unit.dp @@ -39,7 +40,6 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import androidx.navigation.toRoute import com.todoroo.astrid.adapter.SubheaderClickHandler import com.todoroo.astrid.dao.TaskDao import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity @@ -55,6 +55,9 @@ import org.tasks.billing.Inventory import org.tasks.caldav.CaldavAccountSettingsActivity import org.tasks.compose.AddAccountDestination import org.tasks.compose.HomeDestination +import org.tasks.compose.TosUpdateDialog +import org.tasks.compose.WelcomeDestination +import org.tasks.compose.WelcomeScreen import org.tasks.compose.accounts.AddAccountScreen import org.tasks.compose.accounts.AddAccountViewModel import org.tasks.compose.home.HomeScreen @@ -67,6 +70,7 @@ import org.tasks.dialogs.ImportTasksDialog import org.tasks.dialogs.NewFilterDialog import org.tasks.etebase.EtebaseAccountSettingsActivity import org.tasks.extensions.Context.nightMode +import org.tasks.extensions.Context.openUri import org.tasks.extensions.Context.toast import org.tasks.extensions.broughtToFront import org.tasks.extensions.flagsToString @@ -76,6 +80,7 @@ import org.tasks.filters.Filter import org.tasks.jobs.WorkManager import org.tasks.preferences.DefaultFilterProvider import org.tasks.preferences.Preferences +import org.tasks.preferences.TasksPreferences import org.tasks.preferences.fragments.FRAG_TAG_IMPORT_TASKS import org.tasks.sync.AddAccountDialog import org.tasks.sync.SyncAdapters @@ -103,6 +108,7 @@ class MainActivity : AppCompatActivity() { @Inject lateinit var caldavDao: CaldavDao @Inject lateinit var syncAdapters: SyncAdapters @Inject lateinit var workManager: WorkManager + @Inject lateinit var tasksPreferences: TasksPreferences private val viewModel: MainActivityViewModel by viewModels() private var currentNightMode = 0 @@ -140,24 +146,80 @@ class MainActivity : AppCompatActivity() { .accountExists .collectAsStateWithLifecycle(null) .value + val currentTosVersion = firebase.getTosVersion() + val acceptedTosVersion by tasksPreferences + .flow(TasksPreferences.acceptedTosVersion, 0) + .collectAsStateWithLifecycle(0) + val needsTosAcceptance = acceptedTosVersion < currentTosVersion + suspend fun setAcceptedTosVersion(version: Int) { + tasksPreferences.set(TasksPreferences.acceptedTosVersion, version) + } + LaunchedEffect(hasAccount) { Timber.d("hasAccount=$hasAccount") - if (hasAccount == false) { - navController.navigate(AddAccountDestination(showImport = true)) + when (hasAccount) { + false -> navController.navigate(WelcomeDestination) { + popUpTo(0) { inclusive = true } + } + true -> navController.navigate(HomeDestination) { + popUpTo(0) { inclusive = true } + } + else -> {} } isReady = hasAccount != null } + NavHost( navController = navController, startDestination = HomeDestination, ) { - composable { - val route = it.toRoute() - LaunchedEffect(hasAccount) { - if (route.showImport && hasAccount == true) { - navController.popBackStack() + composable { + val addAccountViewModel: AddAccountViewModel = hiltViewModel() + val importBackupLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == RESULT_OK) { + val uri = result.data?.data ?: return@rememberLauncherForActivityResult + ImportTasksDialog.newImportTasksDialog(uri) + .show(supportFragmentManager, FRAG_TAG_IMPORT_TASKS) + } } - } + WelcomeScreen( + onBack = { finish() }, + onSignIn = { + lifecycleScope.launch { + firebase.logEvent(R.string.event_accept_tos) + setAcceptedTosVersion(currentTosVersion) + navController.navigate(AddAccountDestination) + } + }, + onContinueWithoutSync = { + lifecycleScope.launch { + firebase.logEvent(R.string.event_accept_tos) + firebase.logEvent(R.string.event_onboarding_sync, R.string.param_selection to "local") + setAcceptedTosVersion(currentTosVersion) + addAccountViewModel.createLocalAccount() + } + }, + onImportBackup = { + lifecycleScope.launch { + firebase.logEvent(R.string.event_accept_tos) + firebase.logEvent( + R.string.event_onboarding_sync, + R.string.param_selection to "import_backup" + ) + setAcceptedTosVersion(currentTosVersion) + importBackupLauncher.launch( + FileHelper.newFilePickerIntent( + this@MainActivity, + preferences.backupDirectory + ), + ) + } + }, + openLegalUrl = { url -> openUri(url) } + ) + } + composable { val addAccountViewModel: AddAccountViewModel = hiltViewModel() val microsoftVM: MicrosoftSignInViewModel = hiltViewModel() val syncLauncher = @@ -171,16 +233,7 @@ class MainActivity : AppCompatActivity() { ?.let { toast(it) } } } - val importBackupLauncher = - rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == RESULT_OK) { - val uri = result.data?.data ?: return@rememberLauncherForActivityResult - ImportTasksDialog.newImportTasksDialog(uri) - .show(supportFragmentManager, FRAG_TAG_IMPORT_TASKS) - } - } AddAccountScreen( - gettingStarted = route.showImport, hasTasksAccount = inventory.hasTasksAccount, hasPro = inventory.hasPro, onBack = { navController.popBackStack() }, @@ -220,18 +273,27 @@ class MainActivity : AppCompatActivity() { firebase.logEvent(R.string.event_onboarding_sync, R.string.param_selection to platform.name) addAccountViewModel.openUrl(this@MainActivity, platform) }, - onImportBackup = { - firebase.logEvent(R.string.event_onboarding_sync, R.string.param_selection to "import_backup") - importBackupLauncher.launch( - FileHelper.newFilePickerIntent(this@MainActivity, preferences.backupDirectory), - ) - } ) } composable { if (hasAccount != true) { return@composable } + // Show ToS update dialog for existing users that need re-acceptance + if (needsTosAcceptance) { + TosUpdateDialog( + isUpdate = acceptedTosVersion > 0, + onAccept = { + lifecycleScope.launch { + firebase.logEvent(R.string.event_accept_tos_update) + setAcceptedTosVersion(currentTosVersion) + workManager.sync(immediate = true) + } + }, + onExit = { finish() }, + openUrl = { openUri(it) } + ) + } val scope = rememberCoroutineScope() val state = viewModel.state.collectAsStateWithLifecycle().value val drawerState = rememberDrawerState( diff --git a/app/src/main/java/org/tasks/auth/SignInActivity.kt b/app/src/main/java/org/tasks/auth/SignInActivity.kt index 6a4f6cad6..f83590451 100644 --- a/app/src/main/java/org/tasks/auth/SignInActivity.kt +++ b/app/src/main/java/org/tasks/auth/SignInActivity.kt @@ -21,10 +21,6 @@ import androidx.activity.viewModels import androidx.annotation.MainThread import androidx.annotation.WorkerThread import androidx.browser.customtabs.CustomTabsIntent -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.setValue import androidx.compose.ui.window.Dialog import androidx.lifecycle.lifecycleScope import at.bitfire.dav4jvm.exception.HttpException @@ -45,9 +41,7 @@ import org.tasks.billing.Inventory import org.tasks.billing.PurchaseActivity import org.tasks.billing.PurchaseActivityViewModel.Companion.EXTRA_GITHUB import org.tasks.billing.PurchaseActivityViewModel.Companion.EXTRA_NAME_YOUR_PRICE -import org.tasks.compose.ConsentDialog import org.tasks.compose.SignInDialog -import org.tasks.dialogs.DialogBuilder import org.tasks.extensions.Context.openUri import org.tasks.themes.TasksTheme import org.tasks.themes.Theme @@ -71,7 +65,6 @@ import javax.inject.Inject class SignInActivity : ComponentActivity() { @Inject lateinit var theme: Theme @Inject lateinit var inventory: Inventory - @Inject lateinit var dialogBuilder: DialogBuilder @Inject lateinit var firebase: Firebase private val viewModel: SignInViewModel by viewModels() @@ -102,29 +95,17 @@ class SignInActivity : ComponentActivity() { viewModel.error.observe(this, this::handleError) val autoSelect = intent.getSerializableExtra(EXTRA_SELECT_SERVICE) as Platform? - setContent { - var selectedPlatform by rememberSaveable { - mutableStateOf(autoSelect) - } - TasksTheme( - theme = theme.themeBase.index, - primary = theme.themeColor.primaryColor, - ) { - selectedPlatform - ?.let { - Dialog(onDismissRequest = { finish() }) { - ConsentDialog { agree -> - if (agree) { - selectService(it) - } else { - finish() - } - } - } - } - ?: Dialog(onDismissRequest = { finish() }) { + if (autoSelect != null) { + selectService(autoSelect) + } else { + setContent { + TasksTheme( + theme = theme.themeBase.index, + primary = theme.themeColor.primaryColor, + ) { + Dialog(onDismissRequest = { finish() }) { SignInDialog( - selected = { selectedPlatform = it }, + selected = { selectService(it) }, help = { openUri(R.string.help_url_sync) finish() @@ -132,6 +113,7 @@ class SignInActivity : ComponentActivity() { cancel = { finish() } ) } + } } } } @@ -176,6 +158,11 @@ class SignInActivity : ComponentActivity() { } } + private suspend fun setupAccount(): Boolean { + val account = viewModel.setupAccount(authService) + return account != null + } + override fun onDestroy() { super.onDestroy() @@ -199,15 +186,17 @@ class SignInActivity : ComponentActivity() { RC_AUTH -> if (resultCode == RESULT_OK) { lifecycleScope.launch { - val account = try { + try { viewModel.handleResult(authService, data!!) + if (authService.authStateManager.current.isAuthorized) { + if (setupAccount()) { + setResult(RESULT_OK) + finish() + } + } } catch (e: Exception) { returnError(e) } - if (account != null) { - setResult(RESULT_OK) - finish() - } } } else { returnError( diff --git a/app/src/main/java/org/tasks/auth/SignInViewModel.kt b/app/src/main/java/org/tasks/auth/SignInViewModel.kt index cf42a882c..1ccf85564 100644 --- a/app/src/main/java/org/tasks/auth/SignInViewModel.kt +++ b/app/src/main/java/org/tasks/auth/SignInViewModel.kt @@ -4,7 +4,6 @@ import android.content.Context import android.content.Intent import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import org.tasks.data.UUIDHelper import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import net.openid.appauth.AuthorizationException @@ -14,8 +13,9 @@ import net.openid.appauth.GrantTypeValues import net.openid.appauth.TokenRequest import org.tasks.R import org.tasks.caldav.CaldavClientProvider -import org.tasks.data.entity.CaldavAccount +import org.tasks.data.UUIDHelper import org.tasks.data.dao.CaldavDao +import org.tasks.data.entity.CaldavAccount import org.tasks.security.KeyStoreEncryption import timber.log.Timber import javax.inject.Inject @@ -37,7 +37,7 @@ class SignInViewModel @Inject constructor( authService = AuthorizationService(iss, context, debugConnectionBuilder) } - suspend fun handleResult(authService: AuthorizationService, intent: Intent): CaldavAccount? { + suspend fun handleResult(authService: AuthorizationService, intent: Intent) { val response = AuthorizationResponse.fromIntent(intent) val ex = AuthorizationException.fromIntent(intent) val authStateManager = authService.authStateManager @@ -53,12 +53,7 @@ class SignInViewModel @Inject constructor( ex?.let { error.value = it - return null } - - return authStateManager.current - .takeIf { it.isAuthorized } - ?.let { setupAccount(authService) } } suspend fun setupAccount(authService: AuthorizationService): CaldavAccount? { @@ -66,7 +61,7 @@ class SignInViewModel @Inject constructor( val tokenString = auth.accessToken ?: return null val idToken = auth.idToken?.let { IdToken(it) } ?: return null val username = "${authService.iss}_${idToken.sub}" - try { + return try { val homeSet = provider .forUrl( context.getString(R.string.tasks_caldav_url), @@ -75,7 +70,7 @@ class SignInViewModel @Inject constructor( ) .homeSet(username, tokenString) val password = encryption.encrypt(tokenString) - return caldavDao.getAccount(CaldavAccount.TYPE_TASKS, username) + caldavDao.getAccount(CaldavAccount.TYPE_TASKS, username) ?.let { it.copy(error = null, password = password) .also { caldavDao.update(it) } @@ -91,9 +86,10 @@ class SignInViewModel @Inject constructor( it.copy(id = caldavDao.insert(it)) } } catch (e: Exception) { + Timber.d("setupAccount: caught ${e.javaClass.simpleName} - ${e.message}") error.postValue(e) + null } - return null } private suspend fun exchangeAuthorizationCode( diff --git a/app/src/main/java/org/tasks/caldav/CaldavClientProvider.kt b/app/src/main/java/org/tasks/caldav/CaldavClientProvider.kt index ad816d31a..250cf08a0 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavClientProvider.kt +++ b/app/src/main/java/org/tasks/caldav/CaldavClientProvider.kt @@ -12,6 +12,7 @@ import org.tasks.billing.Inventory import org.tasks.data.entity.CaldavAccount import org.tasks.data.getPassword import org.tasks.http.HttpClientFactory +import org.tasks.preferences.TasksPreferences import org.tasks.security.KeyStoreEncryption import java.util.concurrent.TimeUnit import javax.inject.Inject @@ -21,6 +22,7 @@ class CaldavClientProvider @Inject constructor( private val encryption: KeyStoreEncryption, private val inventory: Inventory, private val httpClientFactory: HttpClientFactory, + private val tasksPreferences: TasksPreferences, ) { private val tasksUrl = context.getString(R.string.tasks_caldav_url) @@ -29,7 +31,8 @@ class CaldavClientProvider @Inject constructor( username: String? = null, password: String? = null ): CaldavClient { - val auth = getAuthInterceptor(username, password, url) + val tosVersion = tasksPreferences.get(TasksPreferences.acceptedTosVersion, 0) + val auth = getAuthInterceptor(username, password, url, tosVersion) return CaldavClient( this, createHttpClient( @@ -48,10 +51,12 @@ class CaldavClientProvider @Inject constructor( } suspend fun forAccount(account: CaldavAccount, url: String? = account.url): CaldavClient { + val tosVersion = tasksPreferences.get(TasksPreferences.acceptedTosVersion, 0) val auth = getAuthInterceptor( account.username, account.getPassword(encryption), - account.url + account.url, + tosVersion, ) val client = createHttpClient(auth) return if (account.isTasksOrg) { @@ -64,10 +69,11 @@ class CaldavClientProvider @Inject constructor( private fun getAuthInterceptor( username: String?, password: String?, - url: String? + url: String?, + tosVersion: Int, ): Interceptor? = when { username.isNullOrBlank() || password.isNullOrBlank() -> null - url?.startsWith(tasksUrl) == true -> TasksBasicAuth(username, password, inventory) + url?.startsWith(tasksUrl) == true -> TasksBasicAuth(username, password, inventory, tosVersion) else -> BasicDigestAuthHandler(null, username, password) } diff --git a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt index e30b8cdfb..23e3343bf 100644 --- a/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt +++ b/app/src/main/java/org/tasks/caldav/CaldavSynchronizer.kt @@ -124,7 +124,7 @@ class CaldavSynchronizer @Inject constructor( setError(account, e) } catch (e: HttpException) { when(e.code) { - 402, in 500..599 -> {} + 402, 451, in 500..599 -> {} else -> { firebase.reportException(e) } } setError(account, e) diff --git a/app/src/main/java/org/tasks/caldav/TasksBasicAuth.kt b/app/src/main/java/org/tasks/caldav/TasksBasicAuth.kt index 95a4d9347..f24085ff8 100644 --- a/app/src/main/java/org/tasks/caldav/TasksBasicAuth.kt +++ b/app/src/main/java/org/tasks/caldav/TasksBasicAuth.kt @@ -8,7 +8,8 @@ import org.tasks.billing.Inventory class TasksBasicAuth( val user: String, token: String, - private val inventory: Inventory + private val inventory: Inventory, + private val tosVersion: Int, ) : Interceptor { private val credentials = Credentials.basic(user, token, Charsets.UTF_8) @@ -18,6 +19,7 @@ class TasksBasicAuth( builder.header(SKU, it.sku) builder.header(TOKEN, it.purchaseToken) } + builder.header(TOS_VERSION, tosVersion.toString()) return chain.proceed(builder.build()) } @@ -25,5 +27,6 @@ class TasksBasicAuth( private const val AUTHORIZATION = "Authorization" private const val SKU = "tasks-sku" private const val TOKEN = "tasks-token" + private const val TOS_VERSION = "tasks-tos-version" } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/tasks/compose/Destinations.kt b/app/src/main/java/org/tasks/compose/Destinations.kt index 579b334a2..8dba13fa4 100644 --- a/app/src/main/java/org/tasks/compose/Destinations.kt +++ b/app/src/main/java/org/tasks/compose/Destinations.kt @@ -6,4 +6,7 @@ import kotlinx.serialization.Serializable object HomeDestination @Serializable -data class AddAccountDestination(val showImport: Boolean) +object WelcomeDestination + +@Serializable +object AddAccountDestination diff --git a/app/src/main/java/org/tasks/compose/SignInDialog.kt b/app/src/main/java/org/tasks/compose/SignInDialog.kt index b381a912a..ad5373019 100644 --- a/app/src/main/java/org/tasks/compose/SignInDialog.kt +++ b/app/src/main/java/org/tasks/compose/SignInDialog.kt @@ -2,7 +2,6 @@ package org.tasks.compose import android.content.res.Configuration import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -77,44 +76,6 @@ fun SignInDialog( } } -@Composable -fun ConsentDialog( - agree: (Boolean) -> Unit, -) { - Column(Modifier.background(MaterialTheme.colorScheme.surface)) { - Text( - text = stringResource(id = R.string.sign_in_to_tasks), - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding(16.dp), - color = MaterialTheme.colorScheme.onSurface, - ) - Text( - text = stringResource(id = R.string.sign_in_to_tasks_disclosure), - modifier = Modifier.padding(horizontal = 16.dp), - color = MaterialTheme.colorScheme.onSurface, - ) - Row( - horizontalArrangement = Arrangement.End, - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - ) { - TextButton(onClick = { agree(false) }) { - Text( - text = stringResource(id = R.string.consent_deny), - color = MaterialTheme.colorScheme.onSurface, - ) - } - TextButton(onClick = { agree(true) }) { - Text( - text = stringResource(id = R.string.consent_agree), - color = MaterialTheme.colorScheme.onSurface, - ) - } - } - } -} - @Preview(widthDp = 320) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) @Composable @@ -123,12 +84,3 @@ fun SignInDialogPreview() { SignInDialog(selected = {}, help = {}, cancel = {}) } } - -@Preview(widthDp = 320) -@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) -@Composable -fun DisclosurePreview() { - TasksTheme { - ConsentDialog(agree = {}) - } -} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/TosUpdateDialog.kt b/app/src/main/java/org/tasks/compose/TosUpdateDialog.kt new file mode 100644 index 000000000..ebb8ed61d --- /dev/null +++ b/app/src/main/java/org/tasks/compose/TosUpdateDialog.kt @@ -0,0 +1,75 @@ +package org.tasks.compose + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.PreviewLightDark +import androidx.compose.ui.window.DialogProperties +import org.tasks.R +import org.tasks.themes.TasksTheme + +@Composable +fun TosUpdateDialog( + isUpdate: Boolean, + onAccept: () -> Unit, + onExit: () -> Unit, + openUrl: (String) -> Unit, +) { + AlertDialog( + onDismissRequest = onExit, + properties = DialogProperties( + dismissOnBackPress = true, + dismissOnClickOutside = false, + ), + title = { + Text( + text = stringResource( + if (isUpdate) R.string.tos_updated_title else R.string.terms_of_service_proper + ) + ) + }, + text = { + LegalDisclosure(openLegalUrl = openUrl, textAlign = TextAlign.Start) + }, + dismissButton = { + TextButton(onClick = onExit) { + Text(text = stringResource(R.string.exit)) + } + }, + confirmButton = { + Button(onClick = onAccept) { + Text(text = stringResource(R.string.accept)) + } + } + ) +} + +@PreviewLightDark +@Composable +fun TosUpdateDialogPreview() { + TasksTheme { + TosUpdateDialog( + isUpdate = true, + onAccept = {}, + onExit = {}, + openUrl = {}, + ) + } +} + +@PreviewLightDark +@Composable +fun TosDialogPreview() { + TasksTheme { + TosUpdateDialog( + isUpdate = false, + onAccept = {}, + onExit = {}, + openUrl = {}, + ) + } +} diff --git a/app/src/main/java/org/tasks/compose/WelcomeScreen.kt b/app/src/main/java/org/tasks/compose/WelcomeScreen.kt new file mode 100644 index 000000000..f15b54e50 --- /dev/null +++ b/app/src/main/java/org/tasks/compose/WelcomeScreen.kt @@ -0,0 +1,237 @@ +package org.tasks.compose + +import androidx.activity.compose.BackHandler +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +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.widthIn +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.ClickableText +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +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.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.TextDecoration +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.themes.TasksTheme + +@Composable +fun WelcomeScreen( + onBack: () -> Unit, + onSignIn: () -> Unit, + onContinueWithoutSync: () -> Unit, + onImportBackup: () -> Unit, + openLegalUrl: (String) -> Unit, +) { + BackHandler(onBack = onBack) + Scaffold { paddingValues -> + BoxWithConstraints( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + ) { + val isCompact = maxHeight < 500.dp + if (isCompact) { + Row( + modifier = Modifier + .fillMaxSize() + .padding(24.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(24.dp), + ) { + Box( + modifier = Modifier.weight(1f), + contentAlignment = Alignment.Center, + ) { + Icon( + painter = painterResource(id = R.drawable.ic_round_icon), + contentDescription = stringResource(R.string.tasks_org), + tint = Color.Unspecified, + modifier = Modifier.size(120.dp) + ) + } + WelcomeContent( + onSignIn = onSignIn, + onContinueWithoutSync = onContinueWithoutSync, + onImportBackup = onImportBackup, + openLegalUrl = openLegalUrl, + modifier = Modifier + .weight(1f) + .verticalScroll(rememberScrollState()), + ) + } + } else { + Column( + modifier = Modifier + .fillMaxSize() + .padding(32.dp), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Spacer(modifier = Modifier.weight(1f)) + Icon( + painter = painterResource(id = R.drawable.ic_round_icon), + contentDescription = stringResource(R.string.tasks_org), + tint = Color.Unspecified, + modifier = Modifier.size(152.dp) + ) + Spacer(modifier = Modifier.weight(1f)) + WelcomeContent( + onSignIn = onSignIn, + onContinueWithoutSync = onContinueWithoutSync, + onImportBackup = onImportBackup, + openLegalUrl = openLegalUrl, + modifier = Modifier.fillMaxWidth(), + ) + } + } + } + } +} + +@Composable +private fun WelcomeContent( + onSignIn: () -> Unit, + onContinueWithoutSync: () -> Unit, + onImportBackup: () -> Unit, + openLegalUrl: (String) -> Unit, + modifier: Modifier = Modifier, +) { + val buttonModifier = Modifier + .widthIn(max = 400.dp) + .fillMaxWidth() + + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + LegalDisclosure( + openLegalUrl = openLegalUrl, + modifier = Modifier.widthIn(max = 400.dp), + ) + + Spacer(modifier = Modifier.height(16.dp)) + + Button( + onClick = onSignIn, + modifier = buttonModifier + ) { + Text(text = stringResource(R.string.add_account)) + } + + Spacer(modifier = Modifier.height(8.dp)) + + OutlinedButton( + onClick = onContinueWithoutSync, + modifier = buttonModifier + ) { + Text(text = stringResource(R.string.continue_without_sync)) + } + + Spacer(modifier = Modifier.height(24.dp)) + + Text( + text = stringResource(R.string.returning_user), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + + TextButton(onClick = onImportBackup) { + Text(text = stringResource(R.string.backup_BAc_import)) + } + } +} + +@Composable +internal fun LegalDisclosure( + openLegalUrl: (String) -> Unit, + modifier: Modifier = Modifier, + textAlign: TextAlign = TextAlign.Center, +) { + val bodyStyle = MaterialTheme.typography.bodySmall.copy( + textAlign = textAlign, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + val linkStyle = SpanStyle( + color = MaterialTheme.colorScheme.primary, + textDecoration = TextDecoration.Underline, + ) + val tosText = stringResource(R.string.terms_of_service_proper) + val privacyText = stringResource(R.string.privacy_policy_proper) + val licenseText = stringResource(R.string.gplv3_license) + val template = stringResource(R.string.legal_disclosure) + val formatted = String.format(template, tosText, privacyText, licenseText) + + val tosStart = formatted.indexOf(tosText) + val tosEnd = tosStart + tosText.length + val privacyStart = formatted.indexOf(privacyText) + val privacyEnd = privacyStart + privacyText.length + val licenseStart = formatted.indexOf(licenseText) + val licenseEnd = licenseStart + licenseText.length + + val tosUrl = stringResource(R.string.url_tos) + val privacyUrl = stringResource(R.string.url_privacy_policy) + val licenseUrl = stringResource(R.string.url_license) + + val annotatedText = buildAnnotatedString { + append(formatted) + addStyle(linkStyle, tosStart, tosEnd) + addStringAnnotation(tag = "url", annotation = tosUrl, start = tosStart, end = tosEnd) + addStyle(linkStyle, privacyStart, privacyEnd) + addStringAnnotation(tag = "url", annotation = privacyUrl, start = privacyStart, end = privacyEnd) + addStyle(linkStyle, licenseStart, licenseEnd) + addStringAnnotation(tag = "url", annotation = licenseUrl, start = licenseStart, end = licenseEnd) + } + + ClickableText( + text = annotatedText, + style = bodyStyle, + modifier = modifier, + onClick = { offset -> + annotatedText.getStringAnnotations(tag = "url", start = offset, end = offset) + .firstOrNull() + ?.let { openLegalUrl(it.item) } + } + ) +} + +@PreviewLightDark +@PreviewScreenSizes +@PreviewFontScale +@Composable +fun WelcomeScreenPreview() { + TasksTheme { + WelcomeScreen( + onBack = {}, + onSignIn = {}, + onContinueWithoutSync = {}, + onImportBackup = {}, + openLegalUrl = {}, + ) + } +} diff --git a/app/src/main/java/org/tasks/compose/accounts/AddAccountScreen.kt b/app/src/main/java/org/tasks/compose/accounts/AddAccountScreen.kt index 5e3a8692f..2ba267c73 100644 --- a/app/src/main/java/org/tasks/compose/accounts/AddAccountScreen.kt +++ b/app/src/main/java/org/tasks/compose/accounts/AddAccountScreen.kt @@ -18,16 +18,12 @@ 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 @@ -36,7 +32,6 @@ 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 @@ -55,41 +50,27 @@ 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() - } - } + BackHandler(onBack = onBack) Scaffold( topBar = { TopAppBar( colors = TopAppBarDefaults.topAppBarColors(), navigationIcon = { - if (!gettingStarted) { - IconButton(onClick = onBack) { - Icon( - imageVector = Icons.AutoMirrored.Outlined.ArrowBack, - contentDescription = stringResource(R.string.back), - ) - } + 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) - } - ) + Text(text = stringResource(R.string.add_account)) } ) } @@ -107,21 +88,6 @@ fun AddAccountScreen( 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, @@ -130,28 +96,28 @@ fun AddAccountScreen( 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, @@ -159,29 +125,20 @@ fun AddAccountScreen( 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 - ) - } } } } @@ -241,108 +198,18 @@ fun AccountTypeCard( } } -@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 = {}, ) } } diff --git a/app/src/main/java/org/tasks/jobs/SyncWork.kt b/app/src/main/java/org/tasks/jobs/SyncWork.kt index 97dbba459..c95bbfdc6 100644 --- a/app/src/main/java/org/tasks/jobs/SyncWork.kt +++ b/app/src/main/java/org/tasks/jobs/SyncWork.kt @@ -35,6 +35,7 @@ import org.tasks.gtasks.GoogleTaskSynchronizer import org.tasks.injection.BaseWorker import org.tasks.opentasks.OpenTasksSynchronizer import org.tasks.preferences.Preferences +import org.tasks.preferences.TasksPreferences import org.tasks.sync.microsoft.MicrosoftSynchronizer import org.tasks.time.DateTimeUtils2.currentTimeMillis import timber.log.Timber @@ -53,11 +54,13 @@ class SyncWork @AssistedInject constructor( private val openTasksSynchronizer: Lazy, private val microsoftSynchronizer: Lazy, private val openTaskDao: OpenTaskDao, - private val inventory: Inventory + private val inventory: Inventory, + private val tasksPreferences: TasksPreferences, ) : BaseWorker(context, workerParams, firebase) { override suspend fun run(): Result { Timber.d("Starting...") + if (isBackground) { ContextCompat.getSystemService(context, ConnectivityManager::class.java)?.apply { if (restrictBackgroundStatus == ConnectivityManagerCompat.RESTRICT_BACKGROUND_STATUS_ENABLED) { @@ -95,9 +98,15 @@ class SyncWork @AssistedInject constructor( private val syncStatus = R.string.p_sync_ongoing + private suspend fun hasTosAcceptance(): Boolean { + val currentTosVersion = firebase.getTosVersion() + val acceptedTosVersion = tasksPreferences.get(TasksPreferences.acceptedTosVersion, 0) + return acceptedTosVersion >= currentTosVersion + } + private suspend fun doSync() { val hasNetworkConnectivity = context.hasNetworkConnectivity() - if (hasNetworkConnectivity) { + if (hasNetworkConnectivity && hasTosAcceptance()) { googleTaskJobs().plus(caldavJobs()).awaitAll() } inventory.updateTasksAccount() diff --git a/app/src/main/java/org/tasks/preferences/fragments/HelpAndFeedback.kt b/app/src/main/java/org/tasks/preferences/fragments/HelpAndFeedback.kt index 01346aecc..de565187a 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/HelpAndFeedback.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/HelpAndFeedback.kt @@ -84,6 +84,7 @@ class HelpAndFeedback : InjectingPreferenceFragment() { openUrl(R.string.follow_reddit, R.string.url_reddit) openUrl(R.string.follow_twitter, R.string.url_twitter) openUrl(R.string.source_code, R.string.url_source_code) + openUrl(R.string.terms_of_service, R.string.url_tos) openUrl(R.string.privacy_policy, R.string.url_privacy_policy) } diff --git a/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt b/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt index ec3e8d206..485cb219c 100644 --- a/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt +++ b/app/src/main/java/org/tasks/preferences/fragments/TasksAccount.kt @@ -22,9 +22,12 @@ import org.tasks.auth.SignInActivity import org.tasks.auth.SignInActivity.Platform import org.tasks.billing.Inventory import org.tasks.billing.Purchase +import org.tasks.analytics.Firebase import org.tasks.data.entity.CaldavAccount import org.tasks.data.entity.CaldavAccount.Companion.isPaymentRequired +import org.tasks.data.entity.CaldavAccount.Companion.isTosRequired import org.tasks.extensions.Context.openUri +import org.tasks.preferences.TasksPreferences import org.tasks.extensions.Context.toast import org.tasks.jobs.WorkManager import org.tasks.kmp.org.tasks.time.DateStyle @@ -40,8 +43,11 @@ class TasksAccount : BaseAccountPreference() { @Inject lateinit var inventory: Inventory @Inject lateinit var localBroadcastManager: LocalBroadcastManager @Inject lateinit var workManager: WorkManager + @Inject lateinit var firebase: Firebase + @Inject lateinit var tasksPreferences: TasksPreferences private val viewModel: TasksAccountViewModel by viewModels() + private var tosDialogShown = false private val refreshReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -209,6 +215,11 @@ class TasksAccount : BaseAccountPreference() { findPreference(R.string.local_lists).summary = getString(R.string.migrate_count, quantityString) } + + if (account.isTosRequired() && !tosDialogShown) { + tosDialogShown = true + showTosDialog() + } } private suspend fun refreshPasswords(passwords: List) { @@ -250,6 +261,24 @@ class TasksAccount : BaseAccountPreference() { ) } + private fun showTosDialog() { + dialogBuilder + .newDialog(R.string.tos_updated_title) + .setNeutralButton(R.string.view_tos) { _, _ -> + context?.openUri(R.string.url_tos) + } + .setPositiveButton(R.string.accept) { _, _ -> + lifecycleScope.launch { + val currentTosVersion = firebase.getTosVersion() + tasksPreferences.set(TasksPreferences.acceptedTosVersion, currentTosVersion) + caldavDao.update(account.copy(error = null)) + workManager.sync(immediate = true) + } + } + .setNegativeButton(R.string.cancel, null) + .show() + } + companion object { fun newTasksAccountPreference(account: CaldavAccount): Fragment { val fragment = TasksAccount() diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 4f00c1dbd..dc93c343c 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -675,7 +675,6 @@ اﻵن تاريخ الإنشاء - سيتم إرسال عنوان البريد الإلكتروني ومعرف الحساب الخاصان بك وتخزينهما من قبل Tasks.org. هذه المعلومات سيتم استخدامها من أجل المصادقة ومن أجل تزويدك بالإعلانات المهمة المتعلقة بالخدمة. لن يتم مشاركة هذه المعلومات مع أحد. تذكير تلقائي افتراضي أظهر الإشعارات من أجل المهام بدون أوقات محددة مكتمل @@ -807,6 +806,5 @@ للتأكد من إخطارك في الوقت المناسب، امنح الإذن لضبط التنبيهات والتذكيرات في الإعدادات المتابعة بدون مزامنة %s سيتم حذفها. لا يمكن التراجع عن ذلك! - ساعدني في الاختيار عرض المزيد من المهام diff --git a/app/src/main/res/values-ast/strings.xml b/app/src/main/res/values-ast/strings.xml index 97c12acae..34de5794c 100644 --- a/app/src/main/res/values-ast/strings.xml +++ b/app/src/main/res/values-ast/strings.xml @@ -406,7 +406,6 @@ Data y hora Amestar cuenta Continuar ensin sincronizar - Aidame a escoyer Usuariu Conseña URL diff --git a/app/src/main/res/values-bg-rBG/strings.xml b/app/src/main/res/values-bg-rBG/strings.xml index 43d9f69a8..76a1fb291 100644 --- a/app/src/main/res/values-bg-rBG/strings.xml +++ b/app/src/main/res/values-bg-rBG/strings.xml @@ -649,7 +649,6 @@ Завършена %s Синхронизирайте задачите си с личния профил в Microsoft Вход - Адресът на електронната поща и идентификаторът на профила ще бъдат изпратени и съхранени от Tasks.org. Информацията ще бъде използвана за удостоверяване и за предоставяне на важни съобщения, свързани с услугата. Тази информация няма да бъде споделяна с никого. Съгласявам се Не сега Подразбирано напомняне @@ -719,6 +718,5 @@ Докоснете „Готово“ за запазване на задачата Задачата %s ще бъде премахната. Действието е необратимо! Без синхронизиране - Помощ в избора Повече задачи diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index b5e6d1c9f..f4d20a771 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -508,7 +508,6 @@ Voleu suprimir les tasques seleccionades? Afegeiu un compte Continua sense sincronitzar - Ajuda\'m a triar Notificacions del paquet Recordatori predeterminat Insígnies @@ -668,7 +667,6 @@ Per sobre de la mitjana Desa %d%% Inicieu la sessió a Tasks.org - La vostra adreça de correu electrònic i l\'ID del compte es transmetran i s\'emmagatzemaran a Tasks.org. Aquesta informació s\'utilitzarà per a l\'autenticació i per a proporcionar-vos anuncis relacionats amb serveis importants. Aquesta informació no es compartirà amb ningú. Contrasenya de l\'aplicació Contrasenyes d\'aplicacions Sincronitzeu les vostres tasques i calendaris amb l\'escriptori de tercers i les aplicacions mòbils. Fes clic aquí per a més informació diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 636e30792..d58f11ff2 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -671,7 +671,6 @@ Výchozí upomínka Zobrazovat upozornění na úkoly bez termínů Synchronizovat se svým osobním účtem Microsoft - Vaše e-mailová adresa a ID účtu budou přeneseny a uloženy na Tasks.org. Tyto informace budou použity pro ověření a pro poskytování důležitých oznámení souvisejících se službami. Tyto informace nebudou s nikým sdíleny. Souhlasím Teď ne Přihlásit se @@ -739,7 +738,6 @@ Stiskněte tlačítko Hotovo pro uložení úkolu Aplikace aktualizována Aplikace Tasks byla aktualizována na %s. Chcete zobrazit seznam změn? - Pomozte mi s výběrem Úkol %s bude odstraněn. Tato akce je nevratná! Pokračovat bez synchronizace Zobrazit další úkoly diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 9971f4f83..571c7f765 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -678,7 +678,6 @@ Aldrig Stigende - Din e-mailadresse og dit konto-ID overføres og gemmes af Tasks.org. Disse oplysninger bruges til godkendelse og til at give dig vigtige tjenesterelaterede meddelelser og deles ikke med nogen. Dynamisk Underopgaver på flere niveauer understøttes ikke af Microsoft To Do Overvej at vise din støtte med en donation! @@ -718,7 +717,6 @@ App opdateret Tryk på Færdig for at gemme opgave Fortsæt uden synkronisering - Hjælp mig med at vælge %s bliver slettet. Dette kan ikke fortrydes! Vis flere opgaver diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c401dce37..c8271580b 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -650,7 +650,6 @@ Erinnerungen sind in den Android-Einstellungen deaktiviert Zustimmen Nicht jetzt - Deine E-Mail-Adresse und Account-ID werden an Tasks.org übertragen und dort gespeichert. Diese Informationen werden verwedet, um dich zu authentifizieren und über wichtige servicebezogene Informationen zu benachrichtigen. Deine Daten werden mit niemandem geteilt. Mit persönlichem Microsoft-Konto synchronisieren Anmelden Benachrichtigungen für Aufgaben ohne Fälligkeitsdatum anzeigen @@ -717,7 +716,6 @@ App aktualisiert Tasks wurde gerade auf %s aktualisiert. Möchtest du dir die Änderungsnotizen ansehen? Erledigt drücken, um Aufgabe zu speichern - Hilf mir bei der Auswahl Weiter ohne Synchronisierung %s wird/werden gelöscht. Dies kann nicht rückgängig gemacht werden! Mehr Aufgaben anzeigen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 591e1a661..1acd496fe 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -337,7 +337,6 @@ Ημερομηνία και ώρα Προσθήκη λογαριασμού Συνέχεια χωρίς συγχρονισμό - Βοήθησε με να διαλέξω Χρήστης Κωδικός Ηλεκτρονική διεύθυνση (URL) @@ -570,7 +569,6 @@ Άνω του μέσου όρου Αποθήκευση %d%% Συνδεθείτε στο Tasks.org - Η διεύθυνση ηλεκτρονικού ταχυδρομείου και το αναγνωριστικό του λογαριασμού σας θα μεταδοθούν και θα αποθηκευτούν από το Tasks.org. Οι πληροφορίες αυτές θα χρησιμοποιηθούν για την εξακρίβωση της ταυτότητας και για την παροχή σημαντικών ανακοινώσεων σχετικά με την υπηρεσία. Αυτές οι πληροφορίες δεν θα κοινοποιηθούν σε κανέναν. Κωδικός εφαρμογής Κωδικοί εφαρμογής Συγχρονίστε τις εργασίες και τα ημερολόγια σας με εφαρμογές ηλεκτρονικών υπολογιστών και κινητών συσκευών τρίτων. Πατήστε εδώ για περισσότερες πληροφορίες diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index 8c1cc3886..09f5eea8c 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -655,7 +655,6 @@ Bezonas konton de EteSync.com aŭ memgastigan servilon Sciigoj per portebla aparataro Vi devas ankaŭ doni permeson al savkopia servo per viaj aparataj agordoj. Ne ĉiuj aparatoj provizas savkopian servon. - Via retpoŝtadreso kaj konta ID estos transsendita kaj konservita de Tasks.org. Ĉi tiu informo estos uzata por aŭtentigi kaj por provizi al vi gravajn anoncojn rilate al servo. Ĉi tiu informo ne estos diskongita al iu ajn. Ripetiĝas ĉiu Vidigi nombro da taskoj sur lanĉpiktogramo de Tasks. Ne ĉiuj lanĉiloj subtenas ĉi tiajn simbolojn. Bezonas konton de CalDAV servprovizanto aŭ memgastigan servilon. Por trovi servprovizanton vizitu task.org/caldav @@ -719,6 +718,5 @@ Tasks estis ĝisdatigita al %s. Ĉu vi deziras legi la eldonajn notojn? Forigas %s. Ne eblas malfari tion! Daŭrigi sen sinkronigo - Helpu min elekti Vidi pli da taskoj diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a938a612a..adf5f60f4 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -670,7 +670,6 @@ Fecha de creación Sincronizar con su cuenta personal de Microsoft Iniciar sesión - Tu dirección de correo electrónico y el ID de tu cuenta serán transmitidos y almacenados por Tasks.org. Esta información se utilizará para la autenticación y para proporcionarle anuncios importantes relacionados con el servicio. Esta información no se compartirá con nadie. De acuerdo No ahora Aviso por defecto @@ -740,7 +739,6 @@ Presione en Hecho para guardar la tarea Comentario Continua sin sincronizar - Ayudame a escoger %s será eliminado. ¡Esto no se puede deshacer! Ver más tareas diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 2bbf2a1f7..80769edac 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -652,7 +652,6 @@ Teisalda %s teenusesse Tasks.org Lõpetatud %s Ülesannete loend - Sinu e-posti aadress ja kasutajatunnus saadetakse teenusele Tasks.org ja salvestuvad seal. Neid kasutatakse autentimiseks ja teenusega seotud oluliste teavituste jaoks, kuid ei jagata kolmandate osapooltega. Google\'i kasutajakontoga lihtsustatud andmete sünkroniseerimise võimalus Tere! Minu nimi on Alex ja ma olen sõltumatu arendaja, kes on Taski arendust juhtinud Infosildid @@ -718,7 +717,6 @@ Rakendus on uuendatud Ülesande salvestamiseks vajuta nuppu „Valmis“ Jätka ilma sünkroniseerimata - Aita mul valida %s saab olema kustutatud. Seda tegevust ei saa tagasi pöörata! Vaata veel ülesandeid diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index f18cc038e..c189e5e75 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -652,7 +652,6 @@ Aktibatu oroigarriak Osatu %s Sortze-data - Zure helbide elektronikoa eta kontuaren IDa Tasks.org-era transmititu eta biltegiratuko da. Informazio hau autentifikazio eta zerbitzuari erlazionatutako iragarki garrantzitsuak emateko erabiliko da. Informazio hau ez da inorekin partekatuko. Onartu Orain ez Sinkronizatu zure Microsoft kontu pertsonalarekin diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index 594e54f35..b92a023a9 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -684,7 +684,6 @@ Näytä muokkaa näyttöä ilman lukituksen avaamista Dynaaminen Harkitse tuen osoittamista lahjoituksella! - Sähköpostiosoitteesi ja käyttäjä-ID:si lähetetään säilytettäväksi Tasks.org:iin. Näitä tietoja käytetään tunnistautumiseen ja tärkeiden palveluun liittyvien ilmoitusten toimittamiseen. Tietoja ei jaeta kenenkään kanssa. Aktivoi pika-asetusten käyttö ilman laitteen lukituksen avaamista Ei käytettävissä tunnisteiden, suodattimien tai paikkojen kanssa Muokkaa vetolaatikkoa @@ -700,7 +699,6 @@ Kommentti Eilen Jatka ilman synkronointia - Auta minua valitsemaan %s poistetaan. Tätä ei voi perua! Sovellus päivitetty Sovellus päivitettiin juuri %s. Haluatko lukea julkaisutiedot? diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 14111c417..f606e1167 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -671,7 +671,6 @@ Synchroniser avec votre compte « Microsoft » personnel Se connecter Pas maintenant - Votre adresse électronique et votre identifiant de compte seront transmis et stockés par « Tasks.org ». Ces informations seront utilisées pour l\'authentification et pour vous fournir des annonces importantes relatives au service. Ces informations ne seront partagées avec personne. Accepter Rappel par défaut Afficher les notifications pour les tâches dont les heures d\'échéances ne sont pas renseignées @@ -739,7 +738,6 @@ Tasks vient d\'être mis à jour vers %s. Souhaitez-vous consulter les notes de mise à jour ? Application mise à jour Continuer sans synchronisation - Aidez-moi à choisir %s sera supprimé. Cette opération est irréversible ! Appuyer sur Terminé pour enregistrer la tâche Voir plus de tâches diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index d38b728dd..2178e368e 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -562,7 +562,6 @@ Activa o uso do mosaico Configuración rápida sen desbloquear o dispositivo Dinámico Continuar sen sincronización - Axúdame a escoller Recordatorio predeterminado Recorrencia personalizada Repítese cada @@ -622,7 +621,6 @@ Subscritores de Google Play Patrocinadores de GitHub Patrocinador - O teu enderezo de correo electrónico e o ID da túa conta serán transmitidos e almacenados por Tasks.org. Esta información usarase para a autenticación e para proporcionarche anuncios importantes relacionados co servizo. Esta información non se compartirá con ninguén. Calquera aplicación que use este contrasinal pechará sesión Usa estas credenciais para configurar unha aplicación de terceiros. Conceden acceso completo á túa conta de Tasks.org, non as escribas nin as compartas con ninguén! Non incluído nas subscricións de \'Prezo flexible\' diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml index 58f92ceaf..f4999503f 100644 --- a/app/src/main/res/values-he/strings.xml +++ b/app/src/main/res/values-he/strings.xml @@ -681,7 +681,6 @@ תתי־משימות מקוננות אינן נתמכות על ידי Microsoft To Do השלמה %s יש תזכורת - כתובת הדוא\"ל ומזהה החשבון שלך ישודרו ויאוחסנו על ידי Tasks.org. מידע זה ישמש עבור אימות וכדי לספק לך הכרזות חשובות הקשורות לשירות. מידע זה לא ישותף עם אף אחד. תזכורות מושבתות בהגדרות Android עלות: חינם יש ללחוץ על מקש Enter כדי להוסיף מעברי שורה @@ -742,7 +741,6 @@ לאחר הערה להמשיך ללא סנכרון - עזור לי לבחור %s יימחק. לא ניתן לבטל זאת! הצג עוד משימות diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index d550f401b..560aa0de8 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -670,7 +670,6 @@ Datum stvaranja Sinkroniziraj s tvojim osobnim Microsoft računom Prijavi se - Tvoja e-mail adresa i ID računa će se poslati i spremiti na Tasks.org . Ovi će se podaci koristiti za provjeru autentifikacije i za slanje važnih najava vezanih uz uslugu. Ovi podaci se neće dijeliti ni s kim. Prihvati Ne sada Standardni podsjetitelj @@ -737,7 +736,6 @@ Komentar Postavke aplikacije Nastavi bez sinkronizacije - Pomogni mi odabrati %s će se izbrisati. Ovo se ne može opozvati! Aplikacija je aktualizirana Aplikacija Tasks je upravo aktualizirana na %s. Želiš li vidjeti bilješke o izdanju? diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 91a1ff598..3f04a06e6 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -652,7 +652,6 @@ Bejelentkezés Beleegyezés Most nem - Az Ön e-mail címét és azonodítóját a Tasks.org tárolja. Ezek az információk azonosításra és fontos, a szolgáltatással kapcsolatos információk eljuttatásának céljából kerülnek felhasználásra. Ez az információ senkivel sem kerül megosztásra. Emlékeztetők engedélyezése Lista szerint Sorbarendezés @@ -718,7 +717,6 @@ A Tasks frissítve lett a(z) %s verzióra. Szeretné látni a változások listáját? A(z) %s törlésre kerül. Ez a művelet nem visszavonható! Folytatás szinkronizálás nélkül - Segítséget kérek a választásban Kattints a Kész gombra a feladat elmentéséhez További feladatok megtekintése diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 6a24674a3..5949ad7f5 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -671,7 +671,6 @@ Sincronizzazione con l\'account personale Microsoft Accedi Accetto - Il tuo indirizzo e-mail e l\'ID dell\'account saranno trasmessi e memorizzati da Tasks.org. Queste informazioni saranno utilizzate per l\'autenticazione e per inviarti annunci importanti relativi al servizio. Queste informazioni non saranno condivise con nessuno. Non ora Promemoria predefinito Mostra le notifiche per le attività senza scadenza @@ -740,7 +739,6 @@ Tasks è stata aggiornata a %s. Vuoi vedere le note di rilascio? Premi il tasto di conferma per salvare l\'attività Continua senza sincronizzazione - Aiutami a scegliere %s verrà cancellato. L\'operazione non può essere annullata! Visualizza ulteriori attività diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 4ff8f9425..166b7668e 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -538,7 +538,6 @@ アプリパスワード 招待を辞退 さらなるカスタマイズ - あなたのメールアドレスとアカウントID は、Tasks.org によって送信および保存されます。この情報は、認証とサービス関連の重要なお知らせを提供するために使用され、誰とも共有されることはありません。 同意する あとで フィールドの並び替えや削除 @@ -706,7 +705,6 @@ タスクを保存するには「完了」を押してください 料金: 無料 同期せずに続ける - 選択を手伝ってください %sは削除されます。これは元に戻せません! 料金: $$$ ほかのタスクを表示 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index d8527b2a1..dad814875 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -611,7 +611,6 @@ 마이크로소프트 계정과 동기화 알람 활성화 필드 순서를 변경하거나 삭제하실 수 있습니다 - 이메일 주소와 계정 ID가 Tasks.org으로 전송되어 보관됩니다. 이 정보는 계정 인증과 서비스 관련 중요 공지를 전달하기 위해 쓰이며, 다른 누구와도 공유하지 않습니다. 완료 효과음 재생 시작 %s 전 마감 %s 전 @@ -705,7 +704,6 @@ 코멘트 코멘트 동기화 없이 계속하기 - 고르는 것을 도와주세요 %s 를 삭제합니다. 삭제 후 되돌릴 수 없습니다! 앱이 업데이트됨 Tasks.org가 방금 %s 버전으로 업데이트되었습니다. 릴리즈 노트를 열까요? diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index 3ebe222a4..51fa525b1 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -640,7 +640,6 @@ Numatytas priminimas Atsitiktinai kas %s Įvykdyta - Jūsų el. pašto adresas ir paskyros ID bus perduodami ir saugomi Tasks.org. Ši informacija bus naudojama autentiškumui patvirtinti ir svarbiems su paslaugomis susijusiems pranešimams teikti. Ši informacija nebus niekam perduodama. Atsisakyti Sutinku Ne dabar diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 55a721b76..7fab48fcb 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -664,7 +664,6 @@ Vis varslinger for oppgaver uten frister Donér i dag Kanskje senere - Din e-postadresse og konto-ID sendes til og lagres av Tasks.org. Denne informasjonen benyttes for autentisering og for å kunne tilby deg viktige tjenesterelaterte kunngjøringer. Denne informasjonen deles ikke med noen. hendelse hendelser diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 470226b5a..99e51870a 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -651,7 +651,6 @@ Log in Akkoord Niet nu - Uw e-mailadres en account-ID worden verzonden naar Tasks.org en opgeslagen. Deze informatie wordt gebruikt voor authenticatie en om u te voorzien van belangrijke servicegerelateerde aankondigingen. Deze informatie wordt met niemand gedeeld. Standaardherinnering Toon meldingen voor taken zonder vervaltijd Op lijstvolgorde @@ -718,7 +717,6 @@ Tasks is zojuist geüpdatet naar %s. Wil je de release-opmerkingen zien? Druk op Gereed om de taak op te slaan Doorgaan zonder synchronisatie - Help me kiezen %s wordt verwijderd. Dit kan niet ongedaan worden gemaakt! Meer taken zien diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 32ace5d35..c782e415e 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -675,7 +675,6 @@ Przypomnienia są wyłączone w Ustawieniach Androida Wyświetlanie powiadomień o zadaniach bez terminów do wykonania Synchronizuj z osobistym kontem Microsoft - Twój adres e-mail i identyfikator konta będą przekazywane i przechowywane przez Tasks.org. Informacje te będą wykorzystywane do uwierzytelniania oraz do przekazywania Ci ważnych ogłoszeń związanych z naszymi usługami. Informacje te nie będą nikomu udostępniane. Zgadzam się Nie teraz Domyślne przypomnienie @@ -717,5 +716,4 @@ Zmień priorytet Dynamiczny Kontynuuj bez synchronizacji - Pomóż mi wybrać diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 881dc155a..1765049ad 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -668,7 +668,6 @@ Mostrar notificações de tarefas sem prazo Concordar Agora não - Seu endereço de e-mail e seu ID da conta serão transmitidos e armazenados pelo Tasks.org. Estas informações serão usadas para autenticação e para fornecer-lhe anúncios importantes relacionados ao serviço. Estas informações não serão compartilhadas com ninguém. Ativar lembretes Sincronize com sua conta pessoal da Microsoft Os lembretes estão desabilitados nas configurações do Android @@ -740,6 +739,5 @@ As tarefas acabaram de ser atualizadas para %s. Gostaria de ver as notas de atualização? Pressione \'Concluir\' para salvar a tarefa Continuar sem sincronizar - Ajude-me a escolher %s será excluído. Isso não pode ser desfeito! diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 6f902a1fe..3bdd1103d 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -648,7 +648,6 @@ Os lembretes estão desativados nas configurações do Android Concluído %s Data de criação - O seu endereço de e-mail e ID de conta serão transmitidos e armazenados pelo Tasks.org. Esta informação será utilizada para autenticação e para lhe fornecer anúncios importantes relacionados com serviços. Esta informação não será partilhada com ninguém. Concordo Agora não Sincronize com a sua conta pessoal Microsoft @@ -719,7 +718,6 @@ Adicionar atalho para a tela inicial Custo: Grátis Enviar logs da aplicação - Ajude-me a escolher Continuar sem sync %s será apagado. Isso não poderá ser desfeito! Ver mais tarefas diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 6da477819..cf727ec2d 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -675,7 +675,6 @@ Afișează notificări pentru sarcinile fără termene limită De acord Nu acum - Adresa de e-mail și ID-ul contului vor fi transmise și stocate de Tasks.org. Aceste informații vor fi utilizate pentru autentificare și pentru a furniza anunțuri importante legate de servicii. Aceste informații nu vor fi partajate cu nimeni. Sincronizarea cu contul personal Microsoft Conectează-te Crescător @@ -743,7 +742,6 @@ Pentru a te asigura că ești notificat la momentul potrivit, acordă permisiunea de a seta alarme și mementouri în Setări Apasă Finalizat pentru a salva sarcina Continuă fără sincronizare - Ajută-mă să aleg %s va fi șters. Acest lucru nu poate fi anulat! Aplicație actualizată Tasks a fost actualizată la versiunea %s. Dorești să vezi modificările făcute? diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 80126c00a..a4da75892 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -673,7 +673,6 @@ Включить напоминания Напоминания отключены в настройках Android Дата создания - Ваш адрес электронной почты и идентификатор учетной записи будут передаваться и храниться на сайте Tasks.org. Эта информация будет использоваться для аутентификации и предоставления вам важных объявлений, связанных с сервисом. Эта информация не будет передана никому. Соглашаюсь Не сейчас Готовность %s @@ -747,7 +746,6 @@ Приложение обновлено Нажмите Done, чтобы сохранить задачу Продолжить без синхронизации - Помогите мне выбрать %s будет удалена. Это нельзя отменить! Просмотреть больше задач diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index e1005b1b3..c3c10a4bb 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -692,7 +692,6 @@ Heslo aplikácie Predplatitelia Google Play Prihlásenie účtom Google - Vaša e-mailová adresa a ID účtu bude prenesená a uložená do Tasks.org. Tieto informácie sa použijú na overenie a pre poskytovanie dôležitých oznámení súvisiacich so službami. Tieto informácie nebudú s nikým zdieľané. Heslá aplikácie Synchronizujte svoje úlohy a kalendáre s desktopovými a mobilnými aplikáciami tretích strán. Pre viac informácii kliknite sem Neplatná pozvánka diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 6785fc313..6d440bcb4 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -366,7 +366,6 @@ Време и датум Додај налог Настави без синхронизације - Помоћ при одабиру Корисник Шифра URL @@ -664,7 +663,6 @@ Изнад просека Усними %d%% Пријави се на Tasks.org - Мејл адреса и идентификациони број ће бити послати и складиштени од стране Tasks.org. Ове информације ће се користити за аутентикацију и обезбеђење важних обавештења у вези сервиса. Ове информације се неће делити ни са ким. Шифра апликације Шифре апликације Синхронизуј задатке и календар са десктоп и мобилним апликацијама треће стране. Тапни овде за додатне информације diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index a012e49a6..f76643434 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -643,7 +643,6 @@ Du kan anpassa den här skärmen genom att ordna om eller ta bort fält Standardpåminnelse Visa aviseringar för uppgifter utan deadlinetid - Din e-postadress och konto-id kommer föras över till och lagras av Tasks.org. Uppgifterna kommer användas för autentisering och för att förse dig med viktiga tjänsterelaterade meddelanden. Informationen kommer inte delas med någon annan. Samtyck Inte nu Påminnelser är avstängda i Android-inställningarna @@ -717,7 +716,6 @@ App uppdaterad Tasks uppdaterades just till %s. Vill du se versionsnyheterna? Tryck på \"klar\" för att spara uppgiften - Hjälp mig att välja %s kommer att tas bort. Detta kan inte ångras! Fortsätt utan synkronisering Visa mer uppgifter diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index ce4176c3b..b795113a7 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -689,7 +689,6 @@ Google உடன் உள்நுழைக %s ஐ tasks.org க்கு நகர்த்தவும் %d %% சேமிக்கவும் - உங்கள் மின்னஞ்சல் முகவரி மற்றும் கணக்கு ஐடி TASKS.org ஆல் அனுப்பப்பட்டு சேமிக்கப்படும். இந்த செய்தி அங்கீகாரத்திற்காகவும், முக்கியமான பணி தொடர்பான அறிவிப்புகளை உங்களுக்கு வழங்கவும் பயன்படுத்தப்படும். இந்த செய்தி யாருடனும் பகிரப்படாது. புதிய கடவுச்சொல்லை உருவாக்குங்கள் உங்கள் கடவுச்சொல்லை ஒரு பெயரைக் கொடுங்கள் (விரும்பினால்) உருவாக்கப்பட்டது: %s @@ -718,6 +717,5 @@ பணியைச் சேமிக்க முடிந்தது என்பதை அழுத்தவும் பணிகள் இப்போதுதான் %s ஆகப் புதுப்பிக்கப்பட்டது. வெளியீட்டுக் குறிப்புகளைப் பார்க்க விரும்புகிறீர்களா? ஒத்திசைவு இல்லாமல் தொடரவும் - தேர்வு செய்ய எனக்கு உதவு %s நீக்கப்படும். இதை செயல்தவிர்க்க முடியாது! diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index d32a4585f..89cd2ec0f 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -651,7 +651,6 @@ Oturum aç Kabul et Şimdi değil - E-posta adresiniz ve hesap kimliğiniz Tasks.org tarafından iletilecek ve saklanacaktır. Bu bilgiler kimlik doğrulama ve hizmetle ilgili önemli duyuruları size sağlamak için kullanılacaktır. Bu bilgiler hiç kimse ile paylaşılmayacaktır. Öntanımlı anımsatıcı Bitiş zamanı olmayan görevler için bildirimleri göster Listeye göre @@ -719,6 +718,5 @@ Tamamlandıya basarak görevi kaydet %s silinecek. Bu geri alınamaz! Eşzamanlamadan sürdür - Seçmeme yardım et Daha çok görev gör diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f9ac54d76..f901c0584 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -678,7 +678,6 @@ Синхронізація з особистим обліковим записом Microsoft Увійти Не зараз - Ваша адреса електронної пошти та ID облікового запису будуть передані та збережені Tasks.org. Ці дані використовуватимуться для автентифікації та надання вам важливих оголошень, пов\'язаних з обслуговуванням. Ці дані нікому не будуть передані. Погоджуюся Показувати сповіщення про завдання без запланованого часу Типове нагадування @@ -748,7 +747,6 @@ Tasks щойно оновлено до %s. Переглянете примітки до випуску? Натисніть готово, щоб зберегти завдання Продовжити без синхронізації - Допоможіть обрати %s буде видалено. Дію неможливо скасувати! Переглянути інші завдання diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 71ba3ca6d..c72f5f846 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -634,7 +634,6 @@ Lời nhắc mặc định Hiện thông báo cho công việc không có hạn chót Đồng bộ với tài khoản Microsoft cá nhân - Địa chỉ email và ID tài khoản của bạn sẽ được Tasks.org truyền đi và giữ lại. Thông tin này sẽ được sử dụng để xác thực và cung cấp các thông báo quan trọng liên quan đến dịch vụ. Thông tin này sẽ không được chia sẻ với ai. Đăng nhập Đồng ý Không phải bây giờ diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 0661c6bc5..386633535 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -639,7 +639,6 @@ 完成 %s 与您的个人 Microsoft 账号同步 登录 - Tasks.org 将传输和存储您的电子邮箱地址和账号 ID。 此信息将用于身份验证并向您提供与服务相关的重要公告。 此信息不会与任何人共享。 同意 再想想 默认提醒 @@ -707,7 +706,6 @@ 应用已更新 按完成保存任务 继续但不同步 - 帮我选择 %s 将会删除。此操作无法撤销! 查看更多任务 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 8b74e965e..b7357db1b 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -546,7 +546,6 @@ 在標題和描述中啟用 Markdown 更多選項 您可以透過重新排列或移除欄位來自訂此畫面 - 您的電子郵件地址和帳戶 ID 將被傳輸並儲存於 Tasks.org。這些資訊將用於驗證和提供重要的服務相關公告。這些資訊不會與任何人分享。 請使用這些憑證來設定第三方應用程式。它們授予您對 Tasks.org 帳戶的完全存取權限,請勿將其寫下或與他人分享! 每次重複 分享清單 diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml index c28c2f2bb..275cb21d5 100644 --- a/app/src/main/res/values/keys.xml +++ b/app/src/main/res/values/keys.xml @@ -38,6 +38,8 @@ https://github.com/tasks/tasks/issues https://tasks.org/changelog https://tasks.org/source + https://tasks.org/license + https://tasks.org/terms https://tasks.org/privacy https://tasks.org/backups https://tasks.org/translations @@ -422,6 +424,8 @@ create_shortcut create_widget onboarding_sync + accept_tos + accept_tos_update type picker_mode_date picker_mode_time @@ -431,4 +435,6 @@ last_sync_time multiline_title dynamic_color + tos_version + 1 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 535b9c811..a8ed45e9a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -411,7 +411,6 @@ File %1$s contained %2$s.\n\n Date and time Add account Continue without sync - Help me choose User Password URL @@ -637,6 +636,7 @@ File %1$s contained %2$s.\n\n Issue tracker Open source Privacy + Legal Authorization cancelled Google Play subscribers GitHub Sponsors @@ -650,7 +650,6 @@ File %1$s contained %2$s.\n\n Above average Save %d%% Sign in to Tasks.org - Your e-mail address and account ID will be transmitted and stored by Tasks.org. This information will be used for authentication and to provide you with important service related announcements. This information will not be shared with anyone. App password App passwords Synchronize your tasks and calendars with third-party desktop and mobile apps. Tap here for more info @@ -705,8 +704,16 @@ File %1$s contained %2$s.\n\n Get notified at the right time To make sure you\'re notified at the right time, grant permission to set alarms and reminders in Settings Sign in - Agree - Not now + By using Tasks.org, you agree to the %1$s and %2$s. Tasks is open source software provided under the %3$s. + Terms of service + Terms of Service + Privacy Policy + GPLv3 License + We\'ve updated our Terms of Service + View Terms of Service + Accept + Exit + Returning user? Sorting Grouping Ascending diff --git a/app/src/main/res/xml/help_and_feedback.xml b/app/src/main/res/xml/help_and_feedback.xml index 100a31dec..e9dd73ee6 100644 --- a/app/src/main/res/xml/help_and_feedback.xml +++ b/app/src/main/res/xml/help_and_feedback.xml @@ -73,12 +73,22 @@ + android:title="@string/legal"> + + + + + + + ) { val collapseTags = booleanPreferencesKey("drawer_collapse_tags") val collapseDebug = booleanPreferencesKey("drawer_collapse_debug") val collapsePlaces = booleanPreferencesKey("drawer_collapse_places") + val acceptedTosVersion = intPreferencesKey("accepted_tos_version") } } \ No newline at end of file