├── .gitignore
├── .metadata
├── CHANGELOG.md
├── LICENSE
├── README.md
├── ReadMe
├── iOS研发.png
└── 代码结构.png
├── android
├── .gitignore
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
├── settings.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── kotlin
│ ├── io
│ └── flutter
│ │ └── embedding
│ │ └── android
│ │ ├── DFlutterActivity.java
│ │ ├── DFlutterPageDelegate.java
│ │ └── DViewUtils.java
│ └── tal
│ └── com
│ └── d_stack
│ ├── DStack.java
│ ├── DStackPlugin.kt
│ ├── action
│ ├── DActionManager.java
│ └── DOperationManager.java
│ ├── channel
│ └── DStackMethodHandler.java
│ ├── lifecycle
│ ├── PageLifecycleManager.java
│ ├── PageModel.java
│ └── PageState.java
│ ├── node
│ ├── DNode.java
│ ├── DNodeManager.java
│ ├── DNodeResponse.java
│ └── constants
│ │ ├── DNodeActionType.java
│ │ └── DNodePageType.java
│ ├── observer
│ ├── DStackActivityManager.java
│ ├── DStackLifecycleObserver.java
│ └── FilterActivityManager.java
│ ├── router
│ ├── INativeRouter.java
│ └── INodeOperation.java
│ └── utils
│ ├── DLog.java
│ └── DStackUtils.java
├── example
├── .gitignore
├── .metadata
├── README.md
├── android
│ ├── .gitignore
│ ├── app
│ │ ├── build.gradle
│ │ └── src
│ │ │ ├── debug
│ │ │ └── AndroidManifest.xml
│ │ │ ├── main
│ │ │ ├── AndroidManifest.xml
│ │ │ ├── kotlin
│ │ │ │ └── tal
│ │ │ │ │ └── com
│ │ │ │ │ └── d_stack_example
│ │ │ │ │ ├── DStackApplication.java
│ │ │ │ │ ├── FlutterContainerActivity.kt
│ │ │ │ │ ├── NativeOneActivity.java
│ │ │ │ │ ├── NativeThreeActivity.java
│ │ │ │ │ └── NativeTwoActivity.java
│ │ │ └── res
│ │ │ │ ├── drawable
│ │ │ │ └── launch_background.xml
│ │ │ │ ├── layout
│ │ │ │ ├── layout_one.xml
│ │ │ │ ├── layout_three.xml
│ │ │ │ └── layout_two.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ │ ├── values
│ │ │ │ ├── color.xml
│ │ │ │ └── styles.xml
│ │ │ │ └── xml
│ │ │ │ └── network_security_config.xml
│ │ │ └── profile
│ │ │ └── AndroidManifest.xml
│ ├── build.gradle
│ ├── gradle.properties
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ └── settings.gradle
├── ios
│ ├── .gitignore
│ ├── Flutter
│ │ ├── .last_build_id
│ │ ├── AppFrameworkInfo.plist
│ │ ├── Debug.xcconfig
│ │ └── Release.xcconfig
│ ├── Podfile
│ ├── Podfile.lock
│ ├── Runner.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── xcschemes
│ │ │ └── Runner.xcscheme
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ └── Runner
│ │ ├── AppDelegate.h
│ │ ├── AppDelegate.m
│ │ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ ├── Contents.json
│ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ ├── Icon-App-20x20@1x.png
│ │ │ ├── Icon-App-20x20@2x.png
│ │ │ ├── Icon-App-20x20@3x.png
│ │ │ ├── Icon-App-29x29@1x.png
│ │ │ ├── Icon-App-29x29@2x.png
│ │ │ ├── Icon-App-29x29@3x.png
│ │ │ ├── Icon-App-40x40@1x.png
│ │ │ ├── Icon-App-40x40@2x.png
│ │ │ ├── Icon-App-40x40@3x.png
│ │ │ ├── Icon-App-60x60@2x.png
│ │ │ ├── Icon-App-60x60@3x.png
│ │ │ ├── Icon-App-76x76@1x.png
│ │ │ ├── Icon-App-76x76@2x.png
│ │ │ └── Icon-App-83.5x83.5@2x.png
│ │ └── LaunchImage.imageset
│ │ │ ├── Contents.json
│ │ │ ├── LaunchImage.png
│ │ │ ├── LaunchImage@2x.png
│ │ │ ├── LaunchImage@3x.png
│ │ │ └── README.md
│ │ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ │ ├── CustomViewController.h
│ │ ├── CustomViewController.m
│ │ ├── DStackTestCase.h
│ │ ├── DStackTestCase.m
│ │ ├── DStackViewController.h
│ │ ├── DStackViewController.m
│ │ ├── DemoFlutterViewController.h
│ │ ├── DemoFlutterViewController.m
│ │ ├── FiveViewController.h
│ │ ├── FiveViewController.m
│ │ ├── FourViewController.h
│ │ ├── FourViewController.m
│ │ ├── HomeViewController.h
│ │ ├── HomeViewController.m
│ │ ├── Info.plist
│ │ ├── SecondViewController.h
│ │ ├── SecondViewController.m
│ │ ├── SixViewController.h
│ │ ├── SixViewController.m
│ │ ├── ThirdViewController.h
│ │ ├── ThirdViewController.m
│ │ └── main.m
├── lib
│ ├── main.dart
│ ├── page_widgets.dart
│ ├── test_case.dart
│ └── will_pop_scope_route.dart
├── pubspec.lock
├── pubspec.yaml
└── test
│ └── widget_test.dart
├── ios
├── .gitignore
├── Assets
│ └── .gitkeep
├── Classes
│ ├── Node
│ │ ├── DActionManager.h
│ │ ├── DActionManager.m
│ │ ├── DNode.h
│ │ ├── DNode.m
│ │ ├── DNodeManager.h
│ │ └── DNodeManager.m
│ └── Stack
│ │ ├── DFlutterViewController.h
│ │ ├── DFlutterViewController.m
│ │ ├── DNavigator.h
│ │ ├── DNavigator.m
│ │ ├── DStack.h
│ │ ├── DStack.m
│ │ ├── DStackPlugin.h
│ │ ├── DStackPlugin.m
│ │ └── DStackProvider.h
└── d_stack.podspec
├── lib
├── channel
│ └── dchannel.dart
├── constant
│ └── constant_config.dart
├── d_stack.dart
├── navigator
│ ├── dnavigator_gesture_observer.dart
│ ├── dnavigator_manager.dart
│ └── node_entity.dart
├── observer
│ ├── d_node_observer.dart
│ └── life_cycle_observer.dart
└── widget
│ ├── home_widget.dart
│ └── page_route.dart
├── pubspec.lock
├── pubspec.yaml
└── test
└── d_stack_test.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # Visual Studio Code related
19 | .vscode/
20 |
21 | # Flutter/Dart/Pub related
22 | **/doc/api/
23 | .dart_tool/
24 | .flutter-plugins
25 | .packages
26 | .pub-cache/
27 | .pub/
28 | /build/
29 |
30 | # Android related
31 | **/android/**/gradle-wrapper.jar
32 | **/android/.gradle
33 | **/android/captures/
34 | **/android/gradlew
35 | **/android/gradlew.bat
36 | **/android/local.properties
37 | **/android/**/GeneratedPluginRegistrant.java
38 |
39 | # iOS/XCode related
40 | **/ios/**/*.mode1v3
41 | **/ios/**/*.mode2v3
42 | **/ios/**/*.moved-aside
43 | **/ios/**/*.pbxuser
44 | **/ios/**/*.perspectivev3
45 | **/ios/**/*sync/
46 | **/ios/**/.sconsign.dblite
47 | **/ios/**/.tags*
48 | **/ios/**/.vagrant/
49 | **/ios/**/DerivedData/
50 | **/ios/**/Icon?
51 | **/ios/**/Pods/
52 | **/ios/**/.symlinks/
53 | **/ios/**/profile
54 | **/ios/**/xcuserdata
55 | **/ios/.generated/
56 | **/ios/Flutter/App.framework
57 | **/ios/Flutter/Flutter.framework
58 | **/ios/Flutter/Generated.xcconfig
59 | **/ios/Flutter/app.flx
60 | **/ios/Flutter/app.zip
61 | **/ios/Flutter/flutter_assets/
62 | **/ios/ServiceDefinitions.json
63 | **/ios/Runner/GeneratedPluginRegistrant.*
64 |
65 | # Exceptions to above rules.
66 | !**/ios/**/default.mode1v3
67 | !**/ios/**/default.mode2v3
68 | !**/ios/**/default.pbxuser
69 | !**/ios/**/default.perspectivev3
70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
71 |
--------------------------------------------------------------------------------
/.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: 27321ebbad34b0a3fafe99fac037102196d655ff
8 | channel: stable
9 |
10 | project_type: plugin
11 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.3.4+3-nullsafety
2 | * Android侧修改判断appStart条件
3 | ## 1.3.4+2-nullsafety
4 | * Android侧添加打开透明Flutter控制器方法
5 | ## 1.3.4+1-nullsafety
6 | * iOS侧加入防止同一节点重复入栈的容错
7 | ## 1.3.4-nullsafety
8 | * 支持空安全
9 | ## 1.3.3+1
10 | * android修复根节点返回键问题
11 | ## 1.3.3
12 | * 添加pushAndRemoveUntil方法,iOS修复bug
13 | ## 1.3.2
14 | * Android支持flutter2.0
15 | ## 1.3.1+2
16 | * 修复iOS侧gesture可能导致的数组越界
17 | ## 1.3.1+1
18 | * 修复iOS pop参数传递不能传递问题
19 | ## 1.3.1
20 | * 修复android临近节点判断问题
21 | ## 1.3.0
22 | * 解决白屏问题
23 | * flutter支持自定义进场动画手势返回
24 | * flutter侧dialog入栈管理
25 | * 安卓、iOS侧节点重构
26 | ## 1.2.8+4
27 | * 暴露是否开启动画属性,修复iOS13以上dismiss crash
28 | ## 1.2.8+3
29 | * iOS优化pop手势返回和系统类判断
30 | ## 1.2.8+2
31 | * iOS解决crash
32 | ## 1.2.8+1
33 | * iOS修复bug
34 | ## 1.2.8
35 | * DStack增加过滤activity功能
36 | ## 1.2.7
37 | * 修复短间隔快速关闭两个activity引起的节点错乱
38 | ## 1.2.6
39 | * 增加微信微博qq登陆第三方Activity的过滤
40 | ## 1.2.5
41 | * 修复iOS手势页面滑动取消问题
42 | ## 1.2.4
43 | * 修复iOS flutter打开native传参
44 | ## 1.2.3
45 | * 修复android oppo推送bug,iOS13 dismiss兼容,开放present及自定义动画
46 | ## 1.2.2
47 | * 优化iOS节点去重
48 | ## 1.2.1
49 | * 修复android华为推送bug
50 | ## 1.2.0
51 | * 修复android 出栈bug
52 | ## 1.1.9
53 | * 修复android 第三方activity bug
54 | ## 1.1.8
55 | * 修复ios bug
56 | ## 1.1.7
57 | * 修复ios bug
58 | ## 1.1.6
59 | * 修复可能的android类名错误
60 | ## 1.1.5
61 | * 修复 iOS的homePage不能刷新的异常
62 | ## 1.1.4
63 | * iOS新增flutterViewcontroller 的回调
64 | ## 1.1.3
65 | * 修复错误tag
66 | ## 1.1.2
67 | * 修复生命周期回调bug
68 | ## 1.1.1
69 | * 添加flutter侧获取节点列表功能
70 | ## 1.1.0
71 | * 添加生命周期监听功能,android修改集成方式,现在更加简单了
72 | ## 1.0.0
73 | ## 0.0.1
74 | * TODO: Describe initial release.
75 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2020
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 前言
2 |
3 | 
4 |
5 | Flutter是谷歌的移动UI框架,可以快速在iOS和Android上构建高质量的原生用户界面。 Flutter可以与现有的代码一起工作。
6 |
7 | DStack是为了解决在使用Flutter进行混合开发时,不同类型的页面之间互相跳转时的统一管理和处理。
8 |
9 | 2020年5月,我们对DStack进行整理、封装和推广。
10 |
11 | 2020年8月,集团内部开源,9月,外部开源,以此共建交流。
12 |
13 | 开源并不是我们的终点,我们希望能有更多小伙伴和我们共建DStack,我们一起为Flutter社区做更多的贡献。
14 |
15 |
16 |
17 | ## DStack详细文档
18 |
19 | [中文文档](https://www.yuque.com/tal-tech/ag1kaf)
20 |
21 |
22 |
23 | ## 设计方向
24 |
25 | DStack是基于**节点**进行管理的,**使用简单,易于集成,性能优秀**的混合开发框架。
26 |
27 | - **节点管理**:不同类型页面抽象成节点这种数据结构,便于后期的扩展
28 | - **引擎复用**:利用Flutter引擎复用机制,框架内存性能优秀
29 | - **简单实用**:追求集成和使用简单,对原有工程改动小
30 | - **持续积累**:紧跟Flutter团队每次版本升级,解决新问题,尝试新思路,不断优化
31 | - **开源心态**:开放公开,接受任何源码的贡献,但有比较严格的代码审核
32 |
33 | ## 功能简介
34 |
35 | - 混合页面之间随意跳转
36 |
37 | - 混合页面一致的生命周期管理
38 |
39 | - 页面间数据传递,回传等
40 |
41 | - iOS侧滑返回和android返回键返回
42 |
43 | - 提供一致的页面路由方案
44 |
45 |
46 |
47 | ## 发行版本介绍
48 |
49 | DStack目前有一个版本
50 |
51 | - master分支为tag1.3.1+2 稳定版本
52 |
53 | ### 以下为1.3.1+2 版本安装
54 |
55 | #### 1.引入
56 |
57 | 在 pubspec.yaml 文件中添加依赖:
58 |
59 | ```dart
60 | dependencies:
61 | d_stack: ^1.3.1+2
62 | ```
63 |
64 | #### 2.安装
65 |
66 | 命令行下执行:
67 |
68 | **flutter pub get**
69 |
70 | ## 软件作者贡献列表
71 | @xiaoyuyouer @whqfor @caven775
72 |
73 | (其他贡献者、请详见文档鸣谢)
74 |
75 | ## 合作伙伴
76 |
77 | 
78 |
79 | ## 联系我们
80 |
81 |
82 |
83 | issue: https://github.com/tal-tech/d_stack/issues
84 |
85 |
--------------------------------------------------------------------------------
/ReadMe/iOS研发.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/ReadMe/iOS研发.png
--------------------------------------------------------------------------------
/ReadMe/代码结构.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/ReadMe/代码结构.png
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | group 'tal.com.d_stack'
2 | version '1.0-SNAPSHOT'
3 |
4 | buildscript {
5 | ext.kotlin_version = '1.3.70'
6 | repositories {
7 | google()
8 | jcenter()
9 | }
10 |
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:3.6.1'
13 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
14 | }
15 | }
16 |
17 | rootProject.allprojects {
18 | repositories {
19 | google()
20 | jcenter()
21 | }
22 | }
23 |
24 | apply plugin: 'com.android.library'
25 | apply plugin: 'kotlin-android'
26 |
27 | android {
28 | compileSdkVersion 29
29 |
30 | sourceSets {
31 | main.java.srcDirs += 'src/main/kotlin'
32 | }
33 | defaultConfig {
34 | minSdkVersion 21
35 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
36 | }
37 | lintOptions {
38 | disable 'InvalidPackage'
39 | }
40 | }
41 |
42 | dependencies {
43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
44 | implementation 'androidx.appcompat:appcompat:1.2.0-alpha03'
45 | }
46 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Mar 27 14:56:17 CST 2020
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-5.6.4-all.zip
7 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'd_stack'
2 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/io/flutter/embedding/android/DViewUtils.java:
--------------------------------------------------------------------------------
1 | // Copyright 2013 The Flutter 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 io.flutter.embedding.android;
6 |
7 | import android.app.Activity;
8 | import android.content.Context;
9 | import android.content.ContextWrapper;
10 | import android.os.Build;
11 | import android.view.View;
12 |
13 | public final class DViewUtils {
14 | /**
15 | * Retrieves the {@link Activity} from a given {@link Context}.
16 | *
17 | *
This method will recursively traverse up the context chain if it is a {@link ContextWrapper}
18 | * until it finds the first instance of the base context that is an {@link Activity}.
19 | */
20 | public static Activity getActivity(Context context) {
21 | if (context == null) {
22 | return null;
23 | }
24 | if (context instanceof Activity) {
25 | return (Activity) context;
26 | }
27 | if (context instanceof ContextWrapper) {
28 | // Recurse up chain of base contexts until we find an Activity.
29 | return getActivity(((ContextWrapper) context).getBaseContext());
30 | }
31 | return null;
32 | }
33 |
34 | /**
35 | * Generates a view id.
36 | *
37 | *
In API level 17 and above, this ID is unique. Below 17, the fallback id is used instead.
38 | *
39 | * @param fallbackId the fallback id.
40 | * @return the view id.
41 | */
42 | public static int generateViewId(int fallbackId) {
43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
44 | return View.generateViewId();
45 | }
46 | return fallbackId;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/DStackPlugin.kt:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack
2 |
3 | import androidx.annotation.NonNull;
4 | import io.flutter.embedding.engine.plugins.FlutterPlugin
5 | import io.flutter.plugin.common.MethodCall
6 | import io.flutter.plugin.common.MethodChannel
7 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler
8 | import io.flutter.plugin.common.MethodChannel.Result
9 | import io.flutter.plugin.common.PluginRegistry.Registrar
10 | import tal.com.d_stack.channel.DStackMethodHandler
11 |
12 | /**
13 | * DStackPlugin
14 | */
15 | public class DStackPlugin : FlutterPlugin, MethodCallHandler {
16 | override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
17 | val channel = MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "d_stack")
18 | channel.setMethodCallHandler(DStackMethodHandler())
19 | }
20 |
21 | // This static function is optional and equivalent to onAttachedToEngine. It supports the old
22 | // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
23 | // plugin registration via this function while apps migrate to use the new Android APIs
24 | // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
25 | //
26 | // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
27 | // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
28 | // depending on the user's project. onAttachedToEngine or registerWith must both be defined
29 | // in the same class.
30 | companion object {
31 | @JvmStatic
32 | fun registerWith(registrar: Registrar) {
33 | val channel = MethodChannel(registrar.messenger(), "d_stack")
34 | channel.setMethodCallHandler(DStackPlugin())
35 | }
36 | }
37 |
38 | override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
39 | if (call.method == "getPlatformVersion") {
40 | result.success("Android ${android.os.Build.VERSION.RELEASE}")
41 | } else {
42 | result.notImplemented()
43 | }
44 | }
45 |
46 | override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/action/DActionManager.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.action;
2 |
3 | import android.os.Handler;
4 |
5 | import java.lang.ref.WeakReference;
6 | import java.util.ArrayList;
7 | import java.util.List;
8 | import java.util.Map;
9 |
10 | import tal.com.d_stack.DStack;
11 | import tal.com.d_stack.channel.DStackMethodHandler;
12 | import tal.com.d_stack.node.DNode;
13 | import tal.com.d_stack.node.DNodeManager;
14 | import tal.com.d_stack.node.DNodeResponse;
15 | import tal.com.d_stack.node.constants.DNodeActionType;
16 | import tal.com.d_stack.node.constants.DNodePageType;
17 | import tal.com.d_stack.observer.DStackActivityManager;
18 | import tal.com.d_stack.utils.DStackUtils;
19 |
20 | /**
21 | * 页面实际跳转动作管理
22 | */
23 | public class DActionManager {
24 |
25 | /**
26 | * 打开页面
27 | */
28 | public static void push(DNode node) {
29 | enterPageWithNode(node, DNodeActionType.DNodeActionTypePush, node.isAnimated());
30 | DOperationManager.operation(node);
31 | }
32 |
33 | /**
34 | * 返回当前页面
35 | */
36 | public static void pop(DNode node) {
37 | closePageWithNode(node, DNodeActionType.DNodeActionTypePop, node.isAnimated());
38 | DOperationManager.operation(node);
39 | }
40 |
41 | /**
42 | * 返回指定页面
43 | */
44 | public static void popTo(DNode node, List removeNodes) {
45 | closePageWithNodes(removeNodes, DNodeActionType.DNodeActionTypePopTo, node.isAnimated());
46 | DOperationManager.operation(node);
47 | }
48 |
49 | /**
50 | * 返回根页面
51 | */
52 | public static void popToRoot(DNode node, List removeNodes) {
53 | closePageWithNodes(removeNodes, DNodeActionType.DNodeActionTypePopToRoot, node.isAnimated());
54 | DOperationManager.operation(node);
55 | }
56 |
57 | /**
58 | * 返回指定模块页面
59 | */
60 | public static void popSkip(DNode node, List removeNodes) {
61 | closePageWithNodes(removeNodes, DNodeActionType.DNodeActionTypePopSkip, node.isAnimated());
62 | DOperationManager.operation(node);
63 | }
64 |
65 | /**
66 | * 替换页面
67 | */
68 | public static void replace(DNode node) {
69 | DOperationManager.operation(node);
70 | }
71 |
72 | /**
73 | * 手势返回页面
74 | */
75 | public static void gesture(DNode node) {
76 | DOperationManager.operation(node);
77 | }
78 |
79 | /**
80 | * 打开页面,根据页面类型做不同处理
81 | */
82 | private static void enterPageWithNode(DNode node, String action, boolean animated) {
83 | if (node.isFromFlutter()) {
84 | // 来自flutter消息通道的node
85 | if (node.getPageType().equals(DNodePageType.DNodePageTypeNative)) {
86 | // 打开native页面
87 | // flutter打开native页面,回传给用户侧处理
88 | DStack.getInstance().getNativeRouter().openContainer(
89 | node.getTarget(),
90 | node.getParams()
91 | );
92 | } else if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) {
93 | // 打开flutter页面
94 | // 给当前flutter节点设置对应的activity
95 | DNode currentNode = DNodeManager.getInstance().getCurrentNode();
96 | currentNode.setActivity(new WeakReference(DStackActivityManager.getInstance().getTopActivity()));
97 | }
98 | } else {
99 | // 只是来自native的node,并且是需要打开Flutter页面的,发消息至flutter,打开页面
100 | if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) {
101 | if (node.isRootPage()) {
102 | //flutter根节点,不发通知给flutter
103 | return;
104 | }
105 | DNodeResponse nodeResponse = DNodeManager.getInstance().createNodeResponse(node);
106 | DStackMethodHandler.sendNode(nodeResponse, action, animated);
107 | }
108 | }
109 | }
110 |
111 | /**
112 | * 关闭页面
113 | */
114 | private static void closePageWithNode(DNode node, String action, boolean animated) {
115 | if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) {
116 | if (node.isRootPage() || node.isHomePage()) {
117 | //根节点不移除栈,去判断临界状态
118 | DNodeManager.getInstance().handleNeedRemoveFlutterNode(node);
119 | return;
120 | }
121 | //pop的是flutter页面,发消息至flutter
122 | DNodeResponse nodeResponse = DNodeManager.getInstance().createNodeResponse(node);
123 | DStackMethodHandler.sendNode(nodeResponse, action, animated);
124 | }
125 | }
126 |
127 | /**
128 | * 关闭已移除节点集合的所有页面,包括native和flutter
129 | */
130 | private static void closePageWithNodes(List nodes, final String action, final boolean animated) {
131 | final List> flutterNodes = new ArrayList<>();
132 | List nativeNodes = new ArrayList<>();
133 | int size = nodes.size();
134 | for (int i = 0; i < size; i++) {
135 | DNode loopNode = nodes.get(i);
136 | if (loopNode.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) {
137 | if (!loopNode.isHomePage()) {
138 | DNodeResponse nodeResponse = DNodeManager.getInstance().createNodeResponse(loopNode);
139 | flutterNodes.add(nodeResponse.toMap());
140 | }
141 | } else {
142 | nativeNodes.add(loopNode.getIdentifier());
143 | }
144 | }
145 | //处理需要关闭的控制器
146 | final DNode currentNode = DNodeManager.getInstance().getCurrentNode();
147 | DStackActivityManager.getInstance().closeActivityWithNode(currentNode);
148 | //发送消息给flutter侧处理
149 | //为了保证native侧页面顺利关闭,此处需要延迟一些时间给flutter发消息
150 | //不然会引起surfaceView的绘制问题
151 | new Handler().postDelayed(new Runnable() {
152 | @Override
153 | public void run() {
154 | DStackMethodHandler.sendNode(flutterNodes,
155 | action,
156 | animated);
157 | }
158 | }, 150);
159 | }
160 |
161 | /**
162 | * 替换当前页面
163 | */
164 | public static void replace(DNode node, String action, boolean animated) {
165 | if (node.isFromFlutter()) {
166 | if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) {
167 | DNodeResponse nodeResponse = DNodeManager.getInstance().createNodeResponse(node);
168 | DStackMethodHandler.sendNode(nodeResponse, action, animated);
169 | }
170 | }
171 | }
172 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/action/DOperationManager.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.action;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import tal.com.d_stack.DStack;
7 | import tal.com.d_stack.channel.DStackMethodHandler;
8 | import tal.com.d_stack.node.DNode;
9 | import tal.com.d_stack.node.DNodeManager;
10 | import tal.com.d_stack.node.DNodeResponse;
11 | import tal.com.d_stack.node.constants.DNodeActionType;
12 | import tal.com.d_stack.node.constants.DNodePageType;
13 | import tal.com.d_stack.router.INodeOperation;
14 | import tal.com.d_stack.utils.DLog;
15 |
16 | /**
17 | * 节点操作行为记录
18 | */
19 | public class DOperationManager {
20 |
21 | private static Map> popParams = new HashMap<>();
22 |
23 | public static void operation(DNode node) {
24 | if (!DStack.getInstance().isOpenNodeOperation()) {
25 | return;
26 | }
27 | if (node.getAction().equals(DNodeActionType.DNodeActionTypePush)) {
28 | //页面如果是push行为
29 | if (node.isFromFlutter()) {
30 | //如果是来自flutter的消息
31 | if (node.getPageType().equals(DNodePageType.DNodePageTypeNative)) {
32 | //如果是打开一个native页面,不记录操作,等activity的onCreate方法
33 | return;
34 | }
35 |
36 | }
37 | }
38 | if (node.getAction().equals(DNodeActionType.DNodeActionTypePop)) {
39 | //页面如果是pop行为
40 | if (!node.isBoundary()) {
41 | //不是临界页面
42 | if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) {
43 | //操作一个flutter页面,不记录操作,等didPop消息
44 | //如果返回待参数,需要保留
45 | if (node.getParams() != null && !node.getParams().isEmpty()) {
46 | popParams.put(node.getIdentifier(), node.getParams());
47 | }
48 | return;
49 | }
50 | }
51 | }
52 | DNodeResponse nodeResponse = DNodeManager.getInstance().createNodeResponse(node);
53 | DLog.logE("$$$$$节点操作$$$$$");
54 | DLog.logE(nodeResponse.action + "-----" + nodeResponse.target);
55 | DLog.logE("$$$$$节点操作$$$$$");
56 | if (node.getPageType().equals(DNodePageType.DNodePageTypeFlutter)) {
57 | if (nodeResponse.params == null || nodeResponse.params.isEmpty()) {
58 | if (popParams.containsKey(nodeResponse.identifier)) {
59 | nodeResponse.params = popParams.get(nodeResponse.identifier);
60 | popParams.clear();
61 | }
62 | }
63 | }
64 |
65 | DStackMethodHandler.sendNodeOperation(nodeResponse);
66 | INodeOperation nodeOperation = DStack.getInstance().getNodeOperation();
67 | if (nodeOperation != null) {
68 | nodeOperation.operationNode(nodeResponse);
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/lifecycle/PageLifecycleManager.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.lifecycle;
2 |
3 |
4 | import java.util.List;
5 |
6 | import tal.com.d_stack.channel.DStackMethodHandler;
7 | import tal.com.d_stack.node.DNode;
8 | import tal.com.d_stack.node.DNodeManager;
9 | import tal.com.d_stack.node.constants.DNodeActionType;
10 | import tal.com.d_stack.node.constants.DNodePageType;
11 | import tal.com.d_stack.utils.DLog;
12 |
13 | /**
14 | * 页面状态管理
15 | */
16 | public class PageLifecycleManager {
17 |
18 | /**
19 | * app启动
20 | */
21 | public static void appCreate() {
22 | DLog.logD("appCreate");
23 | PageModel pageModel = new PageModel();
24 | pageModel.setState(PageState.APP_CREATE);
25 | DNode node = DNodeManager.getInstance().getCurrentNode();
26 | if (node == null) {
27 | pageModel.setCurrentPageRoute("/");
28 | pageModel.setCurrentPageType(DNodePageType.DNodePageTypeFlutter);
29 | } else {
30 | pageModel.setCurrentPageRoute(node.getTarget());
31 | pageModel.setCurrentPageType(DNodePageType.DNodePageTypeNative);
32 | }
33 | DStackMethodHandler.sendAppLifeCircle(pageModel);
34 | }
35 |
36 | /**
37 | * app进入前台
38 | */
39 | public static void appForeground() {
40 | DLog.logD("appForeground");
41 | PageModel pageModel = new PageModel();
42 | pageModel.setState(PageState.APP_FOREGROUND);
43 | DNode node = DNodeManager.getInstance().getCurrentNode();
44 | if (node == null) {
45 | pageModel.setCurrentPageRoute("/");
46 | pageModel.setCurrentPageType(DNodePageType.DNodePageTypeFlutter);
47 | } else {
48 | pageModel.setCurrentPageRoute(node.getTarget());
49 | pageModel.setCurrentPageType(node.getPageType());
50 | }
51 | DStackMethodHandler.sendAppLifeCircle(pageModel);
52 | }
53 |
54 | /**
55 | * app进入后台
56 | */
57 | public static void appBackground() {
58 | DLog.logD("appBackground");
59 | PageModel pageModel = new PageModel();
60 | pageModel.setState(PageState.APP_BACKGROUND);
61 | DNode node = DNodeManager.getInstance().getCurrentNode();
62 | if (node == null) {
63 | pageModel.setCurrentPageRoute("/");
64 | pageModel.setCurrentPageType(DNodePageType.DNodePageTypeFlutter);
65 | } else {
66 | pageModel.setCurrentPageRoute(node.getTarget());
67 | pageModel.setCurrentPageType(node.getPageType());
68 | }
69 | DStackMethodHandler.sendAppLifeCircle(pageModel);
70 | }
71 |
72 | /**
73 | * 页面出现
74 | */
75 | public static void pageAppear(DNode node) {
76 | DLog.logD("pageAppear");
77 | if (node == null) {
78 | return;
79 | }
80 | PageModel pageModel = new PageModel();
81 | pageModel.setActionType(DNodeActionType.DNodeActionTypePush);
82 | pageModel.setCurrentPageType(node.getPageType());
83 | pageModel.setCurrentPageRoute(node.getTarget());
84 | List nodeList = DNodeManager.getInstance().getNodeList();
85 | DNode secondLastNode = null;
86 | if (nodeList.size() >= 2) {
87 | secondLastNode = nodeList.get(nodeList.size() - 2);
88 | }
89 | if (secondLastNode == null) {
90 | pageModel.setPrePageType(DNodePageType.DNodePageTypeFlutter);
91 | pageModel.setPrePageRoute("/");
92 | } else {
93 | pageModel.setPrePageType(secondLastNode.getPageType());
94 | pageModel.setPrePageRoute(secondLastNode.getTarget());
95 | }
96 |
97 | DStackMethodHandler.sendPageLifeCircle(pageModel);
98 | }
99 |
100 | /**
101 | * 页面消失
102 | */
103 | public static void pageDisappear(DNode node) {
104 | DLog.logD("pageAppear");
105 | if (node == null) {
106 | return;
107 | }
108 | PageModel pageModel = new PageModel();
109 | pageModel.setActionType(DNodeActionType.DNodeActionTypePop);
110 | pageModel.setPrePageType(node.getPageType());
111 | pageModel.setPrePageRoute(node.getTarget());
112 | List nodeList = DNodeManager.getInstance().getNodeList();
113 | DNode currentNode = null;
114 | if (nodeList.size() > 0) {
115 | currentNode = nodeList.get(nodeList.size() - 1);
116 | }
117 | if (currentNode == null) {
118 | pageModel.setCurrentPageType(DNodePageType.DNodePageTypeFlutter);
119 | pageModel.setCurrentPageRoute("/");
120 | } else {
121 | pageModel.setCurrentPageType(currentNode.getPageType());
122 | pageModel.setCurrentPageRoute(currentNode.getTarget());
123 | }
124 | DStackMethodHandler.sendPageLifeCircle(pageModel);
125 | }
126 |
127 | /**
128 | * 页面出现处理节点替换情况
129 | */
130 | public static void pageAppearWithReplace(DNode preNode, DNode currentNode) {
131 | DLog.logE("pageAppearWithReplace");
132 | if (preNode == null || currentNode == null) {
133 | return;
134 | }
135 | PageModel pageModel = new PageModel();
136 | pageModel.setActionType(DNodeActionType.DNodeActionTypePush);
137 | pageModel.setCurrentPageType(currentNode.getPageType());
138 | pageModel.setCurrentPageRoute(currentNode.getTarget());
139 | pageModel.setPrePageType(preNode.getPageType());
140 | pageModel.setPrePageRoute(preNode.getTarget());
141 | DStackMethodHandler.sendPageLifeCircle(pageModel);
142 | }
143 |
144 | }
145 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/lifecycle/PageModel.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.lifecycle;
2 |
3 |
4 | /**
5 | * 页面状态信息
6 | */
7 | public class PageModel {
8 |
9 | // 当前展示的页面路由
10 | private String currentPageRoute;
11 | // 前一个展示的页面路由,可能为null
12 | private String prePageRoute;
13 | // 页面类型:Flutter/Native
14 | private String currentPageType;
15 | // 页面类型:Flutter/Native
16 | private String prePageType;
17 | // 操作类型:push/pop
18 | private String actionType;
19 | // 应用状态 0:应用启动 1:前台 2:后台 3:应用杀死
20 | private int state = 0;
21 |
22 | public String getCurrentPageRoute() {
23 | return currentPageRoute;
24 | }
25 |
26 | public void setCurrentPageRoute(String currentPageRoute) {
27 | this.currentPageRoute = currentPageRoute;
28 | }
29 |
30 | public String getPrePageRoute() {
31 | return prePageRoute;
32 | }
33 |
34 | public void setPrePageRoute(String prePageRoute) {
35 | this.prePageRoute = prePageRoute;
36 | }
37 |
38 | public String getCurrentPageType() {
39 | return currentPageType;
40 | }
41 |
42 | public void setCurrentPageType(String currentPageType) {
43 | this.currentPageType = currentPageType;
44 | }
45 |
46 | public String getPrePageType() {
47 | return prePageType;
48 | }
49 |
50 | public void setPrePageType(String prePageType) {
51 | this.prePageType = prePageType;
52 | }
53 |
54 | public String getActionType() {
55 | return actionType;
56 | }
57 |
58 | public void setActionType(String actionType) {
59 | this.actionType = actionType;
60 | }
61 |
62 | public int getState() {
63 | return state;
64 | }
65 |
66 | public void setState(int state) {
67 | this.state = state;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/lifecycle/PageState.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.lifecycle;
2 |
3 | /**
4 | * 页面状态常量
5 | */
6 | public class PageState {
7 | public static int APP_CREATE = 0;
8 | public static int APP_FOREGROUND = 1;
9 | public static int APP_BACKGROUND = 2;
10 | public static int APP_STOP = 3;
11 | }
12 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/node/DNode.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.node;
2 |
3 | import android.app.Activity;
4 |
5 | import java.lang.ref.WeakReference;
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | /**
10 | * 页面节点信息
11 | */
12 | public class DNode {
13 |
14 | // 页面跳转类型
15 | private String action;
16 |
17 | // 页面类型
18 | private String pageType;
19 |
20 | // 页面唯一标识
21 | // flutter页面时,是route,native唯一id
22 | private String target;
23 |
24 | // 附带参数
25 | private Map params;
26 |
27 | // 是否来自flutter消息通道的node
28 | private boolean fromFlutter;
29 |
30 | // 节点对应的activity
31 | private WeakReference activity;
32 |
33 | // 是否正在执行popTo,popToRoot,popToSkip方法
34 | private boolean popTo;
35 |
36 | // 是否是flutter主页面
37 | private boolean homePage;
38 |
39 | //是否是根页面
40 | private boolean rootPage;
41 |
42 | //flutter临界节点,native打开flutter使用
43 | private boolean boundary;
44 |
45 | //是否开启转场动画
46 | private boolean animated;
47 |
48 | //页面唯一标识
49 | private String identifier;
50 |
51 | public DNode(Builder builder) {
52 | this.action = builder.action;
53 | this.pageType = builder.pageType;
54 | this.target = builder.target;
55 | this.params = builder.params;
56 | this.homePage = builder.homePage;
57 | this.boundary = builder.boundary;
58 | this.animated = builder.animated;
59 | this.fromFlutter = builder.fromFlutter;
60 | this.activity = builder.activity;
61 | this.popTo = builder.popTo;
62 | this.rootPage = builder.rootPage;
63 | this.identifier = builder.identifier;
64 | }
65 |
66 | public String getIdentifier() {
67 | return identifier;
68 | }
69 |
70 | public void setIdentifier(String identifier) {
71 | this.identifier = identifier;
72 | }
73 |
74 |
75 | public static class Builder {
76 | private String action = "";
77 | private String pageType = "";
78 | private String target = "";
79 | private Map params = new HashMap<>();
80 | private boolean homePage = false;
81 | private boolean boundary = false;
82 | private boolean animated = false;
83 | private boolean fromFlutter = false;
84 | private WeakReference activity = null;
85 | private boolean popTo = false;
86 | private boolean rootPage = false;
87 | private String identifier = "";
88 |
89 | public Builder action(String action) {
90 | this.action = action;
91 | return this;
92 | }
93 |
94 | public Builder pageType(String pageType) {
95 | this.pageType = pageType;
96 | return this;
97 | }
98 |
99 | public Builder target(String target) {
100 | this.target = target;
101 | return this;
102 | }
103 |
104 | public Builder params(Map params) {
105 | this.params = params;
106 | return this;
107 | }
108 |
109 | public Builder isHomePage(boolean isHomePage) {
110 | this.homePage = isHomePage;
111 | return this;
112 | }
113 |
114 | public Builder boundary(boolean boundary) {
115 | this.boundary = boundary;
116 | return this;
117 | }
118 |
119 | public Builder animated(boolean animated) {
120 | this.animated = animated;
121 | return this;
122 | }
123 |
124 | public Builder fromFlutter(boolean fromFlutter) {
125 | this.fromFlutter = fromFlutter;
126 | return this;
127 | }
128 |
129 | public Builder activity(WeakReference activity) {
130 | this.activity = activity;
131 | return this;
132 | }
133 |
134 | public Builder isPopTo(boolean isPopTo) {
135 | this.popTo = isPopTo;
136 | return this;
137 | }
138 |
139 | public Builder isRootPage(boolean isRootPage) {
140 | this.rootPage = isRootPage;
141 | return this;
142 | }
143 |
144 | public Builder identifier(String identifier) {
145 | this.identifier = identifier;
146 | return this;
147 | }
148 |
149 | public DNode build() {
150 | return new DNode(this);
151 | }
152 | }
153 |
154 |
155 | public String getTarget() {
156 | return target;
157 | }
158 |
159 | public void setTarget(String target) {
160 | this.target = target;
161 | }
162 |
163 | public boolean isFromFlutter() {
164 | return fromFlutter;
165 | }
166 |
167 | public void setFromFlutter(boolean fromFlutter) {
168 | this.fromFlutter = fromFlutter;
169 | }
170 |
171 | public Map getParams() {
172 | return params;
173 | }
174 |
175 | public void setParams(Map params) {
176 | this.params = params;
177 | }
178 |
179 | public String getAction() {
180 | return action;
181 | }
182 |
183 | public void setAction(String action) {
184 | this.action = action;
185 | }
186 |
187 | public String getPageType() {
188 | return pageType;
189 | }
190 |
191 | public void setPageType(String pageType) {
192 | this.pageType = pageType;
193 | }
194 |
195 | public WeakReference getActivity() {
196 | return activity;
197 | }
198 |
199 | public void setActivity(WeakReference activity) {
200 | this.activity = activity;
201 | }
202 |
203 | public boolean isPopTo() {
204 | return popTo;
205 | }
206 |
207 | public void setPopTo(boolean popTo) {
208 | this.popTo = popTo;
209 | }
210 |
211 | public boolean isHomePage() {
212 | return homePage;
213 | }
214 |
215 | public void setHomePage(boolean homePage) {
216 | this.homePage = homePage;
217 | }
218 |
219 | public boolean isRootPage() {
220 | return rootPage;
221 | }
222 |
223 | public void setRootPage(boolean rootPage) {
224 | this.rootPage = rootPage;
225 | }
226 |
227 | public boolean isBoundary() {
228 | return boundary;
229 | }
230 |
231 | public void setBoundary(boolean boundary) {
232 | this.boundary = boundary;
233 | }
234 |
235 | public boolean isAnimated() {
236 | return animated;
237 | }
238 |
239 | public void setAnimated(boolean animated) {
240 | this.animated = animated;
241 | }
242 |
243 | @Override
244 | public String toString() {
245 | return "DNode{" +
246 | "action='" + action + '\'' +
247 | ", pageType='" + pageType + '\'' +
248 | ", target='" + target + '\'' +
249 | ", params=" + params + '\'' +
250 | ", fromFlutter=" + fromFlutter + '\'' +
251 | ", activity=" + activity + '\'' +
252 | ", popTo=" + popTo + '\'' +
253 | ", homePage=" + homePage + '\'' +
254 | ", rootPage=" + rootPage + '\'' +
255 | ", boundary=" + boundary + '\'' +
256 | ", animated=" + animated + '\'' +
257 | ", identifier='" + identifier + '\'' +
258 | '}';
259 | }
260 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/node/DNodeResponse.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.node;
2 |
3 | import org.json.JSONException;
4 | import org.json.JSONObject;
5 |
6 | import java.util.HashMap;
7 | import java.util.Map;
8 |
9 | public class DNodeResponse {
10 | public String target = "";
11 | public String pageType = "";
12 | public String action = "";
13 | public Map params;
14 | public boolean homePage = false;
15 | public boolean animated = false;
16 | public boolean boundary = false;
17 | public String identifier = "";
18 |
19 | @Override
20 | public String toString() {
21 | JSONObject jo = new JSONObject();
22 | try {
23 | jo.put("target", target);
24 | jo.put("pageType", pageType);
25 | jo.put("action", action);
26 | jo.put("params", params);
27 | jo.put("homePage", homePage);
28 | jo.put("animated", animated);
29 | jo.put("boundary", boundary);
30 | jo.put("identifier", identifier);
31 | } catch (JSONException e) {
32 | e.printStackTrace();
33 | }
34 | return jo.toString();
35 | }
36 |
37 | public Map toMap() {
38 | Map map = new HashMap<>();
39 | map.put("target", target);
40 | map.put("pageType", pageType);
41 | map.put("action", action);
42 | map.put("params", params);
43 | map.put("homePage", homePage);
44 | map.put("animated", animated);
45 | map.put("boundary", boundary);
46 | map.put("identifier", identifier);
47 | return map;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/node/constants/DNodeActionType.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.node.constants;
2 |
3 | // 跳转类型
4 | public class DNodeActionType {
5 |
6 | // push跳转android
7 | public static final String DNodeActionTypePush = "push";
8 |
9 | // present跳转(ios独有)
10 | public static final String DNodeActionTypePresent = "present";
11 |
12 | // pop返回android
13 | public static final String DNodeActionTypePop = "pop";
14 |
15 | // popTo 返回
16 | public static final String DNodeActionTypePopTo = "popTo";
17 |
18 | // PopToRoot
19 | public static final String DNodeActionTypePopToRoot = "popToRoot";
20 |
21 | // PopSkip
22 | public static final String DNodeActionTypePopSkip = "popSkip";
23 |
24 | // 手势
25 | public static final String DNodeActionTypeGesture = "gesture";
26 |
27 | // Dissmiss返回(ios独有)
28 | public static final String DNodeActionTypeDissmiss = "dissmiss";
29 |
30 | //replace
31 | public static final String DNodeActionTypeReplace = "replace";
32 |
33 | //pushAndRemoveUntil
34 | public static final String DNodeActionPushAndRemoveUntil = "pushAndRemoveUntil";
35 | }
36 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/node/constants/DNodePageType.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.node.constants;
2 |
3 | // 页面类型
4 | public class DNodePageType {
5 |
6 | // 原生页面
7 | public static final String DNodePageTypeNative = "native";
8 |
9 | // Flutter页面
10 | public static final String DNodePageTypeFlutter = "flutter";
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/observer/DStackActivityManager.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.observer;
2 |
3 | import android.app.Activity;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.concurrent.atomic.AtomicReference;
8 |
9 | import io.flutter.embedding.android.DFlutterActivity;
10 | import io.flutter.embedding.android.FlutterActivity;
11 | import io.flutter.embedding.android.FlutterFragmentActivity;
12 | import tal.com.d_stack.node.DNode;
13 | import tal.com.d_stack.utils.DLog;
14 |
15 | /**
16 | * activity的栈管理
17 | */
18 | public class DStackActivityManager {
19 |
20 | private final static AtomicReference INSTANCE = new AtomicReference<>();
21 |
22 | public static DStackActivityManager getInstance() {
23 | for (; ; ) {
24 | DStackActivityManager factory = INSTANCE.get();
25 | if (factory != null) {
26 | return factory;
27 | }
28 | factory = new DStackActivityManager();
29 | if (INSTANCE.compareAndSet(null, factory)) {
30 | return factory;
31 | }
32 | }
33 | }
34 |
35 | //activity栈集合
36 | private List activities;
37 | //栈顶activity
38 | private Activity topActivity;
39 | //栈底activity
40 | private Activity bottomActivity;
41 | //需要移除出栈的activity集合
42 | private List needRemoveActivities;
43 | //正在执行activity栈清除操作
44 | private boolean executeStack;
45 | //是否需要重新attach引擎
46 | private boolean needReAttachEngine = false;
47 |
48 | private DStackActivityManager() {
49 | activities = new ArrayList<>();
50 | needRemoveActivities = new ArrayList<>();
51 | }
52 |
53 | /**
54 | * 栈增加activity
55 | */
56 | public void addActivity(Activity activity) {
57 | if (activity == null) {
58 | return;
59 | }
60 | activities.add(activity);
61 | setBottomAndTopActivity();
62 | }
63 |
64 | /**
65 | * 栈移除activity
66 | */
67 | public void removeActivity(Activity activity) {
68 | if (activity == null) {
69 | return;
70 | }
71 | activities.remove(activity);
72 | handleReAttachEngine(activity);
73 | handleNeedRemoveActivities(activity);
74 | setBottomAndTopActivity();
75 | }
76 |
77 | /**
78 | * 设置栈顶和栈底Activity
79 | */
80 | public void setBottomAndTopActivity() {
81 | if (activities == null || activities.size() == 0) {
82 | topActivity = null;
83 | bottomActivity = null;
84 | } else {
85 | topActivity = activities.get(activities.size() - 1);
86 | bottomActivity = activities.get(0);
87 | }
88 | }
89 |
90 | /**
91 | * 获取栈顶Activity
92 | */
93 | public Activity getTopActivity() {
94 | return topActivity;
95 | }
96 |
97 |
98 | /**
99 | * 判断栈顶的activity是否和要打开的activity是同一个activity
100 | */
101 | public boolean isSameActivity(Class> needOpenActivity) {
102 | if (topActivity != null && needOpenActivity != null) {
103 | String topActivityName = topActivity.getClass().getSimpleName();
104 | String needOpenActivityName = needOpenActivity.getSimpleName();
105 | return topActivityName.trim().equals(needOpenActivityName.trim());
106 | }
107 | return false;
108 | }
109 |
110 | /**
111 | * 关闭栈顶activity
112 | */
113 | public void closeTopActivity() {
114 | if (topActivity == null) {
115 | return;
116 | }
117 | topActivity.finish();
118 | }
119 |
120 | /**
121 | * 关闭栈顶的flutter控制器activity
122 | */
123 | public void closeTopFlutterActivity() {
124 | if (topActivity == null) {
125 | return;
126 | }
127 | if (topActivity instanceof FlutterActivity ||
128 | topActivity.getParent() instanceof FlutterActivity) {
129 | topActivity.finish();
130 | return;
131 | }
132 | if (topActivity instanceof DFlutterActivity ||
133 | topActivity.getParent() instanceof DFlutterActivity) {
134 | topActivity.finish();
135 | return;
136 | }
137 | if (topActivity instanceof FlutterFragmentActivity ||
138 | topActivity.getParent() instanceof FlutterFragmentActivity) {
139 | topActivity.finish();
140 | }
141 | }
142 |
143 | /**
144 | * 把该节点对应activity之上的所有activity关闭
145 | */
146 | public void closeActivityWithNode(DNode node) {
147 | if (node == null || node.getActivity() == null) {
148 | return;
149 | }
150 | boolean find = false;
151 | needRemoveActivities.clear();
152 | Activity activity = node.getActivity().get();
153 | for (int i = activities.size() - 1; i >= 0; i--) {
154 | Activity tempActivity = activities.get(i);
155 | if (tempActivity != activity) {
156 | needRemoveActivities.add(tempActivity);
157 | } else {
158 | find = true;
159 | break;
160 | }
161 | }
162 | if (find) {
163 | if (needRemoveActivities.size() == 0) {
164 | return;
165 | }
166 | executeStack = true;
167 | needRemoveActivities.get(0).finish();
168 | } else {
169 | needRemoveActivities.clear();
170 | }
171 | }
172 |
173 | /**
174 | * 每次关闭activity后,看看待移除列表是否还有activity,继续执行关闭操作
175 | */
176 | private void handleNeedRemoveActivities(Activity activity) {
177 | if (activity == null || needRemoveActivities.size() == 0) {
178 | return;
179 | }
180 | DLog.logE("被关闭的Activity是:" + activity.getClass().getName());
181 | needRemoveActivities.remove(activity);
182 | if (needRemoveActivities.size() == 0) {
183 | //activity栈的处理完成
184 | executeStack = false;
185 | return;
186 | }
187 | //继续取集合第一个activity进行关闭
188 | needRemoveActivities.get(0).finish();
189 | }
190 |
191 | /**
192 | * activity栈正在执行操作,这个方法被调用说明正在执行popTo,popToRoot,popToSkip方法
193 | * activity正在依次顺序关闭,也不需要在执行其他关闭activity的操作
194 | */
195 | public boolean isExecuteStack() {
196 | return executeStack;
197 | }
198 |
199 | /**
200 | * 获取栈内Activity数量
201 | */
202 | public int getActivitiesSize() {
203 | if (activities == null) {
204 | return 0;
205 | }
206 | return activities.size();
207 | }
208 | /**
209 | * 判断当前工程是否是一个纯Flutter工程
210 | */
211 | public boolean isFlutterApp() {
212 | if (bottomActivity == null) {
213 | return true;
214 | }
215 | if (bottomActivity instanceof FlutterActivity ||
216 | bottomActivity.getParent() instanceof FlutterActivity) {
217 | return true;
218 | }
219 | if (bottomActivity instanceof DFlutterActivity ||
220 | bottomActivity.getParent() instanceof DFlutterActivity) {
221 | return true;
222 | }
223 | if (bottomActivity instanceof FlutterFragmentActivity ||
224 | bottomActivity.getParent() instanceof FlutterFragmentActivity) {
225 | return true;
226 | }
227 | return false;
228 | }
229 |
230 | /**
231 | * 判断是否是FlutterActivity
232 | */
233 | public boolean isFlutterActivity(Activity activity) {
234 | if (activity instanceof FlutterActivity) {
235 | return true;
236 | }
237 | if (activity instanceof DFlutterActivity) {
238 | return true;
239 | }
240 | return false;
241 | }
242 |
243 | /**
244 | * 关闭过一个flutterActivity,并且栈里还有flutterActivity,需要重新attach引擎
245 | */
246 | public void handleReAttachEngine(Activity activity) {
247 | if (
248 | activity instanceof FlutterActivity ||
249 | activity instanceof DFlutterActivity ||
250 | activity instanceof FlutterFragmentActivity) {
251 | for (Activity tempActivity : activities) {
252 | if (tempActivity instanceof FlutterActivity ||
253 | tempActivity instanceof DFlutterActivity ||
254 | tempActivity instanceof FlutterFragmentActivity) {
255 | needReAttachEngine = true;
256 | break;
257 | }
258 | }
259 | }
260 | }
261 |
262 | /**
263 | * 设置是否需要重新attach引擎
264 | */
265 | public void setNeedReAttachEngine(boolean needReAttachEngine) {
266 | this.needReAttachEngine = needReAttachEngine;
267 | }
268 |
269 | /**
270 | * 是否需要重新attach引擎
271 | */
272 | public boolean isNeedReAttachEngine() {
273 | return needReAttachEngine;
274 | }
275 |
276 | /**
277 | * 栈里是否有flutter控制器
278 | */
279 | public boolean haveFlutterContainer() {
280 | for (Activity activity : activities) {
281 | if (isFlutterActivity(activity)) {
282 | return true;
283 | }
284 | }
285 | return false;
286 | }
287 |
288 | }
289 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/observer/DStackLifecycleObserver.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.observer;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.os.Bundle;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 |
10 | import java.lang.ref.WeakReference;
11 |
12 | import io.flutter.embedding.android.FlutterView;
13 | import tal.com.d_stack.lifecycle.PageLifecycleManager;
14 | import tal.com.d_stack.node.DNode;
15 | import tal.com.d_stack.node.DNodeManager;
16 | import tal.com.d_stack.node.constants.DNodeActionType;
17 | import tal.com.d_stack.node.constants.DNodePageType;
18 | import tal.com.d_stack.utils.DLog;
19 | import tal.com.d_stack.utils.DStackUtils;
20 |
21 | /**
22 | * 监控App生命周期,进行Activity栈管理
23 | */
24 | public class DStackLifecycleObserver implements Application.ActivityLifecycleCallbacks {
25 |
26 | private int appCount = 0;
27 | //app是否在前台
28 | private boolean isFrontApp = true;
29 | //当前活动activity
30 | private Activity activeActivity;
31 | //是否app启动
32 | boolean appStart;
33 |
34 | @Override
35 | public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {
36 | if (!FilterActivityManager.getInstance().canAdd(activity)) {
37 | return;
38 | }
39 | DStackActivityManager.getInstance().addActivity(activity);
40 | activeActivity = activity;
41 | appStart = DStackActivityManager.getInstance().getActivitiesSize() == 1;
42 | if (appStart) {
43 | DNode node;
44 | //应用刚刚启动时
45 | if (DStackActivityManager.getInstance().isFlutterActivity(activity)) {
46 | //是flutter工程,添加根节点
47 | node = new DNode.Builder()
48 | .target("/")
49 | .pageType(DNodePageType.DNodePageTypeFlutter)
50 | .action(DNodeActionType.DNodeActionTypePush)
51 | .identifier(DStackUtils.generateUniqueId())
52 | .isHomePage(true)
53 | .isRootPage(true)
54 | .build();
55 | } else {
56 | //是native工程,添加根节点
57 | node = new DNode.Builder()
58 | .target("/")
59 | .pageType(DNodePageType.DNodePageTypeNative)
60 | .action(DNodeActionType.DNodeActionTypePush)
61 | .identifier(DStackUtils.generateUniqueId())
62 | .isHomePage(true)
63 | .isRootPage(true)
64 | .build();
65 | }
66 | DNodeManager.getInstance().checkNode(node);
67 | } else {
68 | //应用已经启动,打开新的activity
69 | if (!DStackActivityManager.getInstance().isFlutterActivity(activity)) {
70 | //是native工程,添加普通节点
71 | DNode node = new DNode.Builder()
72 | .target(activity.getClass().getName())
73 | .pageType(DNodePageType.DNodePageTypeNative)
74 | .action(DNodeActionType.DNodeActionTypePush)
75 | .identifier(DStackUtils.generateUniqueId())
76 | .build();
77 | DNodeManager.getInstance().checkNode(node);
78 | }
79 | }
80 | DNodeManager.getInstance().getCurrentNode().setActivity(new WeakReference(activity));
81 | if (appStart) {
82 | //app启动通知
83 | PageLifecycleManager.appCreate();
84 | }
85 | }
86 |
87 | @Override
88 | public void onActivityStarted(@NonNull Activity activity) {
89 | if (!FilterActivityManager.getInstance().canAdd(activity)) {
90 | return;
91 | }
92 | appCount++;
93 | if (!isFrontApp) {
94 | isFrontApp = true;
95 | PageLifecycleManager.appForeground();
96 | }
97 | }
98 |
99 | @Override
100 | public void onActivityResumed(@NonNull Activity activity) {
101 | if (!FilterActivityManager.getInstance().canAdd(activity)) {
102 | return;
103 | }
104 | if (activeActivity != activity) {
105 | //正在执行恢复activity的逻辑,页面返回操作,onCreate,onResumed不是同一个activity
106 | activeActivity = activity;
107 | if (DStackActivityManager.getInstance().isNeedReAttachEngine()) {
108 | //判断是否需要重新attach flutter引擎,1.17以上bug,解决软键盘不能弹出问题
109 | DLog.logE("需要needReAttachEngine");
110 | FlutterView flutterView = DStackUtils.getFlutterView(activity);
111 | DStackUtils.resetAttachEngine(flutterView);
112 | DStackActivityManager.getInstance().setNeedReAttachEngine(false);
113 | }
114 | }
115 | }
116 |
117 |
118 | @Override
119 | public void onActivityPaused(@NonNull Activity activity) {
120 | if (!FilterActivityManager.getInstance().canAdd(activity)) {
121 | return;
122 | }
123 | }
124 |
125 | @Override
126 | public void onActivityStopped(@NonNull Activity activity) {
127 | if (!FilterActivityManager.getInstance().canAdd(activity)) {
128 | return;
129 | }
130 | appCount--;
131 | if (!isFrontApp()) {
132 | isFrontApp = false;
133 | PageLifecycleManager.appBackground();
134 | }
135 | }
136 |
137 | @Override
138 | public void onActivityDestroyed(@NonNull Activity activity) {
139 | if (!FilterActivityManager.getInstance().canAdd(activity)) {
140 | return;
141 | }
142 | boolean isPopTo = DStackActivityManager.getInstance().isExecuteStack();
143 | DStackActivityManager.getInstance().removeActivity(activity);
144 | DNode currentNode = DNodeManager.getInstance().getCurrentNode();
145 | DNode node = new DNode.Builder()
146 | .target(currentNode.getTarget())
147 | .pageType(currentNode.getPageType())
148 | .action(DNodeActionType.DNodeActionTypePop)
149 | .isHomePage(currentNode.isHomePage())
150 | .isRootPage(currentNode.isRootPage())
151 | .identifier(currentNode.getIdentifier())
152 | .isPopTo(isPopTo)
153 | .build();
154 | DNodeManager.getInstance().checkNode(node);
155 | }
156 |
157 | /**
158 | * 判断App是否在前台
159 | */
160 | private boolean isFrontApp() {
161 | return appCount > 0;
162 | }
163 |
164 | @Override
165 | public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {
166 |
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/observer/FilterActivityManager.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.observer;
2 |
3 | import android.app.Activity;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.concurrent.atomic.AtomicReference;
8 |
9 | /**
10 | * 需要过滤的第三方activity管理
11 | */
12 | public class FilterActivityManager {
13 |
14 | private final static AtomicReference INSTANCE = new AtomicReference<>();
15 |
16 | public static FilterActivityManager getInstance() {
17 | for (; ; ) {
18 | FilterActivityManager factory = INSTANCE.get();
19 | if (factory != null) {
20 | return factory;
21 | }
22 | factory = new FilterActivityManager();
23 | if (INSTANCE.compareAndSet(null, factory)) {
24 | return factory;
25 | }
26 | }
27 | }
28 |
29 | private FilterActivityManager() {
30 | filterActivities.add("rom.huawei");
31 | filterActivities.add("rom.oppo");
32 | filterActivities.add("com.tencent");
33 | filterActivities.add("com.sina");
34 | filterActivities.add("com.tal.d_stack_spy");
35 | filterActivities.add("com.yorhp");
36 | }
37 |
38 | private List filterActivities = new ArrayList<>();
39 |
40 | private List androidStack = new ArrayList<>();
41 |
42 | /**
43 | * 判断是否可以添加到混合栈
44 | * @param activity
45 | * @return
46 | */
47 | public boolean canAdd(Activity activity) {
48 | if (activity == null) {
49 | return false;
50 | }
51 | String fullName = activity.getClass().getName();
52 | for (String filterName : filterActivities) {
53 | if (fullName.contains(filterName)) {
54 | return false;
55 | }
56 | }
57 | return true;
58 | }
59 |
60 | /**
61 | * 添加过滤器
62 | * 某些功能性Activity,不需要做节点管理的,添加至过滤
63 | *
64 | * @param filterString 过滤字符串
65 | * @return
66 | */
67 | public boolean addFilter(String filterString) {
68 | return filterActivities.add(filterString);
69 | }
70 |
71 | /**
72 | * 移除已添加的过滤器
73 | *
74 | * @param filterString
75 | * @return
76 | */
77 | public boolean removeFilter(String filterString) {
78 | return filterActivities.remove(filterString);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/router/INativeRouter.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.router;
2 |
3 | import java.util.Map;
4 |
5 | /**
6 | * 路由信息接口
7 | */
8 | public interface INativeRouter {
9 |
10 | void openContainer(String routerUrl, Map params);
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/router/INodeOperation.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.router;
2 |
3 | import tal.com.d_stack.node.DNodeResponse;
4 |
5 | /**
6 | * 节点操作接口
7 | */
8 | public interface INodeOperation {
9 |
10 | void operationNode(DNodeResponse node);
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/utils/DLog.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.utils;
2 |
3 | import android.util.Log;
4 |
5 | import tal.com.d_stack.BuildConfig;
6 |
7 | /**
8 | * 框架日志打印
9 | */
10 | public class DLog {
11 |
12 | public static String TAG = "DStack";
13 |
14 | public static void logE(String log) {
15 | if (BuildConfig.DEBUG) {
16 | Log.e(TAG, log);
17 | }
18 | }
19 |
20 | public static void logD(String log) {
21 | if (BuildConfig.DEBUG) {
22 | Log.d(TAG, log);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/tal/com/d_stack/utils/DStackUtils.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack.utils;
2 |
3 | import android.app.Activity;
4 |
5 | import java.lang.reflect.Field;
6 |
7 | import io.flutter.embedding.android.DFlutterActivity;
8 | import io.flutter.embedding.android.FlutterActivity;
9 | import io.flutter.embedding.android.FlutterFragmentActivity;
10 | import io.flutter.embedding.android.FlutterView;
11 | import io.flutter.embedding.engine.FlutterEngine;
12 | import io.flutter.embedding.engine.FlutterEngineCache;
13 | import tal.com.d_stack.DStack;
14 |
15 | /**
16 | * 框架常用工具类
17 | * 包括获取flutterView示例和重新attach引擎功能
18 | */
19 | public class DStackUtils {
20 |
21 | /**
22 | * 通过传入的FlutterActivity获取对应的FlutterView
23 | */
24 | public static FlutterView getFlutterView(Activity activity) {
25 | if (activity == null) {
26 | return null;
27 | }
28 | FlutterView flutterView = null;
29 | Class c = activity.getClass();
30 | try {
31 | // 处理FlutterActivity
32 | if (activity instanceof FlutterActivity) {
33 | while (c != FlutterActivity.class) {
34 | c = c.getSuperclass();
35 | }
36 | Field fieldDelegate = c.getDeclaredField("delegate");
37 | fieldDelegate.setAccessible(true);
38 | Object objectDelegate = fieldDelegate.get(activity);
39 | Field flutterViewDelegate = objectDelegate.getClass().getDeclaredField("flutterView");
40 | flutterViewDelegate.setAccessible(true);
41 | flutterView = (FlutterView) flutterViewDelegate.get(objectDelegate);
42 | } else if (activity instanceof DFlutterActivity) {
43 | while (c != DFlutterActivity.class) {
44 | c = c.getSuperclass();
45 | }
46 | Field fieldDelegate = c.getDeclaredField("delegate");
47 | fieldDelegate.setAccessible(true);
48 | Object objectDelegate = fieldDelegate.get(activity);
49 | Field flutterViewDelegate = objectDelegate.getClass().getDeclaredField("flutterView");
50 | flutterViewDelegate.setAccessible(true);
51 | flutterView = (FlutterView) flutterViewDelegate.get(objectDelegate);
52 | }
53 | // 处理FlutterFragmentActivity
54 | else if (activity instanceof FlutterFragmentActivity) {
55 | while (c != FlutterFragmentActivity.class) {
56 | c = c.getSuperclass();
57 | }
58 | Field fieldFragment = c.getDeclaredField("flutterFragment");
59 | fieldFragment.setAccessible(true);
60 | Object objectFragment = fieldFragment.get(activity);
61 | Field fieldDelegate = objectFragment.getClass().getDeclaredField("delegate");
62 | fieldDelegate.setAccessible(true);
63 | Object objectDelegate = fieldDelegate.get(objectFragment);
64 | Field flutterViewDelegate = objectDelegate.getClass().getDeclaredField("flutterView");
65 | flutterViewDelegate.setAccessible(true);
66 | flutterView = (FlutterView) flutterViewDelegate.get(objectDelegate);
67 | }
68 | } catch (Exception e) {
69 | DLog.logE(e.getMessage());
70 | } finally {
71 | return flutterView;
72 | }
73 | }
74 |
75 | /**
76 | * 重新绑定当前flutterView对应的flutter引擎
77 | */
78 | public static void resetAttachEngine(FlutterView flutterView) {
79 | if (flutterView == null) {
80 | return;
81 | }
82 | FlutterEngine flutterEngine = FlutterEngineCache.getInstance().get(DStack.ENGINE_ID);
83 | if (flutterEngine == null) {
84 | return;
85 | }
86 | flutterView.detachFromFlutterEngine();
87 | flutterView.attachToFlutterEngine(flutterEngine);
88 | }
89 |
90 | /**
91 | * 获取唯一id
92 | */
93 | public static String generateUniqueId() {
94 | double d = Math.random();
95 | return (int) (d * 100000) + "";
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | /build/
32 |
33 | # Web related
34 | lib/generated_plugin_registrant.dart
35 |
36 | # Exceptions to above rules.
37 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
38 |
--------------------------------------------------------------------------------
/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: 27321ebbad34b0a3fafe99fac037102196d655ff
8 | channel: stable
9 |
10 | project_type: app
11 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # d_stack_example
2 |
3 | Demonstrates how to use the d_stack plugin.
4 |
5 | ## Getting Started
6 |
7 | This project is a starting point for a Flutter application.
8 |
9 | A few resources to get you started if this is your first Flutter project:
10 |
11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
13 |
14 | For help getting started with Flutter, view our
15 | [online documentation](https://flutter.dev/docs), which offers tutorials,
16 | samples, guidance on mobile development, and a full API reference.
17 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
--------------------------------------------------------------------------------
/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 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion 29
30 |
31 | sourceSets {
32 | main.java.srcDirs += 'src/main/kotlin'
33 | }
34 |
35 | lintOptions {
36 | disable 'InvalidPackage'
37 | }
38 |
39 | defaultConfig {
40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
41 | applicationId "tal.com.d_stack_example"
42 | minSdkVersion 21
43 | targetSdkVersion 29
44 | versionCode flutterVersionCode.toInteger()
45 | versionName flutterVersionName
46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
47 | }
48 |
49 | buildTypes {
50 | release {
51 | // TODO: Add your own signing config for the release build.
52 | // Signing with the debug keys for now, so `flutter run --release` works.
53 | signingConfig signingConfigs.debug
54 | }
55 | }
56 | }
57 |
58 | flutter {
59 | source '../..'
60 | }
61 |
62 | dependencies {
63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
64 | implementation 'androidx.appcompat:appcompat:1.2.0-alpha03'
65 | }
66 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
8 |
14 |
15 |
18 |
19 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
34 |
35 |
36 |
37 |
40 |
41 |
42 |
43 |
46 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/tal/com/d_stack_example/DStackApplication.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack_example;
2 |
3 | import android.content.Intent;
4 |
5 | import io.flutter.app.FlutterApplication;
6 | import tal.com.d_stack.DStack;
7 | import tal.com.d_stack.utils.DLog;
8 |
9 | public class DStackApplication extends FlutterApplication {
10 |
11 | DStackApplication application;
12 |
13 | @Override
14 | public void onCreate() {
15 | super.onCreate();
16 | application = this;
17 | DStack.getInstance().init(this, (routerUrl, params) -> {
18 | if (routerUrl.equals("NativePage")) {
19 | Intent intent = new Intent();
20 | intent.setClass(application, NativeThreeActivity.class);
21 | intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
22 | application.startActivity(intent);
23 | }
24 | });
25 | DStack.getInstance().setOpenNodeOperation(true);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/tal/com/d_stack_example/FlutterContainerActivity.kt:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack_example
2 |
3 | import android.content.Context
4 | import io.flutter.embedding.android.DFlutterActivity
5 | import io.flutter.embedding.engine.FlutterEngine
6 | import io.flutter.embedding.engine.FlutterEngineCache
7 | import tal.com.d_stack.DStack
8 |
9 |
10 | /**
11 | * flutter的容器activity,需要重写provideFlutterEngine方法
12 | * 进行引擎复用
13 | */
14 | class FlutterContainerActivity : DFlutterActivity() {
15 |
16 | override fun provideFlutterEngine(context: Context): FlutterEngine? {
17 | return FlutterEngineCache.getInstance().get(DStack.ENGINE_ID)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/tal/com/d_stack_example/NativeOneActivity.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack_example;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.widget.Button;
6 |
7 | import androidx.appcompat.app.AppCompatActivity;
8 |
9 | import tal.com.d_stack.DStack;
10 |
11 | /**
12 | * native页面
13 | */
14 | public class NativeOneActivity extends AppCompatActivity {
15 |
16 | Button btnOpenNative;
17 | Button btnOpenFlutter;
18 |
19 | @Override
20 | public void onCreate(Bundle savedInstanceState) {
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.layout_one);
23 | btnOpenNative = findViewById(R.id.btn_open_native);
24 | btnOpenFlutter = findViewById(R.id.btn_open_flutter);
25 |
26 | btnOpenNative.setOnClickListener(v -> {
27 | Intent nativeIntent = new Intent(this, NativeTwoActivity.class);
28 | startActivity(nativeIntent);
29 | });
30 |
31 | btnOpenFlutter.setOnClickListener(v -> {
32 | DStack.getInstance().pushFlutterPage("page1", null, FlutterContainerActivity.class);
33 | });
34 | }
35 | }
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/tal/com/d_stack_example/NativeThreeActivity.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack_example;
2 |
3 | import android.os.Bundle;
4 | import android.view.View;
5 | import android.widget.Button;
6 |
7 | import androidx.appcompat.app.AppCompatActivity;
8 |
9 | import tal.com.d_stack.DStack;
10 |
11 |
12 | public class NativeThreeActivity extends AppCompatActivity {
13 |
14 | Button btnOpenFlutter;
15 | Button btnPopToRoot;
16 | Button btnPopToFlutter;
17 |
18 | @Override
19 | public void onCreate(Bundle savedInstanceState) {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.layout_three);
22 |
23 | btnOpenFlutter = findViewById(R.id.btn_open_flutter);
24 | btnPopToRoot = findViewById(R.id.btn_popToRoot);
25 | btnPopToFlutter = findViewById(R.id.btn_popFlutter);
26 |
27 | btnOpenFlutter.setOnClickListener(v -> {
28 | DStack.getInstance().pushFlutterPage("page4", null, FlutterContainerActivity.class);
29 | });
30 |
31 | btnPopToRoot.setOnClickListener(v -> DStack.getInstance().popToRoot());
32 |
33 | btnPopToFlutter.setOnClickListener(v -> {
34 | DStack.getInstance().popTo("page2", null);
35 | });
36 | }
37 | }
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/tal/com/d_stack_example/NativeTwoActivity.java:
--------------------------------------------------------------------------------
1 | package tal.com.d_stack_example;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.appcompat.app.AppCompatActivity;
6 |
7 |
8 | public class NativeTwoActivity extends AppCompatActivity {
9 |
10 | @Override
11 | public void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.layout_two);
14 | }
15 | }
--------------------------------------------------------------------------------
/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/layout_one.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
19 |
24 |
25 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/layout/layout_three.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
18 |
19 |
24 |
25 |
30 |
31 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/layout/layout_two.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #00000000
4 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.3.70'
3 | repositories {
4 | google()
5 | jcenter()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:4.0.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | jcenter()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.enableR8=true
3 | android.useAndroidX=true
4 | android.enableJetifier=true
5 | android.injected.testOnly=false
6 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Aug 05 16:54:45 CST 2020
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-6.1.1-all.zip
7 |
--------------------------------------------------------------------------------
/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/ios/.gitignore:
--------------------------------------------------------------------------------
1 | *.mode1v3
2 | *.mode2v3
3 | *.moved-aside
4 | *.pbxuser
5 | *.perspectivev3
6 | **/*sync/
7 | .sconsign.dblite
8 | .tags*
9 | **/.vagrant/
10 | **/DerivedData/
11 | Icon?
12 | **/Pods/
13 | **/.symlinks/
14 | profile
15 | xcuserdata
16 | **/.generated/
17 | Flutter/App.framework
18 | Flutter/Flutter.framework
19 | Flutter/Flutter.podspec
20 | Flutter/Generated.xcconfig
21 | Flutter/app.flx
22 | Flutter/app.zip
23 | Flutter/flutter_assets/
24 | Flutter/flutter_export_environment.sh
25 | ServiceDefinitions.json
26 | Runner/GeneratedPluginRegistrant.*
27 |
28 | # Exceptions to above rules.
29 | !default.mode1v3
30 | !default.mode2v3
31 | !default.pbxuser
32 | !default.perspectivev3
33 |
--------------------------------------------------------------------------------
/example/ios/Flutter/.last_build_id:
--------------------------------------------------------------------------------
1 | 11c5d94d8233a3ac66d1600b04652e8c
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
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 | project 'Runner', {
8 | 'Debug' => :debug,
9 | 'Profile' => :release,
10 | 'Release' => :release,
11 | }
12 |
13 | def flutter_root
14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
15 | unless File.exist?(generated_xcode_build_settings_path)
16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
17 | end
18 |
19 | File.foreach(generated_xcode_build_settings_path) do |line|
20 | matches = line.match(/FLUTTER_ROOT\=(.*)/)
21 | return matches[1].strip if matches
22 | end
23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
24 | end
25 |
26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
27 |
28 | flutter_ios_podfile_setup
29 |
30 | target 'Runner' do
31 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
32 | end
33 |
34 | post_install do |installer|
35 | installer.pods_project.targets.each do |target|
36 | flutter_additional_ios_build_settings(target)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/example/ios/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - d_stack (0.0.1):
3 | - Flutter
4 | - Flutter (1.0.0)
5 |
6 | DEPENDENCIES:
7 | - d_stack (from `.symlinks/plugins/d_stack/ios`)
8 | - Flutter (from `Flutter`)
9 |
10 | EXTERNAL SOURCES:
11 | d_stack:
12 | :path: ".symlinks/plugins/d_stack/ios"
13 | Flutter:
14 | :path: Flutter
15 |
16 | SPEC CHECKSUMS:
17 | d_stack: 218a394e5759f06ed1f1aacbeea9c78314bdcfd6
18 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
19 |
20 | PODFILE CHECKSUM: 8e679eca47255a8ca8067c4c67aab20e64cb974d
21 |
22 | COCOAPODS: 1.10.1
23 |
--------------------------------------------------------------------------------
/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 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface AppDelegate : UIResponder
4 |
5 | @property (strong, nonatomic) UIWindow *window;
6 |
7 | @end
8 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.m:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 | #import
3 | #import "GeneratedPluginRegistrant.h"
4 | #import "ThirdViewController.h"
5 | #import "FourViewController.h"
6 | #import "DStackViewController.h"
7 | #import "HomeViewController.h"
8 | #import "DemoFlutterViewController.h"
9 | #import "SixViewController.h"
10 |
11 | @DStackInject(AppDelegate);
12 |
13 | @interface AppDelegate ()
14 |
15 | @end
16 |
17 | static BOOL isFlutterProject = YES;
18 |
19 | @implementation AppDelegate
20 |
21 | - (BOOL)application:(UIApplication *)application
22 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
23 |
24 | [[DStack sharedInstance] startWithDelegate:self];
25 | [GeneratedPluginRegistrant registerWithRegistry:[DStack sharedInstance].engine];
26 | [[DStack sharedInstance] logEnable:YES];
27 |
28 | UIViewController *rootVC = nil;
29 | if (isFlutterProject) {
30 | DemoFlutterViewController *home = [[DemoFlutterViewController alloc] init];
31 | DStackViewController *navi = [[DStackViewController alloc] initWithRootViewController:home];
32 | rootVC = navi;
33 | } else {
34 | HomeViewController *home = [[HomeViewController alloc] init];
35 | UITabBarController *tab = [[UITabBarController alloc] init];
36 | DStackViewController *navi0 = [[DStackViewController alloc] initWithRootViewController:home];
37 | navi0.tabBarItem.title = @"home";
38 |
39 | DemoFlutterViewController *flutter = [[DemoFlutterViewController alloc] init];
40 | DStackViewController *navi1 = [[DStackViewController alloc] initWithRootViewController:flutter];
41 | navi1.tabBarItem.title = @"flutter";
42 |
43 | // [tab setViewControllers:@[navi1, navi0]];
44 | [tab setViewControllers:@[navi0, navi1]];
45 | tab.delegate = self;
46 |
47 | rootVC = tab;
48 | }
49 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
50 | self.window.backgroundColor = [UIColor whiteColor];
51 | self.window.rootViewController = rootVC;
52 | [self.window makeKeyAndVisible];
53 | [self testChannel];
54 | return YES;
55 | }
56 |
57 |
58 | - (void)testChannel
59 | {
60 | FlutterMethodChannel *channel = [DStack sharedInstance].engine.navigationChannel;
61 |
62 | }
63 |
64 |
65 | /// 当项目中tabBarController的viewControllers里面有DFlutterViewController
66 | /// 或者NavigationViewController的rootViewController是DFlutterViewController作为入口时
67 | /// 项目中必须实现tabBarController的delegate的,
68 | /// - (BOOL)tabBarController:shouldSelectViewController:并且调用DStack的
69 | /// [[DStack sharedInstance] tabBarController:tabBarController willSelectViewController:viewController];
70 | - (BOOL)tabBarController:(UITabBarController *)tabBarController
71 | shouldSelectViewController:(UIViewController *)viewController
72 | {
73 | [[DStack sharedInstance] tabBarController:tabBarController willSelectViewController:viewController];
74 | return YES;
75 | }
76 |
77 |
78 |
79 | + (FlutterEngine *)dStackForFlutterEngine
80 | {
81 | FlutterEngine *engine = [[FlutterEngine alloc] initWithName:@"io.flutter" project:nil];
82 | [engine run];
83 | return engine;
84 | }
85 |
86 | - (nonnull UINavigationController *)dStack:(nonnull DStack *)stack navigationControllerForNode:(nonnull DStackNode *)node
87 | {
88 | return [[self currentController] navigationController];
89 | }
90 |
91 | - (void)dStack:(nonnull DStack *)stack presentWithNode:(nonnull DStackNode *)node
92 | {
93 | UIViewController *didPushController = nil;
94 | // UINavigationController *navi = [self dStack:stack navigationControllerForNode:node];
95 | if ([node.route isEqualToString:@"NativePage2"]) {
96 | didPushController = [[FourViewController alloc] init];
97 | [[self currentController] presentViewController:didPushController animated:node.animated completion:nil];
98 | }
99 | }
100 |
101 | - (void)dStack:(nonnull DStack *)stack pushWithNode:(nonnull DStackNode *)node
102 | {
103 | UIViewController *didPushController = nil;
104 | UINavigationController *navi = [self dStack:stack navigationControllerForNode:node];
105 | if ([node.route isEqualToString:@"NativePage"]) {
106 | didPushController = [[ThirdViewController alloc] init];
107 | } else if ([node.route isEqualToString:@"SixViewController"]) {
108 | didPushController = [[SixViewController alloc] init];
109 | didPushController.hidesBottomBarWhenPushed = YES;
110 | }
111 | [navi pushViewController:didPushController animated:node.animated];
112 | }
113 |
114 | - (nonnull UIViewController *)visibleControllerForCurrentWindow
115 | {
116 | return [self currentController];
117 | }
118 |
119 | -(UIViewController *)currentController
120 | {
121 | return [self currentControllerFromController:self.rootController];
122 | }
123 |
124 | - (UIViewController *)currentControllerFromController:(UIViewController *)controller
125 | {
126 | if (!controller) { return nil;}
127 | UIViewController *presented = controller.presentedViewController;
128 | if (presented) { return [self currentControllerFromController:presented];}
129 | if ([controller isKindOfClass:[UINavigationController class]]) {
130 | UINavigationController *navi = (UINavigationController *)controller;
131 | if (!navi.viewControllers.count) { return navi;}
132 | return [self currentControllerFromController:navi.topViewController];
133 | } else if ([controller isKindOfClass:[UITabBarController class]]) {
134 | UITabBarController *tab = (UITabBarController *)controller;
135 | if (!tab.viewControllers.count) { return tab;}
136 | return [self currentControllerFromController:tab.selectedViewController];
137 | } else {
138 | return controller;
139 | }
140 | }
141 |
142 | - (UIViewController *)rootController
143 | {
144 | UIViewController *rootVC = [UIApplication sharedApplication].delegate.window.rootViewController;
145 | if (!rootVC) {
146 | rootVC = [[[UIApplication sharedApplication] keyWindow] rootViewController];
147 | }
148 | return rootVC;
149 | }
150 |
151 |
152 | @end
153 |
154 |
155 |
--------------------------------------------------------------------------------
/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/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/Base.lproj/Main.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 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/example/ios/Runner/CustomViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // CustomViewController.h
3 | // Runner
4 | //
5 | // Created by Caven on 2021/1/22.
6 | // Copyright © 2021 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface CustomViewController : UIAlertController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/CustomViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // CustomViewController.m
3 | // Runner
4 | //
5 | // Created by Caven on 2021/1/22.
6 | // Copyright © 2021 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "CustomViewController.h"
10 |
11 | @interface CustomViewController ()
12 |
13 | @end
14 |
15 | @implementation CustomViewController
16 |
17 | - (void)viewDidLoad {
18 | [super viewDidLoad];
19 | // Do any additional setup after loading the view.
20 | }
21 |
22 | /*
23 | #pragma mark - Navigation
24 |
25 | // In a storyboard-based application, you will often want to do a little preparation before navigation
26 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
27 | // Get the new view controller using [segue destinationViewController].
28 | // Pass the selected object to the new view controller.
29 | }
30 | */
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/example/ios/Runner/DStackTestCase.h:
--------------------------------------------------------------------------------
1 | //
2 | // DStackTestCase.h
3 | // Runner
4 | //
5 | // Created by Caven on 2020/12/4.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface DStackTestCase : NSObject
14 |
15 | @property (nonatomic, readonly) NSArray *homeTestCases;
16 | @property (nonatomic, readonly) NSArray *secondVCTestCases;
17 | @property (nonatomic, readonly) NSArray *thirdVCTestCases;
18 | @property (nonatomic, readonly) NSArray *fourVCTestCases;
19 | @property (nonatomic, readonly) NSArray *fiveVCTestCases;
20 | @property (nonatomic, readonly) NSArray *sixVCTestCases;
21 |
22 | @end
23 |
24 | NS_ASSUME_NONNULL_END
25 |
--------------------------------------------------------------------------------
/example/ios/Runner/DStackViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // DStackViewController.h
3 | // Runner
4 | //
5 | // Created by TAL on 2020/2/12.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface DStackViewController : UINavigationController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/DStackViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // DStackViewController.m
3 | // Runner
4 | //
5 | // Created by TAL on 2020/2/12.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "DStackViewController.h"
10 |
11 | @interface DStackViewController ()
12 |
13 | @end
14 |
15 | @implementation DStackViewController
16 |
17 | - (void)viewDidLoad {
18 | [super viewDidLoad];
19 | // Do any additional setup after loading the view.
20 | self.navigationBarHidden = YES;
21 | }
22 |
23 | /*
24 | #pragma mark - Navigation
25 |
26 | // In a storyboard-based application, you will often want to do a little preparation before navigation
27 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
28 | // Get the new view controller using [segue destinationViewController].
29 | // Pass the selected object to the new view controller.
30 | }
31 | */
32 |
33 | @end
34 |
--------------------------------------------------------------------------------
/example/ios/Runner/DemoFlutterViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // DemoFlutterViewController.h
3 | // Runner
4 | //
5 | // Created by Caven on 2020/12/4.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "DFlutterViewController.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface DemoFlutterViewController : DFlutterViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/DemoFlutterViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // DemoFlutterViewController.m
3 | // Runner
4 | //
5 | // Created by Caven on 2020/12/4.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "DemoFlutterViewController.h"
10 |
11 | @interface DemoFlutterViewController ()
12 |
13 | @end
14 |
15 | @implementation DemoFlutterViewController
16 |
17 | - (void)viewDidLoad {
18 | [super viewDidLoad];
19 | // Do any additional setup after loading the view.
20 | }
21 |
22 | /*
23 | #pragma mark - Navigation
24 |
25 | // In a storyboard-based application, you will often want to do a little preparation before navigation
26 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
27 | // Get the new view controller using [segue destinationViewController].
28 | // Pass the selected object to the new view controller.
29 | }
30 | */
31 |
32 | @end
33 |
--------------------------------------------------------------------------------
/example/ios/Runner/FiveViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // FiveViewController.h
3 | // Runner
4 | //
5 | // Created by Caven on 2020/12/8.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "HomeViewController.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface FiveViewController : HomeViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/FiveViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // FiveViewController.m
3 | // Runner
4 | //
5 | // Created by Caven on 2020/12/8.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "FiveViewController.h"
10 |
11 | @interface FiveViewController ()
12 |
13 | @end
14 |
15 | @implementation FiveViewController
16 |
17 | - (void)viewDidLoad {
18 | [super viewDidLoad];
19 | }
20 |
21 | - (NSArray *)dataSource
22 | {
23 | return self.testCase.fiveVCTestCases;
24 | }
25 |
26 | @end
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/FourViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // FourViewController.h
3 | // Runner
4 | //
5 | // Created by Caven on 2020/8/27.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "HomeViewController.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface FourViewController : HomeViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/FourViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // FourViewController.m
3 | // Runner
4 | //
5 | // Created by Caven on 2020/8/27.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "FourViewController.h"
10 |
11 | @interface FourViewController ()
12 |
13 | @end
14 |
15 | @implementation FourViewController
16 |
17 | - (void)viewDidLoad {
18 | [super viewDidLoad];
19 | }
20 |
21 | - (NSArray *)dataSource
22 | {
23 | return self.testCase.fourVCTestCases;
24 | }
25 |
26 | @end
27 |
--------------------------------------------------------------------------------
/example/ios/Runner/HomeViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // HomeViewController.h
3 | // Runner
4 | //
5 | // Created by TAL on 2020/2/11.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import
10 | #import "DStackTestCase.h"
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | @interface HomeViewController : UIViewController
15 |
16 | @property (nonatomic, strong) DStackTestCase *testCase;
17 |
18 |
19 | - (NSArray *)dataSource;
20 |
21 | @end
22 |
23 | NS_ASSUME_NONNULL_END
24 |
--------------------------------------------------------------------------------
/example/ios/Runner/HomeViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // HomeViewController.m
3 | // Runner
4 | //
5 | // Created by TAL on 2020/2/11.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "HomeViewController.h"
10 |
11 | @interface HomeViewController ()
12 |
13 | @property (nonatomic, strong) UITableView *tableView;
14 | @property (nonatomic, strong) UILabel *titleView;
15 |
16 | @end
17 |
18 | @implementation HomeViewController
19 |
20 | - (void)viewWillAppear:(BOOL)animated
21 | {
22 | [super viewWillAppear:animated];
23 | [self.navigationController setNavigationBarHidden:YES];
24 | }
25 |
26 | - (void)viewDidLoad {
27 | [super viewDidLoad];
28 | // Do any additional setup after loading the view.
29 | [self.view addSubview:self.titleView];
30 | [self.view addSubview:self.tableView];
31 | [self setNavigationTitle];
32 | }
33 |
34 | - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
35 | {
36 | return [[self dataSource] count];
37 | }
38 |
39 | - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
40 | {
41 | UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
42 | if (!cell) {
43 | cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
44 | }
45 | cell.textLabel.textColor = [UIColor blackColor];
46 | cell.textLabel.textAlignment = NSTextAlignmentCenter;
47 | cell.backgroundColor = [UIColor whiteColor];
48 | NSDictionary *data = self.dataSource[indexPath.row];
49 | cell.textLabel.text = data[@"text"];
50 | return cell;
51 | }
52 |
53 | - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
54 | {
55 | [tableView deselectRowAtIndexPath:indexPath animated:YES];
56 | NSDictionary *data = self.dataSource[indexPath.row];
57 | void (^block)(UIViewController *) = data[@"clicked"];
58 | if (block) {
59 | block(self);
60 | }
61 | }
62 |
63 | - (void)setNavigationTitle
64 | {
65 | self.titleView.text = NSStringFromClass(self.class);
66 | }
67 |
68 | - (NSArray *)dataSource
69 | {
70 | return self.testCase.homeTestCases;
71 | }
72 |
73 |
74 | - (UITableView *)tableView
75 | {
76 | if (!_tableView) {
77 | CGFloat bottom = self.titleView.frame.size.height;
78 | _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, bottom, self.view.frame.size.width, self.view.frame.size.height - bottom)];
79 | _tableView.delegate = self;
80 | _tableView.dataSource = self;
81 | _tableView.rowHeight = 60;
82 | _tableView.backgroundColor = [UIColor whiteColor];
83 | }
84 | return _tableView;
85 | }
86 |
87 | - (UILabel *)titleView
88 | {
89 | if (!_titleView) {
90 | CGFloat height = [UIApplication sharedApplication].statusBarFrame.size.height + 44;
91 | _titleView = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, height)];
92 | _titleView.textAlignment = NSTextAlignmentCenter;
93 | _titleView.backgroundColor = [UIColor orangeColor];
94 | }
95 | return _titleView;
96 | }
97 |
98 | - (DStackTestCase *)testCase
99 | {
100 | if (!_testCase) {
101 | _testCase = [[DStackTestCase alloc] init];
102 | }
103 | return _testCase;
104 | }
105 |
106 | - (void)dealloc
107 | {
108 | NSLog(@"dealloc ==> %@", NSStringFromClass(self.class));
109 | }
110 |
111 | @end
112 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | d_stack_example
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | $(FLUTTER_BUILD_NAME)
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(FLUTTER_BUILD_NUMBER)
23 | LSRequiresIPhoneOS
24 |
25 | UIFileSharingEnabled
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UISupportedInterfaceOrientations
30 |
31 | UIInterfaceOrientationPortrait
32 |
33 | UISupportedInterfaceOrientations~ipad
34 |
35 | UIInterfaceOrientationPortrait
36 | UIInterfaceOrientationPortraitUpsideDown
37 | UIInterfaceOrientationLandscapeLeft
38 | UIInterfaceOrientationLandscapeRight
39 |
40 | UIViewControllerBasedStatusBarAppearance
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/example/ios/Runner/SecondViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // SecondViewController.h
3 | // Runner
4 | //
5 | // Created by TAL on 2020/2/11.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "HomeViewController.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface SecondViewController : HomeViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/SecondViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // SecondViewController.m
3 | // Runner
4 | //
5 | // Created by TAL on 2020/2/11.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "SecondViewController.h"
10 | #import "ThirdViewController.h"
11 |
12 | @interface SecondViewController ()
13 |
14 | @end
15 |
16 | @implementation SecondViewController
17 |
18 | - (void)viewDidLoad {
19 | [super viewDidLoad];
20 | }
21 |
22 | - (NSArray *)dataSource
23 | {
24 | return self.testCase.secondVCTestCases;
25 | }
26 |
27 |
28 |
29 | @end
30 |
--------------------------------------------------------------------------------
/example/ios/Runner/SixViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // SixViewController.h
3 | // Runner
4 | //
5 | // Created by Caven on 2020/12/9.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "HomeViewController.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface SixViewController : HomeViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/SixViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // SixViewController.m
3 | // Runner
4 | //
5 | // Created by Caven on 2020/12/9.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "SixViewController.h"
10 |
11 | @interface SixViewController ()
12 |
13 | @end
14 |
15 | @implementation SixViewController
16 |
17 | - (void)viewDidLoad {
18 | [super viewDidLoad];
19 | // Do any additional setup after loading the view.
20 | }
21 |
22 | - (NSArray *)dataSource
23 | {
24 | return self.testCase.sixVCTestCases;
25 | }
26 |
27 | @end
28 |
--------------------------------------------------------------------------------
/example/ios/Runner/ThirdViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // ThirdViewController.h
3 | // Runner
4 | //
5 | // Created by Caven on 2020/8/27.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "HomeViewController.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | @interface ThirdViewController : HomeViewController
14 |
15 | @end
16 |
17 | NS_ASSUME_NONNULL_END
18 |
--------------------------------------------------------------------------------
/example/ios/Runner/ThirdViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // ThirdViewController.m
3 | // Runner
4 | //
5 | // Created by Caven on 2020/8/27.
6 | // Copyright © 2020 The Chromium Authors. All rights reserved.
7 | //
8 |
9 | #import "ThirdViewController.h"
10 |
11 | @interface ThirdViewController ()
12 |
13 | @end
14 |
15 | @implementation ThirdViewController
16 |
17 |
18 | - (void)viewDidLoad {
19 | [super viewDidLoad];
20 |
21 | }
22 |
23 | - (NSArray *)dataSource
24 | {
25 | return self.testCase.thirdVCTestCases;
26 | }
27 |
28 | @end
29 |
--------------------------------------------------------------------------------
/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/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:d_stack/d_stack.dart';
2 | import 'package:d_stack/observer/life_cycle_observer.dart';
3 | import 'package:d_stack/widget/home_widget.dart';
4 | import 'package:flutter/material.dart';
5 |
6 | import 'page_widgets.dart';
7 |
8 | final bool isFlutterProject = true;
9 |
10 | void main() {
11 | // 注册路由builder , 生命周期监听
12 | WidgetsFlutterBinding.ensureInitialized();
13 | DStack.instance.register(
14 | builders: RouterBuilder.builders(), observer: MyLifeCycleObserver());
15 | runApp(MyApp());
16 | }
17 |
18 | class MyApp extends StatefulWidget {
19 | @override
20 | _MyAppState createState() => _MyAppState();
21 | }
22 |
23 | class _MyAppState extends State with WidgetsBindingObserver {
24 | @override
25 | void initState() {
26 | super.initState();
27 | WidgetsBinding.instance!.addObserver(this);
28 | }
29 |
30 | @override
31 | void dispose() {
32 | WidgetsBinding.instance!.removeObserver(this);
33 | super.dispose();
34 | }
35 |
36 | @override
37 | void didChangeAppLifecycleState(AppLifecycleState state) {
38 | print("didChangeAppLifecycleState ==== $state");
39 | }
40 |
41 | @override
42 | Widget build(BuildContext context) {
43 | return MaterialApp(
44 | navigatorKey: DStack.instance.navigatorKey,
45 | navigatorObservers: [DStack.instance.dStackNavigatorObserver],
46 | home: isFlutterProject
47 | ? DStackWidget(
48 | homePage: Page1(),
49 | homePageRoute: 'page1',
50 | )
51 | : DStackWidget(),
52 | theme: ThemeData(platform: TargetPlatform.iOS),
53 | );
54 | }
55 | }
56 |
57 | class RouterBuilder {
58 | static Map builders() {
59 | Map builders = {
60 | 'page1': page1Builder,
61 | 'page2': page2Builder,
62 | 'page3': page3Builder,
63 | 'page4': page4Builder,
64 | 'page5': page5Builder,
65 | 'page6': page6Builder,
66 | 'page7': page7Builder,
67 | };
68 | return builders;
69 | }
70 |
71 | static DStackWidgetBuilder page1Builder = (Map? params) {
72 | return (BuildContext context) {
73 | return Page1();
74 | };
75 | };
76 |
77 | static DStackWidgetBuilder page2Builder = (Map? params) {
78 | return (BuildContext context) {
79 | return Page2();
80 | };
81 | };
82 |
83 | static DStackWidgetBuilder page3Builder = (Map? params) {
84 | return (BuildContext context) {
85 | return Page3();
86 | };
87 | };
88 |
89 | static DStackWidgetBuilder page4Builder = (Map? params) {
90 | return (BuildContext context) {
91 | return Page4();
92 | };
93 | };
94 |
95 | static DStackWidgetBuilder page5Builder = (Map? params) {
96 | return (BuildContext context) {
97 | return Page5();
98 | };
99 | };
100 |
101 | static DStackWidgetBuilder page6Builder = (Map? params) {
102 | return (BuildContext context) {
103 | return Page6();
104 | };
105 | };
106 |
107 | static DStackWidgetBuilder page7Builder = (Map? params) {
108 | return (BuildContext context) {
109 | return Page7();
110 | };
111 | };
112 | }
113 |
114 | class MyLifeCycleObserver extends DLifeCycleObserver {
115 | @override
116 | void appDidEnterBackground(PageModel model) {
117 | debugPrint(
118 | "MyLifeCycleObserver appDidEnterBackground == ${model.currentPageRoute}");
119 | }
120 |
121 | @override
122 | void appDidEnterForeground(PageModel model) {
123 | debugPrint(
124 | "MyLifeCycleObserver appDidEnterForeground == ${model.currentPageRoute}");
125 | }
126 |
127 | @override
128 | void appDidStart(PageModel model) {
129 | debugPrint("MyLifeCycleObserver appDidStart == ${model.currentPageRoute}");
130 | }
131 |
132 | @override
133 | void pageAppear(PageModel model) {
134 | debugPrint("MyLifeCycleObserver pageAppear model:${model.toString()}");
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/example/lib/page_widgets.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Created with Android Studio.
3 | * User: whqfor
4 | * Date: 2019-11-26
5 | * Time: 14:47
6 | * email: wanghuaqiang@100tal.com
7 | * tartget: page_widgets
8 | */
9 |
10 | import 'package:d_stack/d_stack.dart';
11 | import 'package:flutter/material.dart';
12 |
13 | import 'test_case.dart';
14 |
15 | class Student {
16 | String? name;
17 | int? age;
18 | String? address;
19 | }
20 |
21 | Widget _caseWidget(List items, {BuildContext? context}) {
22 | return Center(
23 | child: ListView.builder(
24 | itemExtent: 60,
25 | itemCount: items.length,
26 | itemBuilder: (BuildContext context, index) {
27 | Map caseMap = items[index];
28 | return GestureDetector(
29 | behavior: HitTestBehavior.opaque,
30 | child: Container(
31 | alignment: Alignment.center,
32 | decoration: BoxDecoration(
33 | border:
34 | Border(bottom: BorderSide(color: Colors.grey, width: 0.5))),
35 | child: Text(caseMap["text"]),
36 | ),
37 | onTap: () {
38 | caseMap["clicked"](context);
39 | },
40 | );
41 | },
42 | ),
43 | );
44 | }
45 |
46 | class Page1 extends StatefulWidget {
47 | @override
48 | State createState() {
49 | return _Page1();
50 | }
51 | }
52 |
53 | // native,
54 | // nativeModal,
55 | // inFromLeft,
56 | // inFromTop,
57 | // inFromRight,
58 | // inFromBottom,
59 | // fadeIn,
60 | // custom,
61 | // material,
62 | // materialFullScreenDialog,
63 | // cupertino,
64 | // cupertinoFullScreenDialog,
65 | // fadeOpaque, ///透明路由类型
66 | // fadeAndScale,
67 | // none,
68 | class _Page1 extends State {
69 | @override
70 | Widget build(BuildContext context) {
71 | final Map? args =
72 | ModalRoute.of(context)!.settings.arguments as Map?;
73 | debugPrint('page1收到前一个页面传来的参数 ==> $args');
74 | return Scaffold(
75 | appBar: AppBar(title: Text('flutter page1'), leading: Container()),
76 | backgroundColor: Colors.white,
77 | body: _caseWidget(TestCase.openFlutterPageCase),
78 | floatingActionButton: FloatingActionButton.extended(
79 | onPressed: () {
80 | DStack.animatedFlutterPage('page2', transition: TransitionType.fadeAndScale);
81 | },
82 | label: const Text('animation'),
83 | icon: const Icon(Icons.touch_app_outlined),
84 | backgroundColor: Colors.pink,
85 | ),
86 | );
87 | }
88 | }
89 |
90 | class Page2 extends StatelessWidget {
91 | @override
92 | Widget build(BuildContext context) {
93 | final Map? args =
94 | ModalRoute.of(context)!.settings.arguments as Map?;
95 | debugPrint('page2收到前一个页面传来的参数 ==> $args');
96 |
97 | return Scaffold(
98 | appBar: AppBar(
99 | title: Text('flutter page2'),
100 | leading: ElevatedButton(
101 | child: Text('返回'),
102 | onPressed: () {
103 | DStack.pop(result: {"test":"测试page2数据返回"});
104 | },
105 | )),
106 | body: _caseWidget(TestCase.closeFlutterPage, context: context),
107 | );
108 | }
109 | }
110 |
111 | class Page3 extends StatelessWidget {
112 | @override
113 | Widget build(BuildContext context) {
114 | return Scaffold(
115 | appBar: AppBar(
116 | title: Text('flutter page3'),
117 | leading: ElevatedButton(
118 | child: Text('返回'),
119 | onPressed: () {
120 | DStack.pop();
121 | },
122 | )),
123 | body: _caseWidget(TestCase.popToPage),
124 | );
125 | }
126 | }
127 |
128 | class Page4 extends StatelessWidget {
129 | @override
130 | Widget build(BuildContext context) {
131 | return Scaffold(
132 | appBar: AppBar(
133 | title: Text('flutter page4'),
134 | leading: ElevatedButton(
135 | child: Text('返回'),
136 | onPressed: () {
137 | DStack.pop(result: {"test":"测试page4数据返回"});
138 | },
139 | ),
140 | ),
141 | body: _caseWidget(TestCase.openFlutterPageCase),
142 | );
143 | }
144 | }
145 |
146 | class Page5 extends StatelessWidget {
147 | @override
148 | Widget build(BuildContext context) {
149 | return Scaffold(
150 | appBar: AppBar(
151 | title: Text('flutter page5'),
152 | leading: ElevatedButton(
153 | child: Text('返回'),
154 | onPressed: () {
155 | DStack.pop();
156 | },
157 | ),
158 | ),
159 | body: _caseWidget(TestCase.page5Cases),
160 | );
161 | }
162 | }
163 |
164 | class Page6 extends StatelessWidget {
165 | @override
166 | Widget build(BuildContext context) {
167 | return Scaffold(
168 | appBar: AppBar(
169 | title: Text('flutter page6'),
170 | leading: ElevatedButton(
171 | child: Text('返回'),
172 | onPressed: () {
173 | DStack.pop();
174 | },
175 | ),
176 | ),
177 | body: _caseWidget(TestCase.page6Cases),
178 | );
179 | }
180 | }
181 |
182 | class Page7 extends StatelessWidget {
183 | @override
184 | Widget build(BuildContext context) {
185 | return Scaffold(
186 | appBar: AppBar(
187 | title: Text('flutter page7'),
188 | leading: ElevatedButton(
189 | child: Text('返回'),
190 | onPressed: () {
191 | DStack.pop();
192 | },
193 | ),
194 | ),
195 | body: _caseWidget(TestCase.page7Cases),
196 | );
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/example/lib/will_pop_scope_route.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/widgets.dart';
2 |
3 | class WillPopScopeRoute extends StatefulWidget {
4 | final Widget child;
5 |
6 | WillPopScopeRoute(this.child);
7 |
8 | @override
9 | WillPopScopeRouteState createState() {
10 | return new WillPopScopeRouteState();
11 | }
12 | }
13 |
14 | class WillPopScopeRouteState extends State {
15 | DateTime? _lastPressedAt; //上次点击时间
16 |
17 | @override
18 | Widget build(BuildContext context) {
19 | return new WillPopScope(
20 | onWillPop: () async {
21 | if (_lastPressedAt == null ||
22 | DateTime.now().difference(_lastPressedAt!) >
23 | Duration(seconds: 2)) {
24 | print('2秒后在按一次退出');
25 | //两次点击间隔超过1秒则重新计时
26 | _lastPressedAt = DateTime.now();
27 | return false;
28 | } else {
29 | print('退出了');
30 | return true;
31 | }
32 | },
33 | child: widget.child);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/example/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.flutter-io.cn"
9 | source: hosted
10 | version: "2.8.1"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.flutter-io.cn"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.flutter-io.cn"
23 | source: hosted
24 | version: "1.1.0"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.flutter-io.cn"
30 | source: hosted
31 | version: "1.3.1"
32 | clock:
33 | dependency: transitive
34 | description:
35 | name: clock
36 | url: "https://pub.flutter-io.cn"
37 | source: hosted
38 | version: "1.1.0"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.flutter-io.cn"
44 | source: hosted
45 | version: "1.15.0"
46 | cupertino_icons:
47 | dependency: "direct main"
48 | description:
49 | name: cupertino_icons
50 | url: "https://pub.flutter-io.cn"
51 | source: hosted
52 | version: "0.1.3"
53 | d_stack:
54 | dependency: "direct main"
55 | description:
56 | path: ".."
57 | relative: true
58 | source: path
59 | version: "1.3.4+3-nullsafety"
60 | fake_async:
61 | dependency: transitive
62 | description:
63 | name: fake_async
64 | url: "https://pub.flutter-io.cn"
65 | source: hosted
66 | version: "1.2.0"
67 | flutter:
68 | dependency: "direct main"
69 | description: flutter
70 | source: sdk
71 | version: "0.0.0"
72 | flutter_test:
73 | dependency: "direct dev"
74 | description: flutter
75 | source: sdk
76 | version: "0.0.0"
77 | matcher:
78 | dependency: transitive
79 | description:
80 | name: matcher
81 | url: "https://pub.flutter-io.cn"
82 | source: hosted
83 | version: "0.12.10"
84 | meta:
85 | dependency: transitive
86 | description:
87 | name: meta
88 | url: "https://pub.flutter-io.cn"
89 | source: hosted
90 | version: "1.7.0"
91 | path:
92 | dependency: transitive
93 | description:
94 | name: path
95 | url: "https://pub.flutter-io.cn"
96 | source: hosted
97 | version: "1.8.0"
98 | sky_engine:
99 | dependency: transitive
100 | description: flutter
101 | source: sdk
102 | version: "0.0.99"
103 | source_span:
104 | dependency: transitive
105 | description:
106 | name: source_span
107 | url: "https://pub.flutter-io.cn"
108 | source: hosted
109 | version: "1.8.1"
110 | stack_trace:
111 | dependency: transitive
112 | description:
113 | name: stack_trace
114 | url: "https://pub.flutter-io.cn"
115 | source: hosted
116 | version: "1.10.0"
117 | stream_channel:
118 | dependency: transitive
119 | description:
120 | name: stream_channel
121 | url: "https://pub.flutter-io.cn"
122 | source: hosted
123 | version: "2.1.0"
124 | string_scanner:
125 | dependency: transitive
126 | description:
127 | name: string_scanner
128 | url: "https://pub.flutter-io.cn"
129 | source: hosted
130 | version: "1.1.0"
131 | term_glyph:
132 | dependency: transitive
133 | description:
134 | name: term_glyph
135 | url: "https://pub.flutter-io.cn"
136 | source: hosted
137 | version: "1.2.0"
138 | test_api:
139 | dependency: transitive
140 | description:
141 | name: test_api
142 | url: "https://pub.flutter-io.cn"
143 | source: hosted
144 | version: "0.4.2"
145 | typed_data:
146 | dependency: transitive
147 | description:
148 | name: typed_data
149 | url: "https://pub.flutter-io.cn"
150 | source: hosted
151 | version: "1.3.0"
152 | vector_math:
153 | dependency: transitive
154 | description:
155 | name: vector_math
156 | url: "https://pub.flutter-io.cn"
157 | source: hosted
158 | version: "2.1.0"
159 | sdks:
160 | dart: ">=2.12.0 <3.0.0"
161 | flutter: ">=2.0.0"
162 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: d_stack_example
2 | description: Demonstrates how to use the d_stack plugin.
3 | publish_to: 'none'
4 |
5 | environment:
6 | sdk: '>=2.12.0 <3.0.0'
7 |
8 | dependencies:
9 | flutter:
10 | sdk: flutter
11 | d_stack:
12 | path: ../
13 |
14 | # The following adds the Cupertino Icons font to your application.
15 | # Use with the CupertinoIcons class for iOS style icons.
16 | cupertino_icons: 0.1.3
17 |
18 | dev_dependencies:
19 | flutter_test:
20 | sdk: flutter
21 |
22 |
23 | # For information on the generic Dart part of this file, see the
24 | # following page: https://dart.dev/tools/pub/pubspec
25 |
26 | # The following section is specific to Flutter.
27 | flutter:
28 |
29 | # The following line ensures that the Material Icons font is
30 | # included with your application, so that you can use the icons in
31 | # the material Icons class.
32 | uses-material-design: true
33 |
34 | # To add assets to your application, add an assets section, like this:
35 | # assets:
36 | # - images/a_dot_burr.jpeg
37 | # - images/a_dot_ham.jpeg
38 |
39 | # An image asset can refer to one or more resolution-specific "variants", see
40 | # https://flutter.dev/assets-and-images/#resolution-aware.
41 |
42 | # For details regarding adding assets from package dependencies, see
43 | # https://flutter.dev/assets-and-images/#from-packages
44 |
45 | # To add custom fonts to your application, add a fonts section here,
46 | # in this "flutter" section. Each entry in this list should have a
47 | # "family" key with the font family name, and a "fonts" key with a
48 | # list giving the asset and other descriptors for the font. For
49 | # example:
50 | # fonts:
51 | # - family: Schyler
52 | # fonts:
53 | # - asset: fonts/Schyler-Regular.ttf
54 | # - asset: fonts/Schyler-Italic.ttf
55 | # style: italic
56 | # - family: Trajan Pro
57 | # fonts:
58 | # - asset: fonts/TrajanPro.ttf
59 | # - asset: fonts/TrajanPro_Bold.ttf
60 | # weight: 700
61 | #
62 | # For details regarding fonts from package dependencies,
63 | # see https://flutter.dev/custom-fonts/#from-packages
64 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | import 'package:flutter_test/flutter_test.dart';
9 |
10 | import '../lib/main.dart';
11 |
12 | void main() {
13 | testWidgets('Verify Platform version', (WidgetTester tester) async {
14 | // Build our app and trigger a frame.
15 | await tester.pumpWidget(MyApp());
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/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/Generated.xcconfig
37 | /Flutter/flutter_export_environment.sh
--------------------------------------------------------------------------------
/ios/Assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tal-tech/d_stack/c10b77f8f13b1fb9de25daad99b25a50cafebe52/ios/Assets/.gitkeep
--------------------------------------------------------------------------------
/ios/Classes/Node/DActionManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // DActionManager.h
3 | // 管理跳转事件
4 | //
5 | // Created by TAL on 2020/1/16.
6 | //
7 |
8 | #import
9 | #import "DNode.h"
10 |
11 | @class DStackNode;
12 |
13 | NS_ASSUME_NONNULL_BEGIN
14 |
15 | /// 页面跳转检测器
16 | /// DNodeManager传递待跳转的节点过来
17 | /// Flutter 进入 Native,发送Node节点至Native
18 | /// Native 进入 Flutter, 发送消息至Flutter
19 | @interface DActionManager : NSObject
20 |
21 | + (void)handlerActionWithNodeList:(NSArray *)nodeList
22 | node:(DNode *)node;
23 | /// rootVC是不是FlutterController
24 | + (BOOL)rootControllerIsFlutterController;
25 |
26 | /// 当前的rootViewController
27 | + (UIViewController *)rootController;
28 |
29 | /// 检查flutter Engine的FlutterViewController是否存在
30 | + (void)checkFlutterViewController;
31 |
32 | /// 当前控制前
33 | + (UIViewController *)currentController;
34 |
35 | /// DNode创建DStackNode
36 | /// @param node DStackNode
37 | + (DStackNode *)stackNodeFromNode:(DNode *)node;
38 |
39 | /// tabBar切换事件
40 | /// @param viewController 将要切换的viewController
41 | /// @param route homePageRoute
42 | + (void)tabBarWillSelectViewController:(UIViewController *)viewController
43 | homePageRoute:(NSString *)route;
44 |
45 | @end
46 |
47 | NS_ASSUME_NONNULL_END
48 |
--------------------------------------------------------------------------------
/ios/Classes/Node/DNode.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNode.h
3 | // 节点类
4 | // 记录节点信息,把每个page映射成DNode
5 | //
6 | // Created by TAL on 2020/1/16.
7 | //
8 |
9 | #import
10 | #import "DStackProvider.h"
11 |
12 | NS_ASSUME_NONNULL_BEGIN
13 |
14 | /// 节点类
15 | @interface DNode : NSObject
16 |
17 | /// 页面跳转类型
18 | @property (nonatomic, assign) DNodeActionType action;
19 |
20 | /// 页面类型
21 | @property (nonatomic, assign) DNodePageType pageType;
22 |
23 | /// 页面唯一标识
24 | /// Flutter页面时是route
25 | /// Native页面时是类名
26 | @property (nonatomic, copy) NSString *target;
27 |
28 | /// 附带参数
29 | @property (nonatomic, strong) NSDictionary *params;
30 |
31 | /// 是否来自Flutter消息通道的Node
32 | @property (nonatomic, assign) BOOL fromFlutter;
33 |
34 | /// 是否可以移除节点
35 | /// 收到didPop消息时才是YES,只有flutter页面会有didDop消息
36 | @property (nonatomic, assign) BOOL canRemoveNode;
37 |
38 | /// 是否是flutter的第一个页面
39 | @property (nonatomic, assign) BOOL isFlutterHomePage;
40 |
41 | /// 是否开启进场动画
42 | @property (nonatomic, assign) BOOL animated;
43 |
44 | /// 是否是临界节点
45 | @property (nonatomic, assign) BOOL boundary;
46 |
47 | /// 唯一id
48 | /// 生成规则 controller类名+contorller的内存地址
49 | /// DFlutterViewController_0x28268d1d0
50 | @property (nonatomic, copy) NSString *identifier;
51 |
52 | /// 是否是根页面
53 | @property (nonatomic, assign) BOOL isRootPage;
54 |
55 | - (NSString *)actionTypeString;
56 | - (NSString *)pageTypeString;
57 | - (NSString *)pageString;
58 | - (void)copyWithNode:(DNode *)node;
59 |
60 | + (DNodePageType)pageTypeWithString:(NSString *)string;
61 | + (DNodeActionType)actionTypeWithString:(NSString *)string;
62 |
63 | @end
64 |
65 | NS_ASSUME_NONNULL_END
66 |
--------------------------------------------------------------------------------
/ios/Classes/Node/DNode.m:
--------------------------------------------------------------------------------
1 | //
2 | // DNode.m
3 | //
4 | //
5 | // Created by TAL on 2020/1/16.
6 | //
7 |
8 | #import "DNode.h"
9 |
10 | @interface DNode ()
11 |
12 | @end
13 |
14 | @implementation DNode
15 |
16 | - (instancetype)init
17 | {
18 | self = [super init];
19 | if (self) {
20 | _target = @"";
21 | }
22 | return self;
23 | }
24 |
25 | - (NSString *)pageTypeString
26 | {
27 | NSString *type = @"";
28 | if (_pageType == DNodePageTypeFlutter) {
29 | type = @"Flutter";
30 | } else if (_pageType == DNodePageTypeNative) {
31 | type = @"Native";
32 | }
33 | return type;
34 | }
35 |
36 | - (NSString *)pageString
37 | {
38 | NSString *type = @"";
39 | if (_pageType == DNodePageTypeFlutter) {
40 | type = @"flutter";
41 | } else if (_pageType == DNodePageTypeNative) {
42 | type = @"native";
43 | }
44 | return type;
45 | }
46 |
47 | - (NSString *)actionTypeString
48 | {
49 | NSString *action = @"";
50 | switch (_action) {
51 | case DNodeActionTypePush:{action = @"push";break;}
52 | case DNodeActionTypePresent:{action = @"present";break;}
53 | case DNodeActionTypePop:{action = @"pop";break;}
54 | case DNodeActionTypePopTo:{action = @"popTo";break;}
55 | case DNodeActionTypePopToRoot:{action = @"popToRoot";break;}
56 | case DNodeActionTypePopSkip:{action = @"popSkip";break;}
57 | case DNodeActionTypeGesture:{action = @"gesture";break;}
58 | case DNodeActionTypeDismiss:{action = @"dismiss";break;}
59 | case DNodeActionTypeReplace:{action = @"replace";break;}
60 | case DNodeActionTypeDidPop:{action = @"didPop";break;}
61 | case DNodeActionTypePushAndRemoveUntil:{action = @"pushAndRemoveUntil";break;}
62 | default:break;
63 | }
64 | return action;
65 | }
66 |
67 | - (void)copyWithNode:(DNode *)node
68 | {
69 | self.action = node.action;
70 | self.pageType = node.pageType;
71 | self.target = node.target;
72 | self.params = node.params;
73 | self.identifier = node.identifier;
74 | }
75 |
76 | + (DNodePageType)pageTypeWithString:(NSString *)string
77 | {
78 | if (!string || [string isEqual:NSNull.null] || !string.length) {
79 | return DNodePageTypeUnknow;
80 | }
81 | NSString *_pageType = string;
82 | DNodePageType pageType = DNodePageTypeUnknow;
83 | if ([_pageType isEqualToString:@"native"]) {
84 | pageType = DNodePageTypeNative;
85 | } else if ([_pageType isEqualToString:@"flutter"]) {
86 | pageType = DNodePageTypeFlutter;
87 | }
88 | return pageType;
89 | }
90 |
91 | + (DNodeActionType)actionTypeWithString:(NSString *)string
92 | {
93 | if (!string || [string isEqual:NSNull.null] || !string.length) {
94 | return DNodeActionTypeUnknow;
95 | }
96 | NSString *_actionType = string;
97 | DNodeActionType actionType = DNodeActionTypeUnknow;
98 | if ([_actionType isEqualToString:@"push"]) {
99 | actionType = DNodeActionTypePush;
100 | } else if ([_actionType isEqualToString:@"present"]) {
101 | actionType = DNodeActionTypePresent;
102 | } else if ([_actionType isEqualToString:@"pop"]) {
103 | actionType = DNodeActionTypePop;
104 | } else if ([_actionType isEqualToString:@"popTo"]) {
105 | actionType = DNodeActionTypePopTo;
106 | } else if ([_actionType isEqualToString:@"popSkip"]) {
107 | actionType = DNodeActionTypePopSkip;
108 | } else if ([_actionType isEqualToString:@"popToRoot"]) {
109 | actionType = DNodeActionTypePopToRoot;
110 | } else if ([_actionType isEqualToString:@"dismiss"]) {
111 | actionType = DNodeActionTypeDismiss;
112 | } else if ([_actionType isEqualToString:@"gesture"]) {
113 | actionType = DNodeActionTypeGesture;
114 | } else if ([_actionType isEqualToString:@"replace"]) {
115 | actionType = DNodeActionTypeReplace;
116 | } else if ([_actionType isEqualToString:@"didPop"]) {
117 | actionType = DNodeActionTypeDidPop;
118 | } else if ([_actionType isEqualToString:@"pushAndRemoveUntil"]) {
119 | actionType = DNodeActionTypePushAndRemoveUntil;
120 | }
121 | return actionType;
122 | }
123 |
124 | - (NSString *)target
125 | {
126 | if (_target && [_target isKindOfClass:NSString.class]) {
127 | return _target;
128 | }
129 | return @"";
130 | }
131 |
132 | - (NSString *)description
133 | {
134 | return [NSString stringWithFormat:@"[class:%@ %p][action:%@][pageType:%@][target:%@][params:%@][identifier:%@]",
135 | NSStringFromClass(self.class),
136 | self,
137 | self.actionTypeString,
138 | self.pageTypeString,
139 | _target,
140 | _params,
141 | _identifier];
142 | }
143 |
144 | - (id)copyWithZone:(NSZone *)zone
145 | {
146 | return [self nodeWithZone:zone];
147 | }
148 |
149 | - (id)mutableCopyWithZone:(NSZone *)zone
150 | {
151 | return [self nodeWithZone:zone];
152 | }
153 |
154 | - (id)nodeWithZone:(NSZone *)zone
155 | {
156 | DNode *node = [[DNode allocWithZone:zone] init];
157 | node.fromFlutter = _fromFlutter;
158 | node.canRemoveNode = _canRemoveNode;
159 | node.isFlutterHomePage = _isFlutterHomePage;
160 | node.animated = _animated;
161 | node.boundary = _boundary;
162 | node.isRootPage = _isRootPage;
163 | node.action = _action;
164 | node.pageType = _pageType;
165 | node.params = _params;
166 | node.target = _target;
167 | node.identifier = _identifier;
168 | return node;
169 | }
170 |
171 | @end
172 |
--------------------------------------------------------------------------------
/ios/Classes/Node/DNodeManager.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNodeManager.h
3 | // 管理节点信息
4 | //
5 | // Created by TAL on 2020/1/16.
6 | //
7 |
8 | #import
9 | #import "DNode.h"
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | /// Node节点管理器
14 | /// 全链路全节点记录
15 | /// Flutter页面打开、关闭都会发消息至Native侧进行节点管理
16 | /// Native页面打开、关闭都会发消息至Native侧进行节点管理
17 | @interface DNodeManager : NSObject
18 |
19 | /// 当前的节点列表
20 | @property (nonatomic, strong, readonly) NSArray *currentNodeList;
21 |
22 | /// 当前显示的节点
23 | @property (nonatomic, strong, readonly, nullable) DNode *currentNode;
24 |
25 | /// 当前显示的节点的前一个节点
26 | @property (nonatomic, strong, readonly, nullable) DNode *preNode;
27 |
28 | /// 每次被移除的节点列表
29 | @property (nonatomic, strong, nullable) NSArray *removedNodes;
30 |
31 | /// 当前被显示的FlutterViewControllerID
32 | @property (nonatomic, copy, nullable) NSString *currentFlutterViewControllerID;
33 |
34 | + (instancetype)sharedInstance;
35 |
36 | + (instancetype)new NS_UNAVAILABLE;
37 | - (instancetype)init NS_UNAVAILABLE;
38 |
39 | /// 配置日志信息
40 | - (void)configLogFileWithDebugMode:(BOOL)debugMode;
41 |
42 | /// 清除本地日志
43 | - (void)cleanLogFile;
44 |
45 | /// 检查节点
46 | /// @param node 节点信息
47 | - (NSArray *)checkNode:(DNode *)node;
48 |
49 | /// 根据target从nodeLlist获取,倒序查找
50 | /// @param target target
51 | - (nullable DNode *)nodeWithTarget:(NSString *)target;
52 |
53 | /// 根据identifier从nodeLlist获取,倒序查找
54 | /// @param identifier identifier
55 | - (nullable DNode *)nodeWithIdentifier:(NSString *)identifier;
56 |
57 | /// 原生页面的pop返回手势能否响应
58 | /// 判断逻辑
59 | /// NodeList的最后一个节点就是当前页面正在显示的页面
60 | /// 取出最后一个节点,判断该节点的前面一个节点
61 | /// 如果前一个节点是Flutter页面,则原生页面的pop返回手势不能响应,否则可以响应
62 | - (BOOL)nativePopGestureCanReponse;
63 |
64 | /// 创建节点
65 | /// @param scheme 节点标识
66 | /// @param pageType 下一页面类型
67 | /// @param actionType 跳转类型
68 | /// @param params 附带参数
69 | - (DNode *)nextPageScheme:(NSString *)scheme
70 | pageType:(DNodePageType)pageType
71 | action:(DNodeActionType)actionType
72 | params:(nullable NSDictionary *)params;
73 |
74 | /// 应用生命周期消息
75 | /// @param state 应用的生命周期
76 | - (void)sendAppliccationLifeCicleToFlutter:(DStackApplicationState)state;
77 |
78 | /// 更新临界节点
79 | /// @param nodeInfo nodeInfo
80 | - (void)updateBoundaryNode:(NSDictionary *)nodeInfo;
81 |
82 | /// 更新根节点信息
83 | /// @param node node
84 | - (BOOL)updateRootNode:(DNode *)node;
85 |
86 | /// 获取日志文件内容
87 | - (nullable NSArray *)logFiles;
88 |
89 | @end
90 |
91 | NS_ASSUME_NONNULL_END
92 |
--------------------------------------------------------------------------------
/ios/Classes/Stack/DFlutterViewController.h:
--------------------------------------------------------------------------------
1 | //
2 | // DFlutterViewController.h
3 | //
4 | //
5 | // Created by TAL on 2020/1/16.
6 | //
7 |
8 | #import
9 | #import
10 |
11 | NS_ASSUME_NONNULL_BEGIN
12 |
13 | /// 当需要打开一个Flutter页面时,必须是DFlutterViewController或者是它的子类
14 | @interface DFlutterViewController : FlutterViewController
15 |
16 | - (void)willUpdateView;
17 | - (void)didUpdateView;
18 | - (void)updateCurrentNode:(id)node;
19 | - (id)currentNode;
20 |
21 | @end
22 |
23 | NS_ASSUME_NONNULL_END
24 |
25 |
--------------------------------------------------------------------------------
/ios/Classes/Stack/DFlutterViewController.m:
--------------------------------------------------------------------------------
1 | //
2 | // DFlutterViewController.m
3 | //
4 | //
5 | // Created by TAL on 2020/1/16.
6 | //
7 |
8 | #import "DFlutterViewController.h"
9 | #import "DNodeManager.h"
10 | #import "DActionManager.h"
11 | #import
12 | #import "DNavigator.h"
13 | #import "DStack.h"
14 |
15 | @interface DFlutterViewController ()
16 |
17 | /// 当前正在被显示的Node
18 | @property (nonatomic, strong) DNode *currentShowNode;
19 |
20 | @end
21 |
22 | @implementation DFlutterViewController
23 |
24 | - (instancetype)init
25 | {
26 | if (self.dStackFlutterEngine.viewController) {
27 | self.dStackFlutterEngine.viewController = nil;
28 | }
29 | if(self = [super initWithEngine:self.dStackFlutterEngine
30 | nibName:nil
31 | bundle:nil]) {
32 | [self config];
33 | }
34 | return self;
35 | }
36 |
37 | - (instancetype)initWithCoder:(NSCoder *)coder
38 | {
39 | if (self.dStackFlutterEngine.viewController) {
40 | self.dStackFlutterEngine.viewController = nil;
41 | }
42 | if(self = [super initWithEngine:self.dStackFlutterEngine
43 | nibName:nil
44 | bundle:nil]) {
45 | [self config];
46 | }
47 | return self;
48 | }
49 |
50 | - (void)config
51 | {
52 | self.view.backgroundColor = [UIColor whiteColor];
53 | [self addNotification];
54 | }
55 |
56 | - (void)viewWillAppear:(BOOL)animated
57 | {
58 | // 必须在页面显示之前判断engine是否存在FlutterViewController
59 | // 否则会因为FlutterViewController不存在而崩溃
60 | if (self.dStackFlutterEngine.viewController != self) {
61 | self.dStackFlutterEngine.viewController = nil;
62 | self.dStackFlutterEngine.viewController = self;
63 | }
64 | [self checkSelfIsInTabBarController];
65 | NSString *identifier = [NSString stringWithFormat:@"%p", self];
66 | [DNodeManager sharedInstance].currentFlutterViewControllerID = identifier;
67 | [super viewWillAppear:animated];
68 | self.navigationController.navigationBarHidden = YES;
69 | }
70 |
71 | - (void)viewDidAppear:(BOOL)animated
72 | {
73 | // 刷新一下FlutterViewController的页面,保证当前显示的view是最新的
74 | [self _surfaceUpdated:YES];
75 | DNode *topNode = [DNodeManager sharedInstance].currentNode;
76 | if (topNode.pageType == DNodePageTypeFlutter) {
77 | [self updateCurrentNode:topNode];
78 | }
79 | [super viewDidAppear:animated];
80 | }
81 |
82 | - (void)viewDidDisappear:(BOOL)animated
83 | {
84 | [self removeGesturePopNode];
85 | [super viewDidDisappear:animated];
86 | }
87 |
88 | /// dismiss手势不会触发页面的viewWillAppear
89 | /// viewDidDisappear里面,flutter会让Engine暂停,不会渲染flutter页面
90 | /// 在dismiss的某些情况下,主动调用viewWillAppear,使Engine进入inactive状态
91 | - (void)willUpdateView
92 | {
93 | [self viewWillAppear:YES];
94 | }
95 |
96 | /// dismiss手势不会触发页面的viewDidAppear
97 | /// viewDidDisappear里面,flutter会让Engine暂停,不会渲染flutter页面
98 | /// 在dismiss的某些情况下,主动调用viewDidAppear,使Engine进入resumed状态
99 | - (void)didUpdateView
100 | {
101 | [self viewDidAppear:YES];
102 | /// 调用这个是为了重新计算页面的布局
103 | /// 因为在非全屏present页面时,该页面是没有状态栏的
104 | /// 所以在dismiss的时候,需要重新展示状态栏,就需要刷新flutter的页面
105 | [super viewDidLayoutSubviews];
106 | }
107 |
108 | - (void)updateCurrentNode:(DNode *)node
109 | {
110 | _currentShowNode = [node copy];
111 | }
112 |
113 | - (id)currentNode
114 | {
115 | return _currentShowNode;
116 | }
117 |
118 | - (void)checkSelfIsInTabBarController
119 | {
120 | UITabBarController *tabBarController = self.tabBarController;
121 | if (tabBarController) {
122 | UIViewController *selectedViewController = tabBarController.selectedViewController;
123 | if (selectedViewController) {
124 | UIViewController *visibleVC = [self visibleSelectedViewController:selectedViewController];
125 | if (visibleVC != self) {return;}
126 | [DActionManager tabBarWillSelectViewController:selectedViewController
127 | homePageRoute:[DStack sharedInstance].flutterHomePageRoute];
128 | }
129 | } else {
130 | // 检查是不是flutter工程
131 | if ([DActionManager rootControllerIsFlutterController]) {
132 | DNode *node = [[DNode alloc] init];
133 | node.pageType = DNodePageTypeFlutter;
134 | node.action = DNodeActionTypeReplace;
135 | node.target = [DStack sharedInstance].flutterHomePageRoute;
136 | [[DNodeManager sharedInstance] updateRootNode:node];
137 | }
138 | }
139 | }
140 |
141 | - (UIViewController *)visibleSelectedViewController:(UIViewController *)selectedViewController
142 | {
143 | if ([selectedViewController isKindOfClass:UINavigationController.class]) {
144 | return [[(UINavigationController *)selectedViewController viewControllers] firstObject];
145 | }
146 | return selectedViewController;
147 | }
148 |
149 | - (void)_surfaceUpdated:(BOOL)appeared
150 | {
151 | SEL sel = NSSelectorFromString(@"surfaceUpdated:");
152 | if (class_respondsToSelector(self.class, sel)) {
153 | NSMethodSignature *signature = [self methodSignatureForSelector:sel];
154 | if (signature) {
155 | NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
156 | invocation.selector = sel;
157 | invocation.target = self;
158 | [invocation setArgument:&appeared atIndex:2];
159 | [invocation invoke];
160 | }
161 | }
162 | }
163 |
164 | - (void)addNotification
165 | {
166 | [[NSNotificationCenter defaultCenter] addObserver:self
167 | selector:@selector(changeBottomBarVisible:)
168 | name:DStackNotificationNameChangeBottomBarVisible
169 | object:nil];
170 | }
171 |
172 | - (void)changeBottomBarVisible:(NSNotification *)notification
173 | {
174 | if (!self.tabBarController) {return;}
175 | NSDictionary *userInfo = notification.userInfo;
176 | if (!userInfo) {
177 | if (self.tabBarController.tabBar.hidden == NO) {
178 | [self.tabBarController.tabBar setHidden:YES];
179 | }
180 | } else {
181 | BOOL hidden = [userInfo[@"hidden"] boolValue];
182 | if (self.tabBarController.tabBar.hidden) {
183 | [self.tabBarController.tabBar setHidden:hidden];
184 | }
185 | }
186 | }
187 |
188 | - (FlutterEngine *)dStackFlutterEngine
189 | {
190 | return [DStack sharedInstance].engine;
191 | }
192 |
193 | - (void)dealloc
194 | {
195 | [[NSNotificationCenter defaultCenter] removeObserver:self];
196 | }
197 |
198 | @end
199 |
--------------------------------------------------------------------------------
/ios/Classes/Stack/DNavigator.h:
--------------------------------------------------------------------------------
1 | //
2 | // DNavigator.m
3 | // d_stack
4 | //
5 | // Created by TAL on 2020/2/3.
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | /// 该类主要用于拦截所有push、present、pop、dismiss等事件
13 | /// 用于全链路全节点记录
14 | @interface UIViewController (DStackFullscreenPopGestureCategory)
15 |
16 | /// 是否开启手势返回
17 | @property (nonatomic, assign) BOOL dStack_interactivePopDisabled;
18 |
19 | - (void)removeGesturePopNode;
20 |
21 | @end
22 |
23 | NS_ASSUME_NONNULL_END
24 |
--------------------------------------------------------------------------------
/ios/Classes/Stack/DStackPlugin.h:
--------------------------------------------------------------------------------
1 | //
2 | // DStackPlugin.h
3 | // plugin channel
4 | //
5 | // Created by TAL on 2020/1/19.
6 | //
7 |
8 | #import
9 |
10 | NS_ASSUME_NONNULL_BEGIN
11 |
12 | typedef NSString *DStackMethodChannelName;
13 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelPlatformVersion; // 获取版本号
14 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendNodeToNative; // flutter发送节点至native
15 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendActionToFlutter; // native发送跳转指令至flutter
16 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendRemoveFlutterPageNode; // flutter发送移除节点的指令到native
17 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendLifeCircle; // 生命周期通道
18 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendNodeList; // 节点列表
19 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendFlutterRootNode; // 设置flutter根节点
20 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendOperationNodeToFlutter; // Operation节点
21 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendSendHomePageRoute; // 发送homePageRoute
22 | UIKIT_EXTERN DStackMethodChannelName const DStackMethodChannelSendSendUpdateBoundaryNode; // 更新临界节点信息
23 |
24 | @interface DStackPlugin : NSObject
25 |
26 | + (instancetype)sharedInstance;
27 |
28 | + (instancetype)new NS_UNAVAILABLE;
29 | - (instancetype)init NS_UNAVAILABLE;
30 |
31 | - (void)invokeMethod:(NSString *_Nullable)method
32 | arguments:(id _Nullable)arguments
33 | result:(FlutterResult _Nullable)callback;
34 | @end
35 |
36 | NS_ASSUME_NONNULL_END
37 |
--------------------------------------------------------------------------------
/ios/Classes/Stack/DStackPlugin.m:
--------------------------------------------------------------------------------
1 | //
2 | // DStackPlugin.m
3 | // plugin channel
4 | //
5 | // Created by TAL on 2020/1/19.
6 | //
7 |
8 | #import "DStackPlugin.h"
9 | #import "DStack.h"
10 |
11 | DStackMethodChannelName const DStackMethodChannelPlatformVersion = @"getPlatformVersion";
12 | DStackMethodChannelName const DStackMethodChannelSendNodeToNative = @"sendNodeToNative";
13 | DStackMethodChannelName const DStackMethodChannelSendActionToFlutter = @"sendActionToFlutter";
14 | DStackMethodChannelName const DStackMethodChannelSendRemoveFlutterPageNode = @"sendRemoveFlutterPageNode";
15 | DStackMethodChannelName const DStackMethodChannelSendLifeCircle = @"sendLifeCycle";
16 | DStackMethodChannelName const DStackMethodChannelSendNodeList = @"sendNodeList";
17 | DStackMethodChannelName const DStackMethodChannelSendFlutterRootNode = @"sendFlutterRootNode";
18 | DStackMethodChannelName const DStackMethodChannelSendOperationNodeToFlutter = @"sendOperationNodeToFlutter";
19 | DStackMethodChannelName const DStackMethodChannelSendSendHomePageRoute = @"sendHomePageRoute";
20 | DStackMethodChannelName const DStackMethodChannelSendSendUpdateBoundaryNode = @"sendUpdateBoundaryNode";
21 |
22 | @interface DStackPlugin ()
23 |
24 | @property (nonatomic, strong) FlutterMethodChannel *methodChannel;
25 |
26 | @end
27 |
28 | @implementation DStackPlugin
29 | + (void)registerWithRegistrar:(NSObject*)registrar {
30 |
31 | FlutterMethodChannel* channel = [FlutterMethodChannel
32 | methodChannelWithName:@"d_stack"
33 | binaryMessenger:registrar.messenger];
34 |
35 | DStackPlugin* instance = [DStackPlugin sharedInstance];
36 | instance.methodChannel = channel;
37 | [registrar addMethodCallDelegate:instance channel:channel];
38 | }
39 |
40 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
41 | DStackLog(@"收到Flutter【%@】的消息\n参数:%@", call.method, call.arguments);
42 | if ([DStackMethodChannelPlatformVersion isEqualToString:call.method]) {
43 | NSString *platform = [@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]];
44 | result(platform);
45 | } else if ([DStackMethodChannelSendNodeToNative isEqualToString:call.method]) {
46 | [self handleSendNodeToNativeMessage:call result:result];
47 | } else if ([DStackMethodChannelSendRemoveFlutterPageNode isEqualToString:call.method]) {
48 | DStack *stack = [DStack sharedInstance];
49 | if ([stack respondsToSelector:@selector(handleRemoveFlutterPageNode:result:)]) {
50 | [stack handleRemoveFlutterPageNode:call result:result];
51 | }
52 | } else if ([DStackMethodChannelSendNodeList isEqualToString:call.method]) {
53 | DStack *stack = [DStack sharedInstance];
54 | if ([stack respondsToSelector:@selector(sendNodeListToFlutter:)]) {
55 | [stack sendNodeListToFlutter:result];
56 | }
57 | } else if ([DStackMethodChannelSendSendHomePageRoute isEqualToString:call.method]) {
58 | DStack *stack = [DStack sharedInstance];
59 | if ([stack respondsToSelector:@selector(sendHomePageRoute:)]) {
60 | [stack sendHomePageRoute:call];
61 | }
62 | } else if ([DStackMethodChannelSendSendUpdateBoundaryNode isEqualToString:call.method]) {
63 | DStack *stack = [DStack sharedInstance];
64 | if ([stack respondsToSelector:@selector(updateBoundaryNode:)]) {
65 | [stack updateBoundaryNode:call];
66 | }
67 | } else {
68 | result(FlutterMethodNotImplemented);
69 | }
70 | }
71 |
72 | - (void)handleSendNodeToNativeMessage:(FlutterMethodCall*)call result:(FlutterResult)result
73 | {
74 | DStack *stack = [DStack sharedInstance];
75 | if ([stack conformsToProtocol:@protocol(DStackPluginProtocol)]) {
76 | if ([stack respondsToSelector:@selector(handleSendNodeToNativeMessage:result:)]) {
77 | [stack handleSendNodeToNativeMessage:call result:result];
78 | }
79 | }
80 | }
81 |
82 | - (void)invokeMethod:(NSString*)method
83 | arguments:(id _Nullable)arguments
84 | result:(FlutterResult _Nullable)callback
85 | {
86 | DStackPlugin *instance = [DStackPlugin sharedInstance];
87 | [instance.methodChannel invokeMethod:method arguments:arguments result:callback];
88 | }
89 |
90 | + (instancetype)sharedInstance
91 | {
92 | static id _instance = nil;
93 | static dispatch_once_t onceToken;
94 | dispatch_once(&onceToken, ^{
95 | _instance = [self.class new];
96 | });
97 | return _instance;
98 | }
99 |
100 | @end
101 |
--------------------------------------------------------------------------------
/ios/Classes/Stack/DStackProvider.h:
--------------------------------------------------------------------------------
1 | //
2 | // DStackProvider.h
3 | // 定义
4 | //
5 | // Created by TAL on 2020/1/16.
6 | //
7 |
8 | #ifndef DStackProvider_h
9 | #define DStackProvider_h
10 |
11 | #import
12 |
13 | FOUNDATION_EXPORT void _dStackLog(NSString *msg, NSString *format, ...);
14 | #define DStackLog(format, ...) (_dStackLog(@"", format, ## __VA_ARGS__))
15 | #define DStackError(format, ...) (_dStackLog(@"!!!!!!!!! DstackError !!!!!!!!! ==> ", format, ## __VA_ARGS__))
16 |
17 | #define DStackInject(_className_) \
18 | class DStackInjectClass; \
19 | char * __DStackInject##_className_##Char \
20 | __attribute__ ((used, section("__DATA, __DStackEInject "))) = ""#_className_""
21 |
22 |
23 | #define DStackDeprecated(msg) __attribute__((deprecated(msg)))
24 |
25 |
26 | // 页面类型
27 | typedef NS_ENUM(NSInteger, DNodePageType) {
28 | DNodePageTypeUnknow,
29 | DNodePageTypeNative, // 原生页面
30 | DNodePageTypeFlutter, // Flutter页面
31 | };
32 |
33 | // 跳转类型
34 | typedef NS_ENUM(NSInteger, DNodeActionType) {
35 | DNodeActionTypeUnknow,
36 | DNodeActionTypePush, // push跳转
37 | DNodeActionTypePresent, // present跳转
38 | DNodeActionTypePop, // pop返回
39 | DNodeActionTypePopTo, // popTo返回
40 | DNodeActionTypePopToRoot, // popToRoot
41 | DNodeActionTypePopSkip, // dopSkip
42 | DNodeActionTypeGesture, // 手势
43 | DNodeActionTypeDismiss, // dismiss返回
44 | DNodeActionTypeReplace, // replace返回
45 | DNodeActionTypeDidPop, // didPop确认
46 | DNodeActionTypePushAndRemoveUntil, // pushAndRemoveUntil
47 | };
48 |
49 | // 应用的生命周期
50 | typedef NS_ENUM(NSInteger, DStackApplicationState) {
51 | DStackApplicationStateStart, // 应用启动
52 | DStackApplicationStateForeground, // 进入前台
53 | DStackApplicationStateBackground, // 进入后台
54 | };
55 |
56 |
57 | @protocol DStackPluginProtocol
58 |
59 | @required
60 | /// 处理Flutter发送至nnative的消息
61 | /// @param call call
62 | /// @param result result
63 | - (void)handleSendNodeToNativeMessage:(FlutterMethodCall*)call
64 | result:(FlutterResult)result;
65 |
66 | /// 处理flutter发送过来的移除节点信息
67 | /// @param call call
68 | /// @param result result
69 | - (void)handleRemoveFlutterPageNode:(FlutterMethodCall*)call
70 | result:(FlutterResult)result;
71 |
72 | /// 发送节点列表到flutter
73 | /// @param result result
74 | - (void)sendNodeListToFlutter:(FlutterResult)result;
75 |
76 | /// 发送homePageRoute
77 | /// @param call call
78 | - (void)sendHomePageRoute:(FlutterMethodCall *)call;
79 |
80 | /// 更新临界节点信息
81 | /// @param call call
82 | - (void)updateBoundaryNode:(FlutterMethodCall *)call;
83 |
84 | @end
85 |
86 | #endif /* DStackProvider_h */
87 |
--------------------------------------------------------------------------------
/ios/d_stack.podspec:
--------------------------------------------------------------------------------
1 | #
2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
3 | # Run `pod lib lint d_stack.podspec' to validate before publishing.
4 | #
5 | Pod::Spec.new do |s|
6 | s.name = 'd_stack'
7 | s.version = '0.0.1'
8 | s.summary = 'A plugin solve the problem of native mix flutter.'
9 | s.description = <<-DESC
10 | A plugin solve the problem of native mix flutter.
11 | DESC
12 | s.homepage = 'http://example.com'
13 | s.license = { :file => '../LICENSE' }
14 | s.author = { 'Your Company' => 'email@example.com' }
15 | s.source = { :path => '.' }
16 | s.source_files = 'Classes/**/*'
17 | s.public_header_files = 'Classes/**/*.h'
18 | s.dependency 'Flutter'
19 | s.platform = :ios, '8.0'
20 |
21 | # Flutter.framework does not contain a i386 slice. Only x86_64 simulators are supported.
22 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'VALID_ARCHS[sdk=iphonesimulator*]' => 'x86_64' }
23 | end
24 |
--------------------------------------------------------------------------------
/lib/channel/dchannel.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Created with Android Studio.
3 | * User: whqfor
4 | * Date: 2020-02-03
5 | * Time: 14:20
6 | * email: wanghuaqiang@tal.com
7 | * tartget: 处理通道相关
8 | */
9 |
10 | import 'dart:async';
11 |
12 | import 'package:d_stack/constant/constant_config.dart';
13 | import 'package:d_stack/navigator/dnavigator_manager.dart';
14 | import 'package:d_stack/observer/d_node_observer.dart';
15 | import 'package:d_stack/observer/life_cycle_observer.dart';
16 | import 'package:flutter/services.dart';
17 |
18 | import '../d_stack.dart';
19 |
20 | class DChannel {
21 | late MethodChannel _methodChannel;
22 |
23 | DChannel(MethodChannel methodChannel) {
24 | _methodChannel = methodChannel;
25 | _methodChannel.setMethodCallHandler((MethodCall call) {
26 | // sendActionToFlutter 处理Native发过来的指令
27 | if (DStackConstant.nodeToFlutter == call.method) {
28 | return DNavigatorManager.handleActionToFlutter(call.arguments)!;
29 | } else if (DStackConstant.lifeCycle == call.method) {
30 | return LifeCycleHandler.handleLifecycleMessage(call.arguments);
31 | } else if (DStackConstant.sendOperationNodeToFlutter == call.method) {
32 | return DNodeObserverHandler.handlerNodeMessage(call.arguments);
33 | }
34 | return Future.value();
35 | });
36 | }
37 |
38 | Future invokeMethod(String method, [dynamic arguments]) async {
39 | return _methodChannel.invokeMethod(method, arguments);
40 | }
41 |
42 | Future sendNodeToNative(Map arguments) async {
43 | assert(arguments != null);
44 | return _methodChannel.invokeMethod(DStackConstant.nodeToNative, arguments);
45 | }
46 |
47 | Future sendRemoveFlutterPageNode(Map arguments) async {
48 | assert(arguments != null);
49 | return _methodChannel.invokeMethod(DStackConstant.checkRemoved, arguments);
50 | }
51 |
52 | Future> getNodeList() async {
53 | return _methodChannel
54 | .invokeMethod(DStackConstant.nodeList, null)
55 | .then((list) {
56 | if (list is List) {
57 | List nodeList = [];
58 | list.forEach((element) {
59 | DStackNode node = DStackNode(
60 | route: element["route"], pageType: element["pageType"]);
61 | nodeList.add(node);
62 | });
63 | return Future.value(nodeList);
64 | }
65 | return Future.value([]);
66 | });
67 | }
68 |
69 | Future sendHomePageRoute(String? route) {
70 | return _methodChannel.invokeMethod(
71 | DStackConstant.sendHomePageRoute, {"homePageRoute": route});
72 | }
73 |
74 | /// 发送更新临界节点的信息
75 | Future sendUpdateBoundaryNode(Map params) {
76 | return _methodChannel.invokeMethod(
77 | DStackConstant.sendUpdateBoundaryNode, params);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/lib/constant/constant_config.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Created with Android Studio.
3 | * User: linkewen
4 | * Date: 2020/11/26
5 | * Time: 16:23
6 | * target: 静态配置
7 | */
8 |
9 | class DStackConstant {
10 | /// action类型
11 | static const String push = "push";
12 | static const String present = "present";
13 | static const String dismiss = "dismiss";
14 | static const String pop = "pop";
15 | static const String popTo = "popTo";
16 | static const String popToRoot = "popToRoot";
17 | static const String popSkip = "popSkip";
18 | static const String replace = "replace";
19 | static const String gesture = "gesture";
20 | static const String pushAndRemoveUntil = "pushAndRemoveUntil";
21 |
22 | /// channel通道
23 | static const String nodeToFlutter = "sendActionToFlutter";
24 | static const String nodeToNative = "sendNodeToNative";
25 | static const String checkRemoved = "sendRemoveFlutterPageNode";
26 | static const String lifeCycle = "sendLifeCycle";
27 | static const String nodeList = "sendNodeList";
28 | static const String sendFlutterRootNode = "sendFlutterRootNode";
29 | static const String sendOperationNodeToFlutter = 'sendOperationNodeToFlutter';
30 | static const String sendHomePageRoute = 'sendHomePageRoute';
31 | static const String sendUpdateBoundaryNode = 'sendUpdateBoundaryNode';
32 |
33 | /// 其他标识
34 | static const String nativeDidPopGesture = "nativeDidPopGesture";
35 | static const String flutterDialog = "flutterDialog";
36 | }
37 |
--------------------------------------------------------------------------------
/lib/d_stack.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Created with Android Studio.
3 | * User: whqfor
4 | * Date: 2020-02-03
5 | * Time: 14:20
6 | * email: wanghuaqiang@tal.com
7 | * tartget: plugin人口
8 | */
9 |
10 | import 'package:d_stack/channel/dchannel.dart';
11 | import 'package:d_stack/navigator/dnavigator_gesture_observer.dart';
12 | import 'package:d_stack/navigator/dnavigator_manager.dart';
13 | import 'package:d_stack/observer/d_node_observer.dart';
14 | import 'package:d_stack/observer/life_cycle_observer.dart';
15 | import 'package:flutter/material.dart';
16 | import 'package:flutter/services.dart';
17 |
18 | enum PageType { native, flutter }
19 |
20 | /// The type of transition to use when pushing/popping a route.
21 | ///
22 | /// [TransitionType.custom] must also provide a transition when used.
23 | enum TransitionType {
24 | native,
25 | nativeModal,
26 | inFromLeft,
27 | inFromTop,
28 | inFromRight,
29 | inFromBottom,
30 | fadeIn, // 渐变
31 | custom, // 自定义,需要传transitionsBuilder
32 | material,
33 | materialFullScreenDialog,
34 | cupertino,
35 | cupertinoFullScreenDialog,
36 | fadeOpaque, // 透明
37 | fadeAndScale, // 透明缩放
38 | none, // 无动画
39 | }
40 |
41 | const Duration defaultPushDuration = Duration(milliseconds: 300);
42 | const Duration defaultPopDuration = Duration(milliseconds: 250);
43 |
44 | typedef DStackWidgetBuilder = WidgetBuilder Function(Map? params);
45 | typedef AnimatedPageBuilder = AnimatedWidget Function(
46 | BuildContext context,
47 | Animation animation,
48 | Animation secondaryAnimation,
49 | WidgetBuilder widgetBuilder);
50 |
51 | typedef PushAnimationPageBuilder = AnimatedWidget Function(
52 | BuildContext context,
53 | Animation animation,
54 | Animation secondaryAnimation,
55 | Widget child);
56 |
57 | class DStack {
58 | static DChannel? _stackChannel;
59 | static final DStack _instance = DStack();
60 |
61 | static DStack get instance {
62 | final MethodChannel _methodChannel = MethodChannel("d_stack");
63 | _stackChannel = DChannel(_methodChannel);
64 | return _instance;
65 | }
66 |
67 | DChannel? get channel => _stackChannel;
68 |
69 | String? _homePageRoute;
70 |
71 | String? get homeRoute => _homePageRoute;
72 |
73 | set homePageRoute(String? route) {
74 | _homePageRoute = route;
75 | if (_homePageRoute != null) {
76 | channel!.sendHomePageRoute(_homePageRoute);
77 | }
78 | }
79 |
80 | /// navigatorKey
81 | final GlobalKey navigatorKey = GlobalKey();
82 |
83 | /// 路由observer
84 | final DStackNavigatorObserver dStackNavigatorObserver =
85 | DStackNavigatorObserver();
86 |
87 | /// 用来监听 应用生命周期
88 | DLifeCycleObserver? dLifeCycleObserver;
89 |
90 | /// 用来监听节点操作
91 | DNodeObserver? dNodeObserver;
92 |
93 | final Map _pageBuilders =
94 | {};
95 |
96 | /// 注册DStack
97 | /// builders 路由的builder
98 | /// observer 生命周期监听者
99 | void register(
100 | {Map? builders,
101 | DLifeCycleObserver? observer,
102 | DNodeObserver? nodeObserver}) {
103 | if (builders?.isNotEmpty == true) {
104 | _pageBuilders.addAll(builders!);
105 | }
106 | dLifeCycleObserver = observer;
107 | dNodeObserver = nodeObserver;
108 | }
109 |
110 | /// 获取一个 DStackWidgetBuilder
111 | /// pageName 路由
112 | DStackWidgetBuilder pageBuilder(String? pageName) {
113 | DStackWidgetBuilder? builder = _pageBuilders[pageName!];
114 | if (builder != null) {
115 | return builder;
116 | } else {
117 | throw Exception('not in the PageRoute');
118 | }
119 | }
120 |
121 | /// 获取节点列表
122 | Future> nodeList() => channel!.getNodeList();
123 |
124 | /// 推出一个页面
125 | /// routeName 路由名,
126 | /// pageType native或者flutter,
127 | /// params 参数
128 | /// animated 是否有进场动画
129 | static Future push(String routeName, PageType pageType,
130 | {Map? params, bool maintainState = true, bool animated = true}) {
131 | return DNavigatorManager.push(routeName, pageType,
132 | params: params, maintainState: maintainState, animated: animated);
133 | }
134 |
135 | /// 弹出一个页面
136 | /// animated 是否有进场动画
137 | static Future present(String routeName, PageType pageType,
138 | {Map? params, bool maintainState = true, bool animated = true}) {
139 | return DNavigatorManager.present(routeName, pageType,
140 | params: params, maintainState: maintainState, animated: animated);
141 | }
142 |
143 | static Future animatedFlutterPage(String routeName, {
144 | Map? params,
145 | TransitionType? transition,
146 | Duration transitionDuration = const Duration(milliseconds: 250),
147 | RouteTransitionsBuilder? transitionsBuilder,
148 | bool replace = false,
149 | bool clearStack = false
150 | }) {
151 | return DNavigatorManager.animatedFlutterPage(routeName,
152 | params: params,
153 | transition: transition,
154 | transitionDuration: transitionDuration,
155 | transitionsBuilder: transitionsBuilder,
156 | replace: replace,
157 | clearStack: clearStack
158 | );
159 | }
160 |
161 | /// 自定义进场动画
162 | /// animationBuilder 进场动画的builder
163 | /// pushDuration 进场时间
164 | /// popDuration 退场时间
165 | /// popGesture 是否支持手势返回
166 | /// 只有是popGesture为true并且
167 | /// MaterialApp(ThemeData(platform: TargetPlatform.iOS);
168 | /// popGesture 才有效
169 | static Future pushWithAnimation(
170 | String routeName,
171 | PageType pageType,
172 | PushAnimationPageBuilder animationBuilder, {
173 | Map? params,
174 | bool replace = false,
175 | bool popGesture = false,
176 | Duration pushDuration = defaultPushDuration,
177 | Duration popDuration = defaultPopDuration,
178 | }) {
179 | return DNavigatorManager.pushWithAnimation(
180 | routeName, pageType, animationBuilder,
181 | params: params,
182 | replace: replace,
183 | pushDuration: pushDuration,
184 | popDuration: popDuration,
185 | popGesture: popGesture);
186 | }
187 |
188 | /// 等同push
189 | /// builder 页面builder
190 | /// animated 是否有进场动画
191 | static Future pushBuild(
192 | String routeName, PageType pageType, WidgetBuilder builder,
193 | {Map? params,
194 | bool maintainState = true,
195 | bool fullscreenDialog = false,
196 | bool animated = true}) {
197 | return DNavigatorManager.pushBuild(routeName, pageType, builder,
198 | params: params,
199 | maintainState: maintainState,
200 | fullscreenDialog: fullscreenDialog,
201 | animated: animated);
202 | }
203 |
204 | /// 只支持flutter使用,替换flutter页面
205 | /// animated 是否有进场动画
206 | static Future replace(
207 | String routeName,
208 | PageType pageType, {
209 | Map? params,
210 | bool maintainState = true,
211 | bool fullscreenDialog = false,
212 | bool animated = true,
213 | bool homePage = false,
214 | }) {
215 | return DNavigatorManager.replace(routeName, pageType,
216 | params: params,
217 | maintainState: maintainState,
218 | homePage: homePage,
219 | animated: animated,
220 | fullscreenDialog: fullscreenDialog);
221 | }
222 |
223 | /// 跳转指定页面并清除剩余所有页面
224 | static pushAndRemoveUntil(
225 | String routeName,
226 | PageType pageType, {
227 | Map? params,
228 | bool maintainState = true,
229 | bool fullscreenDialog = false,
230 | bool animated = true,
231 | bool homePage = false,
232 | }) {
233 | return DNavigatorManager.pushAndRemoveUntil(routeName, pageType,
234 | params: params,
235 | maintainState: maintainState,
236 | homePage: homePage,
237 | animated: animated,
238 | fullscreenDialog: fullscreenDialog);
239 | }
240 |
241 | /// pop
242 | /// 可以不传路由信息
243 | /// result 返回值,可为空
244 | static void pop({Map? result, bool animated = true}) {
245 | DNavigatorManager.pop(result: result, animated: animated);
246 | }
247 |
248 | static Future maybePop({Map? result, bool animated = true}) {
249 | return DNavigatorManager.maybePop(result: result, animated: animated);
250 | }
251 |
252 | /// popTo指定页面
253 | /// 无法popTo到根页面
254 | /// 要popTo到根页面请调用popToRoot
255 | static void popTo(String routeName, PageType pageType,
256 | {Map? result, bool animated = true}) {
257 | DNavigatorManager.popTo(routeName, pageType,
258 | result: result, animated: animated);
259 | }
260 |
261 | /// pop同一组页面
262 | static void popSkip(String skipName, {Map? result, bool animated = true}) {
263 | DNavigatorManager.popSkip(skipName, result: result, animated: animated);
264 | }
265 |
266 | /// 关闭一个页面
267 | /// 如果是push进入的,等同pop
268 | /// 如果是present进入的,效果是从上往下缩回去
269 | static void dismiss({Map? result, bool animated = true}) {
270 | DNavigatorManager.dismiss(result: result, animated: animated);
271 | }
272 |
273 | /// 回到根页面
274 | static void popToRoot({bool animated = true}) {
275 | DNavigatorManager.popToRoot(animated: animated);
276 | }
277 |
278 | @Deprecated('已废弃,请调用popToRoot')
279 | static void popToNativeRoot() {
280 | DNavigatorManager.popToRoot();
281 | }
282 |
283 | /// 自定义转场动画进入页面
284 | /// flutter页面通过animatedBuilder自定义动画
285 | /// native页面会转发到native,由native自行接入实现
286 | /// replace:true flutter的pushReplacement实现
287 | /// replace:false flutter的push实现
288 | @Deprecated('已废弃,请使用pushWithAnimation')
289 | static Future animationPage(
290 | String routeName,
291 | PageType pageType,
292 | AnimatedPageBuilder animatedBuilder, {
293 | Map? params,
294 | Duration? transitionDuration,
295 | bool opaque = true,
296 | bool barrierDismissible = false,
297 | Color? barrierColor,
298 | String? barrierLabel,
299 | bool maintainState = true,
300 | bool fullscreenDialog = false,
301 | bool replace = false,
302 | }) {
303 | return DNavigatorManager.animationPage(
304 | routeName,
305 | pageType,
306 | animatedBuilder,
307 | params,
308 | transitionDuration,
309 | opaque,
310 | barrierDismissible,
311 | barrierColor,
312 | barrierLabel,
313 | maintainState,
314 | fullscreenDialog,
315 | replace);
316 | }
317 | }
318 |
319 | class DStackNode {
320 | final String? route;
321 | final String? pageType;
322 |
323 | DStackNode({this.route, this.pageType});
324 | }
325 |
--------------------------------------------------------------------------------
/lib/navigator/dnavigator_gesture_observer.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Created with Android Studio.
3 | * User: whqfor
4 | * Date: 2020-02-03
5 | * Time: 14:20
6 | * email: wanghuaqiang@tal.com
7 | * tartget: 拦截flutter手势,生成节点信息,然后将节点信息发送到native侧
8 | */
9 |
10 | import 'package:d_stack/constant/constant_config.dart';
11 | import 'package:d_stack/d_stack.dart';
12 | import 'package:flutter/material.dart';
13 |
14 | import 'dnavigator_manager.dart';
15 |
16 | // 路由监听
17 | class DStackNavigatorObserver extends NavigatorObserver {
18 | // 单例
19 | factory DStackNavigatorObserver() => _getInstance()!;
20 |
21 | static DStackNavigatorObserver? get instance => _getInstance();
22 | static DStackNavigatorObserver? _instance;
23 | // 避免过度pop
24 | int routerCount = 0;
25 |
26 | DStackNavigatorObserver._internal();
27 |
28 | static DStackNavigatorObserver? _getInstance() {
29 | if (_instance == null) {
30 | _instance = new DStackNavigatorObserver._internal();
31 | }
32 | return _instance;
33 | }
34 |
35 | // 标识手势引起的pop事件
36 | String? _gesturingRouteName;
37 | String? get gesturingRouteName => this._gesturingRouteName;
38 | void setGesturingRouteName(String? gesturingRouteName) {
39 | this._gesturingRouteName = gesturingRouteName;
40 | }
41 |
42 | Route? _currentRoute;
43 | Route? get currentRoute => _currentRoute;
44 |
45 | /// 页面进入了
46 | /// route 路由目标页面
47 | /// previousRoute 目标页面的上一个页面
48 | @override
49 | void didPush(Route route, Route? previousRoute) {
50 | super.didPush(route, previousRoute);
51 | debugPrint(
52 | ' 【didPush】${route.settings.name}【didPush】');
53 | routerCount += 1;
54 | _currentRoute = route;
55 | if (route is PopupRoute) {
56 | /// dialog进栈
57 | DNavigatorManager.nodeHandle(
58 | DStackConstant.flutterDialog, PageType.flutter, DStackConstant.push,
59 | route: route);
60 | }
61 | }
62 |
63 | /// 页面退出了(手势返回也会走这个方法)
64 | /// route 当前操作页面
65 | /// previousRoute 操作页面的上一个页面
66 | @override
67 | void didPop(Route route, Route? previousRoute) {
68 | super.didPop(route, previousRoute);
69 | routerCount -= 1;
70 | _currentRoute = previousRoute;
71 | debugPrint(
72 | ' 【didPop】${route.settings.name} 【didPop】');
73 | if (route is PopupRoute) {
74 | /// dialog出栈
75 | DNavigatorManager.removeFlutterNode(DStackConstant.flutterDialog,
76 | identifier: DNavigatorManager.identifierWithRoute(route));
77 | } else {
78 | if (gesturingRouteName != null &&
79 | gesturingRouteName == route.settings.name) {
80 | // 由手势导致的pop事件
81 | DNavigatorManager.popWithGesture(route);
82 | } else if (gesturingRouteName != null &&
83 | gesturingRouteName == DStackConstant.nativeDidPopGesture) {
84 | // native手势引起的didPop,native侧已经删除节点,flutter侧不再removeFlutterNode
85 | DStackNavigatorObserver.instance!.setGesturingRouteName(null);
86 | } else {
87 | if (route.settings.name != null) {
88 | DNavigatorManager.removeFlutterNode(route.settings.name,
89 | identifier: DNavigatorManager.identifierWithRoute(route));
90 | }
91 | }
92 | }
93 | }
94 |
95 | @override
96 | void didReplace({Route? newRoute, Route? oldRoute}) {
97 | super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
98 | _currentRoute = newRoute;
99 | }
100 |
101 | /// route 路由目标页面
102 | /// previousRoute 目标页面的上一个页面
103 | // 滑动手势开始
104 | @override
105 | void didStartUserGesture(Route route, Route? previousRoute) {
106 | super.didStartUserGesture(route, previousRoute);
107 | DStackNavigatorObserver.instance!
108 | .setGesturingRouteName(route.settings.name);
109 | }
110 |
111 | // 滑动手势结束
112 | @override
113 | void didStopUserGesture() {
114 | super.didStopUserGesture();
115 | DStackNavigatorObserver.instance!.setGesturingRouteName(null);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/lib/navigator/node_entity.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Created with Android Studio.
3 | * User: linkewen
4 | * Date: 2020/12/3
5 | * Time: 17:18
6 | * target: node节点描述
7 | */
8 |
9 | import 'package:d_stack/d_stack.dart';
10 |
11 | class DNodeEntity {
12 | /// 节点列表
13 | late List nodeList;
14 |
15 | /// 跳转类型
16 | String? action;
17 |
18 | /// 是否有转场动画
19 | bool? animated;
20 |
21 | DNodeEntity.fromJson(Map json) {
22 | if (json["nodes"] != null) {
23 | final List list = [];
24 | (json["nodes"] as List).forEach((element) {
25 | list.add(DNode.fromJson(element));
26 | });
27 | nodeList = list;
28 | action = json["action"] ?? "";
29 | animated = json["animated"] ?? true;
30 | }
31 | }
32 |
33 | Map toJson() {
34 | final Map json = Map();
35 | List list = [];
36 | this.nodeList.forEach((element) {
37 | list.add(element.toJson());
38 | });
39 | json["nodes"] = list;
40 | json["action"] = this.action;
41 | json["animated"] = this.animated;
42 | return json;
43 | }
44 | }
45 |
46 | class DNode {
47 | /// 页面路由
48 | String? target;
49 |
50 | /// 跳转类型
51 | String? action;
52 |
53 | /// 携带参数
54 | Map? params;
55 |
56 | /// 页面类型
57 | PageType? pageType;
58 |
59 | /// 是否为homePage
60 | bool? homePage;
61 |
62 | /// 是否有转场动画
63 | bool? animated;
64 |
65 | /// 是否为临界节点
66 | bool? boundary;
67 |
68 | /// 页面唯一id
69 | String? identifier;
70 |
71 | DNode.fromJson(Map json) {
72 | target = json["target"];
73 | action = json["action"];
74 | params = json["params"];
75 | homePage = json["homePage"] ?? false;
76 | animated = json["animated"] ?? true;
77 | boundary = json["boundary"] ?? false;
78 | identifier = json["identifier"];
79 | String pageString = json["pageType"];
80 | if (pageString.toLowerCase() == "flutter") {
81 | pageType = PageType.flutter;
82 | } else if (pageString.toLowerCase() == "native") {
83 | pageType = PageType.native;
84 | }
85 | }
86 |
87 | Map toJson() {
88 | final Map json = Map();
89 | json["target"] = this.target;
90 | json["action"] = this.action;
91 | json["params"] = this.params;
92 | json["homePage"] = this.homePage;
93 | json["boundary"] = this.boundary;
94 | json["animated"] = this.animated;
95 | json["identifier"] = this.identifier;
96 | switch (this.pageType) {
97 | case PageType.flutter:
98 | {
99 | json["pageType"] = "flutter";
100 | break;
101 | }
102 | case PageType.native:
103 | {
104 | json["pageType"] = "native";
105 | break;
106 | }
107 | }
108 | return json;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/lib/observer/d_node_observer.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Created with Android Studio.
3 | * User: whqfor
4 | * Date: 12/5/20
5 | * Time: 6:04 PM
6 | * target: 监听节点的observer
7 | */
8 |
9 | import 'package:d_stack/d_stack.dart';
10 |
11 | abstract class DNodeObserver {
12 | /// 用户操作的所有行为都将会从这个api传出,可以基于此做行为回放
13 | /// 将要进行操作的节点
14 | void operationNode(Map? node);
15 | }
16 |
17 | class DNodeObserverHandler {
18 | static handlerNodeMessage(Map? node) {
19 | DStack.instance.dNodeObserver?.operationNode(node);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lib/observer/life_cycle_observer.dart:
--------------------------------------------------------------------------------
1 | /*
2 | * Created with Android Studio.
3 | * User: sunjian
4 | * Date: 2020/6/29
5 | * Time: 7:45 PM
6 | * target: 应用的生命周期
7 | */
8 |
9 | import 'package:d_stack/d_stack.dart';
10 |
11 | /// 应用的生命周期 创建 前台 后台
12 | enum DLifeCycleState { create, foreground, background }
13 |
14 | class PageModel {
15 | String? currentPageRoute; // 当前展示的页面路由
16 | String? prePageRoute; // 前一个展示的页面路由,可能为null
17 | String? currentPageType; // 页面类型:Flutter/Native
18 | String? prePageType; // 页面类型:Flutter/Native
19 | String? actionType; // 操作类型:push/pop
20 |
21 | PageModel(
22 | {this.currentPageRoute,
23 | this.prePageRoute,
24 | this.currentPageType,
25 | this.prePageType,
26 | this.actionType});
27 |
28 | @override
29 | String toString() {
30 | return "{'currentPageRoute':$currentPageRoute,'prePageRoute':$prePageRoute,"
31 | "'currentPageType':$currentPageType,'prePageType':$prePageType,"
32 | "'actionType':$actionType}";
33 | }
34 | }
35 |
36 | abstract class DLifeCycleObserver {
37 | void appDidStart(PageModel model); // 创建 对应 create
38 |
39 | void appDidEnterForeground(PageModel model); // 前台 对应foreground
40 |
41 | void appDidEnterBackground(PageModel model); // 后台 对应background
42 |
43 | void pageAppear(PageModel model); // 页面push/pop会调用 .
44 | }
45 |
46 | /// 处理页面生命周期
47 | class LifeCycleHandler {
48 | static handleLifecycleMessage(Map arguments) {
49 | Map? pageParams = arguments['page'];
50 | Map? appParams = arguments['application'];
51 | if (pageParams != null) {
52 | String? appearRoute = pageParams['appearRoute'];
53 | String? disappearRoute = pageParams['disappearRoute'];
54 | String? appearPageType = pageParams['appearPageType'];
55 | String? disappearPageType = pageParams['disappearPageType'];
56 | String? actionType = pageParams['actionType'];
57 | PageModel model = PageModel(
58 | currentPageRoute: appearRoute,
59 | prePageRoute: disappearRoute,
60 | currentPageType: appearPageType,
61 | prePageType: disappearPageType,
62 | actionType: actionType);
63 | DStack.instance.dLifeCycleObserver?.pageAppear(model);
64 | }
65 | if (appParams != null) {
66 | String? currentRoute = appParams['currentRoute'];
67 | String? pageType = appParams['pageType'];
68 | DLifeCycleState state = DLifeCycleState.values[appParams['state']];
69 | PageModel model =
70 | PageModel(currentPageRoute: currentRoute, currentPageType: pageType);
71 | switch (state) {
72 | case DLifeCycleState.create:
73 | DStack.instance.dLifeCycleObserver?.appDidStart(model);
74 | break;
75 | case DLifeCycleState.foreground:
76 | DStack.instance.dLifeCycleObserver?.appDidEnterForeground(model);
77 | break;
78 | case DLifeCycleState.background:
79 | DStack.instance.dLifeCycleObserver?.appDidEnterBackground(model);
80 | break;
81 | }
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/lib/widget/home_widget.dart:
--------------------------------------------------------------------------------
1 | import 'package:d_stack/d_stack.dart';
2 | import 'package:flutter/material.dart';
3 |
4 | /*
5 | * DStack里面的homePage实现
6 | * 在MaterialApp的home里面设置该widget
7 | * 当工程是flutter为主工程时,设置home需要把工程中实际的homePage设置进去,比如DStackWidget(homePage: MyHomePage());
8 | * 当工程是native为主工程时,设置home时直接设置成DStackWidget(),不要填写homePage
9 | * */
10 | /// DStack入口Widget
11 | class DStackWidget extends StatelessWidget {
12 | /// 默认的homePage
13 | final Widget? homePage;
14 |
15 | /// homepage的route
16 | final String? homePageRoute;
17 |
18 | DStackWidget({Key? key, this.homePage, this.homePageRoute}) : super(key: key);
19 |
20 | @override
21 | Widget build(BuildContext context) {
22 | DStack.instance.homePageRoute = homePageRoute;
23 | return homePage ?? Container(color: Colors.white);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | async:
5 | dependency: transitive
6 | description:
7 | name: async
8 | url: "https://pub.flutter-io.cn"
9 | source: hosted
10 | version: "2.8.1"
11 | boolean_selector:
12 | dependency: transitive
13 | description:
14 | name: boolean_selector
15 | url: "https://pub.flutter-io.cn"
16 | source: hosted
17 | version: "2.1.0"
18 | characters:
19 | dependency: transitive
20 | description:
21 | name: characters
22 | url: "https://pub.flutter-io.cn"
23 | source: hosted
24 | version: "1.1.0"
25 | charcode:
26 | dependency: transitive
27 | description:
28 | name: charcode
29 | url: "https://pub.flutter-io.cn"
30 | source: hosted
31 | version: "1.3.1"
32 | clock:
33 | dependency: transitive
34 | description:
35 | name: clock
36 | url: "https://pub.flutter-io.cn"
37 | source: hosted
38 | version: "1.1.0"
39 | collection:
40 | dependency: transitive
41 | description:
42 | name: collection
43 | url: "https://pub.flutter-io.cn"
44 | source: hosted
45 | version: "1.15.0"
46 | fake_async:
47 | dependency: transitive
48 | description:
49 | name: fake_async
50 | url: "https://pub.flutter-io.cn"
51 | source: hosted
52 | version: "1.2.0"
53 | flutter:
54 | dependency: "direct main"
55 | description: flutter
56 | source: sdk
57 | version: "0.0.0"
58 | flutter_test:
59 | dependency: "direct dev"
60 | description: flutter
61 | source: sdk
62 | version: "0.0.0"
63 | matcher:
64 | dependency: transitive
65 | description:
66 | name: matcher
67 | url: "https://pub.flutter-io.cn"
68 | source: hosted
69 | version: "0.12.10"
70 | meta:
71 | dependency: transitive
72 | description:
73 | name: meta
74 | url: "https://pub.flutter-io.cn"
75 | source: hosted
76 | version: "1.7.0"
77 | path:
78 | dependency: transitive
79 | description:
80 | name: path
81 | url: "https://pub.flutter-io.cn"
82 | source: hosted
83 | version: "1.8.0"
84 | sky_engine:
85 | dependency: transitive
86 | description: flutter
87 | source: sdk
88 | version: "0.0.99"
89 | source_span:
90 | dependency: transitive
91 | description:
92 | name: source_span
93 | url: "https://pub.flutter-io.cn"
94 | source: hosted
95 | version: "1.8.1"
96 | stack_trace:
97 | dependency: transitive
98 | description:
99 | name: stack_trace
100 | url: "https://pub.flutter-io.cn"
101 | source: hosted
102 | version: "1.10.0"
103 | stream_channel:
104 | dependency: transitive
105 | description:
106 | name: stream_channel
107 | url: "https://pub.flutter-io.cn"
108 | source: hosted
109 | version: "2.1.0"
110 | string_scanner:
111 | dependency: transitive
112 | description:
113 | name: string_scanner
114 | url: "https://pub.flutter-io.cn"
115 | source: hosted
116 | version: "1.1.0"
117 | term_glyph:
118 | dependency: transitive
119 | description:
120 | name: term_glyph
121 | url: "https://pub.flutter-io.cn"
122 | source: hosted
123 | version: "1.2.0"
124 | test_api:
125 | dependency: transitive
126 | description:
127 | name: test_api
128 | url: "https://pub.flutter-io.cn"
129 | source: hosted
130 | version: "0.4.2"
131 | typed_data:
132 | dependency: transitive
133 | description:
134 | name: typed_data
135 | url: "https://pub.flutter-io.cn"
136 | source: hosted
137 | version: "1.3.0"
138 | vector_math:
139 | dependency: transitive
140 | description:
141 | name: vector_math
142 | url: "https://pub.flutter-io.cn"
143 | source: hosted
144 | version: "2.1.0"
145 | sdks:
146 | dart: ">=2.12.0 <3.0.0"
147 | flutter: ">=2.0.0"
148 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: d_stack
2 | description: 混合栈
3 | version: 1.3.4+3-nullsafety
4 | author: peter
5 | homepage: https://github.com/tal-tech/d_stack
6 |
7 | environment:
8 | sdk: ">=2.12.0 <3.0.0"
9 | flutter: ">=2.0.0"
10 |
11 | dependencies:
12 | flutter:
13 | sdk: flutter
14 |
15 | dev_dependencies:
16 | flutter_test:
17 | sdk: flutter
18 |
19 | # For information on the generic Dart part of this file, see the
20 | # following page: https://dart.dev/tools/pub/pubspec
21 |
22 | # The following section is specific to Flutter.
23 | flutter:
24 | # This section identifies this Flutter project as a plugin project.
25 | # The 'pluginClass' and Android 'package' identifiers should not ordinarily
26 | # be modified. They are used by the tooling to maintain consistency when
27 | # adding or updating assets for this project.
28 | plugin:
29 | platforms:
30 | android:
31 | package: tal.com.d_stack
32 | pluginClass: DStackPlugin
33 | ios:
34 | pluginClass: DStackPlugin
35 |
36 | # To add assets to your plugin package, add an assets section, like this:
37 | # assets:
38 | # - images/a_dot_burr.jpeg
39 | # - images/a_dot_ham.jpeg
40 | #
41 | # For details regarding assets in packages, see
42 | # https://flutter.dev/assets-and-images/#from-packages
43 | #
44 | # An image asset can refer to one or more resolution-specific "variants", see
45 | # https://flutter.dev/assets-and-images/#resolution-aware.
46 |
47 | # To add custom fonts to your plugin package, add a fonts section here,
48 | # in this "flutter" section. Each entry in this list should have a
49 | # "family" key with the font family name, and a "fonts" key with a
50 | # list giving the asset and other descriptors for the font. For
51 | # example:
52 | # fonts:
53 | # - family: Schyler
54 | # fonts:
55 | # - asset: fonts/Schyler-Regular.ttf
56 | # - asset: fonts/Schyler-Italic.ttf
57 | # style: italic
58 | # - family: Trajan Pro
59 | # fonts:
60 | # - asset: fonts/TrajanPro.ttf
61 | # - asset: fonts/TrajanPro_Bold.ttf
62 | # weight: 700
63 | #
64 | # For details regarding fonts in packages, see
65 | # https://flutter.dev/custom-fonts/#from-packages
66 |
67 |
--------------------------------------------------------------------------------
/test/d_stack_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/services.dart';
2 | import 'package:flutter_test/flutter_test.dart';
3 |
4 | void main() {
5 | const MethodChannel channel = MethodChannel('d_stack');
6 |
7 | TestWidgetsFlutterBinding.ensureInitialized();
8 |
9 | setUp(() {
10 | channel.setMockMethodCallHandler((MethodCall methodCall) async {
11 | return '42';
12 | });
13 | });
14 |
15 | tearDown(() {
16 | channel.setMockMethodCallHandler(null);
17 | });
18 | }
19 |
--------------------------------------------------------------------------------