Merge pull request #6096 from k9mail/sign_in_with_google

Add "Sign in with Google" button
This commit is contained in:
cketti 2022-06-05 15:57:43 +02:00 committed by GitHub
commit 3581a0239c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 220 additions and 18 deletions

View file

@ -302,6 +302,10 @@
</intent-filter>
</activity>
<activity
android:name=".activity.setup.OAuthFlowActivity"
android:label="@string/account_setup_basics_title" />
<receiver
android:name=".provider.UnreadWidgetProvider"
android:icon="@drawable/ic_launcher"

View file

@ -1,5 +1,6 @@
package com.fsck.k9.activity.setup
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
@ -193,12 +194,19 @@ class AccountSetupBasics : K9Activity() {
connectionSettings.incoming.authenticationType == AuthType.XOAUTH2 &&
connectionSettings.outgoing.authenticationType == AuthType.XOAUTH2
) {
finishAutoSetup(connectionSettings)
startOAuthFlow(connectionSettings)
} else {
startPasswordFlow()
}
}
private fun startOAuthFlow(connectionSettings: ConnectionSettings) {
val account = createAccount(connectionSettings)
val intent = OAuthFlowActivity.buildLaunchIntent(this, account.uuid)
startActivityForResult(intent, REQUEST_CODE_OAUTH)
}
private fun startPasswordFlow() {
uiState = UiState.PASSWORD_FLOW
@ -233,6 +241,13 @@ class AccountSetupBasics : K9Activity() {
}
private fun finishAutoSetup(connectionSettings: ConnectionSettings) {
val account = createAccount(connectionSettings)
// Check incoming here. Then check outgoing in onActivityResult()
AccountSetupCheckSettings.actionCheckSettings(this, account, CheckDirection.INCOMING)
}
private fun createAccount(connectionSettings: ConnectionSettings): Account {
val email = emailView.text?.toString() ?: error("Email missing")
val password = passwordView.text?.toString()
@ -248,8 +263,7 @@ class AccountSetupBasics : K9Activity() {
localFoldersCreator.createSpecialLocalFolders(account)
// Check incoming here. Then check outgoing in onActivityResult()
AccountSetupCheckSettings.actionCheckSettings(this, account, CheckDirection.INCOMING)
return account
}
private fun onManualSetup() {
@ -309,28 +323,39 @@ class AccountSetupBasics : K9Activity() {
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode != AccountSetupCheckSettings.ACTIVITY_REQUEST_CODE) {
super.onActivityResult(requestCode, resultCode, data)
return
when (requestCode) {
REQUEST_CODE_CHECK_SETTINGS -> handleCheckSettingsResult(resultCode)
REQUEST_CODE_OAUTH -> handleSignInResult(resultCode)
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
if (resultCode == RESULT_OK) {
val account = this.account ?: error("Account instance missing")
private fun handleCheckSettingsResult(resultCode: Int) {
if (resultCode != RESULT_OK) return
if (!checkedIncoming) {
// We've successfully checked incoming. Now check outgoing.
checkedIncoming = true
AccountSetupCheckSettings.actionCheckSettings(this, account, CheckDirection.OUTGOING)
} else {
// We've successfully checked outgoing as well.
preferences.saveAccount(account)
Core.setServicesEnabled(applicationContext)
val account = this.account ?: error("Account instance missing")
AccountSetupNames.actionSetNames(this, account)
}
if (!checkedIncoming) {
// We've successfully checked incoming. Now check outgoing.
checkedIncoming = true
AccountSetupCheckSettings.actionCheckSettings(this, account, CheckDirection.OUTGOING)
} else {
// We've successfully checked outgoing as well.
preferences.saveAccount(account)
Core.setServicesEnabled(applicationContext)
AccountSetupNames.actionSetNames(this, account)
}
}
private fun handleSignInResult(resultCode: Int) {
if (resultCode != RESULT_OK) return
val account = this.account ?: error("Account instance missing")
AccountSetupCheckSettings.actionCheckSettings(this, account, CheckDirection.INCOMING)
}
private enum class UiState {
EMAIL_ADDRESS_ONLY,
PASSWORD_FLOW
@ -340,6 +365,8 @@ class AccountSetupBasics : K9Activity() {
private const val EXTRA_ACCOUNT = "com.fsck.k9.AccountSetupBasics.account"
private const val STATE_KEY_UI_STATE = "com.fsck.k9.AccountSetupBasics.uiState"
private const val STATE_KEY_CHECKED_INCOMING = "com.fsck.k9.AccountSetupBasics.checkedIncoming"
private const val REQUEST_CODE_CHECK_SETTINGS = AccountSetupCheckSettings.ACTIVITY_REQUEST_CODE
private const val REQUEST_CODE_OAUTH = Activity.RESULT_FIRST_USER + 1
@JvmStatic
fun actionNewAccount(context: Context) {

View file

@ -69,6 +69,11 @@ class AuthViewModel(
return authState.isAuthorized
}
fun isUsingGoogle(account: Account): Boolean {
val config = findOAuthConfiguration(account)
return config?.authorizationEndpoint == "https://accounts.google.com/o/oauth2/v2/auth"
}
private fun getOrCreateAuthState(account: Account): AuthState {
return try {
account.oAuthState?.let { AuthState.jsonDeserialize(it) } ?: AuthState()

View file

@ -0,0 +1,89 @@
package com.fsck.k9.activity.setup
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.core.view.isVisible
import com.fsck.k9.Account
import com.fsck.k9.preferences.AccountManager
import com.fsck.k9.ui.R
import com.fsck.k9.ui.base.K9Activity
import com.fsck.k9.ui.observe
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
class OAuthFlowActivity : K9Activity() {
private val authViewModel: AuthViewModel by viewModel()
private val accountManager: AccountManager by inject()
private lateinit var errorText: TextView
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setLayout(R.layout.account_setup_oauth)
setTitle(R.string.account_setup_basics_title)
val accountUUid = intent.getStringExtra(EXTRA_ACCOUNT_UUID) ?: error("Missing account UUID")
val account = accountManager.getAccount(accountUUid) ?: error("Account not found")
errorText = findViewById(R.id.error_text)
val signInButton: View = if (authViewModel.isUsingGoogle(account)) {
findViewById(R.id.google_sign_in_button)
} else {
findViewById(R.id.oauth_sign_in_button)
}
signInButton.isVisible = true
signInButton.setOnClickListener { startOAuthFlow(account) }
authViewModel.init(activityResultRegistry, lifecycle)
authViewModel.uiState.observe(this) { state ->
handleUiUpdates(state)
}
}
private fun handleUiUpdates(state: AuthFlowState) {
when (state) {
AuthFlowState.Idle -> {
return
}
AuthFlowState.Success -> {
setResult(RESULT_OK)
finish()
}
AuthFlowState.Canceled -> {
errorText.text = getString(R.string.account_setup_failed_dlg_oauth_flow_canceled)
}
is AuthFlowState.Failed -> {
errorText.text = getString(R.string.account_setup_failed_dlg_oauth_flow_failed, state)
}
AuthFlowState.NotSupported -> {
errorText.text = getString(R.string.account_setup_failed_dlg_oauth_not_supported)
}
AuthFlowState.BrowserNotFound -> {
errorText.text = getString(R.string.account_setup_failed_dlg_browser_not_found)
}
}
authViewModel.authResultConsumed()
}
private fun startOAuthFlow(account: Account) {
errorText.text = ""
authViewModel.login(account)
}
companion object {
private const val EXTRA_ACCOUNT_UUID = "accountUuid"
fun buildLaunchIntent(context: Context, accountUuid: String): Intent {
return Intent(context, OAuthFlowActivity::class.java).apply {
putExtra(EXTRA_ACCOUNT_UUID, accountUuid)
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 949 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" android:drawable="@drawable/btn_google_signin_dark_disabled" />
<item android:state_pressed="true" android:drawable="@drawable/btn_google_signin_dark_pressed" />
<item android:state_focused="true" android:drawable="@drawable/btn_google_signin_dark_focus" />
<item android:drawable="@drawable/btn_google_signin_dark_normal" />
</selector>

View file

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.fsck.k9.activity.setup.OAuthFlowActivity">
<include layout="@layout/toolbar" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fadingEdge="none"
android:padding="16dp"
android:scrollbarStyle="outsideInset">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_horizontal"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="24dp"
android:text="@string/account_setup_oauth_description"
android:textAppearance="@style/TextAppearance.AppCompat.Body1" />
<Button
android:id="@+id/oauth_sign_in_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/account_setup_oauth_sign_in"
android:visibility="gone" />
<Button
android:id="@+id/google_sign_in_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/btn_google_signin_dark"
android:text="@string/account_setup_oauth_sign_in_with_google"
android:textAllCaps="false"
android:textColor="#ffffff"
android:textSize="14sp"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/error_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
tools:text="@string/account_setup_failed_dlg_browser_not_found" />
</LinearLayout>
</ScrollView>
</LinearLayout>

View file

@ -329,6 +329,12 @@ Please submit bug reports, contribute new features and ask questions at
<string name="account_setup_basics_email_hint">Email address</string>
<string name="account_setup_basics_password_hint">Password</string>
<string name="account_setup_oauth_description">To use this email account with K-9 Mail, you need to sign in and grant the app access to your emails.</string>
<!-- Displayed below the 'account_setup_oauth_description' text -->
<string name="account_setup_oauth_sign_in">Sign in</string>
<!-- Displayed below the 'account_setup_oauth_description' text -->
<string name="account_setup_oauth_sign_in_with_google">Sign in with Google</string>
<!-- Please use the same translation for "screen lock" as is used in the 'Security' section in Android's settings app -->
<string name="account_setup_basics_show_password_need_lock">To view your password here, enable screen lock on this device.</string>
<string name="account_setup_basics_show_password_biometrics_title">Verify your identity</string>