diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml
index 05e0205..1474fb1 100644
--- a/android/src/main/AndroidManifest.xml
+++ b/android/src/main/AndroidManifest.xml
@@ -12,6 +12,11 @@
android:launchMode="singleTask"
android:theme="@style/AuthgearTheme"
android:configChanges="orientation|screenSize" />
+
diff --git a/android/src/main/kotlin/com/authgear/flutter/AuthgearPlugin.kt b/android/src/main/kotlin/com/authgear/flutter/AuthgearPlugin.kt
index ca3c8d3..5b5ff7b 100644
--- a/android/src/main/kotlin/com/authgear/flutter/AuthgearPlugin.kt
+++ b/android/src/main/kotlin/com/authgear/flutter/AuthgearPlugin.kt
@@ -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
@@ -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 {
@@ -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.
@@ -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))
@@ -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(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() {
diff --git a/android/src/main/kotlin/com/authgear/flutter/WebKitWebViewActivity.kt b/android/src/main/kotlin/com/authgear/flutter/WebKitWebViewActivity.kt
new file mode 100644
index 0000000..9dddb03
--- /dev/null
+++ b/android/src/main/kotlin/com/authgear/flutter/WebKitWebViewActivity.kt
@@ -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>>()
+
+ 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>?,
+ 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
+ }
+}
\ No newline at end of file
diff --git a/android/src/main/res/drawable/ic_arrow_back.xml b/android/src/main/res/drawable/ic_arrow_back.xml
new file mode 100644
index 0000000..0e2e863
--- /dev/null
+++ b/android/src/main/res/drawable/ic_arrow_back.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/android/src/main/res/drawable/ic_close.xml b/android/src/main/res/drawable/ic_close.xml
new file mode 100644
index 0000000..7a0ff35
--- /dev/null
+++ b/android/src/main/res/drawable/ic_close.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml
index a1a6af5..971f1d4 100644
--- a/example/android/app/src/main/AndroidManifest.xml
+++ b/example/android/app/src/main/AndroidManifest.xml
@@ -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">