The default implementation returns {@code null}, which will cause the
64 | * activity to use a newly instantiated full-screen view.
65 | */
66 | @Override
67 | public XFlutterView createFlutterView(Context context) {
68 | if(flutterView!=null)
69 | return flutterView;
70 | flutterView = new XFlutterView(this,null,createFlutterNativeView());
71 | return flutterView;
72 | }
73 |
74 | /**
75 | * Hook for subclasses to customize the creation of the
76 | * {@code FlutterNativeView}.
77 | *
78 | * The default implementation returns {@code null}, which will cause the
79 | * activity to use a newly instantiated native view object.
80 | */
81 | @Override
82 | public FlutterNativeView createFlutterNativeView() {
83 | if(nativeView!=null)
84 | return nativeView;
85 | nativeView = new FlutterNativeView(this.getApplicationContext());
86 | return nativeView;
87 | }
88 |
89 | private boolean isFlutterViewAttachedOnMe(){
90 | FrameLayout rootView = (FrameLayout) findViewById(R.id.flutter_rootview);
91 | XFlutterView flutterView = getFlutterView();
92 | ViewGroup priorParent = (ViewGroup) flutterView.getParent();
93 | return rootView == priorParent;
94 | }
95 |
96 | @Override
97 | public final boolean hasPlugin(String key) {
98 | return pluginRegistry.hasPlugin(key);
99 | }
100 |
101 | @Override
102 | public final T valuePublishedByPlugin(String pluginKey) {
103 | return pluginRegistry.valuePublishedByPlugin(pluginKey);
104 | }
105 | @Override
106 | public final Registrar registrarFor(String pluginKey) {
107 | return pluginRegistry.registrarFor(pluginKey);
108 | }
109 | @Override
110 | protected void onCreate(Bundle savedInstanceState) {
111 | boolean firstLaunch = (nativeView==null?true:false);
112 |
113 | super.onCreate(savedInstanceState);
114 | checkIfInitActivityDelegate();
115 | eventDelegate.onCreate(savedInstanceState);
116 |
117 | if(firstLaunch){
118 | eventDelegate.runFlutterBundle();
119 | Class> c = null;
120 | try {
121 | c = Class.forName("io.flutter.plugins.GeneratedPluginRegistrant");
122 | Method method = c.getMethod("registerWith",PluginRegistry.class);
123 | method.invoke(null,pluginRegistry);
124 | } catch (ClassNotFoundException e) {
125 | e.printStackTrace();
126 | } catch (NoSuchMethodException e) {
127 | e.printStackTrace();
128 | } catch (InvocationTargetException e) {
129 | e.printStackTrace();
130 | } catch (IllegalAccessException e) {
131 | e.printStackTrace();
132 | }
133 | }
134 | else{
135 | try {
136 | flutterView.registerReceiver();
137 | }catch (Exception e){
138 | Log.e( "FlutterWrapperActivity ","onCreate flutterView.registerReceiver error" );
139 | }
140 | }
141 | setContentView(R.layout.flutter_layout);
142 | checkIfAddFlutterView();
143 | fakeSnapImgView = (ImageView) findViewById(R.id.flutter_snap_imageview);
144 | fakeSnapImgView.setVisibility(View.GONE);
145 | //Process Intent Extra
146 | Intent intent = getIntent();
147 | Bundle bundle = intent.getExtras();
148 | Uri uri = intent.getData();
149 | if(uri!=null){
150 | HybridStackManager.sharedInstance().openUrlFromFlutter(uri.toString(),null,null);
151 | }
152 | else if(bundle!=null){
153 | HybridStackManager.sharedInstance().openUrlFromFlutter(intent.getStringExtra("url"),(HashMap)intent.getSerializableExtra("query"),(HashMap)intent.getSerializableExtra("params"));
154 | }
155 | flutterWrapperInstCnt++;
156 | }
157 |
158 |
159 | @Override
160 | protected void onResume() {
161 | super.onResume();
162 | fakeSnapImgView.setVisibility(View.GONE);
163 | checkIfAddFlutterView();
164 | if(isFlutterViewAttachedOnMe())
165 | eventDelegate.onResume();
166 | HybridStackManager.sharedInstance().curFlutterActivity = this;
167 | isActive = true;
168 | if(lastbitmap!=null && curFlutterRouteName!=null && curFlutterRouteName.length()>0 ){
169 | HybridStackManager.sharedInstance().methodChannel.invokeMethod("popToRouteNamed",curFlutterRouteName);
170 | }
171 | }
172 |
173 | private void destorybitmap() {
174 | if (lastbitmap != null && !lastbitmap.isRecycled()) {
175 | lastbitmap.recycle();
176 | lastbitmap = null;
177 | }
178 | fakeSnapImgView.setImageBitmap(null);
179 | }
180 |
181 | @Override
182 | protected void onDestroy() {
183 | // eventDelegate.onDestroy();
184 | flutterWrapperInstCnt--;
185 | if(flutterWrapperInstCnt==0){
186 | HybridStackManager.sharedInstance().methodChannel.invokeMethod("popToRoot",null);
187 | }
188 | isActive = false;
189 | try {
190 | destorybitmap();
191 | flutterView.unregisterReceiver();
192 | }catch (Exception e){
193 | Log.e( "FlutterWrapperActivity ","onDestroy flutterView.unregisterReceiver error" );
194 | }
195 | super.onDestroy();
196 | }
197 |
198 | @Override
199 | public void onBackPressed() {
200 | popCurActivity();
201 | }
202 |
203 | @Override
204 | protected void onPause() {
205 | super.onPause();
206 | if(isFlutterViewAttachedOnMe())
207 | eventDelegate.onPause();
208 | isActive = false;
209 | }
210 |
211 | @Override
212 | protected void onStart(){
213 | super.onStart();
214 | eventDelegate.onStart();
215 | HybridStackManager.sharedInstance().curFlutterActivity = this;
216 | isActive = true;
217 | }
218 |
219 | @Override
220 | protected void onStop() {
221 | FrameLayout rootView = (FrameLayout) findViewById(R.id.flutter_rootview);
222 | XFlutterView flutterView = getFlutterView();
223 | ViewGroup priorParent = (ViewGroup) flutterView.getParent();
224 | if(isFlutterViewAttachedOnMe())
225 | eventDelegate.onStop();
226 | super.onStop();
227 | if(super.isFinishing()){
228 | HybridStackManager.sharedInstance().methodChannel.invokeMethod("popRouteNamed",curFlutterRouteName);
229 | if (priorParent == rootView) {
230 | priorParent.removeView(flutterView);
231 | }
232 | }
233 | }
234 |
235 | @Override
236 | protected void onPostResume() {
237 | super.onPostResume();
238 | if(isFlutterViewAttachedOnMe())
239 | eventDelegate.onPostResume();
240 | }
241 |
242 | // @Override - added in API level 23
243 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
244 | if(isFlutterViewAttachedOnMe())
245 | eventDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
246 | }
247 |
248 | @Override
249 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
250 | if(!(isFlutterViewAttachedOnMe() && eventDelegate.onActivityResult(requestCode, resultCode, data))){
251 | super.onActivityResult(requestCode, resultCode, data);
252 | }
253 | }
254 |
255 | @Override
256 | protected void onNewIntent(Intent intent) {
257 | if(isFlutterViewAttachedOnMe())
258 | eventDelegate.onNewIntent(intent);
259 | }
260 |
261 | @Override
262 | public void onUserLeaveHint() {
263 | if(isFlutterViewAttachedOnMe())
264 | eventDelegate.onUserLeaveHint();
265 | }
266 |
267 | @Override
268 | public void onTrimMemory(int level) {
269 | if(isFlutterViewAttachedOnMe())
270 | eventDelegate.onTrimMemory(level);
271 | }
272 |
273 | @Override
274 | public void onLowMemory() {
275 | if(isFlutterViewAttachedOnMe())
276 | eventDelegate.onLowMemory();
277 | }
278 |
279 | @Override
280 | public void onConfigurationChanged(Configuration newConfig) {
281 | super.onConfigurationChanged(newConfig);
282 | if(isFlutterViewAttachedOnMe())
283 | eventDelegate.onConfigurationChanged(newConfig);
284 | }
285 |
286 | //ActivityDelegate Related
287 | void checkIfInitActivityDelegate(){
288 | if(nativeView==null){
289 | Intent intent = getIntent();
290 | Bundle bundle = intent.getExtras();
291 | Uri uri = intent.getData();
292 | HashMap arguments = new HashMap();
293 | if(uri!=null){
294 | arguments = HybridStackManager.assembleChanArgs(uri.toString(),null,null);
295 | }
296 | else if(bundle!=null){
297 | arguments = HybridStackManager.assembleChanArgs(intent.getStringExtra("url"),(HashMap)intent.getSerializableExtra("query"),(HashMap)intent.getSerializableExtra("params"));
298 | }
299 | HybridStackManager.sharedInstance().mainEntryParams = arguments;
300 | }
301 | if(delegate == null) {
302 | delegate = new XFlutterActivityDelegate(this, this);
303 | }
304 | else {
305 | delegate.resetActivity(this);
306 | }
307 | eventDelegate = delegate;
308 | pluginRegistry = delegate;
309 | }
310 |
311 | @Override
312 | public boolean isActive() {
313 | return isActive;
314 | }
315 |
316 | public void openUrl(String url) {
317 | HybridStackManager.sharedInstance().curFlutterActivity = null;
318 | if(url.contains("flutter=true")){
319 | Intent intent = new Intent(FlutterWrapperActivity.this, FlutterWrapperActivity.class);
320 | intent.setAction(Intent.ACTION_RUN);
321 | intent.setData(Uri.parse(url));
322 | this.innerStartActivity(intent,true);
323 | }
324 | else{
325 | Uri tmpUri = Uri.parse(url);
326 | String tmpUrl = String.format("%s://%s",tmpUri.getScheme(),tmpUri.getHost());
327 | HashMap query = new HashMap();
328 | for(String key : tmpUri.getQueryParameterNames()){
329 | query.put(key,tmpUri.getQueryParameter(key));
330 | }
331 | XURLRouter.sharedInstance().openUrlWithQueryAndParams(tmpUrl,query,null);
332 | saveFinishSnapshot(false);
333 | }
334 | }
335 |
336 | public void innerStartActivity(Intent intent,boolean showSnapshot){
337 | this.startActivity(intent);
338 | saveFinishSnapshot(showSnapshot);
339 | }
340 |
341 |
342 | @Override
343 | public void setCurFlutterRouteName(String curFlutterRouteName){
344 | this.curFlutterRouteName = curFlutterRouteName;
345 | }
346 |
347 | @Override
348 | public void popCurActivity() {
349 | finish();
350 | saveFinishSnapshot(true);
351 | }
352 |
353 | //Flutter View Related Logic
354 | void checkIfAddFlutterView() {
355 | final FrameLayout rootView = (FrameLayout) findViewById(R.id.flutter_rootview);
356 | final XFlutterView flutterView = getFlutterView();
357 | ViewGroup priorParent = (ViewGroup) flutterView.getParent();
358 | if (priorParent == rootView) {
359 | return;
360 | }
361 | final ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
362 | FrameLayout.LayoutParams.MATCH_PARENT);
363 | final FlutterWrapperActivity activity = this;
364 | if (priorParent != null) {
365 | priorParent.removeView(flutterView);
366 | final Handler handler = new Handler();
367 | handler.postDelayed(new Runnable() {
368 | @Override
369 | public void run() {
370 | //Do something after delay of 20ms
371 | if (flutterView.getParent() == null && activity.isActive==true ) {
372 | rootView.addView(flutterView, params);
373 | flutterView.resetActivity(activity);
374 | }
375 | }
376 | }, 20);
377 | }
378 | else{
379 | rootView.addView(flutterView, params);
380 | flutterView.resetActivity(activity);
381 | }
382 | }
383 |
384 | void saveFinishSnapshot(boolean showSnapshot){
385 | XFlutterView fv = getFlutterView();
386 | lastbitmap = fv.getBitmap();
387 | fakeSnapImgView.setImageBitmap(lastbitmap);
388 | if(showSnapshot)
389 | fakeSnapImgView.setVisibility(View.VISIBLE);
390 | }
391 | }
392 |
--------------------------------------------------------------------------------
/android/src/main/java/com/taobao/hybridstackmanager/HybridStackManager.java:
--------------------------------------------------------------------------------
1 | package com.taobao.hybridstackmanager;
2 | import android.net.Uri;
3 |
4 | import java.net.URLEncoder;
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import android.os.Build;
9 | import io.flutter.plugin.common.MethodCall;
10 | import io.flutter.plugin.common.MethodChannel;
11 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
12 | import io.flutter.plugin.common.MethodChannel.Result;
13 | import io.flutter.plugin.common.PluginRegistry.Registrar;
14 |
15 | /**
16 | * hybridstackmanager
17 | */
18 | public class HybridStackManager implements MethodCallHandler {
19 | /**
20 | * Plugin registration.
21 | */
22 | private static HybridStackManager hybridStackManager = null;
23 | public MethodChannel methodChannel;
24 | public FlutterActivityChecker curFlutterActivity;
25 | public HashMap mainEntryParams;
26 | public HashMap deviceInfoParams;
27 |
28 | public static HybridStackManager sharedInstance() {
29 | if (hybridStackManager != null) { return hybridStackManager; }
30 | hybridStackManager = new HybridStackManager();
31 | return hybridStackManager;
32 | }
33 |
34 | public static void registerWith(Registrar registrar) {
35 | hybridStackManager = HybridStackManager.sharedInstance();
36 | hybridStackManager.methodChannel = new MethodChannel(registrar.messenger(), "hybrid_stack_manager");
37 | hybridStackManager.methodChannel.setMethodCallHandler(hybridStackManager);
38 | }
39 |
40 | public static HashMap assembleChanArgs(String url, HashMap query, HashMap params) {
41 | HashMap arguments = new HashMap();
42 | Uri uri = Uri.parse(url);
43 | String tmpUrl = String.format("%s://%s", uri.getScheme(), uri.getHost());
44 | HashMap tmpQuery = new HashMap();
45 | if (query != null) { tmpQuery.putAll(query); }
46 | for (String key : uri.getQueryParameterNames()) {
47 | tmpQuery.put(key, uri.getQueryParameter(key));
48 | }
49 |
50 | HashMap tmpParams = new HashMap();
51 | if (params != null) { tmpQuery.putAll(params); }
52 |
53 | if (tmpUrl != null) { arguments.put("url", tmpUrl); }
54 | if (tmpQuery != null) { arguments.put("query", tmpQuery); }
55 | if (tmpParams != null) { arguments.put("params", tmpParams); }
56 | return arguments;
57 | }
58 |
59 | public static String concatUrl(String url, HashMap query, HashMap params) {
60 | // assert(params==null||params.size()==0);
61 | Uri uri = Uri.parse(url);
62 | Uri.Builder builder = uri.buildUpon();
63 | if (query != null) {
64 | for (Object key : query.keySet()) {
65 | Object value = query.get(key);
66 | if (value != null) {
67 | final String str;
68 | str = value.toString();
69 | builder.appendQueryParameter(String.valueOf(key),str);
70 | }
71 | }
72 | }
73 | return builder.build().toString();
74 | }
75 |
76 | public void openUrlFromFlutter(String url, HashMap query, HashMap params) {
77 | HybridStackManager.sharedInstance().methodChannel.invokeMethod("openURLFromFlutter",
78 | assembleChanArgs(url, query, params));
79 | }
80 |
81 | @Override
82 | public void onMethodCall(MethodCall call, Result result) {
83 | if (call.method.equals("openUrlFromNative")) {
84 | if (curFlutterActivity != null && curFlutterActivity.isActive()) {
85 | HashMap openUrlInfo = (HashMap)call.arguments;
86 | String url = (String)openUrlInfo.get("url");
87 | HashMap query = (HashMap)openUrlInfo.get("query");
88 | HashMap params = (HashMap)openUrlInfo.get("params");
89 | String concatUrl = concatUrl(url, query, params);
90 | curFlutterActivity.openUrl(concatUrl);
91 | }
92 | result.success("OK");
93 | } else if (call.method.equals("getMainEntryParams")) {
94 | if (mainEntryParams == null) { mainEntryParams = new HashMap(); }
95 | result.success(mainEntryParams);
96 | // mainEntryParams = null;
97 | } else if (call.method.equals("updateCurFlutterRoute")) {
98 | String curRouteName = (String)call.arguments;
99 | if (curFlutterActivity != null && curFlutterActivity.isActive()) {
100 | curFlutterActivity.setCurFlutterRouteName(curRouteName);
101 | }
102 | result.success("OK");
103 | } else if (call.method.equals("popCurPage")) {
104 | if (curFlutterActivity != null && curFlutterActivity.isActive()) {
105 | curFlutterActivity.popCurActivity();
106 | }
107 | result.success("OK");
108 | }else {
109 | result.notImplemented();
110 | }
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/android/src/main/java/com/taobao/hybridstackmanager/XFlutterActivityDelegate.java:
--------------------------------------------------------------------------------
1 | // Copyright 2017 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 |
5 | package com.taobao.hybridstackmanager;
6 |
7 | import android.animation.Animator;
8 | import android.animation.AnimatorListenerAdapter;
9 | import android.app.Activity;
10 | import android.app.Application;
11 | import android.content.Context;
12 | import android.content.Intent;
13 | import android.content.pm.ActivityInfo;
14 | import android.content.pm.ApplicationInfo;
15 | import android.content.pm.PackageManager;
16 | import android.content.pm.PackageManager.NameNotFoundException;
17 | import android.content.res.Configuration;
18 | import android.content.res.Resources.NotFoundException;
19 | import android.graphics.drawable.Drawable;
20 | import android.os.Build;
21 | import android.os.Bundle;
22 | import android.util.Log;
23 | import android.util.TypedValue;
24 | import android.view.View;
25 | import android.view.ViewGroup;
26 | import android.view.Window;
27 | import android.view.WindowManager.LayoutParams;
28 | import io.flutter.plugin.common.PluginRegistry;
29 | import io.flutter.plugin.platform.PlatformPlugin;
30 | import io.flutter.util.Preconditions;
31 | import io.flutter.view.FlutterMain;
32 | import io.flutter.view.FlutterNativeView;
33 | import io.flutter.view.FlutterView;
34 | import java.util.ArrayList;
35 | import io.flutter.app.FlutterActivityEvents;
36 |
37 | /**
38 | * Class that performs the actual work of tying Android {@link Activity}
39 | * instances to Flutter.
40 | *
41 | * This exists as a dedicated class (as opposed to being integrated directly
42 | * into {@link FlutterActivity}) to facilitate applications that don't wish
43 | * to subclass {@code FlutterActivity}. The most obvious example of when this
44 | * may come in handy is if an application wishes to subclass the Android v4
45 | * support library's {@code FragmentActivity}.
46 | *
47 | * Usage:
48 | * To wire this class up to your activity, simply forward the events defined
49 | * in {@link FlutterActivityEvents} from your activity to an instance of this
50 | * class. Optionally, you can make your activity implement
51 | * {@link PluginRegistry} and/or {@link io.flutter.view.FlutterView.Provider}
52 | * and forward those methods to this class as well.
53 | */
54 | public final class XFlutterActivityDelegate
55 | implements FlutterActivityEvents,
56 | FlutterView.Provider,
57 | PluginRegistry {
58 | private static final String SPLASH_SCREEN_META_DATA_KEY = "io.flutter.app.android.SplashScreenUntilFirstFrame";
59 | private static final String TAG = "FlutterActivityDelegate";
60 | private static final LayoutParams matchParent =
61 | new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
62 |
63 | /**
64 | * Specifies the mechanism by which Flutter views are created during the
65 | * operation of a {@code FlutterActivityDelegate}.
66 | *
67 | * A delegate's view factory will be consulted during
68 | * {@link #onCreate(Bundle)}. If it returns {@code null}, then the delegate
69 | * will fall back to instantiating a new full-screen {@code FlutterView}.
70 | *
71 | * A delegate's native view factory will be consulted during
72 | * {@link #onCreate(Bundle)}. If it returns {@code null}, then the delegate
73 | * will fall back to instantiating a new {@code FlutterNativeView}. This is
74 | * useful for applications to override to reuse the FlutterNativeView held
75 | * e.g. by a pre-existing background service.
76 | */
77 | public interface ViewFactory {
78 | FlutterView createFlutterView(Context context);
79 | FlutterNativeView createFlutterNativeView();
80 | }
81 |
82 | private Activity activity;
83 | private final ViewFactory viewFactory;
84 | private FlutterView flutterView;
85 | private View launchView;
86 |
87 | public XFlutterActivityDelegate(Activity activity, ViewFactory viewFactory) {
88 | this.activity = Preconditions.checkNotNull(activity);
89 | this.viewFactory = Preconditions.checkNotNull(viewFactory);
90 | }
91 |
92 | @Override
93 | public FlutterView getFlutterView() {
94 | return flutterView;
95 | }
96 |
97 | public void resetActivity(Activity activity){
98 | this.activity = activity;
99 | }
100 |
101 | // The implementation of PluginRegistry forwards to flutterView.
102 | @Override
103 | public boolean hasPlugin(String key) {
104 | return flutterView.getPluginRegistry().hasPlugin(key);
105 | }
106 |
107 | @Override
108 | @SuppressWarnings("unchecked")
109 | public T valuePublishedByPlugin(String pluginKey) {
110 | return (T) flutterView.getPluginRegistry().valuePublishedByPlugin(pluginKey);
111 | }
112 |
113 | @Override
114 | public Registrar registrarFor(String pluginKey) {
115 | return flutterView.getPluginRegistry().registrarFor(pluginKey);
116 | }
117 |
118 | @Override
119 | public boolean onRequestPermissionsResult(
120 | int requestCode, String[] permissions, int[] grantResults) {
121 | return flutterView.getPluginRegistry().onRequestPermissionsResult(requestCode, permissions, grantResults);
122 | }
123 |
124 | /*
125 | * Method onRequestPermissionResult(int, String[], int[]) was made
126 | * unavailable on 2018-02-28, following deprecation. This comment is left as
127 | * a temporary tombstone for reference, to be removed on 2018-03-28 (or at
128 | * least four weeks after release of unavailability).
129 | *
130 | * https://github.com/flutter/flutter/wiki/Changelog#typo-fixed-in-flutter-engine-android-api
131 | */
132 |
133 | @Override
134 | public boolean onActivityResult(int requestCode, int resultCode, Intent data) {
135 | return flutterView.getPluginRegistry().onActivityResult(requestCode, resultCode, data);
136 | }
137 |
138 | @Override
139 | public void onCreate(Bundle savedInstanceState) {
140 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
141 | Window window = activity.getWindow();
142 | window.addFlags(LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
143 | window.setStatusBarColor(0x40000000);
144 | window.getDecorView().setSystemUiVisibility(PlatformPlugin.DEFAULT_SYSTEM_UI);
145 | }
146 |
147 | String[] args = getArgsFromIntent(activity.getIntent());
148 | FlutterMain.ensureInitializationComplete(activity.getApplicationContext(), args);
149 |
150 | flutterView = viewFactory.createFlutterView(activity);
151 | if (flutterView == null) {
152 | FlutterNativeView nativeView = viewFactory.createFlutterNativeView();
153 | flutterView = new FlutterView(activity, null, nativeView);
154 | flutterView.setLayoutParams(matchParent);
155 | activity.setContentView(flutterView);
156 | launchView = createLaunchView();
157 | if (launchView != null) {
158 | addLaunchView();
159 | }
160 | }
161 | }
162 |
163 | public void runFlutterBundle(){
164 | // When an activity is created for the first time, we direct the
165 | // FlutterView to re-use a pre-existing Isolate rather than create a new
166 | // one. This is so that an Isolate coming in from the ViewFactory is
167 | // used.
168 | final boolean reuseIsolate = true;
169 |
170 | if (loadIntent(activity.getIntent(), reuseIsolate)) {
171 | return;
172 | }
173 | String appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
174 | if (appBundlePath != null) {
175 | flutterView.runFromBundle(appBundlePath, null, "main", reuseIsolate);
176 | }
177 | }
178 |
179 | @Override
180 | public void onNewIntent(Intent intent) {
181 | // Only attempt to reload the Flutter Dart code during development. Use
182 | // the debuggable flag as an indicator that we are in development mode.
183 | if (!isDebuggable() || !loadIntent(intent)) {
184 | flutterView.getPluginRegistry().onNewIntent(intent);
185 | }
186 | }
187 |
188 | private boolean isDebuggable() {
189 | return (activity.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
190 | }
191 |
192 | @Override
193 | public void onPause() {
194 | // Application app = (Application) activity.getApplicationContext();
195 | // if (app instanceof FlutterApplication) {
196 | // FlutterApplication flutterApp = (FlutterApplication) app;
197 | // if (this.equals(flutterApp.getCurrentActivity())) {
198 | // Log.i(TAG, "onPause setting current activity to null");
199 | // flutterApp.setCurrentActivity(null);
200 | // }
201 | // }
202 | if (flutterView != null) {
203 | flutterView.onPause();
204 | }
205 | }
206 |
207 | //@Override
208 | public void onStart() {
209 | if (flutterView != null) {
210 | flutterView.onStart();
211 | }
212 | }
213 |
214 |
215 | @Override
216 | public void onResume() {
217 | Application app = (Application) activity.getApplicationContext();
218 | // if (app instanceof FlutterApplication) {
219 | // FlutterApplication flutterApp = (FlutterApplication) app;
220 | // Log.i(TAG, "onResume setting current activity to this");
221 | // flutterApp.setCurrentActivity(activity);
222 | // } else {
223 | // Log.i(TAG, "onResume app wasn't a FlutterApplication!!");
224 | // }
225 | }
226 |
227 | //@Override
228 | public void onStop() {
229 | flutterView.onStop();
230 | }
231 |
232 | @Override
233 | public void onPostResume() {
234 | if (flutterView != null) {
235 | flutterView.onPostResume();
236 | }
237 | }
238 |
239 | @Override
240 | public void onDestroy() {
241 | Application app = (Application) activity.getApplicationContext();
242 | // if (app instanceof FlutterApplication) {
243 | // FlutterApplication flutterApp = (FlutterApplication) app;
244 | // if (this.equals(flutterApp.getCurrentActivity())) {
245 | // Log.i(TAG, "onDestroy setting current activity to null");
246 | // flutterApp.setCurrentActivity(null);
247 | // }
248 | // }
249 | if (flutterView != null) {
250 | final boolean detach =
251 | flutterView.getPluginRegistry().onViewDestroy(flutterView.getFlutterNativeView());
252 | if (detach) {
253 | // Detach, but do not destroy the FlutterView if a plugin
254 | // expressed interest in its FlutterNativeView.
255 | flutterView.detach();
256 | } else {
257 | flutterView.destroy();
258 | }
259 | }
260 | }
261 |
262 | @Override
263 | public boolean onBackPressed() {
264 | if (flutterView != null) {
265 | flutterView.popRoute();
266 | return true;
267 | }
268 | return false;
269 | }
270 |
271 | @Override
272 | public void onUserLeaveHint() {
273 | flutterView.getPluginRegistry().onUserLeaveHint();
274 | }
275 |
276 | @Override
277 | public void onTrimMemory(int level) {
278 | // Use a trim level delivered while the application is running so the
279 | // framework has a chance to react to the notification.
280 | if (level == TRIM_MEMORY_RUNNING_LOW) {
281 | flutterView.onMemoryPressure();
282 | }
283 | }
284 |
285 | @Override
286 | public void onLowMemory() {
287 | flutterView.onMemoryPressure();
288 | }
289 |
290 | @Override
291 | public void onConfigurationChanged(Configuration newConfig) {
292 | }
293 |
294 | private static String[] getArgsFromIntent(Intent intent) {
295 | // Before adding more entries to this list, consider that arbitrary
296 | // Android applications can generate intents with extra data and that
297 | // there are many security-sensitive args in the binary.
298 | ArrayList args = new ArrayList();
299 | if (intent.getBooleanExtra("trace-startup", false)) {
300 | args.add("--trace-startup");
301 | }
302 | if (intent.getBooleanExtra("start-paused", false)) {
303 | args.add("--start-paused");
304 | }
305 | if (intent.getBooleanExtra("use-test-fonts", false)) {
306 | args.add("--use-test-fonts");
307 | }
308 | if (intent.getBooleanExtra("enable-dart-profiling", false)) {
309 | args.add("--enable-dart-profiling");
310 | }
311 | if (intent.getBooleanExtra("enable-software-rendering", false)) {
312 | args.add("--enable-software-rendering");
313 | }
314 | if (intent.getBooleanExtra("skia-deterministic-rendering", false)) {
315 | args.add("--skia-deterministic-rendering");
316 | }
317 | if (intent.getBooleanExtra("trace-skia", false)) {
318 | args.add("--trace-skia");
319 | }
320 | if (!args.isEmpty()) {
321 | String[] argsArray = new String[args.size()];
322 | return args.toArray(argsArray);
323 | }
324 | return null;
325 | }
326 |
327 | private boolean loadIntent(Intent intent) {
328 | final boolean reuseIsolate = false;
329 | return loadIntent(intent, reuseIsolate);
330 | }
331 |
332 | private boolean loadIntent(Intent intent, boolean reuseIsolate) {
333 | String action = intent.getAction();
334 | if (Intent.ACTION_RUN.equals(action)) {
335 | String route = intent.getStringExtra("route");
336 | String appBundlePath = intent.getDataString();
337 | if (appBundlePath == null) {
338 | // Fall back to the installation path if no bundle path
339 | // was specified.
340 | appBundlePath = FlutterMain.findAppBundlePath(activity.getApplicationContext());
341 | }
342 | if (route != null) {
343 | flutterView.setInitialRoute(route);
344 | }
345 | flutterView.runFromBundle(appBundlePath, intent.getStringExtra("snapshot"), "main", reuseIsolate);
346 | return true;
347 | }
348 |
349 | return false;
350 | }
351 |
352 | /**
353 | * Creates a {@link View} containing the same {@link Drawable} as the one set as the
354 | * {@code windowBackground} of the parent activity for use as a launch splash view.
355 | *
356 | * Returns null if no {@code windowBackground} is set for the activity.
357 | */
358 | private View createLaunchView() {
359 | if (!showSplashScreenUntilFirstFrame()) {
360 | return null;
361 | }
362 | final Drawable launchScreenDrawable = getLaunchScreenDrawableFromActivityTheme();
363 | if (launchScreenDrawable == null) {
364 | return null;
365 | }
366 | final View view = new View(activity);
367 | view.setLayoutParams(matchParent);
368 | view.setBackground(launchScreenDrawable);
369 | return view;
370 | }
371 |
372 | /**
373 | * Extracts a {@link Drawable} from the parent activity's {@code windowBackground}.
374 | *
375 | * {@code android:windowBackground} is specifically reused instead of a other attributes
376 | * because the Android framework can display it fast enough when launching the app as opposed
377 | * to anything defined in the Activity subclass.
378 | *
379 | * Returns null if no {@code windowBackground} is set for the activity.
380 | */
381 | @SuppressWarnings("deprecation")
382 | private Drawable getLaunchScreenDrawableFromActivityTheme() {
383 | TypedValue typedValue = new TypedValue();
384 | if (!activity.getTheme().resolveAttribute(
385 | android.R.attr.windowBackground,
386 | typedValue,
387 | true)) {;
388 | return null;
389 | }
390 | if (typedValue.resourceId == 0) {
391 | return null;
392 | }
393 | try {
394 | return activity.getResources().getDrawable(typedValue.resourceId);
395 | } catch (NotFoundException e) {
396 | Log.e(TAG, "Referenced launch screen windowBackground resource does not exist");
397 | return null;
398 | }
399 | }
400 |
401 | /**
402 | * Let the user specify whether the activity's {@code windowBackground} is a launch screen
403 | * and should be shown until the first frame via a tag in the activity.
404 | */
405 | private Boolean showSplashScreenUntilFirstFrame() {
406 | try {
407 | ActivityInfo activityInfo = activity.getPackageManager().getActivityInfo(
408 | activity.getComponentName(),
409 | PackageManager.GET_META_DATA|PackageManager.GET_ACTIVITIES);
410 | Bundle metadata = activityInfo.metaData;
411 | return metadata != null && metadata.getBoolean(SPLASH_SCREEN_META_DATA_KEY);
412 | } catch (NameNotFoundException e) {
413 | return false;
414 | }
415 | }
416 |
417 | /**
418 | * Show and then automatically animate out the launch view.
419 | *
420 | * If a launch screen is defined in the user application's AndroidManifest.xml as the
421 | * activity's {@code windowBackground}, display it on top of the {@link FlutterView} and
422 | * remove the activity's {@code windowBackground}.
423 | *
424 | * Fade it out and remove it when the {@link FlutterView} renders its first frame.
425 | */
426 | private void addLaunchView() {
427 | if (launchView == null) {
428 | return;
429 | }
430 |
431 | activity.addContentView(launchView, matchParent);
432 | flutterView.addFirstFrameListener(new FlutterView.FirstFrameListener() {
433 | @Override
434 | public void onFirstFrame() {
435 | XFlutterActivityDelegate.this.launchView.animate()
436 | .alpha(0f)
437 | // Use Android's default animation duration.
438 | .setListener(new AnimatorListenerAdapter() {
439 | @Override
440 | public void onAnimationEnd(Animator animation) {
441 | // Views added to an Activity's addContentView is always added to its
442 | // root FrameLayout.
443 | ((ViewGroup) XFlutterActivityDelegate.this.launchView.getParent())
444 | .removeView(XFlutterActivityDelegate.this.launchView);
445 | XFlutterActivityDelegate.this.launchView = null;
446 | }
447 | });
448 |
449 | XFlutterActivityDelegate.this.flutterView.removeFirstFrameListener(this);
450 | }
451 | });
452 |
453 | // Resets the activity theme from the one containing the launch screen in the window
454 | // background to a blank one since the launch screen is now in a view in front of the
455 | // FlutterView.
456 | //
457 | // We can make this configurable if users want it.
458 | activity.setTheme(android.R.style.Theme_Black_NoTitleBar);
459 | }
460 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/taobao/hybridstackmanager/XFlutterView.java:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Chromium Authors. All rights reserved.
2 | // Use of this source code is governed by a BSD-style license that can be
3 | // found in the LICENSE file.
4 | package com.taobao.hybridstackmanager;
5 |
6 | import android.app.Activity;
7 | import android.content.BroadcastReceiver;
8 | import android.content.Context;
9 | import android.content.IntentFilter;
10 | import android.util.AttributeSet;
11 |
12 | import java.lang.reflect.Field;
13 | import java.util.List;
14 |
15 | import io.flutter.plugin.common.ActivityLifecycleListener;
16 | import io.flutter.plugin.common.JSONMethodCodec;
17 | import io.flutter.plugin.common.MethodChannel;
18 | import io.flutter.plugin.platform.PlatformPlugin;
19 | import io.flutter.view.FlutterNativeView;
20 | import io.flutter.view.FlutterView;
21 |
22 | /**
23 | * An Android view containing a Flutter app.
24 | */
25 | public class XFlutterView extends FlutterView
26 | {
27 | public XFlutterView(Context context, AttributeSet attrs, FlutterNativeView nativeView){
28 | super(context,attrs,nativeView);
29 | }
30 | public void registerReceiver() {
31 | try {
32 | //Register Receiver
33 | Field privateField0 = FlutterView.class.getDeclaredField("mDiscoveryReceiver");
34 | privateField0.setAccessible(true);
35 | BroadcastReceiver mDiscoveryReceiver = (BroadcastReceiver) privateField0.get(this);
36 | if (mDiscoveryReceiver != null) {
37 | if ((getContext().getApplicationInfo().flags & 2) != 0 && mDiscoveryReceiver != null) {
38 | getContext().registerReceiver(mDiscoveryReceiver, new IntentFilter("io.flutter.view.DISCOVER"));
39 | }
40 | }
41 | } catch (NoSuchFieldException e) {
42 | e.printStackTrace();
43 | }
44 | catch (IllegalAccessException e) {
45 | e.printStackTrace();
46 | }
47 | }
48 | public void unregisterReceiver(){
49 | try {
50 | //UnRegister Receiver
51 | Field privateField0 = FlutterView.class.getDeclaredField("mDiscoveryReceiver");
52 | privateField0.setAccessible(true);
53 | BroadcastReceiver mDiscoveryReceiver = (BroadcastReceiver)privateField0.get(this);
54 | if (mDiscoveryReceiver != null) {
55 | this.getContext().unregisterReceiver(mDiscoveryReceiver);
56 | }
57 | } catch (NoSuchFieldException e) {
58 | e.printStackTrace();
59 | }
60 | catch (IllegalAccessException e) {
61 | e.printStackTrace();
62 | }
63 | }
64 | public void resetActivity(Activity activity){
65 | try {
66 | Field privateField1 = FlutterView.class.getDeclaredField("mNativeView");
67 | privateField1.setAccessible(true);
68 | FlutterNativeView mNativeView = (FlutterNativeView)privateField1.get(this);
69 | try {
70 | mNativeView.attachViewAndActivity(this, activity);
71 | }
72 | catch (AssertionError ae){
73 | System.out.println("In new implementation for FlutterPluginRegistry,AssertionError is thrown when try to attach twice, it doesn't matter even we ignore it.");
74 | }
75 |
76 | Field privateField2 = FlutterView.class.getDeclaredField("mActivityLifecycleListeners");
77 | privateField2.setAccessible(true);
78 | List mActivityLifecycleListener = (List)privateField2.get(this);
79 | mActivityLifecycleListener.clear();
80 | PlatformPlugin platformPlugin = new PlatformPlugin(activity);
81 | MethodChannel flutterPlatformChannel = new MethodChannel(this, "flutter/platform", JSONMethodCodec.INSTANCE);
82 | flutterPlatformChannel.setMethodCallHandler(platformPlugin);
83 | addActivityLifecycleListener(platformPlugin);
84 | } catch (NoSuchFieldException e) {
85 | e.printStackTrace();
86 | }
87 | catch (IllegalAccessException e) {
88 | e.printStackTrace();
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/android/src/main/java/com/taobao/hybridstackmanager/XURLRouter.java:
--------------------------------------------------------------------------------
1 | package com.taobao.hybridstackmanager;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.net.Uri;
6 | import java.util.HashMap;
7 |
8 | public class XURLRouter {
9 | final static String kOpenUrlPrefix = "hrd";
10 | static XURLRouter sRouterInst;
11 | Context mAppContext;
12 | XURLRouterHandler mNativeRouterHandler;
13 | public static XURLRouter sharedInstance(){
14 | if(sRouterInst==null){
15 | sRouterInst = new XURLRouter();
16 | }
17 | return sRouterInst;
18 | }
19 | public void setAppContext(Context context){
20 | mAppContext = context;
21 | }
22 | public void setNativeRouterHandler(XURLRouterHandler handler){
23 | mNativeRouterHandler = handler;
24 | }
25 | public boolean openUrlWithQueryAndParams(String url, HashMap query, HashMap params){
26 | Uri tmpUri = Uri.parse(url);
27 | if(!kOpenUrlPrefix.equals(tmpUri.getScheme()))
28 | return false;
29 | if(query!=null && query.containsKey("flutter") && (Boolean) query.get("flutter")){
30 | Intent intent = new Intent(mAppContext,FlutterWrapperActivity.class);
31 | intent.setData(Uri.parse(url));
32 | intent.setAction(Intent.ACTION_VIEW);
33 | mAppContext.startActivity(intent);
34 | return true;
35 | }
36 | if(mNativeRouterHandler!=null) {
37 | Class activityCls = mNativeRouterHandler.openUrlWithQueryAndParams(url, query, params);
38 | Intent intent = new Intent(mAppContext,activityCls);
39 | intent.setData(Uri.parse(url));
40 | intent.setAction(Intent.ACTION_VIEW);
41 | mAppContext.startActivity(intent);
42 | }
43 | return false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/android/src/main/java/com/taobao/hybridstackmanager/XURLRouterHandler.java:
--------------------------------------------------------------------------------
1 | package com.taobao.hybridstackmanager;
2 |
3 | import java.util.HashMap;
4 |
5 | public interface XURLRouterHandler {
6 | public Class openUrlWithQueryAndParams(String url, HashMap query, HashMap params);
7 | }
8 |
--------------------------------------------------------------------------------
/android/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/android/src/main/res/layout/flutter_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
14 |
19 |
20 |
25 |
--------------------------------------------------------------------------------
/android/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/android/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .dart_tool/
3 |
4 | .packages
5 | .pub/
6 |
7 | build/
8 |
9 | .flutter-plugins
10 |
--------------------------------------------------------------------------------
/example/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/example/.idea/libraries/Dart_SDK.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/.idea/libraries/Flutter_Plugins.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/.idea/libraries/Flutter_for_Android.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/example/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/example/.idea/runConfigurations/main_dart.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: c7ea3ca377e909469c68f2ab878a5bc53d3cf66b
8 | channel: beta
9 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # hybrid_stack_manager_example
2 |
3 | Demonstrates how to use the hybrid_stack_manager plugin.
4 |
5 | ## Getting Started
6 |
7 | For help getting started with Flutter, view our online
8 | [documentation](https://flutter.io/).
9 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | *.class
3 | .gradle
4 | /local.properties
5 | /.idea/workspace.xml
6 | /.idea/libraries
7 | .DS_Store
8 | /build
9 | /captures
10 | GeneratedPluginRegistrant.java
11 |
--------------------------------------------------------------------------------
/example/android/.idea/caches/build_file_checksums.ser:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/.idea/caches/build_file_checksums.ser
--------------------------------------------------------------------------------
/example/android/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/example/android/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/example/android/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/example/android/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | apply plugin: 'com.android.application'
15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
16 |
17 | android {
18 | compileSdkVersion 27
19 |
20 | lintOptions {
21 | disable 'InvalidPackage'
22 | }
23 |
24 | defaultConfig {
25 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
26 | applicationId "com.example.hybridstackmanagerexample"
27 | minSdkVersion 16
28 | targetSdkVersion 27
29 | versionCode 1
30 | versionName "1.0"
31 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
32 | }
33 |
34 | buildTypes {
35 | release {
36 | // TODO: Add your own signing config for the release build.
37 | // Signing with the debug keys for now, so `flutter run --release` works.
38 | signingConfig signingConfigs.debug
39 | }
40 | }
41 | }
42 |
43 | flutter {
44 | source '../..'
45 | }
46 |
47 | dependencies {
48 | testImplementation 'junit:junit:4.12'
49 | androidTestImplementation 'com.android.support.test:runner:1.0.1'
50 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
51 | }
52 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
15 |
19 |
25 |
29 |
32 |
33 |
34 |
35 |
36 |
37 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/hybridstackmanagerexample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.hybridstackmanagerexample;
2 |
3 | import android.app.Activity;
4 | import android.net.Uri;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.widget.Button;
8 | import android.widget.LinearLayout;
9 | import com.taobao.hybridstackmanager.*;
10 |
11 | import java.util.HashMap;
12 |
13 | public class MainActivity extends Activity implements XURLRouterHandler {
14 | @Override
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | XURLRouter.sharedInstance().setAppContext(getApplicationContext());
18 | setContentView(R.layout.placeholder);
19 | setTitle("Native Root Page");
20 | setupOperationBtns();
21 | setupNativeOpenUrlHandler();
22 | }
23 |
24 | void setupOperationBtns(){
25 | LinearLayout layout = findViewById(R.id.native_root);
26 | final Button btn=new Button(this);
27 | btn.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
28 | btn.setText("Click to jump Flutter");
29 | btn.setOnClickListener(new View.OnClickListener() {
30 | @Override
31 | public void onClick(View v) {
32 | HashMap m = new HashMap();
33 | m.put("flutter",true);
34 | XURLRouter.sharedInstance().openUrlWithQueryAndParams("hrd://fdemo",m,null);
35 |
36 | }
37 | });
38 | layout.addView(btn);
39 | }
40 | void setupNativeOpenUrlHandler(){
41 | XURLRouter.sharedInstance().setNativeRouterHandler(this);
42 | }
43 | public Class openUrlWithQueryAndParams(String url, HashMap query, HashMap params){
44 | Uri tmpUri = Uri.parse(url);
45 | if("ndemo".equals(tmpUri.getHost())){
46 | return XDemoActivity.class;
47 | }
48 | return null;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/hybridstackmanagerexample/XDemoActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.hybridstackmanagerexample;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.view.View;
6 | import android.widget.Button;
7 | import android.widget.LinearLayout;
8 |
9 | import com.taobao.hybridstackmanager.XURLRouter;
10 |
11 | import java.util.HashMap;
12 |
13 | public class XDemoActivity extends Activity{
14 | static int sNativeActivityIdx = 0;
15 | protected void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | setContentView(R.layout.placeholder);
18 | setupOperationBtns();
19 | sNativeActivityIdx++;
20 | setTitle(String.format("Native Demo Page(%d)",sNativeActivityIdx));
21 | }
22 |
23 | void setupOperationBtns(){
24 | LinearLayout layout = findViewById(R.id.native_root);
25 | Button btn=new Button(this);
26 | btn.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
27 | btn.setText("Click to jump Flutter");
28 | btn.setOnClickListener(new View.OnClickListener() {
29 | @Override
30 | public void onClick(View v) {
31 | HashMap m = new HashMap();
32 | m.put("flutter",true);
33 | XURLRouter.sharedInstance().openUrlWithQueryAndParams("hrd://fdemo",m,null);
34 |
35 | }
36 | });
37 | layout.addView(btn);
38 |
39 | btn=new Button(this);
40 | btn.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
41 | btn.setText("Click to jump native");
42 | btn.setOnClickListener(new View.OnClickListener() {
43 | @Override
44 | public void onClick(View v) {
45 | XURLRouter.sharedInstance().openUrlWithQueryAndParams("hrd://ndemo",null,null);
46 |
47 | }
48 | });
49 | layout.addView(btn);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/layout/placeholder.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | }
6 |
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.0.1'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | google()
15 | jcenter()
16 | }
17 | }
18 |
19 | rootProject.buildDir = '../build'
20 | subprojects {
21 | project.buildDir = "${rootProject.buildDir}/${project.name}"
22 | }
23 | subprojects {
24 | project.evaluationDependsOn(':app')
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
30 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.injected.testOnly=false
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/android/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 23 08:50:38 CEST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
7 |
--------------------------------------------------------------------------------
/example/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/example/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
4 |
5 | def plugins = new Properties()
6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
7 | if (pluginsFile.exists()) {
8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
9 | }
10 |
11 | plugins.each { name, path ->
12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
13 | include ":$name"
14 | project(":$name").projectDir = pluginDirectory
15 | }
16 |
--------------------------------------------------------------------------------
/example/hybrid_stack_manager_example.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/example/hybrid_stack_manager_example_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 | GeneratedPluginRegistrant.h
13 | GeneratedPluginRegistrant.m
14 |
15 | .generated/
16 |
17 | *.pbxuser
18 | *.mode1v3
19 | *.mode2v3
20 | *.perspectivev3
21 |
22 | !default.pbxuser
23 | !default.mode1v3
24 | !default.mode2v3
25 | !default.perspectivev3
26 |
27 | xcuserdata
28 |
29 | *.moved-aside
30 |
31 | *.pyc
32 | *sync/
33 | Icon?
34 | .tags*
35 |
36 | /Flutter/app.flx
37 | /Flutter/app.zip
38 | /Flutter/flutter_assets/
39 | /Flutter/App.framework
40 | /Flutter/Flutter.framework
41 | /Flutter/Generated.xcconfig
42 | /ServiceDefinitions.json
43 |
44 | Pods/
45 | .symlinks/
46 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 8.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
2 | #include "Generated.xcconfig"
3 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment this line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true'
6 |
7 | def parse_KV_file(file, separator='=')
8 | file_abs_path = File.expand_path(file)
9 | if !File.exists? file_abs_path
10 | return [];
11 | end
12 | pods_ary = []
13 | skip_line_start_symbols = ["#", "/"]
14 | File.foreach(file_abs_path) { |line|
15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
16 | plugin = line.split(pattern=separator)
17 | if plugin.length == 2
18 | podname = plugin[0].strip()
19 | path = plugin[1].strip()
20 | podpath = File.expand_path("#{path}", file_abs_path)
21 | pods_ary.push({:name => podname, :path => podpath});
22 | else
23 | puts "Invalid plugin specification: #{line}"
24 | end
25 | }
26 | return pods_ary
27 | end
28 |
29 | target 'Runner' do
30 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
31 | # referring to absolute paths on developers' machines.
32 | system('rm -rf .symlinks')
33 | system('mkdir -p .symlinks/plugins')
34 |
35 | # Flutter Pods
36 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig')
37 | if generated_xcode_build_settings.empty?
38 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first."
39 | end
40 | generated_xcode_build_settings.map { |p|
41 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR'
42 | symlink = File.join('.symlinks', 'flutter')
43 | File.symlink(File.dirname(p[:path]), symlink)
44 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path]))
45 | end
46 | }
47 |
48 | # Plugin Pods
49 | plugin_pods = parse_KV_file('../.flutter-plugins')
50 | plugin_pods.map { |p|
51 | symlink = File.join('.symlinks', 'plugins', p[:name])
52 | File.symlink(p[:path], symlink)
53 | pod p[:name], :path => File.join(symlink, 'ios')
54 | }
55 | end
56 |
57 | post_install do |installer|
58 | installer.pods_project.targets.each do |target|
59 | target.build_configurations.each do |config|
60 | config.build_settings['ENABLE_BITCODE'] = 'NO'
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
31 |
32 |
33 |
34 |
40 |
41 |
42 |
43 |
44 |
45 |
56 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
75 |
77 |
83 |
84 |
85 |
86 |
88 |
89 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface AppDelegate : FlutterAppDelegate
5 |
6 | @end
7 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #include "AppDelegate.h"
2 | #include "GeneratedPluginRegistrant.h"
3 | #import "XRootController.h"
4 | #import
5 | #import "XDemoController.h"
6 |
7 | @interface AppDelegate(UIGestureRecognizerDelegate)
8 | @end
9 |
10 | @implementation AppDelegate
11 | - (BOOL)application:(UIApplication *)application
12 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
13 | // Override point for customization after application launch.
14 | UINavigationController *rootNav = [[UINavigationController alloc] initWithRootViewController:[XRootController new]];
15 | rootNav.interactivePopGestureRecognizer.delegate = self;
16 | UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
17 | window.rootViewController = rootNav;
18 | [window makeKeyAndVisible];
19 | self.window = window;
20 | [self setupNativeOpenUrlHandler];
21 | return YES;
22 | }
23 |
24 | - (void)setupNativeOpenUrlHandler{
25 | [[XURLRouter sharedInstance] setNativeOpenUrlHandler:^UIViewController *(NSString *url,NSDictionary *query,NSDictionary *params){
26 | NSURL *tmpUrl = [NSURL URLWithString:url];
27 | if([@"ndemo" isEqualToString:tmpUrl.host]){
28 | return [XDemoController new];
29 | }
30 | return nil;
31 | }];
32 | }
33 |
34 | #pragma mark - UIGestureRecognizerDelegate
35 | - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer{
36 | return TRUE;
37 | }
38 | @end
39 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | hybrid_stack_manager_example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UISupportedInterfaceOrientations
28 |
29 | UIInterfaceOrientationPortrait
30 | UIInterfaceOrientationLandscapeLeft
31 | UIInterfaceOrientationLandscapeRight
32 |
33 | UISupportedInterfaceOrientations~ipad
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationPortraitUpsideDown
37 | UIInterfaceOrientationLandscapeLeft
38 | UIInterfaceOrientationLandscapeRight
39 |
40 | UIViewControllerBasedStatusBarAppearance
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/example/ios/Runner/XDemoController.h:
--------------------------------------------------------------------------------
1 | //
2 | // XDemoController.h
3 | // Runner
4 | //
5 | // Created by KyleWong on 2018/8/13.
6 | // Copyright © 2018 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface XDemoController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/XDemoController.m:
--------------------------------------------------------------------------------
1 | //
2 | // XDemoController.m
3 | // Runner
4 | //
5 | // Created by KyleWong on 2018/8/13.
6 | // Copyright © 2018 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "XDemoController.h"
10 | #import
11 |
12 | static NSInteger sNativeVCIdx = 0;
13 |
14 | @interface XDemoController ()
15 | @end
16 |
17 | @implementation XDemoController
18 |
19 | - (void)viewDidLoad {
20 | [super viewDidLoad];
21 | sNativeVCIdx++;
22 | NSString *title = [NSString stringWithFormat:@"Native demo page(%ld)",(long)sNativeVCIdx];
23 | self.title = title;
24 | // Do any additional setup after loading the view.
25 | }
26 |
27 | - (void)viewWillAppear:(BOOL)animated{
28 | [super viewWillAppear:animated];
29 | [self.navigationController setNavigationBarHidden:NO animated:NO];
30 | }
31 |
32 | - (void)loadView{
33 | UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
34 | [view setBackgroundColor:[UIColor whiteColor]];
35 | UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];
36 | [btn setTitle:@"Click to jump Native" forState:UIControlStateNormal];
37 | [view addSubview:btn];
38 | [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
39 | [btn setCenter:CGPointMake(view.center.x, view.center.y-50)];
40 | [btn addTarget:self action:@selector(onJumpNativePressed) forControlEvents:UIControlEventTouchUpInside];
41 |
42 | btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];
43 | [btn setTitle:@"Click to jump Flutter" forState:UIControlStateNormal];
44 | [view addSubview:btn];
45 | [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
46 | [btn setCenter:CGPointMake(view.center.x, view.center.y+50)];
47 | [btn addTarget:self action:@selector(onJumpFlutterPressed) forControlEvents:UIControlEventTouchUpInside];
48 |
49 | self.view = view;
50 | }
51 |
52 | - (void)onJumpNativePressed{
53 | XOpenURLWithQueryAndParams(@"hrd://ndemo", nil, nil);
54 | }
55 |
56 | - (void)onJumpFlutterPressed{
57 | XOpenURLWithQueryAndParams(@"hrd://fdemo", @{@"flutter":@(true)}, nil);
58 | }
59 | @end
60 |
--------------------------------------------------------------------------------
/example/ios/Runner/XRootController.h:
--------------------------------------------------------------------------------
1 | //
2 | // XRootController.h
3 | // Runner
4 | //
5 | // Created by KyleWong on 2018/8/13.
6 | // Copyright © 2018 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface XRootController : UIViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/XRootController.m:
--------------------------------------------------------------------------------
1 | //
2 | // XRootController.m
3 | // Runner
4 | //
5 | // Created by KyleWong on 2018/8/13.
6 | // Copyright © 2018 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "XRootController.h"
10 | #import
11 |
12 | @interface XRootController ()
13 |
14 | @end
15 |
16 | @implementation XRootController
17 |
18 | - (void)viewDidLoad {
19 | [super viewDidLoad];
20 | self.title = @"Native root page";
21 | // Do any additional setup after loading the view.
22 | }
23 |
24 | - (void)loadView{
25 | UIView *view = [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds];
26 | [view setBackgroundColor:[UIColor whiteColor]];
27 | UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200, 40)];
28 | [btn setTitle:@"Click to jump Flutter" forState:UIControlStateNormal];
29 | [view addSubview:btn];
30 | [btn setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
31 | [btn setCenter:view.center];
32 | [btn addTarget:self action:@selector(onJumpFlutterPressed) forControlEvents:UIControlEventTouchUpInside];
33 | self.view = view;
34 | }
35 |
36 | - (void)onJumpFlutterPressed{
37 | XOpenURLWithQueryAndParams(@"hrd://fdemo", @{@"flutter":@(true)}, nil);
38 | }
39 | @end
40 |
--------------------------------------------------------------------------------
/example/ios/Runner/main.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 | #import "AppDelegate.h"
4 |
5 | int main(int argc, char* argv[]) {
6 | @autoreleasepool {
7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/example/lib/app_config.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:hybrid_stack_manager/hybrid_stack_manager_plugin.dart';
3 |
4 | import 'fdemo.dart';
5 |
6 | class AppConfig {
7 | static final AppConfig _singleton = new AppConfig._internal();
8 | static final GlobalKey gHomeItemPageWidgetKey =
9 | new GlobalKey(debugLabel: "[KWLM]");
10 | static AppConfig sharedInstance() {
11 | Router.sharedInstance().globalKeyForRouter = gHomeItemPageWidgetKey;
12 | Router.sharedInstance().routerWidgetHandler =
13 | ({RouterOption routeOption, Key key}) {
14 | if (routeOption.url == "hrd://fdemo") {
15 | return new FDemoWidget(routeOption, key: key);
16 | }
17 | return null;
18 | };
19 | return _singleton;
20 | }
21 |
22 | AppConfig._internal() {}
23 | }
24 |
--------------------------------------------------------------------------------
/example/lib/fdemo.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:hybrid_stack_manager/hybrid_stack_manager_plugin.dart';
3 |
4 | class FDemoWidget extends StatelessWidget {
5 | RouterOption routeOption;
6 | FDemoWidget(RouterOption option, {Key key}) : super(key: key) {
7 | routeOption = option;
8 | }
9 | Widget build(BuildContext context) {
10 | Map m = Utils.parseUniquePageName(routeOption.userInfo);
11 | return new Scaffold(
12 | appBar: new AppBar(
13 | leading: new GestureDetector(
14 | child: new Icon(Icons.arrow_back),
15 | onTap: () {
16 | HybridStackManagerPlugin.hybridStackManagerPlugin.popCurPage();
17 | }),
18 | // Here we take the value from the MyHomePage object that was created by
19 | // the App.build method, and use it to set our appbar title.
20 | title: new Text("Flutter Page(${m["id"]})"),
21 | ),
22 | body: new Center(
23 | // Center is a layout widget. It takes a single child and positions it
24 | // in the middle of the parent.
25 | child: new Column(
26 | // Column is also layout widget. It takes a list of children and
27 | // arranges them vertically. By default, it sizes itself to fit its
28 | // children horizontally, and tries to be as tall as its parent.
29 | //
30 | // Invoke "debug paint" (press "p" in the console where you ran
31 | // "flutter run", or select "Toggle Debug Paint" from the Flutter tool
32 | // window in IntelliJ) to see the wireframe for each widget.
33 | //
34 | // Column has various properties to control how it sizes itself and
35 | // how it positions its children. Here we use mainAxisAlignment to
36 | // center the children vertically; the main axis here is the vertical
37 | // axis because Columns are vertical (the cross axis would be
38 | // horizontal).
39 | mainAxisAlignment: MainAxisAlignment.center,
40 | children: [
41 | new SizedBox(width: 1.0, height: 100.0),
42 | new GestureDetector(
43 | child: new Text("Click to open FlutterPage"),
44 | onTap: () {
45 | HybridStackManagerPlugin.hybridStackManagerPlugin
46 | .openUrlFromNative(
47 | url: "hrd://fdemo", query: {"flutter": true});
48 | },
49 | ),
50 | new SizedBox(width: 1.0, height: 100.0),
51 | new GestureDetector(
52 | child: new Text("Click to open NativePage"),
53 | onTap: () {
54 | HybridStackManagerPlugin.hybridStackManagerPlugin
55 | .openUrlFromNative(url: "hrd://ndemo");
56 | },
57 | )
58 | ],
59 | ),
60 | ),
61 | floatingActionButton:
62 | null // This trailing comma makes auto-formatting nicer for build methods.
63 | );
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:hybrid_stack_manager/hybrid_stack_manager_plugin.dart';
3 |
4 | import 'app_config.dart';
5 | import 'my_app.dart';
6 |
7 | void main() async {
8 | AppConfig.sharedInstance();
9 | HybridStackManagerPlugin plugin =
10 | HybridStackManagerPlugin.hybridStackManagerPlugin;
11 | Map args = await plugin.getMainEntryParams();
12 | runApp(new MyApp());
13 | if (args != null && args["url"] != null) {
14 | RouterOption routeOption = new RouterOption(
15 | url: args["url"], query: args["query"], params: args["params"]);
16 | Router.sharedInstance().pushPageWithOptionsFromFlutter(
17 | routeOption: routeOption, animated: false);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/lib/my_app.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 |
3 | import 'app_config.dart';
4 |
5 | class MyApp extends StatefulWidget {
6 | MyApp();
7 | State createState() {
8 | return new MyAppState();
9 | }
10 | }
11 |
12 | class MyAppState extends State {
13 | @override
14 | void initState() {
15 | // TODO: implement initState
16 | super.initState();
17 | }
18 |
19 | @override
20 | Widget build(BuildContext context) {
21 | ThemeData themeData = new ThemeData(
22 | primarySwatch: Colors.blue,
23 | );
24 | return new MaterialApp(
25 | title: '混合栈Demo',
26 | theme: themeData,
27 | home: new MyHomeWidget(key: AppConfig.gHomeItemPageWidgetKey),
28 | );
29 | }
30 | }
31 |
32 | class MyHomeWidget extends StatefulWidget {
33 | MyHomeWidget({Key key}) : super(key: key);
34 |
35 | @override
36 | State createState() {
37 | return new MyHomeWidgetState();
38 | }
39 | }
40 |
41 | class MyHomeWidgetState extends State {
42 | @override
43 | Widget build(BuildContext context) {
44 | return new Container(
45 | color: Colors.white,
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: hybrid_stack_manager_example
2 | description: Demonstrates how to use the hybrid_stack_manager plugin.
3 |
4 | dependencies:
5 | flutter:
6 | sdk: flutter
7 |
8 | # The following adds the Cupertino Icons font to your application.
9 | # Use with the CupertinoIcons class for iOS style icons.
10 | cupertino_icons: ^0.1.2
11 |
12 | dev_dependencies:
13 | flutter_test:
14 | sdk: flutter
15 |
16 | hybrid_stack_manager:
17 | path: ../
18 |
19 | # For information on the generic Dart part of this file, see the
20 | # following page: https://www.dartlang.org/tools/pub/pubspec
21 |
22 | # The following section is specific to Flutter.
23 | flutter:
24 |
25 | # The following line ensures that the Material Icons font is
26 | # included with your application, so that you can use the icons in
27 | # the material Icons class.
28 | uses-material-design: true
29 |
30 | # To add assets to your application, add an assets section, like this:
31 | # assets:
32 | # - images/a_dot_burr.jpeg
33 | # - images/a_dot_ham.jpeg
34 |
35 | # An image asset can refer to one or more resolution-specific "variants", see
36 | # https://flutter.io/assets-and-images/#resolution-aware.
37 |
38 | # For details regarding adding assets from package dependencies, see
39 | # https://flutter.io/assets-and-images/#from-packages
40 |
41 | # To add custom fonts to your application, add a fonts section here,
42 | # in this "flutter" section. Each entry in this list should have a
43 | # "family" key with the font family name, and a "fonts" key with a
44 | # list giving the asset and other descriptors for the font. For
45 | # example:
46 | # fonts:
47 | # - family: Schyler
48 | # fonts:
49 | # - asset: fonts/Schyler-Regular.ttf
50 | # - asset: fonts/Schyler-Italic.ttf
51 | # style: italic
52 | # - family: Trajan Pro
53 | # fonts:
54 | # - asset: fonts/TrajanPro.ttf
55 | # - asset: fonts/TrajanPro_Bold.ttf
56 | # weight: 700
57 | #
58 | # For details regarding fonts from package dependencies,
59 | # see https://flutter.io/custom-fonts/#from-packages
60 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | // To perform an interaction with a widget in your test, use the WidgetTester utility that Flutter
3 | // provides. For example, you can send tap and scroll gestures. You can also use WidgetTester to
4 | // find child widgets in the widget tree, read text, and verify that the values of widget properties
5 | // are correct.
6 |
7 | import 'package:flutter/material.dart';
8 | import 'package:flutter_test/flutter_test.dart';
9 | import 'package:hybrid_stack_manager_example/my_app.dart';
10 |
11 | void main() {
12 | testWidgets('Verify Platform version', (WidgetTester tester) async {
13 | // Build our app and trigger a frame.
14 | await tester.pumpWidget(new MyApp());
15 |
16 | // Verify that platform version is retrieved.
17 | expect(
18 | find.byWidgetPredicate(
19 | (Widget widget) =>
20 | widget is Text && widget.data.startsWith('Running on:'),
21 | ),
22 | findsOneWidget);
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/flutter_chann_plugin.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/flutter_chann_plugin_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/hybrid_stack_manager.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/hybrid_stack_manager_android.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | .vagrant/
3 | .sconsign.dblite
4 | .svn/
5 |
6 | .DS_Store
7 | *.swp
8 | profile
9 |
10 | DerivedData/
11 | build/
12 |
13 | *.pbxuser
14 | *.mode1v3
15 | *.mode2v3
16 | *.perspectivev3
17 |
18 | !default.pbxuser
19 | !default.mode1v3
20 | !default.mode2v3
21 | !default.perspectivev3
22 |
23 | xcuserdata
24 |
25 | *.moved-aside
26 |
27 | *.pyc
28 | *sync/
29 | Icon?
30 | .tags*
31 |
32 |
--------------------------------------------------------------------------------
/ios/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/ios/Assets/.gitkeep
--------------------------------------------------------------------------------
/ios/Classes/FlutterViewWrapperController.h:
--------------------------------------------------------------------------------
1 | //
2 | // FlutterViewWrapperController.h
3 | // Runner
4 | //
5 | // Created by 正物 on 08/03/2018.
6 | // Copyright © 2018 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "XFlutterViewController.h"
11 | #import "UIViewController+URLRouter.h"
12 |
13 | typedef void (^FlutterViewWillAppearBlock) (void);
14 |
15 | @interface FlutterViewWrapperController : UIViewController
16 | + (XFlutterViewController *)flutterVC;
17 | @property(nonatomic,copy) NSString *curFlutterRouteName;
18 | @property(nonatomic,copy) FlutterViewWillAppearBlock viewWillAppearBlock;
19 | @end
20 |
--------------------------------------------------------------------------------
/ios/Classes/FlutterViewWrapperController.m:
--------------------------------------------------------------------------------
1 | //
2 | // FlutterViewWrapperController.m
3 | // Runner
4 | //
5 | // Created by 正物 on 08/03/2018.
6 | // Copyright © 2018 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "FlutterViewWrapperController.h"
10 | #import
11 | #import "HybridStackManager.h"
12 |
13 | typedef NS_ENUM(NSInteger,FlutterVCSwitchCategory){
14 | FlutterVCSwitchCategoryOK,
15 | };
16 |
17 | typedef void (^FlutterWrapperHandleBlock)();
18 |
19 | @interface FlutterViewWrapperController ()
20 | @property (nonatomic,strong) UIImageView *fakeSnapImgView;
21 | @property(nonatomic,strong) UIImage *lastSnapshot;
22 | @property(nonatomic,copy) NSString *lastFlutterRouteName;
23 | @end
24 |
25 | @implementation FlutterViewWrapperController
26 | #pragma mark - LifeCycle
27 | - (instancetype)initWithURL:(NSURL *)url query:(NSDictionary *)query nativeParams:(NSDictionary *)nativeParams {
28 | self = [super initWithURL:url query:query nativeParams:nativeParams];
29 | if (self) {
30 | }
31 | return self;
32 | }
33 |
34 | - (void)loadView{
35 | UIView *view = [[UIView alloc] init];
36 | [view setBackgroundColor:[UIColor whiteColor]];
37 | self.view = view;
38 | }
39 |
40 | - (BOOL)shouldAutomaticallyForwardAppearanceMethods{
41 | return TRUE;
42 | }
43 |
44 | - (void)viewDidLoad {
45 | [super viewDidLoad];
46 | // Do any additional setup after loading the view.
47 | self.fakeSnapImgView = [[UIImageView alloc] initWithFrame:self.view.bounds];
48 | self.fakeSnapImgView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
49 | [self.fakeSnapImgView setBackgroundColor:[UIColor clearColor]];
50 | [self.view addSubview:self.fakeSnapImgView];
51 | }
52 |
53 | - (void)didReceiveMemoryWarning{
54 | [super didReceiveMemoryWarning];
55 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC];
56 | if([[flutterVC parentViewController] isEqual:self]){
57 | [flutterVC didReceiveMemoryWarning];
58 | }
59 | }
60 |
61 | -(void)viewWillAppear:(BOOL)animated{
62 | [super viewWillAppear:animated];
63 | [self.navigationController setNavigationBarHidden:YES animated:NO];
64 | if(self.viewWillAppearBlock){
65 | self.viewWillAppearBlock();
66 | self.viewWillAppearBlock = nil;
67 | }
68 | if(!self.lastSnapshot){
69 | dispatch_async(dispatch_get_main_queue(), ^{
70 | [self addChildFlutterVC];
71 | });
72 | }
73 | }
74 |
75 | -(void)viewDidAppear:(BOOL)animated {
76 | [super viewDidAppear:animated];
77 | [self addChildFlutterVC];
78 | if(self.curFlutterRouteName.length && self.lastSnapshot){
79 | [[HybridStackManager sharedInstance].methodChannel invokeMethod:@"popToRouteNamed" arguments:self.curFlutterRouteName];
80 | }
81 | [[FlutterViewWrapperController flutterVC].view setUserInteractionEnabled:TRUE];
82 | }
83 |
84 | - (void)viewWillDisappear:(BOOL)animated{
85 | [super viewWillDisappear:animated];
86 | UINavigationController *rootNav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController;
87 | NSArray *curStackAry = rootNav.viewControllers;
88 | NSInteger idx = [curStackAry indexOfObject:self];
89 | if(idx != NSNotFound && idx != curStackAry.count-1){
90 | [self saveSnapshot];
91 | }
92 | [[FlutterViewWrapperController flutterVC].view setUserInteractionEnabled:FALSE];
93 | }
94 |
95 | - (void)viewDidDisappear:(BOOL)animated {
96 | [super viewDidDisappear:animated];
97 | UINavigationController *rootNav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController;
98 | NSArray *ary = [rootNav.viewControllers filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) {
99 | if([evaluatedObject isKindOfClass:[FlutterViewWrapperController class]])
100 | return TRUE;
101 | return FALSE;
102 | }]];
103 | if(!ary.count){
104 | [[HybridStackManager sharedInstance].methodChannel invokeMethod:@"popToRoot" arguments:nil];
105 | }
106 |
107 | NSArray *curStackAry = rootNav.viewControllers;
108 | NSInteger idx = [curStackAry indexOfObject:self];
109 | if(idx == NSNotFound){
110 | [[HybridStackManager sharedInstance].methodChannel invokeMethod:@"popRouteNamed" arguments:self.lastFlutterRouteName];
111 | }
112 | }
113 | #pragma mark - Child/Parent VC
114 | - (void)showFlutterViewOverSnapshot{
115 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC];
116 | BOOL priorIsMyChild = (flutterVC.parentViewController == self);
117 | if(self.lastSnapshot){
118 | [self.view bringSubviewToFront:self.fakeSnapImgView];
119 | }
120 | flutterVC.view.frame = self.view.bounds;
121 | flutterVC.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
122 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
123 | [self.view bringSubviewToFront:flutterVC.view];
124 | self.lastSnapshot = nil;
125 | });
126 | }
127 |
128 | - (void)addChildFlutterVC{
129 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC];
130 | if(self == flutterVC.parentViewController){
131 | [self showFlutterViewOverSnapshot];
132 | return;
133 | }
134 | if( nil != flutterVC.parentViewController){
135 | [self removeChildFlutterVC];
136 | }
137 | [self.view addSubview:flutterVC.view];
138 | [self addChildViewController:flutterVC];
139 | [self showFlutterViewOverSnapshot];
140 | }
141 |
142 | - (void)removeChildFlutterVC{
143 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC];
144 | //Remove VC
145 | [flutterVC removeFromParentViewController];
146 | [flutterVC.view removeFromSuperview];
147 | }
148 |
149 | - (void)saveSnapshot{
150 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC];
151 | if(flutterVC.parentViewController != self)
152 | return;
153 | if(self.lastSnapshot == nil){
154 | UIGraphicsBeginImageContextWithOptions([UIScreen mainScreen].bounds.size, YES, 0);
155 | [flutterVC.view drawViewHierarchyInRect:flutterVC.view.bounds afterScreenUpdates:NO];
156 | self.lastSnapshot = UIGraphicsGetImageFromCurrentImageContext();
157 | UIGraphicsEndImageContext();
158 | [self.fakeSnapImgView setImage:self.lastSnapshot];
159 | [self.view bringSubviewToFront:self.fakeSnapImgView];
160 | }
161 | }
162 |
163 | + (XFlutterViewController *)flutterVC{
164 | static dispatch_once_t onceToken;
165 | static XFlutterViewController *sxFlutterVC;
166 | if(sxFlutterVC)
167 | return sxFlutterVC;
168 | dispatch_once(&onceToken, ^{
169 | sxFlutterVC = [[XFlutterViewController alloc] initWithProject:nil nibName:nil bundle:nil];
170 | });
171 | return sxFlutterVC;
172 | }
173 | @end
174 |
--------------------------------------------------------------------------------
/ios/Classes/HybridStackManager.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface HybridStackManager : NSObject
4 | + (instancetype)sharedInstance;
5 | @property (nonatomic,strong) FlutterMethodChannel* methodChannel;
6 | @property (nonatomic,strong) NSDictionary* mainEntryParams;
7 | @end
8 |
9 |
--------------------------------------------------------------------------------
/ios/Classes/HybridStackManager.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import "HybridStackManager.h"
3 | #import "FlutterViewWrapperController.h"
4 | #import "XURLRouter.h"
5 |
6 | @interface HybridStackManager()
7 | @property (nonatomic,strong) NSObject* registrar;
8 | @end
9 |
10 | @implementation HybridStackManager
11 | + (instancetype)sharedInstance{
12 | static HybridStackManager * sharedInst;
13 | static dispatch_once_t onceToken;
14 | dispatch_once(&onceToken, ^{
15 | sharedInst = [[HybridStackManager alloc] init];
16 | });
17 | return sharedInst;
18 | }
19 |
20 | + (void)registerWithRegistrar:(NSObject*)registrar{
21 | HybridStackManager* instance = [HybridStackManager sharedInstance];
22 | instance.methodChannel = [FlutterMethodChannel
23 | methodChannelWithName:@"hybrid_stack_manager"
24 | binaryMessenger:[registrar messenger]];
25 | [registrar addMethodCallDelegate:instance channel:instance.methodChannel];
26 | instance.registrar = registrar;
27 | }
28 |
29 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
30 | if ([@"openUrlFromNative" isEqualToString:call.method]) {
31 | NSDictionary *openUrlInfo = call.arguments;
32 | XOpenURLWithQueryAndParams(openUrlInfo[@"url"], openUrlInfo[@"query"],openUrlInfo[@"params"]);
33 | }
34 | else if([@"getMainEntryParams" isEqualToString:call.method]){
35 | NSDictionary *params = self.mainEntryParams?:@{};
36 | if([[[UIDevice currentDevice] systemVersion] compare:@"9.0" options:NSNumericSearch] == NSOrderedAscending){
37 | NSMutableDictionary *mutDict = [NSMutableDictionary dictionary];
38 | NSMutableDictionary *mutParams = [NSMutableDictionary dictionaryWithDictionary:params];
39 | params=mutParams;
40 | }
41 | result(params);
42 | // self.mainEntryParams = nil;
43 | }
44 | else if([@"updateCurFlutterRoute" isEqualToString:call.method]){
45 | NSString *curRouteName = call.arguments;
46 | UINavigationController *rootNav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController;
47 | UIViewController *topVC = rootNav.topViewController;
48 | if([topVC isKindOfClass:[FlutterViewWrapperController class]]){
49 | FlutterViewWrapperController *flutterVC = topVC;
50 | [flutterVC setCurFlutterRouteName:curRouteName];
51 | }
52 | }
53 | else if([@"popCurPage" isEqualToString:call.method]){
54 | BOOL animated = YES;
55 | if (call.arguments && [call.arguments isKindOfClass:[NSNumber class]]) {
56 | animated = [(NSNumber *)call.arguments boolValue];
57 | }
58 | UINavigationController *nav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController;
59 | if([nav.topViewController isKindOfClass:[FlutterViewWrapperController class]]){
60 | [nav popViewControllerAnimated:animated];
61 | }
62 | }
63 | else {
64 | result(FlutterMethodNotImplemented);
65 | }
66 | }
67 | @end
68 |
--------------------------------------------------------------------------------
/ios/Classes/UIViewController+URLRouter.h:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+URLRouter.h
3 | // hybrid_stack_manager
4 | //
5 | // Created by KyleWong on 2018/8/13.
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | @interface UIViewController (URLRouter)
13 | - (instancetype)initWithURL:(NSURL *)url query:(NSDictionary *)query nativeParams:(NSDictionary *)nativeParams;
14 | @end
15 |
16 | NS_ASSUME_NONNULL_END
17 |
--------------------------------------------------------------------------------
/ios/Classes/UIViewController+URLRouter.m:
--------------------------------------------------------------------------------
1 | //
2 | // UIViewController+URLRouter.m
3 | // hybrid_stack_manager
4 | //
5 | // Created by KyleWong on 2018/8/13.
6 | //
7 |
8 | #import "UIViewController+URLRouter.h"
9 |
10 | @implementation UIViewController (URLRouter)
11 | - (instancetype)initWithURL:(NSURL *)url query:(NSDictionary *)query nativeParams:(NSDictionary *)nativeParams{
12 | if(self = [super init]){
13 |
14 | }
15 | return self;
16 | }
17 | @end
18 |
--------------------------------------------------------------------------------
/ios/Classes/XFlutterModule.h:
--------------------------------------------------------------------------------
1 | //
2 | // XFlutterModule.h
3 | // FleaMarket
4 | //
5 | // Created by 正物 on 2018/03/08.
6 | // Copyright © 2017 正物. All rights reserved.
7 | //
8 |
9 | #import
10 | #import
11 | #import "FlutterViewWrapperController.h"
12 |
13 | @interface XFlutterModule : NSObject
14 | + (instancetype)new __attribute__((unavailable("Must use sharedInstance instead.")));
15 | - (instancetype)init __attribute__((unavailable("Must use sharedInstance instead.")));
16 | + (instancetype)sharedInstance;
17 | @property (nonatomic,assign) BOOL isInFlutterRootPage;
18 | @property (nonatomic,strong) XFlutterViewController *flutterVC;
19 | - (void)openURL:(NSString *)aUrl query:(NSDictionary *)query params:(NSDictionary *)params;
20 | - (void)warmupFlutter;
21 | @end
22 |
--------------------------------------------------------------------------------
/ios/Classes/XFlutterModule.m:
--------------------------------------------------------------------------------
1 | //
2 | // XFlutterModule.m
3 | // FleaMarket
4 | //
5 | // Created by 正物 on 2018/03/08.
6 | // Copyright © 2017 正物. All rights reserved.
7 | //
8 |
9 | #import "XFlutterModule.h"
10 | #import "HybridStackManager.h"
11 | #import
12 |
13 | @interface XFlutterModule()
14 | {
15 | BOOL _isInFlutterRootPage;
16 | bool _isFlutterWarmedup;
17 | }
18 | @end
19 |
20 | @implementation XFlutterModule
21 | @synthesize isInFlutterRootPage = _isInFlutterRootPage;
22 | #pragma mark - XModuleProtocol
23 | + (instancetype)sharedInstance{
24 | static XFlutterModule *sXFlutterModule;
25 | if(sXFlutterModule)
26 | return sXFlutterModule;
27 | static dispatch_once_t onceToken;
28 | dispatch_once(&onceToken, ^{
29 | sXFlutterModule = [[[self class] alloc] initInstance];
30 | [sXFlutterModule warmupFlutter];
31 | });
32 | return sXFlutterModule;
33 | }
34 |
35 | - (instancetype)initInstance{
36 | if(self = [super init]){
37 | _isInFlutterRootPage = TRUE;
38 | }
39 | return self;
40 | }
41 |
42 | - (XFlutterViewController *)flutterVC{
43 | return [FlutterViewWrapperController flutterVC];
44 | }
45 |
46 | - (void)warmupFlutter{
47 | if(_isFlutterWarmedup)
48 | return;
49 | XFlutterViewController *flutterVC = [FlutterViewWrapperController flutterVC];
50 | [flutterVC view];
51 | [NSClassFromString(@"GeneratedPluginRegistrant") performSelector:NSSelectorFromString(@"registerWithRegistry:") withObject:flutterVC];
52 | _isFlutterWarmedup = true;
53 | }
54 |
55 | + (NSDictionary *)parseParamsKV:(NSString *)aParamsStr{
56 | NSMutableDictionary *dict = [NSMutableDictionary dictionary];
57 | NSArray *kvAry = [aParamsStr componentsSeparatedByString:@"&"];
58 | for(NSString *kv in kvAry){
59 | NSArray *ary = [kv componentsSeparatedByString:@"="];
60 | if (ary.count == 2) {
61 | NSString *key = ary.firstObject;
62 | NSString *value = [ary.lastObject stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
63 | [dict setValue:value forKey:key];
64 | }
65 | }
66 | return dict;
67 | }
68 |
69 | - (void)openURL:(NSString *)aUrl query:(NSDictionary *)query params:(NSDictionary *)params{
70 | static BOOL sIsFirstPush = TRUE;
71 | //Process aUrl and Query Stuff.
72 | NSURL *url = [NSURL URLWithString:aUrl];
73 |
74 | NSMutableDictionary *mQuery = [NSMutableDictionary dictionaryWithDictionary:query];
75 | [mQuery addEntriesFromDictionary:[XFlutterModule parseParamsKV:url.query]];
76 | NSMutableDictionary *mParams = [NSMutableDictionary dictionaryWithDictionary:params];
77 | [mParams addEntriesFromDictionary:[XFlutterModule parseParamsKV:url.parameterString]];
78 | NSString *pageUrl = [NSString stringWithFormat:@"%@://%@",url.scheme,url.host];
79 |
80 | FlutterMethodChannel *methodChann = [HybridStackManager sharedInstance].methodChannel;
81 | NSMutableDictionary *arguments = [NSMutableDictionary dictionary];
82 | [arguments setValue:pageUrl forKey:@"url"];
83 |
84 | NSMutableDictionary *mutQuery = [NSMutableDictionary dictionary];
85 | for(NSString *key in query.allKeys){
86 | id value = [query objectForKey:key];
87 | //[TODO]: Add customized implementations for non-json-serializable objects into json-serializable ones.
88 | [mutQuery setValue:value forKey:key];
89 | }
90 | [arguments setValue:mutQuery forKey:@"query"];
91 |
92 | NSMutableDictionary *mutParams = [NSMutableDictionary dictionary];
93 | for(NSString *key in mParams.allKeys){
94 | id value = [mParams objectForKey:key];
95 | //[TODO]: Add customized implementations for non-json-serializable objects into json-serializable ones.
96 | [mutParams setValue:value forKey:key];
97 | }
98 | [arguments setValue:mutParams forKey:@"params"];
99 |
100 | [arguments setValue:@(0) forKey:@"animated"];
101 |
102 | //Push
103 | UINavigationController *currentNavigation = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController;
104 | FlutterViewWrapperController *viewController = [[FlutterViewWrapperController alloc] initWithURL:[NSURL URLWithString:aUrl] query:query nativeParams:params];
105 | viewController.viewWillAppearBlock = ^(){
106 | //Process first & later message sending according distinguishly.
107 | if(sIsFirstPush){
108 | [HybridStackManager sharedInstance].mainEntryParams = arguments;
109 | sIsFirstPush = FALSE;
110 | }
111 | else{
112 | [methodChann invokeMethod:@"openURLFromFlutter" arguments:arguments result:^(id _Nullable result) {
113 | }];
114 | }
115 | };
116 | [currentNavigation pushViewController:viewController animated:YES];
117 | }
118 |
119 | #pragma mark - XFlutterModuleProtocol
120 | @end
121 |
--------------------------------------------------------------------------------
/ios/Classes/XFlutterViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // XFlutterViewController.h
3 | // flutter_chann_plugin
4 | //
5 | // Created by 正物 on 18/03/2018.
6 | //
7 |
8 | #import
9 |
10 | @interface XFlutterViewController : FlutterViewController
11 | @end
12 |
--------------------------------------------------------------------------------
/ios/Classes/XFlutterViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // XFlutterViewController.m
3 | // flutter_chann_plugin
4 | //
5 | // Created by 正物 on 18/03/2018.
6 | //
7 |
8 | #import "XFlutterViewController.h"
9 |
10 | @interface XFlutterViewController ()
11 | @property (nonatomic,assign) BOOL enableViewWillAppear;
12 | @end
13 |
14 | @implementation XFlutterViewController
15 |
16 | - (void)viewDidLoad {
17 | [super viewDidLoad];
18 | self.enableViewWillAppear = TRUE;
19 | // Do any additional setup after loading the view.
20 | }
21 |
22 | - (void)didReceiveMemoryWarning {
23 | [super didReceiveMemoryWarning];
24 | // Dispose of any resources that can be recreated.
25 | }
26 |
27 | - (void)viewWillAppear:(BOOL)animated{
28 | if(self.enableViewWillAppear == FALSE)
29 | return;
30 | [super viewWillAppear:animated];
31 | self.enableViewWillAppear = FALSE;
32 | }
33 |
34 | - (void)viewDidAppear:(BOOL)animated{
35 | [super viewDidAppear:animated];
36 | }
37 |
38 | - (void)viewWillDisappear:(BOOL)animated{
39 | [super viewWillDisappear:animated];
40 | }
41 |
42 | - (void)viewDidDisappear:(BOOL)animated{
43 | [super viewDidDisappear:animated];
44 | self.enableViewWillAppear = TRUE;
45 | }
46 | /*
47 | #pragma mark - Navigation
48 |
49 | // In a storyboard-based application, you will often want to do a little preparation before navigation
50 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
51 | // Get the new view controller using [segue destinationViewController].
52 | // Pass the selected object to the new view controller.
53 | }
54 | */
55 | //- (UIEdgeInsets)paddingEdgeInsets{
56 | // UIEdgeInsets edgeInsets = UIEdgeInsetsZero;
57 | // if (@available(iOS 11, *)) {
58 | // edgeInsets = UIEdgeInsetsMake(0, self.view.safeAreaInsets.left, self.view.safeAreaInsets.bottom, self.view.safeAreaInsets.right);
59 | // } else {
60 | // edgeInsets = UIEdgeInsetsZero;
61 | // }
62 | // return edgeInsets;
63 | //}
64 | @end
65 |
66 |
--------------------------------------------------------------------------------
/ios/Classes/XURLRouter.h:
--------------------------------------------------------------------------------
1 | //
2 | // XURLRouter.h
3 | // Runner
4 | //
5 | // Created by KyleWong on 2018/8/13.
6 | // Copyright © 2018 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import
10 | #define kOpenUrlPrefix @"hrd"
11 |
12 | typedef UIViewController* (^NativeOpenUrlHandler)(NSString *,NSDictionary *,NSDictionary *);
13 | void XOpenURLWithQueryAndParams(NSString *url,NSDictionary *query,NSDictionary *params);
14 |
15 | @interface XURLRouter : NSObject
16 | @property (nonatomic,weak) NativeOpenUrlHandler nativeOpenUrlHandler;
17 | + (instancetype)sharedInstance;
18 | @end
19 |
--------------------------------------------------------------------------------
/ios/Classes/XURLRouter.m:
--------------------------------------------------------------------------------
1 | //
2 | // XURLRouter.m
3 | // Runner
4 | //
5 | // Created by KyleWong on 2018/8/13.
6 | // Copyright © 2018 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "XURLRouter.h"
10 | #import "XFlutterModule.h"
11 |
12 | @implementation XURLRouter
13 | + (instancetype)sharedInstance{
14 | static XURLRouter *sInstance;
15 | static dispatch_once_t onceToken;
16 | dispatch_once(&onceToken, ^{
17 | sInstance = [XURLRouter new];
18 | });
19 | return sInstance;
20 | }
21 | @end
22 |
23 | void XOpenURLWithQueryAndParams(NSString *url,NSDictionary *query,NSDictionary *params){
24 | NSURL *tmpUrl = [NSURL URLWithString:url];
25 | UINavigationController *rootNav = (UINavigationController*)[UIApplication sharedApplication].delegate.window.rootViewController;
26 | if(![kOpenUrlPrefix isEqualToString:tmpUrl.scheme])
27 | return;
28 | if([[query objectForKey:@"flutter"] boolValue]){
29 | [[XFlutterModule sharedInstance] openURL:url query:query params:params];
30 | return;
31 | }
32 | NativeOpenUrlHandler handler = [XURLRouter sharedInstance].nativeOpenUrlHandler;
33 | if(handler!=nil)
34 | {
35 | UIViewController *vc = handler(url,query,params);
36 | if(vc!=nil)
37 | [rootNav pushViewController:vc animated:YES];
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/ios/LICENSE:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FlutterRepo/hybrid_stack_manager/a1abfd3cf541a5fed92554d3b56372ab70fd11eb/ios/LICENSE
--------------------------------------------------------------------------------
/ios/README.md:
--------------------------------------------------------------------------------
1 | ##XFlutterModule
2 |
--------------------------------------------------------------------------------
/ios/hybrid_stack_manager.podspec.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hybrid_stack_manager",
3 | "version": "0.0.6",
4 | "summary": "hybrid_stack_manager",
5 | "description": "混合栈管理",
6 | "homepage": "https://github.com/FlutterRepo/hybrid_stack_manager.git",
7 | "license": {
8 | "type": "MIT",
9 | "file": "LICENSE"
10 | },
11 | "authors": {
12 | "正物": "kang.wang1988@gmail.com"
13 | },
14 | "platforms": {
15 | "ios": "8.0"
16 | },
17 | "source": {
18 | "git": "git@github.com:FlutterRepo/hybrid_stack_manager.git",
19 | "tag": "0.0.6"
20 | },
21 | "source_files": [
22 | "Classes",
23 | "Classes/*.{h,m}",
24 | "Classes/**/*.{h,m}"
25 | ],
26 | "dependency": [
27 | "Flutter"
28 | ],
29 | "exclude_files": "Classes/Exclude",
30 | "resources": "Resources/*",
31 | "prefix_header_contents": "",
32 | "requires_arc": true
33 | }
--------------------------------------------------------------------------------
/lib/hybrid_stack_manager.dart:
--------------------------------------------------------------------------------
1 | import 'dart:async';
2 | import 'package:flutter/services.dart';
3 |
4 | typedef Future MethodHandler(MethodCall call);
5 |
6 | class HybridStackManagerPlugin {
7 | static HybridStackManagerPlugin hybridStackManagerPlugin =
8 | new HybridStackManagerPlugin._internal();
9 | MethodChannel _channel;
10 | MethodHandler _handler;
11 | void setMethodCallHandler(MethodHandler hdler) {
12 | _handler = hdler;
13 | _channel.setMethodCallHandler(_handler);
14 | }
15 |
16 | HybridStackManagerPlugin._internal() {
17 | _channel = new MethodChannel('hybrid_stack_manager');
18 | }
19 | openUrlFromNative({String url, Map query, Map params, bool animated}) {
20 | _channel.invokeMethod("openUrlFromNative", {
21 | "url": url ?? "",
22 | "query": (query ?? {}),
23 | "params": (params ?? {}),
24 | "animated": animated ?? true
25 | });
26 | }
27 |
28 | popCurPage({bool animated = true}) {
29 | _channel.invokeMethod("popCurPage", animated);
30 | }
31 |
32 | updateCurFlutterRoute(String curRouteName) {
33 | _channel.invokeMethod("updateCurFlutterRoute", curRouteName ?? "");
34 | }
35 |
36 | Future getMainEntryParams() async {
37 | dynamic info = await _channel.invokeMethod("getMainEntryParams");
38 | return new Future.sync(() => info as Map);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lib/hybrid_stack_manager_plugin.dart:
--------------------------------------------------------------------------------
1 | export 'hybrid_stack_manager.dart';
2 | export 'router_option.dart';
3 | export 'router.dart';
4 | export 'utils.dart';
5 |
--------------------------------------------------------------------------------
/lib/router.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter/services.dart';
3 | import 'router_option.dart';
4 | import 'hybrid_stack_manager.dart';
5 | import 'utils.dart';
6 |
7 | typedef Widget FlutterWidgetHandler({RouterOption routeOption, Key key});
8 |
9 | class XMaterialPageRoute extends MaterialPageRoute {
10 | final WidgetBuilder builder;
11 | final bool animated;
12 | Duration get transitionDuration {
13 | if (animated == true) return const Duration(milliseconds: 300);
14 | return const Duration(milliseconds: 0);
15 | }
16 |
17 | XMaterialPageRoute({
18 | this.builder,
19 | this.animated,
20 | RouteSettings settings: const RouteSettings(),
21 | }) : super(builder: builder, settings: settings);
22 | }
23 |
24 | class Router extends Object {
25 | static final Router singleton = new Router._internal();
26 | List flutterRootPageNameLst = new List();
27 | String currentPageUrl = null;
28 | FlutterWidgetHandler routerWidgetHandler;
29 | GlobalKey globalKeyForRouter;
30 | static Router sharedInstance() {
31 | return singleton;
32 | }
33 |
34 | Router._internal() {
35 | HybridStackManagerPlugin.hybridStackManagerPlugin
36 | .setMethodCallHandler((MethodCall methodCall) {
37 | String method = methodCall.method;
38 | if (method == "openURLFromFlutter") {
39 | Map args = methodCall.arguments;
40 | if (args != null) {
41 | bool animated = (args["animated"] == 1);
42 | Router.sharedInstance().pushPageWithOptionsFromFlutter(
43 | routeOption: new RouterOption(
44 | url: args["url"],
45 | query: args["query"],
46 | params: args["params"]),
47 | animated: animated ?? false);
48 | }
49 | } else if (method == "popToRoot") {
50 | Router.sharedInstance().popToRoot();
51 | } else if (method == "popToRouteNamed") {
52 | Router.sharedInstance().popToRouteNamed(methodCall.arguments);
53 | } else if (method == "popRouteNamed") {
54 | Router.sharedInstance().popRouteNamed(methodCall.arguments);
55 | }
56 | });
57 | }
58 | popToRoot() {
59 | NavigatorState navState = Navigator.of(globalKeyForRouter.currentContext);
60 | List> navHistory = navState.history;
61 | int histLen = navHistory.length;
62 | for (int i = histLen - 1; i >= 1; i--) {
63 | Route route = navHistory.elementAt(i);
64 | navState.removeRoute(route);
65 | }
66 | }
67 |
68 | popToRouteNamed(String routeName) {
69 | NavigatorState navState = Navigator.of(globalKeyForRouter.currentContext);
70 | List> navHistory = navState.history;
71 | int histLen = navHistory.length;
72 | for (int i = histLen - 1; i >= 1; i--) {
73 | Route route = navHistory.elementAt(i);
74 | if (!(route is XMaterialPageRoute) ||
75 | ((route as XMaterialPageRoute).settings.name != routeName)) {
76 | navState.removeRoute(route);
77 | }
78 | if ((route is XMaterialPageRoute) &&
79 | ((route as XMaterialPageRoute).settings.name == routeName)) break;
80 | }
81 | }
82 |
83 | popRouteNamed(String routeName) {
84 | NavigatorState navState = Navigator.of(globalKeyForRouter.currentContext);
85 | List> navHistory = navState.history;
86 | int histLen = navHistory.length;
87 | for (int i = histLen - 1; i >= 1; i--) {
88 | Route route = navHistory.elementAt(i);
89 | if ((route is XMaterialPageRoute) &&
90 | ((route as XMaterialPageRoute).settings.name == routeName)) {
91 | navState.removeRoute(route);
92 | break;
93 | }
94 | }
95 | }
96 |
97 | pushPageWithOptionsFromFlutter({RouterOption routeOption, bool animated}) {
98 | Widget page =
99 | Router.sharedInstance().pageFromOption(routeOption: routeOption);
100 | if (page != null) {
101 | XMaterialPageRoute pageRoute = new XMaterialPageRoute(
102 | settings: new RouteSettings(name: routeOption.userInfo),
103 | animated: animated,
104 | builder: (BuildContext context) {
105 | return page;
106 | });
107 |
108 | Navigator.of(globalKeyForRouter.currentContext).push(pageRoute);
109 | HybridStackManagerPlugin.hybridStackManagerPlugin
110 | .updateCurFlutterRoute(routeOption.userInfo);
111 | } else {
112 | HybridStackManagerPlugin.hybridStackManagerPlugin.openUrlFromNative(
113 | url: routeOption.url,
114 | query: routeOption.query,
115 | params: routeOption.params);
116 | }
117 | NavigatorState navState = Navigator.of(globalKeyForRouter.currentContext);
118 | List> navHistory = navState.history;
119 | }
120 |
121 | pushPageWithOptionsFromNative({RouterOption routeOption, bool animated}) {
122 | HybridStackManagerPlugin.hybridStackManagerPlugin.openUrlFromNative(
123 | url: routeOption.url,
124 | query: routeOption.query,
125 | params: routeOption.params,
126 | animated: animated);
127 | }
128 |
129 | pageFromOption({RouterOption routeOption, Key key}) {
130 | try {
131 | currentPageUrl = routeOption.url + "?" + converUrl(routeOption.query);
132 | } catch (e) {}
133 | routeOption.userInfo = Utils.generateUniquePageName(routeOption.url);
134 | if (routerWidgetHandler != null)
135 | return routerWidgetHandler(routeOption: routeOption, key: key);
136 | }
137 |
138 | static String converUrl(Map query) {
139 | String tmpUrl = "";
140 | if (query != null) {
141 | bool skipfirst = true;
142 | query.forEach((key, value) {
143 | if (skipfirst) {
144 | skipfirst = false;
145 | } else {
146 | tmpUrl = tmpUrl + "&";
147 | }
148 | tmpUrl = tmpUrl + (key + "=" + value.toString());
149 | });
150 | }
151 | return Uri.encodeFull(tmpUrl);
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/lib/router_option.dart:
--------------------------------------------------------------------------------
1 | class RouterOption {
2 | String url;
3 | Map query;
4 | Map params;
5 | String userInfo;
6 | RouterOption({this.url, this.query, this.params});
7 | }
8 |
--------------------------------------------------------------------------------
/lib/utils.dart:
--------------------------------------------------------------------------------
1 | class Utils extends Object {
2 | static int baseId = 100;
3 | static String pageNameSeperatorToken = "_";
4 |
5 | static int generatePrimaryPageId() {
6 | return baseId++;
7 | }
8 |
9 | static Map parseUniquePageName(String pageName) {
10 | List components = pageName.split(pageNameSeperatorToken);
11 | if (components.length != 2) return null;
12 | return {"name": components[0], "id": components[1]};
13 | }
14 |
15 | static String generateUniquePageName(String pageName) {
16 | return (pageName ?? "") +
17 | pageNameSeperatorToken +
18 | generatePrimaryPageId().toString();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: hybrid_stack_manager
2 | description: Hybrid stack management when using Flutter and Native(iOS/Android) simutaneously.
3 | version: 0.0.6
4 | author: KyleWong
5 | homepage: https://github.com/FlutterRepo/hybrid_stack_manager.git
6 |
7 | environment:
8 | sdk: ">=2.0.0-dev.48.0 <3.0.0"
9 | flutter: ">=0.3.1 <2.0.0"
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | # For information on the generic Dart part of this file, see the
15 | # following page: https://www.dartlang.org/tools/pub/pubspec
16 |
17 | # The following section is specific to Flutter.
18 | flutter:
19 | plugin:
20 | androidPackage: com.taobao.hybridstackmanager
21 | pluginClass: HybridStackManager
22 |
23 | # To add assets to your plugin package, add an assets section, like this:
24 | # assets:
25 | # - images/a_dot_burr.jpeg
26 | # - images/a_dot_ham.jpeg
27 | #
28 | # For details regarding assets in packages, see
29 | # https://flutter.io/assets-and-images/#from-packages
30 | #
31 | # An image asset can refer to one or more resolution-specific "variants", see
32 | # https://flutter.io/assets-and-images/#resolution-aware.
33 |
34 | # To add custom fonts to your plugin package, add a fonts section here,
35 | # in this "flutter" section. Each entry in this list should have a
36 | # "family" key with the font family name, and a "fonts" key with a
37 | # list giving the asset and other descriptors for the font. For
38 | # example:
39 | # fonts:
40 | # - family: Schyler
41 | # fonts:
42 | # - asset: fonts/Schyler-Regular.ttf
43 | # - asset: fonts/Schyler-Italic.ttf
44 | # style: italic
45 | # - family: Trajan Pro
46 | # fonts:
47 | # - asset: fonts/TrajanPro.ttf
48 | # - asset: fonts/TrajanPro_Bold.ttf
49 | # weight: 700
50 | #
51 | # For details regarding fonts in packages, see
52 | # https://flutter.io/custom-fonts/#from-packages
53 |
--------------------------------------------------------------------------------