diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b9a2ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.DS_Store +.dart_tool/ +.idea + +.packages +.pub/ + +build/ +.flutter-plugins diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..cae5295 --- /dev/null +++ b/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: bbfbf1770cca2da7c82e887e4e4af910034800b6 + channel: stable + +project_type: plugin diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..dfe237f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,20 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Flutter: Attach to Device", + "type": "dart", + "request": "attach", + "program": "example/lib/main.dart" + }, + { + "name": "Flutter", + "program": "example/lib/main.dart", + "request": "launch", + "type": "dart" + } + ] +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..41cc7d8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/README.md b/README.md new file mode 100644 index 0000000..623ce94 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# g_faraday + +## 快速集成 + +### 创建一个 flutter module 项目 + +``` +flutter create -t module #{your_project_name} + +``` + +### 在module目录下创建一个 flutter plugin 项目 + +``` +cd #{your_project_name} + +flutter create -t plugin --platforms ios,android faraday_helper + +``` + +### 在flutter module项目中依赖g_faraday +``` +``` diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..c6cbe56 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,8 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures diff --git a/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java new file mode 100644 index 0000000..5ae5d55 --- /dev/null +++ b/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java @@ -0,0 +1,25 @@ +package io.flutter.plugins; + +import io.flutter.plugin.common.PluginRegistry; +import com.yuxiaor.flutter.g_faraday.GFaradayPlugin; + +/** + * Generated file. Do not edit. + */ +public final class GeneratedPluginRegistrant { + public static void registerWith(PluginRegistry registry) { + if (alreadyRegisteredWith(registry)) { + return; + } + GFaradayPlugin.registerWith(registry.registrarFor("com.yuxiaor.flutter.g_faraday.GFaradayPlugin")); + } + + private static boolean alreadyRegisteredWith(PluginRegistry registry) { + final String key = GeneratedPluginRegistrant.class.getCanonicalName(); + if (registry.hasPlugin(key)) { + return true; + } + registry.registrarFor(key); + return false; + } +} diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..768358c --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,43 @@ +group 'com.yuxiaor.flutter.g_faraday' +version '1.0-SNAPSHOT' + +buildscript { + ext.kotlin_version = '1.3.72' + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.0.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + defaultConfig { + minSdkVersion 16 + } + lintOptions { + disable 'InvalidPackage' + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..38c8d45 --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,4 @@ +org.gradle.jvmargs=-Xmx1536M +android.enableR8=true +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..01a286e --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..a877076 --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'g_faraday' diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9340104 --- /dev/null +++ b/android/src/main/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + diff --git a/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/Faraday.kt b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/Faraday.kt new file mode 100644 index 0000000..05c3618 --- /dev/null +++ b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/Faraday.kt @@ -0,0 +1,158 @@ +package com.yuxiaor.flutter.g_faraday + +import android.app.Activity +import android.content.Intent +import androidx.fragment.app.FragmentActivity +import com.yuxiaor.flutter.g_faraday.delegates.DefaultNavigatorDelegate +import com.yuxiaor.flutter.g_faraday.delegates.FaradayNavigator +import com.yuxiaor.flutter.g_faraday.plugins.ActivityAwarePlugin +import com.yuxiaor.flutter.g_faraday.plugins.ResultListener +import com.yuxiaor.flutter.g_faraday.utils.NativeContextProvider +import com.yuxiaor.flutter.g_faraday.utils.getArgs +import com.yuxiaor.flutter.g_faraday.utils.getFlutterArgs +import com.yuxiaor.flutter.g_faraday.utils.startForResult +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.dart.DartExecutor +import io.flutter.embedding.engine.plugins.FlutterPlugin +import java.io.Serializable +import java.util.concurrent.atomic.AtomicInteger + +/** + * Author: Edward + * Date: 2020-08-31 + * Description: + */ +object Faraday { + + //native -> flutter intent args key + internal const val FLUTTER_ARGS_KEY = "_flutter_args_key" + const val ARGS_KEY = "_args_key_" + private val nextCode = AtomicInteger() + var navigator: FaradayNavigator? = null + + private val activityAwarePlugin = ActivityAwarePlugin() + private val faradayPlugin = GFaradayPlugin() + + @JvmStatic + val engine by lazy { FlutterEngine(NativeContextProvider.context) } + + @JvmStatic + fun startFlutterEngine(navigatorDelegate: FaradayNavigator? = null) { + this.navigator = navigatorDelegate ?: DefaultNavigatorDelegate() + //注册插件 + registerPlugin(faradayPlugin) + registerPlugin(activityAwarePlugin) + //开始执行dart代码,启动引擎 + engine.dartExecutor.executeDartEntrypoint(DartExecutor.DartEntrypoint.createDefault()) + } + + @JvmStatic + fun registerPlugin(plugin: FlutterPlugin) { + engine.plugins.add(plugin) + } + + @JvmStatic + fun getCurrentActivity(): Activity? { + return activityAwarePlugin.binding?.activity + } + + /** + * When jumped to native page form a flutter page with args,this method is provided to get the args. + * Or you can also call [Intent.getFlutterArgs()] to get args if you are using kotlin. + */ + @JvmStatic + fun getFlutterArgs(intent: Intent): Serializable? { + return intent.getFlutterArgs() + } + + + @JvmStatic + fun openFlutter(activity: FragmentActivity, url: String, vararg params: Pair, onActivityResult: ((data: HashMap?) -> Unit)? = null) { + activity.openFlutter(url, *params) { + onActivityResult?.invoke(it) + } + } + + @JvmStatic + fun openFlutter(activity: Activity, url: String, vararg params: Pair) { + activity.openFlutter(url, *params) + } + + @JvmStatic + fun openFlutter(activity: Activity, url: String, requestCode: Int, vararg params: Pair) { + activity.openFlutter(url, requestCode, *params) + } + + internal fun openActivityForResult(intent: Intent, callback: (result: HashMap?) -> Unit) { + val nextRequestCode = nextCode.getAndIncrement() + getCurrentActivity()?.startActivityForResult(intent, nextRequestCode) + ResultListener(activityAwarePlugin) { requestCode, resultCode, data -> + if (requestCode == nextRequestCode && resultCode == Activity.RESULT_OK) { + callback.invoke(data?.getArgs()) + } + } + } + + + internal fun onPageCreate(args: Any?, callback: (seqId: Int) -> Unit) { + faradayPlugin.onPageCreate(args, callback) + } + + internal fun onPageShow(seqId: Int) { + faradayPlugin.onPageShow(seqId) + } + + internal fun onPageHidden(seqId: Int) { + faradayPlugin.onPageHidden(seqId) + } + + internal fun onPageDealloc(seqId: Int) { + faradayPlugin.onPageDealloc(seqId) + } +} + + +/** + * Native to flutter + * @param url flutter router + * @param params params from native to flutter + * @param onActivityResult callback for Activity Result + */ +fun FragmentActivity.openFlutter(url: String, vararg params: Pair, onActivityResult: ((data: HashMap?) -> Unit)? = null) { + val args = hashMapOf(*params).apply { this["name"] = url } + val intent = Intent(this, FaradayActivity::class.java).putExtra(Faraday.FLUTTER_ARGS_KEY, args) + startForResult(intent) { resultCode, data -> + if (resultCode == Activity.RESULT_OK) { + onActivityResult?.invoke(data?.getArgs()) + } + } +} + + +/** + * Native to flutter + * @param url flutter router + * @param params params from native to flutter + * + * no Activity result + */ +fun Activity.openFlutter(url: String, vararg params: Pair) { + val args = hashMapOf(*params).apply { this["name"] = url } + val intent = Intent(this, FaradayActivity::class.java).putExtra(Faraday.FLUTTER_ARGS_KEY, args) + startActivity(intent) +} + + +/** + * Native to flutter + * @param url flutter router + * @param params params from native to flutter + * + * you need to override [onActivityResult] in your Activity to get the result + */ +fun Activity.openFlutter(url: String, requestCode: Int, vararg params: Pair) { + val args = hashMapOf(*params).apply { this["name"] = url } + val intent = Intent(this, FaradayActivity::class.java).putExtra(Faraday.FLUTTER_ARGS_KEY, args) + startActivityForResult(intent, requestCode) +} + diff --git a/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/FaradayActivity.kt b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/FaradayActivity.kt new file mode 100644 index 0000000..fee3911 --- /dev/null +++ b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/FaradayActivity.kt @@ -0,0 +1,48 @@ +package com.yuxiaor.flutter.g_faraday + +import android.content.Context +import android.os.Bundle +import io.flutter.embedding.android.FlutterActivity +import io.flutter.embedding.engine.FlutterEngine + +/** + * Author: Edward + * Date: 2020-09-01 + * Description: + */ +class FaradayActivity : FlutterActivity() { + + private var seqId = 0 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + //native -> flutter args + val args = intent.getSerializableExtra(Faraday.FLUTTER_ARGS_KEY) + Faraday.onPageCreate(args) { + seqId = it + } + } + + override fun provideFlutterEngine(context: Context): FlutterEngine? { + return Faraday.engine + } + + override fun onResume() { + super.onResume() + Faraday.onPageShow(seqId) + } + + + override fun onPause() { + super.onPause() + Faraday.onPageHidden(seqId) + + } + + override fun onDestroy() { + super.onDestroy() + Faraday.onPageDealloc(seqId) + } + + +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/FaradayFragment.kt b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/FaradayFragment.kt new file mode 100644 index 0000000..79ae33b --- /dev/null +++ b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/FaradayFragment.kt @@ -0,0 +1,66 @@ +package com.yuxiaor.flutter.g_faraday + +import android.content.Context +import android.os.Bundle +import io.flutter.embedding.android.FlutterFragment +import io.flutter.embedding.engine.FlutterEngine + +/** + * Author: Edward + * Date: 2020-09-07 + * Description: + */ +class FaradayFragment private constructor() : FlutterFragment() { + + private var seqId = 0 + + + companion object { + + @JvmStatic + fun newInstance(route: String, vararg params: Pair): FaradayFragment { + val args = hashMapOf(*params).apply { this["name"] = route } + val bundle = Bundle().apply { putSerializable(Faraday.FLUTTER_ARGS_KEY, args) } + return FaradayFragment().apply { arguments = bundle } + } + } + + + override fun provideFlutterEngine(context: Context): FlutterEngine? { + return Faraday.engine + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val args = arguments?.getSerializable(Faraday.FLUTTER_ARGS_KEY) + Faraday.onPageCreate(args) { + seqId = it + } + } + + + override fun onHiddenChanged(hidden: Boolean) { + super.onHiddenChanged(hidden) + if (!hidden) { + Faraday.onPageShow(seqId) + } else { + Faraday.onPageHidden(seqId) + } + } + + override fun onResume() { + super.onResume() + Faraday.onPageShow(seqId) + } + + override fun onPause() { + super.onPause() + Faraday.onPageHidden(seqId) + } + + override fun onDestroy() { + super.onDestroy() + Faraday.onPageDealloc(seqId) + } + +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/GFaradayPlugin.kt b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/GFaradayPlugin.kt new file mode 100644 index 0000000..d22e4fc --- /dev/null +++ b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/GFaradayPlugin.kt @@ -0,0 +1,95 @@ +package com.yuxiaor.flutter.g_faraday + +import androidx.annotation.NonNull +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result +import io.flutter.plugin.common.PluginRegistry.Registrar +import java.io.Serializable + +/** GFaradayPlugin */ +class GFaradayPlugin : FlutterPlugin, MethodCallHandler { + private var channel: MethodChannel? = null + + companion object { + @JvmStatic + fun registerWith(registrar: Registrar) { + val channel = MethodChannel(registrar.messenger(), "g_faraday") + channel.setMethodCallHandler(GFaradayPlugin()) + + } + } + + override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.flutterEngine.dartExecutor, "g_faraday") + channel?.setMethodCallHandler(this) + } + + + override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { + when (call.method) { + "getPlatformVersion" -> result.success("Android ${android.os.Build.VERSION.RELEASE}") + "pushNativePage" -> { + val name = call.argument("name") + require(name != null) { "page route name should not be null" } + Faraday.navigator?.push(name, call.argument("arguments")) { result.success(it) } + } + "popContainer" -> { + val arg = call.arguments + Faraday.navigator?.pop(arg as? Serializable) + if (arg != null && arg !is Serializable) { + print("=========返回值丢失,返回值类型 $arg") + } + result.success(null) + } + "disableHorizontalSwipePopGesture" -> { + val disable = call.arguments as? Boolean ?: false + print(if (!disable) "enable" else "disable" + " Horizontal Swipe PopGesture") + result.success(null) + } + else -> result.notImplemented() + } + } + + fun onPageCreate(args: Any?, callback: (seqId: Int) -> Unit) { + channel?.invoke("pageCreate", args) { + val seqId = it as Int + callback.invoke(seqId) + } + } + + fun onPageShow(seqId: Int) { + channel?.invokeMethod("pageShow", seqId) + } + + fun onPageHidden(seqId: Int) { + channel?.invokeMethod("pageHidden", seqId) + } + + fun onPageDealloc(seqId: Int) { + channel?.invokeMethod("pageDealloc", seqId) + } + + + override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { + channel?.setMethodCallHandler(null) + } + + + private fun MethodChannel.invoke(method: String, arguments: Any?, callback: ((result: Any?) -> Unit)? = null) { + invokeMethod(method, arguments, object : Result { + + override fun notImplemented() { + } + + override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) { + } + + override fun success(result: Any?) { + callback?.invoke(result) + } + }) + } +} diff --git a/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/delegates/DefaultNavigatorDelegate.kt b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/delegates/DefaultNavigatorDelegate.kt new file mode 100644 index 0000000..4c0ca25 --- /dev/null +++ b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/delegates/DefaultNavigatorDelegate.kt @@ -0,0 +1,46 @@ +package com.yuxiaor.flutter.g_faraday.delegates + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import com.yuxiaor.flutter.g_faraday.Faraday +import java.io.Serializable + +/** + * Author: Edward + * Date: 2020-09-02 + * Description: + */ + +/** + * Default Navigator Delegate + */ +class DefaultNavigatorDelegate : FaradayNavigator { + + /** + * Open native page + * @param name route name + * @param arguments data from flutter page to native page + * @param callback onActivityResult callback + */ + override fun push(name: String, arguments: Serializable?, callback: (result: HashMap?) -> Unit) { + val intent = Intent(Intent.ACTION_VIEW) + intent.data = Uri.parse(name) + intent.putExtra(Faraday.ARGS_KEY, arguments) + Faraday.openActivityForResult(intent, callback) + } + + /** + * Close container Activity when flutter pops the last page + * @param result data from flutter to native + */ + override fun pop(result: Serializable?) { + val activity = Faraday.getCurrentActivity() ?: return + if (result != null) { + activity.setResult(Activity.RESULT_OK, Intent().apply { putExtra(Faraday.ARGS_KEY, result) }) + } + activity.finish() + } + +} + diff --git a/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/delegates/FaradayNavigator.kt b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/delegates/FaradayNavigator.kt new file mode 100644 index 0000000..f87a782 --- /dev/null +++ b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/delegates/FaradayNavigator.kt @@ -0,0 +1,16 @@ +package com.yuxiaor.flutter.g_faraday.delegates + +import java.io.Serializable + +/** + * Author: Edward + * Date: 2020-09-02 + * Description: + */ + +interface FaradayNavigator { + + fun push(name: String, arguments: Serializable?, callback: (result: HashMap?) -> Unit) + + fun pop(result: Serializable?) +} diff --git a/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/plugins/ActivityAwarePlugin.kt b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/plugins/ActivityAwarePlugin.kt new file mode 100644 index 0000000..8356468 --- /dev/null +++ b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/plugins/ActivityAwarePlugin.kt @@ -0,0 +1,58 @@ +package com.yuxiaor.flutter.g_faraday.plugins + +import android.content.Intent +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.embedding.engine.plugins.activity.ActivityAware +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding +import io.flutter.plugin.common.PluginRegistry + +/** + * Author: Edward + * Date: 2020-08-31 + * Description: + */ +class ActivityAwarePlugin : FlutterPlugin, ActivityAware { + + var binding: ActivityPluginBinding? = null + + override fun onAttachedToActivity(binding: ActivityPluginBinding) { + this.binding = binding + } + + override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + } + + override fun onDetachedFromActivity() { + } + + override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { + } + + override fun onDetachedFromActivityForConfigChanges() { + } + + fun addActivityResultListener(listener: PluginRegistry.ActivityResultListener) { + binding?.addActivityResultListener(listener) + } + + fun removeActivityResultListener(listener: PluginRegistry.ActivityResultListener) { + binding?.removeActivityResultListener(listener) + } +} + + +class ResultListener(private val activityAwarePlugin: ActivityAwarePlugin, private val callback: (requestCode: Int, resultCode: Int, data: Intent?) -> Unit) : PluginRegistry.ActivityResultListener { + + init { + activityAwarePlugin.addActivityResultListener(this) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?): Boolean { + callback.invoke(requestCode, resultCode, data) + activityAwarePlugin.removeActivityResultListener(this) + return false + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/utils/IntentExt.kt b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/utils/IntentExt.kt new file mode 100644 index 0000000..a3eda04 --- /dev/null +++ b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/utils/IntentExt.kt @@ -0,0 +1,66 @@ +package com.yuxiaor.flutter.g_faraday.utils + +import android.content.Intent +import android.os.Bundle +import android.os.Parcelable +import com.yuxiaor.flutter.g_faraday.Faraday +import java.io.Serializable + +/** + * Author: Edward + * Date: 2020-09-02 + * Description: + */ +fun Intent.fillArgs(params: Array>): Intent { + params.forEach { + when (val value = it.second) { + null -> putExtra(it.first, null as? Serializable?) + is Int -> putExtra(it.first, value) + is Long -> putExtra(it.first, value) + is CharSequence -> putExtra(it.first, value) + is String -> putExtra(it.first, value) + is Float -> putExtra(it.first, value) + is Double -> putExtra(it.first, value) + is Char -> putExtra(it.first, value) + is Short -> putExtra(it.first, value) + is Boolean -> putExtra(it.first, value) + is Serializable -> putExtra(it.first, value) + is Bundle -> putExtra(it.first, value) + is Parcelable -> putExtra(it.first, value) + is Array<*> -> when { + value.isArrayOf() -> putExtra(it.first, value) + value.isArrayOf() -> putExtra(it.first, value) + value.isArrayOf() -> putExtra(it.first, value) + else -> throw Exception("Intent extra ${it.first} has wrong type ${value.javaClass.name}") + } + is IntArray -> putExtra(it.first, value) + is LongArray -> putExtra(it.first, value) + is FloatArray -> putExtra(it.first, value) + is DoubleArray -> putExtra(it.first, value) + is CharArray -> putExtra(it.first, value) + is ShortArray -> putExtra(it.first, value) + is BooleanArray -> putExtra(it.first, value) + else -> throw Exception("Intent extra ${it.first} has wrong type ${value.javaClass.name}") + } + } + return this +} + +/** + * 获取Intent携带的所有数据 + */ +fun Intent.getArgs(): HashMap { + val map = hashMapOf() + extras?.keySet()?.forEach { + map[it] = extras?.get(it) + } + return map +} + + +/** + * 从flutter页面传给原生页面的数据 + */ +fun Intent.getFlutterArgs(): Serializable? { + return getSerializableExtra(Faraday.ARGS_KEY) +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/utils/NativeContextProvider.kt b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/utils/NativeContextProvider.kt new file mode 100644 index 0000000..241bdc0 --- /dev/null +++ b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/utils/NativeContextProvider.kt @@ -0,0 +1,45 @@ +package com.yuxiaor.flutter.g_faraday.utils + +import android.app.Application +import android.content.ContentProvider +import android.content.ContentValues +import android.database.Cursor +import android.net.Uri + +/** + * Author: Edward + * Date: 2019-08-13 + * Description: + */ +class NativeContextProvider : ContentProvider() { + + companion object { + lateinit var context: Application + } + + override fun onCreate(): Boolean { + val app = context as Application + NativeContextProvider.context = app + return true + } + + override fun insert(uri: Uri, values: ContentValues?): Uri? { + return null + } + + override fun query(uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, sortOrder: String?): Cursor? { + return null + } + + override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array?): Int { + return 0 + } + + override fun delete(uri: Uri, selection: String?, selectionArgs: Array?): Int { + return 0 + } + + override fun getType(uri: Uri): String? { + return null + } +} \ No newline at end of file diff --git a/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/utils/ResultHelper.kt b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/utils/ResultHelper.kt new file mode 100644 index 0000000..c3ad716 --- /dev/null +++ b/android/src/main/kotlin/com/yuxiaor/flutter/g_faraday/utils/ResultHelper.kt @@ -0,0 +1,64 @@ +package com.yuxiaor.flutter.g_faraday.utils + +import android.content.Intent +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import java.util.concurrent.atomic.AtomicInteger + +/** + * Author: Edward + * Date: 2020-09-02 + * Description: + */ + +/** + * Start Activity for result with args. + */ +inline fun FragmentActivity.startForResult(vararg param: Pair, noinline callback: (resultCode: Int, data: Intent?) -> Unit) { + startForResult(Intent(this, T::class.java).fillArgs(param), callback) +} + +/** + * Start Activity for result with Intent + */ +fun FragmentActivity.startForResult(intent: Intent, callback: (resultCode: Int, data: Intent?) -> Unit) { + ResultFragment.get(this).startForResult(intent, callback) +} + + +internal class ResultFragment private constructor() : Fragment() { + + companion object { + private const val TAG = "ResultFragment" + fun get(activity: FragmentActivity): ResultFragment { + val manager = activity.supportFragmentManager + var fragment = manager.findFragmentByTag(TAG) + if (fragment == null) { + fragment = ResultFragment() + manager.beginTransaction().add(fragment, TAG).commitAllowingStateLoss() + manager.executePendingTransactions() + } + return fragment as ResultFragment + } + } + + private val nextLocalRequestCode = AtomicInteger() + private val resultMap = mutableMapOf Unit>() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + retainInstance = true + } + + fun startForResult(intent: Intent, listener: (Int, Intent?) -> Unit) { + val requestCode = nextLocalRequestCode.getAndIncrement() + resultMap[requestCode] = listener + startActivityForResult(intent, requestCode) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + val listener = resultMap.remove(requestCode) + listener?.invoke(resultCode, data) + } +} \ No newline at end of file diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 0000000..f3c2053 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Exceptions to above rules. +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/example/.metadata b/example/.metadata new file mode 100644 index 0000000..bcef94e --- /dev/null +++ b/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: bbfbf1770cca2da7c82e887e4e4af910034800b6 + channel: stable + +project_type: app diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..2b91535 --- /dev/null +++ b/example/README.md @@ -0,0 +1,16 @@ +# g_faraday_example + +Demonstrates how to use the g_faraday plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000..0a741cb --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle new file mode 100644 index 0000000..3dc5a4f --- /dev/null +++ b/example/android/app/build.gradle @@ -0,0 +1,51 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 28 + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + applicationId "com.yuxiaor.flutter.g_faraday_example" + minSdkVersion 23 + targetSdkVersion 28 + versionCode 1 + versionName "1.0" + } + + buildTypes { + release { + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.2.0' +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..346d3af --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a029935 --- /dev/null +++ b/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/yuxiaor/flutter/g_faraday_example/App.kt b/example/android/app/src/main/kotlin/com/yuxiaor/flutter/g_faraday_example/App.kt new file mode 100644 index 0000000..173bcc7 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/yuxiaor/flutter/g_faraday_example/App.kt @@ -0,0 +1,18 @@ +package com.yuxiaor.flutter.g_faraday_example + +import android.app.Application +import com.yuxiaor.flutter.g_faraday.Faraday + +/** + * Author: Edward + * Date: 2020-09-02 + * Description: + */ +class App : Application() { + + + override fun onCreate() { + super.onCreate() + Faraday.startFlutterEngine() + } +} \ No newline at end of file diff --git a/example/android/app/src/main/kotlin/com/yuxiaor/flutter/g_faraday_example/FirstActivity.kt b/example/android/app/src/main/kotlin/com/yuxiaor/flutter/g_faraday_example/FirstActivity.kt new file mode 100644 index 0000000..76604eb --- /dev/null +++ b/example/android/app/src/main/kotlin/com/yuxiaor/flutter/g_faraday_example/FirstActivity.kt @@ -0,0 +1,44 @@ +package com.yuxiaor.flutter.g_faraday_example + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.widget.Button +import android.widget.TextView +import androidx.appcompat.app.AppCompatActivity +import com.yuxiaor.flutter.g_faraday.openFlutter +import com.yuxiaor.flutter.g_faraday.utils.getFlutterArgs + +/** + * Author: Edward + * Date: 2020-09-03 + * Description: + */ +class FirstActivity : AppCompatActivity() { + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + val infoTxt = findViewById(R.id.infoTxt) + val btn1 = findViewById + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist new file mode 100644 index 0000000..e9b80ee --- /dev/null +++ b/example/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + g_faraday_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/Runner/ViewControllers/FirstViewController.swift b/example/ios/Runner/ViewControllers/FirstViewController.swift new file mode 100644 index 0000000..0d40505 --- /dev/null +++ b/example/ios/Runner/ViewControllers/FirstViewController.swift @@ -0,0 +1,37 @@ +// +// FirstViewController.swift +// Runner +// +// Created by gix on 2020/9/2. +// + +import UIKit +import g_faraday + +class FirstViewController: UIViewController, FaradayResultProvider { + + var result: Any? + + @IBOutlet weak var label: UILabel? + + override func viewDidLoad() { + super.viewDidLoad() + + + } + + @IBAction func openFlutter(sender: UIButton) { + let vc = FPage.home.flutterViewController { r in + debugPrint(r.debugDescription) + } + + navigationController?.pushViewController(vc, animated: true) + + } + + @IBAction func back(sender: UIButton) { + result = ["id": 2345] + navigationController?.popViewController(animated: true) + } + +} diff --git a/example/ios/Runner/ViewControllers/FirstViewController.xib b/example/ios/Runner/ViewControllers/FirstViewController.xib new file mode 100644 index 0000000..e13d56c --- /dev/null +++ b/example/ios/Runner/ViewControllers/FirstViewController.xib @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/ios/Runner/ViewControllers/MainTabBarController.swift b/example/ios/Runner/ViewControllers/MainTabBarController.swift new file mode 100644 index 0000000..8ba49ff --- /dev/null +++ b/example/ios/Runner/ViewControllers/MainTabBarController.swift @@ -0,0 +1,10 @@ +// +// MainTabBarController.swift +// Runner +// +// Created by gix on 2020/9/2. +// + +import UIKit + +class MainTabBarController: UITabBarController {} diff --git a/example/ios/Runner/ViewControllers/Tab0ViewController.swift b/example/ios/Runner/ViewControllers/Tab0ViewController.swift new file mode 100644 index 0000000..a79ebed --- /dev/null +++ b/example/ios/Runner/ViewControllers/Tab0ViewController.swift @@ -0,0 +1,33 @@ +// +// Tab0ViewController.swift +// Runner +// +// Created by gix on 2020/9/4. +// + +import UIKit +import g_faraday + +class Tab0ViewController: UIViewController, FaradayNavigationBarHiddenProtocol { + + override func viewDidLoad() { + super.viewDidLoad() + + let vc = FPage.tab.flutterViewController() { _ in + + } + + vc.willMove(toParent: self) + addChild(vc) + view.addSubview(vc.view) + vc.didMove(toParent: self) + + vc.view.frame = view.frame; + vc.view.autoresizingMask = [.flexibleWidth, .flexibleWidth] + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(true, animated: false) + } +} diff --git a/example/ios/Runner/ViewControllers/Tab1ViewController.swift b/example/ios/Runner/ViewControllers/Tab1ViewController.swift new file mode 100644 index 0000000..95408f8 --- /dev/null +++ b/example/ios/Runner/ViewControllers/Tab1ViewController.swift @@ -0,0 +1,30 @@ +// +// Tab1ViewController.swift +// Runner +// +// Created by gix on 2020/9/2. +// + +import UIKit +import g_faraday + +class Tab1ViewController: UIViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + // Do any additional setup after loading the view. + } + + + @IBAction func openFlutterDemo(sender: UIButton) { + + let vc = FPage.home.flutterViewController { r in + sender.setTitle("result from flutter \(r ?? "none")", for: .normal) + } + + navigationController?.pushViewController(vc, animated: true); + + } + +} diff --git a/example/lib/main.dart b/example/lib/main.dart new file mode 100644 index 0000000..4a86547 --- /dev/null +++ b/example/lib/main.dart @@ -0,0 +1,31 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:g_faraday/g_faraday.dart'; + +import 'pages/embedding_page.dart'; +import 'pages/first_page.dart'; +import 'pages/home_page.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatefulWidget { + @override + _MyAppState createState() => _MyAppState(); +} + +class _MyAppState extends State { + Map routes = { + 'first_page': (RouteSettings settings) => CupertinoPageRoute(builder: (context) => FirstPage(0), settings: settings), + 'home': (RouteSettings settings) => CupertinoPageRoute(builder: (context) => HomePage(settings.arguments), settings: settings), + 'tab': (RouteSettings settings) => CupertinoPageRoute(builder: (context) => EmbeddingPage(0), settings: settings), + }; + + @override + Widget build(BuildContext context) { + return CupertinoApp( + onGenerateRoute: Faraday.factory((setttings) => routes[setttings.name](setttings)), + ); + } +} diff --git a/example/lib/pages/embedding_page.dart b/example/lib/pages/embedding_page.dart new file mode 100644 index 0000000..dff431a --- /dev/null +++ b/example/lib/pages/embedding_page.dart @@ -0,0 +1,49 @@ +import 'package:flutter/cupertino.dart'; +import 'package:g_faraday/g_faraday.dart'; + +class EmbeddingPage extends StatefulWidget { + final int value; + + EmbeddingPage(this.value); + + @override + _EmbeddingPageState createState() => _EmbeddingPageState(); +} + +class _EmbeddingPageState extends State { + int value; + + @override + void initState() { + value = widget.value; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + child: SafeArea( + child: Container( + alignment: Alignment.center, + child: Wrap( + runSpacing: 8.0, + spacing: 8.0, + children: [ + ...List.generate( + 20, + (_) => CupertinoButton.filled( + child: Text('$value'), + onPressed: () { + setState(() { + value += 2; + }); + Navigator.of(context).pushNamedFromNative('tab', present: false); + }), + ) + ], + ), + ), + ), + ); + } +} diff --git a/example/lib/pages/first_page.dart b/example/lib/pages/first_page.dart new file mode 100644 index 0000000..bda5585 --- /dev/null +++ b/example/lib/pages/first_page.dart @@ -0,0 +1,75 @@ +import 'package:flutter/cupertino.dart'; +import 'package:g_faraday/g_faraday.dart'; + +import 'package:g_faraday_example/pages/second_page.dart'; + +class FirstPage extends StatefulWidget { + final int value; + + FirstPage(this.value); + + @override + _FirstPageState createState() => _FirstPageState(); +} + +class _FirstPageState extends State { + int c; + + @override + void initState() { + c = widget.value ?? -1; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + navigationBar: CupertinoNavigationBar( + middle: const Text('Plugin example app'), + ), + child: WillPopScope( + onWillPop: () { + showDialog(context); + return Future.value(false); + }, + child: SafeArea( + child: Container( + child: Center( + child: Column(children: [ + Text('value: $c'), + CupertinoButton.filled( + child: Text('replace with native'), + onPressed: () => Navigator.of(context).pushReplacementNamed('native://native_page_first').then((r) => showResult(r)), + ), + CupertinoButton( + child: Text('push native'), + onPressed: () => Navigator.of(context).pushNamed('native://native_page_first').then((r) => showResult(r)), + ), + CupertinoButton.filled( + child: Text('push'), + onPressed: () => Navigator.of(context).push(CupertinoPageRoute(builder: (context) => FirstPage(c + 1))).then((r) => showResult(r)), + ), + CupertinoButton(child: Text('pop'), onPressed: () => Navigator.of(context).pop('pop flutter')), + CupertinoButton.filled( + child: Text('popAndPush'), onPressed: () => Navigator.of(context).popAndPushNamed('home', result: 'pop popAndPushNamed')), + CupertinoButton(child: Text('pop to native'), onPressed: () => Navigator.of(context).popUntilNative(context, 'pop popUntilNative')) + ]), + ), + ), + ), + ), + ); + } + + showResult(result) { + showCupertinoDialog( + context: context, + builder: (context) => Container( + child: Center( + child: Text('result: $result'), + ), + ), + barrierDismissible: true, + ); + } +} diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart new file mode 100644 index 0000000..4eecc84 --- /dev/null +++ b/example/lib/pages/home_page.dart @@ -0,0 +1,102 @@ +import 'package:flutter/cupertino.dart'; +import 'package:g_faraday_example/pages/first_page.dart'; +import 'package:g_faraday_example/pages/second_page.dart'; + +class HomePage extends StatefulWidget { + final Map args; + + HomePage(this.args); + + @override + _HomePageState createState() => _HomePageState(); +} + +class _HomePageState extends State { + var info = ""; + + @override + void initState() { + super.initState(); + info = "${widget.args}"; + } + + @override + Widget build(BuildContext context) { + return CupertinoPageScaffold( + child: SafeArea( + child: WillPopScope( + onWillPop: () { + //拦截返回键 + showDialog(context); + return Future.value(false); + }, + child: Container( + color: CupertinoColors.white, + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Center(child: Text("Flutter Home Page")), + button( + "pop with result", + () => Navigator.of(context).pop('[lutter pop with result]'), + ), + button( + "maybe pop", + () => Navigator.of(context).maybePop('result form maybe pop'), + ), + button( + "open Native,并等待返回数据", + () => openNativeForResult(), + ), + button( + "open Flutter", + () => Navigator.of(context).push(CupertinoPageRoute( + builder: (context) => FirstPage(-2), + )), + ), + button( + "open willscope flutter page", + () => Navigator.of(context).push(CupertinoPageRoute(builder: (context) => SecondPage())).then((v) { + setState(() { + info += "$v \n"; + }); + }), + ), + SizedBox(height: 16), + Text(info), + ], + ), + ), + ), + ), + ); + } + + /// + /// 打开原生页面,并等待返回结果 + /// + openNativeForResult() async { + final result = await Navigator.of(context).pushNamed( + "native://native_page_first", + arguments: {'data': 'data form flutter home'}, + ); + + info = info + "\n\n原生返回给Flutter的数据:\n$result"; + setState(() {}); + } + + Widget button(String text, VoidCallback callback) { + return Container( + margin: EdgeInsets.only(top: 16), + child: CupertinoButton( + color: CupertinoColors.activeBlue, + child: Text( + text, + style: TextStyle(fontSize: 14, color: CupertinoColors.white), + ), + onPressed: () => callback.call(), + ), + ); + } +} diff --git a/example/lib/pages/second_page.dart b/example/lib/pages/second_page.dart new file mode 100644 index 0000000..3952896 --- /dev/null +++ b/example/lib/pages/second_page.dart @@ -0,0 +1,45 @@ +import 'package:flutter/cupertino.dart'; + +class SecondPage extends StatefulWidget { + @override + _SecondPageState createState() => _SecondPageState(); +} + +class _SecondPageState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return WillPopScope( + onWillPop: () { + //拦截返回键 + showDialog(context); + return Future.value(false); + }, + child: CupertinoPageScaffold( + child: Center( + child: CupertinoButton( + child: Text("SecondPage"), + onPressed: () => Navigator.of(context).maybePop(), + ))), + ); + } +} + +showDialog(BuildContext context) { + showCupertinoDialog( + context: context, + builder: (c) => Center( + child: CupertinoButton.filled( + child: Text("确认返回?"), + onPressed: () { + Navigator.of(c).pop(); + Navigator.of(context).pop('value from dialog'); + }, + ), + ), + ); +} diff --git a/example/lib/test_app.dart b/example/lib/test_app.dart new file mode 100644 index 0000000..7adfefc --- /dev/null +++ b/example/lib/test_app.dart @@ -0,0 +1,8 @@ +// import 'package:flutter/cupertino.dart'; +// import 'package:g_faraday/g_faraday.dart'; + +// import 'pages/home_page.dart'; + +// class TestApp extends MiniApp with SimpleMiniAppRoutes { +// Map routes = {}; +// } diff --git a/example/pubspec.lock b/example/pubspec.lock new file mode 100644 index 0000000..13d9d67 --- /dev/null +++ b/example/pubspec.lock @@ -0,0 +1,168 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.3" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.14.13" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + g_faraday: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.1" + g_json: + dependency: transitive + description: + name: g_json + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.8" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.8" + path: + dependency: transitive + description: + name: path + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.7.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.7.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.5" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.17" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.8" +sdks: + dart: ">=2.9.0-14.0.dev <3.0.0" + flutter: ">=1.20.0 <2.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml new file mode 100644 index 0000000..d0e8c8c --- /dev/null +++ b/example/pubspec.yaml @@ -0,0 +1,68 @@ +name: g_faraday_example +description: Demonstrates how to use the g_faraday plugin. +build_name: "1.0.0" +build_number: 2 + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + g_faraday: + # When depending on this package from a real application you should use: + # g_faraday: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^0.1.3 + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart new file mode 100644 index 0000000..c73180f --- /dev/null +++ b/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:g_faraday_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/g_faraday.iml b/g_faraday.iml new file mode 100644 index 0000000..429df7d --- /dev/null +++ b/g_faraday.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..aa479fd --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,37 @@ +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +.DS_Store +*.swp +profile + +DerivedData/ +build/ +GeneratedPluginRegistrant.h +GeneratedPluginRegistrant.m + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +/Flutter/Generated.xcconfig +/Flutter/flutter_export_environment.sh \ No newline at end of file diff --git a/ios/Assets/.gitkeep b/ios/Assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/ios/Classes/Engine/EngineWrapper.swift b/ios/Classes/Engine/EngineWrapper.swift new file mode 100644 index 0000000..93b0e7c --- /dev/null +++ b/ios/Classes/Engine/EngineWrapper.swift @@ -0,0 +1,40 @@ +// +// FaradayEngine.swift +// g_faraday +// +// Created by gix on 2020/9/3. +// + +import Foundation + +class EngineWrapper { + + lazy var engine: FlutterEngine = { + // 1. initial flutter engine + let engine = FlutterEngine(name: "io.flutter.faraday", project: nil, allowHeadlessExecution: true) + + // 1.1 run + engine.run(withEntrypoint: nil) + + // 2. regist all Plugins + guard let clazz: AnyObject = NSClassFromString("GeneratedPluginRegistrant") else { + fatalError("missing GeneratedPluginRegistrant") + } + + let registerSelector: Selector = Selector(("registerWithRegistry:")) + let _ = clazz.perform(registerSelector, with: engine) + + + return engine + }() + + var viewController: FaradayFlutterViewController? { + return engine.viewController as? FaradayFlutterViewController + } + + func warm() { + debugPrint("\(engine) started") +// let vc = FaradayFlutterViewController("", maintainState: false, arguments: "", engine: engine, callback: { _ in }) +// vc.pushRoute("/") + } +} diff --git a/ios/Classes/Faraday.swift b/ios/Classes/Faraday.swift new file mode 100644 index 0000000..ab4b903 --- /dev/null +++ b/ios/Classes/Faraday.swift @@ -0,0 +1,142 @@ +// +// Faraday.swift +// g_faraday +// +// Created by gix on 2020/9/2. +// + +import Foundation +import Flutter + +public typealias CallbackToken = UUID + +public protocol FaradayNavigationDelegate: NSObjectProtocol { + + // open native view controller + func push(_ callbackToken: CallbackToken, name: String, isFlutterRoute: Bool, isPresent: Bool, arguments: Dictionary?) + + // close native view controller + func pop() -> FaradayFlutterViewController? +} + +public protocol FaradayNetProvider { + + func request(_ url: String, method: String, queryParameters: [String: Any]?, body: Any?, addtionArguments: [String: Any]?, completionHandler: @escaping (Any?) -> Void) +} + +public protocol FaradayCommonHandler { + + func canHandle(_ method: String, arguments: Any?) -> Bool + + func handle(_ method: String, arguments: Any?, completion: @escaping (Any?) -> Void) +} + +public enum PageState { + + case create(String, Any?) // name, arguments + case show(Int) // seq + case hiden(Int) // seq + case dealloc(Int) //seq + + var info: (String, Any?) { + switch self { + case .create(let name, let arguments): + return ("pageCreate", ["name": name, "args": arguments]) + case .show(let seq): + return ("pageShow", seq) + case .hiden(let seq): + return ("pageHidden", seq) + case .dealloc(let seq): + return ("pageDealloc", seq) + } + } +} + +public class Faraday { + + public static let sharedInstance = Faraday() + + public var currentFaradayViewController: FaradayFlutterViewController? { + return wrapper.engine.viewController as? FaradayFlutterViewController + } + + private init() { + debugPrint("Faraday `sharedInstance` initailed !") + } + + private weak var navigatorDelegate: FaradayNavigationDelegate? // not retain + private(set) var netProvider: FaradayNetProvider? // will be retained + private(set) var commonHandler: FaradayCommonHandler? // will be retained + + private var channel: FlutterMethodChannel? + + let wrapper = EngineWrapper() + + private var callbackCache = [UUID: FlutterResult]() + + func setup(channel: FlutterMethodChannel) { + self.channel = channel; + } + + public func startFlutterEngine(navigatorDelegate: FaradayNavigationDelegate, netProvider: FaradayNetProvider? = nil, commonHandler: FaradayCommonHandler? = nil) -> Bool { + self.navigatorDelegate = navigatorDelegate + self.netProvider = netProvider + self.commonHandler = commonHandler + wrapper.warm() + return true + } + + // create new flutter view controller + public static func createFlutterViewController(_ name: String, arguments: Any? = nil, callback: @escaping (Any?) -> () = { r in debugPrint("result not be used \(String(describing: r))")}) -> FlutterViewController { + + let faraday = Faraday.sharedInstance; + + let vc = FaradayFlutterViewController(name, arguments: arguments, engine: faraday.wrapper.engine, callback: callback) + + return vc + } + + public static func sendPageState(_ state: PageState, result: @escaping (Any?) -> Void) { + let info = state.info; + Faraday.sharedInstance.channel?.invokeMethod(info.0, arguments: info.1, result: { r in + result(r) + }) + } + + public static func refreshViewController(_ viewController: FaradayFlutterViewController) { + viewController.engine?.viewController = nil + + viewController.viewWillAppear(false) + viewController.viewDidLayoutSubviews() + viewController.viewDidAppear(false) + } + + public static func callback(_ token: CallbackToken?, result: Any?) { + if let t = token { + if let cb = Faraday.sharedInstance.callbackCache.removeValue(forKey: t) { + cb(result) + } + } + } + + func push(native arguments: Any?, callback: @escaping FlutterResult) { + let uuid = UUID() + callbackCache[uuid] = callback + + guard let arg = arguments as? Dictionary, let name = arg["name"] as? String else { + fatalError("arguments invalid") + } + + let isFlutterRoute = arg["isFlutterRoute"] as? Bool ?? false + let isPresent = arg["present"] as? Bool ?? false + + navigatorDelegate?.push(uuid, name: name, isFlutterRoute: isFlutterRoute, isPresent: isPresent, arguments: arg["arguments"] as? [String: Any]) + } + + func pop(flutterContainer arguments: Any?, callback: FlutterResult) { + let vc = navigatorDelegate?.pop() + vc?.callback?(arguments) + callback(nil) + } + +} diff --git a/ios/Classes/GFaradayPlugin.h b/ios/Classes/GFaradayPlugin.h new file mode 100644 index 0000000..fb63498 --- /dev/null +++ b/ios/Classes/GFaradayPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface GFaradayPlugin : NSObject +@end diff --git a/ios/Classes/GFaradayPlugin.m b/ios/Classes/GFaradayPlugin.m new file mode 100644 index 0000000..e9728cf --- /dev/null +++ b/ios/Classes/GFaradayPlugin.m @@ -0,0 +1,15 @@ +#import "GFaradayPlugin.h" +#if __has_include() +#import +#else +// Support project import fallback if the generated compatibility header +// is not copied when this plugin is created as a library. +// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 +#import "g_faraday-Swift.h" +#endif + +@implementation GFaradayPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftGFaradayPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/ios/Classes/SwiftGFaradayPlugin.swift b/ios/Classes/SwiftGFaradayPlugin.swift new file mode 100644 index 0000000..ab148c6 --- /dev/null +++ b/ios/Classes/SwiftGFaradayPlugin.swift @@ -0,0 +1,75 @@ +import Flutter +import UIKit + +public class SwiftGFaradayPlugin: NSObject, FlutterPlugin { + + var netChannel: FlutterMethodChannel? + var commonChannel: FlutterMethodChannel? + + public static func register(with registrar: FlutterPluginRegistrar) { + // + let channel = FlutterMethodChannel(name: "g_faraday", binaryMessenger: registrar.messenger()) + let commonChannel = FlutterMethodChannel(name: "g_faraday/common", binaryMessenger: registrar.messenger()) + let netChannel = FlutterMethodChannel(name: "g_faraday/net", binaryMessenger: registrar.messenger()) + + let instance = SwiftGFaradayPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + Faraday.sharedInstance.setup(channel: channel) + + // + instance.netChannel = netChannel + instance.netChannel?.setMethodCallHandler({ call, result in + guard call.method == "request" else { + result(FlutterMethodNotImplemented) + return + } + guard let netProvider = Faraday.sharedInstance.netProvider else { + result(FlutterMethodNotImplemented) + return + } + guard var args = call.arguments as? [String: Any] else { + result(FlutterError(code: "1", message: "arguments not valid", details: "\(call.arguments ?? "")")) + return + } + guard let url = args.removeValue(forKey: "url") as? String, !url.isEmpty else { + result(FlutterError(code: "2", message: "url not valid", details: "\(call.arguments ?? "")")) + return + } + guard let method = args.removeValue(forKey: "method") as? String, !method.isEmpty else { + result(FlutterError(code: "3", message: "method not valid", details: "\(call.arguments ?? "")")) + return + } + let queryParameters = args.removeValue(forKey: "queryParameters") as? [String: Any] + let body = args.removeValue(forKey: "body") + + netProvider.request(url, method: method, queryParameters: queryParameters, body: body, addtionArguments: args.isEmpty ? nil : args) { r in + result(r) + } + }) + + // + instance.commonChannel = commonChannel + instance.commonChannel?.setMethodCallHandler({ call, result in + guard let cp = Faraday.sharedInstance.commonHandler, cp.canHandle(call.method, arguments: call.arguments) else { + result(FlutterMethodNotImplemented) + return + } + cp.handle(call.method, arguments: call.arguments, completion: result) + }) + + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + if (call.method == "getPlatformVersion") { + result("iOS " + UIDevice.current.systemVersion) + } else if (call.method == "pushNativePage") { + Faraday.sharedInstance.push(native: call.arguments, callback: result) + } else if (call.method == "popContainer") { + Faraday.sharedInstance.pop(flutterContainer: call.arguments, callback: result) + } else if (call.method == "disableHorizontalSwipePopGesture") { + Faraday.sharedInstance.wrapper.viewController?.disableHorizontalSwipePopGesture(disable: call.arguments as? Bool ?? false) + result(nil) + } + } + +} diff --git a/ios/Classes/UI/FaradayFlutterViewController.swift b/ios/Classes/UI/FaradayFlutterViewController.swift new file mode 100644 index 0000000..338b27a --- /dev/null +++ b/ios/Classes/UI/FaradayFlutterViewController.swift @@ -0,0 +1,99 @@ +// +// FaradayFlutterViewController.swift +// g_faraday +// +// Created by gix on 2020/9/2. +// + +import UIKit + +public class FaradayFlutterViewController: FlutterViewController { + + public let name: String + public let arguments: Any? + public let callback: ((Any?) -> ())? + + private var isShowing = false + private weak var previousFlutterViewController: FaradayFlutterViewController? + + var seq: Int? + + init(_ name: String, arguments: Any?, engine: FlutterEngine, callback: @escaping (Any?) ->()) { + self.name = name + self.arguments = arguments + self.callback = callback + previousFlutterViewController = engine.viewController as? FaradayFlutterViewController + engine.viewController = nil + super.init(engine: engine, nibName: nil, bundle: nil) + isShowing = true + Faraday.sendPageState(.create(name, arguments)) { [weak self] r in + self?.seq = r as? Int + debugPrint("seq: \(r!) create page succeed") + } + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + weak var interactivePopGestureRecognizerDelegate: UIGestureRecognizerDelegate? + + public override func viewDidLoad() { + super.viewDidLoad() + } + + public func disableHorizontalSwipePopGesture(disable: Bool) { + navigationController?.interactivePopGestureRecognizer?.isEnabled = !disable + } + + public override func viewWillAppear(_ animated: Bool) { + if engine?.viewController != self { + if let s = seq { + engine?.viewController = self + isShowing = true + Faraday.sendPageState(.show(s)) { r in + let succeed = r as? Bool ?? false + debugPrint("seq: \(s) send pageState `show` \(succeed ? "succeed" : "failed")") + } + } + } + super.viewWillAppear(animated) + } + + public override func viewDidAppear(_ animated: Bool) { + interactivePopGestureRecognizerDelegate = navigationController?.interactivePopGestureRecognizer?.delegate + navigationController?.interactivePopGestureRecognizer?.delegate = nil + super.viewDidAppear(animated) + } + + public override func viewWillDisappear(_ animated: Bool) { + if let p = previousFlutterViewController, p.isShowing { + Faraday.refreshViewController(p) + } + super.viewWillDisappear(animated) + } + + public override func viewDidDisappear(_ animated: Bool) { + navigationController?.interactivePopGestureRecognizer?.delegate = interactivePopGestureRecognizerDelegate + navigationController?.interactivePopGestureRecognizer?.isEnabled = true + if let s = seq { + isShowing = false + Faraday.sendPageState(.hiden(s)) { r in + let succeed = r as? Bool ?? false + debugPrint("seq: \(s) send pageState `hiden` \(succeed ? "succeed" : "failed")") + } + } + super.viewDidAppear(animated) + } + + deinit { + if let s = seq { + Faraday.sendPageState(.dealloc(s)) { r in + let succeed = r as? Bool ?? false + debugPrint("seq: \(s) send pageState `dealloc` \(succeed ? "succeed" : "failed")") + } + } + debugPrint("faraday flutter deinit") + } +} + diff --git a/ios/Classes/UI/FlutterViewControllerHolder.swift b/ios/Classes/UI/FlutterViewControllerHolder.swift new file mode 100644 index 0000000..25f6f0f --- /dev/null +++ b/ios/Classes/UI/FlutterViewControllerHolder.swift @@ -0,0 +1,12 @@ +// +// FlutterViewControllerHolder.swift +// g_faraday +// +// Created by gix on 2020/9/3. +// + +import Foundation + +class FlutterViewControllerHolder { + +} diff --git a/ios/Classes/UI/NavigationController.swift b/ios/Classes/UI/NavigationController.swift new file mode 100644 index 0000000..ccbf896 --- /dev/null +++ b/ios/Classes/UI/NavigationController.swift @@ -0,0 +1,178 @@ +// +// NavigationController.swift +// g_faraday +// +// Created by gix on 2020/9/2. +// + +import UIKit + +private let swizzle: (AnyClass, Selector, Selector) -> () = { fromClass, originalSelector, swizzledSelector in + guard + let originalMethod = class_getInstanceMethod(fromClass, originalSelector), + let swizzledMethod = class_getInstanceMethod(fromClass, swizzledSelector) + else { return } + method_exchangeImplementations(originalMethod, swizzledMethod) +} + +// 当前 ViewController 需要隐藏navigationBar时,需遵循此协议,此外如果是rootVC,则需要单独设置navigationBarHidden 不可用 navigationBar.isHidden = true 替代 +public protocol FaradayNavigationBarHiddenProtocol {} + +public protocol FaradayResultProvider: UIViewController { + var result: Any? { get } +} + +extension FaradayFlutterViewController: FaradayNavigationBarHiddenProtocol { } + +public extension UINavigationController { + + static let farady_automaticallyHandleNavigationBarHidenAndCallback: () -> () = { + + swizzle(UINavigationController.self, #selector(pushViewController(_:animated:)), #selector(faraday_pushViewController(_:animated:))) + swizzle(UINavigationController.self, #selector(popViewController(animated:)), #selector(faraday_popViewController(animated:))) + swizzle(UINavigationController.self, #selector(popToViewController(_:animated:)), #selector(faraday_popToViewController(_:animated:))) + swizzle(UINavigationController.self, #selector(popToRootViewController(animated:)), #selector(faraday_popToRootViewController(animated:))) + + UIViewController.faraday_handleCallback() + } + + func pushViewController(_ viewController: UIViewController, with callbackToken: CallbackToken, animated: Bool) { + viewController.callbackToken = callbackToken + pushViewController(viewController, animated: animated) + } + + @objc func faraday_pushViewController(_ viewController: UIViewController, animated: Bool) { + var formHidden = false + var toHidden = false + + if let _ = viewControllers.last as? FaradayNavigationBarHiddenProtocol { + formHidden = true + } + if let _ = viewController as? FaradayNavigationBarHiddenProtocol { + toHidden = true + } + + faraday_pushViewController(viewController, animated: animated) + if formHidden != toHidden { + setNavigationBarHidden(toHidden, animated: animated) + } + } + + @objc func faraday_popViewController(animated: Bool) -> UIViewController? { + if viewControllers.count <= 1 { + return nil + } + var formHidden = false + var toHidden = false + + if let _ = viewControllers.last as? FaradayNavigationBarHiddenProtocol { + formHidden = true + } + if let _ = viewControllers[viewControllers.count - 2] as? FaradayNavigationBarHiddenProtocol { + toHidden = true + } + + defer { + if formHidden != toHidden { + setNavigationBarHidden(toHidden, animated: animated) + } + } + let vc = faraday_popViewController(animated: animated) + if let p = vc as? FaradayResultProvider { + Faraday.callback(p.callbackToken, result: p.result) + } + return vc + } + + @objc func faraday_popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { + if viewControllers.count <= 1 { + return nil + } + var formHidden = false + var toHidden = false + + if let _ = viewControllers.last as? FaradayNavigationBarHiddenProtocol { + formHidden = true + } + + if let _ = viewController as? FaradayNavigationBarHiddenProtocol { + toHidden = true + } + + defer { + if formHidden != toHidden { + setNavigationBarHidden(toHidden, animated: animated) + } + } + let vcs = faraday_popToViewController(viewController, animated: animated) + vcs?.forEach({ vc in + if let p = vc as? FaradayResultProvider { + Faraday.callback(p.callbackToken, result: p.result) + } + }) + return vcs; + } + + @objc func faraday_popToRootViewController(animated: Bool) -> [UIViewController]? { + if viewControllers.count <= 1 { + return nil + } + var formHidden = false + var toHidden = false + + if let _ = viewControllers.last as? FaradayNavigationBarHiddenProtocol { + formHidden = true + } + if let _ = viewControllers.first as? FaradayNavigationBarHiddenProtocol { + toHidden = true + } + + defer { + if formHidden != toHidden { + setNavigationBarHidden(toHidden, animated: animated) + } + } + let vcs = faraday_popToRootViewController(animated: animated) + vcs?.forEach({ vc in + if let p = vc as? FaradayResultProvider { + Faraday.callback(p.callbackToken, result: p.result) + } + }) + return vcs; + } +} + + +public extension UIViewController { + + private struct AssociatedKeys { + static var CallbackName = "faraday_CallbackName" + } + + var callbackToken: CallbackToken? { + get { + return objc_getAssociatedObject(self, &AssociatedKeys.CallbackName) as? CallbackToken + } + set { + if let newValue = newValue { + objc_setAssociatedObject(self, &AssociatedKeys.CallbackName, newValue as CallbackToken?, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + } + } + } + + fileprivate static let faraday_handleCallback: () -> () = { + swizzle(UINavigationController.self, #selector(dismiss(animated:completion:)), #selector(faraday_dismiss(animated:completion:))) + } + + func present(_ viewControllerToPresent: UIViewController, with callbackToken: CallbackToken, animated flag: Bool, completion: (() -> Void)? = nil) { + viewControllerToPresent.callbackToken = callbackToken + present(viewControllerToPresent, animated: flag, completion: completion) + } + + @objc func faraday_dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + if let p = self as? FaradayResultProvider { + Faraday.callback(p.callbackToken, result: p.result) + } + faraday_dismiss(animated: flag, completion: completion) + } +} diff --git a/ios/g_faraday.podspec b/ios/g_faraday.podspec new file mode 100644 index 0000000..90a2a37 --- /dev/null +++ b/ios/g_faraday.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint g_faraday.podspec' to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'g_faraday' + s.version = '0.0.1' + s.summary = 'A new flutter plugin project.' + s.description = <<-DESC +A new flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '8.0' + + # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' } + s.swift_version = '5.0' +end diff --git a/lib/g_faraday.dart b/lib/g_faraday.dart new file mode 100644 index 0000000..d6867c6 --- /dev/null +++ b/lib/g_faraday.dart @@ -0,0 +1,4 @@ +export 'src/route/navigator.dart'; +export 'src/route/navigator_ext.dart'; + +export 'src/faraday.dart'; diff --git a/lib/src/faraday.dart b/lib/src/faraday.dart new file mode 100644 index 0000000..8982fa3 --- /dev/null +++ b/lib/src/faraday.dart @@ -0,0 +1,59 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; + +import 'route/native_bridge.dart'; +import 'route/route.dart'; + +typedef FaradayDecorator = Widget Function(BuildContext context, Widget child); + +class Faraday { + /// + ///`Flutter Native 容器`: iOS端是指`FaradayFlutterViewController` Android端是指 + ///`FlutterActivity`或者`FlutterFragment`容器初始化时需要指定 `name` 以及 `arguments` + ///以下统一简称容器 + /// + ///`Faraday` 内部会为每一个`容器`实例维护一个 [Navigator], 并根据`容器`参数设置 `initialRoute` + /// + /// 因为内部维护的时一个标准的Navigator对象,所以你可以像写一个纯Flutter项目那样进行页面导航以及传递参数 + /// + /// ```dart + /// @override + /// void onPress() async { + /// final result = await Navigator.of(context).pushNamed('Any Route'); + /// debugPrint(result.toString()); + /// } + /// ``` + /// 注意如果路由未找到,会自动转发到 `Native` 侧处理 + /// + ///同理关闭页面传值也很简单 + /// ```dart + /// @override + /// void onPress() async { + /// final result = await Navigator.of(context).pop('Any ...'); + /// debugPrint(result.toString()); + /// } + /// ``` + /// + static RouteFactory factory(RouteFactory rawFactory, + {FaradayDecorator decorator, RouteFactory nativeMockFactory, String mockInitialname, Object mockInitialArguments}) { + final f = (settings) { + return FaradayPageRouteBuilder( + pageBuilder: (context) { + if (kDebugMode) { + if (nativeMockFactory != null) assert(mockInitialname != null && mockInitialname.isNotEmpty); + final page = FaradayNativeBridge( + onGenerateRoute: rawFactory, + mockInitialSettings: RouteSettings(name: mockInitialname, arguments: mockInitialArguments), + mockNativeRouteFactory: nativeMockFactory, + ); + return decorator != null ? decorator(context, page) : page; + } + final page = FaradayNativeBridge(onGenerateRoute: rawFactory); + return decorator != null ? decorator(context, page) : page; + }, + settings: settings, + ); + }; + return f; + } +} diff --git a/lib/src/route/arg.dart b/lib/src/route/arg.dart new file mode 100644 index 0000000..35aaf48 --- /dev/null +++ b/lib/src/route/arg.dart @@ -0,0 +1,16 @@ +import 'package:flutter/widgets.dart'; + +import 'observer.dart'; + +class FaradayArguments { + final GlobalKey key; + final Object arguments; + final String name; + final int seq; + final observer = FaradayNavigatorObserver(); + + FaradayArguments(callArguments, String name, this.seq) + : key = GlobalKey(debugLabel: 'entry: $name'), + arguments = callArguments, + name = name; +} diff --git a/lib/src/route/native_bridge.dart b/lib/src/route/native_bridge.dart new file mode 100644 index 0000000..7d7bde3 --- /dev/null +++ b/lib/src/route/native_bridge.dart @@ -0,0 +1,167 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +import 'navigator.dart'; +import 'arg.dart'; + +const MethodChannel _channel = const MethodChannel('g_faraday'); + +class FaradayNativeBridge extends StatefulWidget { + final RouteFactory onGenerateRoute; + + final RouteFactory mockNativeRouteFactory; + final RouteSettings mockInitialSettings; + + FaradayNativeBridge({Key key, @required this.onGenerateRoute, this.mockNativeRouteFactory, this.mockInitialSettings}) : super(key: key); + + static FaradayNativeBridgeState of(BuildContext context) { + FaradayNativeBridgeState faraday; + if (context is StatefulElement && context.state is FaradayNativeBridgeState) { + faraday = context.state as FaradayNativeBridgeState; + } + return faraday ?? context.findAncestorStateOfType(); + } + + @override + FaradayNativeBridgeState createState() => FaradayNativeBridgeState(); +} + +class FaradayNativeBridgeState extends State { + List _navigatorStack = []; + int _index = 0; + int _preIndex = 0; + int _seq = 0; + + RouteSettings get _mockInitialSettings => widget.mockInitialSettings; + RouteFactory get _mockNativeRouteFactory => widget.mockNativeRouteFactory; + + @override + void initState() { + super.initState(); + _channel.setMethodCallHandler(_handler); + + if (kDebugMode) { + if (widget.mockInitialSettings != null) { + _handler(MethodCall('pageCreate', {'name': _mockInitialSettings.name, 'arg': _mockInitialSettings.arguments})); + } + } + } + + Future push( + String name, { + Map arguments, + bool present = false, + bool flutterRoute = false, + }) async { + if (kDebugMode) { + if (_mockNativeRouteFactory != null) { + if (flutterRoute) { + final key = _navigatorStack.last.key as LabeledGlobalKey; + if (key.currentState is FaradayNavigatorState) { + return (key.currentState as FaradayNavigatorState).pushNamed(name, arguments: arguments); + } + } + return Navigator.of(context, rootNavigator: true).push(_mockNativeRouteFactory(RouteSettings( + name: name, + arguments: {'present': present, 'flutterRoute': flutterRoute, if (arguments != null) ...arguments}, + ))); + } + } + // + return _channel.invokeMethod( + 'pushNativePage', + {'name': name, 'present': present, 'flutterRoute': flutterRoute, if (arguments != null) 'arguments': arguments}, + ); + } + + Future pop(Key key, [T result]) async { + if (kDebugMode) { + if (_mockNativeRouteFactory != null) { + return Navigator.of(context, rootNavigator: true).maybePop(result); + } + } + assert(_navigatorStack.isNotEmpty); + assert(_navigatorStack[_index].arg.key == key); + await _channel.invokeMethod('popContainer', result); + } + + Future disableHorizontalSwipePopGesture(bool disable) async { + if (kDebugMode) { + if (_mockNativeRouteFactory != null) { + return; + } + } + await _channel.invokeMethod('disableHorizontalSwipePopGesture', disable); + } + + bool isOnTop(Key key) { + return _navigatorStack.isNotEmpty && _navigatorStack[_index].arg.key == key; + } + + @override + Widget build(BuildContext context) { + if (_navigatorStack.isEmpty || _index == -1) return Container(); + + return IndexedStack( + children: _navigatorStack, + index: _index, + ); + } + + Future _handler(MethodCall call) { + int index() { + int seq = call.arguments as int; + final index = _navigatorStack.indexWhere((n) => n.arg.seq == seq); + assert(index != -1, 'page not found'); + return index; + } + + switch (call.method) { + case 'pageCreate': + String name = call.arguments['name']; + final arg = FaradayArguments(call.arguments, name, _seq++); + _navigatorStack.add(appRoot(arg)); + _updateIndex(_navigatorStack.length - 1); + return Future.value(arg.seq); + case 'pageShow': + _updateIndex(index()); + return Future.value(true); + case 'pageHidden': + if (index() == _index) _updateIndex(_preIndex); + return Future.value(true); + case 'pageDealloc': + final current = _navigatorStack[_index]; + _navigatorStack.removeAt(index()); + _updateIndex(_navigatorStack.indexOf(current)); + return Future.value(true); + default: + return Future.value(false); + } + } + + void _updateIndex(int index) { + if (index >= _navigatorStack.length || index < 0) return; + setState(() { + _preIndex = _index; + _index = index; + debugPrint('index: $_index, preIndex: $_preIndex, max_seq: $_seq'); + }); + } + + FaradayNavigator appRoot(FaradayArguments arg) { + return FaradayNavigator( + key: GlobalKey(debugLabel: 'seq: ${arg.seq}'), + arg: arg, + initialRoute: arg.name, + onGenerateRoute: widget.onGenerateRoute, + onGenerateInitialRoutes: (navigator, initialRoute) => [ + widget.onGenerateRoute(RouteSettings(name: initialRoute, arguments: arg.arguments)), + ], + ); + } +} diff --git a/lib/src/route/navigator.dart b/lib/src/route/navigator.dart new file mode 100644 index 0000000..6106b85 --- /dev/null +++ b/lib/src/route/navigator.dart @@ -0,0 +1,94 @@ +import 'package:flutter/widgets.dart'; + +import 'arg.dart'; +import 'native_bridge.dart'; +import 'observer.dart'; + +class FaradayNavigator extends Navigator { + final FaradayArguments arg; + + FaradayNavigator( + {Key key, + List pages = const >[], + PopPageCallback onPopPage, + String initialRoute, + RouteListFactory onGenerateInitialRoutes, + RouteFactory onGenerateRoute, + RouteFactory onUnknownRoute, + DefaultTransitionDelegate transitionDelegate = const DefaultTransitionDelegate(), + this.arg, + List observers}) + : super( + key: key, + pages: pages, + onPopPage: onPopPage, + initialRoute: initialRoute, + onGenerateInitialRoutes: onGenerateInitialRoutes, + onGenerateRoute: onGenerateRoute, + onUnknownRoute: onUnknownRoute, + transitionDelegate: transitionDelegate, + observers: [ + arg.observer, + if (observers != null) ...observers, + ]); + + @override + FaradayNavigatorState createState() => FaradayNavigatorState(); + + static FaradayNavigatorState of(BuildContext context) { + FaradayNavigatorState faraday; + if (context is StatefulElement && context.state is FaradayNavigatorState) { + faraday = context.state as FaradayNavigatorState; + } + return faraday ?? context.findAncestorStateOfType(); + } +} + +class FaradayNavigatorState extends NavigatorState with WidgetsBindingObserver { + @override + FaradayNavigator get widget => super.widget; + + FaradayNavigatorObserver get observer => widget.arg.observer; + + @override + void initState() { + observer.disableHorizontalSwipePopGesture.addListener(notifyNativeDisableOrEnableBackGesture); + WidgetsBinding.instance.addObserver(this); + super.initState(); + } + + void notifyNativeDisableOrEnableBackGesture() { + FaradayNativeBridge.of(context).disableHorizontalSwipePopGesture(observer.disableHorizontalSwipePopGesture.value); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + // WidgetsBindingObserver + @override + Future didPopRoute() { + if (!FaradayNativeBridge.of(context).isOnTop(widget.arg.key)) return Future.value(false); + return maybePop(); + } + + @override + Future pushNamed(String routeName, {Object arguments}) { + try { + return super.pushNamed(routeName, arguments: arguments); + } catch (e) { + return FaradayNativeBridge.of(context).push(routeName, arguments: arguments); + } + } + + @override + void pop([T result]) { + if (observer.onlyOnePage) { + FaradayNativeBridge.of(context).pop(widget.arg.key, result); + } else { + super.pop(); + } + } +} diff --git a/lib/src/route/navigator_ext.dart b/lib/src/route/navigator_ext.dart new file mode 100644 index 0000000..cb14991 --- /dev/null +++ b/lib/src/route/navigator_ext.dart @@ -0,0 +1,19 @@ +import 'package:flutter/widgets.dart'; + +import 'navigator.dart'; +import 'native_bridge.dart'; + +extension NavigatorStateX on NavigatorState { + Future popUntilNative(BuildContext context, [T result]) { + return FaradayNativeBridge.of(context).pop(FaradayNavigator.of(context).widget.arg.key, result); + } + + Future pushNamedFromNative(String routeName, {Object arguments, bool present = false, bool flutterRoute = true}) { + return FaradayNativeBridge.of(context).push( + routeName, + arguments: arguments, + flutterRoute: flutterRoute, + present: present, + ); + } +} diff --git a/lib/src/route/observer.dart b/lib/src/route/observer.dart new file mode 100644 index 0000000..0cec0c3 --- /dev/null +++ b/lib/src/route/observer.dart @@ -0,0 +1,49 @@ +import 'package:flutter/widgets.dart'; + +class FaradayNavigatorObserver extends NavigatorObserver { + int _pageNumInStack = 0; + + bool get onlyOnePage => _pageNumInStack == 1; + + ValueNotifier get disableHorizontalSwipePopGesture => _disableHorizontalSwipePopGesture; + final _disableHorizontalSwipePopGesture = ValueNotifier(false); + + @override + void didPush(Route route, Route previousRoute) { + _increment(route); + } + + void didReplace({Route newRoute, Route oldRoute}) { + _update(page: _pageNumInStack, route: newRoute); + } + + @override + void didPop(Route route, Route previousRoute) { + _decrement(previousRoute); + } + + @override + void didRemove(Route route, Route previousRoute) { + _decrement(previousRoute); + } + + void _increment(Route route) { + _update(page: _pageNumInStack + 1, route: route); + } + + void _decrement(Route route) { + _update(page: _pageNumInStack - 1, route: route); + } + + void _update({@required int page, Route route}) { + _pageNumInStack = page; + Future.delayed(Duration(milliseconds: 200)).then((_) { + if (route is ModalRoute) { + // ignore: invalid_use_of_protected_member + _disableHorizontalSwipePopGesture.value = route.hasScopedWillPopCallback || !onlyOnePage; + } else { + _disableHorizontalSwipePopGesture.value = !onlyOnePage; + } + }); + } +} diff --git a/lib/src/route/route.dart b/lib/src/route/route.dart new file mode 100644 index 0000000..382d0fb --- /dev/null +++ b/lib/src/route/route.dart @@ -0,0 +1,24 @@ +import 'package:flutter/cupertino.dart'; + +class FaradayPageRouteBuilder extends PageRouteBuilder { + FaradayPageRouteBuilder({ + RouteSettings settings, + @required WidgetBuilder pageBuilder, + // this.transitionsBuilder = _defaultTransitionsBuilder, + // this.transitionDuration = const Duration(milliseconds: 300), + // this.opaque = true, + // this.barrierDismissible = false, + // this.barrierColor, + // this.barrierLabel, + }) : assert(pageBuilder != null), + super( + transitionDuration: Duration(microseconds: 0), + settings: settings, + pageBuilder: (context, _, __) => pageBuilder(context), + maintainState: false); // disab + + @override + Future willPop() { + return Future.value(RoutePopDisposition.bubble); + } +} diff --git a/lib/src/widgets/debugger.dart b/lib/src/widgets/debugger.dart new file mode 100644 index 0000000..4f66350 --- /dev/null +++ b/lib/src/widgets/debugger.dart @@ -0,0 +1,16 @@ +import 'package:flutter/widgets.dart'; + +class FaradayDebugger extends StatefulWidget { + @override + _FaradayDebuggerState createState() => _FaradayDebuggerState(); +} + +class _FaradayDebuggerState extends State { + @override + Widget build(BuildContext context) { + return Container( + alignment: Alignment.center, + child: Text('Debugger'), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..b8fa2ce --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,154 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + async: + dependency: transitive + description: + name: async + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.4.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.3" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.14.13" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + g_json: + dependency: "direct main" + description: + name: g_json + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.1" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.12.8" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.8" + path: + dependency: transitive + description: + name: path + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.7.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.7.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.9.5" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.5" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.2.17" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.2.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.8" +sdks: + dart: ">=2.9.0-14.0.dev <3.0.0" + flutter: ">=1.20.0 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..8839c3c --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,64 @@ +name: g_faraday +description: A new flutter plugin project. +version: 0.0.1 +author: +homepage: "sss" + +environment: + sdk: ">=2.7.0 <3.0.0" + flutter: ">=1.20.0 <2.0.0" + +dependencies: + flutter: + sdk: flutter + g_json: ^1.2.0 + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' and Android 'package' identifiers should not ordinarily + # be modified. They are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + android: + package: com.yuxiaor.flutter.g_faraday + pluginClass: GFaradayPlugin + ios: + pluginClass: GFaradayPlugin + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/test/g_faraday_test.dart b/test/g_faraday_test.dart new file mode 100644 index 0000000..f8e4648 --- /dev/null +++ b/test/g_faraday_test.dart @@ -0,0 +1,22 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const MethodChannel channel = MethodChannel('g_faraday'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return '42'; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('getPlatformVersion', () async { + // expect(await GFaraday.platformVersion, '42'); + }); +} diff --git a/test/route/push_test.dart b/test/route/push_test.dart new file mode 100644 index 0000000..e0b09cf --- /dev/null +++ b/test/route/push_test.dart @@ -0,0 +1,25 @@ +// import 'package:flutter/services.dart'; +// import 'package:flutter_test/flutter_test.dart'; + +// void main() { +// const MethodChannel channel = MethodChannel('g_faraday'); + +// TestWidgetsFlutterBinding.ensureInitialized(); + +// setUp(() { +// channel.setMockMethodCallHandler((MethodCall methodCall) async { +// if (methodCall.method == 'pushNativePage') { +// return methodCall.arguments; +// } +// return null; +// }); +// }); + +// tearDown(() { +// channel.setMockMethodCallHandler(null); +// }); + +// test('pushNativePage', () async { +// expect(Faraday); +// }); +// }