Skip to content

Commit

Permalink
Add custom web view authgear#31
Browse files Browse the repository at this point in the history
  • Loading branch information
tung2744 authored Feb 22, 2024
2 parents 1d8f873 + 80b7f1d commit a4861ae
Show file tree
Hide file tree
Showing 13 changed files with 822 additions and 41 deletions.
5 changes: 5 additions & 0 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
android:launchMode="singleTask"
android:theme="@style/AuthgearTheme"
android:configChanges="orientation|screenSize" />
<activity
android:name=".WebKitWebViewActivity"
android:exported="false"
android:launchMode="singleTask"
android:theme="@style/AuthgearTheme" />
</application>
<queries>
<package android:name="com.android.chrome" />
Expand Down
41 changes: 38 additions & 3 deletions android/src/main/kotlin/com/authgear/flutter/AuthgearPlugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.PackageInfo
import android.graphics.Color
import android.net.Uri
import android.os.Build
import android.os.Handler
Expand Down Expand Up @@ -32,7 +33,7 @@ import io.flutter.plugin.common.PluginRegistry
import org.json.JSONObject
import java.security.*
import java.security.interfaces.RSAPublicKey
import java.util.UUID
import java.util.*


class AuthgearPlugin: FlutterPlugin, ActivityAware, MethodCallHandler, PluginRegistry.ActivityResultListener {
Expand Down Expand Up @@ -125,9 +126,12 @@ class AuthgearPlugin: FlutterPlugin, ActivityAware, MethodCallHandler, PluginReg
}

override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
this.storeWechat(call)
when (call.method) {
"authenticate" -> {
"registerWechatRedirectURI" -> {
this.storeWechat(call)
result.success(null)
}
"openAuthorizeURL" -> {
val url = Uri.parse(call.argument("url"))
val redirectURI = Uri.parse(call.argument("redirectURI"))
// Custom tabs do not support incognito mode for now.
Expand All @@ -142,6 +146,24 @@ class AuthgearPlugin: FlutterPlugin, ActivityAware, MethodCallHandler, PluginReg
val intent = OAuthCoordinatorActivity.createAuthorizationIntent(activity, url)
activity.startActivityForResult(intent, requestCode)
}
"openAuthorizeURLWithWebView" -> {
val url = Uri.parse(call.argument("url"))
val redirectURI = Uri.parse(call.argument("redirectURI"))
val actionBarBackgroundColor = this.readColorInt(call, "actionBarBackgroundColor")
val actionBarButtonTintColor = this.readColorInt(call, "actionBarButtonTintColor")
val options = WebKitWebViewActivity.Options(url, redirectURI)
options.actionBarBackgroundColor = actionBarBackgroundColor
options.actionBarButtonTintColor = actionBarButtonTintColor

val requestCode = startActivityHandles.push(StartActivityHandle(TAG_AUTHENTICATION, result))
val activity = activityBinding?.activity
if (activity == null) {
result.noActivity()
return
}
val intent = WebKitWebViewActivity.createIntent(activity, options)
activity.startActivityForResult(intent, requestCode)
}
"openURL" -> {
val url = Uri.parse(call.argument("url"))
val requestCode = startActivityHandles.push(StartActivityHandle(TAG_OPEN_URL, result))
Expand Down Expand Up @@ -781,6 +803,19 @@ class AuthgearPlugin: FlutterPlugin, ActivityAware, MethodCallHandler, PluginReg
val sig = signature.sign()
return "$strToSign.${sig.base64URLEncode()}"
}

private fun readColorInt(call: MethodCall, key: String): Int? {
val s: String? = call.argument<String?>(key)
if (s != null) {
val l = s.toLong(16)
val a = (l shr 24 and 0xff).toInt()
val r = (l shr 16 and 0xff).toInt()
val g = (l shr 8 and 0xff).toInt()
val b = (l and 0xff).toInt()
return Color.argb(a, r, g, b)
}
return null
}
}

internal fun Result.noActivity() {
Expand Down
269 changes: 269 additions & 0 deletions android/src/main/kotlin/com/authgear/flutter/WebKitWebViewActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
package com.authgear.flutter

import android.annotation.TargetApi
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.webkit.*
import androidx.annotation.*
import androidx.appcompat.app.ActionBar
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.DrawableCompat

class WebKitWebViewActivity: AppCompatActivity() {
companion object {
private const val MENU_ID_CANCEL = 1
private const val KEY_OPTIONS = "KEY_OPTIONS"
private const val KEY_WEB_VIEW_STATE = "KEY_WEB_VIEW_STATE"
private const val TAG_FILE_CHOOSER = 1

fun createIntent(ctx: Context, options: Options): Intent {
val intent = Intent(ctx, WebKitWebViewActivity::class.java)
intent.putExtra(KEY_OPTIONS, options.toBundle())
return intent
}
}

private lateinit var mWebView: WebView
private var result: Uri? = null
private val handles = StartActivityHandles<ValueCallback<Array<Uri>>>()

class Options {
var url: Uri
var redirectURI: Uri
var actionBarBackgroundColor: Int? = null
var actionBarButtonTintColor: Int? = null

constructor(url: Uri, redirectURI: Uri) {
this.url = url
this.redirectURI = redirectURI
}

internal constructor(bundle: Bundle) {
this.url = bundle.getParcelable("url")!!
this.redirectURI = bundle.getParcelable("redirectURI")!!
if (bundle.containsKey("actionBarBackgroundColor")) {
this.actionBarBackgroundColor = bundle.getInt("actionBarBackgroundColor")
}
if (bundle.containsKey("actionBarButtonTintColor")) {
this.actionBarButtonTintColor = bundle.getInt("actionBarButtonTintColor")
}
}

fun toBundle(): Bundle {
val bundle = Bundle()
bundle.putParcelable("url", this.url)
bundle.putParcelable("redirectURI", this.redirectURI)
this.actionBarBackgroundColor?.let {
bundle.putInt("actionBarBackgroundColor", it)
}
this.actionBarButtonTintColor?.let {
bundle.putInt("actionBarButtonTintColor", it)
}
return bundle
}
}

private class MyWebViewClient constructor(private val activity: WebKitWebViewActivity) :
WebViewClient() {

@TargetApi(Build.VERSION_CODES.N)
override fun shouldOverrideUrlLoading(
view: WebView?,
request: WebResourceRequest?
): Boolean {
val uri = request?.url!!
if (this.shouldOverrideUrlLoading(uri)) {
return true
}
return super.shouldOverrideUrlLoading(view, request)
}

@SuppressWarnings("deprecation")
override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
val uri = Uri.parse(url!!)!!
if (this.shouldOverrideUrlLoading(uri)) {
return true
}
return super.shouldOverrideUrlLoading(view, url)
}

private fun shouldOverrideUrlLoading(uri: Uri): Boolean {
if (this.checkRedirectURI(uri)) {
return true;
}
return false;
}

private fun checkRedirectURI(uri: Uri): Boolean {
val redirectURI = this.activity.getOptions().redirectURI
val withoutQuery = this.removeQueryAndFragment(uri)
if (withoutQuery.toString() == redirectURI.toString()) {
this.activity.result = uri
this.activity.callSetResult()
this.activity.finish()
return true
}
return false;
}

private fun removeQueryAndFragment(uri: Uri): Uri {
return uri.buildUpon().query(null).fragment(null).build()
}
}

private class MyWebChromeClient constructor(private val activity: WebKitWebViewActivity) :
WebChromeClient() {

@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onShowFileChooser(
webView: WebView?,
filePathCallback: ValueCallback<Array<Uri>>?,
fileChooserParams: FileChooserParams?
): Boolean {
val handle = StartActivityHandle(TAG_FILE_CHOOSER, filePathCallback!!)
val requestCode = this.activity.handles.push(handle)
val intent = fileChooserParams!!.createIntent()
this.activity.startActivityForResult(intent, requestCode)
return true
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val options = this.getOptions()

// Do not show title.
supportActionBar?.setDisplayShowTitleEnabled(false)

// Configure navigation bar background color.
options.actionBarBackgroundColor?.let {
supportActionBar?.setBackgroundDrawable(ColorDrawable(it))
}

// Show back button.
supportActionBar?.displayOptions = ActionBar.DISPLAY_SHOW_HOME or ActionBar.DISPLAY_HOME_AS_UP

// Configure the back button.
var backButtonDrawable = getDrawableCompat(R.drawable.ic_arrow_back)
if (options.actionBarButtonTintColor != null) {
backButtonDrawable =
tintDrawable(backButtonDrawable, options.actionBarButtonTintColor!!)
}
supportActionBar?.setHomeAsUpIndicator(backButtonDrawable)

// Configure web view.
this.mWebView = WebView(this)
this.setContentView(this.mWebView)
this.mWebView.setWebViewClient(MyWebViewClient(this))
this.mWebView.setWebChromeClient(MyWebChromeClient(this))
val webSettings: WebSettings = this.mWebView.getSettings()
webSettings.javaScriptEnabled = true

if (savedInstanceState == null) {
this.mWebView.loadUrl(options.url.toString())
}
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val webViewBundle = Bundle()
this.mWebView.saveState(webViewBundle)
outState.putBundle(KEY_WEB_VIEW_STATE, webViewBundle)
}

override fun onRestoreInstanceState(savedInstanceState: Bundle) {
super.onRestoreInstanceState(savedInstanceState)
val bundle = savedInstanceState.getBundle(KEY_WEB_VIEW_STATE)
if (bundle != null) {
this.mWebView.restoreState(bundle)
}
}

override fun onBackPressed() {
if (mWebView.canGoBack()) {
mWebView.goBack()
} else {
callSetResult()
super.onBackPressed()
}
}

override fun onCreateOptionsMenu(menu: Menu): Boolean {
val options = getOptions()

// Configure the close button.
var drawable = getDrawableCompat(R.drawable.ic_close)
if (options.actionBarButtonTintColor != null) {
drawable = tintDrawable(drawable, options.actionBarButtonTintColor!!)
}
menu.add(Menu.NONE, MENU_ID_CANCEL, Menu.NONE, android.R.string.cancel)
.setIcon(drawable)
.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
return super.onCreateOptionsMenu(menu)
}

override fun onOptionsItemSelected(@NonNull item: MenuItem): Boolean {
if (item.getItemId() === android.R.id.home) {
onBackPressed()
return true
}
if (item.getItemId() === MENU_ID_CANCEL) {
callSetResult()
finish()
return true
}
return super.onOptionsItemSelected(item)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, @Nullable data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val handle = handles.pop(requestCode)
?: return
when (handle.tag) {
TAG_FILE_CHOOSER -> when (resultCode) {
Activity.RESULT_CANCELED -> handle.value.onReceiveValue(null)
Activity.RESULT_OK -> if (data != null && data.data != null) {
handle.value.onReceiveValue(arrayOf(data.data!!))
} else {
handle.value.onReceiveValue(null)
}
}
}
}

private fun getOptions(): Options {
val bundle: Bundle = this.intent.getParcelableExtra(KEY_OPTIONS)!!
return Options(bundle)
}

private fun callSetResult() {
if (this.result == null) {
this.setResult(Activity.RESULT_CANCELED)
} else {
val intent = Intent()
intent.data = this.result
this.setResult(Activity.RESULT_OK, intent)
}
}

private fun getDrawableCompat(@DrawableRes id: Int): Drawable {
return ResourcesCompat.getDrawable(resources, id, null)!!
}

private fun tintDrawable(drawable: Drawable, @ColorInt color: Int): Drawable {
val newDrawable: Drawable =
DrawableCompat.wrap(drawable).constantState!!.newDrawable().mutate()
DrawableCompat.setTint(newDrawable, color)
return newDrawable
}
}
11 changes: 11 additions & 0 deletions android/src/main/res/drawable/ic_arrow_back.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal"
android:autoMirrored="true">
<path
android:fillColor="@android:color/white"
android:pathData="M313,520L537,744L480,800L160,480L480,160L537,216L313,440L800,440L800,520L313,520Z"/>
</vector>
10 changes: 10 additions & 0 deletions android/src/main/res/drawable/ic_close.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
</vector>
1 change: 0 additions & 1 deletion example/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
Expand Down
Loading

0 comments on commit a4861ae

Please sign in to comment.