Add e-mail disclosure dialog

pull/2004/head
Alex Baker 2 years ago
parent 29519c24cc
commit f68ef7cbe5

@ -15,36 +15,33 @@ package org.tasks.auth
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import androidx.activity.ComponentActivity
import android.view.ViewGroup import androidx.activity.compose.setContent
import android.widget.BaseAdapter
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.browser.customtabs.CustomTabsIntent 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 androidx.lifecycle.lifecycleScope
import at.bitfire.dav4jvm.exception.HttpException import at.bitfire.dav4jvm.exception.HttpException
import com.google.android.material.composethemeadapter.MdcTheme
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.openid.appauth.AuthState import net.openid.appauth.*
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 org.tasks.R import org.tasks.R
import org.tasks.Tasks.Companion.IS_GENERIC import org.tasks.Tasks.Companion.IS_GENERIC
import org.tasks.analytics.Firebase import org.tasks.analytics.Firebase
import org.tasks.billing.Inventory import org.tasks.billing.Inventory
import org.tasks.billing.PurchaseActivity import org.tasks.billing.PurchaseActivity
import org.tasks.billing.PurchaseActivity.Companion.EXTRA_GITHUB 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.dialogs.DialogBuilder
import org.tasks.extensions.Context.openUri import org.tasks.extensions.Context.openUri
import org.tasks.injection.InjectingAppCompatActivity
import org.tasks.themes.ThemeColor import org.tasks.themes.ThemeColor
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.CountDownLatch 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. * - Initiate the authorization request using the built-in heuristics or a user-selected browser.
*/ */
@AndroidEntryPoint @AndroidEntryPoint
class SignInActivity : InjectingAppCompatActivity() { class SignInActivity : ComponentActivity() {
@Inject lateinit var themeColor: ThemeColor @Inject lateinit var themeColor: ThemeColor
@Inject lateinit var inventory: Inventory @Inject lateinit var inventory: Inventory
@Inject lateinit var dialogBuilder: DialogBuilder @Inject lateinit var dialogBuilder: DialogBuilder
@ -86,62 +83,52 @@ class SignInActivity : InjectingAppCompatActivity() {
private val authStateManager: AuthStateManager private val authStateManager: AuthStateManager
get() = authService.authStateManager get() = authService.authStateManager
enum class Platform {
GOOGLE,
GITHUB,
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
viewModel.error.observe(this, this::handleError) viewModel.error.observe(this, this::handleError)
val titles = resources.getStringArray(R.array.sign_in_titles) val autoSelect = intent.getSerializableExtra(EXTRA_SELECT_SERVICE) as Platform?
val summaries = resources.getStringArray(R.array.sign_in_summaries) setContent {
val typedArray = resources.obtainTypedArray(R.array.sign_in_icons) var selectedPlatform by rememberSaveable {
val icons = IntArray(typedArray.length()) mutableStateOf(autoSelect)
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<ImageView>(R.id.image_view)
icon.setImageResource(icons[position])
view.findViewById<TextView>(R.id.text2).text = titles[position]
view.findViewById<TextView>(R.id.text1).text = summaries[position]
if (position == 1) {
icon.drawable.setTint(getColor(R.color.icon_tint))
}
return view
} }
MdcTheme {
override fun getCount() = titles.size selectedPlatform
?.let {
override fun getItem(position: Int) = titles[position] Dialog(onDismissRequest = { finish() }) {
ConsentDialog { agree ->
override fun getItemId(position: Int): Long = position.toLong() if (agree) {
} selectService(it)
val autoSelect = intent.getIntExtra(EXTRA_SELECT_SERVICE, -1) } else {
if (autoSelect >= 0 && autoSelect < titles.size) { finish()
selectService(autoSelect) }
} else { }
dialogBuilder.newDialog() }
.setTitle(R.string.sign_in_to_tasks)
.setNeutralButton(R.string.help) { _, _ ->
openUri(R.string.help_url_sync)
finish()
} }
.setNegativeButton(R.string.cancel) { _, _ -> ?: Dialog(onDismissRequest = { finish() }) {
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) { viewModel.initializeAuthService(when (which) {
0 -> AuthorizationService.ISS_GOOGLE Platform.GOOGLE -> AuthorizationService.ISS_GOOGLE
1 -> AuthorizationService.ISS_GITHUB Platform.GITHUB -> AuthorizationService.ISS_GITHUB
else -> throw IllegalArgumentException()
}) })
startAuthorization() startAuthorization()
} }

@ -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 = {})
}
}

@ -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 = {})
}
}

@ -2,20 +2,12 @@ package org.tasks.sync
import android.app.Dialog import android.app.Dialog
import android.os.Bundle 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.core.os.bundleOf
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.fragment.app.setFragmentResult import androidx.fragment.app.setFragmentResult
import com.google.android.material.composethemeadapter.MdcTheme import com.google.android.material.composethemeadapter.MdcTheme
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.tasks.BuildConfig
import org.tasks.R import org.tasks.R
import org.tasks.compose.SyncAccount
import org.tasks.dialogs.DialogBuilder import org.tasks.dialogs.DialogBuilder
import org.tasks.extensions.Context.openUri import org.tasks.extensions.Context.openUri
import javax.inject.Inject import javax.inject.Inject
@ -44,57 +36,10 @@ class AddAccountDialog : DialogFragment() {
.setTitle(R.string.choose_synchronization_service) .setTitle(R.string.choose_synchronization_service)
.setContent { .setContent {
MdcTheme { MdcTheme {
Column(modifier = Modifier.verticalScroll(rememberScrollState())) { org.tasks.compose.AddAccountDialog(
if (!hasTasksAccount) { hasTasksAccount = hasTasksAccount,
SyncAccount( selected = this::selected
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) }
)
}
} }
} }
.setNeutralButton(R.string.help) { _, _ -> activity?.openUri(R.string.help_url_sync) } .setNeutralButton(R.string.help) { _, _ -> activity?.openUri(R.string.help_url_sync) }

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TwoLineListItem xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:minHeight="?attr/listPreferredItemHeight"
android:mode="twoLine"
android:paddingStart="@dimen/keyline_first"
android:paddingTop="@dimen/list_item_spacing"
android:paddingEnd="@dimen/keyline_first"
android:paddingBottom="@dimen/list_item_spacing">
<ImageView
android:id="@+id/image_view"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_alignParentStart="true"
android:layout_marginEnd="@dimen/keyline_first"
app:tint="@null" />
<TextView
android:id="@id/text1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_toEndOf="@id/image_view"
android:textAppearance="?attr/textAppearanceListItem" />
<TextView
android:id="@id/text2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/text1"
android:layout_alignStart="@id/text1"
android:textAppearance="?attr/textAppearanceListItemSecondary" />
</TwoLineListItem>

@ -138,21 +138,6 @@
<item>2</item> <item>2</item>
</string-array> </string-array>
<string-array name="sign_in_titles">
<item>@string/google_play_subscribers</item>
<item>@string/github_sponsors</item>
</string-array>
<string-array name="sign_in_summaries">
<item>@string/sign_in_with_google</item>
<item>@string/sign_in_with_github</item>
</string-array>
<array name="sign_in_icons">
<item>@drawable/ic_google</item>
<item>@drawable/ic_octocat</item>
</array>
<string-array name="map_theme_names"> <string-array name="map_theme_names">
<item>@string/map_theme_use_app_theme</item> <item>@string/map_theme_use_app_theme</item>
<item>@string/theme_light</item> <item>@string/theme_light</item>

@ -669,6 +669,7 @@ File %1$s contained %2$s.\n\n
<string name="above_average">Above average</string> <string name="above_average">Above average</string>
<string name="save_percent">Save %d%%</string> <string name="save_percent">Save %d%%</string>
<string name="sign_in_to_tasks">Sign in to Tasks.org</string> <string name="sign_in_to_tasks">Sign in to Tasks.org</string>
<string name="sign_in_to_tasks_disclosure">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.</string>
<string name="app_password">App password</string> <string name="app_password">App password</string>
<string name="app_passwords">App passwords</string> <string name="app_passwords">App passwords</string>
<string name="app_passwords_more_info">Synchronize your tasks and calendars with third-party desktop and mobile apps. Tap here for more info</string> <string name="app_passwords_more_info">Synchronize your tasks and calendars with third-party desktop and mobile apps. Tap here for more info</string>
@ -736,4 +737,6 @@ File %1$s contained %2$s.\n\n
<string name="enable_reminders">Enable reminders</string> <string name="enable_reminders">Enable reminders</string>
<string name="enable_reminders_description">Reminders are disabled in Android Settings</string> <string name="enable_reminders_description">Reminders are disabled in Android Settings</string>
<string name="sign_in">Sign in</string> <string name="sign_in">Sign in</string>
<string name="consent_agree">Agree</string>
<string name="consent_deny">Not now</string>
</resources> </resources>

Loading…
Cancel
Save