Skip to content

Commit

Permalink
MBL-1286: OAuth when default browser not chrome (#2012)
Browse files Browse the repository at this point in the history
* - If default browser not Chrome, use Webview

* - Comments

* - Alert to introduce staging credentials

* cleanup
  • Loading branch information
Arkariang authored Apr 8, 2024
1 parent 3bc4ede commit dec62dd
Show file tree
Hide file tree
Showing 8 changed files with 296 additions and 58 deletions.
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@
android:name=".ui.activities.WebViewActivity"
android:parentActivityName=".ui.activities.DiscoveryActivity"
android:theme="@style/WebViewActivity" />
<activity
android:name=".ui.activities.OAuthWebViewActivity"
android:parentActivityName=".ui.activities.LoginToutActivity"
android:theme="@style/WebViewActivity" />
<activity
android:name=".ui.activities.ResetPasswordActivity"
android:windowSoftInputMode="adjustResize"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@ import android.app.AlertDialog
import android.app.Application
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.runtime.Composable
import androidx.core.content.ContextCompat
import com.kickstarter.KSApplication
import com.kickstarter.R
import com.kickstarter.libs.Environment
import com.kickstarter.libs.featureflag.FlagKey
import com.kickstarter.ui.SharedPreferenceKey
import com.kickstarter.ui.activities.AppThemes
import com.stripe.android.paymentsheet.PaymentSheet

fun Context.isKSApplication() = (this is KSApplication) && !this.isInUnitTests
Expand All @@ -17,6 +23,25 @@ fun Context.getEnvironment(): Environment? {
return (this.applicationContext as KSApplication).component().environment()
}

@Composable
fun Context.isDarkModeEnabled(env: Environment): Boolean {
val darkModeEnabled = env.featureFlagClient()?.getBoolean(FlagKey.ANDROID_DARK_MODE_ENABLED) ?: false
val theme = env.sharedPreferences()
?.getInt(SharedPreferenceKey.APP_THEME, AppThemes.MATCH_SYSTEM.ordinal)
?: AppThemes.MATCH_SYSTEM.ordinal

return if (darkModeEnabled) {
when (theme) {
AppThemes.MATCH_SYSTEM.ordinal -> isSystemInDarkTheme()
AppThemes.DARK.ordinal -> true
AppThemes.LIGHT.ordinal -> false
else -> false
}
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
isSystemInDarkTheme() // Force dark mode uses system theme
} else false
}

/**
* if the current context is an instance of Application android base class
* register the callbacks provided on the parameter.
Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/com/kickstarter/ui/IntentKey.kt
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ object IntentKey {
const val RESET_PASSWORD_FACEBOOK_LOGIN = "com.kickstarter.kickstarter.intent_reset_password_facebook"
const val FLAGGINGKIND = "com.kickstarter.kickstarter.intent_report_project"
const val PREVIOUS_SCREEN = "com.kickstarter.kickstarter.previous_screen"
const val OAUTH_REDIRECT_URL = "com.kickstarter.kickstarter.oauth_redirect_url"
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.kickstarter.ui.activities

import android.app.Activity
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.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.annotation.StringRes
import androidx.browser.customtabs.CustomTabsIntent
Expand Down Expand Up @@ -64,6 +66,16 @@ class LoginToutActivity : ComponentActivity() {

private val oAuthLogcat = "OAuth: "

private val startForResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
result.data?.let {
val url = it.getStringExtra(IntentKey.OAUTH_REDIRECT_URL) ?: ""
// - Redirection takes place from WebView, as default browser is not Chrome
afterRedirection(url, it)
}
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var darkModeEnabled = false
Expand Down Expand Up @@ -219,16 +231,27 @@ class LoginToutActivity : ComponentActivity() {
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
Timber.d("$oAuthLogcat onNewIntent Intent: $intent, data: ${intent?.data}")
// - Intent generated when the deepLink redirection takes place
intent?.let { oAuthViewModel.produceState(intent = it) }
intent?.let {
val url = intent.data.toString()
// - Redirection takes place from ChromeTab, as the defaultBrowser is Chrome
afterRedirection(url, it)
}
}

private fun afterRedirection(url: String, intent: Intent) {
val uri = Uri.parse(url)
uri?.let {
if (OAuthViewModel.isAfterRedirectionStep(it))
oAuthViewModel.produceState(intent = intent, uri)
}
}

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

if (state.user.isNotNull()) {
Expand All @@ -239,14 +262,25 @@ class LoginToutActivity : ComponentActivity() {
}
}

private fun openChromeTabWithUrl(url: String) {
/**
* If default Browser is Chrome a CustomChromeTab will be open with give URL
* If default Browser is not Chrome Webview will be open with given URL
*/
private fun openChromeTabOrWebViewWithUrl(url: String) {
val authorizationUri = Uri.parse(url)

val tabIntent = CustomTabsIntent.Builder().build()

val packageName = ChromeTabsHelper.getPackageNameToUse(this)
tabIntent.intent.setPackage(packageName)
tabIntent.launchUrl(this, authorizationUri)
if (packageName == "com.android.chrome") {
tabIntent.intent.setPackage(packageName)
tabIntent.launchUrl(this, authorizationUri)
} else {
val intent: Intent = Intent(this, OAuthWebViewActivity::class.java)
.putExtra(IntentKey.URL, authorizationUri.toString())
startForResult.launch(intent)
this.overridePendingTransition(R.anim.slide_up, R.anim.fade_out)
}
}

private fun facebookLoginClick() =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.kickstarter.ui.activities

import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.ViewGroup
import android.webkit.HttpAuthHandler
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import android.webkit.WebViewDatabase
import android.widget.EditText
import android.widget.LinearLayout
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import androidx.compose.runtime.Composable
import androidx.compose.ui.viewinterop.AndroidView
import com.kickstarter.libs.utils.extensions.getEnvironment
import com.kickstarter.libs.utils.extensions.isDarkModeEnabled
import com.kickstarter.libs.utils.extensions.isNotNull
import com.kickstarter.ui.IntentKey
import com.kickstarter.ui.compose.designsystem.KickstarterApp
import com.kickstarter.ui.extensions.text
import com.kickstarter.viewmodels.OAuthViewModel

/**
* Will be used for OAuth when default Browser is not Chrome
* with other browsers (Firefox, Opera, Arc, Duck Duck Go ... etc) even based in Chromium
* the redirection was not triggered on `LoginToutActivity.onNewIntent`, and the customTabInstance was never killed.
*/
class OAuthWebViewActivity : ComponentActivity() {
val callback: (String) -> Unit = { inputString ->
val intent = Intent()
.putExtra(IntentKey.OAUTH_REDIRECT_URL, inputString)
this.setResult(Activity.RESULT_OK, intent)
this.finish()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val url = intent.getStringExtra(IntentKey.URL) ?: ""
this.getEnvironment()?.let { env ->
setContent {
val darModeEnabled = this.isDarkModeEnabled(env = env)
KickstarterApp(useDarkTheme = darModeEnabled) {
WebView(url, this, callback)
}
}
}
}

@Composable
private fun WebView(url: String, context: Context, callback: (String) -> Unit) {
AndroidView(factory = {
WebView(it).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
this.webViewClient = CustomWebViewClient(context = context, callback)
this.settings.allowFileAccess = true
}
}, update = {
it.loadUrl(url)
})
}
}

class CustomWebViewClient(private val context: Context, private val callback: (String) -> Unit) : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
request?.url?.let {
if (OAuthViewModel.isAfterRedirectionStep(it)) {
callback(it.toString())
}
} ?: callback("")
return false
}

/**
* Only used on staging environments for basic http authentication
*/
@RequiresApi(Build.VERSION_CODES.O)
override fun onReceivedHttpAuthRequest(
view: WebView?,
handler: HttpAuthHandler?,
host: String?,
realm: String?
) {
val webDatabase = WebViewDatabase.getInstance(context)

val alert: AlertDialog.Builder = AlertDialog.Builder(context)

val container = LinearLayout(context)
container.orientation = LinearLayout.VERTICAL
val user = EditText(context)
user.hint = "Staging credential User:"
val password = EditText(context)
password.hint = "Staging credential Password:"

container.addView(user)
container.addView(password)

alert.setView(container)
alert.setPositiveButton(
"Send",
DialogInterface.OnClickListener { dialog, whichButton ->
if (user.isNotNull() && password.isNotNull()) {
webDatabase.setHttpAuthUsernamePassword(
host,
realm,
user.text(),
password.text()
)
handler?.proceed(user.text(), password.text())
super.onReceivedHttpAuthRequest(view, handler, host, realm)
}
}
)
alert.show()
}
}
16 changes: 16 additions & 0 deletions app/src/main/java/com/kickstarter/ui/activities/WebViewActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.activity.addCallback
import com.kickstarter.databinding.WebViewLayoutBinding
import com.kickstarter.ui.IntentKey
import com.kickstarter.ui.extensions.finishWithAnimation
import com.kickstarter.ui.views.KSWebView

class WebViewActivity : ComponentActivity() {
private lateinit var binding: WebViewLayoutBinding
Expand All @@ -17,6 +18,21 @@ class WebViewActivity : ComponentActivity() {

val toolbarTitle = intent.getStringExtra(IntentKey.TOOLBAR_TITLE)
toolbarTitle?.let { binding.webViewToolbar.webViewToolbar.setTitle(it) }

binding.webView.setDelegate(object : KSWebView.Delegate {
override fun externalLinkActivated(url: String) {
}

override fun pageIntercepted(url: String) {
if (url.contains("authenticate")) {
finish()
}
}

override fun onReceivedError(url: String) {
}
})

val url = intent.getStringExtra(IntentKey.URL)
url?.let { binding.webView.loadUrl(it) }

Expand Down
Loading

0 comments on commit dec62dd

Please sign in to comment.