Skip to content

Commit

Permalink
[no-jira]: unify login screens, two viewModels. [PROPOSAL] (#1962)
Browse files Browse the repository at this point in the history
  • Loading branch information
Arkariang authored Mar 5, 2024
1 parent 153dcf3 commit 4e57079
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 132 deletions.
8 changes: 3 additions & 5 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,9 @@
android:windowSoftInputMode="adjustResize"
android:theme="@style/Login" />
<activity
android:name=".ui.activities.OAuthActivity"
android:name=".ui.activities.LoginToutActivity"
android:parentActivityName=".ui.activities.DiscoveryActivity"
android:theme="@style/Login"
android:launchMode="singleInstancePerTask"
android:exported="true">
<intent-filter android:autoVerify="true">
Expand All @@ -158,10 +160,6 @@
<data android:host="authenticate" />
</intent-filter>
</activity>
<activity
android:name=".ui.activities.LoginToutActivity"
android:parentActivityName=".ui.activities.DiscoveryActivity"
android:theme="@style/Login" />
<activity
android:name=".ui.activities.MessagesActivity"
android:parentActivityName=".ui.activities.MessageThreadsActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.kickstarter.ui.activities

import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.browser.customtabs.CustomTabsIntent
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.lifecycle.lifecycleScope
import com.facebook.AccessToken
import com.kickstarter.R
import com.kickstarter.libs.ActivityRequestCodes
Expand All @@ -20,7 +23,9 @@ import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.libs.utils.extensions.coalesceWithV2
import com.kickstarter.libs.utils.extensions.getEnvironment
import com.kickstarter.libs.utils.extensions.getResetPasswordIntent
import com.kickstarter.libs.utils.extensions.isNotNull
import com.kickstarter.libs.utils.extensions.showAlertDialog
import com.kickstarter.models.chrome.ChromeTabsHelper
import com.kickstarter.services.apiresponses.ErrorEnvelope.FacebookUser
import com.kickstarter.ui.IntentKey
import com.kickstarter.ui.SharedPreferenceKey
Expand All @@ -30,12 +35,15 @@ import com.kickstarter.ui.data.ActivityResult.Companion.create
import com.kickstarter.ui.data.LoginReason
import com.kickstarter.ui.extensions.startDisclaimerChromeTab
import com.kickstarter.ui.extensions.startLogin
import com.kickstarter.ui.extensions.startOauthActivity
import com.kickstarter.ui.extensions.startSignup
import com.kickstarter.viewmodels.LoginToutViewModel
import com.kickstarter.viewmodels.OAuthViewModel
import com.kickstarter.viewmodels.OAuthViewModelFactory
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import kotlinx.coroutines.launch
import timber.log.Timber

class LoginToutActivity : ComponentActivity() {

Expand All @@ -52,13 +60,23 @@ class LoginToutActivity : ComponentActivity() {

private val disposables = CompositeDisposable()

private lateinit var oAuthViewModelFactory: OAuthViewModelFactory
private val oAuthViewModel: OAuthViewModel by viewModels {
oAuthViewModelFactory
}

private val oAuthLogcat = "OAuth: "

var oauthFlagEnabled: Boolean = false

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var darkModeEnabled = false
var oauthFlagEnabled = false

this.getEnvironment()?.let { env ->
environment = env
viewModelFactory = LoginToutViewModel.Factory(env)
oAuthViewModelFactory = OAuthViewModelFactory(environment = env)
this.ksString = requireNotNull(env.ksString())
darkModeEnabled =
env.featureFlagClient()?.getBoolean(FlagKey.ANDROID_DARK_MODE_ENABLED) ?: false
Expand Down Expand Up @@ -99,12 +117,26 @@ class LoginToutActivity : ComponentActivity() {
},
featureFlagState = oauthFlagEnabled,
onSignUpOrLogInClicked = {
this@LoginToutActivity.startOauthActivity()
oAuthViewModel.produceState(intent = intent)
}
)
}
}

logInAndSignUpAndLoginWithFacebookVM()

if (oauthFlagEnabled) {
setUpOAuthViewModel()
}
}

/***
* Handles the the viewModel RXJava subscriptions for the user cases:
* - LogIn non OAuth
* - SignUp non OAuth
* - LogIn with Facebook
*/
private fun logInAndSignUpAndLoginWithFacebookVM() {
val loginReason = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getSerializableExtra(IntentKey.LOGIN_REASON, LoginReason::class.java)
} else {
Expand Down Expand Up @@ -210,6 +242,46 @@ class LoginToutActivity : ComponentActivity() {
.addToDisposable(disposables)
}

override fun onDestroy() {
Timber.d("$oAuthLogcat onDestroy")
super.onDestroy()
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
if (oauthFlagEnabled) {
Timber.d("$oAuthLogcat onNewIntent Intent: $intent, data: ${intent?.data}")
// - Intent generated when the deepLink redirection takes place
intent?.let { oAuthViewModel.produceState(intent = it) }
}
}

private fun setUpOAuthViewModel() {
lifecycleScope.launch {
oAuthViewModel.uiState.collect { state ->
// - Intent generated with onCreate
if (state.isAuthorizationStep && state.authorizationUrl.isNotEmpty()) {
openChromeTabWithUrl(state.authorizationUrl)
}

if (state.user.isNotNull()) {
setResult(RESULT_OK)
this@LoginToutActivity.finish()
}
}
}
}

private fun openChromeTabWithUrl(url: String) {
val authorizationUri = Uri.parse(url)

val tabIntent = CustomTabsIntent.Builder().build()
tabIntent.intent.flags = Intent.FLAG_ACTIVITY_NO_HISTORY

val packageName = ChromeTabsHelper.getPackageNameToUse(this)
tabIntent.intent.setPackage(packageName)
tabIntent.launchUrl(this, authorizationUri)
}

private fun facebookLoginClick() =
viewModel.inputs.facebookLoginClick(
this,
Expand Down
103 changes: 0 additions & 103 deletions app/src/main/java/com/kickstarter/ui/activities/OAuthActivity.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import com.kickstarter.ui.IntentKey
import com.kickstarter.ui.activities.DisclaimerItems
import com.kickstarter.ui.activities.HelpActivity
import com.kickstarter.ui.activities.LoginToutActivity
import com.kickstarter.ui.activities.OAuthActivity
import com.kickstarter.ui.activities.SignupActivity
import com.kickstarter.ui.data.PledgeData
import com.kickstarter.ui.data.PledgeReason
Expand Down Expand Up @@ -234,12 +233,6 @@ fun Activity.startPreLaunchProjectActivity(project: Project, previousScreen: Str
TransitionUtils.transition(this, TransitionUtils.slideInFromRight())
}

fun Activity.startOauthActivity() {
val intent = Intent().setClass(this, OAuthActivity::class.java)
startActivityForResult(intent, ActivityRequestCodes.LOGIN_FLOW)
TransitionUtils.transition(this, TransitionUtils.slideInFromRight())
}

fun Activity.startLogin() {
val intent = Intent().getLoginActivityIntent(this)
startActivityForResult(intent, ActivityRequestCodes.LOGIN_FLOW)
Expand Down
21 changes: 12 additions & 9 deletions app/src/main/java/com/kickstarter/viewmodels/OAuthViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class OAuthViewModel(
private val verifier: PKCE
) : ViewModel() {

private val logcat = "Oauth :"
private val hostEndpoint = environment.webEndpoint()
private val loginUseCase = LoginUseCase(environment)
private val apiClient = requireNotNull(environment.apiClientV2())
Expand All @@ -52,10 +53,9 @@ class OAuthViewModel(
WebEndpoint.STAGING -> Secrets.Api.Client.STAGING
else -> ""
}
private val codeVerifier = verifier.generateRandomCodeVerifier(entropy = CodeVerifier.MIN_CODE_VERIFIER_ENTROPY)

private var mutableUIState = MutableStateFlow(OAuthUiState())

lateinit var codeVerifier: String
val uiState: StateFlow<OAuthUiState>
get() = mutableUIState.asStateFlow()
.stateIn(
Expand All @@ -73,11 +73,11 @@ class OAuthViewModel(
val code = uri.getQueryParameter("code")

if (scheme == REDIRECT_URI_SCHEMA && host == REDIRECT_URI_HOST && !code.isNullOrBlank()) {
Timber.d("retrieve token after redirectionDeeplink: $code")
Timber.d("$logcat retrieve token after redirectionDeeplink: $code")
apiClient.loginWithCodes(codeVerifier, code, clientID)
.asFlow()
.flatMapLatest { token ->
Timber.d("About to persist token to currentUser: $token")
Timber.d("$logcat About to persist token to currentUser: $token")
loginUseCase.setToken(token.accessToken())
apiClient.fetchCurrentUser()
.asFlow()
Expand All @@ -86,7 +86,7 @@ class OAuthViewModel(
}
}
.catch {
Timber.e("error while getting the token or user: $it")
Timber.e("$logcat error while getting the token or user: ${processThrowable(it)}")
mutableUIState.emit(
OAuthUiState(
error = processThrowable(it),
Expand All @@ -96,7 +96,7 @@ class OAuthViewModel(
loginUseCase.logout()
}
.collect { user ->
Timber.d("About to persist user to currentUser: $user")
Timber.d("$logcat About to persist user to currentUser: $user")
loginUseCase.setUser(user)
mutableUIState.emit(
OAuthUiState(
Expand All @@ -107,7 +107,7 @@ class OAuthViewModel(
}

if (scheme == REDIRECT_URI_SCHEMA && host == REDIRECT_URI_HOST && code.isNullOrBlank()) {
val error = "No code after redirection"
val error = "$logcat No code after redirection"
Timber.e(error)
mutableUIState.emit(
OAuthUiState(
Expand All @@ -118,8 +118,9 @@ class OAuthViewModel(
}

if (intent.data == null) {
codeVerifier = verifier.generateRandomCodeVerifier(entropy = CodeVerifier.MIN_CODE_VERIFIER_ENTROPY)
val url = generateAuthorizationUrlWithParams()
Timber.d("isAuthorizationStep $url")
Timber.d("$logcat isAuthorizationStep $url and codeVerifier: $codeVerifier")
mutableUIState.emit(
OAuthUiState(
authorizationUrl = url,
Expand All @@ -134,7 +135,9 @@ class OAuthViewModel(
if (!throwable.message.isNullOrBlank()) return throwable.message ?: ""

if (throwable is ApiException) {
return throwable.errorEnvelope().errorMessages().toString()
val apiError = throwable.errorEnvelope()?.errorMessages()?.toString() ?: ""
val genericError = throwable.response().message()
return "$genericError / $apiError"
}

return "error while getting the token or user"
Expand Down
Loading

0 comments on commit 4e57079

Please sign in to comment.