├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README-zh.md ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── cn │ └── com │ └── agree │ └── flutter │ └── miniprogram │ ├── MiniProgramPlugin.java │ ├── components │ └── WrappedJSContext.java │ └── handler │ └── JsContextHandler.java ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── cn │ │ │ │ └── com │ │ │ │ └── agree │ │ │ │ └── flutter │ │ │ │ └── miniprogram │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ ├── drawable │ │ │ ├── launch_background.xml │ │ │ └── launch_image.png │ │ │ ├── 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 │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── assets │ ├── app.json │ ├── images │ │ └── avatar.png │ └── page │ │ ├── button.aml │ │ ├── checkbox.aml │ │ ├── icon.aml │ │ ├── image.aml │ │ ├── index.aml │ │ ├── input.aml │ │ ├── jsapi.aml │ │ ├── slider.aml │ │ ├── switch.aml │ │ ├── text.aml │ │ ├── video.aml │ │ ├── view.aml │ │ └── webview.aml ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── 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 │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── main.dart │ └── src │ │ └── jsapi.dart ├── pubspec.yaml └── test │ ├── event_emitter_test.dart │ ├── function_test.dart │ ├── utils_test.dart │ └── widget_test.dart ├── flutter_mini_program.iml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── FlutterMiniProgramPlugin.h │ └── FlutterMiniProgramPlugin.m └── flutter_mini_program.podspec ├── lib ├── App.dart ├── EventEmitter.dart ├── HtmlParser.dart ├── Page.dart ├── PageParser.dart ├── StyleParser.dart ├── components │ ├── animation │ │ ├── fade_in.dart │ │ ├── index.dart │ │ ├── rotating.dart │ │ └── scale.dart │ ├── button │ │ └── index.dart │ ├── cell │ │ └── index.dart │ ├── form │ │ └── index.dart │ ├── icon │ │ └── index.dart │ ├── input │ │ └── index.dart │ ├── switch │ │ └── index.dart │ ├── theme.dart │ └── ui.dart ├── console │ └── JsonViewer.dart ├── engine │ └── JSContext.dart ├── tags │ ├── BreakTag.dart │ ├── ButtonTag.dart │ ├── CheckboxGroupTag.dart │ ├── HrTag.dart │ ├── IconTag.dart │ ├── ImageTag.dart │ ├── InputTag.dart │ ├── ListViewTag.dart │ ├── SliderTag.dart │ ├── SwitchTag.dart │ ├── TableTag.dart │ ├── TextTag.dart │ ├── VideoTag.dart │ ├── ViewTag.dart │ └── WebViewTag.dart └── utils │ ├── ConvertUtil.dart │ ├── ResourceUtil.dart │ └── WidgetUtil.dart └── pubspec.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | .DS_Store 3 | .idea/ 4 | 5 | # Files and directories created by pub 6 | .dart_tool/ 7 | .packages 8 | .pub/ 9 | build/ 10 | # If you're building an application, you may want to check-in your pubspec.lock 11 | pubspec.lock 12 | 13 | # Directory created by dartdoc 14 | # If you don't generate documentation locally you can remove this line. 15 | doc/api/ 16 | 17 | temp/ -------------------------------------------------------------------------------- /.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: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: beta 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.0.1 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 小青年 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. 22 | -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 | # Flutter 小程序 2 | 3 | 一个基于 Flutter 框架的小程序开发框架。通过解析 HTML 标签 和 CSS 样式,用于开发小程序应用,项目灵感来自 [FlutterHtmlView](https://github.com/PonnamKarthik/FlutterHtmlView)。 4 | 5 | ## 特性 6 | 7 | - 将 html 标签转换为 Flutter 组件 8 | - 支持使用 css 样式渲染 Flutter 组件 9 | - 支持模板编译及数据绑定 10 | 11 | ## 框架 12 | 13 | ### 视图层 14 | 15 | 框架的视图层由 html 与 css 编写,通过 .html 文件定义页面的视图层,由组件来进行展示。将逻辑层的数据反应成视图,同时将视图层的事件发送给逻辑层。组件(Component)是视图的基本组成单元。 16 | 17 | ```html 18 | 26 | 27 | 45 | 46 | 54 | ``` 55 | 56 | ### 逻辑层 57 | 58 | 小程序开发框架的逻辑层使用 JS / Dart 语法进行开发,通过 Dart / Flutter 插件完成视图逻辑的组织及原生插件调用。逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。 59 | 60 | ```dart 61 | Page({ 62 | config: { 63 | "navigationBarTitleText": "Flutter MiniProgram - JS-API" 64 | }, 65 | data: { 66 | message: "Hello Flutter's MiniProgram" 67 | }, 68 | onLoad() { 69 | 70 | }, 71 | methods: { 72 | onEvaluateJavascript: function() { 73 | log('onEvaluateJavascript'); 74 | } 75 | } 76 | }); 77 | ``` 78 | 79 | ## 组件 80 | 81 | ### 支持的组件 82 | 83 | - view: 视图容器 84 | - icon: 图标 85 | - text: 文本 86 | - br: 换行符 87 | - hr: 水平分隔线 88 | - p: 段落 89 | - h1 ~ h6: 标题 90 | - a: 链接 91 | - button: 按钮 92 | - input: 输入框 93 | - checkbox: 复选框 94 | - switch: 单选开关 95 | - slider: 滑块 96 | - image: 图片 97 | - video: 视频 98 | - table: 表格 99 | - list-view: 列表 100 | - web-view: 网页容器 101 | 102 | ### 视图 103 | 104 | 视图容器,相当于 Web 的 div 标签或者 React Native 的 View 组件。 105 | 106 | #### API 107 | 108 | | 属性名 | 类型 | 默认值 | 描述 | 109 | |----------|:-------------:|------:|:-------------:| 110 | | class | String | | 自定义样式名 | 111 | | style | String | | 内联样式 | 112 | | onTap | EventHandle | | 点击事件 | 113 | | onLongTap | EventHandle | | 长按事件 | 114 | 115 | ### Button 116 | 117 | 按钮。 118 | 119 | #### API 120 | 121 | | 属性名 | 类型 | 默认值 | 描述 | 122 | |----------|:-------------:|------:|:-------------:| 123 | | size | String | default | 按钮的大小 | 124 | | type | String | default | 按钮的样式类型 | 125 | | plain | Boolean | false | 按钮是否镂空,背景色透明 | 126 | | disabled | Boolean | false | 是否禁用 | 127 | | loading | Boolean | false | 名称前是否带 loading 图标 | 128 | 129 | #### 示例 130 | 131 | ```html 132 | 133 | 按钮 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | disabled 状态 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | loading 状态 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | plain 状态 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 按钮大小 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | ``` 189 | 190 | ### Switch 191 | 192 | 开关选择器。 193 | 194 | #### API 195 | 196 | | 属性名 | 类型 | 默认值 | 描述 | 197 | |----------|:-------------:|------:|:-------------:| 198 | | checked | Boolean | | 是否选中 | 199 | | disabled | Boolean | | 禁用状态 | 200 | | size | double | | 自定义大小 | 201 | | color | String | | 自定义颜色 | 202 | | onChange | EventHandle | | checked 改变时触发 | 203 | 204 | ### 文本输入框 205 | 206 | 文本输入框, 相当于 Web 的 input 标签 或者 iOS 中的 UITextField 和 Android 中的 EditText。 207 | 208 | | 属性名 | 类型 | 默认值 | 描述 | 209 | |----------|:-------------:|------:|:-------------:| 210 | | type | String | | input 的类型 | 211 | | placeholder | String | | 占位符 | 212 | | focus | Boolean | false | 获取焦点 | 213 | 214 | ```html 215 | 216 | 这是一个可以自动聚焦的input: 217 | 218 | 219 | 220 | 数字键盘: 221 | 222 | 223 | ``` 224 | 225 | ### 图片 226 | 227 | API 228 | 229 | | 属性名 | 类型 | 默认值 | 描述 | 230 | |----------|:-------------:|------:|:-------------:| 231 | | class | String | | 自定义样式名 | 232 | | style | String | | 内联样式 | 233 | | src | String | | 图片路径 | 234 | | fit | String | | 图片裁剪、缩放的模式, fill | contain | cover | fitWidth | fitHeight | none | scaleDown | 235 | 236 | ```html 237 | 238 |

网络图片:

239 | 241 |
242 | 243 |

本地图片:

244 | 245 |
246 | 247 |

图片裁剪模式 fit = "fill":

248 | 249 |
250 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文文档](./README-zh.md) 2 | 3 | # Flutter MiniProgram 4 | 5 | A Flutter's mini-program development framework by parsing HTML、CSS and JS / Dart. This Project is inspired by [FlutterHtmlView](https://github.com/PonnamKarthik/FlutterHtmlView). 6 | 7 | ## Features 8 | 9 | - Convert html tags to flutter widgets 10 | - Support use css to rendering flutter widgets 11 | - Support template compile and data binding 12 | 13 | ## Framework 14 | 15 | ### View layer 16 | 17 | The view layer of the framework is written by html and css, and the view layer of the page is defined by the .html file, which is displayed by the component. The data of the logical layer is reflected into a view, and the events of the view layer are sent to the logical layer. A Component is the basic building block of a view. 18 | 19 | ```html 20 | 28 | 29 | 47 | 48 | 56 | ``` 57 | 58 | ### Logic Layer 59 | 60 | The logic layer of the mini-program development framework is developed using JS / Dart syntax, and the organization of the view logic and native plugin calls are done through the Dart / Flutter plugin. The logic layer processes the data and sends it to the view layer, while accepting event feedback from the view layer. 61 | 62 | ```js 63 | Page({ 64 | config: { 65 | "navigationBarTitleText": "Flutter MiniProgram - JS-API" 66 | }, 67 | data: { 68 | message: "Hello Flutter's MiniProgram" 69 | }, 70 | onLoad() { 71 | 72 | }, 73 | methods: { 74 | onEvaluateJavascript: function() { 75 | log('onEvaluateJavascript'); 76 | } 77 | } 78 | }); 79 | ``` 80 | 81 | ## Component 82 | 83 | ### Supported Tags 84 | 85 | - view/div: 视图容器 86 | - icon: 图标 87 | - text: 文本 88 | - br: 换行符 89 | - hr: 水平分隔线 90 | - p: 段落 91 | - h1 ~ h6: 标题 92 | - a: 链接 93 | - button: 按钮 94 | - input: 输入框 95 | - checkbox: 复选框 96 | - switch: 单选开关 97 | - slider: 滑块 98 | - image: 图片 99 | - video: 视频 100 | - table: 表格 101 | - list-view: 列表 102 | - web-view: 网页容器 103 | 104 | ### View 105 | 106 | A view container, equivalent to the div tag of the Web or the View component of React Native. 107 | 108 | API 109 | 110 | | Attribute name | Types | Defaults | Description | 111 | |----------|:-------------:|------:|:-------------:| 112 | | class | String | | Custom style name | 113 | | style | String | | Inline style | 114 | | onTap | EventHandle | | Click event | 115 | | onLongTap | EventHandle | | Long press event | -------------------------------------------------------------------------------- /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 'cn.com.agree.flutter.miniprogram' 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | maven { url 'https://jitpack.io' } 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.2.1' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | maven { url 'https://jitpack.io' } 20 | } 21 | } 22 | 23 | apply plugin: 'com.android.library' 24 | 25 | android { 26 | compileSdkVersion 27 27 | 28 | defaultConfig { 29 | minSdkVersion 16 30 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 31 | } 32 | lintOptions { 33 | disable 'InvalidPackage' 34 | } 35 | compileOptions { 36 | sourceCompatibility 1.8 37 | targetCompatibility 1.8 38 | } 39 | } 40 | 41 | dependencies { 42 | implementation 'com.android.support:support-annotations:27.+' 43 | implementation 'com.android.support:support-v4:27.+' 44 | implementation 'com.github.LiquidPlayer:LiquidCore:0.6.0' 45 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_mini_program' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/java/cn/com/agree/flutter/miniprogram/MiniProgramPlugin.java: -------------------------------------------------------------------------------- 1 | package cn.com.agree.flutter.miniprogram; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONTokener; 5 | import org.liquidplayer.javascript.JSException; 6 | import org.liquidplayer.javascript.JSValue; 7 | 8 | import java.util.ArrayList; 9 | import java.util.HashMap; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import cn.com.agree.flutter.miniprogram.handler.JsContextHandler; 14 | import io.flutter.plugin.common.JSONUtil; 15 | import io.flutter.plugin.common.PluginRegistry.Registrar; 16 | 17 | public class MiniProgramPlugin { 18 | public final static String NAMESPACE = "io.jojodev.flutter.liquidcore"; 19 | 20 | /** 21 | * Plugin registration. 22 | */ 23 | public static void registerWith(Registrar registrar) { 24 | JsContextHandler.registerWith(registrar); 25 | } 26 | 27 | /** 28 | * Convert arguments to dart objects. 29 | * 30 | * @param args Object[] 31 | * @return Serializable arguments. 32 | * @see io.flutter.plugin.common.StandardMessageCodec#writeValue 33 | */ 34 | @SuppressWarnings("JavadocReference") 35 | public static List convertToDartObjects(Object... args) { 36 | List allowedArgs = new ArrayList<>(args.length); 37 | for (Object value : args) { 38 | allowedArgs.add(convertToDartObject(value)); 39 | } 40 | 41 | return allowedArgs; 42 | } 43 | 44 | /** 45 | * Convert objects into a format that can be easily digested by Dart. 46 | * 47 | * @param value the object to transform. 48 | * @return the transformed object. 49 | */ 50 | public static Object convertToDartObject(Object value) { 51 | if (value == null || value == Boolean.TRUE || value == Boolean.FALSE || 52 | value instanceof Number || value instanceof String || value instanceof byte[] || value instanceof int[] 53 | || value instanceof long[] || value instanceof double[] || value instanceof List || value instanceof Map) { 54 | return value; 55 | } else { 56 | if (value instanceof JSValue) { 57 | JSValue jsValue = (JSValue) value; 58 | if (jsValue.isUndefined() || jsValue.isNull()) { 59 | value = null; 60 | } else if (jsValue.isNumber()) { 61 | value = jsValue.toNumber(); 62 | } else if (jsValue.isBoolean()) { 63 | value = jsValue.toBoolean(); 64 | } else if (jsValue.isString()) { 65 | value = jsValue.toString(); 66 | } else { 67 | if (jsValue.isObject() && jsValue.toObject().isFunction()) { 68 | // This really only works if it's returning a function that was originally 69 | // passed in from Dart. 70 | Map map = new HashMap<>(); 71 | map.put("__dart_liquidcore_type__", "function"); 72 | map.put("__dart_liquidcore_ref__", jsValue.toObject().property("__dart_liquidcore_function_id__").toString()); 73 | value = map; 74 | } else { 75 | // Convert to JSON object for serialization/deserialization. 76 | JSONTokener tokener = new JSONTokener(jsValue.toJSON()); 77 | try { 78 | value = JSONUtil.unwrap(tokener.nextValue()); 79 | } catch (JSONException e) { 80 | e.printStackTrace(); 81 | } 82 | } 83 | } 84 | 85 | return value; 86 | } else if (value instanceof Exception) { 87 | Map map = new HashMap<>(); 88 | map.put("__dart_liquidcore_type__", "exception"); 89 | map.put("type", value.getClass().getSimpleName()); 90 | map.put("message", value.toString()); 91 | if (value instanceof JSException) { 92 | map.put("stack", ((JSException) value).stack()); 93 | } else { 94 | StringBuilder stack = new StringBuilder(); 95 | for (StackTraceElement st : ((Exception) value).getStackTrace()) { 96 | stack.append(st.toString()).append("\n"); 97 | } 98 | map.put("stack", stack.toString()); 99 | } 100 | return map; 101 | } else { 102 | return value.toString(); 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /android/src/main/java/cn/com/agree/flutter/miniprogram/components/WrappedJSContext.java: -------------------------------------------------------------------------------- 1 | package cn.com.agree.flutter.miniprogram.components; 2 | 3 | import org.liquidplayer.javascript.JSContext; 4 | 5 | public class WrappedJSContext { 6 | private JSContext jsContext; 7 | 8 | public WrappedJSContext() { 9 | jsContext = new JSContext(); 10 | } 11 | 12 | public JSContext getJSContext() { 13 | return jsContext; 14 | } 15 | 16 | public void freeUp() { 17 | jsContext = null; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /android/src/main/java/cn/com/agree/flutter/miniprogram/handler/JsContextHandler.java: -------------------------------------------------------------------------------- 1 | package cn.com.agree.flutter.miniprogram.handler; 2 | 3 | import android.support.annotation.Nullable; 4 | 5 | import org.liquidplayer.javascript.JSContext; 6 | import org.liquidplayer.javascript.JSException; 7 | import org.liquidplayer.javascript.JSFunction; 8 | 9 | import java.util.Collections; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import cn.com.agree.flutter.miniprogram.BuildConfig; 14 | import cn.com.agree.flutter.miniprogram.MiniProgramPlugin; 15 | import cn.com.agree.flutter.miniprogram.components.WrappedJSContext; 16 | import io.flutter.plugin.common.EventChannel; 17 | import io.flutter.plugin.common.MethodCall; 18 | import io.flutter.plugin.common.MethodChannel; 19 | import io.flutter.plugin.common.PluginRegistry.Registrar; 20 | 21 | /** 22 | * WIP: Need to implement properly. 23 | * It currently allows you to evaluate javascript and get properties back. 24 | * As well as provide basic functionality for returning references to callback objects. 25 | */ 26 | public class JsContextHandler implements MethodChannel.MethodCallHandler, EventChannel.StreamHandler { 27 | 28 | private final Object lockFile = new Object(); 29 | private final Map instances = Collections.synchronizedMap(new HashMap()); 30 | 31 | private MethodChannel jsContextChannel; 32 | private EventChannel.EventSink eventSink; 33 | 34 | public static void registerWith(Registrar registrar) { 35 | new JsContextHandler(registrar); 36 | } 37 | 38 | private JsContextHandler(Registrar registrar) { 39 | jsContextChannel = new MethodChannel(registrar.messenger(), MiniProgramPlugin.NAMESPACE + "/jscontext"); 40 | jsContextChannel.setMethodCallHandler(this); 41 | 42 | EventChannel contextException = new EventChannel(registrar.messenger(), MiniProgramPlugin.NAMESPACE + "/jscontextException"); 43 | contextException.setStreamHandler(this); 44 | } 45 | 46 | @Override 47 | public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) { 48 | try { 49 | handleMethodCall(methodCall, result); 50 | } catch (Exception e) { 51 | if (BuildConfig.DEBUG) { 52 | // Only print error stack trace in debug mode. 53 | e.printStackTrace(); 54 | } 55 | result.error("exception", e.toString(), MiniProgramPlugin.convertToDartObject(e)); 56 | } 57 | } 58 | 59 | private void handleMethodCall(MethodCall call, final MethodChannel.Result result) { 60 | final String contextId = call.argument("contextId"); 61 | if (contextId == null) { 62 | result.error("error", "Context ID not specified", null); 63 | return; 64 | } 65 | 66 | final WrappedJSContext wrappedJsContext = getOrCreateInstance(contextId); 67 | final JSContext jsContext = wrappedJsContext.getJSContext(); 68 | 69 | if ("evaluateScript".equals(call.method)) { 70 | String script = call.argument("script"); 71 | String sourceURL = call.argument("sourceURL"); 72 | Integer startingLineNumber = call.argument("startingLineNumber"); 73 | if (startingLineNumber == null) { 74 | startingLineNumber = 0; 75 | } 76 | if (script == null) { 77 | result.error("error", "Please specify a script!", null); 78 | return; 79 | } 80 | result.success(MiniProgramPlugin.convertToDartObject(jsContext.evaluateScript(script, sourceURL, startingLineNumber))); 81 | } else if ("setExceptionHandler".equals(call.method)) { 82 | jsContext.setExceptionHandler(new JSContext.IJSExceptionHandler() { 83 | @Override 84 | public void handle(JSException exception) { 85 | if (eventSink != null) { 86 | eventSink.success(buildArguments(contextId, exception)); 87 | } 88 | } 89 | }); 90 | result.success(null); 91 | } else if ("clearExceptionHandler".equals(call.method)) { 92 | jsContext.clearExceptionHandler(); 93 | result.success(null); 94 | } else if ("property".equals(call.method)) { 95 | String prop = call.argument("prop"); 96 | result.success(MiniProgramPlugin.convertToDartObject(jsContext.property(prop))); 97 | } else if ("setProperty".equals(call.method)) { 98 | String prop = call.argument("prop"); 99 | Object value = call.argument("value"); 100 | Integer attr = call.argument("attr"); 101 | String type = call.argument("type"); 102 | 103 | if ("function".equals(type)) { 104 | // Returns a promise. 105 | final String functionId = (String) value; 106 | final JSFunction dartCb = new JSFunction(jsContext, "dartCb") { 107 | @SuppressWarnings("unused") 108 | public void dartCb(final JSFunction resolve, final JSFunction error, Object... args) { 109 | Map arguments = buildArguments(contextId, MiniProgramPlugin.convertToDartObjects(args)); 110 | arguments.put("functionId", functionId); 111 | jsContextChannel.invokeMethod("dynamicFunction", arguments, new MethodChannel.Result() { 112 | @Override 113 | public void success(@Nullable Object returnValue) { 114 | resolve.call(resolve, returnValue); 115 | } 116 | 117 | @Override 118 | public void error(String type, @Nullable String errorMessage, @Nullable Object o) { 119 | error.call(error, type, errorMessage); 120 | } 121 | 122 | @Override 123 | public void notImplemented() { 124 | error.call(error, "dynamicFunction not implemented!"); 125 | } 126 | }); 127 | } 128 | }; 129 | 130 | final String code = "(function (dartCb) {\n" + 131 | " return function () {\n" + 132 | " let args = arguments;\n" + 133 | " return new Promise((resolve, error) => {\n" + 134 | " return dartCb.call(dartCb, resolve, error, ...args);\n" + 135 | " })\n" + 136 | " };\n" + 137 | "})"; 138 | 139 | JSFunction anon = jsContext.evaluateScript(code).toFunction(); 140 | value = anon.call(anon, dartCb).toFunction(); // Return the promise. 141 | // Store a reference to the dart function's uuid. 142 | ((JSFunction) value).property("__dart_liquidcore_function_id__", functionId); 143 | } 144 | 145 | attr = attr == null ? 0 : attr; 146 | jsContext.property(prop, value, attr); 147 | result.success(null); 148 | } else if ("hasProperty".equals(call.method)) { 149 | String prop = call.argument("prop"); 150 | result.success(jsContext.hasProperty(prop)); 151 | } else if ("deleteProperty".equals(call.method)) { 152 | String prop = call.argument("prop"); 153 | result.success(jsContext.deleteProperty(prop)); 154 | } else if ("cleanUp".equals(call.method)) { 155 | // Remove all references to the jsContext. 156 | synchronized (lockFile) { 157 | instances.remove(contextId); 158 | wrappedJsContext.freeUp(); 159 | } 160 | } 161 | } 162 | 163 | private WrappedJSContext getExistingInstance(String key) { 164 | synchronized (lockFile) { 165 | if (!instances.containsKey(key)) { 166 | return null; 167 | } 168 | return instances.get(key); 169 | } 170 | } 171 | 172 | private WrappedJSContext getOrCreateInstance(String key) { 173 | synchronized (lockFile) { 174 | WrappedJSContext instance = getExistingInstance(key); 175 | if (instance == null) { 176 | instance = new WrappedJSContext(); 177 | instances.put(key, instance); 178 | } 179 | return instance; 180 | } 181 | } 182 | 183 | @Override 184 | public void onListen(Object o, EventChannel.EventSink eventSink) { 185 | this.eventSink = eventSink; 186 | } 187 | 188 | @Override 189 | public void onCancel(Object o) { 190 | } 191 | 192 | private static Map buildArguments(String contextId, Object value) { 193 | Map result = new HashMap<>(); 194 | result.put("contextId", contextId); 195 | result.put("value", value); 196 | return result; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.lock 4 | *.log 5 | *.pyc 6 | *.swp 7 | .DS_Store 8 | .atom/ 9 | .buildlog/ 10 | .history 11 | .svn/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # Visual Studio Code related 20 | .vscode/ 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .packages 27 | .pub-cache/ 28 | .pub/ 29 | build/ 30 | 31 | # Android related 32 | **/android/**/gradle-wrapper.jar 33 | **/android/.gradle 34 | **/android/captures/ 35 | **/android/gradlew 36 | **/android/gradlew.bat 37 | **/android/local.properties 38 | **/android/**/GeneratedPluginRegistrant.java 39 | 40 | # iOS/XCode related 41 | **/ios/**/*.mode1v3 42 | **/ios/**/*.mode2v3 43 | **/ios/**/*.moved-aside 44 | **/ios/**/*.pbxuser 45 | **/ios/**/*.perspectivev3 46 | **/ios/**/*sync/ 47 | **/ios/**/.sconsign.dblite 48 | **/ios/**/.tags* 49 | **/ios/**/.vagrant/ 50 | **/ios/**/DerivedData/ 51 | **/ios/**/Icon? 52 | **/ios/**/Pods/ 53 | **/ios/**/.symlinks/ 54 | **/ios/**/profile 55 | **/ios/**/xcuserdata 56 | **/ios/.generated/ 57 | **/ios/Flutter/App.framework 58 | **/ios/Flutter/Flutter.framework 59 | **/ios/Flutter/Generated.xcconfig 60 | **/ios/Flutter/app.flx 61 | **/ios/Flutter/app.zip 62 | **/ios/Flutter/flutter_assets/ 63 | **/ios/ServiceDefinitions.json 64 | **/ios/Runner/GeneratedPluginRegistrant.* 65 | 66 | # Exceptions to above rules. 67 | !**/ios/**/default.mode1v3 68 | !**/ios/**/default.mode2v3 69 | !**/ios/**/default.pbxuser 70 | !**/ios/**/default.perspectivev3 71 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 72 | -------------------------------------------------------------------------------- /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: 5391447fae6209bb21a89e6a5a6583cac1af9b4b 8 | channel: beta 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_mini_program_example 2 | 3 | Demonstrates how to use the flutter_mini_program 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.io/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.io/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.io/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 27 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | applicationId "cn.com.agree.flutter.miniprogram.example" 36 | minSdkVersion 16 37 | targetSdkVersion 27 38 | versionCode flutterVersionCode.toInteger() 39 | versionName flutterVersionName 40 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 41 | } 42 | 43 | buildTypes { 44 | release { 45 | // Signing with the debug keys for now, so `flutter run --release` works. 46 | signingConfig signingConfigs.debug 47 | } 48 | } 49 | 50 | compileOptions { 51 | sourceCompatibility JavaVersion.VERSION_1_8 52 | targetCompatibility JavaVersion.VERSION_1_8 53 | } 54 | } 55 | 56 | flutter { 57 | source '../..' 58 | } 59 | 60 | dependencies { 61 | testImplementation 'junit:junit:4.12' 62 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 63 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 64 | } 65 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 19 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/cn/com/agree/flutter/miniprogram/MainActivity.java: -------------------------------------------------------------------------------- 1 | package cn.com.agree.flutter.miniprogram; 2 | 3 | import android.os.Bundle; 4 | import io.flutter.app.FlutterActivity; 5 | import io.flutter.plugins.GeneratedPluginRegistrant; 6 | 7 | public class MainActivity extends FlutterActivity { 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | GeneratedPluginRegistrant.registerWith(this); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/example/android/app/src/main/res/drawable/launch_image.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.2.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-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/assets/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "page/button.aml", 4 | "page/jsapi.aml" 5 | ], 6 | "window": { 7 | "navigationBarTitleText": "Flutter 小程序" 8 | }, 9 | "tabBar": { 10 | "list": [ 11 | { 12 | "pagePath": "pages/ui/index", 13 | "text": "组件" 14 | }, 15 | { 16 | "pagePath": "pages/api/index", 17 | "text": "接口" 18 | } 19 | ] 20 | } 21 | } -------------------------------------------------------------------------------- /example/assets/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/example/assets/images/avatar.png -------------------------------------------------------------------------------- /example/assets/page/button.aml: -------------------------------------------------------------------------------- 1 | 59 | 60 | 70 | 71 | -------------------------------------------------------------------------------- /example/assets/page/checkbox.aml: -------------------------------------------------------------------------------- 1 | 15 | 16 | -------------------------------------------------------------------------------- /example/assets/page/icon.aml: -------------------------------------------------------------------------------- 1 | 27 | 28 | 35 | 36 | -------------------------------------------------------------------------------- /example/assets/page/image.aml: -------------------------------------------------------------------------------- 1 | 63 | 64 | 71 | 72 | -------------------------------------------------------------------------------- /example/assets/page/index.aml: -------------------------------------------------------------------------------- 1 | 17 | 18 | -------------------------------------------------------------------------------- /example/assets/page/input.aml: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "navigationBarTitleText": "Flutter MiniProgram - Input" 4 | } 5 | 6 | 7 | 27 | 28 | -------------------------------------------------------------------------------- /example/assets/page/jsapi.aml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 28 | 29 | -------------------------------------------------------------------------------- /example/assets/page/slider.aml: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "navigationBarTitleText": "Flutter 小程序 - Slider" 4 | } 5 | 6 | 7 | 14 | 15 | -------------------------------------------------------------------------------- /example/assets/page/switch.aml: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "navigationBarTitleText": "Flutter 小程序 - Switch" 4 | } 5 | 6 | 7 | 64 | 65 | -------------------------------------------------------------------------------- /example/assets/page/text.aml: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "navigationBarTitleText": "Flutter 小程序 - Text" 4 | } 5 | 6 | 7 | 72 | 73 | -------------------------------------------------------------------------------- /example/assets/page/video.aml: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "navigationBarTitleText": "Flutter 小程序 - Video" 4 | } 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/assets/page/view.aml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | 23 | -------------------------------------------------------------------------------- /example/assets/page/webview.aml: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "navigationBarTitleText": "Flutter 小程序 - WebView" 4 | } 5 | 6 | 7 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | pods_ary = [] 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) { |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | pods_ary.push({:name => podname, :path => podpath}); 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | } 32 | return pods_ary 33 | end 34 | 35 | target 'Runner' do 36 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 37 | # referring to absolute paths on developers' machines. 38 | system('rm -rf .symlinks') 39 | system('mkdir -p .symlinks/plugins') 40 | 41 | # Flutter Pods 42 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 43 | if generated_xcode_build_settings.empty? 44 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 45 | end 46 | generated_xcode_build_settings.map { |p| 47 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 48 | symlink = File.join('.symlinks', 'flutter') 49 | File.symlink(File.dirname(p[:path]), symlink) 50 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 51 | end 52 | } 53 | 54 | # Plugin Pods 55 | plugin_pods = parse_KV_file('../.flutter-plugins') 56 | plugin_pods.map { |p| 57 | symlink = File.join('.symlinks', 'plugins', p[:name]) 58 | File.symlink(p[:path], symlink) 59 | pod p[:name], :path => File.join(symlink, 'ios') 60 | } 61 | end 62 | 63 | post_install do |installer| 64 | installer.pods_project.targets.each do |target| 65 | target.build_configurations.each do |config| 66 | config.build_settings['ENABLE_BITCODE'] = 'NO' 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/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 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | flutter_mini_program_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /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:flutter/material.dart'; 2 | import 'package:flutter_mini_program/App.dart'; 3 | import 'package:flutter_mini_program/Page.dart'; 4 | 5 | void main() { 6 | runApp(MiniProgramApp()); 7 | } 8 | 9 | class MiniProgramApp extends StatefulWidget { 10 | @override 11 | MiniProgramAppState createState() => MiniProgramAppState(); 12 | } 13 | 14 | class MiniProgramAppState extends State { 15 | @override 16 | Widget build(BuildContext context) { 17 | App.init(context, routes: { 18 | // Home 19 | "/": Page(url: 'assets/page/index.aml'), 20 | // View 21 | "/view": Page(url: 'assets/page/view.aml'), 22 | // Icon 23 | "/icon": Page(url: 'assets/page/icon.aml'), 24 | // Text 25 | "/text": Page(url: 'assets/page/text.aml'), 26 | // Button 27 | "/button": Page(url: 'assets/page/button.aml'), 28 | // Input 29 | "/input": Page(url: 'assets/page/input.aml'), 30 | // Checkbox 31 | "/checkbox": Page(url: 'assets/page/checkbox.aml'), 32 | // Switch 33 | "/switch": Page(url: 'assets/page/switch.aml'), 34 | // Slider 35 | "/slider": Page(url: 'assets/page/slider.aml'), 36 | // Image 37 | "/image": Page(url: 'assets/page/image.aml'), 38 | // Video 39 | "/video": Page(url: 'assets/page/video.aml'), 40 | // WebView 41 | "/webview": Page(url: 'assets/page/webview.aml'), 42 | // JS_API 43 | "/jsapi": Page(url: 'assets/page/jsapi.aml') 44 | }); 45 | 46 | return MaterialApp( 47 | onGenerateRoute: App.router.generator, 48 | debugShowCheckedModeBanner: true); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /example/lib/src/jsapi.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mini_program/Page.dart'; 3 | import 'package:flutter_mini_program/engine/JSContext.dart'; 4 | 5 | class JSAPIPage extends Page { 6 | String url; 7 | BuildContext mContext; 8 | 9 | JSAPIPage({this.url}); 10 | 11 | @override 12 | void onCreate(BuildContext context, Page page) { 13 | mContext = context; 14 | } 15 | 16 | Map get methods => { 17 | "onEvaluateJavascript": () async { 18 | JSContext jsContext = new JSContext(); 19 | const code = """ 20 | // Attached as a property of the current global context scope. 21 | var obj = { 22 | number: 1, 23 | string: 'string', 24 | date: new Date(), 25 | array: [1, 'string', null, undefined], 26 | func: function () {} 27 | }; 28 | var a = 10; 29 | // Is a variable, and not attached as a property of the context. 30 | let objLet = { number: 1, yayForLet: true }; 31 | """; 32 | await jsContext.evaluateScript(code); 33 | 34 | jsContext.setProperty("log", (String message) async { 35 | print("[log] $message"); 36 | }); 37 | await jsContext.evaluateScript("log(JSON.stringify(obj))"); 38 | 39 | print("******************************"); 40 | var obj = await jsContext.property("obj"); 41 | print("obj = $obj"); 42 | print("******************************"); 43 | } 44 | }; 45 | } -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_mini_program_example 2 | description: Demonstrates how to use the flutter_mini_program plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.0.0-dev.68.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^0.1.2 15 | # toast: ^0.0.6 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | flutter_mini_program: 22 | path: ../ 23 | 24 | # For information on the generic Dart part of this file, see the 25 | # following page: https://www.dartlang.org/tools/pub/pubspec 26 | 27 | # The following section is specific to Flutter. 28 | flutter: 29 | 30 | # The following line ensures that the Material Icons font is 31 | # included with your application, so that you can use the icons in 32 | # the material Icons class. 33 | uses-material-design: true 34 | 35 | # To add assets to your application, add an assets section, like this: 36 | assets: 37 | - assets/app.json 38 | - assets/page/ 39 | - assets/images/ 40 | 41 | # An image asset can refer to one or more resolution-specific "variants", see 42 | # https://flutter.io/assets-and-images/#resolution-aware. 43 | 44 | # For details regarding adding assets from package dependencies, see 45 | # https://flutter.io/assets-and-images/#from-packages 46 | 47 | # To add custom fonts to your application, 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 from package dependencies, 65 | # see https://flutter.io/custom-fonts/#from-packages 66 | -------------------------------------------------------------------------------- /example/test/event_emitter_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_mini_program/EventEmitter.dart'; 2 | 3 | void main() { 4 | EventEmitter emitter = new EventEmitter(); 5 | emitter.on("onCustomEvent", (data) { 6 | print(data[0] + ":" + data[1]); 7 | }); 8 | emitter.emit("onCustomEvent", ["message", "on execute"]); 9 | emitter.emit("onCustomEvent", ["message", "on execute again"]); 10 | 11 | emitter.once("onceCustomEvent", (data) { 12 | print(data[0] + ": " + data[1]); 13 | }); 14 | emitter.emit("onceCustomEvent", ["message", "once execute"]); 15 | emitter.emit("onceCustomEvent", ["message", "once execute again"]); 16 | 17 | var callback = () { 18 | print("off execute"); 19 | }; 20 | emitter.on("customEvent", callback); 21 | emitter.emit("customEvent"); 22 | emitter.off("customEvent", callback); 23 | emitter.emit("customEvent"); 24 | } 25 | -------------------------------------------------------------------------------- /example/test/function_test.dart: -------------------------------------------------------------------------------- 1 | //class _$ { 2 | // Symbol last; 3 | // noSuchMethod() => last = i.memberName; 4 | //} 5 | //final dynamic $ = _$(); 6 | // 7 | //main() { 8 | // print($.x1); // Symbol #x1 9 | //} 10 | 11 | class ClassTest { 12 | noSuchMethod(Invocation invocation) { 13 | print(invocation.isMethod); 14 | print(invocation.memberName); 15 | Invocation.setter(const Symbol("test="), ""); 16 | 17 | print(invocation.typeArguments); 18 | } 19 | } 20 | final dynamic $ = ClassTest(); 21 | 22 | main() { 23 | $.test(['type']); 24 | } 25 | 26 | -------------------------------------------------------------------------------- /example/test/utils_test.dart: -------------------------------------------------------------------------------- 1 | void main() { 2 | 3 | } -------------------------------------------------------------------------------- /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/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_mini_program_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MiniProgramApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | 27 | // Map pages = AppConfig['pages']; 28 | // if (Application.router == null) { 29 | // for (var page in pages.entries) { 30 | // var pagePath = pages[page.key]; 31 | // registerRoute(router, page.key, "assets/$pagePath"); 32 | // } 33 | // Application.router = router; 34 | // } 35 | // 36 | // Future loadAppConfig() async { 37 | // var config = 38 | // await ResourceUtil.loadStringFromAssetFile(context, "assets/app.json"); 39 | // setState(() { 40 | // AppConfig = json.decode(config); 41 | // }); 42 | // } 43 | // 44 | // void registerRoute(Router router, String routePath, String pagePath) { 45 | // router.define(routePath, handler: new Handler( 46 | // handlerFunc: (BuildContext context, Map> params) { 47 | // return new BasePageView(url: pagePath); 48 | // })); 49 | // } 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /flutter_mini_program.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhaomenghuan/flutter-mini-program/ad6657eda882dc1e406f2bfcdd8c2c90cf61a896/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/FlutterMiniProgramPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface FlutterMiniProgramPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/FlutterMiniProgramPlugin.m: -------------------------------------------------------------------------------- 1 | #import "FlutterMiniProgramPlugin.h" 2 | 3 | @implementation FlutterMiniProgramPlugin 4 | + (void)registerWithRegistrar:(NSObject*)registrar { 5 | FlutterMethodChannel* channel = [FlutterMethodChannel 6 | methodChannelWithName:@"flutter_mini_program" 7 | binaryMessenger:[registrar messenger]]; 8 | FlutterMiniProgramPlugin* instance = [[FlutterMiniProgramPlugin alloc] init]; 9 | [registrar addMethodCallDelegate:instance channel:channel]; 10 | } 11 | 12 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 13 | if ([@"getPlatformVersion" isEqualToString:call.method]) { 14 | result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); 15 | } else { 16 | result(FlutterMethodNotImplemented); 17 | } 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ios/flutter_mini_program.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'flutter_mini_program' 6 | s.version = '0.0.1' 7 | s.summary = 'A new Flutter plugin.' 8 | s.description = <<-DESC 9 | A new Flutter plugin. 10 | DESC 11 | s.homepage = 'http://example.com' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Your Company' => 'email@example.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | 19 | s.ios.deployment_target = '8.0' 20 | end 21 | 22 | -------------------------------------------------------------------------------- /lib/App.dart: -------------------------------------------------------------------------------- 1 | import 'package:fluro/fluro.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_mini_program/engine/JSContext.dart'; 4 | 5 | class App { 6 | static Router router; 7 | static JSContext jsContext; 8 | 9 | static init(BuildContext context, {routes}) { 10 | // Router 11 | var router = new Router(); 12 | 13 | // 404 14 | router.notFoundHandler = new Handler( 15 | handlerFunc: (BuildContext context, Map> params) { 16 | return new Container(width: 0.0, height: 0.0); 17 | }); 18 | 19 | routes.forEach((name, page) { 20 | router.define(name, handler: new Handler(handlerFunc: 21 | (BuildContext context, Map> params) { 22 | return page; 23 | })); 24 | }); 25 | 26 | App.router = router; 27 | App.jsContext = new JSContext(); 28 | 29 | initFramework(); 30 | } 31 | 32 | static initFramework() async { 33 | await App.jsContext.evaluateScript(''' 34 | var pages = {}; 35 | function Page(options) { 36 | var page = {}; 37 | page.config = options.config || {}; 38 | page.data = options.data || {}; 39 | page.methods = {}; 40 | if(options.methods) { 41 | for(var key in options.methods) { 42 | page.methods[key] = options.methods[key].toString(); 43 | } 44 | } 45 | return page; 46 | } 47 | '''); 48 | 49 | jsContext.setProperty( 50 | "invokeMethod", (String method, List arguments) async {}); 51 | } 52 | 53 | static navigateTo(BuildContext context, String path) { 54 | App.router 55 | .navigateTo(context, path, transition: TransitionType.inFromRight); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/EventEmitter.dart: -------------------------------------------------------------------------------- 1 | class EventEmitter { 2 | Map> events; 3 | 4 | EventEmitter() { 5 | events = new Map>(); 6 | } 7 | 8 | // Return the number of events that have been subscribed to. 9 | int get eventCount { 10 | return events.keys.length; 11 | } 12 | 13 | /// Return the total number of subscriptions. 14 | int get subscriptionCount { 15 | var count = 0; 16 | events.keys.forEach((String key) { 17 | count = count + events[key].length; 18 | }); 19 | return count; 20 | } 21 | 22 | /// Subscribe a target to an event. 23 | void on(String type, Function listener) { 24 | List subscribers = new List(); 25 | if (events.containsKey(type)) { 26 | subscribers = events[type]; 27 | } else { 28 | subscribers = new List(); 29 | events[type] = subscribers; 30 | } 31 | subscribers.add(listener); 32 | } 33 | 34 | void once(String type, Function listener) { 35 | bool fired = false; 36 | 37 | Function onceWrapper([List arguments]) { 38 | if (!fired) { 39 | fired = true; 40 | (arguments == null) ? listener() : listener(arguments); 41 | } 42 | } 43 | 44 | this.on(type, onceWrapper); 45 | } 46 | 47 | /// Unsubscribe a target to an event. 48 | bool off(String type, Function listener) { 49 | if (!events.containsKey(type)) { 50 | return false; 51 | } 52 | List subscribers = events[type]; 53 | for (var i = 0; i < subscribers.length; i++) { 54 | Function target = subscribers[i]; 55 | if (identical(target, listener)) { 56 | subscribers.remove(target); 57 | return true; 58 | } 59 | } 60 | 61 | return false; 62 | } 63 | 64 | /// Post the event [String] and data provider [Function] to the subscriptions. 65 | void emit(String type, [List arguments]) { 66 | if (events.containsKey(type)) { 67 | var subscribers = events[type]; 68 | if (subscribers.length > 0) { 69 | subscribers.forEach((Function target) { 70 | (arguments == null) ? target() : target(arguments); 71 | }); 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /lib/HtmlParser.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mini_program/Page.dart'; 3 | import 'package:flutter_mini_program/StyleParser.dart'; 4 | import 'package:flutter_mini_program/tags/BreakTag.dart'; 5 | import 'package:flutter_mini_program/tags/ButtonTag.dart'; 6 | import 'package:flutter_mini_program/tags/CheckboxGroupTag.dart'; 7 | import 'package:flutter_mini_program/tags/HrTag.dart'; 8 | import 'package:flutter_mini_program/tags/IconTag.dart'; 9 | import 'package:flutter_mini_program/tags/ImageTag.dart'; 10 | import 'package:flutter_mini_program/tags/InputTag.dart'; 11 | import 'package:flutter_mini_program/tags/ListViewTag.dart'; 12 | import 'package:flutter_mini_program/tags/SliderTag.dart'; 13 | import 'package:flutter_mini_program/tags/SwitchTag.dart'; 14 | import 'package:flutter_mini_program/tags/TableTag.dart'; 15 | import 'package:flutter_mini_program/tags/TextTag.dart'; 16 | import 'package:flutter_mini_program/tags/VideoTag.dart'; 17 | import 'package:flutter_mini_program/tags/ViewTag.dart'; 18 | import 'package:flutter_mini_program/tags/WebViewTag.dart'; 19 | import 'package:html/dom.dart' as dom; 20 | import 'package:html/parser.dart' show parse; 21 | 22 | class HtmlParser { 23 | /// Parse HTML File 24 | dom.Element parseHTML(String html) { 25 | dom.Document document = parse(html); 26 | return document.body; 27 | } 28 | 29 | /// Parse Node Element 30 | parseTag(Page page, dom.Node node) { 31 | Widget widget = new Container(height: 0.0, width: 0.0); 32 | 33 | if (node is dom.Text) { 34 | dom.Text text = node as dom.Text; 35 | String data = text.data.trim(); 36 | if (data.isNotEmpty) { 37 | widget = new Text(data); 38 | } 39 | } 40 | 41 | if (node is dom.Element) { 42 | Map nodeStyles = StyleParser.parseTagStyleDeclaration(page, node); 43 | var name = node.localName; 44 | switch (name) { 45 | case 'div': 46 | case 'view': 47 | widget = new ViewTag(page: page, element: node, style: nodeStyles); 48 | break; 49 | case 'text': 50 | case 'p': 51 | case 'span': 52 | case 'h1': 53 | case 'h2': 54 | case 'h3': 55 | case 'h4': 56 | case 'h5': 57 | case 'h6': 58 | case 'a': 59 | widget = new TextTag(page: page, element: node, style: nodeStyles); 60 | break; 61 | case 'table': 62 | widget = new TableTag(page: page, element: node, style: nodeStyles); 63 | break; 64 | case 'hr': 65 | widget = new HrTag(page: page, element: node, style: nodeStyles); 66 | break; 67 | case 'br': 68 | widget = new BreakTag(page: page, element: node, style: nodeStyles); 69 | break; 70 | case 'icon': 71 | widget = new IconTag(page: page, element: node, style: nodeStyles); 72 | break; 73 | case 'input': 74 | widget = new InputTag(page: page, element: node, style: nodeStyles); 75 | break; 76 | case 'button': 77 | widget = new ButtonTag(page: page, element: node, style: nodeStyles); 78 | break; 79 | case 'checkbox-group': 80 | widget = new CheckboxGroupTag( 81 | page: page, element: node, style: nodeStyles); 82 | break; 83 | case 'switch': 84 | widget = new SwitchTag(page: page, element: node, style: nodeStyles); 85 | break; 86 | case 'slider': 87 | widget = new SliderTag(page: page, element: node, style: nodeStyles); 88 | break; 89 | case 'list-view': 90 | widget = 91 | new ListViewTag(page: page, element: node, style: nodeStyles); 92 | break; 93 | case 'image': 94 | case 'img': 95 | widget = new ImageTag(page: page, element: node, style: nodeStyles); 96 | break; 97 | case 'video': 98 | widget = new VideoTag(page: page, element: node, style: nodeStyles); 99 | break; 100 | case 'web-view': 101 | widget = new WebViewTag(page: page, element: node, style: nodeStyles); 102 | break; 103 | default: 104 | break; 105 | } 106 | 107 | return widget; 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/Page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mini_program/App.dart'; 3 | import 'package:flutter_mini_program/EventEmitter.dart'; 4 | import 'package:flutter_mini_program/PageParser.dart'; 5 | import 'package:flutter_mini_program/engine/JSContext.dart'; 6 | import 'package:flutter_mini_program/utils/ConvertUtil.dart'; 7 | import 'package:flutter_mini_program/utils/ResourceUtil.dart'; 8 | 9 | class Page extends StatefulWidget { 10 | String url; 11 | EventEmitter emitter; 12 | Widget view; 13 | BuildContext context; 14 | JSContext jsContext; 15 | 16 | List widgetList = new List(); 17 | 18 | // Page DSL 19 | String html; 20 | 21 | // Page Config 22 | Map config = {}; 23 | 24 | // Page Style 25 | Map style = {}; 26 | 27 | // Page Data 28 | Map data = {}; 29 | 30 | // Page Methods 31 | Map methods = {}; 32 | 33 | Page({this.url}) { 34 | emitter = new EventEmitter(); 35 | } 36 | 37 | @override 38 | PageState createState() => PageState(); 39 | 40 | void onCreate(BuildContext context, Page page) { 41 | this.context = context; 42 | } 43 | 44 | void invoke(String functionTag) { 45 | if (functionTag != null) { 46 | Map methodMap = ConvertUtil.parseFunction(functionTag); 47 | String name = methodMap['name']; 48 | List arguments = methodMap['arguments']; 49 | 50 | print('---------------------'); 51 | print('method: $name'); 52 | print('arguments: $arguments'); 53 | print('---------------------'); 54 | 55 | if (methods != null && methods.containsKey(name)) { 56 | String callback = methods[name]; 57 | print(callback); 58 | // Function.apply(callback, arguments); 59 | 60 | // TODO: API 61 | switch (name) { 62 | case 'openPage': 63 | App.navigateTo(this.context, arguments[0]); 64 | break; 65 | } 66 | } 67 | } 68 | } 69 | } 70 | 71 | class PageState extends State { 72 | bool _didFailToParse = false; 73 | 74 | // Init Page 75 | void initPageContext() async { 76 | widget.jsContext = new JSContext(); 77 | widget.jsContext.setProperty("log", (String message) async { 78 | print("[log] $message"); 79 | }); 80 | } 81 | 82 | @override 83 | void initState() { 84 | super.initState(); 85 | initPageContext(); 86 | } 87 | 88 | void parseContent() async { 89 | try { 90 | widget.html = 91 | await ResourceUtil.loadStringFromAssetFile(context, widget.url); 92 | Page page = await PageParser.parse(widget); 93 | 94 | setState(() { 95 | widget.config = page.config; 96 | widget.data = page.data; 97 | widget.methods = page.methods; 98 | widget.widgetList = page.widgetList; 99 | }); 100 | } catch (e) { 101 | setState(() { 102 | _didFailToParse = true; 103 | }); 104 | } 105 | } 106 | 107 | String parseTitle() { 108 | var title = ''; 109 | if (widget.config != null && 110 | widget.config.containsKey('navigationBarTitleText')) { 111 | title = widget.config['navigationBarTitleText']; 112 | } 113 | return title; 114 | } 115 | 116 | @override 117 | Widget build(BuildContext context) { 118 | parseContent(); 119 | 120 | if (_didFailToParse || widget.widgetList == null) { 121 | return new Container(width: 0.0, height: 0.0); 122 | } 123 | if (widget.onCreate != null) { 124 | widget.onCreate(context, widget); 125 | } 126 | 127 | return new Scaffold( 128 | appBar: AppBar(title: Text(parseTitle()), centerTitle: true), 129 | body: SingleChildScrollView(child: Wrap(children: widget.widgetList))); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /lib/PageParser.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mini_program/App.dart'; 3 | import 'package:flutter_mini_program/HtmlParser.dart'; 4 | import 'package:flutter_mini_program/Page.dart'; 5 | import 'package:flutter_mini_program/StyleParser.dart'; 6 | import 'package:html/dom.dart' as dom; 7 | 8 | class PageParser { 9 | /// Parse HTML Page 10 | static Future parse(Page page) async { 11 | List widgetList = new List(); 12 | HtmlParser htmlParser = new HtmlParser(); 13 | dom.Element docBody = htmlParser.parseHTML(page.html); 14 | 15 | // style 16 | List styleElements = docBody.getElementsByTagName("style"); 17 | if (styleElements.length > 0) { 18 | dom.Element styleElement = styleElements.first; 19 | page.style = StyleParser.visitStyleSheet(styleElement.text); 20 | } 21 | 22 | // template 23 | dom.Element templateElement = 24 | docBody.getElementsByTagName("template").first; 25 | List templateChildren = templateElement.children; 26 | if (templateChildren.length > 0) { 27 | templateChildren.forEach( 28 | (dom.Node node) => widgetList.add(htmlParser.parseTag(page, node))); 29 | page.widgetList = widgetList; 30 | } 31 | 32 | // script 33 | List scriptElements = docBody.getElementsByTagName("script"); 34 | if (scriptElements.length > 0) { 35 | dom.Element scriptElement = scriptElements.first; 36 | page.jsContext.setProperty("Page", (Map options) {}); 37 | var global = await registerPageLogic(page.url, scriptElement.text.trim()); 38 | page.config = global['config']; 39 | page.data = global['data']; 40 | page.methods = global['methods']; 41 | } 42 | 43 | return page; 44 | } 45 | 46 | // Register Page Logic 47 | static dynamic registerPageLogic(String name, String script) async { 48 | await App.jsContext.evaluateScript(''' 49 | pages['$name'] = $script; 50 | '''); 51 | var pages = await App.jsContext.property("pages"); 52 | return pages[name]; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/StyleParser.dart: -------------------------------------------------------------------------------- 1 | import 'package:csslib/parser.dart' as css; 2 | import 'package:csslib/visitor.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_mini_program/Page.dart'; 5 | import 'package:html/dom.dart' as dom; 6 | 7 | class StyleParser { 8 | /// Spin-up CSS parser in checked mode to detect any problematic CSS. Normally, 9 | /// CSS will allow any property/value pairs regardless of validity; all of our 10 | /// tests (by default) will ensure that the CSS is really valid. 11 | static StyleSheet parseCss(String cssInput, 12 | {List errors, css.PreprocessorOptions opts}) => 13 | css.parse(cssInput, 14 | errors: errors, 15 | options: opts == null 16 | ? css.PreprocessorOptions( 17 | useColors: true, 18 | verbose: true, 19 | checked: true, 20 | warningsAsErrors: true, 21 | polyfill: true, 22 | inputFile: 'memory') 23 | : opts); 24 | 25 | static formatCss(String cssInput) { 26 | StyleSheet styleSheet = parseCss(cssInput); 27 | var clsVisits = new CssPrinter(); 28 | return clsVisits..visitStyleSheet(styleSheet).toString(); 29 | } 30 | 31 | static Map visitStyleSheet(String cssInput) { 32 | StyleSheet styleSheet = parseCss(cssInput); 33 | Map styleSheetMap = new Map(); 34 | for (RuleSet ruleSet in styleSheet.topLevels) { 35 | String selectorName = ruleSet.selectorGroup.span.text; 36 | Map declarationMap = new Map(); 37 | for (var declaration in ruleSet.declarationGroup.declarations) { 38 | List declarationTextSpan = declaration.span.text.split(':'); 39 | String property = declarationTextSpan[0].trim(); 40 | String value = declarationTextSpan[1].trim(); 41 | declarationMap[property] = value; 42 | } 43 | styleSheetMap[selectorName] = declarationMap; 44 | } 45 | return styleSheetMap; 46 | } 47 | 48 | /// Parse Class Attribute 49 | static Map parseClassAttribute(Page page, dom.Node node, Map nodeStyles) { 50 | if (node is dom.Element) { 51 | if (page.style != null && node.classes != null) { 52 | node.classes.forEach((name) { 53 | Map declarationMap = page.style['.$name']; 54 | if (declarationMap != null) { 55 | declarationMap.forEach((property, value) { 56 | nodeStyles[property] = value; 57 | }); 58 | } 59 | }); 60 | } 61 | } 62 | return nodeStyles; 63 | } 64 | 65 | // Parse Style Attribute 66 | static Map parseStyleAttribute(dom.Node node, Map nodeStyles) { 67 | RegExp _style = new RegExp(r'([a-zA-Z\-]+)\s*:\s*([^;]*)'); 68 | var styleText = node.attributes['style']; 69 | if (styleText != null) { 70 | Iterable matches = _style.allMatches(styleText); 71 | for (Match match in matches) { 72 | String property = match[1].trim(); 73 | String value = match[2].trim(); 74 | nodeStyles[property] = value; 75 | } 76 | } 77 | return nodeStyles; 78 | } 79 | 80 | // Parse Node Tag Style 81 | static Map parseTagStyleSheet(Page page, dom.Node node) { 82 | Map nodeStyles = {}; 83 | // 1. Class Attribute 84 | parseClassAttribute(page, node, nodeStyles); 85 | // 2. Style Attribute 86 | parseStyleAttribute(node, nodeStyles); 87 | return nodeStyles; 88 | } 89 | 90 | /// 91 | /// [CSS writing order](https://github.com/necolas/idiomatic-css) 92 | /// 93 | /// 1.Positioning attribute(position, z-index, top, right, bottom, left) 94 | /// 2.Display & Box Model attribute(display, overflow, box-sizing, width, height, padding, margin, border) 95 | /// 4.Color attribute(background, color) 96 | /// 4.Text attribute(font, font-family, font-size, line-height, letter-spacing, text-align) 97 | /// 5.Other(cursor, animation, transition) 98 | /// 99 | /// TextStyle Property: 100 | /// 101 | /// inherit: 是否继承 102 | /// color: 字体颜色 as: #FF0000 103 | /// fontSize: 字体尺寸 104 | /// fontWeight: 字体粗细 105 | /// fontStyle: 字体样式(normal|italic|oblique) 106 | /// letterSpacing: 字母间隙(负值可以让字母更紧凑) 107 | /// wordSpacing: 单词间隙(负值可以让单词更紧凑) 108 | /// textBaseline: 文本绘制基线(alphabetic|ideographic) 109 | /// height: 高度 110 | /// locale: 区域设置 111 | /// foreground 112 | /// background 113 | /// shadows 114 | /// decoration: 文字装饰(none|underline|overline|lineThrough) 115 | /// decorationColor: 文字装饰颜色 116 | /// decorationStyle: 文字装饰样式 117 | /// debugLabel 118 | /// fontFamily: 字体 119 | /// 120 | /// 121 | static Map parseTagStyleDeclaration(Page page, dom.Element node) { 122 | Map styleDeclaration = {}; 123 | List _blockTags = const ['view', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p']; 124 | String tag = node.localName; 125 | Map nodeStyles = parseTagStyleSheet(page, node); 126 | 127 | // BlockTags 128 | if (_blockTags.contains(tag)) { 129 | styleDeclaration['display'] = 'block'; 130 | } else { 131 | styleDeclaration['display'] = 'inline'; 132 | } 133 | 134 | switch (tag) { 135 | case 'h1': 136 | styleDeclaration['fontSize'] = 32.0; 137 | break; 138 | case 'h2': 139 | styleDeclaration['fontSize'] = 24.0; 140 | break; 141 | case 'h3': 142 | styleDeclaration['fontSize'] = 20.0; 143 | break; 144 | case 'h4': 145 | styleDeclaration['fontSize'] = 16.0; 146 | break; 147 | case 'h5': 148 | styleDeclaration['fontSize'] = 12.8; 149 | break; 150 | case 'h6': 151 | styleDeclaration['fontSize'] = 11.2; 152 | break; 153 | case 'a': 154 | styleDeclaration['textDecoration'] = TextDecoration.underline; 155 | styleDeclaration['color'] = parseColor("#1965B5"); 156 | break; 157 | case 'b': 158 | case 'strong': 159 | styleDeclaration['fontWeight'] = FontWeight.bold; 160 | break; 161 | case 'i': 162 | case 'em': 163 | styleDeclaration['fontStyle'] = FontStyle.italic; 164 | break; 165 | case 'u': 166 | styleDeclaration['textDecoration'] = TextDecoration.underline; 167 | break; 168 | } 169 | 170 | if (nodeStyles != null) { 171 | for (var property in nodeStyles.keys) { 172 | String value = nodeStyles[property]; 173 | switch (property) { 174 | case 'display': 175 | styleDeclaration['display'] = value; 176 | break; 177 | case 'width': 178 | styleDeclaration['width'] = parsePxSize(value); 179 | break; 180 | case 'height': 181 | styleDeclaration['height'] = parsePxSize(value); 182 | break; 183 | case 'margin': 184 | styleDeclaration['margin'] = parseMargin(value); 185 | break; 186 | case 'padding': 187 | styleDeclaration['padding'] = parsePadding(value); 188 | break; 189 | case 'color': 190 | styleDeclaration['color'] = parseColor(value); 191 | break; 192 | case 'background-color': 193 | styleDeclaration['backgroundColor'] = parseColor(value); 194 | break; 195 | case 'font-weight': 196 | styleDeclaration['fontWeight'] = parseFontWeight(value); 197 | break; 198 | case 'font-style': 199 | styleDeclaration['fontStyle'] = parseFontStyle(value); 200 | break; 201 | case 'font-size': 202 | styleDeclaration['fontSize'] = parsePxSize(value); 203 | break; 204 | case 'text-decoration': 205 | styleDeclaration['textDecoration'] = parseTextDecoration(value); 206 | break; 207 | case 'text-decoration-color': 208 | styleDeclaration['textDecorationColor'] = parseColor(value); 209 | break; 210 | case 'text-decoration-style': 211 | styleDeclaration['textDecorationStyle'] = 212 | parseTextDecorationStyle(value); 213 | break; 214 | case 'text-align': 215 | styleDeclaration['textAlign'] = parseTextAlign(value); 216 | break; 217 | case 'direction': 218 | styleDeclaration['direction'] = parseTextDirection(value); 219 | break; 220 | } 221 | } 222 | } 223 | 224 | return styleDeclaration; 225 | } 226 | 227 | /// Parse Color 228 | static parseColor(String value) { 229 | var colorHex = 0xff000000; 230 | if (value.length == 7) { 231 | colorHex = int.parse(value.replaceAll(r"#", "0xff")); 232 | } else if (value.length == 9) { 233 | colorHex = int.parse(value.replaceAll(r"#", "0x")); 234 | } else if (value.length == 4) { 235 | value = value.replaceFirst(r"#", ""); 236 | value = value.split("").map((c) => "$c$c").join(); 237 | colorHex = int.parse("0xff$value"); 238 | } 239 | return new Color(colorHex); 240 | } 241 | 242 | /// Parse Size 243 | static parsePxSize(String value) { 244 | value = value.replaceAll('px', '').trim(); 245 | return double.parse(value); 246 | } 247 | 248 | static parseEdgeInsets(String value) { 249 | return EdgeInsets.all(parsePxSize(value)); 250 | } 251 | 252 | /// [margin](https://developer.mozilla.org/en-US/docs/Web/CSS/margin) 253 | static parseMargin(String value) { 254 | List valueList = value.split(r" "); 255 | switch (valueList.length) { 256 | case 1: 257 | return EdgeInsets.all(parsePxSize(value)); 258 | case 2: 259 | double vertical = parsePxSize(valueList[0]); 260 | double horizontal = parsePxSize(valueList[1]); 261 | return EdgeInsets.fromLTRB(horizontal, vertical, horizontal, vertical); 262 | case 3: 263 | double top = parsePxSize(valueList[0]); 264 | double horizontal = parsePxSize(valueList[1]); 265 | double bottom = parsePxSize(valueList[2]); 266 | return EdgeInsets.fromLTRB(horizontal, top, horizontal, bottom); 267 | case 4: 268 | double top = parsePxSize(valueList[0]); 269 | double right = parsePxSize(valueList[1]); 270 | double bottom = parsePxSize(valueList[2]); 271 | double left = parsePxSize(valueList[3]); 272 | return EdgeInsets.fromLTRB(left, top, right, bottom); 273 | } 274 | } 275 | 276 | static parsePadding(String value) { 277 | return parseEdgeInsets(value); 278 | } 279 | 280 | /// [font-style](https://developer.mozilla.org/en-US/docs/Web/CSS/font-style) 281 | static FontStyle parseFontStyle(String value) { 282 | return (value == 'italic') ? FontStyle.italic : FontStyle.normal; 283 | } 284 | 285 | /// [font-weight](https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight] 286 | static FontWeight parseFontWeight(String fontWeight) { 287 | switch (fontWeight) { 288 | case 'normal': 289 | return FontWeight.normal; 290 | case 'bold': 291 | return FontWeight.bold; 292 | case '100': 293 | return FontWeight.w100; 294 | case '200': 295 | return FontWeight.w200; 296 | case '300': 297 | return FontWeight.w300; 298 | case '400': 299 | return FontWeight.w400; 300 | case '500': 301 | return FontWeight.w500; 302 | case '600': 303 | return FontWeight.w600; 304 | case '700': 305 | return FontWeight.w700; 306 | case '800': 307 | return FontWeight.w800; 308 | case '900': 309 | return FontWeight.w900; 310 | } 311 | } 312 | 313 | /// [text-align](https://developer.mozilla.org/en-US/docs/Web/CSS/text-align) 314 | /// TODO: Temporarily not effective 315 | static TextAlign parseTextAlign(String textAlign) { 316 | switch (textAlign) { 317 | case 'left': 318 | return TextAlign.left; 319 | case 'right': 320 | return TextAlign.right; 321 | case 'center': 322 | return TextAlign.center; 323 | case 'justify': 324 | return TextAlign.justify; 325 | case 'start': 326 | return TextAlign.start; 327 | case 'end': 328 | return TextAlign.end; 329 | } 330 | } 331 | 332 | /// [text-decoration](https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration) 333 | static TextDecoration parseTextDecoration(String textDecoration) { 334 | switch (textDecoration) { 335 | // 默认, 定义标准的文本 336 | case 'none': 337 | return TextDecoration.none; 338 | break; 339 | // 下划线 340 | case 'underline': 341 | return TextDecoration.underline; 342 | break; 343 | // 顶划线 344 | case 'overline': 345 | return TextDecoration.overline; 346 | break; 347 | // 删除线 348 | case 'line-through': 349 | return TextDecoration.lineThrough; 350 | break; 351 | } 352 | } 353 | 354 | /// [text-decoration-style](https://developer.mozilla.org/en-US/docs/Web/CSS/text-decoration-style) 355 | static TextDecorationStyle parseTextDecorationStyle( 356 | String textDecorationStyle) { 357 | switch (textDecorationStyle) { 358 | case 'solid': 359 | return TextDecorationStyle.solid; 360 | break; 361 | case 'double': 362 | return TextDecorationStyle.double; 363 | break; 364 | case 'dotted': 365 | return TextDecorationStyle.dotted; 366 | break; 367 | case 'dashed': 368 | return TextDecorationStyle.dashed; 369 | break; 370 | case 'wavy': 371 | return TextDecorationStyle.wavy; 372 | break; 373 | } 374 | } 375 | 376 | /// [direction](https://developer.mozilla.org/en-US/docs/Web/CSS/direction) 377 | static TextDirection parseTextDirection(String value) { 378 | return (value == 'ltr') ? TextDirection.ltr : TextDirection.rtl; 379 | } 380 | } 381 | -------------------------------------------------------------------------------- /lib/components/animation/fade_in.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FadeIn extends StatefulWidget { 4 | final Widget child; 5 | final int duration; 6 | final listener; 7 | final autoPlay; 8 | final AnimationController controller; 9 | 10 | FadeIn({ 11 | key, 12 | @required this.child, 13 | this.duration = 250, 14 | this.listener, 15 | this.autoPlay = true, 16 | this.controller 17 | }) : super(key: key); 18 | 19 | @override 20 | FadeInState createState() => FadeInState(); 21 | } 22 | 23 | class FadeInState extends State with SingleTickerProviderStateMixin { 24 | AnimationController controller; 25 | Animation animation; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | createAnimate(); 31 | } 32 | 33 | @override 34 | void dispose() { 35 | controller.dispose(); 36 | super.dispose(); 37 | } 38 | 39 | void createAnimate() { 40 | if (widget.controller != null) { 41 | controller = widget.controller; 42 | } else { 43 | controller = AnimationController( 44 | vsync: this, 45 | duration: Duration( 46 | milliseconds: widget.duration 47 | ) 48 | ); 49 | } 50 | 51 | animation = Tween(begin: 0.0, end: 1.0) 52 | .animate(controller) 53 | ..addListener(() { 54 | setState(() => null); 55 | }); 56 | 57 | // 判断是添加监听 58 | if (widget.listener != null) { 59 | animation.addStatusListener(widget.listener); 60 | } 61 | 62 | // 判断是否自动播放 63 | if (widget.autoPlay) controller.forward(); 64 | } 65 | 66 | @override 67 | Widget build(BuildContext context) { 68 | return Opacity( 69 | opacity: animation.value, 70 | child: widget.child 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/components/animation/index.dart: -------------------------------------------------------------------------------- 1 | export './rotating.dart'; 2 | export './fade_in.dart'; 3 | export './scale.dart'; 4 | -------------------------------------------------------------------------------- /lib/components/animation/rotating.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Rotating extends StatefulWidget { 4 | final Widget child; 5 | final int duration; 6 | 7 | Rotating(this.child, { 8 | this.duration = 1200 9 | }); 10 | 11 | @override 12 | RotatingState createState() => RotatingState(); 13 | } 14 | 15 | class RotatingState extends State with SingleTickerProviderStateMixin { 16 | AnimationController controller; 17 | Animation animation; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | controller = AnimationController( 23 | vsync: this, 24 | duration: Duration( 25 | milliseconds: widget.duration 26 | ) 27 | ) 28 | ..repeat(); 29 | animation = Tween(begin: 0.0, end: 360.0) 30 | .animate(controller) 31 | ..addListener(() { 32 | setState(() => null); 33 | }); 34 | } 35 | 36 | @override 37 | void dispose() { 38 | controller.dispose(); 39 | super.dispose(); 40 | } 41 | 42 | @override 43 | Widget build(BuildContext context) { 44 | return Container( 45 | child: Transform.rotate( 46 | angle: (animation.value ~/ 30) * 30.0 * 0.0174533, 47 | child: widget.child 48 | ) 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/components/animation/scale.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class Scale extends StatefulWidget { 4 | final Widget child; 5 | final bool autoPlay; 6 | final int duration; 7 | final double begin; 8 | final double end; 9 | 10 | Scale({ 11 | key, 12 | @required this.child, 13 | this.autoPlay = true, 14 | this.duration = 150, 15 | this.begin = 0.0, 16 | this.end = 1.0 17 | }) : super(key: key); 18 | 19 | @override 20 | ScaleState createState() => ScaleState(); 21 | } 22 | 23 | class ScaleState extends State with TickerProviderStateMixin { 24 | AnimationController controller; 25 | Animation transform; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | createAnimate(); 31 | } 32 | 33 | // 创建动画 34 | void createAnimate() { 35 | controller = AnimationController( 36 | duration: Duration(milliseconds: widget.duration), 37 | vsync: this 38 | ); 39 | 40 | // 内容放大动画 41 | transform = Tween(begin: widget.begin, end: widget.end) 42 | .animate( 43 | CurvedAnimation( 44 | parent: controller, 45 | curve: Curves.ease 46 | ) 47 | ); 48 | 49 | if (widget.autoPlay) controller.forward(); 50 | } 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return AnimatedBuilder( 55 | animation: controller, 56 | builder: (BuildContext context, Widget child) { 57 | return Transform.scale( 58 | scale: transform.value, 59 | child: widget.child 60 | ); 61 | } 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/components/button/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../animation/rotating.dart'; 3 | import '../theme.dart'; 4 | import '../icon/index.dart'; 5 | 6 | // 颜色类型 7 | enum UIButtonType { 8 | acquiescent, 9 | primary, 10 | warn 11 | } 12 | 13 | // size 14 | enum UIButtonSize { 15 | acquiescent, 16 | mini 17 | } 18 | 19 | class UIButton extends StatefulWidget { 20 | // 内容 21 | dynamic child; 22 | // 禁用 23 | final bool disabled; 24 | // 点击回调 25 | final Function onClick; 26 | // loading 27 | final bool loading; 28 | // 按钮大小类型 29 | UIButtonSize sizeType; 30 | // 按钮主题 31 | Map theme; 32 | // 按钮大小 33 | Map size; 34 | // 主题 35 | final List> themeConfig = [ 36 | // 默认 37 | { 38 | 'backgroundColor': defaultBackgroundColor, 39 | 'fontColor': Colors.black, 40 | 'disabledBackgroundColor': Color(0xfff7f7f7), 41 | 'borderColor': Color(0xffc6c6c6), 42 | 'hollowColor': Color(0xff353535) 43 | }, 44 | // primary 45 | { 46 | 'backgroundColor': primary, 47 | 'fontColor': Colors.white, 48 | 'disabledBackgroundColor': primaryDisabled, 49 | 'borderColor': Color(0xffc6c6c6), 50 | 'hollowColor': primary 51 | }, 52 | // warn 53 | { 54 | 'backgroundColor': warn, 55 | 'fontColor': Colors.white, 56 | 'disabledBackgroundColor': warnDisabled, 57 | 'borderColor': Color(0xffc6c6c6), 58 | 'hollowColor': warn 59 | } 60 | ]; 61 | // 大小配置 62 | final List> sizeConfig = [ 63 | { 64 | 'fontSize': 18.0, 65 | 'height': 45.0, 66 | 'iconSize': 16.0, 67 | 'borderSize': 0.5 68 | }, 69 | { 70 | 'fontSize': 13.0, 71 | 'height': 30.0, 72 | 'iconSize': 14.0, 73 | 'borderSize': 0.4 74 | } 75 | ]; 76 | 77 | UIButton( 78 | this.child, 79 | { 80 | this.onClick, 81 | UIButtonSize size = UIButtonSize.acquiescent, 82 | hollow = false, 83 | UIButtonType theme = UIButtonType.acquiescent, 84 | this.disabled = false, 85 | this.loading = false, 86 | } 87 | ) { 88 | this.size = sizeConfig[size.index]; 89 | this.sizeType = size; 90 | final themeConf = themeConfig[theme.index]; 91 | // 判断是否空心 92 | if (hollow) { 93 | this.theme = { 94 | 'backgroundColor': Colors.transparent, 95 | 'fontColor': themeConf['hollowColor'], 96 | 'disabledBackgroundColor': null, 97 | 'borderColor': themeConf['hollowColor'] 98 | }; 99 | } else { 100 | this.theme = themeConf; 101 | } 102 | } 103 | 104 | @override 105 | _ButtonState createState() => _ButtonState(); 106 | } 107 | class _ButtonState extends State { 108 | // 按钮点击 109 | onClick() { 110 | if (widget.onClick is Function) { 111 | widget.onClick(); 112 | } 113 | } 114 | 115 | // 渲染按钮内容 116 | Widget renderChild(content) { 117 | // size 118 | final size = widget.size; 119 | // 是否禁用状态 120 | final bool disabled = widget.loading || widget.disabled; 121 | Widget child; 122 | if (content is String) { 123 | child = Text(content); 124 | } else { 125 | child = content; 126 | } 127 | 128 | // 内容 129 | List children = [ 130 | DefaultTextStyle( 131 | style: TextStyle( 132 | fontSize: size['fontSize'], 133 | color: widget.theme['fontColor'] 134 | ), 135 | child: child 136 | ) 137 | ]; 138 | 139 | if (widget.loading) { 140 | final Widget icon = Padding( 141 | padding: EdgeInsets.only(right: 5), 142 | child: Rotating(Icon( 143 | WeIcons.loading, 144 | color: widget.theme['fontColor'], 145 | size: size['iconSize'] 146 | )) 147 | ); 148 | children.insert(0, icon); 149 | } 150 | 151 | return Opacity( 152 | opacity: disabled ? 0.7 : 1, 153 | child: Row( 154 | mainAxisAlignment: MainAxisAlignment.center, 155 | mainAxisSize: widget.sizeType == UIButtonSize.mini ? MainAxisSize.min : MainAxisSize.max, 156 | children: children 157 | ) 158 | ); 159 | } 160 | 161 | @override 162 | Widget build(BuildContext context) { 163 | // size 164 | final size = widget.size; 165 | final theme = widget.theme; 166 | // 是否禁用状态 167 | final bool disabled = widget.loading || widget.disabled; 168 | // 圆角 169 | final BorderRadius borderRadius = BorderRadius.all(Radius.circular(4)); 170 | // 按钮 171 | final Widget button = Container( 172 | height: size['height'], 173 | padding: EdgeInsets.only(left: 10, right: 10), 174 | decoration: BoxDecoration( 175 | color: disabled ? theme['disabledBackgroundColor'] : null, 176 | borderRadius: borderRadius, 177 | border: Border.all(width: size['borderSize'], color: theme['borderColor']) 178 | ), 179 | child: renderChild(widget.child) 180 | ); 181 | 182 | // 禁用状态 183 | if (disabled) { 184 | return button; 185 | } 186 | 187 | return Material( 188 | borderRadius: borderRadius, 189 | color: widget.theme['backgroundColor'], 190 | child: InkWell( 191 | onTap: onClick, 192 | borderRadius: borderRadius, 193 | child: button 194 | ) 195 | ); 196 | } 197 | } -------------------------------------------------------------------------------- /lib/components/cell/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../theme.dart'; 3 | 4 | // 边框 5 | const Divider _border = Divider(height: 1, color: defaultBorderColor); 6 | // 间距 7 | const double _spacing = 20.0; 8 | 9 | class WeCells extends StatelessWidget { 10 | final bool boxBorder; 11 | final double spacing; 12 | List children; 13 | 14 | WeCells({ 15 | this.boxBorder = true, 16 | this.spacing = _spacing, 17 | @required children, 18 | }) { 19 | final List newChildren = []; 20 | children.forEach((item) { 21 | if (item != children[0]) { 22 | newChildren.add( 23 | Padding( 24 | padding: EdgeInsets.only(left: spacing), 25 | child: _border 26 | ) 27 | ); 28 | } 29 | newChildren.add(item); 30 | }); 31 | 32 | // 判断是否添加容器边框 33 | if (boxBorder) { 34 | newChildren.add(_border); 35 | newChildren.insert(0, _border); 36 | } 37 | 38 | this.children = newChildren; 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return Container( 44 | decoration: BoxDecoration( 45 | color: Colors.white 46 | ), 47 | child: Column( 48 | children: children 49 | ) 50 | ); 51 | } 52 | } 53 | 54 | class WeCell extends StatelessWidget { 55 | // label 56 | Widget label; 57 | // 内容 58 | Widget content; 59 | // footer 60 | final Widget footer; 61 | // 对齐方式 62 | final Alignment align; 63 | // 间距 64 | final double spacing; 65 | // 最小高度 66 | final double minHeight; 67 | // 点击 68 | final Function onClick; 69 | 70 | WeCell({ 71 | label, 72 | content, 73 | this.footer, 74 | this.align = Alignment.centerRight, 75 | this.spacing = _spacing, 76 | this.minHeight = 46.0, 77 | this.onClick 78 | }) { 79 | // label 80 | if (label is String) { 81 | this.label = Text(label); 82 | } else { 83 | this.label = label; 84 | } 85 | 86 | // content 87 | if (content is String) { 88 | this.content = Text(content); 89 | } else { 90 | this.content = content; 91 | } 92 | } 93 | 94 | // 点击 95 | void onTap() { 96 | onClick(); 97 | } 98 | 99 | @override 100 | Widget build(BuildContext context) { 101 | List children = []; 102 | 103 | // label 104 | if (label is Widget) { 105 | children = [ 106 | label 107 | ]; 108 | 109 | if (content is Widget) { 110 | children.add( 111 | Expanded( 112 | flex: 1, 113 | child: Align( 114 | alignment: align, 115 | child: content 116 | ) 117 | ) 118 | ); 119 | } 120 | } else { 121 | children = [ 122 | Expanded( 123 | flex: 1, 124 | child: content 125 | ) 126 | ]; 127 | } 128 | 129 | // footer 130 | if (footer != null) { 131 | children.add( 132 | Padding( 133 | padding: EdgeInsets.only(left: 5), 134 | child: footer 135 | ) 136 | ); 137 | } 138 | 139 | final child = Container( 140 | constraints: BoxConstraints( 141 | minHeight: minHeight 142 | ), 143 | child: DefaultTextStyle( 144 | style: TextStyle( 145 | fontSize: 16.0, 146 | color: Colors.black 147 | ), 148 | child: Padding( 149 | padding: EdgeInsets.only(left: spacing, right: spacing), 150 | child: Row( 151 | crossAxisAlignment: CrossAxisAlignment.center, 152 | children: children 153 | ) 154 | ) 155 | ) 156 | ); 157 | 158 | if (onClick == null) { 159 | return child; 160 | } 161 | 162 | return Material( 163 | color: Colors.white, 164 | child: InkWell( 165 | onTap: onTap, 166 | child: child 167 | ) 168 | ); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /lib/components/form/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../cell/index.dart'; 3 | 4 | class WeForm extends StatefulWidget { 5 | final List children; 6 | 7 | WeForm({ 8 | key, 9 | @required this.children 10 | }) : super(key: key); 11 | 12 | static WeFormState of(BuildContext context) { 13 | final WeFormScope scope = context.inheritFromWidgetOfExactType(WeFormScope); 14 | return scope?.state; 15 | } 16 | 17 | @override 18 | WeFormState createState() => WeFormState(); 19 | } 20 | 21 | class WeFormState extends State { 22 | final Map formValue = {}; 23 | 24 | // 设置表单值 25 | void setValue(Map value) { 26 | setState(() { 27 | formValue.addAll(value); 28 | }); 29 | } 30 | 31 | validate() { 32 | print(formValue); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return WeFormScope( 38 | state: this, 39 | formValue: formValue, 40 | child: WeCells( 41 | children: widget.children 42 | ) 43 | ); 44 | } 45 | } 46 | 47 | class WeFormScope extends InheritedWidget { 48 | final WeFormState state; 49 | final formValue; 50 | 51 | WeFormScope({ 52 | Key key, 53 | this.state, 54 | this.formValue, 55 | Widget child 56 | }) : super(key: key, child: child); 57 | 58 | static WeFormScope of(BuildContext context) { 59 | return context.inheritFromWidgetOfExactType(WeFormScope); 60 | } 61 | 62 | //是否重建widget就取决于数据是否相同 63 | @override 64 | bool updateShouldNotify(WeFormScope oldWidget) { 65 | return formValue != oldWidget.formValue; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/components/icon/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | const String fontFamily = 'iconfont'; 4 | const String fontPackage = 'weui'; 5 | 6 | class WeIcons { 7 | // success 8 | static const IconData success = IconData(0xe640, fontFamily: fontFamily, fontPackage: fontPackage); 9 | // info 10 | static const IconData info = IconData(0xe6d0, fontFamily: fontFamily, fontPackage: fontPackage); 11 | // time 12 | static const IconData time = IconData(0xe65a, fontFamily: fontFamily, fontPackage: fontPackage); 13 | // close 14 | static const IconData close = IconData(0xe66b, fontFamily: fontFamily, fontPackage: fontPackage); 15 | // hook 16 | static const IconData hook = IconData(0xe631, fontFamily: fontFamily, fontPackage: fontPackage); 17 | // loading 18 | static const IconData loading = IconData(0xe770, fontFamily: fontFamily, fontPackage: fontPackage); 19 | // clear 20 | static const IconData clear = IconData(0xe67e, fontFamily: fontFamily, fontPackage: fontPackage); 21 | // down 22 | static const IconData down = IconData(0xe609, fontFamily: fontFamily, fontPackage: fontPackage); 23 | // del 24 | static const IconData del = IconData(0xe74c, fontFamily: fontFamily, fontPackage: fontPackage); 25 | // 三角形 26 | static const IconData triangle = IconData(0xe6b4, fontFamily: fontFamily, fontPackage: fontPackage); 27 | } 28 | -------------------------------------------------------------------------------- /lib/components/input/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../cell/index.dart'; 3 | import '../../utils/WidgetUtil.dart'; 4 | import '../icon/index.dart'; 5 | 6 | class WeInput extends StatefulWidget { 7 | // key 8 | final GlobalKey key; 9 | // label 10 | dynamic label; 11 | // 默认值 12 | final String defaultValue; 13 | // 最大行数 14 | final int maxLines; 15 | // 提示文字 16 | final String hintText; 17 | // footer 18 | final Widget footer; 19 | // 是否显示清除 20 | bool clearable; 21 | // 文字对其方式 22 | final TextAlign textAlign; 23 | // 输入框类型 24 | final TextInputType type; 25 | // 密码框 26 | final bool obscureText; 27 | // 样式 28 | final TextStyle style; 29 | // 是否自动获取光标 30 | final bool autofocus; 31 | // label宽度 32 | final double labelWidth; 33 | // onChange 34 | final Function(String value) onChange; 35 | 36 | WeInput({ 37 | label, 38 | this.key, 39 | this.defaultValue = '', 40 | this.maxLines = 1, 41 | this.hintText, 42 | this.footer, 43 | this.clearable = false, 44 | this.textAlign = TextAlign.start, 45 | this.type = TextInputType.text, 46 | this.obscureText = false, 47 | this.style, 48 | this.autofocus = false, 49 | this.labelWidth = 80.0, 50 | this.onChange 51 | }) : this.label = toTextWidget(label, 'label'), 52 | super(key: key); 53 | 54 | @override 55 | WeInputState createState() => WeInputState(); 56 | } 57 | 58 | class WeInputState extends State { 59 | final TextEditingController controller = TextEditingController(); 60 | 61 | WeInputState() { 62 | _init(); 63 | } 64 | 65 | // 初始化 66 | _init() { 67 | WidgetsBinding.instance.addPostFrameCallback((Duration time) { 68 | _setValue(widget.defaultValue); 69 | }); 70 | } 71 | 72 | // 清除value 73 | void _clearValue() { 74 | _setValue(''); 75 | if (widget.onChange is Function) { 76 | widget.onChange(''); 77 | } 78 | } 79 | 80 | // 输入框onChange 81 | void _onChange(String value) { 82 | if (widget.onChange is Function) { 83 | widget.onChange(value); 84 | } 85 | setState(() {}); 86 | } 87 | 88 | void _setValue(value) { 89 | setState(() { 90 | controller.text = value; 91 | }); 92 | } 93 | 94 | @override 95 | Widget build(BuildContext context) { 96 | // 清除按钮 97 | final Widget clearWidget = GestureDetector( 98 | onTap: _clearValue, 99 | child: Container( 100 | child: Icon( 101 | WeIcons.clear, 102 | color: Color(0xffc8c9cc), 103 | size: 25.0 104 | ) 105 | ) 106 | ); 107 | 108 | // label 109 | Widget label; 110 | if (widget.label is Widget) { 111 | label = Container( 112 | width: widget.labelWidth, 113 | child: widget.label 114 | ); 115 | } 116 | 117 | // footer 118 | Widget footer; 119 | if (widget.clearable) { 120 | footer = controller.text.length > 0 ? clearWidget : null; 121 | } else { 122 | footer = widget.footer; 123 | } 124 | 125 | return WeCell( 126 | // label 127 | label: label, 128 | footer: footer, 129 | content: DefaultTextStyle( 130 | style: TextStyle( 131 | fontSize: 16.0, 132 | color: Colors.black 133 | ), 134 | child: TextField( 135 | autofocus: widget.autofocus, 136 | textAlign: widget.textAlign, 137 | keyboardType: widget.type, 138 | obscureText: widget.obscureText, 139 | style: widget.style, 140 | controller: controller, 141 | onChanged: _onChange, 142 | maxLines: widget.maxLines, 143 | decoration: InputDecoration( 144 | border: InputBorder.none, 145 | hintText: widget.hintText 146 | ) 147 | ) 148 | ) 149 | ); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /lib/components/switch/index.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import '../theme.dart'; 3 | 4 | class UISwitch extends StatefulWidget { 5 | // 颜色 6 | final Color color; 7 | // 大小 8 | final double size; 9 | // 选中状态 10 | final bool checked; 11 | // 禁用状态 12 | final bool disabled; 13 | // 点击变化的回调函 14 | final Function(bool checked) onChange; 15 | 16 | UISwitch({ 17 | this.color = primary, 18 | this.size = 28.0, 19 | this.checked, 20 | this.disabled = false, 21 | this.onChange 22 | }); 23 | 24 | @override 25 | WeSwitchState createState() => WeSwitchState(); 26 | } 27 | 28 | class WeSwitchState extends State with TickerProviderStateMixin { 29 | double _width; 30 | double _height; 31 | bool _open = false; 32 | // 动画 33 | AnimationController controller; 34 | Animation moveAnimation; 35 | Animation colorAnimation; 36 | 37 | @override 38 | void initState() { 39 | _width = widget.size * 2; 40 | _height = widget.size + 2; 41 | _open = widget.checked == null ? false : widget.checked; 42 | // 初始化动画 43 | initAnimate(); 44 | super.initState(); 45 | } 46 | 47 | void initAnimate() { 48 | // 初始化动画 49 | controller = AnimationController( 50 | duration: Duration(milliseconds: 200), 51 | vsync: this 52 | ); 53 | // move 54 | moveAnimation = Tween(begin: 0.0, end: widget.size - 2) 55 | .animate( 56 | CurvedAnimation( 57 | parent: controller, 58 | curve: Curves.easeIn 59 | ) 60 | ) 61 | ..addListener(() { 62 | setState(() {}); 63 | }); 64 | // color 65 | colorAnimation = ColorTween(begin: Colors.white, end: widget.color) 66 | .animate( 67 | CurvedAnimation( 68 | parent: controller, 69 | curve: Curves.linear 70 | ) 71 | ); 72 | // 默认选中状态 73 | if (_open) controller.forward(); 74 | } 75 | 76 | void click() { 77 | final bool state = !_open; 78 | // 判断是否禁用 79 | if (widget.disabled) return; 80 | // checked为bool时候表示是受控组件 81 | if (widget.checked == null) { 82 | _open = state; 83 | toggleChecked(); 84 | } 85 | // onChange 86 | if (widget.onChange is Function) widget.onChange(state); 87 | } 88 | 89 | void toggleChecked() { 90 | if (_open) { 91 | controller.forward(); 92 | } else { 93 | controller.reverse(); 94 | } 95 | } 96 | 97 | @override 98 | void didUpdateWidget(UISwitch oldWidget) { 99 | super.didUpdateWidget(oldWidget); 100 | if (widget.checked != oldWidget.checked) { 101 | _open = widget.checked; 102 | toggleChecked(); 103 | } 104 | } 105 | 106 | @override 107 | void dispose() { 108 | controller.dispose(); 109 | super.dispose(); 110 | } 111 | 112 | @override 113 | Widget build(BuildContext context) { 114 | return SizedBox( 115 | width: _width, 116 | height: _height, 117 | child: Opacity( 118 | opacity: widget.disabled ? 0.6 : 1, 119 | child: GestureDetector( 120 | onTap: click, 121 | child: DecoratedBox( 122 | decoration: BoxDecoration( 123 | color: colorAnimation.value, 124 | border: Border.all(width: 1.0, color: Color.fromRGBO(0, 0, 0, 0.1)), 125 | borderRadius: BorderRadius.all(Radius.circular(25.0)) 126 | ), 127 | child: Stack( 128 | children: [ 129 | // icon 130 | Positioned( 131 | top: 1.0, 132 | left: 1.0, 133 | child: Transform.translate( 134 | offset: Offset(moveAnimation.value, 0.0), 135 | child: SizedBox( 136 | width: widget.size, 137 | height: widget.size, 138 | child: DecoratedBox( 139 | decoration: BoxDecoration( 140 | color: Colors.white, 141 | borderRadius: BorderRadius.all(Radius.circular(widget.size)), 142 | boxShadow: [ 143 | BoxShadow( 144 | color: Color.fromRGBO(0, 0, 0, 0.05), 145 | blurRadius: 1.0, 146 | spreadRadius: 0, 147 | offset: Offset(0, 3.0) 148 | ), 149 | 150 | BoxShadow( 151 | color: Color.fromRGBO(0, 0, 0, 0.1), 152 | blurRadius: 2.0, 153 | spreadRadius: 0, 154 | offset: Offset(0, 2.0) 155 | ), 156 | BoxShadow( 157 | color: Color.fromRGBO(0, 0, 0, 0.05), 158 | blurRadius: 3.0, 159 | spreadRadius: 0, 160 | offset: Offset(0, 3.0) 161 | ) 162 | ] 163 | ) 164 | ) 165 | ) 166 | ) 167 | ) 168 | ] 169 | ) 170 | ) 171 | ) 172 | ) 173 | ); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /lib/components/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | // 主色 4 | const Color primary = Color(0xff1AAD19); 5 | // 主色禁用 6 | const Color primaryDisabled = Color(0xff9ED99D); 7 | // 默认背景色 8 | const Color defaultBackgroundColor = Color(0xfff8f8f8); 9 | // 默认边框 10 | const Color defaultBorderColor = Color(0xffd8d8d8); 11 | // 遮罩背景色 12 | const Color maskColor = Color.fromRGBO(17, 17, 17, 0.6); 13 | // 警告色 14 | const Color warn = Color(0xffE64340); 15 | // 主色禁用 16 | const Color warnDisabled = Color(0xffEC8B89); -------------------------------------------------------------------------------- /lib/components/ui.dart: -------------------------------------------------------------------------------- 1 | library fmp; 2 | 3 | // UIButton 4 | export './button/index.dart'; 5 | // UISwitch 6 | export './switch/index.dart'; -------------------------------------------------------------------------------- /lib/console/JsonViewer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | typedef OnBuildNode = Widget Function( 4 | JsonNode parent, 5 | String nodeName, 6 | dynamic nodeValue, 7 | ); 8 | 9 | class JsonViewerRoot extends StatefulWidget { 10 | JsonViewerRoot({ 11 | /// json object 12 | /// 要展示的对象 13 | @required this.jsonObj, 14 | 15 | /// Auto-expand level 16 | /// 自动展开层级 17 | this.expandDeep = 2, 18 | 19 | /// Build node callback 20 | /// 构建节点的回调 21 | this.onBuildNode, 22 | }) { 23 | if (this.onBuildNode == null) { 24 | this.onBuildNode = this.onBuildNodeDefault; 25 | } 26 | } 27 | 28 | final dynamic jsonObj; 29 | final int expandDeep; 30 | OnBuildNode onBuildNode; 31 | 32 | /// 默认的构建节点的回调, 当需要创建新的节点时触发 33 | Widget onBuildNodeDefault( 34 | JsonNode parent, 35 | String nodeName, 36 | dynamic nodeValue, 37 | ) { 38 | JsonNode node; 39 | double leftOffset; 40 | if (nodeValue == null) { 41 | node = JsonViewerNode(); 42 | } else if (nodeValue is Map) { 43 | node = JsonViewerMapNode(); 44 | leftOffset = 10; 45 | } else if (nodeValue is List) { 46 | node = JsonViewerListNode(); 47 | leftOffset = 10; 48 | } else { 49 | node = JsonViewerNode(); 50 | leftOffset = 0; 51 | } 52 | node.root = parent != null ? parent.root : this; 53 | node.parent = parent; 54 | node.nodeName = nodeName; 55 | node.nodeValue = nodeValue; 56 | node.leftOffset = leftOffset; 57 | node.expandDeep = parent != null ? parent.expandDeep - 1 : this.expandDeep; 58 | return node; 59 | } 60 | 61 | @override 62 | State createState() => JsonViewerRootState(); 63 | } 64 | 65 | class JsonViewerRootState extends State { 66 | JsonViewerRootState(); 67 | 68 | @override 69 | void initState() { 70 | super.initState(); 71 | } 72 | 73 | @override 74 | Widget build(BuildContext context) { 75 | return this.widget.onBuildNode(null, "[root]", this.widget.jsonObj); 76 | } 77 | } 78 | 79 | abstract class JsonNode implements Widget { 80 | /// 最顶级的 81 | JsonViewerRoot root; 82 | 83 | /// 上一个节点 84 | JsonNode parent; 85 | 86 | /// 当前节点名 87 | String nodeName; 88 | 89 | /// 当前节点值 90 | T nodeValue; 91 | 92 | /// 左边偏移值 93 | double leftOffset; 94 | 95 | /// 自动展开层次, 每次构建节点减1 96 | int expandDeep; 97 | } 98 | 99 | abstract class JsonOpenNode implements Widget { 100 | bool isOpen = false; 101 | 102 | List buildChild(); 103 | } 104 | 105 | class JsonViewerMapNode extends StatefulWidget 106 | implements JsonNode>, JsonOpenNode { 107 | @override 108 | State createState() => JsonViewerMapNodeState(); 109 | 110 | @override 111 | JsonViewerRoot root; 112 | @override 113 | JsonNode parent; 114 | @override 115 | String nodeName; 116 | @override 117 | Map nodeValue; 118 | 119 | @override 120 | bool isOpen = false; 121 | 122 | @override 123 | double leftOffset; 124 | 125 | @override 126 | List buildChild() { 127 | List result = []; 128 | nodeValue.forEach((k, v) { 129 | result.add(root.onBuildNode(this, k, v)); 130 | }); 131 | return result; 132 | } 133 | 134 | @override 135 | int expandDeep; 136 | } 137 | 138 | /// map类型的节点 139 | /// 如: {"key":value} 140 | class JsonViewerMapNodeState extends State { 141 | @override 142 | void initState() { 143 | // TODO: implement initState 144 | super.initState(); 145 | widget.isOpen = widget.expandDeep > 0; 146 | } 147 | 148 | @override 149 | Widget build(BuildContext context) { 150 | Widget result = GestureDetector( 151 | onTap: () { 152 | this.setState(() { 153 | widget.isOpen = !widget.isOpen; 154 | }); 155 | }, 156 | child: Row( 157 | children: [ 158 | Icon(widget.isOpen ? Icons.arrow_drop_down : Icons.arrow_right), 159 | Text( 160 | widget.nodeName, 161 | style: TextStyle(color: Colors.indigo), 162 | ) 163 | ], 164 | )); 165 | if (widget.isOpen) { 166 | result = Column( 167 | children: [ 168 | result, 169 | Padding( 170 | padding: EdgeInsets.only(left: widget.leftOffset), 171 | child: Column( 172 | children: widget.buildChild(), 173 | ), 174 | ) 175 | ], 176 | ); 177 | } 178 | 179 | return result; 180 | } 181 | } 182 | 183 | /// list类型的节点 184 | /// 如: [value1,value2] 185 | class JsonViewerListNode extends StatefulWidget 186 | implements JsonNode>, JsonOpenNode { 187 | @override 188 | State createState() => JsonViewerListNodeState(); 189 | 190 | @override 191 | JsonViewerRoot root; 192 | @override 193 | JsonNode parent; 194 | @override 195 | String nodeName; 196 | @override 197 | List nodeValue; 198 | 199 | @override 200 | bool isOpen = false; 201 | 202 | @override 203 | double leftOffset; 204 | 205 | @override 206 | List buildChild() { 207 | List result = []; 208 | var i = 0; 209 | nodeValue.forEach((v) { 210 | result.add(root.onBuildNode(this, "[$i]", v)); 211 | i++; 212 | }); 213 | return result; 214 | } 215 | 216 | @override 217 | int expandDeep; 218 | } 219 | 220 | class JsonViewerListNodeState extends State { 221 | @override 222 | void initState() { 223 | super.initState(); 224 | widget.isOpen = widget.expandDeep > 0; 225 | } 226 | 227 | @override 228 | Widget build(BuildContext context) { 229 | Widget result = GestureDetector( 230 | onTap: () { 231 | this.setState(() { 232 | widget.isOpen = !widget.isOpen; 233 | }); 234 | }, 235 | child: Row( 236 | children: [ 237 | Icon(widget.isOpen ? Icons.arrow_drop_down : Icons.arrow_right), 238 | Text( 239 | widget.nodeName, 240 | style: TextStyle(color: Colors.deepPurple), 241 | ), 242 | Text( 243 | " [${widget.nodeValue.length}]", 244 | style: TextStyle(color: Colors.indigoAccent), 245 | ), 246 | ], 247 | )); 248 | if (widget.isOpen) { 249 | result = Column( 250 | children: [ 251 | result, 252 | Padding( 253 | padding: EdgeInsets.only(left: widget.leftOffset), 254 | child: Column( 255 | children: widget.buildChild(), 256 | ), 257 | ) 258 | ], 259 | ); 260 | } 261 | 262 | return result; 263 | } 264 | } 265 | 266 | class JsonViewerNode extends StatelessWidget implements JsonNode { 267 | @override 268 | Widget build(BuildContext context) { 269 | var color = Colors.black; 270 | if (this.nodeValue == null) { 271 | color = Colors.redAccent; 272 | } else { 273 | switch (this.nodeValue.runtimeType) { 274 | case bool: 275 | color = Colors.teal; 276 | break; 277 | case int: 278 | color = Colors.lightGreen; 279 | break; 280 | } 281 | } 282 | 283 | return Padding( 284 | padding: EdgeInsets.only(left: 24), 285 | child: Row( 286 | children: [ 287 | Text( 288 | this.nodeName, 289 | style: TextStyle(color: Colors.black54), 290 | ), 291 | Text(" : "), 292 | Text( 293 | this.nodeValue == null ? "null" : this.nodeValue.toString(), 294 | style: TextStyle(color: color), 295 | ), 296 | ], 297 | ), 298 | ); 299 | } 300 | 301 | @override 302 | JsonViewerRoot root; 303 | @override 304 | JsonNode parent; 305 | @override 306 | String nodeName; 307 | 308 | @override 309 | var nodeValue; 310 | 311 | @override 312 | double leftOffset; 313 | 314 | @override 315 | int expandDeep; 316 | } -------------------------------------------------------------------------------- /lib/engine/JSContext.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | const NAMESPACE = "io.jojodev.flutter.liquidcore"; 7 | 8 | /// This enables more verbose logging, if desired. 9 | bool enableLiquidCoreLogging = false; 10 | 11 | void liquidcoreLog(String message) { 12 | if (enableLiquidCoreLogging) { 13 | print(message); 14 | } 15 | } 16 | 17 | typedef void ExceptionHandler(String error); 18 | 19 | class JSException implements Exception { 20 | final String message; 21 | final String exceptionType; 22 | final String stackTraceString; 23 | 24 | JSException(this.message, this.exceptionType, this.stackTraceString); 25 | 26 | @override 27 | String toString() => 28 | 'JSException($message, $exceptionType)\n[\n$stackTraceString]'; 29 | } 30 | 31 | /// Extremely basic interface for a native Javascript context. 32 | /// Currently only supported on Android devices. 33 | class JSContext { 34 | /// Specifies that a property has no special attributes. 35 | static const JSPropertyAttributeNone = 0; 36 | 37 | /// Specifies that a property is read-only. 38 | static const JSPropertyAttributeReadOnly = 1 << 1; 39 | 40 | /// Specifies that a property should not be enumerated by 41 | /// JSPropertyEnumerators and JavaScript for...in loops. 42 | static const JSPropertyAttributeDontEnum = 1 << 2; 43 | 44 | /// Specifies that the delete operation should fail on a property. 45 | static const JSPropertyAttributeDontDelete = 1 << 3; 46 | 47 | static final MethodChannel _methodChannel = 48 | const MethodChannel('$NAMESPACE/jscontext') 49 | ..setMethodCallHandler(_platformCallHandler); 50 | 51 | static final _uuid = new Uuid(); 52 | static final _instances = new Map(); 53 | 54 | final EventChannel _jsContextExceptionChannel = 55 | const EventChannel("$NAMESPACE/jscontextException"); 56 | 57 | StreamSubscription _jsContextExceptionSubscription; 58 | 59 | ExceptionHandler _exceptionHandler; 60 | 61 | String _instanceId; 62 | var _jsFunctions = new Map(); 63 | 64 | JSContext() { 65 | _instanceId = _uuid.v4(); 66 | _instances[_instanceId] = this; 67 | } 68 | 69 | /// Set a property. 70 | /// 71 | /// [prop] The name of the property to set. 72 | /// 73 | /// [value] The object to set it to. If this is a function, due to limitations in Flutter's 74 | /// communication mechanism, it'll be converted to a promise on the Javascript side. 75 | /// 76 | /// [attributes] And OR'd list of JSProperty constants. 77 | Future setProperty(String prop, dynamic value, [int attributes]) { 78 | var arguments = { 79 | 'prop': prop, 80 | 'value': value, 81 | 'attr': attributes, 82 | }; 83 | if (value is Function) { 84 | var functionId = _uuid.v4(); 85 | _jsFunctions[functionId] = value; 86 | value = functionId; 87 | arguments['type'] = 'function'; 88 | arguments['value'] = functionId; 89 | } 90 | return _invokeMethod("setProperty", arguments); 91 | } 92 | 93 | /// Return a property. 94 | Future property(String prop) { 95 | var arguments = { 96 | 'prop': prop, 97 | }; 98 | 99 | return _invokeMethod("property", arguments).then((value) { 100 | return _transformValue(value); 101 | }); 102 | } 103 | 104 | /// Whether the current context contains a property [prop]. 105 | Future hasProperty(String prop) { 106 | return _invokeMethod("hasProperty", { 107 | 'prop': prop, 108 | }); 109 | } 110 | 111 | /// Returns true if the property was deleted. 112 | Future deleteProperty(String prop) { 113 | return _invokeMethod("deleteProperty", { 114 | 'prop': prop, 115 | }); 116 | } 117 | 118 | /// Free up the context resources. 119 | Future cleanUp() { 120 | return _invokeMethod("cleanUp"); 121 | } 122 | 123 | /// Executes the JavaScript code in [script] in this context 124 | /// 125 | /// [script] The code to execute 126 | /// [sourceURL] The URI of the source file, only used for reporting in stack trace (optional) 127 | /// [startingLineNumber] The beginning line number, only used for reporting in stack trace (optional) 128 | Future evaluateScript(String script, 129 | [String sourceURL, int startingLineNumber = 0]) { 130 | return _invokeMethod("evaluateScript", { 131 | 'script': script, 132 | 'sourceURL': sourceURL, 133 | 'startingLineNumber': startingLineNumber, 134 | }); 135 | } 136 | 137 | /// Sets the JS exception handler for this context. Any thrown JSException in this 138 | /// context will be sent to this handler. 139 | Future setExceptionHandler(ExceptionHandler exceptionHandler) { 140 | this._exceptionHandler = exceptionHandler; 141 | 142 | if (_jsContextExceptionSubscription == null) { 143 | // Listen to the exception event stream. 144 | _jsContextExceptionSubscription = 145 | _jsContextExceptionChannel.receiveBroadcastStream().listen((error) { 146 | if (_exceptionHandler != null) { 147 | _exceptionHandler(error); 148 | } 149 | }); 150 | } 151 | 152 | return _invokeMethod("setExceptionHandler", {}); 153 | } 154 | 155 | /// Clears a previously set exception handler. 156 | Future clearExceptionHandler() { 157 | this._exceptionHandler = null; 158 | if (_jsContextExceptionSubscription != null) { 159 | _jsContextExceptionSubscription.cancel(); 160 | _jsContextExceptionSubscription = null; 161 | } 162 | 163 | return _invokeMethod("clearExceptionHandler", {}); 164 | } 165 | 166 | /// Send a message over to the native implementation. 167 | Future _invokeMethod(String method, 168 | [Map arguments = const {}]) { 169 | Map withInstanceId = Map.of(arguments); 170 | withInstanceId['contextId'] = _instanceId; 171 | return _methodChannel.invokeMethod(method, withInstanceId); 172 | } 173 | 174 | /// Converts a value returned from the native platform into a suitable 175 | /// dart object. 176 | dynamic _transformValue(value) { 177 | if (value is Map) { 178 | // Return the function reference callback. 179 | if (value['__dart_liquidcore_type__'] == 'function') { 180 | return _jsFunctions[value['__dart_liquidcore_ref__']]; 181 | } else if (value['__dart_liquidcore_type__'] == 'exception') { 182 | return JSException(value['message'], value['type'], value['stack']); 183 | } 184 | } 185 | 186 | return value; 187 | } 188 | 189 | static Future _platformCallHandler(MethodCall call) async { 190 | liquidcoreLog('_platformCallHandler call ${call.method} ${call.arguments}'); 191 | var arguments = (call.arguments as Map); 192 | String contextId = arguments['contextId']; 193 | JSContext instance = _instances[contextId]; 194 | if (instance == null) { 195 | liquidcoreLog("jsContext $contextId was not found!"); 196 | return null; 197 | } 198 | dynamic value = arguments['value']; 199 | switch (call.method) { 200 | case 'dynamicFunction': 201 | var functionId = arguments['functionId']; 202 | List args = value; 203 | var func = instance._jsFunctions[functionId]; 204 | if (func != null) { 205 | var decodedArgs = 206 | args.map((arg) => instance._transformValue(arg)).toList(); 207 | return Function.apply(func, decodedArgs); 208 | } 209 | break; 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /lib/tags/BreakTag.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_mini_program/Page.dart'; 3 | import 'package:html/dom.dart' as dom; 4 | 5 | /// Creates a line break from a template
tag. 6 | class BreakTag extends StatelessWidget { 7 | final Page page; 8 | final dom.Element element; 9 | final Map style; 10 | 11 | BreakTag({this.page, this.element, this.style}); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | assert(element.localName == 'br'); 16 | return new Container(height: 8.0); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/tags/ButtonTag.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_mini_program/Page.dart'; 5 | import 'package:html/dom.dart' as dom; 6 | import 'package:flutter_mini_program/components/ui.dart'; 7 | 8 | /// Builds a icon from a