Add CheckIsGoogleSignIn and use GoogleSignInButton
This commit is contained in:
parent
615e16895f
commit
35020a1cb9
11 changed files with 245 additions and 85 deletions
|
@ -1,5 +1,6 @@
|
|||
package com.fsck.k9.activity.setup
|
||||
|
||||
@Deprecated("Could be removed with the legacy account setup UI")
|
||||
object GoogleOAuthHelper {
|
||||
fun isGoogle(hostname: String): Boolean {
|
||||
return hostname.lowercase().endsWith(".gmail.com") ||
|
||||
|
|
|
@ -5,6 +5,7 @@ import app.k9mail.feature.account.oauth.data.AuthorizationRepository
|
|||
import app.k9mail.feature.account.oauth.data.AuthorizationStateRepository
|
||||
import app.k9mail.feature.account.oauth.domain.DomainContract
|
||||
import app.k9mail.feature.account.oauth.domain.DomainContract.UseCase
|
||||
import app.k9mail.feature.account.oauth.domain.usecase.CheckIsGoogleSignIn
|
||||
import app.k9mail.feature.account.oauth.domain.usecase.FinishOAuthSignIn
|
||||
import app.k9mail.feature.account.oauth.domain.usecase.GetOAuthRequestIntent
|
||||
import app.k9mail.feature.account.oauth.domain.usecase.SuggestServerName
|
||||
|
@ -45,10 +46,13 @@ val featureAccountOAuthModule: Module = module {
|
|||
|
||||
factory<UseCase.FinishOAuthSignIn> { FinishOAuthSignIn(repository = get()) }
|
||||
|
||||
factory<UseCase.CheckIsGoogleSignIn> { CheckIsGoogleSignIn() }
|
||||
|
||||
viewModel {
|
||||
AccountOAuthViewModel(
|
||||
getOAuthRequestIntent = get(),
|
||||
finishOAuthSignIn = get(),
|
||||
checkIsGoogleSignIn = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,10 @@ interface DomainContract {
|
|||
fun interface CheckIsAuthorized {
|
||||
suspend fun execute(authorizationState: AuthorizationState): Boolean
|
||||
}
|
||||
|
||||
fun interface CheckIsGoogleSignIn {
|
||||
fun execute(hostname: String): Boolean
|
||||
}
|
||||
}
|
||||
|
||||
interface AuthorizationRepository {
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package app.k9mail.feature.account.oauth.domain.usecase
|
||||
|
||||
import app.k9mail.feature.account.oauth.domain.DomainContract.UseCase
|
||||
|
||||
internal class CheckIsGoogleSignIn : UseCase.CheckIsGoogleSignIn {
|
||||
override fun execute(hostname: String): Boolean {
|
||||
for (domain in domainList) {
|
||||
if (hostname.lowercase().endsWith(domain)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val domainList = listOf(
|
||||
".gmail.com",
|
||||
".googlemail.com",
|
||||
".google.com",
|
||||
)
|
||||
}
|
||||
}
|
|
@ -55,6 +55,7 @@ internal fun AccountOAuthContent(
|
|||
SignInItem(
|
||||
emailAddress = state.emailAddress,
|
||||
onSignInClick = { onEvent(Event.SignInClicked) },
|
||||
isGoogleSignIn = state.isGoogleSignIn,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,6 @@ import app.k9mail.core.ui.compose.common.mvi.observe
|
|||
import app.k9mail.core.ui.compose.designsystem.template.Scaffold
|
||||
import app.k9mail.feature.account.common.ui.AppTitleTopHeader
|
||||
import app.k9mail.feature.account.common.ui.WizardNavigationBar
|
||||
import app.k9mail.feature.account.common.ui.WizardNavigationBarState
|
||||
import app.k9mail.feature.account.oauth.R
|
||||
import app.k9mail.feature.account.oauth.domain.entity.OAuthResult
|
||||
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.Effect
|
||||
|
|
|
@ -18,11 +18,16 @@ class AccountOAuthViewModel(
|
|||
initialState: State = State(),
|
||||
private val getOAuthRequestIntent: UseCase.GetOAuthRequestIntent,
|
||||
private val finishOAuthSignIn: UseCase.FinishOAuthSignIn,
|
||||
private val checkIsGoogleSignIn: UseCase.CheckIsGoogleSignIn,
|
||||
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
|
||||
|
||||
override fun initState(state: State) {
|
||||
val isGoogleSignIn = checkIsGoogleSignIn.execute(state.hostname)
|
||||
|
||||
updateState {
|
||||
state.copy()
|
||||
state.copy(
|
||||
isGoogleSignIn = isGoogleSignIn,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ import app.k9mail.feature.account.oauth.ui.view.SignInView
|
|||
internal fun LazyItemScope.SignInItem(
|
||||
emailAddress: String,
|
||||
onSignInClick: () -> Unit,
|
||||
isGoogleSignIn: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ListItem(
|
||||
|
@ -18,6 +19,7 @@ internal fun LazyItemScope.SignInItem(
|
|||
SignInView(
|
||||
emailAddress = emailAddress,
|
||||
onSignInClick = onSignInClick,
|
||||
isGoogleSignIn = isGoogleSignIn,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import app.k9mail.core.ui.compose.common.DevicePreviews
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.button.Button
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextCaption
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextSubtitle1
|
||||
|
@ -18,6 +19,7 @@ import app.k9mail.feature.account.oauth.R
|
|||
internal fun SignInView(
|
||||
emailAddress: String,
|
||||
onSignInClick: () -> Unit,
|
||||
isGoogleSignIn: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
@ -38,9 +40,35 @@ internal fun SignInView(
|
|||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
Button(
|
||||
text = stringResource(id = R.string.account_oauth_sign_in_button),
|
||||
onClick = onSignInClick,
|
||||
)
|
||||
if (isGoogleSignIn) {
|
||||
SignInWithGoogleButton(
|
||||
onClick = onSignInClick,
|
||||
)
|
||||
} else {
|
||||
Button(
|
||||
text = stringResource(id = R.string.account_oauth_sign_in_button),
|
||||
onClick = onSignInClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DevicePreviews
|
||||
@Composable
|
||||
internal fun SignInViewPreview() {
|
||||
SignInView(
|
||||
emailAddress = "test@example.com",
|
||||
onSignInClick = {},
|
||||
isGoogleSignIn = false,
|
||||
)
|
||||
}
|
||||
|
||||
@DevicePreviews
|
||||
@Composable
|
||||
internal fun SignInViewWithGooglePreview() {
|
||||
SignInView(
|
||||
emailAddress = "test@gmail.com",
|
||||
onSignInClick = {},
|
||||
isGoogleSignIn = true,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package app.k9mail.feature.account.oauth.domain.usecase
|
||||
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isFalse
|
||||
import assertk.assertions.isTrue
|
||||
import org.junit.Test
|
||||
|
||||
class CheckIsGoogleSignInTest {
|
||||
|
||||
private val testSubject = CheckIsGoogleSignIn()
|
||||
|
||||
@Test
|
||||
fun `should return true when hostname ends with a google domain`() {
|
||||
val hostnames = listOf(
|
||||
"mail.gmail.com",
|
||||
"mail.googlemail.com",
|
||||
"mail.google.com",
|
||||
)
|
||||
|
||||
for (hostname in hostnames) {
|
||||
assertThat(testSubject.execute(hostname)).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return false when hostname does not end with a google domain`() {
|
||||
val hostnames = listOf(
|
||||
"mail.example.com",
|
||||
"mail.example.org",
|
||||
"mail.example.net",
|
||||
)
|
||||
|
||||
for (hostname in hostnames) {
|
||||
assertThat(testSubject.execute(hostname)).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -27,6 +27,60 @@ class AccountOAuthViewModelTest {
|
|||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
@Test
|
||||
fun `should change state when google hostname found on initState`() = runTest {
|
||||
val testSubject = createTestSubject(
|
||||
isGoogleSignIn = true,
|
||||
)
|
||||
|
||||
val stateTurbine = testSubject.state.testIn(backgroundScope)
|
||||
val effectTurbine = testSubject.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(State())
|
||||
}
|
||||
|
||||
testSubject.initState(defaultState)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(defaultState.copy(isGoogleSignIn = true))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not change state when no google hostname found on initState`() = runTest {
|
||||
val testSubject = createTestSubject(
|
||||
isGoogleSignIn = false,
|
||||
)
|
||||
|
||||
val stateTurbine = testSubject.state.testIn(backgroundScope)
|
||||
val effectTurbine = testSubject.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(State())
|
||||
}
|
||||
|
||||
testSubject.initState(defaultState)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(defaultState.copy(isGoogleSignIn = false))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should launch OAuth when SignInClicked event received`() = runTest {
|
||||
val initialState = defaultState
|
||||
|
@ -118,7 +172,7 @@ class AccountOAuthViewModelTest {
|
|||
val authorizationState = AuthorizationState(state = "state")
|
||||
val testSubject = createTestSubject(
|
||||
authorizationResult = AuthorizationResult.Success(authorizationState),
|
||||
initialState = initialState
|
||||
initialState = initialState,
|
||||
)
|
||||
val stateTurbine = testSubject.state.testIn(backgroundScope)
|
||||
val effectTurbine = testSubject.effect.testIn(backgroundScope)
|
||||
|
@ -162,7 +216,7 @@ class AccountOAuthViewModelTest {
|
|||
val initialState = defaultState
|
||||
val testSubject = createTestSubject(
|
||||
authorizationResult = AuthorizationResult.Canceled,
|
||||
initialState = initialState
|
||||
initialState = initialState,
|
||||
)
|
||||
val stateTurbine = testSubject.state.testIn(backgroundScope)
|
||||
val effectTurbine = testSubject.effect.testIn(backgroundScope)
|
||||
|
@ -190,90 +244,92 @@ class AccountOAuthViewModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `should finish OAuth sign in when onOAuthResult received with success but authorization result is cancelled`() = runTest {
|
||||
val initialState = defaultState
|
||||
val testSubject = createTestSubject(
|
||||
authorizationResult = AuthorizationResult.Canceled,
|
||||
initialState = initialState
|
||||
)
|
||||
val stateTurbine = testSubject.state.testIn(backgroundScope)
|
||||
val effectTurbine = testSubject.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
fun `should finish OAuth sign in when onOAuthResult received with success but authorization result is cancelled`() =
|
||||
runTest {
|
||||
val initialState = defaultState
|
||||
val testSubject = createTestSubject(
|
||||
authorizationResult = AuthorizationResult.Canceled,
|
||||
initialState = initialState,
|
||||
)
|
||||
val stateTurbine = testSubject.state.testIn(backgroundScope)
|
||||
val effectTurbine = testSubject.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(initialState)
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(initialState)
|
||||
}
|
||||
|
||||
testSubject.event(Event.OnOAuthResult(resultCode = Activity.RESULT_OK, data = intent))
|
||||
|
||||
val loadingState = initialState.copy(isLoading = true)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(loadingState)
|
||||
}
|
||||
|
||||
val failureState = loadingState.copy(
|
||||
isLoading = false,
|
||||
error = Error.Cancelled,
|
||||
)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(failureState)
|
||||
}
|
||||
}
|
||||
|
||||
testSubject.event(Event.OnOAuthResult(resultCode = Activity.RESULT_OK, data = intent))
|
||||
|
||||
val loadingState = initialState.copy(isLoading = true)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(loadingState)
|
||||
}
|
||||
|
||||
val failureState = loadingState.copy(
|
||||
isLoading = false,
|
||||
error = Error.Cancelled,
|
||||
)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(failureState)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should finish OAuth sign in when onOAuthResult received with success but authorization result is failure`() = runTest {
|
||||
val initialState = defaultState
|
||||
val failure = Exception("failure")
|
||||
val testSubject = createTestSubject(
|
||||
authorizationResult = AuthorizationResult.Failure(failure),
|
||||
initialState = initialState
|
||||
)
|
||||
val stateTurbine = testSubject.state.testIn(backgroundScope)
|
||||
val effectTurbine = testSubject.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
fun `should finish OAuth sign in when onOAuthResult received with success but authorization result is failure`() =
|
||||
runTest {
|
||||
val initialState = defaultState
|
||||
val failure = Exception("failure")
|
||||
val testSubject = createTestSubject(
|
||||
authorizationResult = AuthorizationResult.Failure(failure),
|
||||
initialState = initialState,
|
||||
)
|
||||
val stateTurbine = testSubject.state.testIn(backgroundScope)
|
||||
val effectTurbine = testSubject.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(initialState)
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(initialState)
|
||||
}
|
||||
|
||||
testSubject.event(Event.OnOAuthResult(resultCode = Activity.RESULT_OK, data = intent))
|
||||
|
||||
val loadingState = initialState.copy(isLoading = true)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(loadingState)
|
||||
}
|
||||
|
||||
val failureState = loadingState.copy(
|
||||
isLoading = false,
|
||||
error = Error.Unknown(failure),
|
||||
)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(failureState)
|
||||
}
|
||||
}
|
||||
|
||||
testSubject.event(Event.OnOAuthResult(resultCode = Activity.RESULT_OK, data = intent))
|
||||
|
||||
val loadingState = initialState.copy(isLoading = true)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(loadingState)
|
||||
}
|
||||
|
||||
val failureState = loadingState.copy(
|
||||
isLoading = false,
|
||||
error = Error.Unknown(failure),
|
||||
)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(failureState)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should emit NavigateBack effect when OnBackClicked event received`() = runTest {
|
||||
val viewModel = createTestSubject()
|
||||
|
|
Loading…
Reference in a new issue