diff --git a/app/src/main/java/org/tasks/auth/SignInActivity.kt b/app/src/main/java/org/tasks/auth/SignInActivity.kt index 6dfdc5441..8a5ea36d0 100644 --- a/app/src/main/java/org/tasks/auth/SignInActivity.kt +++ b/app/src/main/java/org/tasks/auth/SignInActivity.kt @@ -15,36 +15,33 @@ package org.tasks.auth import android.content.Intent import android.os.Bundle -import android.view.View -import android.view.ViewGroup -import android.widget.BaseAdapter -import android.widget.ImageView -import android.widget.TextView +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent 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 +import com.google.android.material.composethemeadapter.MdcTheme import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.launch -import net.openid.appauth.AuthState -import net.openid.appauth.AuthorizationException -import net.openid.appauth.AuthorizationRequest -import net.openid.appauth.AuthorizationServiceConfiguration -import net.openid.appauth.ClientSecretBasic -import net.openid.appauth.RegistrationRequest -import net.openid.appauth.RegistrationResponse -import net.openid.appauth.ResponseTypeValues +import net.openid.appauth.* import org.tasks.R import org.tasks.Tasks.Companion.IS_GENERIC import org.tasks.analytics.Firebase import org.tasks.billing.Inventory import org.tasks.billing.PurchaseActivity import org.tasks.billing.PurchaseActivity.Companion.EXTRA_GITHUB +import org.tasks.compose.ConsentDialog +import org.tasks.compose.SignInDialog import org.tasks.dialogs.DialogBuilder import org.tasks.extensions.Context.openUri -import org.tasks.injection.InjectingAppCompatActivity import org.tasks.themes.ThemeColor import timber.log.Timber import java.util.concurrent.CountDownLatch @@ -63,7 +60,7 @@ import javax.inject.Inject * - Initiate the authorization request using the built-in heuristics or a user-selected browser. */ @AndroidEntryPoint -class SignInActivity : InjectingAppCompatActivity() { +class SignInActivity : ComponentActivity() { @Inject lateinit var themeColor: ThemeColor @Inject lateinit var inventory: Inventory @Inject lateinit var dialogBuilder: DialogBuilder @@ -86,62 +83,52 @@ class SignInActivity : InjectingAppCompatActivity() { private val authStateManager: AuthStateManager get() = authService.authStateManager + enum class Platform { + GOOGLE, + GITHUB, + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.error.observe(this, this::handleError) - val titles = resources.getStringArray(R.array.sign_in_titles) - val summaries = resources.getStringArray(R.array.sign_in_summaries) - val typedArray = resources.obtainTypedArray(R.array.sign_in_icons) - val icons = IntArray(typedArray.length()) - for (i in icons.indices) { - icons[i] = typedArray.getResourceId(i, 0) - } - typedArray.recycle() - val adapter = object : BaseAdapter() { - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view = layoutInflater.inflate(R.layout.simple_list_item_2_themed, null) - val icon = view.findViewById(R.id.image_view) - icon.setImageResource(icons[position]) - view.findViewById(R.id.text2).text = titles[position] - view.findViewById(R.id.text1).text = summaries[position] - if (position == 1) { - icon.drawable.setTint(getColor(R.color.icon_tint)) - } - return view + val autoSelect = intent.getSerializableExtra(EXTRA_SELECT_SERVICE) as Platform? + setContent { + var selectedPlatform by rememberSaveable { + mutableStateOf(autoSelect) } - - override fun getCount() = titles.size - - override fun getItem(position: Int) = titles[position] - - override fun getItemId(position: Int): Long = position.toLong() - } - val autoSelect = intent.getIntExtra(EXTRA_SELECT_SERVICE, -1) - if (autoSelect >= 0 && autoSelect < titles.size) { - selectService(autoSelect) - } else { - dialogBuilder.newDialog() - .setTitle(R.string.sign_in_to_tasks) - .setNeutralButton(R.string.help) { _, _ -> - openUri(R.string.help_url_sync) - finish() + MdcTheme { + selectedPlatform + ?.let { + Dialog(onDismissRequest = { finish() }) { + ConsentDialog { agree -> + if (agree) { + selectService(it) + } else { + finish() + } + } + } } - .setNegativeButton(R.string.cancel) { _, _ -> - finish() + ?: Dialog(onDismissRequest = { finish() }) { + SignInDialog( + selected = { selectedPlatform = it }, + help = { + openUri(R.string.help_url_sync) + finish() + }, + cancel = { finish() } + ) } - .setAdapter(adapter) { _, which -> selectService(which) } - .setOnCancelListener { finish() } - .show() + } } } - private fun selectService(which: Int) { + private fun selectService(which: Platform) { viewModel.initializeAuthService(when (which) { - 0 -> AuthorizationService.ISS_GOOGLE - 1 -> AuthorizationService.ISS_GITHUB - else -> throw IllegalArgumentException() + Platform.GOOGLE -> AuthorizationService.ISS_GOOGLE + Platform.GITHUB -> AuthorizationService.ISS_GITHUB }) startAuthorization() } diff --git a/app/src/main/java/org/tasks/compose/AddAccountDialog.kt b/app/src/main/java/org/tasks/compose/AddAccountDialog.kt new file mode 100644 index 000000000..8ed09422d --- /dev/null +++ b/app/src/main/java/org/tasks/compose/AddAccountDialog.kt @@ -0,0 +1,82 @@ +package org.tasks.compose + +import android.content.res.Configuration +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.ContentAlpha +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import com.google.android.material.composethemeadapter.MdcTheme +import org.tasks.BuildConfig +import org.tasks.R +import org.tasks.sync.AddAccountDialog.Platform + +@Composable +fun AddAccountDialog( + hasTasksAccount: Boolean, + selected: (Platform) -> Unit, +) { + Column(modifier = Modifier.verticalScroll(rememberScrollState())) { + if (!hasTasksAccount) { + SyncAccount( + title = R.string.tasks_org, + description = R.string.tasks_org_description, + icon = R.drawable.ic_round_icon, + onClick = { selected(Platform.TASKS_ORG) } + ) + } + SyncAccount( + title = R.string.gtasks_GPr_header, + description = R.string.google_tasks_selection_description, + icon = R.drawable.ic_google, + onClick = { selected(Platform.GOOGLE_TASKS) } + ) + if (BuildConfig.DEBUG) { + SyncAccount( + title = R.string.microsoft, + description = R.string.microsoft_selection_description, + icon = R.drawable.ic_microsoft_tasks, + onClick = { selected(Platform.MICROSOFT) } + ) + } + SyncAccount( + title = R.string.davx5, + description = R.string.davx5_selection_description, + icon = R.drawable.ic_davx5_icon_green_bg, + onClick = { selected(Platform.DAVX5) } + ) + SyncAccount( + title = R.string.caldav, + description = R.string.caldav_selection_description, + icon = R.drawable.ic_webdav_logo, + tint = MaterialTheme.colors.onSurface.copy( + alpha = ContentAlpha.medium + ), + onClick = { selected(Platform.CALDAV) } + ) + SyncAccount( + title = R.string.etesync, + description = R.string.etesync_selection_description, + icon = R.drawable.ic_etesync, + onClick = { selected(Platform.ETESYNC) } + ) + SyncAccount( + title = R.string.decsync, + description = R.string.decsync_selection_description, + icon = R.drawable.ic_decsync, + onClick = { selected(Platform.DECSYNC_CC) } + ) + } +} + +@Preview(showBackground = true, widthDp = 320) +@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun AddAccountDialogPreview() { + MdcTheme { + AddAccountDialog(hasTasksAccount = false, selected = {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/compose/SignInDialog.kt b/app/src/main/java/org/tasks/compose/SignInDialog.kt new file mode 100644 index 000000000..3f9aaa0ab --- /dev/null +++ b/app/src/main/java/org/tasks/compose/SignInDialog.kt @@ -0,0 +1,114 @@ +package org.tasks.compose + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.ContentAlpha +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.google.android.material.composethemeadapter.MdcTheme +import org.tasks.R +import org.tasks.auth.SignInActivity + +@Composable +fun SignInDialog( + selected: (SignInActivity.Platform) -> Unit, + help: () -> Unit, + cancel: () -> Unit, +) { + Column( + modifier = Modifier + .verticalScroll(rememberScrollState()) + .background(MaterialTheme.colors.surface) + ) { + Text( + text = stringResource(id = R.string.sign_in_to_tasks), + style = MaterialTheme.typography.h6, + modifier = Modifier.padding(16.dp), + ) + SyncAccount( + title = R.string.sign_in_with_google, + description = R.string.google_play_subscribers, + icon = R.drawable.ic_google, + onClick = { selected(SignInActivity.Platform.GOOGLE) } + ) + SyncAccount( + title = R.string.sign_in_with_github, + description = R.string.github_sponsors, + icon = R.drawable.ic_octocat, + tint = MaterialTheme.colors.onSurface.copy( + alpha = ContentAlpha.medium + ), + onClick = { selected(SignInActivity.Platform.GITHUB) } + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(16.dp) + ) { + TextButton(onClick = help) { + Text(text = stringResource(id = R.string.help)) + } + Spacer(modifier = Modifier.weight(1f)) + TextButton(onClick = cancel) { + Text(text = stringResource(id = R.string.cancel)) + } + } + } +} + +@Composable +fun ConsentDialog( + agree: (Boolean) -> Unit, +) { + Column(Modifier.background(MaterialTheme.colors.surface)) { + Text( + text = stringResource(id = R.string.sign_in_to_tasks), + style = MaterialTheme.typography.h6, + modifier = Modifier.padding(16.dp), + ) + Text( + text = stringResource(id = R.string.sign_in_to_tasks_disclosure), + modifier = Modifier.padding(horizontal = 16.dp), + ) + Row( + horizontalArrangement = Arrangement.End, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) { + TextButton(onClick = { agree(false) }) { + Text(text = stringResource(id = R.string.consent_deny)) + } + TextButton(onClick = { agree(true) }) { + Text(text = stringResource(id = R.string.consent_agree)) + } + } + } +} + +@Preview(widthDp = 320) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun SignInDialogPreview() { + MdcTheme { + SignInDialog(selected = {}, help = {}, cancel = {}) + } +} + +@Preview(widthDp = 320) +@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 320) +@Composable +fun DisclosurePreview() { + MdcTheme { + ConsentDialog(agree = {}) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/tasks/sync/AddAccountDialog.kt b/app/src/main/java/org/tasks/sync/AddAccountDialog.kt index 9aa725d92..49bb5d6ca 100644 --- a/app/src/main/java/org/tasks/sync/AddAccountDialog.kt +++ b/app/src/main/java/org/tasks/sync/AddAccountDialog.kt @@ -2,20 +2,12 @@ package org.tasks.sync import android.app.Dialog import android.os.Bundle -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ContentAlpha -import androidx.compose.material.MaterialTheme -import androidx.compose.ui.Modifier import androidx.core.os.bundleOf import androidx.fragment.app.DialogFragment import androidx.fragment.app.setFragmentResult import com.google.android.material.composethemeadapter.MdcTheme import dagger.hilt.android.AndroidEntryPoint -import org.tasks.BuildConfig import org.tasks.R -import org.tasks.compose.SyncAccount import org.tasks.dialogs.DialogBuilder import org.tasks.extensions.Context.openUri import javax.inject.Inject @@ -44,57 +36,10 @@ class AddAccountDialog : DialogFragment() { .setTitle(R.string.choose_synchronization_service) .setContent { MdcTheme { - Column(modifier = Modifier.verticalScroll(rememberScrollState())) { - if (!hasTasksAccount) { - SyncAccount( - title = R.string.tasks_org, - description = R.string.tasks_org_description, - icon = R.drawable.ic_round_icon, - onClick = { selected(Platform.TASKS_ORG) } - ) - } - SyncAccount( - title = R.string.gtasks_GPr_header, - description = R.string.google_tasks_selection_description, - icon = R.drawable.ic_google, - onClick = { selected(Platform.GOOGLE_TASKS) } - ) - if (BuildConfig.DEBUG) { - SyncAccount( - title = R.string.microsoft, - description = R.string.microsoft_selection_description, - icon = R.drawable.ic_microsoft_tasks, - onClick = { selected(Platform.MICROSOFT) } - ) - } - SyncAccount( - title = R.string.davx5, - description = R.string.davx5_selection_description, - icon = R.drawable.ic_davx5_icon_green_bg, - onClick = { selected(Platform.DAVX5) } - ) - SyncAccount( - title = R.string.caldav, - description = R.string.caldav_selection_description, - icon = R.drawable.ic_webdav_logo, - tint = MaterialTheme.colors.onSurface.copy( - alpha = ContentAlpha.medium - ), - onClick = { selected(Platform.CALDAV) } - ) - SyncAccount( - title = R.string.etesync, - description = R.string.etesync_selection_description, - icon = R.drawable.ic_etesync, - onClick = { selected(Platform.ETESYNC) } - ) - SyncAccount( - title = R.string.decsync, - description = R.string.decsync_selection_description, - icon = R.drawable.ic_decsync, - onClick = { selected(Platform.DECSYNC_CC) } - ) - } + org.tasks.compose.AddAccountDialog( + hasTasksAccount = hasTasksAccount, + selected = this::selected + ) } } .setNeutralButton(R.string.help) { _, _ -> activity?.openUri(R.string.help_url_sync) } diff --git a/app/src/main/res/layout/simple_list_item_2_themed.xml b/app/src/main/res/layout/simple_list_item_2_themed.xml deleted file mode 100644 index 15c528e9d..000000000 --- a/app/src/main/res/layout/simple_list_item_2_themed.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index c7b6ccec5..7c1e02598 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -138,21 +138,6 @@ 2 - - @string/google_play_subscribers - @string/github_sponsors - - - - @string/sign_in_with_google - @string/sign_in_with_github - - - - @drawable/ic_google - @drawable/ic_octocat - - @string/map_theme_use_app_theme @string/theme_light diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8870f5a4d..13453496f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -669,6 +669,7 @@ 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 @@ -736,4 +737,6 @@ File %1$s contained %2$s.\n\n Enable reminders Reminders are disabled in Android Settings Sign in + Agree + Not now