diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md deleted file mode 100644 index bde1342e..00000000 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ /dev/null @@ -1,26 +0,0 @@ ---- -name: "\U0001F41B Bug Report" -about: Something isn't working as expected ---- - -## Bug Report - -**仅限中文与英文**, 其他语言的提交将直接被关闭 - -请先确认查找了已有的 issue [GitHub issues](https://github.com/apache/incubator-shardingsphere-example/issues). - -为了更好的收录您反馈或者提交的相关pr. 请您关注您提交的问题, 我们可能需要更多的详细信息, 我们会在issue下先您收集相关信息, -如果长时间未得到您的回复, 如果我们无法在某些环境上重现该问题, 并且您**超过7天未回复**, 我们可能会关 **闭掉issue**, 谢谢 - - -### 您当前的flutter doctor信息 - -### 预期的表现 - -### 实际的表现 - -### 预期的分析 (给出您能想到, 任何您能想到的) - -### 重现的方式, 例如从 A界面 点击 b, 跳转到B页面, 界面出现溢出乱码等. - -### 用于重现此问题或者可能解决以上问题的示例代码(例如github 链接代码) diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md deleted file mode 100644 index 478e88df..00000000 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: "\U0001F680 Feature Request" -about: I have a suggestion ---- - -## Feature Request - -**仅限中文与英文**, 其他语言的提交将直接被关闭 - -请先确认查找了已有的 issue [GitHub issues](https://github.com/apache/incubator-shardingsphere-example/issues). - -为了更好的收录您反馈或者提交的相关pr. 请您关注您提交的问题, 我们可能需要更多的详细信息, 我们会在issue下先您收集相关信息, -如果长时间未得到您的回复, 如果我们无法在某些环境上重现该问题, 并且您**超过7天未回复**, 我们可能会关 **闭掉issue**, 谢谢 - - -### 您的功能需求是否与哪些问题有关? - -### 描述您想要的功能. diff --git a/.github/ISSUE_TEMPLATE/page-about.md b/.github/ISSUE_TEMPLATE/page-about.md deleted file mode 100644 index d79ca10a..00000000 --- a/.github/ISSUE_TEMPLATE/page-about.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -name: "📄 Page About" -about: something about page ---- - -## Page About - -**仅限中文与英文**, 其他语言的提交将直接被关闭 - -请先确认查找了已有的 issue [GitHub issues](https://github.com/apache/incubator-shardingsphere-example/issues). - -为了更好的收录您反馈或者提交的相关pr. 请您关注您提交的问题, 我们可能需要更多的详细信息, 我们会在issue下先您收集相关信息, -如果长时间未得到您的回复, 如果我们无法在某些环境上重现该问题, 并且您**超过7天未回复**, 我们可能会关 **闭掉issue**, 谢谢 - - - - -## 界面增加或者更新的内容概括 - -## 界面数据 - -例如: -``` -{ - "name": "standard_for_slider", - "screenShot": "", - "author":"sanfan", - "title":"slider组件", - "email": "hanxu@qq.com", - "desc": "slider, new Slider", - "id": "8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096" -} - -``` -## Page 关联的 DEMO 信息 - -例如: - -``` -{ - "name": "intor page", - "screenShot": "", - "author":"sanfan", - "title":"介绍页", - "email": "hanxu317@qq.com", - "desc": "desc", - "id": "ee4feb8e_32ae_4241_9c8a_5c9e1f92b096" -}, -{ - "name": "intor pag2e", - "screenShot": "", - "author":"sanfan", - "title":"介绍页", - "email": "hanxu317@qq.com", - "desc": "desc", - "id": "ee4feb8e_32ae_4241_9c8a_5c9e1f92b097" -} -``` - -## 引入第三方包的文件与版本号(如果有引入, 请标明) - - diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 0a142371..00000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -name: "\U0001F914 Question" -about: Usage question that isn't answered in docs or discussion ---- - -## Question - -**仅限中文与英文**, 其他语言的提交将直接被关闭 - -请先确认查找了已有的 issue [GitHub issues](https://github.com/apache/incubator-shardingsphere-example/issues). - -为了更好的收录您反馈或者提交的相关pr. 请您关注您提交的问题, 我们可能需要更多的详细信息, 我们会在issue下先您收集相关信息, -如果长时间未得到您的回复, 如果我们无法在某些环境上重现该问题, 并且您**超过7天未回复**, 我们可能会关 **闭掉issue**, 谢谢 - diff --git a/.github/issue_template.md b/.github/issue_template.md new file mode 100644 index 00000000..e69de29b diff --git a/.gitignore b/.gitignore index 26bbd932..639431bc 100644 --- a/.gitignore +++ b/.gitignore @@ -18,7 +18,6 @@ .metadata - # IntelliJ related *.iml *.ipr diff --git a/README-en.md b/README-en.md index 7862edad..e6a91cfe 100644 --- a/README-en.md +++ b/README-en.md @@ -85,4 +85,4 @@ The advantages of Flutter mainly include: -Powered by Alibaba Auction Front-end Team +Powered by [Alibaba Auction Front-end Team](https://github.com/alibaba-paimai-frontend) diff --git a/README.md b/README.md index bd2f01d0..8ea37a97 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ Language: [English](https://github.com/alibaba/flutter-go/blob/master/README-en.md) | [中文简体](https://github.com/alibaba/flutter-go/blob/master/README.md) ## Flutter Go -# test - ![https://img.alicdn.com/tfs/TB1OJkeHNYaK1RjSZFnXXa80pXa-229-229.png](https://img.alicdn.com/tfs/TB1OJkeHNYaK1RjSZFnXXa80pXa-229-229.png) > 帮助开发者快速上手 Flutter **Flutter Go 1.0 Android版已正式发布** @@ -22,37 +20,17 @@ Language: [English](https://github.com/alibaba/flutter-go/blob/master/README-en. -## 运行方式 -- 查看一下版本号是否正确 -```dart - flutter --version -``` -- 运行以下命令查看是否需要安装其它依赖项来完成安装 -```dart - flutter doctor -``` -- 运行启动您的应用 -```dart - flutter packages get - flutter run -``` - -- 如果有其他问题,请参考 - - https://flutterchina.club/setup-macos/ - - https://flutter.dev/docs/get-started/install/macos ## Release安装包下载地址 -### android正式版,下载地址: +android下载地址: -- 华为市场已上线,华为应用市场搜索 "Fluttergo"或者直接[点击下载](https://appstore.huawei.com/search/fluttergo) - + -### iphone正式版,下载地址: +iphone下载地址: AppStore上面进行搜索fluttego -- AppStore 搜索 "Fluttergo" 或者直接[点击下载](https://itunes.apple.com/cn/app/flutter-go/id1462026296?mt=8) - + ## 基础环境 @@ -145,5 +123,6 @@ flutter优点主要包括: - 大家的互相信任,尊重与支持,才是开源社区前进的动力和来源. -Powered by 阿里拍卖前端团队 +Powered by [阿里拍卖前端团队](https://github.com/alibaba-paimai-frontend) ++++++++ \ No newline at end of file diff --git a/android/app/build.gradle b/android/app/build.gradle index 847acc04..03806468 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -51,23 +51,8 @@ android { versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - - manifestPlaceholders = [ - JPUSH_PKGNAME : "com.alibaba.fluttergo", - JPUSH_APPKEY : "62eb07d227d1f11dd7fa6239", //JPush上注册的包名对应的appkey. - JPUSH_CHANNEL : "developer-default", - ] - -// ndk { -// //选择要添加的对应cpu类型的.so库。 -// abiFilters 'armeabi', 'armeabi-v7a','x86', 'x86_64', 'mips'//, 'arm64-v8a' -// // 还可以添加 'x86', 'x86_64', 'mips', 'mips64' -// } - } - - signingConfigs { release { keyAlias keystoreProperties['keyAlias'] @@ -101,8 +86,6 @@ dependencies { ///implementation 'com.google.firebase:firebase-perf:16.2.3' // 登陆 ////implementation 'com.google.firebase:firebase-auth:16.0.3' - - } //firebase apply plugin: 'com.google.gms.google-services' diff --git a/android/app/release/app-release.apk b/android/app/release/app-release.apk deleted file mode 100644 index 296ac626..00000000 Binary files a/android/app/release/app-release.apk and /dev/null differ diff --git a/android/app/release/output.json b/android/app/release/output.json index 5dae1a91..16e1b0c5 100644 --- a/android/app/release/output.json +++ b/android/app/release/output.json @@ -1 +1 @@ -[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0.6","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file +[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"0.0.5","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index b7417d00..80c74332 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -7,22 +7,20 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> - - - - + + + + android:label="fluttergo" + android:icon="@mipmap/ic_launcher_logo"> @@ -18,9 +14,9 @@ - + - + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index c6bb52b8..d94b59b8 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -22,8 +22,6 @@ ???? CFBundleVersion $(FLUTTER_BUILD_NUMBER) - LSApplicationCategoryType - LSRequiresIPhoneOS NSCameraUsageDescription diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements deleted file mode 100644 index 903def2a..00000000 --- a/ios/Runner/Runner.entitlements +++ /dev/null @@ -1,8 +0,0 @@ - - - - - aps-environment - development - - diff --git a/lib/api/api.dart b/lib/api/api.dart index 748e3422..93d4f6b8 100644 --- a/lib/api/api.dart +++ b/lib/api/api.dart @@ -1,36 +1,10 @@ class Api{ -// static const String BASE_URL = 'http://flutter-go.alibaba.net/'; - static const String BASE_URL = 'https://flutter-go.pub/api/'; + // static const String BASE_URL = 'http://127.0.0.1:6001/'; + static const String BASE_URL = 'http://flutter-go.alibaba.net/'; static const String DO_LOGIN = BASE_URL+'doLogin';//登陆 static const String CHECK_LOGIN = BASE_URL+'checkLogin';//验证登陆 static const String LOGOUT = BASE_URL+'logout';//退出登陆 - - static const String GET_USER_INFO = BASE_URL+'getUserInfo';//获取用户信息 - - - static const String VERSION = BASE_URL+'getAppVersion';//检查版本 - - static const String FEEDBACK = BASE_URL+'auth/feedback';//建议反馈 - -// static const String LOTOUT = BASE_URL+'logout';//退出登陆 - - static const String GET_ALL_COLLECTION = BASE_URL+'auth/getAllUserCollection';//获取全部收藏 - - static const String REMOVE_COLLECTION = BASE_URL+'auth/removeCollection';//移除收藏 - - static const String ADD_COLLECTION = BASE_URL+'auth/addCollection';//添加收藏 - - static const String CHECK_COLLECTED = BASE_URL+'checkCollected';//校验收藏 - - static const String SET_THEMECOLOR = BASE_URL+'auth/setThemeColor';//设置主题颜色 - - static const String GET_THEMECOLOR = BASE_URL +'/getThemeColor';//获取主题颜色 - - static const String GET_WIDGET_TREE = BASE_URL + 'getCateList';//获取widget列表树 - - static const String SEARCH_WIDGET = BASE_URL+'searchWidget';//搜索组件 -} - +} \ No newline at end of file diff --git a/lib/components/cate_card.dart b/lib/components/cate_card.dart index aab9fa48..4d43c5d0 100644 --- a/lib/components/cate_card.dart +++ b/lib/components/cate_card.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; +import '../model/cat.dart'; import '../resources/widget_name_to_icon.dart'; import '../components/widget_item_container.dart'; -import '../model/widget.dart'; class CateCard extends StatefulWidget { - final CategoryComponent category; + final Cat category; CateCard({@required this.category}); @override _CateCardState createState() => _CateCardState(); @@ -13,15 +13,28 @@ class CateCard extends StatefulWidget { class _CateCardState extends State { // 一级菜单目录下的二级Cat集合 - List _firstChildList; + List _firstChildList = new List(); + CatControlModel catControl = new CatControlModel(); @override void initState() { super.initState(); - _firstChildList = widget.category.children; + getFirstChildCategoriesByParentId(); } + // 获取一层目录下的二级内容 + getFirstChildCategoriesByParentId() async { + int parentId = widget.category.id; + // 构建查询条件 + Cat childCateCondition = new Cat(parentId: parentId); + List list = await catControl.getList(childCateCondition); + if (list.isNotEmpty&&list.length>=1 && this.mounted) { + setState(() { + _firstChildList = list; + }); + } + } @override Widget build(BuildContext context) { @@ -30,6 +43,7 @@ class _CateCardState extends State { //首字母转为大写 widget.category.name.substring(0, 1), widget.category.name.substring(0, 1).toUpperCase()); + return Container( width: screenWidth, padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), @@ -105,8 +119,9 @@ class _CateCardState extends State { ), ), child: WidgetItemContainer( - commonItems: this._firstChildList, - columnCount: 3 + categories: this._firstChildList, + columnCount: 3, + isWidgetPoint:false ), ); } diff --git a/lib/components/category.dart b/lib/components/category.dart index 0789e90e..7c6c0715 100644 --- a/lib/components/category.dart +++ b/lib/components/category.dart @@ -8,12 +8,11 @@ import '../model/widget.dart'; import '../widgets/index.dart'; import '../components/widget_item_container.dart'; - +enum CateOrWigdet { Cat, WidgetDemo } class CategoryHome extends StatefulWidget { - CategoryHome(this.token); - final String token; - + CategoryHome(this.name); + final String name; @override _CategoryHome createState() => new _CategoryHome(); @@ -22,11 +21,12 @@ class CategoryHome extends StatefulWidget { class _CategoryHome extends State { String title = ''; // 显示列表 cat or widget; - List items = []; - List widgetPoints = []; - List catHistory = new List(); - + List categories = []; + List widgetPoints = []; + List catHistory = new List(); + CatControlModel catControl = new CatControlModel(); + WidgetControlModel widgetControl = new WidgetControlModel(); // 所有的可用demos; List widgetDemosList = new WidgetDemoList().getDemos(); @@ -34,60 +34,80 @@ class _CategoryHome extends State { void initState() { super.initState(); // 初始化加入顶级的name - print("这是新界面的id:>>> ${widget.token}"); - - CommonItem targetGroup = Application.widgetTree.find(widget.token) ?? []; - print("targetGroup::: $targetGroup"); - - catHistory.add( - targetGroup - ); - this.setState(() { - items = targetGroup.children; + this.getCatByName(widget.name).then((Cat cat) { + catHistory.add(cat); + searchCatOrWigdet(); }); - searchCatOrWidget(); } - + Future getCatByName(String name) async { + return await catControl.getCatByName(name); + } Future back() { -// if (catHistory.length == 1) { -// return Future.value(true); -// } -// catHistory.removeLast(); -// searchCatOrWidget(); - return Future.value(true); + if (catHistory.length == 1) { + return Future.value(true); + } + catHistory.removeLast(); + searchCatOrWigdet(); + return Future.value(false); } - void go(CommonItem cat) { + void go(Cat cat) { catHistory.add(cat); - searchCatOrWidget(); + searchCatOrWigdet(); } - void searchCatOrWidget() async { - CommonItem widgetTree = Application.widgetTree; - // 假设进入这个界面的parent一定存在 - CommonItem targetGroup = catHistory.last; + void searchCatOrWigdet() async { + // 假设进入这个界面的parent一定存在 + Cat parentCat = catHistory.last; + // 继续搜索显示下一级depth: depth + 1, parentId: parentCat.id + List _categories = + await catControl.getList(new Cat(parentId: parentCat.id)); + List _widgetPoints = new List(); + if (_categories.isEmpty) { + _widgetPoints = + await widgetControl.getList(new WidgetPoint(catId: parentCat.id)); + } + this.setState(() { - title = targetGroup.name; + categories = _categories; + title = parentCat.name; + widgetPoints = _widgetPoints; }); } - void onCatgoryTap(CommonItem cat) { + void onCatgoryTap(Cat cat) { go(cat); } - + void onWidgetTap(WidgetPoint widgetPoint) { + String targetName = widgetPoint.name; + String targetRouter = '/category/error/404'; + widgetDemosList.forEach((item) { + // print("targetRouter = item.routerName> ${[item.name,targetName]}"); + if (item.name == targetName) { + targetRouter = item.routerName; + } + }); + Application.router.navigateTo(context, "$targetRouter"); + } Widget _buildContent() { WidgetItemContainer wiContaienr = WidgetItemContainer( columnCount: 3, - commonItems: items + categories: categories, + isWidgetPoint:false ); - - + if (widgetPoints.length > 0) { + wiContaienr = WidgetItemContainer( + categories: widgetPoints, + columnCount: 3, + isWidgetPoint:true + ); + } return Container( padding: const EdgeInsets.only(bottom: 10.0, top: 5.0), decoration: BoxDecoration( @@ -102,18 +122,14 @@ class _CategoryHome extends State { @override Widget build(BuildContext context) { - - return Scaffold( appBar: AppBar( - title: Text("$title"), + title: Text(title), ), body: WillPopScope( - onWillPop: () { return back(); }, - child: ListView( children: [ _buildContent(), diff --git a/lib/components/flutter_markdown/LICENSE b/lib/components/flutter_markdown/LICENSE deleted file mode 100644 index e7892520..00000000 --- a/lib/components/flutter_markdown/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2017 Google, Inc. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without -// modification, are permitted provided that the following conditions are -// met: -// -// * Redistributions of source code must retain the above copyright -// notice, this list of conditions and the following disclaimer. -// * Redistributions in binary form must reproduce the above -// copyright notice, this list of conditions and the following disclaimer -// in the documentation and/or other materials provided with the -// distribution. -// * Neither the name of Google Inc. nor the names of its -// contributors may be used to endorse or promote products derived from -// this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/components/flutter_markdown/README.md b/lib/components/flutter_markdown/README.md deleted file mode 100644 index 35c4a267..00000000 --- a/lib/components/flutter_markdown/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Flutter Markdown -[![pub package](https://img.shields.io/pub/v/flutter_markdown.svg)](https://pub.dartlang.org/packages/flutter_markdown) -[![Build Status](https://travis-ci.org/flutter/flutter_markdown.svg?branch=master)](https://travis-ci.org/flutter/flutter_markdown) - - -A markdown renderer for Flutter. It supports the -[original format](https://daringfireball.net/projects/markdown/), but no inline -html. - -## Getting Started - -Using the Markdown widget is simple, just pass in the source markdown as a -string: - - new Markdown(data: markdownSource); - -If you do not want the padding or scrolling behavior, use the MarkdownBody -instead: - - new MarkdownBody(data: markdownSource); - -By default, Markdown uses the formatting from the current material design theme, -but it's possible to create your own custom styling. Use the MarkdownStyle class -to pass in your own style. If you don't want to use Markdown outside of material -design, use the MarkdownRaw class. - -## Image support - -The `Img` tag only supports the following image locations: - -* From the network: Use a URL prefixed by either `http://` or `https://`. - -* From local files on the device: Use an absolute path to the file, for example by - concatenating the file name with the path returned by a known storage location, - such as those provided by the [`path_provider`](https://pub.dartlang.org/packages/path_provider) - plugin. - -* From image locations referring to bundled assets: Use an asset name prefixed by `resource:`. - like `resource:assets/image.png`. diff --git a/lib/components/flutter_markdown/lib/flutter_markdown.dart b/lib/components/flutter_markdown/lib/flutter_markdown.dart deleted file mode 100644 index 8d7ed6ea..00000000 --- a/lib/components/flutter_markdown/lib/flutter_markdown.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -/// A library to render markdown formatted text. -library flutter_markdown; - -export 'src/builder.dart'; -export 'src/style_sheet.dart'; -export 'src/widget.dart'; diff --git a/lib/components/flutter_markdown/lib/src/builder.dart b/lib/components/flutter_markdown/lib/src/builder.dart deleted file mode 100644 index 089e83c3..00000000 --- a/lib/components/flutter_markdown/lib/src/builder.dart +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io'; - -import 'package:flutter/gestures.dart'; -import 'package:flutter/widgets.dart'; -import 'package:markdown/markdown.dart' as md; -import 'package:path/path.dart' as p; -import 'style_sheet.dart'; - -typedef Widget DemoBuilder(Map attrs); - -final Set _kBlockTags = new Set.from([ - 'p', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'li', - 'blockquote', - 'pre', - 'ol', - 'ul', - 'hr', -]); - -const List _kListTags = const ['ul', 'ol']; - -bool _isBlockTag(String tag) => _kBlockTags.contains(tag); -bool _isListTag(String tag) => _kListTags.contains(tag); - -class _BlockElement { - _BlockElement(this.tag); - - final String tag; - final List children = []; - - int nextListIndex = 0; -} - -/// A collection of widgets that should be placed adjacent to (inline with) -/// other inline elements in the same parent block. -/// -/// Inline elements can be textual (a/em/strong) represented by [RichText] -/// widgets or images (img) represented by [Image.network] widgets. -/// -/// Inline elements can be nested within other inline elements, inheriting their -/// parent's style along with the style of the block they are in. -/// -/// When laying out inline widgets, first, any adjacent RichText widgets are -/// merged, then, all inline widgets are enclosed in a parent [Wrap] widget. -class _InlineElement { - _InlineElement(this.tag, {this.style}); - - final String tag; - - /// Created by merging the style defined for this element's [tag] in the - /// delegate's [MarkdownStyleSheet] with the style of its parent. - final TextStyle style; - - final List children = []; -} - -/// A delegate used by [MarkdownBuilder] to control the widgets it creates. -abstract class MarkdownBuilderDelegate { - /// Returns a gesture recognizer to use for an `a` element with the given - /// `href` attribute. - GestureRecognizer createLink(String href); - - /// Returns formatted text to use to display the given contents of a `pre` - /// element. - /// - /// The `styleSheet` is the value of [MarkdownBuilder.styleSheet]. - TextSpan formatText(MarkdownStyleSheet styleSheet, String code); -} - -/// Builds a [Widget] tree from parsed Markdown. -/// -/// See also: -/// -/// * [Markdown], which is a widget that parses and displays Markdown. -class MarkdownBuilder implements md.NodeVisitor { - /// Creates an object that builds a [Widget] tree from parsed Markdown. - MarkdownBuilder({ - this.delegate, - this.styleSheet, - this.imageDirectory, - this.demoParser - }); - - /// A delegate that controls how link and `pre` elements behave. - final MarkdownBuilderDelegate delegate; - - /// Defines which [TextStyle] objects to use for each type of element. - final MarkdownStyleSheet styleSheet; - - final DemoBuilder demoParser; - /// The base directory holding images referenced by Img tags with local file paths. - final Directory imageDirectory; - - final List _listIndents = []; - final List<_BlockElement> _blocks = <_BlockElement>[]; - final List<_InlineElement> _inlines = <_InlineElement>[]; - final List _linkHandlers = []; - - - /// Returns widgets that display the given Markdown nodes. - /// - /// The returned widgets are typically used as children in a [ListView]. - List build(List nodes) { - _listIndents.clear(); - _blocks.clear(); - _inlines.clear(); - _linkHandlers.clear(); - - _blocks.add(new _BlockElement(null)); - - for (md.Node node in nodes) { - assert(_blocks.length == 1); - node.accept(this); - } - - assert(_inlines.isEmpty); - return _blocks.single.children; - } - - @override - void visitText(md.Text text) { - if (_blocks.last.tag == null) // Don't allow text directly under the root. - return; - - _addParentInlineIfNeeded(_blocks.last.tag); - - final TextSpan span = _blocks.last.tag == 'pre' - ? delegate.formatText(styleSheet, text.text) - : new TextSpan( - style: _inlines.last.style, - text: text.text, - recognizer: _linkHandlers.isNotEmpty ? _linkHandlers.last : null, - ); - - _inlines.last.children.add(new RichText( - textScaleFactor: styleSheet.textScaleFactor, - text: span, - )); - } - - @override - bool visitElementBefore(md.Element element) { -// print("visitElementBefore ${element.tag}"); - final String tag = element.tag; - if (_isBlockTag(tag)) { - _addAnonymousBlockIfNeeded(styleSheet.styles[tag]); - if (_isListTag(tag)) - _listIndents.add(tag); - _blocks.add(new _BlockElement(tag)); - } else { - _addParentInlineIfNeeded(_blocks.last.tag); - - TextStyle parentStyle = _inlines.last.style; - _inlines.add(new _InlineElement( - tag, - style: parentStyle.merge(styleSheet.styles[tag]), - )); - } - - if (tag == 'a') { - _linkHandlers.add(delegate.createLink(element.attributes['href'])); - } - return true; - } - - @override - void visitElementAfter(md.Element element) { - final String tag = element.tag; - if (_isBlockTag(tag)) { - _addAnonymousBlockIfNeeded(styleSheet.styles[tag]); - - final _BlockElement current = _blocks.removeLast(); - Widget child; - - if (current.children.isNotEmpty) { - child = new Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: current.children, - ); - } else { - child = const SizedBox(); - } - - if (_isListTag(tag)) { - assert(_listIndents.isNotEmpty); - _listIndents.removeLast(); - } else if (tag == 'li') { - if (_listIndents.isNotEmpty) { - child = new Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - new SizedBox( - width: styleSheet.listIndent, - child: _buildBullet(_listIndents.last), - ), - new Expanded(child: child) - ], - ); - } - } else if (tag == 'blockquote') { - child = new DecoratedBox( - decoration: styleSheet.blockquoteDecoration, - child: new Padding( - padding: new EdgeInsets.all(styleSheet.blockquotePadding), - child: child, - ), - ); - } else if (tag == 'pre') { - child = new DecoratedBox( - decoration: styleSheet.codeblockDecoration, - child: new Padding( - padding: new EdgeInsets.all(styleSheet.codeblockPadding), - child: child, - ), - ); - } else if (tag == 'hr') { - child = new DecoratedBox( - decoration: styleSheet.horizontalRuleDecoration, - child: child, - ); - } - _addBlockChild(child); - } else { - - final _InlineElement current = _inlines.removeLast(); - final _InlineElement parent = _inlines.last; - - if (tag == 'img') { - // create an image widget for this image - current.children.add(_buildImage(element.attributes['src'])); - } else if (tag == 'a') { - _linkHandlers.removeLast(); - } else if (tag == 'demo') { - current.children.add(_buildGoDemos(element.attributes)); - } - - - if (current.children.isNotEmpty) { - parent.children.addAll(current.children); - } - } - } - Widget _buildGoDemos(Map attrs) { - Widget targetGoDemos; - - if (demoParser != null) { - targetGoDemos = demoParser(attrs); - } - - return targetGoDemos ?? new Text('demo not exits'); - } - - Widget _buildImage(String src) { - final List parts = src.split('#'); - if (parts.isEmpty) - return const SizedBox(); - - final String path = parts.first; - double width; - double height; - if (parts.length == 2) { - final List dimensions = parts.last.split('x'); - if (dimensions.length == 2) { - width = double.parse(dimensions[0]); - height = double.parse(dimensions[1]); - } - } - - Uri uri = Uri.parse(path); - Widget child; - if (uri.scheme == 'http' || uri.scheme == 'https') { - child = new Image.network(uri.toString(), width: width, height: height); - } else if (uri.scheme == 'data') { - child = _handleDataSchemeUri(uri, width, height); - } else if (uri.scheme == "resource") { - child = new Image.asset(path.substring(9), width: width, height: height); - } else { - String filePath = (imageDirectory == null - ? uri.toFilePath() - : p.join(imageDirectory.path, uri.toFilePath())); - child = new Image.file(new File(filePath), width: width, height: height); - } - - if (_linkHandlers.isNotEmpty) { - TapGestureRecognizer recognizer = _linkHandlers.last; - return new GestureDetector(child: child, onTap: recognizer.onTap); - } else { - return child; - } - } - - Widget _handleDataSchemeUri(Uri uri, final double width, final double height) { - final String mimeType = uri.data.mimeType; - if (mimeType.startsWith('image/')) { - return new Image.memory(uri.data.contentAsBytes(), width: width, height: height); - } else if (mimeType.startsWith('text/')) { - return new Text(uri.data.contentAsString()); - } - return const SizedBox(); - } - - Widget _buildBullet(String listTag) { - if (listTag == 'ul') - return new Text('•', textAlign: TextAlign.center, style: styleSheet.styles['li']); - - final int index = _blocks.last.nextListIndex; - return new Padding( - padding: const EdgeInsets.only(right: 5.0), - child: new Text('${index + 1}.', textAlign: TextAlign.right, style: styleSheet.styles['li']), - ); - } - - void _addParentInlineIfNeeded(String tag) { - if (_inlines.isEmpty) { - _inlines.add(new _InlineElement( - tag, - style: styleSheet.styles[tag], - )); - } - } - - void _addBlockChild(Widget child) { - final _BlockElement parent = _blocks.last; - if (parent.children.isNotEmpty) - parent.children.add(new SizedBox(height: styleSheet.blockSpacing)); - parent.children.add(child); - parent.nextListIndex += 1; - } - - void _addAnonymousBlockIfNeeded(TextStyle style) { - if (_inlines.isEmpty) { - return; - } - - final _InlineElement inline = _inlines.single; - if (inline.children.isNotEmpty) { - List mergedInlines = _mergeInlineChildren(inline); - final Wrap wrap = new Wrap(children: mergedInlines); - _addBlockChild(wrap); - _inlines.clear(); - } - } - - /// Merges adjacent [TextSpan] children of the given [_InlineElement] - List _mergeInlineChildren(_InlineElement inline) { - List mergedTexts = []; - for (Widget child in inline.children) { - if (mergedTexts.isNotEmpty && mergedTexts.last is RichText && child is RichText) { - RichText previous = mergedTexts.removeLast(); - List children = previous.text.children != null - ? new List.from(previous.text.children) - : [previous.text]; - children.add(child.text); - TextSpan mergedSpan = new TextSpan(children: children); - mergedTexts.add(new RichText( - textScaleFactor: styleSheet.textScaleFactor, - text: mergedSpan, - )); - } else { - mergedTexts.add(child); - } - } - return mergedTexts; - } -} diff --git a/lib/components/flutter_markdown/lib/src/style_sheet.dart b/lib/components/flutter_markdown/lib/src/style_sheet.dart deleted file mode 100644 index 6c44b773..00000000 --- a/lib/components/flutter_markdown/lib/src/style_sheet.dart +++ /dev/null @@ -1,307 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'package:flutter/material.dart'; - -/// Defines which [TextStyle] objects to use for which Markdown elements. -class MarkdownStyleSheet { - /// Creates an explicit mapping of [TextStyle] objects to Markdown elements. - MarkdownStyleSheet({ - this.a, - this.p, - this.code, - this.h1, - this.h2, - this.h3, - this.h4, - this.h5, - this.h6, - this.em, - this.strong, - this.blockquote, - this.img, - this.blockSpacing, - this.listIndent, - this.blockquotePadding, - this.blockquoteDecoration, - this.codeblockPadding, - this.codeblockDecoration, - this.horizontalRuleDecoration, - this.textScaleFactor = 1.0 - }) : _styles = { - 'a': a, - 'p': p, - 'li': p, - 'code': code, - 'pre': p, - 'h1': h1, - 'h2': h2, - 'h3': h3, - 'h4': h4, - 'h5': h5, - 'h6': h6, - 'em': em, - 'strong': strong, - 'blockquote': blockquote, - 'img': img, - }; - - /// Creates a [MarkdownStyleSheet] from the [TextStyle]s in the provided [ThemeData]. - factory MarkdownStyleSheet.fromTheme(ThemeData theme) { - assert(theme?.textTheme?.body1?.fontSize != null); - return new MarkdownStyleSheet( - a: const TextStyle(color: Colors.blue), - p: theme.textTheme.body1, - code: new TextStyle( - color: Colors.grey.shade700, - fontFamily: "monospace", - fontSize: theme.textTheme.body1.fontSize * 0.85 - ), - h1: theme.textTheme.headline, - h2: theme.textTheme.title, - h3: theme.textTheme.subhead, - h4: theme.textTheme.body2, - h5: theme.textTheme.body2, - h6: theme.textTheme.body2, - em: const TextStyle(fontStyle: FontStyle.italic), - strong: const TextStyle(fontWeight: FontWeight.bold), - blockquote: theme.textTheme.body1, - img: theme.textTheme.body1, - blockSpacing: 8.0, - listIndent: 32.0, - blockquotePadding: 8.0, - blockquoteDecoration: new BoxDecoration( - color: Colors.blue.shade100, - borderRadius: new BorderRadius.circular(2.0) - ), - codeblockPadding: 8.0, - codeblockDecoration: new BoxDecoration( - color: Colors.grey.shade100, - borderRadius: new BorderRadius.circular(2.0) - ), - horizontalRuleDecoration: new BoxDecoration( - border: new Border( - top: new BorderSide(width: 5.0, color: Colors.grey.shade300) - ), - ), - ); - } - - /// Creates a [MarkdownStyle] from the [TextStyle]s in the provided [ThemeData]. - /// - /// This constructor uses larger fonts for the headings than in - /// [MarkdownStyle.fromTheme]. - factory MarkdownStyleSheet.largeFromTheme(ThemeData theme) { - return new MarkdownStyleSheet( - a: const TextStyle(color: Colors.blue), - p: theme.textTheme.body1, - code: new TextStyle( - color: Colors.grey.shade700, - fontFamily: "monospace", - fontSize: theme.textTheme.body1.fontSize * 0.85 - ), - h1: theme.textTheme.display3, - h2: theme.textTheme.display2, - h3: theme.textTheme.display1, - h4: theme.textTheme.headline, - h5: theme.textTheme.title, - h6: theme.textTheme.subhead, - em: const TextStyle(fontStyle: FontStyle.italic), - strong: const TextStyle(fontWeight: FontWeight.bold), - blockquote: theme.textTheme.body1, - img: theme.textTheme.body1, - blockSpacing: 8.0, - listIndent: 32.0, - blockquotePadding: 8.0, - blockquoteDecoration: new BoxDecoration( - color: Colors.blue.shade100, - borderRadius: new BorderRadius.circular(2.0) - ), - codeblockPadding: 8.0, - codeblockDecoration: new BoxDecoration( - color: Colors.grey.shade100, - borderRadius: new BorderRadius.circular(2.0) - ), - horizontalRuleDecoration: new BoxDecoration( - border: new Border( - top: new BorderSide(width: 5.0, color: Colors.grey.shade300) - ), - ), - ); - } - - /// Creates a new [MarkdownStyleSheet] based on the current style, with the - /// provided parameters overridden. - MarkdownStyleSheet copyWith({ - TextStyle a, - TextStyle p, - TextStyle code, - TextStyle h1, - TextStyle h2, - TextStyle h3, - TextStyle h4, - TextStyle h5, - TextStyle h6, - TextStyle em, - TextStyle strong, - TextStyle blockquote, - TextStyle img, - double blockSpacing, - double listIndent, - double blockquotePadding, - Decoration blockquoteDecoration, - double codeblockPadding, - Decoration codeblockDecoration, - Decoration horizontalRuleDecoration, - double textScaleFactor, - }) { - return new MarkdownStyleSheet( - a: a ?? this.a, - p: p ?? this.p, - code: code ?? this.code, - h1: h1 ?? this.h1, - h2: h2 ?? this.h2, - h3: h3 ?? this.h3, - h4: h4 ?? this.h4, - h5: h5 ?? this.h5, - h6: h6 ?? this.h6, - em: em ?? this.em, - strong: strong ?? this.strong, - blockquote: blockquote ?? this.blockquote, - img: img ?? this.img, - blockSpacing: blockSpacing ?? this.blockSpacing, - listIndent: listIndent ?? this.listIndent, - blockquotePadding: blockquotePadding ?? this.blockquotePadding, - blockquoteDecoration: blockquoteDecoration ?? this.blockquoteDecoration, - codeblockPadding: codeblockPadding ?? this.codeblockPadding, - codeblockDecoration: codeblockDecoration ?? this.codeblockDecoration, - horizontalRuleDecoration: horizontalRuleDecoration ?? this.horizontalRuleDecoration, - textScaleFactor : textScaleFactor ?? this.textScaleFactor, - ); - } - - /// The [TextStyle] to use for `a` elements. - final TextStyle a; - - /// The [TextStyle] to use for `p` elements. - final TextStyle p; - - /// The [TextStyle] to use for `code` elements. - final TextStyle code; - - /// The [TextStyle] to use for `h1` elements. - final TextStyle h1; - - /// The [TextStyle] to use for `h2` elements. - final TextStyle h2; - - /// The [TextStyle] to use for `h3` elements. - final TextStyle h3; - - /// The [TextStyle] to use for `h4` elements. - final TextStyle h4; - - /// The [TextStyle] to use for `h5` elements. - final TextStyle h5; - - /// The [TextStyle] to use for `h6` elements. - final TextStyle h6; - - /// The [TextStyle] to use for `em` elements. - final TextStyle em; - - /// The [TextStyle] to use for `strong` elements. - final TextStyle strong; - - /// The [TextStyle] to use for `blockquote` elements. - final TextStyle blockquote; - - /// The [TextStyle] to use for `img` elements. - final TextStyle img; - - /// The amount of vertical space to use between block-level elements. - final double blockSpacing; - - /// The amount of horizontal space to indent list items. - final double listIndent; - - /// The padding to use for `blockquote` elements. - final double blockquotePadding; - - /// The decoration to use behind `blockquote` elements. - final Decoration blockquoteDecoration; - - /// The padding to use for `pre` elements. - final double codeblockPadding; - - /// The decoration to use behind for `pre` elements. - final Decoration codeblockDecoration; - - /// The decoration to use for `hr` elements. - final Decoration horizontalRuleDecoration; - - // The text scale factor to use in textual elements - final double textScaleFactor; - - /// A [Map] from element name to the corresponding [TextStyle] object. - Map get styles => _styles; - Map _styles; - - @override - bool operator ==(dynamic other) { - if (identical(this, other)) - return true; - if (other.runtimeType != MarkdownStyleSheet) - return false; - final MarkdownStyleSheet typedOther = other; - return typedOther.a == a - && typedOther.p == p - && typedOther.code == code - && typedOther.h1 == h1 - && typedOther.h2 == h2 - && typedOther.h3 == h3 - && typedOther.h4 == h4 - && typedOther.h5 == h5 - && typedOther.h6 == h6 - && typedOther.em == em - && typedOther.strong == strong - && typedOther.blockquote == blockquote - && typedOther.img == img - && typedOther.blockSpacing == blockSpacing - && typedOther.listIndent == listIndent - && typedOther.blockquotePadding == blockquotePadding - && typedOther.blockquoteDecoration == blockquoteDecoration - && typedOther.codeblockPadding == codeblockPadding - && typedOther.codeblockDecoration == codeblockDecoration - && typedOther.horizontalRuleDecoration == horizontalRuleDecoration - && typedOther.textScaleFactor == textScaleFactor; - } - - @override - int get hashCode { - return hashList([ - a, - p, - code, - h1, - h2, - h3, - h4, - h5, - h6, - em, - strong, - blockquote, - img, - blockSpacing, - listIndent, - blockquotePadding, - blockquoteDecoration, - codeblockPadding, - codeblockDecoration, - horizontalRuleDecoration, - textScaleFactor, - ]); - } -} diff --git a/lib/components/flutter_markdown/lib/src/widget.dart b/lib/components/flutter_markdown/lib/src/widget.dart deleted file mode 100644 index c9d5cbaf..00000000 --- a/lib/components/flutter_markdown/lib/src/widget.dart +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright 2016 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:io'; - -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:markdown/markdown.dart' as md; -import 'package:meta/meta.dart'; - -import 'builder.dart'; -import 'style_sheet.dart'; -// -typedef Widget ItemDemoBuilder(Map attrs); - -/// Signature for callbacks used by [MarkdownWidget] when the user taps a link. -/// -/// Used by [MarkdownWidget.onTapLink]. -typedef void MarkdownTapLinkCallback(String href); - -/// Creates a format [TextSpan] given a string. -/// -/// Used by [MarkdownWidget] to highlight the contents of `pre` elements. -abstract class SyntaxHighlighter { // ignore: one_member_abstracts - /// Returns the formated [TextSpan] for the given string. - TextSpan format(String source); -} - -/// A base class for widgets that parse and display Markdown. -/// -/// Supports all standard Markdown from the original -/// [Markdown specification](https://daringfireball.net/projects/markdown/). -/// -/// See also: -/// -/// * [Markdown], which is a scrolling container of Markdown. -/// * [MarkdownBody], which is a non-scrolling container of Markdown. -/// * -abstract class MarkdownWidget extends StatefulWidget { - /// Creates a widget that parses and displays Markdown. - /// - /// The [data] argument must not be null. - const MarkdownWidget({ - Key key, - @required this.data, - this.styleSheet, - this.syntaxHighlighter, - this.onTapLink, - this.imageDirectory, - this.demoBuilder, - }) : assert(data != null), - super(key: key); - - /// The Markdown to display. - final String data; - - /// The styles to use when displaying the Markdown. - /// - /// If null, the styles are inferred from the current [Theme]. - final MarkdownStyleSheet styleSheet; - - /// The syntax highlighter used to color text in `pre` elements. - /// - /// If null, the [MarkdownStyleSheet.code] style is used for `pre` elements. - final SyntaxHighlighter syntaxHighlighter; - - /// Called when the user taps a link. - final MarkdownTapLinkCallback onTapLink; - - /// The base directory holding images referenced by Img tags with local file paths. - final Directory imageDirectory; - - final ItemDemoBuilder demoBuilder; - /// Subclasses should override this function to display the given children, - /// which are the parsed representation of [data]. - @protected - Widget build(BuildContext context, List children); - - @override - _MarkdownWidgetState createState() => new _MarkdownWidgetState(); -} - -class DemosSyntax extends md.InlineSyntax { - DemosSyntax() : super('\\[demo:([a-z0-9_+-]+)\\]'); - bool onMatch(parser, match) { - var anchor = new md.Element.empty('demo'); - anchor.attributes['id'] = match[1]; - parser.addNode(anchor); - return true; - } -} - -class _MarkdownWidgetState extends State implements MarkdownBuilderDelegate { - List _children; - final List _recognizers = []; - - @override - void didChangeDependencies() { - _parseMarkdown(); - super.didChangeDependencies(); - } - - @override - void didUpdateWidget(MarkdownWidget oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.data != oldWidget.data - || widget.styleSheet != oldWidget.styleSheet) - _parseMarkdown(); - } - - @override - void dispose() { - _disposeRecognizers(); - super.dispose(); - } - - void _parseMarkdown() { - final MarkdownStyleSheet styleSheet = widget.styleSheet ?? new MarkdownStyleSheet.fromTheme(Theme.of(context)); - - _disposeRecognizers(); - - // TODO: This can be optimized by doing the split and removing \r at the same time - final List lines = widget.data.replaceAll('\r\n', '\n').split('\n'); - final md.ExtensionSet extens = new md.ExtensionSet([ - md.FencedCodeBlockSyntax() - ], [ - new DemosSyntax(), - new md.InlineHtmlSyntax(), - ]); - final md.Document document = new md.Document(encodeHtml: false, extensionSet: extens); - final MarkdownBuilder builder = new MarkdownBuilder( - delegate: this, - styleSheet: styleSheet, - imageDirectory: widget.imageDirectory, - demoParser: widget.demoBuilder - ); - _children = builder.build(document.parseLines(lines)); - } - - void _disposeRecognizers() { - if (_recognizers.isEmpty) - return; - final List localRecognizers = new List.from(_recognizers); - _recognizers.clear(); - for (GestureRecognizer recognizer in localRecognizers) - recognizer.dispose(); - } - - @override - GestureRecognizer createLink(String href) { - final TapGestureRecognizer recognizer = new TapGestureRecognizer() - ..onTap = () { - if (widget.onTapLink != null) - widget.onTapLink(href); - }; - _recognizers.add(recognizer); - return recognizer; - } - - @override - TextSpan formatText(MarkdownStyleSheet styleSheet, String code) { - if (widget.syntaxHighlighter != null) - return widget.syntaxHighlighter.format(code); - return new TextSpan(style: styleSheet.code, text: code); - } - - @override - Widget build(BuildContext context) => widget.build(context, _children); -} - -/// A non-scrolling widget that parses and displays Markdown. -/// -/// Supports all standard Markdown from the original -/// [Markdown specification](https://daringfireball.net/projects/markdown/). -/// -/// See also: -/// -/// * [Markdown], which is a scrolling container of Markdown. -/// * -class MarkdownBody extends MarkdownWidget { - /// Creates a non-scrolling widget that parses and displays Markdown. - const MarkdownBody({ - Key key, - String data, - MarkdownStyleSheet styleSheet, - SyntaxHighlighter syntaxHighlighter, - MarkdownTapLinkCallback onTapLink, - Directory imageDirectory, - ItemDemoBuilder demoBuilder, - }) : super( - key: key, - data: data, - styleSheet: styleSheet, - syntaxHighlighter: syntaxHighlighter, - onTapLink: onTapLink, - imageDirectory: imageDirectory, - demoBuilder: demoBuilder - ); - - @override - Widget build(BuildContext context, List children) { - if (children.length == 1) - return children.single; - return new Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: children, - ); - } -} - -/// A scrolling widget that parses and displays Markdown. -/// -/// Supports all standard Markdown from the original -/// [Markdown specification](https://daringfireball.net/projects/markdown/). -/// -/// See also: -/// -/// * [MarkdownBody], which is a non-scrolling container of Markdown. -/// * -class Markdown extends MarkdownWidget { - /// Creates a scrolling widget that parses and displays Markdown. - const Markdown({ - Key key, - String data, - MarkdownStyleSheet styleSheet, - SyntaxHighlighter syntaxHighlighter, - MarkdownTapLinkCallback onTapLink, - Directory imageDirectory, - this.padding: const EdgeInsets.all(16.0), - }) : super( - key: key, - data: data, - styleSheet: styleSheet, - syntaxHighlighter: syntaxHighlighter, - onTapLink: onTapLink, - imageDirectory: imageDirectory, - ); - - /// The amount of space by which to inset the children. - final EdgeInsets padding; - - @override - Widget build(BuildContext context, List children) { - return new ListView(padding: padding, children: children); - } -} diff --git a/lib/components/full_screen_code_dialog.dart b/lib/components/full_screen_code_dialog.dart index c8f05476..76147749 100644 --- a/lib/components/full_screen_code_dialog.dart +++ b/lib/components/full_screen_code_dialog.dart @@ -9,10 +9,9 @@ import 'package:flutter_go/utils/example_code_parser.dart'; import 'package:flutter_go/utils/syntax_highlighter.dart'; class FullScreenCodeDialog extends StatefulWidget { - const FullScreenCodeDialog({this.filePath, this.remoteFilePath}); + const FullScreenCodeDialog({this.filePath}); final String filePath; - final String remoteFilePath; _FullScreenCodeDialogState createState() => _FullScreenCodeDialogState(); } diff --git a/lib/components/list_refresh.dart b/lib/components/list_refresh.dart index c5d3b0d8..b4a13244 100644 --- a/lib/components/list_refresh.dart +++ b/lib/components/list_refresh.dart @@ -174,7 +174,6 @@ class _ListRefreshState extends State { return widget.renderItem(index, items[index]); } } - return null; }, controller: _scrollController, ), diff --git a/lib/components/loading.dart b/lib/components/loading.dart deleted file mode 100644 index 659b2ff8..00000000 --- a/lib/components/loading.dart +++ /dev/null @@ -1,94 +0,0 @@ -// -// Created with Android Studio. -// User: 三帆 -// Date: 07/08/2019 -// Time: 08:40 -// email: sanfan.hx@alibaba-inc.com -// tartget: 代码获取自: https://blog.csdn.net/O_time/article/details/86496537 -// -import 'dart:async'; -import 'package:flutter/material.dart'; - -// ignore: must_be_immutable -class NetLoadingDialog extends StatefulWidget { - String loadingText; - bool outsideDismiss; - bool loading; - Function dismissCallback; - Future requestCallBack; - - NetLoadingDialog( - {Key key, - this.loadingText = "loading...", - this.outsideDismiss = true, - this.dismissCallback, - this.loading, - this.requestCallBack}) - : super(key: key); - - @override - State createState() => _LoadingDialog(); -} - -class _LoadingDialog extends State { - _dismissDialog() { - if (widget.dismissCallback != null) { - widget.dismissCallback(); - } - Navigator.of(context).pop(); - } - - @override - void initState() { - super.initState(); - if (widget.requestCallBack != null) { - widget.requestCallBack.then((_) { - Navigator.pop(context); - }); - } - } - - @override - Widget build(BuildContext context) { - if (!widget.loading) { - return Container(); - } - return new GestureDetector( - onTap: widget.outsideDismiss ? _dismissDialog : null, - child: Material( - type: MaterialType.transparency, - child: new Center( - child: new SizedBox( - width: 120.0, - height: 120.0, - child: new Container( - decoration: ShapeDecoration( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(8.0), - ), - ), - ), - child: new Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - new CircularProgressIndicator(), - new Padding( - padding: const EdgeInsets.only( - top: 20.0, - ), - child: new Text( - widget.loadingText, - style: new TextStyle(fontSize: 12.0), - ), - ), - ], - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/components/markdown.dart b/lib/components/markdown.dart index af055381..02cf6af7 100644 --- a/lib/components/markdown.dart +++ b/lib/components/markdown.dart @@ -1,4 +1,4 @@ -import '../components/flutter_markdown/lib/flutter_markdown.dart' as md; +import 'package:flutter_markdown/flutter_markdown.dart' as md; import 'package:flutter/material.dart'; import 'package:flutter_go/utils/high_light_code.dart'; diff --git a/lib/components/single_theme_color.dart b/lib/components/single_theme_color.dart deleted file mode 100644 index 8a9088a0..00000000 --- a/lib/components/single_theme_color.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_go/event/event_bus.dart'; -import 'package:flutter_go/event/event_model.dart'; -// import 'package:event_bus/event_bus.dart'; - -class SingleThemeColor extends StatelessWidget { - final int themeColor; - final String coloeName; - - const SingleThemeColor({Key key, this.themeColor, this.coloeName}) - : super(key: key); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: (){ - if(ApplicationEvent.event != null){ - print('fire ${this.themeColor}'); - ApplicationEvent.event.fire(UserSettingThemeColorEvent(this.themeColor)); - Navigator.of(context).pop(); - } - }, - child: Column( - children: [ - Container( - width: 50, - height: 50, - margin: const EdgeInsets.all(5.0), - decoration: BoxDecoration( - borderRadius: BorderRadius.all( - Radius.circular(50), - ), - color: Color(this.themeColor), - ), - ), - Text( - this.coloeName, - style: TextStyle( - color: Color(this.themeColor), - fontSize: 14.0, - ), - ) - ], - ), - ); - } -} diff --git a/lib/components/widget_demo.dart b/lib/components/widget_demo.dart index 8783b18a..bbce9f51 100644 --- a/lib/components/widget_demo.dart +++ b/lib/components/widget_demo.dart @@ -4,7 +4,6 @@ import 'dart:core'; import 'package:flutter/material.dart'; -import 'package:flutter_go/utils/data_utils.dart'; import '../routers/application.dart'; import '../routers/routers.dart'; @@ -25,8 +24,8 @@ class WidgetDemo extends StatefulWidget { {Key key, @required this.title, @required this.contentList, - this.codeUrl, - this.docUrl, + @required this.codeUrl, + @required this.docUrl, this.bottomNaviBar}) : super(key: key); @@ -38,7 +37,7 @@ class _WidgetDemoState extends State { CollectionControlModel _collectionControl = new CollectionControlModel(); var _collectionIcons; List widgetDemosList = new WidgetDemoList().getDemos(); - String widgetType = 'old'; + String _router = ''; final GlobalKey _scaffoldKey = GlobalKey(); List _buildContent() { @@ -65,67 +64,56 @@ class _WidgetDemoState extends State { @override void initState() { super.initState(); - // 这里不能直接 使用 ` ModalRoute.of(context)` 会产生报错 - Future.delayed(Duration.zero, () { - String currentPath = ModalRoute.of(context).settings.name; - if (currentPath.indexOf('/standard-page') == 0) { - widgetType = 'standard'; - } - Map params = { - 'type': widgetType, - "url": currentPath, - "name": widget.title - }; - DataUtils.checkCollected(params).then((result) { - if (this.mounted) { - setState(() { - _hasCollected = result; - }); + _collectionControl.getRouterByName(widget.title).then((list) { + widgetDemosList.forEach((item) { + if (item.name == widget.title) { + _router = item.routerName; } }); + if (this.mounted) { + setState(() { + _hasCollected = list.length > 0; + }); + } }); } // 点击收藏按钮 _getCollection() { - String currentRouterPath = ModalRoute.of(context).settings.name; - Map params = { - "type": widgetType, - "url": currentRouterPath, - "name": widget.title - }; if (_hasCollected) { // 删除操作 - DataUtils.removeCollected(params, context).then((result) { - if (result) { + _collectionControl.deleteByName(widget.title).then((result) { + if (result > 0 && this.mounted) { + setState(() { + _hasCollected = false; + }); _scaffoldKey.currentState .showSnackBar(SnackBar(content: Text('已取消收藏'))); if (ApplicationEvent.event != null) { ApplicationEvent.event - .fire(CollectionEvent(widget.title, currentRouterPath, true)); - } - if (this.mounted) { - setState(() { - _hasCollected = false; - }); + .fire(CollectionEvent(widget.title, _router, true)); } + return; } + print('删除错误'); }); } else { // 插入操作 - DataUtils.addCollected(params, context).then((result) { - if (result) { - if (this.mounted) { - setState(() { - _hasCollected = true; - }); - } - _scaffoldKey.currentState - .showSnackBar(SnackBar(content: Text('收藏成功'))); + _collectionControl + .insert(Collection(name: widget.title, router: _router)) + .then((result) { + if (this.mounted) { + setState(() { + _hasCollected = true; + }); + if (ApplicationEvent.event != null) { ApplicationEvent.event - .fire(CollectionEvent(widget.title, currentRouterPath, false)); + .fire(CollectionEvent(widget.title, _router, false)); } + + _scaffoldKey.currentState + .showSnackBar(SnackBar(content: Text('收藏成功'))); } }); } @@ -141,38 +129,7 @@ class _WidgetDemoState extends State { '${Routes.codeView}?filePath=${Uri.encodeComponent(widget.codeUrl)}'); } } - List> buildPopupMenu() { - List> comps = []; - if (widget.docUrl != null) { - comps.add( - PopupMenuItem( - value: 'doc', - child: ListTile( - leading: Icon( - Icons.library_books, - size: 22.0, - ), - title: Text('查看文档'), - ), - ) - ); - } - if (widget.codeUrl != null) { - comps.add( - PopupMenuItem( - value: 'code', - child: ListTile( - leading: Icon( - Icons.code, - size: 22.0, - ), - title: Text('查看Demo'), - ), - ) - ); - } - return comps; - } + @override Widget build(BuildContext context) { if (_hasCollected) { @@ -180,34 +137,50 @@ class _WidgetDemoState extends State { } else { _collectionIcons = Icons.favorite_border; } - List> menus = buildPopupMenu(); - List actions = [ - new IconButton( - tooltip: 'goBack home', - onPressed: () { - Navigator.popUntil(context, ModalRoute.withName('/')); - }, - icon: Icon(Icons.home), - ), - new IconButton( - tooltip: 'collection', - onPressed: _getCollection, - icon: Icon(_collectionIcons), - ), - ]; - if (menus.length > 0) { - actions.add( - PopupMenuButton( - onSelected: _selectValue, - itemBuilder: (BuildContext context) => menus, - ) - ); - } return Scaffold( key: _scaffoldKey, appBar: AppBar( title: Text(widget.title), - actions: actions, + actions: [ + new IconButton( + tooltip: 'goBack home', + onPressed: () { + Navigator.popUntil(context, ModalRoute.withName('/')); + }, + icon: Icon(Icons.home), + ), + new IconButton( + tooltip: 'collection', + onPressed: _getCollection, + icon: Icon(_collectionIcons), + ), + PopupMenuButton( + onSelected: _selectValue, + itemBuilder: (BuildContext context) => >[ + const PopupMenuItem( + value: 'doc', + child: ListTile( + leading: Icon( + Icons.library_books, + size: 22.0, + ), + title: Text('查看文档'), + ), + ), + const PopupMenuDivider(), + const PopupMenuItem( + value: 'code', + child: ListTile( + leading: Icon( + Icons.code, + size: 22.0, + ), + title: Text('查看Demo'), + ), + ), + ], + ), + ], ), body: Container( padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), diff --git a/lib/components/widget_item_container.dart b/lib/components/widget_item_container.dart index 6572cb08..a1461857 100644 --- a/lib/components/widget_item_container.dart +++ b/lib/components/widget_item_container.dart @@ -3,89 +3,56 @@ import 'package:fluro/fluro.dart'; import './widget_item.dart'; import '../routers/application.dart'; import '../widgets/index.dart'; -import '../model/widget.dart'; class WidgetItemContainer extends StatelessWidget { final int columnCount; //一行几个 - final List commonItems; -// final bool isWidgetPoint; + final List categories; + final bool isWidgetPoint; // 所有的可用demos; final List widgetDemosList = new WidgetDemoList().getDemos(); WidgetItemContainer( {Key key, - @required this.commonItems, + @required this.categories, @required this.columnCount, -// @required this.isWidgetPoint - }) + @required this.isWidgetPoint}) : super(key: key); - /// 跳转goup - void tapToGroup(CategoryComponent cate, BuildContext context) { - Application.router - .navigateTo(context, "/category/${cate.token}", transition: TransitionType.inFromRight); - } - - /// 跳转到老的widget界面 - void tapToOldWidget(WidgetLeaf leaf, BuildContext context) { - - String targetName = leaf.name; - String targetRouter = '/category/error/404'; - widgetDemosList.forEach((item) { - if (item.name == targetName) { - targetRouter = item.routerName; - } - }); - Application.router.navigateTo(context, targetRouter, transition: TransitionType.inFromRight); - } - - /// 跳转到新的标准页 - void tapToStandardPage(WidgetLeaf leaf, BuildContext context) { - String targetRouter = '/standard-page/${leaf.pageId}'; - Application.router.navigateTo(context, targetRouter, transition: TransitionType.inFromRight); - } - List _buildColumns(context) { List _listWidget = []; List _listRows = []; int addI; - for (int i = 0, length = commonItems.length; i < length; i += columnCount) { + for (int i = 0, length = categories.length; i < length; i += columnCount) { _listRows = []; for (int innerI = 0; innerI < columnCount; innerI++) { addI = innerI + i; if (addI < length) { - CommonItem item = commonItems[addI]; - - + dynamic item = categories[addI]; _listRows.add( Expanded( flex: 1, child: WidgetItem( title: item.name, onTap: () { - String type = item.type; - - if (type == "category") { - return tapToGroup(item as CategoryComponent, context); + if (isWidgetPoint) { + String targetName = item.name; + String targetRouter = '/category/error/404'; + widgetDemosList.forEach((item) { + if (item.name == targetName) { + targetRouter = item.routerName; + } + }); + Application.router.navigateTo(context, "$targetRouter", transition: TransitionType.inFromRight); + } else { + Application.router + .navigateTo(context, "/category/${item.name}", transition: TransitionType.inFromRight); } - if (type == "widget") { - WidgetLeaf leaf = item as WidgetLeaf; - - if (leaf.display == "standard") { - return tapToStandardPage(leaf, context); - } else { - return tapToOldWidget(leaf, context); - } - } - - Application.router - .navigateTo(context, "/category/error/404", transition: TransitionType.inFromRight); }, index: addI, totalCount: length, rowLength: columnCount, - textSize: true ? 'middle' : 'small', + textSize: isWidgetPoint ? 'middle' : 'small', ), ), ); @@ -114,4 +81,3 @@ class WidgetItemContainer extends StatelessWidget { ); } } - diff --git a/lib/event/event_bus.dart b/lib/event/event_bus.dart index d51e9b10..ce2123df 100644 --- a/lib/event/event_bus.dart +++ b/lib/event/event_bus.dart @@ -2,4 +2,4 @@ import 'package:event_bus/event_bus.dart'; class ApplicationEvent{ static EventBus event; -} +} \ No newline at end of file diff --git a/lib/event/event_model.dart b/lib/event/event_model.dart index ca753ec9..f72f0cb6 100644 --- a/lib/event/event_model.dart +++ b/lib/event/event_model.dart @@ -4,16 +4,4 @@ class CollectionEvent{ final bool isRemove; // token uid... CollectionEvent(this.widgetName,this.router,this.isRemove); -} - -class UserGithubOAuthEvent{ - final String loginName; - final String token; - final bool isSuccess; - UserGithubOAuthEvent(this.loginName,this.token,this.isSuccess); -} - -class UserSettingThemeColorEvent{ - final int settingThemeColor; - UserSettingThemeColorEvent(this.settingThemeColor); } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 52082f22..fdcee399 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,23 +11,19 @@ import 'package:flutter_go/model/search_history.dart'; import 'package:flutter_go/utils/analytics.dart' as Analytics; import 'package:flutter_go/views/login_page/login_page.dart'; import 'package:flutter_go/utils/data_utils.dart'; -import 'package:flutter_go/model/user_info.dart'; -import 'package:flutter_jpush/flutter_jpush.dart'; -import 'package:flutter_go/event/event_bus.dart'; -import 'package:flutter_go/event/event_model.dart'; -import 'package:event_bus/event_bus.dart'; -import 'package:flutter_go/model/widget.dart'; -import 'package:flutter_go/standard_pages/index.dart'; + //import 'views/welcome_page/index.dart'; +const int ThemeColor = 0xFFC91B3A; SpUtil sp; var db; class MyApp extends StatefulWidget { MyApp() { final router = new Router(); + Routes.configureRoutes(router); - // 这里设置项目环境 + Application.router = router; } @@ -38,105 +34,28 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { bool _hasLogin = false; bool _isLoading = true; - UserInformation _userInfo; - bool isConnected = false; - String registrationId; - List notificationList = []; - int themeColor = 0xFFC91B3A; - - _MyAppState() { - final eventBus = new EventBus(); - ApplicationEvent.event = eventBus; - } @override void initState() { super.initState(); - _startupJpush(); - - FlutterJPush.addConnectionChangeListener((bool connected) { - setState(() { - /// 是否连接,连接了才可以推送 - print("连接状态改变:$connected"); - this.isConnected = connected; - if (connected) { - //在启动的时候会去连接自己的服务器,连接并注册成功之后会返回一个唯一的设备号 - try { - FlutterJPush.getRegistrationID().then((String regId) { - print("主动获取设备号:$regId"); - setState(() { - this.registrationId = regId; - }); - }); - } catch (error) { - print('主动获取设备号Error:$error'); - } - ; - } - }); - }); - - FlutterJPush.addReceiveNotificationListener( - (JPushNotification notification) { - setState(() { - /// 收到推送 - print("收到推送提醒: $notification"); - notificationList.add(notification); - }); - }); - - FlutterJPush.addReceiveOpenNotificationListener( - (JPushNotification notification) { - setState(() { - print("打开了推送提醒: $notification"); - - /// 打开了推送提醒 - notificationList.add(notification); - }); - }); - - FlutterJPush.addReceiveCustomMsgListener((JPushMessage msg) { - setState(() { - print("收到推送消息提醒: $msg"); - - /// 打开了推送提醒 - notificationList.add(msg); - }); - }); - DataUtils.checkLogin().then((hasLogin) { - if (hasLogin.runtimeType == UserInformation) { - setState(() { - _hasLogin = true; - _isLoading = false; - _userInfo = hasLogin; - // 设置初始化的主题色 - // if (hasLogin.themeColor != 'default') { - // themeColor = int.parse(hasLogin.themeColor); - // } - }); - } else { - setState(() { - _hasLogin = hasLogin; - _isLoading = false; - }); - } - }).catchError((onError) { setState(() { - _hasLogin = false; + _hasLogin = hasLogin; + _isLoading = false; + }); + }).catchError((onError){ + setState(() { + _hasLogin = true; _isLoading = false; }); print('身份信息验证失败:$onError'); }); - ApplicationEvent.event.on().listen((event) { - print('接收到的 event $event'); - }); } showWelcomePage() { if (_isLoading) { return Container( - color: Color(this.themeColor), + color: const Color(ThemeColor), child: Center( child: SpinKitPouringHourglass(color: Colors.white), ), @@ -144,7 +63,7 @@ class _MyAppState extends State { } else { // 判断是否已经登录 if (_hasLogin) { - return AppPage(_userInfo); + return AppPage(); } else { return LoginPage(); } @@ -153,11 +72,10 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { -// WidgetTree.getCommonItemByPath([15, 17], Application.widgetTree); return new MaterialApp( - title: 'titles', + title: 'title', theme: new ThemeData( - primaryColor: Color(this.themeColor), + primaryColor: Color(ThemeColor), backgroundColor: Color(0xFFEFEFEF), accentColor: Color(0xFF888888), textTheme: TextTheme( @@ -165,7 +83,7 @@ class _MyAppState extends State { body1: TextStyle(color: Color(0xFF888888), fontSize: 16.0), ), iconTheme: IconThemeData( - color: Color(this.themeColor), + color: Color(ThemeColor), size: 35.0, ), ), @@ -177,24 +95,11 @@ class _MyAppState extends State { } } -void _startupJpush() async { - print("初始化jpush"); - await FlutterJPush.startup(); - print("初始化jpush成功"); -} - void main() async { final provider = new Provider(); await provider.init(true); sp = await SpUtil.getInstance(); new SearchHistoryList(sp); - - await DataUtils.getWidgetTreeList().then((List json) { - List data = WidgetTree.insertDevPagesToList(json, StandardPages().getLocalList()); - Application.widgetTree = WidgetTree.buildWidgetTree(data); - print("Application.widgetTree>>>> ${Application.widgetTree}"); - }); db = Provider.db; runApp(new MyApp()); } - diff --git a/lib/model/cat.dart b/lib/model/cat.dart index 5ac493f4..e9ecae7b 100644 --- a/lib/model/cat.dart +++ b/lib/model/cat.dart @@ -1,107 +1,107 @@ -// -//import 'dart:async'; -// -//import 'package:flutter_go/utils/sql.dart'; -// -//abstract class CatInterface{ -// int get id; -// //类目名称 -// String get name; -// //描述 -// String get desc; -// //第几级类目,默认 1 -// int get depth; -// //父类目id,没有为 0 -// int get parentId; -//} -// -//class Cat implements CatInterface { -// int id; -// String name; -// String desc; -// int depth; -// int parentId; -// -// Cat({this.id, this.name, this.desc, this.depth, this.parentId}); -// -// Cat.fromJSON(Map json) -// : id = json['id'], -// name = json['name'], -// desc = json['desc'], -// depth = json['depth'], -// parentId = json['parentId']; -// -// String toString() { -// return '(Cat $name)'; -// } -// -// Map toMap() { -// return { -// 'id': id, -// 'name': name, -// 'desc': desc, -// 'depth': depth, -// 'parentId': parentId -// }; -// } -// Map toSqlCondition() { -// Map _map = this.toMap(); -// Map condition = {}; -// _map.forEach((k, value) { -// -// if (value != null) { -// -// condition[k] = value; -// } -// }); -// -// if (condition.isEmpty) { -// return {}; -// } -// -// return condition; -// } -//} -// -// -//class CatControlModel{ -// final String table = 'cat'; -// Sql sql; -// CatControlModel() { -// sql = Sql.setTable(table); -// } -// -// /// 获取一级类目 -// Future mainList() async{ -// List listJson = await sql.getByCondition(conditions: {'parentId': 0}); -// List cats = listJson.map((json) { -// return new Cat.fromJSON(json); -// }).toList(); -// return cats; -// } -// -// // 获取Cat不同深度与parent的列表 -// Future> getList([Cat cat]) async{ -// -// -// if (cat == null) { -// cat = new Cat(depth: 1, parentId: 0); -// } -// // print("cat in getList ${cat.toMap()}"); -// List listJson = await sql.getByCondition(conditions: cat.toSqlCondition()); -// List cats = listJson.map((json) { -// return new Cat.fromJSON(json); -// }).toList(); -// return cats; -// } -// -// // 通过name获取Cat对象信息 -// Future getCatByName(String name) async { -// List json = await sql.getByCondition(conditions: {'name': name}); -// if (json.isEmpty) { -// return null; -// } -// return new Cat.fromJSON(json.first); -// } -// -//} + +import 'dart:async'; + +import 'package:flutter_go/utils/sql.dart'; + +abstract class CatInterface{ + int get id; + //类目名称 + String get name; + //描述 + String get desc; + //第几级类目,默认 1 + int get depth; + //父类目id,没有为 0 + int get parentId; +} + +class Cat implements CatInterface { + int id; + String name; + String desc; + int depth; + int parentId; + + Cat({this.id, this.name, this.desc, this.depth, this.parentId}); + + Cat.fromJSON(Map json) + : id = json['id'], + name = json['name'], + desc = json['desc'], + depth = json['depth'], + parentId = json['parentId']; + + String toString() { + return '(Cat $name)'; + } + + Map toMap() { + return { + 'id': id, + 'name': name, + 'desc': desc, + 'depth': depth, + 'parentId': parentId + }; + } + Map toSqlCondition() { + Map _map = this.toMap(); + Map condition = {}; + _map.forEach((k, value) { + + if (value != null) { + + condition[k] = value; + } + }); + + if (condition.isEmpty) { + return {}; + } + + return condition; + } +} + + +class CatControlModel{ + final String table = 'cat'; + Sql sql; + CatControlModel() { + sql = Sql.setTable(table); + } + + /// 获取一级类目 + Future mainList() async{ + List listJson = await sql.getByCondition(conditions: {'parentId': 0}); + List cats = listJson.map((json) { + return new Cat.fromJSON(json); + }).toList(); + return cats; + } + + // 获取Cat不同深度与parent的列表 + Future> getList([Cat cat]) async{ + + + if (cat == null) { + cat = new Cat(depth: 1, parentId: 0); + } + // print("cat in getList ${cat.toMap()}"); + List listJson = await sql.getByCondition(conditions: cat.toSqlCondition()); + List cats = listJson.map((json) { + return new Cat.fromJSON(json); + }).toList(); + return cats; + } + + // 通过name获取Cat对象信息 + Future getCatByName(String name) async { + List json = await sql.getByCondition(conditions: {'name': name}); + if (json.isEmpty) { + return null; + } + return new Cat.fromJSON(json.first); + } + +} diff --git a/lib/model/collection.dart b/lib/model/collection.dart index 49233bbe..388fb3d8 100644 --- a/lib/model/collection.dart +++ b/lib/model/collection.dart @@ -49,21 +49,15 @@ class CollectionControlModel { List list = await sql.getByCondition(); List resultList = []; list.forEach((item){ - print('collection item =>> $item'); + print(item); resultList.add(Collection.fromJSON(item)); }); return resultList; } - /// 通过收藏名获取router - /// 因为名称很容易重复. 所以这里使用path router做唯一判断 -// Future getRouterByName(String name) async { -// List list = await sql.getByCondition(conditions: {'name': name}); -// return list; -// } - - Future getRouterByUrl(String path) async { - List list = await sql.getByCondition(conditions: {'router': path}); + // 通过收藏名获取router + Future getRouterByName(String name) async { + List list = await sql.getByCondition(conditions: {'name': name}); return list; } @@ -71,8 +65,4 @@ class CollectionControlModel { Future deleteByName(String name) async{ return await sql.delete(name,'name'); } - // 通过path删除 - Future deleteByPath(String path) async{ - return await sql.delete(path,'router'); - } } diff --git a/lib/model/responseData.dart b/lib/model/responseData.dart deleted file mode 100644 index 1b5327e5..00000000 --- a/lib/model/responseData.dart +++ /dev/null @@ -1,25 +0,0 @@ -class ResponseData{ - int status; - bool success; - String message; - - ResponseData(this.status, this.success,this.message); - - ResponseData.fromJson(Map json) - : status = json['status'], - success = json['success'], - message=json['message']; - - Map toJson() => - { - 'status': status, - 'success': success, - 'messsage': message - }; - - @override - String toString() { - return 'status: $status ,success: $success,message: ${message.toString()}'; - } - -} \ No newline at end of file diff --git a/lib/model/user_info.dart b/lib/model/user_info.dart index 5007e512..b47b08a3 100644 --- a/lib/model/user_info.dart +++ b/lib/model/user_info.dart @@ -1,32 +1,24 @@ -class UserInformation { +class UserInfo { String username; int id; String avatarPic; String themeColor; + String urlName; - UserInformation({ + UserInfo({ this.avatarPic, this.id, this.themeColor, + this.urlName, this.username, }); - factory UserInformation.fromJson(Map json) { - print('fromJOSN $json ${json['id'].runtimeType}'); - String name = json['name']; - int userId ; - if(json['name'] == null){ - name = json['url_name']; - } - if(json['id'].runtimeType == int){ - userId = json['id']; - }else{ - userId = int.parse(json['id']); - } - return UserInformation( + factory UserInfo.fromJson(Map json) { + return UserInfo( avatarPic: json['avatar_pic'], - id: userId, - username: name, - themeColor: json['theme_color']); + id: int.parse(json['id']), + username: json['name'], + themeColor: json['theme_color'], + urlName: json['url_name']); } } diff --git a/lib/model/user_info_cache.dart b/lib/model/user_info_cache.dart index 8bfe018c..e2045f92 100644 --- a/lib/model/user_info_cache.dart +++ b/lib/model/user_info_cache.dart @@ -59,6 +59,4 @@ class UserInfoControlModel { Future deleteAll() async{ return await sql.deleteAll(); } - - } diff --git a/lib/model/version.dart b/lib/model/version.dart deleted file mode 100644 index 019873cf..00000000 --- a/lib/model/version.dart +++ /dev/null @@ -1,29 +0,0 @@ -class Data { - String version; - String name; - - Data.fromJson(Map json) - : version = json['version'], - name = json['name']; - - @override - String toString() { - return 'name: $name ,version: $version'; - } -} - -class Version { - Data data; - int status; - bool success; - - Version.formJson(Map json) - : status = json['status'], - success = json['success'], - data = Data.fromJson(json['data']); - - @override - String toString() { - return 'status: $status ,success: $success,date: ${data.toString()}'; - } -} diff --git a/lib/model/widget.dart b/lib/model/widget.dart index d2be8362..bcc6ae7f 100644 --- a/lib/model/widget.dart +++ b/lib/model/widget.dart @@ -2,16 +2,9 @@ import 'dart:async'; import "package:flutter/material.dart"; -import "package:flutter_go/routers/application.dart"; + import 'package:flutter_go/utils/sql.dart'; -enum treeNode { - CategoryComponent, - WidgetLeaf -} - -//typedef aaa - abstract class WidgetInterface { int get id; @@ -149,226 +142,3 @@ class WidgetControlModel { return widgets; } } -// 抽象类 -abstract class CommonItem { - int id; - String name; - int parentId; - String type; - List children; - String token; - - /// 父级节点, 存放整个CommonItem对象node = ! null - /// - CommonItem parent; - String toString() { - return "CommonItem {name: $name, type: $type, parentId: $parentId, token: $token, children长度 ${children}"; - } - - T getChild(String token); - T addChildren(Object item); - // 从children树中. 查找任意子节点 - T find(String token, [CommonItem node]); -} - -// tree的group树干 -class CategoryComponent extends CommonItem { - int id; - String name; - int parentId; - CommonItem parent; - String token; - - - List children = []; - - String type = 'category'; - - CategoryComponent({ - @required this.id, - @required this.name, - @required this.parentId, - this.type = 'categoryw', - this.children, - this.parent - }); - CategoryComponent.fromJson(Map json) { - if (json['id'] != null && json['id'].runtimeType == String) { - this.id = int.parse(json['id']); - } else { - this.id = json['id']; - } - this.name = json['name']; - this.parentId = json['parentId']; - this.token = json['id'].toString() + json['type']; - } - void addChildren(Object item) { - if (item is CategoryComponent) { - CategoryComponent cate = item; - cate.parent = this; - this.children.add( - cate - ); - } - if (item is WidgetLeaf) { - WidgetLeaf widget = item; - widget.parent = this; - this.children.add( - widget - ); - } - } - @override - CommonItem getChild(String token) { - return children.firstWhere((CommonItem item) => item.token == token, orElse: () => null); - } - - @override - CommonItem find(String token, [CommonItem node]) { - CommonItem ret; - if (node !=null) { - if (node.token == token) { - return node; - } else { - // 循环到叶子节点, 返回 空 - if (node.children == null) { - return null; - } - for (int i = 0; i < node.children.length; i++) { - CommonItem temp = this.find(token, node.children[i]); - if (temp != null) { - ret = temp; - } - } - } - } else { - ret = find(token, this); - } - return ret; - } -} - -// 叶子节点 -class WidgetLeaf extends CommonItem { - int id; - String name; - int parentId; - String display; // 展示类型, 区分老的widget文件下的详情 - String author; // 文档负责人 - String path; // 路由地址 - String pageId; // 界面ID - CommonItem parent; - - String type = 'widget'; - WidgetLeaf({ - @required this.id, - @required this.name, - @required this.display, - this.author, - this.path, - this.pageId - }); - - WidgetLeaf.fromJson(Map json) { - if (json['id'] != null && json['id'].runtimeType == String) { - this.id = int.parse(json['id']); - } else { - this.id = json['id']; - } - this.name = json['name']; - this.display = json['display']; - this.author = json['author'] ?? null; - this.path = json['path'] ?? null; - this.pageId = json['pageId'] ?? null; - this.token = json['id'].toString() + json['type']; - } - @override - CommonItem getChild(String token) { - return null; - } - @override - addChildren(Object item) { - // TODO: implement addChildren - return null; - } - - CommonItem find(String token, [CommonItem node]){ - return null; - } -} - -class WidgetTree { - // 构建树型结构 - static CategoryComponent buildWidgetTree(List json, [parent]){ - CategoryComponent current; - if (parent != null) { - current = parent; - } else { - current = CategoryComponent(id: 0, name: 'root', parentId: null, children: []); - } - json.forEach((item) { - // 归属分类级别 - if (['root', 'category'].indexOf(item['type']) != -1) { - CategoryComponent cate = CategoryComponent.fromJson(item); - if (cate.children != null) { - buildWidgetTree(item['children'], cate); - } - current.addChildren(cate); - } else { - // 归属最后一层叶子节点 - WidgetLeaf cate = WidgetLeaf.fromJson(item); - current.addChildren(cate); - } - }); - return current; - } - - static insertDevPagesToList(List list, List devPages) { - List devChildren = []; - int index = 9999999; - if (Application.env == ENV.PRODUCTION) { - return list; - } - devPages.forEach((item) { - index++; - if (item['id'] != null) { - devChildren.add({ - "id": index.toString(), - "name": item['name'], - "parentId": "99999999999", - "type": "widget", - "display": "standard", - "author": item['author'], - "pageId": item['id'] - }); - } - }); - list.forEach((item) { - if (item['name'].toString().toUpperCase() == 'DEVELOPER') { - List children = item['children']; - children.insert(0, { - "id": "99999999999", - "name": "本地代码", - "parentId": item['id'], - "type": "category", - "children": devChildren - }); - } - }); - return list; - } - static CategoryComponent getCommonItemById(List path, CategoryComponent root) { - print("getCommonItemByPath $path"); - print("root $root"); - CommonItem childLeaf; - int first = path.first; - path = path.sublist(1); - print("path:::: $path"); - if (path.length >= 0) { -// childLeaf = root.getChild(path.first); - } - - - return childLeaf; - } -} \ No newline at end of file diff --git a/lib/page_demo_package/.demo.json b/lib/page_demo_package/.demo.json deleted file mode 100644 index 1967c339..00000000 --- a/lib/page_demo_package/.demo.json +++ /dev/null @@ -1 +0,0 @@ -[{"name":"demoName","screenShot":"","author":"yourName","email":"yourEmail","desc":"这是一个测试的标准demo","id":"1a29aa8e_32ae_4241_9c8a_5c9e1f92b096"},{"name":"local","screenShot":"","author":"ab","email":"email","desc":"ags","id":"2c1d57d0_42ae_4241_9c8a_5c9e1f92b096"}] \ No newline at end of file diff --git a/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096/.demo.json b/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096/.demo.json deleted file mode 100644 index de6b8e00..00000000 --- a/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096/.demo.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "demoName", - "screenShot": "", - "author":"yourName", - "email": "yourEmail", - "desc": "这是一个测试的标准demo", - "id": "1a29aa8e_32ae_4241_9c8a_5c9e1f92b096" -} - \ No newline at end of file diff --git a/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096/index.dart b/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096/index.dart deleted file mode 100644 index 68193686..00000000 --- a/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096/index.dart +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created with flutter go cli -// User: yourName -// Time: 2019-06-10 20:37:27.289097 -// email: yourEmail -// desc: 这是一个测试的标准demo -// - -import 'src/index.dart'; - -var demoWidgets = [ - new Demo() -]; - - \ No newline at end of file diff --git a/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096/src/index.dart b/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096/src/index.dart deleted file mode 100644 index e2992379..00000000 --- a/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096/src/index.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; - -class Demo extends StatefulWidget { - @override - _State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) { - return Container( - child: RaisedButton(onPressed: () {}, child: Text('我是md中引入的demo')) - ); - } -} - \ No newline at end of file diff --git a/lib/page_demo_package/index.dart b/lib/page_demo_package/index.dart deleted file mode 100644 index 1e6a4966..00000000 --- a/lib/page_demo_package/index.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096/index.dart' as StandardDemo_demoName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096; -import 'local_ab_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096/index.dart' as StandardDemo_local_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096; -var demoObjects = { - '1a29aa8e_32ae_4241_9c8a_5c9e1f92b096': StandardDemo_demoName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096.demoWidgets, - '2c1d57d0_42ae_4241_9c8a_5c9e1f92b096': StandardDemo_local_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096.demoWidgets -}; \ No newline at end of file diff --git a/lib/page_demo_package/info.json b/lib/page_demo_package/info.json deleted file mode 100644 index 669b078a..00000000 --- a/lib/page_demo_package/info.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "70c429df-c27d-4843-8e28-1e6885c9276b": { - "name": "button-red", - "screenShot": "", - "author": "sanfan", - "email": "sanfan.hx@alibaba-inc.com", - "desc": "desc", - } -} - - - - - - - - diff --git a/lib/page_demo_package/local_ab_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096/.demo.json b/lib/page_demo_package/local_ab_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096/.demo.json deleted file mode 100644 index e6e8114d..00000000 --- a/lib/page_demo_package/local_ab_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096/.demo.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "local", - "screenShot": "", - "author":"ab", - "email": "email", - "desc": "ags", - "id": "2c1d57d0_42ae_4241_9c8a_5c9e1f92b096" -} - \ No newline at end of file diff --git a/lib/page_demo_package/local_ab_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096/index.dart b/lib/page_demo_package/local_ab_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096/index.dart deleted file mode 100644 index 4e315373..00000000 --- a/lib/page_demo_package/local_ab_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096/index.dart +++ /dev/null @@ -1,15 +0,0 @@ -// -// Created with flutter go cli -// User: ab -// Time: 2019-08-06 17:26:02.905889 -// email: email -// desc: ags -// - -import 'src/index.dart'; - -var demoWidgets = [ - new Demo() -]; - - \ No newline at end of file diff --git a/lib/page_demo_package/local_ab_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096/src/index.dart b/lib/page_demo_package/local_ab_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096/src/index.dart deleted file mode 100644 index d0d3921e..00000000 --- a/lib/page_demo_package/local_ab_2c1d57d0_42ae_4241_9c8a_5c9e1f92b096/src/index.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:flutter/material.dart'; - -class Demo extends StatefulWidget { - @override - _State createState() => _State(); -} - -class _State extends State { - @override - Widget build(BuildContext context) { - return Container( - child: Text("this is flutter go init demo"), - ); - } -} - \ No newline at end of file diff --git a/lib/page_demo_package/readme.md b/lib/page_demo_package/readme.md deleted file mode 100644 index be5a40f1..00000000 --- a/lib/page_demo_package/readme.md +++ /dev/null @@ -1,20 +0,0 @@ -# 目录说明 - -本目录文件结构与文件命名, 使用cli进行更新 - - -# demos 目录文件结构 - -``` -demos -├── ${demoName}-${author}-${32位demoID} -│   ├── index.dart -│   └── src -│   ├── .demo.json -│   └── ${demoName}.dart -├── ...${demoName}-${author}-${32位demoID} -├── index.dart -├── info.json -└── readme.md -``` - \ No newline at end of file diff --git a/lib/resources/widget_name_to_icon.dart b/lib/resources/widget_name_to_icon.dart index 82f2593f..644d145d 100644 --- a/lib/resources/widget_name_to_icon.dart +++ b/lib/resources/widget_name_to_icon.dart @@ -1,11 +1,9 @@ import 'package:flutter/material.dart'; class WidgetName2Icon { static Map icons = { - "Developer": Icons.developer_mode, - "Standard": Icons.pages , "Element":Icons.explicit, "Components":Icons.extension, - "Theme":Icons.filter_b_and_w, + "Themes":Icons.filter_b_and_w, "Form":Icons.table_chart, "Frame":Icons.aspect_ratio, "Media":Icons.subscriptions, diff --git a/lib/routers/application.dart b/lib/routers/application.dart index 443ce40c..8a03d9c7 100644 --- a/lib/routers/application.dart +++ b/lib/routers/application.dart @@ -1,20 +1,12 @@ import 'package:flutter/material.dart'; import 'package:fluro/fluro.dart'; + import 'package:flutter_go/utils/shared_preferences.dart'; -import '../model/widget.dart'; -enum ENV { - PRODUCTION, - DEV, -} class Application { - /// 通过Application设计环境变量 - static ENV env = ENV.DEV; - static Router router; static TabController controller; - static SpUtil sharePeference; - static CategoryComponent widgetTree; + static SpUtil sharePeferences; static Map github = { 'widgetsURL':'https://github.com/alibaba/flutter-go/blob/develop/lib/widgets/', @@ -22,15 +14,4 @@ class Application { //'master':'https://github.com/alibaba-paimai-frontend/flutter-common-widgets-app/tree/master/lib/widgets/' }; - /// 所有获取配置的唯一入口 - Map get config { - if (Application.env == ENV.PRODUCTION) { - return {}; - } - if (Application.env == ENV.DEV) { - return {}; - } - return {}; - } - } diff --git a/lib/routers/router_handler.dart b/lib/routers/router_handler.dart index 3d6e671e..4491ebcb 100644 --- a/lib/routers/router_handler.dart +++ b/lib/routers/router_handler.dart @@ -7,57 +7,38 @@ import 'package:flutter_go/components/full_screen_code_dialog.dart'; import 'package:flutter_go/views/web_page/web_view_page.dart'; import 'package:flutter_go/views/home.dart'; import 'package:flutter_go/views/login_page/login_page.dart'; -import 'package:flutter_go/model/user_info.dart'; -import 'package:flutter_go/views/collection_page/collection_page.dart'; -import 'package:flutter_go/views/collection_page/collection_full_page.dart'; -import 'package:flutter_go/views/standard_demo_page/index.dart'; -import 'package:flutter_go/views/issuse_message_page/issuse_message_page.dart'; // app的首页 var homeHandler = new Handler( handlerFunc: (BuildContext context, Map> params) { - return new AppPage(UserInformation(id: 0)); + return new AppPage(); }, ); -var collectionFullHandler = new Handler( - handlerFunc: (BuildContext context,Map> params){ - bool hasLogined = params['hasLogin']?.first == 'true'; - return CollectionFullPage(hasLogined: hasLogined); - } -); - -var collectionHandler = new Handler( - handlerFunc: (BuildContext context,Map> params){ - bool hasLogined = params['hasLogin']?.first == 'true'; - return CollectionPage(hasLogined: hasLogined); - } -); - var categoryHandler = new Handler( handlerFunc: (BuildContext context, Map> params) { - String ids = params["ids"]?.first; + String name = params["type"]?.first; - return new CategoryHome(ids); + return new CategoryHome(name); }, ); var widgetNotFoundHandler = new Handler( handlerFunc: (BuildContext context, Map> params) { - return new WidgetNotFound(); - }); + return new WidgetNotFound(); +}); var loginPageHandler = new Handler( handlerFunc: (BuildContext context, Map> params) { - return LoginPage(); - }); + return LoginPage(); +}); var fullScreenCodeDialog = new Handler( handlerFunc: (BuildContext context, Map> params) { - String path = params['filePath']?.first; - return new FullScreenCodeDialog( - filePath: path, - ); - }); + String path = params['filePath']?.first; + return new FullScreenCodeDialog( + filePath: path, + ); +}); var webViewPageHand = new Handler( handlerFunc: (BuildContext context, Map> params) { @@ -65,17 +46,3 @@ var webViewPageHand = new Handler( String url = params['url']?.first; return new WebViewPage(url, title); }); - - -var standardPageHandler = new Handler( - handlerFunc: (BuildContext context, Map> params) { - String id = params['id']?.first; - return StandardView(id: id); - } -); - - -var issuesMessageHandler = new Handler( - handlerFunc: (BuildContext context, Map> params) { - return IssuesMessagePage(); - }); diff --git a/lib/routers/routers.dart b/lib/routers/routers.dart index 711e2285..b433d69c 100644 --- a/lib/routers/routers.dart +++ b/lib/routers/routers.dart @@ -2,21 +2,17 @@ import 'package:fluro/fluro.dart'; import 'package:flutter/material.dart'; import 'package:flutter_go/utils/analytics.dart' show analytics; + import '../widgets/index.dart'; import './router_handler.dart'; -import '../standard_pages/index.dart'; + class Routes { static String root = "/"; static String home = "/home"; static String widgetDemo = '/widget-demo'; static String codeView = '/code-view'; - static String githubCodeView = '/github-code-view'; static String webViewPage = '/web-view-page'; static String loginPage = '/loginpage'; - static String issuesMessage='/issuesMessage'; - static String collectionPage = '/collection-page'; - static String collectionFullPage = '/collection-full-page'; - static String standardPage = '/standard-page/:id'; static void configureRoutes(Router router) { List widgetDemosList = new WidgetDemoList().getDemos(); @@ -24,14 +20,12 @@ class Routes { handlerFunc: (BuildContext context, Map> params) { }); router.define(home, handler: homeHandler); - router.define(collectionPage,handler:collectionHandler); - router.define(collectionFullPage,handler:collectionFullHandler); - router.define('/category/:ids', handler: categoryHandler); + + router.define('/category/:type', handler: categoryHandler); router.define('/category/error/404', handler: widgetNotFoundHandler); router.define(loginPage, handler: loginPageHandler); router.define(codeView,handler:fullScreenCodeDialog); router.define(webViewPage,handler:webViewPageHand); - router.define(issuesMessage, handler: issuesMessageHandler); widgetDemosList.forEach((demo) { Handler handler = new Handler( handlerFunc: (BuildContext context, Map> params) { @@ -42,10 +36,5 @@ class Routes { }); router.define('${demo.routerName}', handler: handler); }); - router.define(standardPage,handler:standardPageHandler); -// router.define(webViewPage,handler:webViewPageHand); -// standardPages.forEach((String id, String md) => { -// -// }); } } diff --git a/lib/standard_pages/.pages.json b/lib/standard_pages/.pages.json deleted file mode 100644 index 9b3a3ca9..00000000 --- a/lib/standard_pages/.pages.json +++ /dev/null @@ -1 +0,0 @@ -[{"name":"local","screenShot":"","author":"hnaxu","title":"本地","email":"hanxu@qq.com","desc":"desc","id":"5d7178d0_42ae_4241_9c8a_5c9e1f92b096"},{"name":"test","screenShot":"","author":"abc","title":"ya","email":"adsf.com","desc":"desc","id":"84f38e00_42ae_4241_9c8a_5c9e1f92b096"},{"name":"standard","screenShot":"","author":"sanfan","title":"介绍页","email":"hanxu317@qq.com","desc":"desc","id":"ee4feb8e_32ae_4241_9c8a_5c9e1f92b096"},{"name":"standard_for_slider","screenShot":"","author":"sanfan","title":"slider组件","email":"hanxu@qq.com","desc":"slider, new Slider","id":"8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096"}] \ No newline at end of file diff --git a/lib/standard_pages/index.dart b/lib/standard_pages/index.dart deleted file mode 100644 index 6af27c34..00000000 --- a/lib/standard_pages/index.dart +++ /dev/null @@ -1,35 +0,0 @@ - -import 'local_hnaxu_5d7178d0_42ae_4241_9c8a_5c9e1f92b096/index.dart' as StandardPage_local_5d7178d0_42ae_4241_9c8a_5c9e1f92b096; -import 'test_abc_84f38e00_42ae_4241_9c8a_5c9e1f92b096/index.dart' as StandardPage_test_84f38e00_42ae_4241_9c8a_5c9e1f92b096; -import 'standard_sanfan_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096/index.dart' as StandardPage_standard_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096; -import 'standard_for_slider_sanfan_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096/index.dart' as StandardPage_standard_for_slider_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096; -class StandardPages { - Map standardPages; - Map getPages() { - return { - "0": "0" , - "5d7178d0_42ae_4241_9c8a_5c9e1f92b096" : StandardPage_local_5d7178d0_42ae_4241_9c8a_5c9e1f92b096.getMd() -, - "84f38e00_42ae_4241_9c8a_5c9e1f92b096" : StandardPage_test_84f38e00_42ae_4241_9c8a_5c9e1f92b096.getMd() -, - "ee4feb8e_32ae_4241_9c8a_5c9e1f92b096" : StandardPage_standard_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096.getMd() -, - "8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096" : StandardPage_standard_for_slider_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096.getMd() - }; - } - List> getLocalList() { - return [ - {}, - { "id": "5d7178d0_42ae_4241_9c8a_5c9e1f92b096", "name": "local", "email": "hanxu@qq.com", "author": "hnaxu"} -, - { "id": "84f38e00_42ae_4241_9c8a_5c9e1f92b096", "name": "test", "email": "adsf.com", "author": "abc"} -, - { "id": "ee4feb8e_32ae_4241_9c8a_5c9e1f92b096", "name": "standard", "email": "hanxu317@qq.com", "author": "sanfan"} -, - { "id": "8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096", "name": "standard_for_slider", "email": "hanxu@qq.com", "author": "sanfan"} - ]; - } - -} - - \ No newline at end of file diff --git a/lib/standard_pages/local_hnaxu_5d7178d0_42ae_4241_9c8a_5c9e1f92b096/.page.json b/lib/standard_pages/local_hnaxu_5d7178d0_42ae_4241_9c8a_5c9e1f92b096/.page.json deleted file mode 100644 index d6a9e3d0..00000000 --- a/lib/standard_pages/local_hnaxu_5d7178d0_42ae_4241_9c8a_5c9e1f92b096/.page.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "local", - "screenShot": "", - "author":"hnaxu", - "title":"本地", - "email": "hanxu@qq.com", - "desc": "desc", - "id": "5d7178d0_42ae_4241_9c8a_5c9e1f92b096" -} - \ No newline at end of file diff --git a/lib/standard_pages/local_hnaxu_5d7178d0_42ae_4241_9c8a_5c9e1f92b096/index.dart b/lib/standard_pages/local_hnaxu_5d7178d0_42ae_4241_9c8a_5c9e1f92b096/index.dart deleted file mode 100644 index 6e506d10..00000000 --- a/lib/standard_pages/local_hnaxu_5d7178d0_42ae_4241_9c8a_5c9e1f92b096/index.dart +++ /dev/null @@ -1,52 +0,0 @@ -String getMd() { - return """ - # 标准的详情页 - -您可以在这个界面中, 编写大多数的markdown文案, 他会在 **goCli watch** 下同步被编译成 **dart** 文件 - -您可以通过goCli创建详情页所需要的demo - -``` -goCLi createDemo -``` - -在flutter go 根文件下通过命令行输入以上命令可以进行以下操作 - -[✓] 请输入新增加的demo名称? demoName - -[✓] 请输入您的姓名(使用英文) yourName - -[✓] 请输入您的github的email地址 yourEmail - -[✓] 请输入您demo的描述 这是一个测试的标准demo - - -在完成以上操作后, 可以得到这样的输出: - - -``` ------------------- -您新增的组件信息如下 -================== -{ - name : demoName - author : yourName - email : yourEmail - desc : 这是一个测试的标准demo -} -================== -[✓] Is this the config you want ? (Y/n) y -{ - 新建的demo文件位于 : /flutter go/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - demoId为 : 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - markdown中调用方式 : [demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -} - -``` -您可以在任意详情页中, 通过以下方式调用 - -``` -[demo: 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -```"""; - -} diff --git a/lib/standard_pages/local_hnaxu_5d7178d0_42ae_4241_9c8a_5c9e1f92b096/index.md b/lib/standard_pages/local_hnaxu_5d7178d0_42ae_4241_9c8a_5c9e1f92b096/index.md deleted file mode 100644 index 61a5f516..00000000 --- a/lib/standard_pages/local_hnaxu_5d7178d0_42ae_4241_9c8a_5c9e1f92b096/index.md +++ /dev/null @@ -1,48 +0,0 @@ -# 标准的详情页 - -您可以在这个界面中, 编写大多数的markdown文案, 他会在 **goCli watch** 下同步被编译成 **dart** 文件 - -您可以通过goCli创建详情页所需要的demo - -``` -goCLi createDemo -``` - -在flutter go 根文件下通过命令行输入以上命令可以进行以下操作 - -[✓] 请输入新增加的demo名称? demoName - -[✓] 请输入您的姓名(使用英文) yourName - -[✓] 请输入您的github的email地址 yourEmail - -[✓] 请输入您demo的描述 这是一个测试的标准demo - - -在完成以上操作后, 可以得到这样的输出: - - -``` ------------------- -您新增的组件信息如下 -================== -{ - name : demoName - author : yourName - email : yourEmail - desc : 这是一个测试的标准demo -} -================== -[✓] Is this the config you want ? (Y/n) y -{ - 新建的demo文件位于 : /flutter go/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - demoId为 : 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - markdown中调用方式 : [demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -} - -``` -您可以在任意详情页中, 通过以下方式调用 - -``` -[demo: 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -``` \ No newline at end of file diff --git a/lib/standard_pages/standard_for_slider_sanfan_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096/.page.json b/lib/standard_pages/standard_for_slider_sanfan_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096/.page.json deleted file mode 100644 index fa8a3a6a..00000000 --- a/lib/standard_pages/standard_for_slider_sanfan_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096/.page.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "standard_for_slider", - "screenShot": "", - "author":"sanfan", - "title":"slider组件", - "email": "hanxu@qq.com", - "desc": "slider, new Slider", - "id": "8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096" -} - \ No newline at end of file diff --git a/lib/standard_pages/standard_for_slider_sanfan_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096/index.dart b/lib/standard_pages/standard_for_slider_sanfan_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096/index.dart deleted file mode 100644 index bd222ee8..00000000 --- a/lib/standard_pages/standard_for_slider_sanfan_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096/index.dart +++ /dev/null @@ -1,61 +0,0 @@ -String getMd() { - return """ - # Slider Page - -您可以在这个界面中, 编写大多数的markdown文案, 他会在 **goCli watch** 下同步被编译成 **dart** 文件 - -您可以通过goCli创建详情页所需要的demo - - - - -``` -goCLi createDemo -``` - -在flutter go 根文件下通过命令行输入以上命令可以进行以下操作 - -[✓] 请输入新增加的demo名称? demoName - -[✓] 请输入您的姓名(使用英文) yourName - -[✓] 请输入您的github的email地址 yourEmail - -[✓] 请输入您demo的描述 这是一个测试的标准demo - - -在完成以上操作后, 可以得到这样的输出: - - -``` ------------------- -您新增的组件信息如下 -================== -{ - name : demoName - author : yourName - email : yourEmail - desc : 这是一个测试的标准demo -} -================== -[✓] Is this the config you want ? (Y/n) y -{ - 新建的demo文件位于 : /flutter go/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - demoId为 : 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - markdown中调用方式 : [demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -} - -``` -您可以在任意详情页中, 通过以下方式调用 - -``` -[demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -``` - -调用效果: - -[demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096]"""; - - -} - \ No newline at end of file diff --git a/lib/standard_pages/standard_for_slider_sanfan_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096/index.md b/lib/standard_pages/standard_for_slider_sanfan_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096/index.md deleted file mode 100644 index 0cf3597e..00000000 --- a/lib/standard_pages/standard_for_slider_sanfan_8ab2b5c2_42ae_4241_9c8a_5c9e1f92b096/index.md +++ /dev/null @@ -1,55 +0,0 @@ -# Slider Page - -您可以在这个界面中, 编写大多数的markdown文案, 他会在 **goCli watch** 下同步被编译成 **dart** 文件 - -您可以通过goCli创建详情页所需要的demo - - - - -``` -goCLi createDemo -``` - -在flutter go 根文件下通过命令行输入以上命令可以进行以下操作 - -[✓] 请输入新增加的demo名称? demoName - -[✓] 请输入您的姓名(使用英文) yourName - -[✓] 请输入您的github的email地址 yourEmail - -[✓] 请输入您demo的描述 这是一个测试的标准demo - - -在完成以上操作后, 可以得到这样的输出: - - -``` ------------------- -您新增的组件信息如下 -================== -{ - name : demoName - author : yourName - email : yourEmail - desc : 这是一个测试的标准demo -} -================== -[✓] Is this the config you want ? (Y/n) y -{ - 新建的demo文件位于 : /flutter go/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - demoId为 : 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - markdown中调用方式 : [demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -} - -``` -您可以在任意详情页中, 通过以下方式调用 - -``` -[demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -``` - -调用效果: - -[demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] \ No newline at end of file diff --git a/lib/standard_pages/standard_sanfan_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096/.page.json b/lib/standard_pages/standard_sanfan_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096/.page.json deleted file mode 100644 index 1e6d54b7..00000000 --- a/lib/standard_pages/standard_sanfan_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096/.page.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "standard", - "screenShot": "", - "author":"sanfan", - "title":"介绍页", - "email": "hanxu317@qq.com", - "desc": "desc", - "id": "ee4feb8e_32ae_4241_9c8a_5c9e1f92b096" -} - \ No newline at end of file diff --git a/lib/standard_pages/standard_sanfan_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096/index.dart b/lib/standard_pages/standard_sanfan_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096/index.dart deleted file mode 100644 index be0e2607..00000000 --- a/lib/standard_pages/standard_sanfan_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096/index.dart +++ /dev/null @@ -1,56 +0,0 @@ -String getMd() { - return """ - # 标准的详情页 - -您可以在这个界面中, 编写大多数的markdown文案, 他会在 **goCli watch** 下同步被编译成 **dart** 文件 - -您可以通过goCli创建详情页所需要的demo -[demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] - - -``` -goCLi createDemo -``` - -在flutter go 根文件下通过命令行输入以上命令可以进行以下操作 - -[✓] 请输入新增加的demo名称? demoName - -[✓] 请输入您的姓名(使用英文) yourName - -[✓] 请输入您的github的email地址 yourEmail - -[✓] 请输入您demo的描述 这是一个测试的标准demo - - -在完成以上操作后, 可以得到这样的输出: - - -``` ------------------- -您新增的组件信息如下 -================== -{ - name : demoName - author : yourName - email : yourEmail - desc : 这是一个测试的标准demo -} -================== -[✓] Is this the config you want ? (Y/n) y -{ - 新建的demo文件位于 : /flutter go/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - demoId为 : 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - markdown中调用方式 : [demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -} - -``` -您可以在任意详情页中, 通过以下方式调用 - -``` -[demo: 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -```"""; - - -} - \ No newline at end of file diff --git a/lib/standard_pages/standard_sanfan_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096/index.md b/lib/standard_pages/standard_sanfan_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096/index.md deleted file mode 100644 index 4744d61f..00000000 --- a/lib/standard_pages/standard_sanfan_ee4feb8e_32ae_4241_9c8a_5c9e1f92b096/index.md +++ /dev/null @@ -1,50 +0,0 @@ -# 标准的详情页 - -您可以在这个界面中, 编写大多数的markdown文案, 他会在 **goCli watch** 下同步被编译成 **dart** 文件 - -您可以通过goCli创建详情页所需要的demo -[demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] - - -``` -goCLi createDemo -``` - -在flutter go 根文件下通过命令行输入以上命令可以进行以下操作 - -[✓] 请输入新增加的demo名称? demoName - -[✓] 请输入您的姓名(使用英文) yourName - -[✓] 请输入您的github的email地址 yourEmail - -[✓] 请输入您demo的描述 这是一个测试的标准demo - - -在完成以上操作后, 可以得到这样的输出: - - -``` ------------------- -您新增的组件信息如下 -================== -{ - name : demoName - author : yourName - email : yourEmail - desc : 这是一个测试的标准demo -} -================== -[✓] Is this the config you want ? (Y/n) y -{ - 新建的demo文件位于 : /flutter go/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - demoId为 : 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - markdown中调用方式 : [demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -} - -``` -您可以在任意详情页中, 通过以下方式调用 - -``` -[demo: 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -``` \ No newline at end of file diff --git a/lib/standard_pages/test_abc_84f38e00_42ae_4241_9c8a_5c9e1f92b096/.page.json b/lib/standard_pages/test_abc_84f38e00_42ae_4241_9c8a_5c9e1f92b096/.page.json deleted file mode 100644 index 297b026e..00000000 --- a/lib/standard_pages/test_abc_84f38e00_42ae_4241_9c8a_5c9e1f92b096/.page.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "test", - "screenShot": "", - "author":"abc", - "title":"ya", - "email": "adsf.com", - "desc": "desc", - "id": "84f38e00_42ae_4241_9c8a_5c9e1f92b096" -} - \ No newline at end of file diff --git a/lib/standard_pages/test_abc_84f38e00_42ae_4241_9c8a_5c9e1f92b096/index.dart b/lib/standard_pages/test_abc_84f38e00_42ae_4241_9c8a_5c9e1f92b096/index.dart deleted file mode 100644 index 6e506d10..00000000 --- a/lib/standard_pages/test_abc_84f38e00_42ae_4241_9c8a_5c9e1f92b096/index.dart +++ /dev/null @@ -1,52 +0,0 @@ -String getMd() { - return """ - # 标准的详情页 - -您可以在这个界面中, 编写大多数的markdown文案, 他会在 **goCli watch** 下同步被编译成 **dart** 文件 - -您可以通过goCli创建详情页所需要的demo - -``` -goCLi createDemo -``` - -在flutter go 根文件下通过命令行输入以上命令可以进行以下操作 - -[✓] 请输入新增加的demo名称? demoName - -[✓] 请输入您的姓名(使用英文) yourName - -[✓] 请输入您的github的email地址 yourEmail - -[✓] 请输入您demo的描述 这是一个测试的标准demo - - -在完成以上操作后, 可以得到这样的输出: - - -``` ------------------- -您新增的组件信息如下 -================== -{ - name : demoName - author : yourName - email : yourEmail - desc : 这是一个测试的标准demo -} -================== -[✓] Is this the config you want ? (Y/n) y -{ - 新建的demo文件位于 : /flutter go/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - demoId为 : 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - markdown中调用方式 : [demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -} - -``` -您可以在任意详情页中, 通过以下方式调用 - -``` -[demo: 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -```"""; - -} diff --git a/lib/standard_pages/test_abc_84f38e00_42ae_4241_9c8a_5c9e1f92b096/index.md b/lib/standard_pages/test_abc_84f38e00_42ae_4241_9c8a_5c9e1f92b096/index.md deleted file mode 100644 index 61a5f516..00000000 --- a/lib/standard_pages/test_abc_84f38e00_42ae_4241_9c8a_5c9e1f92b096/index.md +++ /dev/null @@ -1,48 +0,0 @@ -# 标准的详情页 - -您可以在这个界面中, 编写大多数的markdown文案, 他会在 **goCli watch** 下同步被编译成 **dart** 文件 - -您可以通过goCli创建详情页所需要的demo - -``` -goCLi createDemo -``` - -在flutter go 根文件下通过命令行输入以上命令可以进行以下操作 - -[✓] 请输入新增加的demo名称? demoName - -[✓] 请输入您的姓名(使用英文) yourName - -[✓] 请输入您的github的email地址 yourEmail - -[✓] 请输入您demo的描述 这是一个测试的标准demo - - -在完成以上操作后, 可以得到这样的输出: - - -``` ------------------- -您新增的组件信息如下 -================== -{ - name : demoName - author : yourName - email : yourEmail - desc : 这是一个测试的标准demo -} -================== -[✓] Is this the config you want ? (Y/n) y -{ - 新建的demo文件位于 : /flutter go/lib/page_demo_package/demoName_yourName_1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - demoId为 : 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096 - markdown中调用方式 : [demo:1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -} - -``` -您可以在任意详情页中, 通过以下方式调用 - -``` -[demo: 1a29aa8e_32ae_4241_9c8a_5c9e1f92b096] -``` \ No newline at end of file diff --git a/lib/utils/data_utils.dart b/lib/utils/data_utils.dart index d57f3cba..97858111 100644 --- a/lib/utils/data_utils.dart +++ b/lib/utils/data_utils.dart @@ -1,200 +1,30 @@ import 'dart:async' show Future; -import 'package:fluro/fluro.dart'; -import 'package:flutter_go/model/collection.dart'; -import 'package:flutter_go/model/version.dart'; -import 'package:flutter_go/model/widget.dart'; -import 'package:package_info/package_info.dart'; -import 'package:flutter_go/model/responseData.dart'; - import './net_utils.dart'; import '../model/user_info.dart'; import 'package:flutter_go/api/api.dart'; -import 'package:flutter_go/routers/application.dart'; -import 'package:flutter_go/routers/routers.dart'; -class DataUtils { + +class DataUtils{ // 登陆获取用户信息 - static Future doLogin(Map params) async { + static Future doLogin(Map params) async{ var response = await NetUtils.post(Api.DO_LOGIN, params); - try { - UserInformation userInfo = UserInformation.fromJson(response['data']); - return userInfo; - } catch (err) { - return response['message']; - } - } - - // 获取用户信息 - static Future getUserInfo(Map params) async { - var response = await NetUtils.get(Api.GET_USER_INFO, params); - try { - UserInformation userInfo = UserInformation.fromJson(response['data']); - return userInfo; - } catch (err) { - return response['message']; - } + UserInfo userInfo = UserInfo.fromJson(response['data']); + return userInfo; } // 验证登陆 - static Future checkLogin() async { + + static Future checkLogin() async{ var response = await NetUtils.get(Api.CHECK_LOGIN); - print('response: $response'); - try { - if (response['success']) { - print('${response['success']} ${response['data']} response[succes]'); - UserInformation userInfo = UserInformation.fromJson(response['data']); - print('${response['data']} $userInfo'); - return userInfo; - } else { - return response['success']; - } - } catch (err) { - return response['message']; - } - } - - // 一键反馈 - static Future feedback(Map params, context) async { - var response = await NetUtils.post(Api.FEEDBACK, params); - if (response['status'] == 401 && response['message'] == '请先登录') { - Application.router.navigateTo(context, '${Routes.loginPage}', - transition: TransitionType.nativeModal); - } - return response['success']; - } - - //设置主题颜色 - static Future setThemeColor(int color, context) async { - var response = - await NetUtils.post(Api.SET_THEMECOLOR, {'color': color.toString()}); - if (response['status'] == 401 && response['message'] == '请先登录') { - Application.router.navigateTo(context, '${Routes.loginPage}', - transition: TransitionType.nativeModal); - } - return response['success']; - } - - //获取主题颜色 - static Future getThemeColor() async { - var response = await NetUtils.get(Api.GET_THEMECOLOR); + print('验证登陆:$response'); return response['success']; } // 退出登陆 - static Future logout() async { + static Future logout() async{ var response = await NetUtils.get(Api.LOGOUT); print('退出登陆 $response'); return response['success']; } - - // 检查版本 - static Future checkVersion(Map params) async { - var response = await NetUtils.get(Api.VERSION, params); - Version version = Version.formJson(response); - var currVersion = version.data.version; - PackageInfo packageInfo = await PackageInfo.fromPlatform(); - var localVersion = packageInfo.version; - //相同=0、大于=1、小于=-1 - // localVersion = '0.0.2'; - // currVersion = '1.0.6'; - if (currVersion.compareTo(localVersion) == 1) { - return true; - } else { - return false; - } - } - - /// 获取widget列表处的树型数据 - static Future getWidgetTreeList() async { - try { - var response = await NetUtils.get(Api.GET_WIDGET_TREE); - print('组件树dddd:$response'); - if (response != null && response['success']) { - return response['data']; - } else { - return []; - } - } catch (error) { - print('获取组件树 error $error'); - } - } - - // 校验是否收藏 - static Future checkCollected(Map params) async { - print('url 地址:${Api.CHECK_COLLECTED} $params'); - try { - var response = await NetUtils.post(Api.CHECK_COLLECTED, params); - return response != null && response['hasCollected']; - } catch (error) { - print('校验收藏 error $error'); - } - } - - // 添加收藏 - static Future addCollected(Map params, context) async { - var response = await NetUtils.post(Api.ADD_COLLECTION, params); - if (response['status'] == 401 && response['message'] == '请先登录') { - Application.router.navigateTo(context, '${Routes.loginPage}', - transition: TransitionType.nativeModal); - } - return response != null && response['success']; - } - - // 移出收藏 - static Future removeCollected(Map params, context) async { - var response = await NetUtils.post(Api.REMOVE_COLLECTION, params); - if (response['status'] == 401 && response['message'] == '请先登录') { - Application.router.navigateTo(context, '${Routes.loginPage}', - transition: TransitionType.nativeModal); - } - return response != null && response['success']; - } - - // 获取全部收藏 - static Future getAllCollections(context) async { - var response = await NetUtils.get(Api.GET_ALL_COLLECTION); - List responseList = []; - if (response['status'] == 401 && response['message'] == '请先登录') { - Application.router.navigateTo(context, '${Routes.loginPage}', - transition: TransitionType.nativeModal); - } - if (response != null && response['success'] == true) { - for (int i = 0; i < response['data'].length; i++) { - Map tempCo = response['data'][i]; - responseList.add(Collection.fromJSON( - {"name": tempCo['name'], "router": tempCo['url']})); - } - return responseList; - } else { - return []; - } - } - - // 搜索组件 - static Future searchWidget(String name) async { - var response = await NetUtils.get(Api.SEARCH_WIDGET, {"name": name}); - List list = []; - if (response != null && response['success'] == true) { - for (int i = 0; i < response['data'].length; i++) { - var json = response['data'][i]; - String routerName; - if (json['display'] == 'old') { - routerName = json['path']; - } else { - routerName = json['pageId']; - } - Map tempMap = { - "name": json['name'], - "cnName": json['name'], - "routerName": routerName, - "catId": json['parentId'].runtimeType == String ? int.parse(json['parentId']) : json['parentId'] - }; - list.add(WidgetPoint.fromJSON(tempMap)); - } - return list; - } else { - return []; - } - } -} +} \ No newline at end of file diff --git a/lib/utils/net_utils.dart b/lib/utils/net_utils.dart index b93c00d1..2f39746c 100644 --- a/lib/utils/net_utils.dart +++ b/lib/utils/net_utils.dart @@ -4,14 +4,15 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:dio/dio.dart'; import 'package:path_provider/path_provider.dart'; -Map optHeader = { - 'accept-language': 'zh-cn', - 'content-type': 'application/json' +Map optHeader = { + 'accept-language':'zh-cn', + 'content-type':'application/json' }; -var dio = new Dio(BaseOptions(connectTimeout: 30000, headers: optHeader)); +var dio = new Dio(BaseOptions(connectTimeout: 30000,headers: optHeader)); class NetUtils { + static Future get(String url, [Map params]) async { var response; @@ -19,7 +20,7 @@ class NetUtils { // (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = // (HttpClient client) { // client.findProxy = (uri) { - // return "PROXY 30.10.24.79:8889"; + // return "PROXY 30.10.26.193:8888"; // }; // }; @@ -27,6 +28,7 @@ class NetUtils { String documentsPath = documentsDir.path; var dir = new Directory("$documentsPath/cookies"); await dir.create(); + // print('documentPath:${dir.path}'); dio.interceptors.add(CookieManager(PersistCookieJar(dir: dir.path))); if (params != null) { response = await dio.get(url, queryParameters: params); @@ -37,18 +39,6 @@ class NetUtils { } static Future post(String url, Map params) async { - // // 设置代理 便于本地 charles 抓包 - // (dio.httpClientAdapter as DefaultHttpClientAdapter).onHttpClientCreate = - // (HttpClient client) { - // client.findProxy = (uri) { - // return "PROXY 30.10.24.79:8889"; - // }; - // }; - Directory documentsDir = await getApplicationDocumentsDirectory(); - String documentsPath = documentsDir.path; - var dir = new Directory("$documentsPath/cookies"); - await dir.create(); - dio.interceptors.add(CookieManager(PersistCookieJar(dir: dir.path))); var response = await dio.post(url, data: params); return response.data; } diff --git a/lib/views/collection_page/collection_full_page.dart b/lib/views/collection_page/collection_full_page.dart deleted file mode 100644 index 292be897..00000000 --- a/lib/views/collection_page/collection_full_page.dart +++ /dev/null @@ -1,168 +0,0 @@ -/// @Author: 一凨 -/// @Date: 2019-06-05 14:01:03 -/// @Last Modified by: 一凨 -/// @Last Modified time: 2019-06-05 14:01:03 -import 'package:flutter/material.dart'; -import 'package:event_bus/event_bus.dart'; -import 'package:fluro/fluro.dart'; - -import 'package:flutter_go/model/collection.dart'; -import 'package:flutter_go/routers/application.dart'; -import 'package:flutter_go/event/event_bus.dart'; -import 'package:flutter_go/event/event_model.dart'; -import 'package:flutter_go/utils/data_utils.dart'; - -class CollectionFullPage extends StatefulWidget { - final bool hasLogined; - CollectionFullPage({Key key, this.hasLogined}) : super(key: key); - - @override - _CollectionFullPageState createState() => _CollectionFullPageState(); -} - -class _CollectionFullPageState extends State { - _CollectionFullPageState() { - final eventBus = new EventBus(); - ApplicationEvent.event = eventBus; - } - - CollectionControlModel _collectionControl = new CollectionControlModel(); - List _collectionList = []; - ScrollController _scrollController = new ScrollController(); - var _icons; - - @override - void initState() { - super.initState(); - _getList(); - ApplicationEvent.event.on().listen((event) { - _getList(); - }); - } - - void _getList() { - _collectionList.clear(); - DataUtils.getAllCollections(context).then((collectionList) { - if (this.mounted) { - setState(() { - _collectionList = collectionList; - }); - } - }); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - Widget _renderList(context, index) { - if (index == 0) { - return Container( - height: 40.0, - padding: const EdgeInsets.only(left: 10.0), - child: Row( - children: [ - Icon( - Icons.warning, - size: 22.0, - ), - SizedBox( - width: 5.0, - ), - Text('常用的组件都可以收藏在这里哦'), - ], - ), - ); - } - if (_collectionList[index - 1].router.contains('http')) { - if (_collectionList[index - 1].name.endsWith('Doc')) { - _icons = Icons.library_books; - } else { - _icons = Icons.language; - } - } else { - _icons = Icons.extension; - } - return Container( - padding: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 5.0), - margin: const EdgeInsets.only(bottom: 7.0), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - new BoxShadow( - color: const Color(0xFFd0d0d0), - blurRadius: 1.0, - spreadRadius: 2.0, - offset: Offset(3.0, 2.0), - ), - ], - ), - child: ListTile( - leading: Icon( - _icons, - size: 30.0, - color: Theme.of(context).primaryColor, - ), - title: Text( - Uri.decodeComponent(_collectionList[index - 1].name), - overflow: TextOverflow.ellipsis, - style: TextStyle(fontSize: 17.0), - ), - trailing: - Icon(Icons.keyboard_arrow_right, color: Colors.grey, size: 30.0), - onTap: () { - Application.router.navigateTo( - context, _collectionList[index - 1].router, - transition: TransitionType.inFromRight); - - // if (_collectionList[index - 1].router.contains('http')) { - // // 注意这里title已经转义过了 - // Application.router.navigateTo(context, - // '${Routes.webViewPage}?title=${_collectionList[index - 1].name}&url=${Uri.encodeComponent(_collectionList[index - 1].router)}'); - // } else { - // Application.router - // .navigateTo(context, "${_collectionList[index - 1].router}"); - // } - }, - ), - ); - } - - ListView buildContent() { - if (_collectionList.length == 0) { - return ListView( - children: [ - Column( - children: [ - Image.asset( - 'assets/images/nothing.png', - fit: BoxFit.contain, - width: MediaQuery.of(context).size.width / 2, - ), - Text('暂无收藏,赶紧去收藏一个吧!'), - ], - ), - ], - ); - } - return ListView.builder( - itemBuilder: _renderList, - itemCount: _collectionList.length + 1, - controller: _scrollController, - ); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('我的收藏'), - ), - body: Container( - child: buildContent(), - ), - ); - } -} diff --git a/lib/views/collection_page/collection_page.dart b/lib/views/collection_page/collection_page.dart index 3935d9d2..6b4c609b 100644 --- a/lib/views/collection_page/collection_page.dart +++ b/lib/views/collection_page/collection_page.dart @@ -1,5 +1,5 @@ -/// @Author: 一凨 -/// @Date: 2019-01-08 17:12:58 +/// @Author: 一凨 +/// @Date: 2019-01-08 17:12:58 /// @Last Modified by: 一凨 /// @Last Modified time: 2019-01-14 20:13:28 @@ -11,13 +11,9 @@ import 'package:flutter_go/routers/application.dart'; import 'package:flutter_go/routers/routers.dart'; import 'package:flutter_go/event/event_bus.dart'; import 'package:flutter_go/event/event_model.dart'; -import 'package:flutter_go/utils/data_utils.dart'; + class CollectionPage extends StatefulWidget { - final bool hasLogined; - - CollectionPage({Key key, this.hasLogined}) : super(key: key); - _CollectionPageState createState() => _CollectionPageState(); } @@ -48,13 +44,16 @@ class _CollectionPageState extends State { void _getList() { _collectionList.clear(); - // DataUtils.getAllCollections(context).then((collectionList) { - // if (this.mounted) { - // setState(() { - // _collectionList = collectionList; - // }); - // } - // }); + _collectionControl.getAllCollection().then((resultList) { + resultList.forEach((item) { + _collectionList.add(item); + }); + if (this.mounted) { + setState(() { + _collectionList = _collectionList; + }); + } + }); } Widget _renderList(context, index) { @@ -106,8 +105,7 @@ class _CollectionPageState extends State { color: Theme.of(context).primaryColor, ), title: Text( - _collectionList[index - 1].name, -// Uri.decodeComponent(_collectionList[index - 1].name), + Uri.decodeComponent(_collectionList[index - 1].name), overflow: TextOverflow.ellipsis, style: TextStyle(fontSize: 17.0), ), diff --git a/lib/views/first_page/drawer_page.dart b/lib/views/first_page/drawer_page.dart deleted file mode 100644 index f0fc1be0..00000000 --- a/lib/views/first_page/drawer_page.dart +++ /dev/null @@ -1,247 +0,0 @@ -import 'dart:async'; - -import 'package:fluro/fluro.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_go/components/single_theme_color.dart'; -import 'package:flutter_go/model/user_info.dart'; -import 'package:share/share.dart'; -import 'package:flutter_go/utils/data_utils.dart'; -import 'package:flutter_go/routers/application.dart'; -import 'package:flutter_go/routers/routers.dart'; -import './search_page.dart'; -import 'package:flutter_go/event/event_bus.dart'; -import 'package:flutter_go/event/event_model.dart'; -import 'package:event_bus/event_bus.dart'; - -const List> defalutThemeColor = [ - {'cnName': 'Flutter篮', 'value': 0xFF3391EA}, - {'cnName': '拍卖红', 'value': 0xFFC91B3A}, - {'cnName': '阿里橙', 'value': 0xFFF7852A}, -]; - -class DrawerPage extends StatefulWidget { - final UserInformation userInfo; - DrawerPage({Key key, this.userInfo}) : super(key: key); - - @override - _DrawerPageState createState() => _DrawerPageState(); -} - -class _DrawerPageState extends State { - final TextStyle textStyle = - TextStyle(fontSize: 16, fontWeight: FontWeight.w300); - bool hasLogin; - - _DrawerPageState() { - final eventBus = new EventBus(); - ApplicationEvent.event = eventBus; - } - - @override - void initState() { - super.initState(); - ApplicationEvent.event.on().listen((event) { - print('接收到的 event ${event.settingThemeColor}'); - }); - hasLogin = this.widget.userInfo.id != 0; - } - - Future logoutDialog(BuildContext context) { - return showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: Text('确认退出登陆?'), - // content: Text('退出登陆后将没法进行'), - actions: [ - FlatButton( - onPressed: () { - // 退出登陆 - DataUtils.logout().then((result) { - if (result) { - Application.router.navigateTo( - context, '${Routes.loginPage}', - transition: TransitionType.native, clearStack: true); - } - }); - }, - child: Text( - '确认', - style: TextStyle(color: Colors.red), - ), - ), - FlatButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text('取消'), - ) - ], - ); - }); - } - - void showLogoutDialog(BuildContext context) { - if (hasLogin) { - logoutDialog(context); - } else { - Application.router.navigateTo(context, '${Routes.loginPage}', - transition: TransitionType.native, clearStack: true); - } - } - - void pushPage(BuildContext context, Widget page, {String pageName}) { - if (context == null || page == null) return; - Navigator.push(context, CupertinoPageRoute(builder: (ctx) => page)); - } - - Future buildSimpleDialog(BuildContext context) { - return showDialog( - context: context, - builder: (BuildContext context) { - return Dialog( - child: Container( - padding: const EdgeInsets.symmetric(vertical: 20.0), - height: 300.0, - color: Colors.white, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - mainAxisSize: MainAxisSize.max, - children: buildThemeColorChildren(), - )), - ); - }); - } - - List buildThemeColorChildren() { - List tempWidget = []; - for (var i = 0; i < defalutThemeColor.length; i++) { - tempWidget.add(SingleThemeColor( - themeColor: defalutThemeColor[i]['value'], - coloeName: defalutThemeColor[i]['cnName'], - )); - } - return tempWidget; - } - - @override - Widget build(BuildContext context) { - return ListView( - padding: EdgeInsets.zero, - children: [ - UserAccountsDrawerHeader( - accountName: Text(''), - accountEmail: Container( - padding: const EdgeInsets.only(bottom: 20.0), - child: Text( - hasLogin ? widget.userInfo.username : ' ', - style: TextStyle(fontSize: 28), - ), - ), - decoration: BoxDecoration( - image: new DecorationImage( - fit: BoxFit.cover, - image: new NetworkImage(hasLogin - ? widget.userInfo.avatarPic - : 'https://hbimg.huabanimg.com/9bfa0fad3b1284d652d370fa0a8155e1222c62c0bf9d-YjG0Vt_fw658'), - ), - ), - ), - // new Divider(), - ListTile( - leading: Icon( - Icons.search, - size: 27.0, - ), - title: Text( - '全网搜', - style: textStyle, - ), - onTap: () { - pushPage(context, SearchPage(), pageName: "SearchPage"); - }, - ), - ListTile( - leading: Icon( - Icons.favorite, - size: 27.0, - ), - title: Text( - '我的收藏', - style: textStyle, - ), - onTap: () { - Application.router.navigateTo(context, - '${Routes.collectionFullPage}?hasLogin=${hasLogin.toString()}', - transition: TransitionType.fadeIn); - }, - ), - // new Divider(), - // ListTile( - // leading: Icon( - // Icons.settings, - // size: 27.0, - // ), - // title: Text( - // '主题色', - // style: textStyle, - // ), - // onTap: () { - // buildSimpleDialog(context); - // }, - // ), - new Divider(), - - ListTile( - leading: Icon( - Icons.email, - size: 27.0, - ), - title: Text( - '反馈/建议', - style: textStyle, - ), - onTap: () { - if (hasLogin) { - //issue 未登陆状态 返回登陆页面 - Application.router.navigateTo(context, '${Routes.issuesMessage}'); - } else { - //No description provided. - Application.router.navigateTo(context, '${Routes.loginPage}'); - - } - }, - ), - ListTile( - leading: Icon( - Icons.share, - size: 27.0, - ), - title: Text( - '分享 App', - style: textStyle, - ), - onTap: () { - Share.share('https://flutter-go.pub/website/'); - }, - ), - new Divider(), - ListTile( - leading: Icon( - hasLogin ? Icons.exit_to_app : Icons.supervised_user_circle, - size: 27.0, - ), - title: Text( - hasLogin ? '退出登陆' : '点击登录', - style: textStyle, - ), - onTap: () { - showLogoutDialog(context); - // logoutDialog(context); - }, - ), - ], - ); - } -} diff --git a/lib/views/first_page/first_page.dart b/lib/views/first_page/first_page.dart index 38840ba1..be9de1e5 100644 --- a/lib/views/first_page/first_page.dart +++ b/lib/views/first_page/first_page.dart @@ -26,33 +26,31 @@ class FirstPageState extends State with AutomaticKeepAliveClientMixin bool get wantKeepAlive => true; - @override + @override void initState() { super.initState(); if (key == null) { - key = GlobalKey(); - // key = const Key('__RIKEY1__'); + key = GlobalKey(); + // key = const Key('__RIKEY1__'); //获取sharePre - _unKnow = _prefs.then((SharedPreferences prefs) { - return (prefs.getBool('disclaimer::Boolean') ?? false); - }); + _unKnow = _prefs.then((SharedPreferences prefs) { + return (prefs.getBool('disclaimer::Boolean') ?? false); + }); /// 判断是否需要弹出免责声明,已经勾选过不在显示,就不会主动弹 _unKnow.then((bool value) { - new Future.delayed(const Duration(seconds: 1),(){ - if (!value && key.currentState is DisclaimerMsgState && key.currentState.showAlertDialog is Function) { - key.currentState.showAlertDialog(context); - } - }); + new Future.delayed(const Duration(seconds: 1),(){ + if (!value && key.currentState is DisclaimerMsgState && key.currentState.showAlertDialog is Function) { + key.currentState.showAlertDialog(context); + } + }); }); } } Future getIndexListData([Map params]) async { - /// const juejin_flutter = 'https://timeline-merger-ms.juejin.im/v1/get_tag_entry?src=web&tagId=5a96291f6fb9a0535b535438'; - const juejin_flutter = 'https://fluttergo.pub:9527/juejin.im/v1/get_tag_entry?src=web&tagId=5a96291f6fb9a0535b535438'; - + const juejin_flutter = 'https://timeline-merger-ms.juejin.im/v1/get_tag_entry?src=web&tagId=5a96291f6fb9a0535b535438'; var pageIndex = (params is Map) ? params['pageIndex'] : 0; final _param = {'page':pageIndex,'pageSize':20,'sort':'rankIndex'}; var responseList = []; @@ -95,15 +93,15 @@ class FirstPageState extends State with AutomaticKeepAliveClientMixin Column( children: [ Stack( - //alignment: const FractionalOffset(0.9, 0.1),//方法一 - children: [ - Pagination(), - Positioned(//方法二 - top: 10.0, - left: 0.0, - child: DisclaimerMsg(key:key,pWidget:this) - ), - ]), + //alignment: const FractionalOffset(0.9, 0.1),//方法一 + children: [ + Pagination(), + Positioned(//方法二 + top: 10.0, + left: 0.0, + child: DisclaimerMsg(key:key,pWidget:this) + ), + ]), SizedBox(height: 1, child:Container(color: Theme.of(context).primaryColor)), SizedBox(height: 10), ], @@ -129,7 +127,7 @@ class FirstPageState extends State with AutomaticKeepAliveClientMixin // SizedBox(height: 2, child:Container(color: Theme.of(context).primaryColor)), new Expanded( //child: new List(), - child: listComp.ListRefresh(getIndexListData,makeCard,headerView) + child: listComp.ListRefresh(getIndexListData,makeCard,headerView) ) ] diff --git a/lib/views/first_page/main_page.dart b/lib/views/first_page/main_page.dart index 9301ab55..43383a85 100644 --- a/lib/views/first_page/main_page.dart +++ b/lib/views/first_page/main_page.dart @@ -1,67 +1,54 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; -import 'package:flutter_go/views/first_page/drawer_page.dart'; import './first_page.dart'; import './sub_page.dart'; import './main_app_bar.dart'; import './search_page.dart'; -import 'package:flutter_go/model/user_info.dart'; -import 'package:flutter_go/routers/application.dart' show Application; -import 'package:flutter_go/routers/routers.dart' show Routes; - -DefaultTabController _tabController; -TabBar _tabBar; class _Page { final String labelId; - final int labelIndex; - _Page(this.labelId,this.labelIndex); + _Page(this.labelId); } final List<_Page> _allPages = <_Page>[ - _Page('热门资讯', 1), - _Page('FG-官网', 2), - _Page('FG-web版', 3), - ///_Page('项目4'), + _Page('项目1'), + _Page('项目2'), + _Page('项目3'), + _Page('项目4'), ]; class MainPage extends StatelessWidget { - final UserInformation userInfo; - - MainPage({Key key, this.userInfo}) : super(key: key); @override Widget build(BuildContext context) { print("MainPagess build......"); - _tabController = DefaultTabController( + return DefaultTabController( length: _allPages.length, - child: Scaffold( - appBar: MyAppBar( + child: Scaffold( + appBar: new MyAppBar( leading: Container( - child: ClipOval( - child: Image.network( - userInfo.id == 0?'https://hbimg.huabanimg.com/9bfa0fad3b1284d652d370fa0a8155e1222c62c0bf9d-YjG0Vt_fw658':userInfo.avatarPic, - scale: 15.0, - ), - )), + child: new ClipOval( + child: Image.network( + 'https://hbimg.huabanimg.com/9bfa0fad3b1284d652d370fa0a8155e1222c62c0bf9d-YjG0Vt_fw658', + scale: 15.0, + ), + ) + ), centerTitle: true, - title: TabLayout(), + title: TabLayout(), actions: [ - IconButton( + IconButton( icon: Icon(Icons.search), onPressed: () { pushPage(context, SearchPage(), pageName: "SearchPage"); }) ], ), - drawer: Drawer( - child: DrawerPage( - userInfo: userInfo - ), - ), - body: TabBarViewLayout(), + body: TabBarViewLayout(), +// drawer: Drawer( +// child: MainLeftPage(), +// ), )); - return _tabController; } } @@ -73,38 +60,35 @@ void pushPage(BuildContext context, Widget page, {String pageName}) { class TabLayout extends StatelessWidget { @override Widget build(BuildContext context) { - _tabBar = TabBar( + return TabBar( isScrollable: true, //labelPadding: EdgeInsets.all(12.0), - labelPadding: EdgeInsets.only(top: 12.0, left: 12.0, right: 12.0), + labelPadding: EdgeInsets.only(top: 12.0,left: 12.0,right:12.0), indicatorSize: TabBarIndicatorSize.label, - tabs: _allPages.map((_Page page) => Tab(text: page.labelId)).toList(), - onTap: (index) { - if (index == 1) { - DefaultTabController.of(context).animateTo(0); - Application.router.navigateTo(context, '${Routes.webViewPage}?title=${Uri.encodeComponent('Flutter Go 官方网站')}&url=${Uri.encodeComponent('https://flutter-go.pub')}'); - } else if (index == 2) { - -// new Future.delayed(const Duration(seconds: 1),(){ -// showAlertDialog(Application.globalContext); -// }); - - DefaultTabController.of(context).animateTo(0); - Application.router.navigateTo(context, '${Routes.webViewPage}?title=${Uri.encodeComponent('Flutter Go web版(H5)')}&url=${Uri.encodeComponent('https://flutter-go.pub/flutter_go_web')}'); - } - } + tabs: _allPages + .map((_Page page) => + Tab(text: page.labelId)) + .toList(), ); - return _tabBar; } } class TabBarViewLayout extends StatelessWidget { Widget buildTabView(BuildContext context, _Page page) { - int labelIndex = page.labelIndex; - switch (labelIndex) { - case 1: + String labelId = page.labelId; + switch (labelId) { + case '项目1': return FirstPage(); break; + case '项目2': + return SubPage(); + break; + case '项目3': + return SubPage(); + break; + case '项目4': + return SubPage(); + break; default: return Container(); break; @@ -116,7 +100,7 @@ class TabBarViewLayout extends StatelessWidget { print("TabBarViewLayout build......."); return TabBarView( children: _allPages.map((_Page page) { - return buildTabView(context, page); - }).toList()); + return buildTabView(context, page); + }).toList()); } } diff --git a/lib/views/first_page/search_page.dart b/lib/views/first_page/search_page.dart index e3d751c8..6d519738 100644 --- a/lib/views/first_page/search_page.dart +++ b/lib/views/first_page/search_page.dart @@ -23,7 +23,7 @@ final _industryPage = Industry.IndustryPage(itemTitle: (state){ ), subtitle: Text(state.res[index].source), onTap: () { - // 在这里对选中的结果进行解析 + // 在这里对选中的结果进行解析,因为我目前是用golang实现的,所以就没贴代码了。 print(state.res[index].source); final itemTitle = state.res[index].title; final itemUrl = state.res[index].source; @@ -44,7 +44,7 @@ class SearchPage extends StatelessWidget { /// print('suggestion::${Industry.suggestion}'); return Scaffold( appBar: PreferredSize( - preferredSize: Size(double.infinity, 52), // is the height + preferredSize: Size(double.infinity, 52), // 44 is the height child: AppBar(title: searchBarPage) ), //body: ProgressView(), @@ -160,7 +160,7 @@ class _SearchBarPageState extends State { controller: controller, decoration: InputDecoration( contentPadding: EdgeInsets.only(top: 0.0), - hintText: '全网搜索 Flutter 相关知识库', + hintText: '搜索全局flutter知识库', hintStyle:TextStyle(fontSize: 12.0), border: InputBorder.none ), diff --git a/lib/views/home.dart b/lib/views/home.dart index 862d66b2..c0f63681 100644 --- a/lib/views/home.dart +++ b/lib/views/home.dart @@ -7,12 +7,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:flutter_go/utils/data_utils.dart'; import 'package:flutter_go/utils/shared_preferences.dart'; import 'package:flutter_go/views/first_page/first_page.dart'; import 'package:flutter_go/views/first_page/main_page.dart'; -import 'package:fluro/fluro.dart'; -import 'package:flutter_go/views/user_page/user_page.dart'; import 'package:flutter_go/views/widget_page/widget_page.dart'; import 'package:flutter_go/views/welcome_page/fourth_page.dart'; import 'package:flutter_go/views/collection_page/collection_page.dart'; @@ -23,13 +20,10 @@ import 'package:flutter_go/widgets/index.dart'; import 'package:flutter_go/components/search_input.dart'; import 'package:flutter_go/model/search_history.dart'; import 'package:flutter_go/resources/widget_name_to_icon.dart'; -import 'package:flutter_go/model/user_info.dart'; + +const int ThemeColor = 0xFFC91B3A; class AppPage extends StatefulWidget { - final UserInformation userInfo; - - AppPage(this.userInfo); - @override State createState() { return _MyHomePageState(); @@ -43,37 +37,35 @@ class _MyHomePageState extends State SearchHistoryList searchHistoryList; bool isSearch = false; String appBarTitle = tabData[0]['text']; - List _list = List(); + List list = List(); int _currentIndex = 0; static List tabData = [ {'text': '业界动态', 'icon': Icon(Icons.language)}, {'text': 'WIDGET', 'icon': Icon(Icons.extension)}, - {'text': '关于手册', 'icon': Icon(Icons.import_contacts)}, - {'text': '个人中心', 'icon': Icon(Icons.account_circle)}, - + {'text': '组件收藏', 'icon': Icon(Icons.favorite)}, + {'text': '关于手册', 'icon': Icon(Icons.import_contacts)} ]; - List _myTabs = []; + List myTabs = []; @override void initState() { super.initState(); - print('widget.userInfo ${widget.userInfo}'); initSearchHistory(); for (int i = 0; i < tabData.length; i++) { - _myTabs.add(BottomNavigationBarItem( + myTabs.add(BottomNavigationBarItem( icon: tabData[i]['icon'], title: Text( tabData[i]['text'], ), )); } - _list + list // ..add(FirstPage()) - ..add(MainPage(userInfo: widget.userInfo)) - ..add(WidgetPage()) - ..add(FourthPage()) - ..add(UserPage(userInfo: widget.userInfo)); + ..add(MainPage()) + ..add(WidgetPage(Provider.db)) + ..add(CollectionPage()) + ..add(FourthPage()); } @override @@ -89,27 +81,33 @@ class _MyHomePageState extends State } void onWidgetTap(WidgetPoint widgetPoint, BuildContext context) { + List widgetDemosList = new WidgetDemoList().getDemos(); String targetName = widgetPoint.name; - searchHistoryList.add( - SearchHistory(name: targetName, targetRouter: widgetPoint.routerName)); + String targetRouter = '/category/error/404'; + widgetDemosList.forEach((item) { + if (item.name == targetName) { + targetRouter = item.routerName; + } + }); + searchHistoryList + .add(SearchHistory(name: targetName, targetRouter: targetRouter)); print("searchHistoryList1 ${searchHistoryList.toString()}"); - Application.router.navigateTo(context, widgetPoint.routerName, - transition: TransitionType.inFromRight); + print("searchHistoryList2 ${targetRouter}"); + print("searchHistoryList3 ${widgetPoint.name}"); + Application.router.navigateTo(context, "$targetRouter"); } Widget buildSearchInput(BuildContext context) { - return new SearchInput((value) async { + return new SearchInput((value) async { if (value != '') { - print('value ::: $value'); - // List list = await widgetControl.search(value); - List list = await DataUtils.searchWidget(value); + List list = await widgetControl.search(value); return list .map((item) => new MaterialSearchResult( value: item.name, icon: WidgetName2Icon.icons[item.name] ?? null, text: 'widget', onTap: () { - onWidgetTap(item, context); + onWidgetTap(item, context); }, )) .toList(); @@ -119,36 +117,35 @@ class _MyHomePageState extends State }, (value) {}, () {}); } - renderAppBar(BuildContext context, Widget widget, int index) { - if (index == 1) { - return AppBar(title: buildSearchInput(context)); + renderAppBar(BuildContext context,Widget widget,int index) { + print('renderAppBar=====>>>>>>${index}'); + if(index == 0) { + return null; } + return AppBar(title: buildSearchInput(context)); } @override Widget build(BuildContext context) { return new Scaffold( - appBar: renderAppBar(context, widget, _currentIndex), - body: IndexedStack( - index: _currentIndex, - children: _list, - ), + appBar: renderAppBar(context,widget,_currentIndex), + body: list[_currentIndex], bottomNavigationBar: BottomNavigationBar( - items: _myTabs, + items: myTabs, //高亮 被点击高亮 currentIndex: _currentIndex, //修改 页面 - onTap: _itemTapped, + onTap: _ItemTapped, //shifting :按钮点击移动效果 //fixed:固定 type: BottomNavigationBarType.fixed, - fixedColor: Theme.of(context).primaryColor, + fixedColor: Color(0xFFC91B3A), ), ); } - void _itemTapped(int index) { + void _ItemTapped(int index) { setState(() { _currentIndex = index; appBarTitle = tabData[index]['text']; diff --git a/lib/views/issuse_message_page/issuse_message_page.dart b/lib/views/issuse_message_page/issuse_message_page.dart deleted file mode 100644 index c8c0a4b8..00000000 --- a/lib/views/issuse_message_page/issuse_message_page.dart +++ /dev/null @@ -1,152 +0,0 @@ -import 'dart:convert'; - -import 'package:flutter/material.dart'; -import 'package:flutter_spinkit/flutter_spinkit.dart'; -import 'package:zefyr/zefyr.dart'; -import 'package:flutter_go/utils/data_utils.dart'; -import 'package:notus/convert.dart'; -import 'package:fluttertoast/fluttertoast.dart'; - - - -class IssuesMessagePage extends StatefulWidget { - @override - _IssuesMessagePageState createState() => _IssuesMessagePageState(); -} - -class _IssuesMessagePageState extends State { - final TextEditingController _controller = new TextEditingController(); - final ZefyrController _zefyrController = new ZefyrController(NotusDocument()); - final FocusNode _focusNode = new FocusNode(); - String _title = ""; - var _delta; - - @override - void initState() { - _controller.addListener(() { - print("_controller.text:${_controller.text}"); - setState(() { - _title = _controller.text; - }); - }); - - _zefyrController.document.changes.listen((change) { - setState(() { - _delta = _zefyrController.document.toDelta(); - }); - }); - - super.initState(); - } - - void dispose() { - _controller.dispose(); - _zefyrController.dispose(); - super.dispose(); - } - - _submit() { - if (_title.trim().isEmpty) { - _show('标题不能为空'); - } else { - String mk = (_delta==null)?'No description provided.':notusMarkdown.encode(_delta); - DataUtils.feedback({'title': _title, "body": mk},context).then((result) { - _show('提交成功'); - Navigator.maybePop(context); - }); - } - } - - _show(String msgs){ - Fluttertoast.showToast( - msg: msgs, - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.CENTER, - timeInSecForIos: 1, - backgroundColor: Theme.of(context).primaryColor, - textColor: Colors.white, - fontSize: 16.0); - } - - Widget buildLoading() { - return Opacity( - opacity: .5, - child: Container( - width: MediaQuery.of(context).size.width * 0.85, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(8.0)), - color: Colors.black, - ), - child: SpinKitPouringHourglass(color: Colors.white), - ), - ); - } - - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('反馈/意见'), - actions: [ - FlatButton.icon( - onPressed: () { - _submit(); - }, - icon: Icon( - Icons.near_me, - color: Colors.white, - size: 12, - ), - label: Text( - '发送', - style: TextStyle(color: Colors.white), - ), - ) - ], - elevation: 1.0, - ), - body: ZefyrScaffold( - child: Padding( - padding: EdgeInsets.all(8), - child: ListView( - children: [ - Text('输入标题:'), - new TextFormField( - maxLength: 50, - controller: _controller, - decoration: new InputDecoration( - hintText: 'Title', - ), - ), - Text('内容:'), - _descriptionEditor(), - ], - ), - ), - )); - } - - Widget _descriptionEditor() { - final theme = new ZefyrThemeData( - toolbarTheme: ZefyrToolbarTheme.fallback(context).copyWith( - color: Colors.grey.shade800, - toggleColor: Colors.grey.shade900, - iconColor: Colors.white, - disabledIconColor: Colors.grey.shade500, - ), - ); - - return ZefyrTheme( - data: theme, - child: ZefyrField( - height: 400.0, - decoration: InputDecoration(labelText: 'Description'), - controller: _zefyrController, - focusNode: _focusNode, - autofocus: true, - physics: ClampingScrollPhysics(), - ), - ); - } -} diff --git a/lib/views/login_page/login_page.dart b/lib/views/login_page/login_page.dart index d0025764..0e658ec8 100644 --- a/lib/views/login_page/login_page.dart +++ b/lib/views/login_page/login_page.dart @@ -1,17 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; -import 'package:event_bus/event_bus.dart'; -import 'package:fluttertoast/fluttertoast.dart'; - import 'package:flutter_go/utils/data_utils.dart'; import 'package:flutter_go/views/home.dart'; -import 'package:flutter_go/event/event_bus.dart'; -import 'package:flutter_go/event/event_model.dart'; import 'package:flutter_go/model/user_info_cache.dart'; -import 'package:flutter_go/routers/application.dart'; -import 'package:flutter_go/routers/routers.dart'; -import 'package:flutter_go/model/user_info.dart'; class LoginPage extends StatefulWidget { @override @@ -19,11 +11,6 @@ class LoginPage extends StatefulWidget { } class _LoginPageState extends State { - _LoginPageState() { - final eventBus = new EventBus(); - ApplicationEvent.event = eventBus; - } - // 利用FocusNode和_focusScopeNode来控制焦点 可以通过FocusNode.of(context)来获取widget树中默认的_focusScopeNode FocusNode _emailFocusNode = new FocusNode(); FocusNode _passwordFocusNode = new FocusNode(); @@ -48,6 +35,7 @@ class _LoginPageState extends State { _userInfoControlModel.getAllInfo().then((list) { if (list.length > 0) { UserInfo _userInfo = list[0]; + print('获取的数据:${_userInfo.username} ${_userInfo.password}'); setState(() { _userNameEditingController.text = _userInfo.username; _passwordEditingController.text = _userInfo.password; @@ -59,61 +47,27 @@ class _LoginPageState extends State { } catch (err) { print('读取用户本地存储的用户信息出错 $err'); } - - ApplicationEvent.event.on().listen((event) { - print('loginName:${event.loginName} token:${event.token} 1234567'); - if (event.isSuccess == true) { - // oAuth 认证成功 - setState(() { - isLoading = true; - }); - DataUtils.getUserInfo( - {'loginName': event.loginName, 'token': event.token}) - .then((result) { - setState(() { - isLoading = false; - }); - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute(builder: (context) => AppPage(result)), - (route) => route == null); - }).catchError((onError) { - print('获取身份信息 error:::$onError'); - setState(() { - isLoading = false; - }); - }); - } else { - Fluttertoast.showToast( - msg: '验证失败', - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.CENTER, - timeInSecForIos: 1, - backgroundColor: Theme.of(context).primaryColor, - textColor: Colors.white, - fontSize: 16.0); - } - }); } // 创建登录界面的TextForm Widget buildSignInTextForm() { - return Container( - decoration: BoxDecoration( + return new Container( + decoration: new BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(8)), ), width: MediaQuery.of(context).size.width * 0.8, height: 190, // * Flutter提供了一个Form widget,它可以对输入框进行分组,然后进行一些统一操作,如输入内容校验、输入框重置以及输入内容保存。 - child: Form( + child: new Form( key: _signInFormKey, - child: Column( + child: new Column( mainAxisSize: MainAxisSize.min, children: [ Flexible( child: Padding( padding: const EdgeInsets.only( left: 25, right: 25, top: 20, bottom: 20), - child: TextFormField( + child: new TextFormField( controller: _userNameEditingController, //关联焦点 focusNode: _emailFocusNode, @@ -124,14 +78,14 @@ class _LoginPageState extends State { _focusScopeNode.requestFocus(_passwordFocusNode); }, - decoration: InputDecoration( - icon: Icon( + decoration: new InputDecoration( + icon: new Icon( Icons.email, color: Colors.black, ), hintText: "Github 登录名", border: InputBorder.none), - style: TextStyle(fontSize: 16, color: Colors.black), + style: new TextStyle(fontSize: 16, color: Colors.black), //验证 validator: (value) { if (value.isEmpty) { @@ -146,7 +100,7 @@ class _LoginPageState extends State { ), ), ), - Container( + new Container( height: 1, width: MediaQuery.of(context).size.width * 0.75, color: Colors.grey[400], @@ -154,18 +108,18 @@ class _LoginPageState extends State { Flexible( child: Padding( padding: const EdgeInsets.only(left: 25, right: 25, top: 20), - child: TextFormField( + child: new TextFormField( controller: _passwordEditingController, focusNode: _passwordFocusNode, - decoration: InputDecoration( - icon: Icon( + decoration: new InputDecoration( + icon: new Icon( Icons.lock, color: Colors.black, ), hintText: "Github 登录密码", border: InputBorder.none, - suffixIcon: IconButton( - icon: Icon( + suffixIcon: new IconButton( + icon: new Icon( Icons.remove_red_eye, color: Colors.black, ), @@ -174,7 +128,7 @@ class _LoginPageState extends State { ), //输入密码,需要用*****显示 obscureText: !isShowPassWord, - style: TextStyle(fontSize: 16, color: Colors.black), + style: new TextStyle(fontSize: 16, color: Colors.black), validator: (value) { if (value == null || value.isEmpty) { return "密码不可为空!"; @@ -188,7 +142,7 @@ class _LoginPageState extends State { ), ), ), - Container( + new Container( height: 1, width: MediaQuery.of(context).size.width * 0.75, color: Colors.grey[400], @@ -200,15 +154,15 @@ class _LoginPageState extends State { } Widget buildSignInButton() { - return GestureDetector( - child: Container( + return new GestureDetector( + child: new Container( padding: EdgeInsets.only(left: 42, right: 42, top: 10, bottom: 10), - decoration: BoxDecoration( + decoration: new BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(5)), color: Theme.of(context).primaryColor), - child: Text( + child: new Text( "LOGIN", - style: TextStyle(fontSize: 25, color: Colors.white), + style: new TextStyle(fontSize: 25, color: Colors.white), ), ), onTap: () { @@ -217,7 +171,7 @@ class _LoginPageState extends State { // 如果输入都检验通过,则进行登录操作 // Scaffold.of(context) // .showSnackBar(new SnackBar(content: new Text("执行登录操作"))); - //调用所有自孩子��save回调,保存表单内容 + //调用所有自孩子的save回调,保存表单内容 doLogin(); } }, @@ -226,56 +180,38 @@ class _LoginPageState extends State { // 登陆操作 doLogin() { - print("doLogin"); _signInFormKey.currentState.save(); setState(() { isLoading = true; }); DataUtils.doLogin({'username': username, 'password': password}) - .then((userResult) { + .then((result) { + print(result); setState(() { isLoading = false; }); - if (userResult.runtimeType == UserInformation) { - try { - _userInfoControlModel.deleteAll().then((result) { - // print('删除结果:$result'); - _userInfoControlModel - .insert(UserInfo(password: password, username: username)) - .then((value) { - print('存储成功:$value'); - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute(builder: (context) => AppPage(userResult)), - (route) => route == null); - }); + try { + _userInfoControlModel.deleteAll().then((result) { + // print('删除结果:$result'); + _userInfoControlModel + .insert(UserInfo(password: password, username: username)) + .then((value) { + // print('存储成功:$value'); + Navigator.of(context).pushAndRemoveUntil( + new MaterialPageRoute(builder: (context) => AppPage()), + (route) => route == null); }); - } catch (err) { - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute(builder: (context) => AppPage(userResult)), - (route) => route == null); - } - }else if(userResult.runtimeType == String){ - Fluttertoast.showToast( - msg: userResult, - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.CENTER, - timeInSecForIos: 1, - backgroundColor: Theme.of(context).primaryColor, - textColor: Colors.white, - fontSize: 16.0); + }); + } catch (err) { + Navigator.of(context).pushAndRemoveUntil( + new MaterialPageRoute(builder: (context) => AppPage()), + (route) => route == null); } - }).catchError((errorMsg) { + }).catchError((onError) { + print(onError); setState(() { isLoading = false; }); - Fluttertoast.showToast( - msg: errorMsg.toString(), - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.CENTER, - timeInSecForIos: 1, - backgroundColor: Theme.of(context).primaryColor, - textColor: Colors.white, - fontSize: 16.0); }); } @@ -331,70 +267,15 @@ class _LoginPageState extends State { mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: 35.0), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Image.asset( - 'assets/images/gitHub.png', - fit: BoxFit.contain, - width: 60.0, - height: 60.0, - ), - Image.asset( - 'assets/images/arrow.png', - fit: BoxFit.contain, - width: 40.0, - height: 30.0, - ), - Image.asset( - 'assets/images/FlutterGo.png', - fit: BoxFit.contain, - width: 60.0, - height: 60.0, - ), - ], + Image.asset( + 'assets/images/FlutterGo.png', + fit: BoxFit.contain, + width: 100.0, + height: 100.0, ), buildSignInTextForm(), buildSignInButton(), - SizedBox(height: 15.0), - new Container( - height: 1, - width: MediaQuery.of(context).size.width * 0.75, - color: Colors.grey[400], - margin: const EdgeInsets.only(bottom: 10.0), - ), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - FlatButton( - child: Text( - 'Github OAuth 认证', - style: TextStyle( - color: Theme.of(context).primaryColor, - decoration: TextDecoration.underline), - ), - onPressed: () { - Application.router.navigateTo(context, - '${Routes.webViewPage}?title=Github&url=${Uri.encodeComponent("https://github.com/login/oauth/authorize?client_id=cfe4795e76382ae8a5bd&scope=user,public_repo")}'); - }, - ), - FlatButton( - child: Text( - '游客登录', - style: TextStyle( - color: Theme.of(context).primaryColor, - decoration: TextDecoration.underline), - ), - onPressed: () { - Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (context) => - AppPage(UserInformation(id: 0))), - (route) => route == null); - }, - ) - ], - ) + SizedBox(height: 35.0), ], ), Positioned( diff --git a/lib/views/standard_demo_page/index.dart b/lib/views/standard_demo_page/index.dart deleted file mode 100644 index 96872367..00000000 --- a/lib/views/standard_demo_page/index.dart +++ /dev/null @@ -1,187 +0,0 @@ -// -// Created with Android Studio. -// User: 三帆 -// Date: 25/05/2019 -// Time: 21:46 -// email: sanfan.hx@alibaba-inc.com -// tartget: xxx -// - -import 'package:flutter/material.dart'; -import '../../components/widget_demo.dart'; -import 'dart:convert'; -import '../../components/markdown.dart' as mdCopy; -import '../../components/flutter_markdown/lib/flutter_markdown.dart'; -import '../../standard_pages/index.dart'; -import '../../page_demo_package/index.dart'; -import 'package:flutter_go/routers/application.dart'; -import 'package:flutter_go/utils/net_utils.dart'; -import 'package:flutter_go/components/loading.dart'; - -const githubUrl = 'https://raw.githubusercontent.com/alibaba/flutter-go/beta/lib/standard_pages/'; -const PagesUrl = 'https://raw.githubusercontent.com/alibaba/flutter-go/beta/lib/standard_pages/.pages.json'; -const DemosUrl = 'https://raw.githubusercontent.com/alibaba/flutter-go/beta/lib/page_demo_package/.demo.json'; - - -// ONLINE || LOCAL -ENV env = Application.env; - -class StandardView extends StatefulWidget { - final String id; - final String detailMd; - StandardView({this.id, this.detailMd}); - @override - _StandardView createState() => _StandardView(); -} - -class _StandardView extends State { - String markdownDesc = ''; - String pageTitle = ''; - bool isLoading = false; - String author = ''; - String email = ''; - StandardPages standardPage = new StandardPages(); - @override - void initState() { - super.initState(); - this.getPageInfo(); - } - didChangeDependencies() { - print("didChangeDependencies"); - } - /// 本地调用的获取文章属性的基本信息 - Future localGetPagesAttrsInfo() async { - String jsonString = await DefaultAssetBundle.of(context).loadString('lib/standard_pages/.pages.json'); - List jsonList = json.decode(jsonString); - Map pageDetail = jsonList.firstWhere((item) => item['id'] == widget.id); - - if (pageDetail != null) { - setState(() { - pageTitle = pageDetail['title'] ?? '请加入title'; - author = pageDetail['author']; - email = pageDetail['email']; - }); - } - } - - /// 从本地获取基本文章信息 - String localGetPagesMarkdown() { - - String pageId = widget.id; - Map pagesList = standardPage.getPages(); - print('pagesList[pageId]>>> ${pagesList[pageId]}'); - return pagesList[pageId]; - } - Future getContentOnline() async { - - String content = 'online content'; - this.setState(() { - isLoading = true; - }); - - List response = jsonDecode(await NetUtils.get(PagesUrl)); - - - - Map targetPage = response.firstWhere((page) => page['id'] == widget.id); - if (targetPage == null) { - setState(() { - isLoading = false; - }); - return Future(() => '未获取界面相当信息'); - } - setState(() { - pageTitle = targetPage['title'] ?? 'xxx'; - author = targetPage['author']; - email = targetPage['email']; - }); - - String pageName = targetPage['name'] + "_" +targetPage['author']+ "_" +targetPage['id']; - String pageContent = await NetUtils.get(githubUrl + pageName + "/index.md"); - setState(() { - isLoading = false; - }); - return Future(() => pageContent); - } - /// 获取当面界面的相关信息. 需要区分环境 - /// 本地环境下, 从本地获取 standard_pages的目录中互殴 - /// 线上环境. 从github的api中获取 - Future getPageInfo() async { - String conent = ''; - print("env:::: $env"); - - if (env == ENV.PRODUCTION) { - conent = await getContentOnline(); - } else { - conent = localGetPagesMarkdown(); - localGetPagesAttrsInfo(); - } - if (this.mounted) { - setState(() { - markdownDesc = conent; - }); - } - return Future(() => conent); - } - Widget buildFootInfo() { - if (!isLoading) { - return Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('创建者: $author'), - Text('邮箱: $email'), - Text('界面id: ${widget.id}') - ], - ), - ); - } - return Container(); - } - - Widget buildMarkdown() { - - - if (markdownDesc == null) { - return null; - } else { - if (Application.env == ENV.DEV) { - // 为了能在local改变的时候. 动态更新内容, getPageInfo只有初始化状态下会有效果 - markdownDesc = localGetPagesMarkdown(); - } - } - - return MarkdownBody( - data: markdownDesc, - syntaxHighlighter:new mdCopy.HighLight(), - demoBuilder: (Map attrs) { - List demo = demoObjects[attrs['id']]; - if (demo == null) { - String errString = "not found ${attrs['id']} in demo packages"; - debugPrint(errString); - demo = [Text(errString)]; - } - - return Column(children: demo); - }); - } - - - @override - Widget build(BuildContext context) { - return new WidgetDemo( - title: pageTitle, -// codeUrl: 'elements/Form/Button/DropdownButton/demo.dart', - contentList: [ - NetLoadingDialog( - loading: isLoading, - outsideDismiss: false, - ), - buildMarkdown(), - SizedBox(height: 30), - buildFootInfo(), - SizedBox(height: 30) - ], - ); - } -} diff --git a/lib/views/user_page/user_page.dart b/lib/views/user_page/user_page.dart deleted file mode 100644 index 74924c58..00000000 --- a/lib/views/user_page/user_page.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_go/model/user_info.dart'; -import 'package:flutter_go/views/first_page/drawer_page.dart'; - -class UserPage extends StatefulWidget { - final UserInformation userInfo; - - UserPage({Key key, this.userInfo}) : super(key: key); - - @override - _UserPageState createState() => _UserPageState(); -} - -class _UserPageState extends State { - @override - Widget build(BuildContext context) { - print(widget.userInfo); - return Scaffold( - body: Container( - child: DrawerPage( - userInfo: widget.userInfo, - ), - ), - ); - } -} diff --git a/lib/views/web_page/web_view_page.dart b/lib/views/web_page/web_view_page.dart index 196ddb35..df3793a5 100644 --- a/lib/views/web_page/web_view_page.dart +++ b/lib/views/web_page/web_view_page.dart @@ -1,18 +1,16 @@ -/// @Author: 一凨 -/// @Date: 2019-01-14 17:44:47 +/// @Author: 一凨 +/// @Date: 2019-01-14 17:44:47 /// @Last Modified by: 一凨 /// @Last Modified time: 2019-01-14 19:47:14 import 'dart:core'; -import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_webview_plugin/flutter_webview_plugin.dart'; import 'package:flutter_go/model/collection.dart'; import 'package:flutter_go/event/event_bus.dart'; import 'package:flutter_go/event/event_model.dart'; -import 'package:flutter_go/api/api.dart'; -import 'package:flutter_go/routers/application.dart' show Application; class WebViewPage extends StatefulWidget { final String url; @@ -22,121 +20,103 @@ class WebViewPage extends StatefulWidget { _WebViewPageState createState() => _WebViewPageState(); } -class _WebViewPageState extends State with AutomaticKeepAliveClientMixin{ - @override - bool get wantKeepAlive => true; - - final flutterWebviewPlugin = new FlutterWebviewPlugin(); +class _WebViewPageState extends State { + bool _hasCollected = false; + String _router = ''; + var _collectionIcons; + CollectionControlModel _collectionControl = new CollectionControlModel(); final GlobalKey _scaffoldKey = GlobalKey(); - @override void initState() { super.initState(); - dialog = dialogContext(false); - - flutterWebviewPlugin.onUrlChanged.listen((String url) { - - print('url change:$url'); - if (url.indexOf('loginSuccess') > -1) { - String urlQuery = url.substring(url.indexOf('?') + 1); - String loginName, token; - List queryList = urlQuery.split('&'); - for (int i = 0; i < queryList.length; i++) { - String queryNote = queryList[i]; - int eqIndex = queryNote.indexOf('='); - if (queryNote.substring(0, eqIndex) == 'loginName') { - loginName = queryNote.substring(eqIndex + 1); - } - if (queryNote.substring(0, eqIndex) == 'accessToken') { - token = queryNote.substring(eqIndex + 1); - } + _collectionControl + .getRouterByName(Uri.encodeComponent(widget.title.trim())) + .then((list) { + list.forEach((item) { + if (widget.title.trim() == item['name']) { + _router = item['router']; } - if (ApplicationEvent.event != null) { - ApplicationEvent.event - .fire(UserGithubOAuthEvent(loginName, token, true)); - } - print('ready close'); - - flutterWebviewPlugin.close(); - // 验证成功 - } else if (url.indexOf('${Api.BASE_URL}loginFail') == 0) { - // 验证失败 - if (ApplicationEvent.event != null) { - ApplicationEvent.event.fire(UserGithubOAuthEvent('', '', true)); - } - flutterWebviewPlugin.close(); - } - }); - flutterWebviewPlugin.onStateChanged.listen((state) async { - print('url state:$state'); - if(state.type == WebViewState.finishLoad) { + }); + if (mounted) { + setState(() { + _hasCollected = list.length > 0; + }); } }); } - Widget dialogContext(bool isShow){ - if(!isShow){ - return Container(child:Text("")); + // 点击收藏按钮 + _getCollection() { + if (_hasCollected) { + // 删除操作 + _collectionControl + .deleteByName(Uri.encodeComponent(widget.title.trim())) + .then((result) { + if (result > 0 && this.mounted) { + setState(() { + _hasCollected = false; + }); + _scaffoldKey.currentState + .showSnackBar(SnackBar(content: Text('已取消收藏'))); + if (ApplicationEvent.event != null) { + ApplicationEvent.event + .fire(CollectionEvent(widget.title, _router, true)); + } + return; + } + print('删除错误'); + }); + } else { + // 插入操作 + _collectionControl + .insert(Collection( + name: Uri.encodeComponent(widget.title.trim()), + router: widget.url)) + .then((result) { + if (this.mounted) { + setState(() { + _hasCollected = true; + }); + + if (ApplicationEvent.event != null) { + ApplicationEvent.event + .fire(CollectionEvent(widget.title, _router, false)); + } + _scaffoldKey.currentState + .showSnackBar(SnackBar(content: Text('收藏成功'))); + } + }); } - return Container( - height: 200, - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Text('分享到',style: TextStyle(fontSize:16,color: Colors.deepOrange)), - FlatButton( - child: Text('取消',style: TextStyle(fontSize:16,color: Colors.black45),), - onPressed: (){ - setState(() { - dialog = dialogContext(false); - - }); - } - ) - ] - ) - ]) - ); } - Container dialog; @override Widget build(BuildContext context) { + if (_hasCollected) { + _collectionIcons = Icons.favorite; + } else { + _collectionIcons = Icons.favorite_border; + } return Scaffold( - key: _scaffoldKey, - appBar: AppBar( - title: Text(widget.title), -// actions: [ -// IconButton( -// icon: Icon(Icons.announcement), -// onPressed: () { -// /// flutterWebviewPlugin.evalJavascript("alert('Flutter Go H5 版本')"); -// setState(() { -// dialog = dialogContext(true); -// }); -// }, -// ) -// ], - ), - body: WebviewScaffold( - url: widget.url, - withZoom: true, - withLocalStorage: true, - withJavascript: true, - userAgent: "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36", - bottomNavigationBar:dialog, - initialChild: Container( - color: Colors.white, - child: const Center( - child: Text("Loading...."), + key: _scaffoldKey, + appBar: AppBar( + title: Text(widget.title), + actions: [ + new IconButton( + tooltip: 'goBack home', + onPressed: _getCollection, + icon: Icon( + _collectionIcons, ), ), - )); - + ], + ), + body: WebviewScaffold( + url: widget.url, + withZoom: false, + withLocalStorage: true, + withJavascript: true, + ), + ); } } diff --git a/lib/views/widget_page/widget_page.dart b/lib/views/widget_page/widget_page.dart index 5df042cb..dc6b0014 100644 --- a/lib/views/widget_page/widget_page.dart +++ b/lib/views/widget_page/widget_page.dart @@ -6,28 +6,28 @@ import 'package:flutter/material.dart'; import 'package:flutter_go/components/cate_card.dart'; - -import 'package:flutter_go/routers/application.dart'; - +import 'package:flutter_go/model/cat.dart'; class WidgetPage extends StatefulWidget { - - - + final db; + final CatControlModel catModel; + WidgetPage(this.db) + : catModel = new CatControlModel(), + super(); @override - SecondPageState createState() => new SecondPageState(); + SecondPageState createState() => new SecondPageState(catModel); } class SecondPageState extends State with AutomaticKeepAliveClientMixin{ - - SecondPageState() : super(); + CatControlModel catModel; + SecondPageState(this.catModel) : super(); TextEditingController controller; String active = 'test'; String data = '无'; - + List categories = []; @override bool get wantKeepAlive => true; @@ -35,16 +35,25 @@ class SecondPageState extends State with AutomaticKeepAliveClientMix @override void initState() { super.initState(); + renderCats(); } - + void renderCats() { + catModel.getList().then((List data) { + if (data.isNotEmpty) { + setState(() { + categories = data; + }); + } + }); + } Widget buildGrid() { // 存放最后的widget List tiles = []; - Application.widgetTree.children.forEach((dynamic item) { + for (Cat item in categories) { tiles.add(new CateCard(category: item)); - }); + } return new ListView( children: tiles, ); @@ -53,6 +62,11 @@ class SecondPageState extends State with AutomaticKeepAliveClientMix @override Widget build(BuildContext context) { super.build(context); + if (categories.length == 0) { + return ListView( + children: [new Container()], + ); + } return Container( color: Theme.of(context).backgroundColor, child: this.buildGrid(), diff --git a/lib/widgets/components/Menu/CheckedPopupMenuItem/demo.dart b/lib/widgets/components/Menu/CheckedPopupMenuItem/demo.dart index 6cd604d7..967381f5 100644 --- a/lib/widgets/components/Menu/CheckedPopupMenuItem/demo.dart +++ b/lib/widgets/components/Menu/CheckedPopupMenuItem/demo.dart @@ -15,7 +15,7 @@ class _CheckedPopupMenuItemDemoState extends State { final String _checkedValue1 = 'One'; final String _checkedValue2 = 'Two'; - final String _checkedValue3 = 'Three'; + final String _checkedValue3 = 'Free'; final String _checkedValue4 = 'Four'; @override diff --git a/lib/widgets/components/Menu/DropdownMenuItem/demo.dart b/lib/widgets/components/Menu/DropdownMenuItem/demo.dart index effe3dd8..9c3e423f 100644 --- a/lib/widgets/components/Menu/DropdownMenuItem/demo.dart +++ b/lib/widgets/components/Menu/DropdownMenuItem/demo.dart @@ -11,7 +11,7 @@ class DropdownMenuItemDemo extends StatefulWidget { class _DropdownMenuItemDemoState extends State { - String dropdown1Value = 'Three'; + String dropdown1Value = 'Free'; String dropdown2Value; String dropdown3Value = 'Four'; @@ -32,7 +32,7 @@ class _DropdownMenuItemDemoState extends State { dropdown1Value = newValue; }); }, - items: ['One', 'Two', 'Three', 'Four'].map>((String value) { + items: ['One', 'Two', 'Free', 'Four'].map>((String value) { return DropdownMenuItem( value: value, child: Text(value), @@ -53,7 +53,7 @@ class _DropdownMenuItemDemoState extends State { dropdown2Value = newValue; }); }, - items: ['One', 'Two', 'Three', 'Four'].map>((String value) { + items: ['One', 'Two', 'Free', 'Four'].map>((String value) { return DropdownMenuItem( value: value, child: Text(value), @@ -74,7 +74,7 @@ class _DropdownMenuItemDemoState extends State { }); }, items: [ - 'One', 'Two', 'Three', 'Four', 'Can', 'I', 'Have', 'A', 'Little', + 'One', 'Two', 'Free', 'Four', 'Can', 'I', 'Have', 'A', 'Little', 'Bit', 'More', 'Five', 'Six', 'Seven', 'Eight', 'Nine', 'Ten' ] .map>((String value) { diff --git a/lib/widgets/elements/Form/Radio/Radio/index.dart b/lib/widgets/elements/Form/Radio/Radio/index.dart index ba347f19..8ec9dd9f 100644 --- a/lib/widgets/elements/Form/Radio/Radio/index.dart +++ b/lib/widgets/elements/Form/Radio/Radio/index.dart @@ -6,6 +6,7 @@ /// target: Radio相关 import 'package:flutter/material.dart'; + import 'package:flutter_go/components/widget_demo.dart'; import 'demo.dart'; @@ -25,7 +26,7 @@ Radio widget 代表表单中的单选按钮, 当groupValue = value时代表组 - value → T - 单选的值。 """; class Demo extends StatefulWidget { - static const String routeName = '/element/Form/Radio/Radio'; + static const String routeName = '/element/Form/Radio/index'; _DemoState createState() => _DemoState(); } @@ -39,7 +40,7 @@ class _DemoState extends State { ], title: 'Radio', docUrl: 'https://docs.flutter.io/flutter/material/Radio-class.html', - codeUrl: 'elements/Form/Radio/Radio/demo.dart', + codeUrl: 'elements/Form/Radio/Radio/index.dart', ); } } diff --git a/lib/widgets/elements/Form/Slider/Slider/index.dart b/lib/widgets/elements/Form/Slider/Slider/index.dart index 515ae27e..9862e916 100644 --- a/lib/widgets/elements/Form/Slider/Slider/index.dart +++ b/lib/widgets/elements/Form/Slider/Slider/index.dart @@ -70,7 +70,7 @@ const contentB = ''' '''; class Demo extends StatefulWidget { - static const String routeName = 'element/form/Slider/Slider'; + static const String routeName = 'elements/Form/Slider/Slider'; _Demo createState() => _Demo(); } diff --git a/lib/widgets/elements/Form/Switch/AnimatedSwitcher/index.dart b/lib/widgets/elements/Form/Switch/AnimatedSwitcher/index.dart index 85c42be1..1f90412a 100644 --- a/lib/widgets/elements/Form/Switch/AnimatedSwitcher/index.dart +++ b/lib/widgets/elements/Form/Switch/AnimatedSwitcher/index.dart @@ -34,7 +34,7 @@ class Demo extends StatefulWidget { class _Demo extends State { Widget build(BuildContext context) { return WidgetDemo( - title: 'AnimatedSwitcher', + title: 'SwitchListTile', codeUrl: 'elements/Form/Switch/AnimatedSwitcher/demo.dart', contentList: [contentA, AnimatedSwitcherDemo()], docUrl: '', diff --git a/lib/widgets/elements/Form/Text/RichText/index.dart b/lib/widgets/elements/Form/Text/RichText/index.dart index 0e2e8451..b5bb5be8 100644 --- a/lib/widgets/elements/Form/Text/RichText/index.dart +++ b/lib/widgets/elements/Form/Text/RichText/index.dart @@ -30,7 +30,7 @@ class _Demo extends State { return WidgetDemo( title: 'Rich Text', docUrl: 'https://docs.flutter.io/flutter/widgets/RichText-class.html', - codeUrl: 'elements/Form/Text/RichText/demo.dart', + codeUrl: 'elements/Form/Text/RichText/index.dart', contentList: [ intro, RichTextDemo(), diff --git a/lib/widgets/themes/Material/MaterialColor/index.dart b/lib/widgets/themes/Material/MaterialColor/index.dart index 79117f93..d4467e98 100644 --- a/lib/widgets/themes/Material/MaterialColor/index.dart +++ b/lib/widgets/themes/Material/MaterialColor/index.dart @@ -21,7 +21,7 @@ const String content1 = ''' '''; class Demo extends StatefulWidget { - static const String routeName = '/Themes/Material/MaterialColor'; + static const String routeName = '/themes/Material/MaterialColor'; _DemoState createState() => _DemoState(); } diff --git a/pubspec.lock b/pubspec.lock index 4bc7b1b4..e6e3e04f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,111 +1,111 @@ # Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile +# See https://www.dartlang.org/tools/pub/glossary#lockfile packages: args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.5.2" + version: "1.5.1" async: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" bloc: dependency: "direct main" description: name: bloc - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.4" charcode: dependency: transitive description: name: charcode - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" city_pickers: dependency: "direct main" description: name: city_pickers - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.0.4" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.14.11" cookie_jar: dependency: "direct main" description: name: cookie_jar - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.1" + version: "1.0.0" csslib: dependency: transitive description: name: csslib - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.16.1" + version: "0.16.0" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.1.2" dio: dependency: "direct main" description: name: dio - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.13" + version: "2.1.3" event_bus: dependency: "direct main" description: name: event_bus - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" firebase_analytics: dependency: "direct main" description: name: firebase_analytics - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0+1" firebase_core: dependency: "direct main" description: name: firebase_core - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.3.4" fluro: dependency: "direct main" description: name: fluro - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.5.1" + version: "1.4.0" flutter: dependency: "direct main" description: flutter @@ -115,28 +115,21 @@ packages: dependency: "direct main" description: name: flutter_bloc - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.11.1" - flutter_downloader: + flutter_markdown: dependency: "direct main" description: - name: flutter_downloader - url: "https://pub.dartlang.org" + name: flutter_markdown + url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.9" - flutter_jpush: - dependency: "direct main" - description: - name: flutter_jpush - url: "https://pub.dartlang.org" - source: hosted - version: "0.0.4" + version: "0.2.0" flutter_spinkit: dependency: "direct main" description: name: flutter_spinkit - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" flutter_test: @@ -148,154 +141,98 @@ packages: dependency: "direct main" description: name: flutter_webview_plugin - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.3.5" - fluttertoast: - dependency: "direct main" - description: - name: fluttertoast - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" + version: "0.3.4" html: dependency: "direct main" description: name: html - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.14.0+2" image_picker: dependency: "direct main" description: name: image_picker - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.5.4+3" + version: "0.6.0+2" intl: dependency: "direct main" description: name: intl - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.15.7" lpinyin: dependency: transitive description: name: lpinyin - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.7" markdown: - dependency: "direct main" + dependency: transitive description: name: markdown - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.12.5" meta: - dependency: "direct main" - description: - name: meta - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.6" - notus: dependency: transitive description: - name: notus - url: "https://pub.dartlang.org" + name: meta + url: "https://pub.flutter-io.cn" source: hosted - version: "0.1.3" - open_file: - dependency: "direct main" - description: - name: open_file - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.3" - package_info: - dependency: "direct main" - description: - name: package_info - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.0+6" + version: "1.1.6" path: - dependency: "direct main" + dependency: transitive description: name: path - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.6.2" path_provider: dependency: "direct main" description: name: path_provider - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.2.0" + version: "1.0.0" pedantic: dependency: transitive description: name: pedantic - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.7.0" - permission_handler: - dependency: "direct main" - description: - name: permission_handler - url: "https://pub.dartlang.org" - source: hosted - version: "3.2.1+1" - quill_delta: - dependency: transitive - description: - name: quill_delta - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" + version: "1.5.0" quiver: dependency: transitive description: name: quiver - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.3" - quiver_hashcode: - dependency: transitive - description: - name: quiver_hashcode - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.0" rxdart: dependency: transitive description: name: rxdart - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.21.0" - share: - dependency: "direct main" - description: - name: share - url: "https://pub.dartlang.org" - source: hosted - version: "0.6.2+1" shared_preferences: dependency: "direct main" description: name: shared_preferences - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.3" sky_engine: @@ -307,86 +244,79 @@ packages: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.5.5" sqflite: dependency: "direct main" description: name: sqflite - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.6+3" + version: "1.1.5" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.3" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" string_scanner: - dependency: "direct main" + dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.4" synchronized: dependency: transitive description: name: synchronized - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0+1" + version: "2.1.0" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "0.2.5" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.6" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted - version: "5.1.2" + version: "5.0.2" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.8" - zefyr: - dependency: "direct main" - description: - path: zefyr - relative: true - source: path - version: "0.0.1" sdks: - dart: ">=2.2.2 <3.0.0" - flutter: ">=1.6.0 <2.0.0" + dart: ">=2.2.0 <3.0.0" + flutter: ">=1.2.1 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6401c44a..d85a81e9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,10 +7,10 @@ description: flutter_go # Both the version and the builder number may be overridden in flutter # build by specifying --build-name and --build-number, respectively. # Read more about versioning at semver.org. -version: 1.0.6 +version: 1.0.0 environment: - sdk: ">=2.1.0 <3.0.0" + sdk: ">=2.0.0-dev.68.0 <3.0.0" dependencies: flutter: @@ -21,16 +21,16 @@ dependencies: cupertino_icons: ^0.1.2 event_bus: ^1.0.1 fluro: ^1.3.4 - image_picker: ^0.5.0 + image_picker: ^0.6.0+1 sqflite: ^1.1.5 + flutter_markdown: ^0.2.0 url_launcher: ^5.0.2 # 本地存储、收藏功能 shared_preferences: ^0.4.3 - share: ^0.6.1+1 flutter_spinkit: "^3.1.0" - fluttertoast: ^3.1.0 + path_provider: ^1.0.0 dio: ^2.0.15 - flutter_webview_plugin: ^0.3.5 + flutter_webview_plugin: ^0.3.4 cookie_jar: ^1.0.0 # 日期格式化 intl: 0.15.7 @@ -41,19 +41,6 @@ dependencies: flutter_bloc: ^0.11.1 bloc: ^0.12.0 html: ^0.14.0+2 - markdown: ^2.0.0 - meta: ^1.0.5 - string_scanner: ^1.0.0 - path: ^1.5.1 - flutter_downloader: ^1.1.7 - path_provider: ^1.1.0 - permission_handler: ^3.0.0 - open_file: ^2.0.1+2 - package_info: ^0.4.0+3 - flutter_jpush: ^0.0.4 - zefyr: - path: ./zefyr - dev_dependencies: flutter_test: @@ -61,6 +48,7 @@ dev_dependencies: # For information on the generic Dart part of this file, see the # following page: https://www.dartlang.org/tools/pub/pubspec + # The following section is specific to Flutter. flutter: @@ -211,8 +199,6 @@ flutter: - lib/widgets/themes/Cupertino/CupertinoTabScaffold/demo.dart - lib/widgets/themes/Cupertino/CupertinoTabView/demo.dart - lib/widgets/themes/Cupertino/CupertinoTimerPicker/demo.dart - - lib/page_demo_package/.demo.json - - lib/standard_pages/.pages.json - assets/app.db - assets/images/ - assets/fonts/ diff --git a/zefyr/.gitignore b/zefyr/.gitignore deleted file mode 100644 index 9f87252c..00000000 --- a/zefyr/.gitignore +++ /dev/null @@ -1,72 +0,0 @@ -# Miscellaneous -*.class -*.log -*.pyc -*.swp -.DS_Store -.atom/ -.buildlog/ -.history -.svn/ - -# IntelliJ related -*.iml -*.ipr -*.iws -.idea/ - -# The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line -# is commented out by default. -#.vscode/ - -# Flutter/Dart/Pub related -**/doc/api/ -.dart_tool/ -.flutter-plugins -.packages -.pub-cache/ -.pub/ -build/ - -# Android related -**/android/**/gradle-wrapper.jar -**/android/.gradle -**/android/captures/ -**/android/gradlew -**/android/gradlew.bat -**/android/local.properties -**/android/**/GeneratedPluginRegistrant.java - -# iOS/XCode related -**/ios/**/*.mode1v3 -**/ios/**/*.mode2v3 -**/ios/**/*.moved-aside -**/ios/**/*.pbxuser -**/ios/**/*.perspectivev3 -**/ios/**/*sync/ -**/ios/**/.sconsign.dblite -**/ios/**/.tags* -**/ios/**/.vagrant/ -**/ios/**/DerivedData/ -**/ios/**/Icon? -**/ios/**/Pods/ -**/ios/**/.symlinks/ -**/ios/**/profile -**/ios/**/xcuserdata -**/ios/.generated/ -**/ios/Flutter/App.framework -**/ios/Flutter/Flutter.framework -**/ios/Flutter/Generated.xcconfig -**/ios/Flutter/app.flx -**/ios/Flutter/app.zip -**/ios/Flutter/flutter_assets/ -**/ios/ServiceDefinitions.json -**/ios/Runner/GeneratedPluginRegistrant.* - -# Exceptions to above rules. -!**/ios/**/default.mode1v3 -!**/ios/**/default.mode2v3 -!**/ios/**/default.pbxuser -!**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/zefyr/CHANGELOG.md b/zefyr/CHANGELOG.md deleted file mode 100644 index ac071598..00000000 --- a/zefyr/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## [0.0.1] - TODO: Add release date. - -* TODO: Describe initial release. diff --git a/zefyr/LICENSE b/zefyr/LICENSE deleted file mode 100644 index ba75c69f..00000000 --- a/zefyr/LICENSE +++ /dev/null @@ -1 +0,0 @@ -TODO: Add your license here. diff --git a/zefyr/README.md b/zefyr/README.md deleted file mode 100644 index cc0c4567..00000000 --- a/zefyr/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# zefyr - -A new Flutter package project. - -## Getting Started - -This project is a starting point for a Dart -[package](https://flutter.dev/developing-packages/), -a library module containing code that can be shared easily across -multiple Flutter or Dart projects. - -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. diff --git a/zefyr/lib/src/fast_diff.dart b/zefyr/lib/src/fast_diff.dart deleted file mode 100644 index ade850b6..00000000 --- a/zefyr/lib/src/fast_diff.dart +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'dart:math' as math; - -/// Performs a fast diff operation on two input strings based on provided -/// [cursorPosition]. -DiffResult fastDiff(String oldText, String newText, int cursorPosition) { - var delta = newText.length - oldText.length; - var limit = math.max(0, cursorPosition - delta); - var end = oldText.length; - while (end > limit && oldText[end - 1] == newText[end + delta - 1]) { - end -= 1; - } - var start = 0; - var startLimit = cursorPosition - math.max(0, delta); - while (start < startLimit && oldText[start] == newText[start]) { - start += 1; - } - final String deleted = (start < end) ? oldText.substring(start, end) : ''; - final inserted = newText.substring(start, end + delta); - return new DiffResult(start, deleted, inserted); -} - -/// A diff between two strings of text. -class DiffResult { - /// Start index in old text at which changes begin. - final int start; - - /// Deleted text in old text. - final String deleted; - - /// Inserted text. - final String inserted; - - DiffResult(this.start, this.deleted, this.inserted); - - @override - String toString() => 'DiffResult[$start, "$deleted", "$inserted"]'; -} diff --git a/zefyr/lib/src/widgets/buttons.dart b/zefyr/lib/src/widgets/buttons.dart deleted file mode 100644 index 675f4ea3..00000000 --- a/zefyr/lib/src/widgets/buttons.dart +++ /dev/null @@ -1,583 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:notus/notus.dart'; -import 'package:url_launcher/url_launcher.dart'; - -import 'scope.dart'; -import 'theme.dart'; -import 'toolbar.dart'; - -/// A button used in [ZefyrToolbar]. -/// -/// Create an instance of this widget with [ZefyrButton.icon] or -/// [ZefyrButton.text] constructors. -/// -/// Toolbar buttons are normally created by a [ZefyrToolbarDelegate]. -class ZefyrButton extends StatelessWidget { - /// Creates a toolbar button with an icon. - ZefyrButton.icon({ - @required this.action, - @required IconData icon, - double iconSize, - this.onPressed, - }) : assert(action != null), - assert(icon != null), - _icon = icon, - _iconSize = iconSize, - _text = null, - _textStyle = null, - super(); - - /// Creates a toolbar button containing text. - /// - /// Note that [ZefyrButton] has fixed width and does not expand to accommodate - /// long texts. - ZefyrButton.text({ - @required this.action, - @required String text, - TextStyle style, - this.onPressed, - }) : assert(action != null), - assert(text != null), - _icon = null, - _iconSize = null, - _text = text, - _textStyle = style, - super(); - - /// Toolbar action associated with this button. - final ZefyrToolbarAction action; - final IconData _icon; - final double _iconSize; - final String _text; - final TextStyle _textStyle; - - /// Callback to trigger when this button is tapped. - final VoidCallback onPressed; - - bool get isAttributeAction { - return kZefyrToolbarAttributeActions.keys.contains(action); - } - - @override - Widget build(BuildContext context) { - final toolbar = ZefyrToolbar.of(context); - final editor = toolbar.editor; - final toolbarTheme = ZefyrTheme.of(context).toolbarTheme; - final pressedHandler = _getPressedHandler(editor, toolbar); - final iconColor = (pressedHandler == null) - ? toolbarTheme.disabledIconColor - : toolbarTheme.iconColor; - if (_icon != null) { - return RawZefyrButton.icon( - action: action, - icon: _icon, - size: _iconSize, - iconColor: iconColor, - color: _getColor(editor, toolbarTheme), - onPressed: _getPressedHandler(editor, toolbar), - ); - } else { - assert(_text != null); - var style = _textStyle ?? new TextStyle(); - style = style.copyWith(color: iconColor); - return RawZefyrButton( - action: action, - child: new Text(_text, style: style), - color: _getColor(editor, toolbarTheme), - onPressed: _getPressedHandler(editor, toolbar), - ); - } - } - - Color _getColor(ZefyrScope editor, ZefyrToolbarTheme theme) { - if (isAttributeAction) { - final attribute = kZefyrToolbarAttributeActions[action]; - final isToggled = (attribute is NotusAttribute) - ? editor.selectionStyle.containsSame(attribute) - : editor.selectionStyle.contains(attribute); - return isToggled ? theme.toggleColor : null; - } - return null; - } - - VoidCallback _getPressedHandler( - ZefyrScope editor, ZefyrToolbarState toolbar) { - if (onPressed != null) { - return onPressed; - } else if (isAttributeAction) { - final attribute = kZefyrToolbarAttributeActions[action]; - if (attribute is NotusAttribute) { - return () => _toggleAttribute(attribute, editor); - } - } else if (action == ZefyrToolbarAction.close) { - return () => toolbar.closeOverlay(); - } else if (action == ZefyrToolbarAction.hideKeyboard) { - return () => editor.hideKeyboard(); - } - - return null; - } - - void _toggleAttribute(NotusAttribute attribute, ZefyrScope editor) { - final isToggled = editor.selectionStyle.containsSame(attribute); - if (isToggled) { - editor.formatSelection(attribute.unset); - } else { - editor.formatSelection(attribute); - } - } -} - -/// Raw button widget used by [ZefyrToolbar]. -/// -/// See also: -/// -/// * [ZefyrButton], which wraps this widget and implements most of the -/// action-specific logic. -class RawZefyrButton extends StatelessWidget { - const RawZefyrButton({ - Key key, - @required this.action, - @required this.child, - @required this.color, - @required this.onPressed, - }) : super(key: key); - - /// Creates a [RawZefyrButton] containing an icon. - RawZefyrButton.icon({ - @required this.action, - @required IconData icon, - double size, - Color iconColor, - @required this.color, - @required this.onPressed, - }) : child = new Icon(icon, size: size, color: iconColor), - super(); - - /// Toolbar action associated with this button. - final ZefyrToolbarAction action; - - /// Child widget to show inside this button. Usually an icon. - final Widget child; - - /// Background color of this button. - final Color color; - - /// Callback to trigger when this button is pressed. - final VoidCallback onPressed; - - /// Returns `true` if this button is currently toggled on. - bool get isToggled => color != null; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final width = theme.buttonTheme.constraints.minHeight + 4.0; - final constraints = theme.buttonTheme.constraints.copyWith( - minWidth: width, maxHeight: theme.buttonTheme.constraints.minHeight); - final radius = BorderRadius.all(Radius.circular(3.0)); - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 1.0, vertical: 6.0), - child: RawMaterialButton( - shape: RoundedRectangleBorder(borderRadius: radius), - elevation: 0.0, - fillColor: color, - constraints: constraints, - onPressed: onPressed, - child: child, - ), - ); - } -} - -/// Controls heading styles. -/// -/// When pressed, this button displays overlay toolbar with three -/// buttons for each heading level. -class HeadingButton extends StatefulWidget { - const HeadingButton({Key key}) : super(key: key); - - @override - _HeadingButtonState createState() => _HeadingButtonState(); -} - -class _HeadingButtonState extends State { - @override - Widget build(BuildContext context) { - final toolbar = ZefyrToolbar.of(context); - return toolbar.buildButton( - context, - ZefyrToolbarAction.heading, - onPressed: showOverlay, - ); - } - - void showOverlay() { - final toolbar = ZefyrToolbar.of(context); - toolbar.showOverlay(buildOverlay); - } - - Widget buildOverlay(BuildContext context) { - final toolbar = ZefyrToolbar.of(context); - final buttons = Row( - children: [ - SizedBox(width: 8.0), - toolbar.buildButton(context, ZefyrToolbarAction.headingLevel1), - toolbar.buildButton(context, ZefyrToolbarAction.headingLevel2), - toolbar.buildButton(context, ZefyrToolbarAction.headingLevel3), - ], - ); - return ZefyrToolbarScaffold(body: buttons); - } -} - -/// Controls image attribute. -/// -/// When pressed, this button displays overlay toolbar with three -/// buttons for each heading level. -class ImageButton extends StatefulWidget { - const ImageButton({Key key}) : super(key: key); - - @override - _ImageButtonState createState() => _ImageButtonState(); -} - -class _ImageButtonState extends State { - @override - Widget build(BuildContext context) { - final toolbar = ZefyrToolbar.of(context); - return toolbar.buildButton( - context, - ZefyrToolbarAction.image, - onPressed: showOverlay, - ); - } - - void showOverlay() { - final toolbar = ZefyrToolbar.of(context); - toolbar.showOverlay(buildOverlay); - } - - Widget buildOverlay(BuildContext context) { - final toolbar = ZefyrToolbar.of(context); - final buttons = Row( - children: [ - SizedBox(width: 8.0), - toolbar.buildButton(context, ZefyrToolbarAction.cameraImage, - onPressed: _pickFromCamera), - toolbar.buildButton(context, ZefyrToolbarAction.galleryImage, - onPressed: _pickFromGallery), - ], - ); - return ZefyrToolbarScaffold(body: buttons); - } - - void _pickFromCamera() async { - final editor = ZefyrToolbar.of(context).editor; - final image = await editor.imageDelegate.pickImage(ImageSource.camera); - if (image != null) - editor.formatSelection(NotusAttribute.embed.image(image)); - } - - void _pickFromGallery() async { - final editor = ZefyrToolbar.of(context).editor; - final image = await editor.imageDelegate.pickImage(ImageSource.gallery); - if (image != null) - editor.formatSelection(NotusAttribute.embed.image(image)); - } -} - -class LinkButton extends StatefulWidget { - const LinkButton({Key key}) : super(key: key); - - @override - _LinkButtonState createState() => _LinkButtonState(); -} - -class _LinkButtonState extends State { - final TextEditingController _inputController = TextEditingController(); - Key _inputKey; - bool _formatError = false; - ZefyrScope _editor; - - bool get isEditing => _inputKey != null; - - @override - Widget build(BuildContext context) { - final toolbar = ZefyrToolbar.of(context); - final editor = toolbar.editor; - final enabled = - hasLink(editor.selectionStyle) || !editor.selection.isCollapsed; - - return toolbar.buildButton( - context, - ZefyrToolbarAction.link, - onPressed: enabled ? showOverlay : null, - ); - } - - bool hasLink(NotusStyle style) => style.contains(NotusAttribute.link); - - String getLink([String defaultValue]) { - final editor = ZefyrToolbar.of(context).editor; - final attrs = editor.selectionStyle; - if (hasLink(attrs)) { - return attrs.value(NotusAttribute.link); - } - return defaultValue; - } - - void showOverlay() { - final toolbar = ZefyrToolbar.of(context); - toolbar.showOverlay(buildOverlay).whenComplete(cancelEdit); - } - - void closeOverlay() { - final toolbar = ZefyrToolbar.of(context); - toolbar.closeOverlay(); - } - - void edit() { - final toolbar = ZefyrToolbar.of(context); - setState(() { - _inputKey = new UniqueKey(); - _inputController.text = getLink('https://'); - _inputController.addListener(_handleInputChange); - toolbar.markNeedsRebuild(); - }); - } - - void doneEdit() { - final toolbar = ZefyrToolbar.of(context); - setState(() { - var error = false; - if (_inputController.text.isNotEmpty) { - try { - var uri = Uri.parse(_inputController.text); - if ((uri.isScheme('https') || uri.isScheme('http')) && - uri.host.isNotEmpty) { - toolbar.editor.formatSelection( - NotusAttribute.link.fromString(_inputController.text)); - } else { - error = true; - } - } on FormatException { - error = true; - } - } - if (error) { - _formatError = error; - toolbar.markNeedsRebuild(); - } else { - _inputKey = null; - _inputController.text = ''; - _inputController.removeListener(_handleInputChange); - toolbar.markNeedsRebuild(); - toolbar.editor.focus(); - } - }); - } - - void cancelEdit() { - if (mounted) { - final editor = ZefyrToolbar.of(context).editor; - setState(() { - _inputKey = null; - _inputController.text = ''; - _inputController.removeListener(_handleInputChange); - editor.focus(); - }); - } - } - - void unlink() { - final editor = ZefyrToolbar.of(context).editor; - editor.formatSelection(NotusAttribute.link.unset); - closeOverlay(); - } - - void copyToClipboard() { - var link = getLink(); - assert(link != null); - Clipboard.setData(new ClipboardData(text: link)); - } - - void openInBrowser() async { - final editor = ZefyrToolbar.of(context).editor; - var link = getLink(); - assert(link != null); - if (await canLaunch(link)) { - editor.hideKeyboard(); - await launch(link, forceWebView: true); - } - } - - void _handleInputChange() { - final toolbar = ZefyrToolbar.of(context); - setState(() { - _formatError = false; - toolbar.markNeedsRebuild(); - }); - } - - Widget buildOverlay(BuildContext context) { - final toolbar = ZefyrToolbar.of(context); - final style = toolbar.editor.selectionStyle; - - String value = 'Tap to edit link'; - if (style.contains(NotusAttribute.link)) { - value = style.value(NotusAttribute.link); - } - final clipboardEnabled = value != 'Tap to edit link'; - final body = !isEditing - ? _LinkView(value: value, onTap: edit) - : _LinkInput( - key: _inputKey, - controller: _inputController, - formatError: _formatError, - ); - final items = [Expanded(child: body)]; - if (!isEditing) { - final unlinkHandler = hasLink(style) ? unlink : null; - final copyHandler = clipboardEnabled ? copyToClipboard : null; - final openHandler = hasLink(style) ? openInBrowser : null; - final buttons = [ - toolbar.buildButton(context, ZefyrToolbarAction.unlink, - onPressed: unlinkHandler), - toolbar.buildButton(context, ZefyrToolbarAction.clipboardCopy, - onPressed: copyHandler), - toolbar.buildButton( - context, - ZefyrToolbarAction.openInBrowser, - onPressed: openHandler, - ), - ]; - items.addAll(buttons); - } - final trailingPressed = isEditing ? doneEdit : closeOverlay; - final trailingAction = - isEditing ? ZefyrToolbarAction.confirm : ZefyrToolbarAction.close; - - return ZefyrToolbarScaffold( - body: Row(children: items), - trailing: toolbar.buildButton( - context, - trailingAction, - onPressed: trailingPressed, - ), - ); - } -} - -class _LinkInput extends StatefulWidget { - final TextEditingController controller; - final bool formatError; - - const _LinkInput( - {Key key, @required this.controller, this.formatError: false}) - : super(key: key); - - @override - _LinkInputState createState() { - return new _LinkInputState(); - } -} - -class _LinkInputState extends State<_LinkInput> { - final FocusNode _focusNode = FocusNode(); - - ZefyrScope _editor; - bool _didAutoFocus = false; - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - if (!_didAutoFocus) { - FocusScope.of(context).requestFocus(_focusNode); - _didAutoFocus = true; - } - - final toolbar = ZefyrToolbar.of(context); - - if (_editor != toolbar.editor) { - _editor?.toolbarFocusNode = null; - _editor = toolbar.editor; - _editor.toolbarFocusNode = _focusNode; - } - } - - @override - void dispose() { - _editor?.toolbarFocusNode = null; - _focusNode.dispose(); - _editor = null; - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final toolbarTheme = ZefyrTheme.of(context).toolbarTheme; - final color = - widget.formatError ? Colors.redAccent : toolbarTheme.iconColor; - final style = theme.textTheme.subhead.copyWith(color: color); - return TextField( - style: style, - keyboardType: TextInputType.url, - focusNode: _focusNode, - controller: widget.controller, - autofocus: true, - decoration: new InputDecoration( - hintText: 'https://', - filled: true, - fillColor: toolbarTheme.color, - border: InputBorder.none, - contentPadding: const EdgeInsets.all(10.0), - ), - ); - } -} - -class _LinkView extends StatelessWidget { - const _LinkView({Key key, @required this.value, this.onTap}) - : super(key: key); - final String value; - final VoidCallback onTap; - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - final toolbarTheme = ZefyrTheme.of(context).toolbarTheme; - Widget widget = new ClipRect( - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - Container( - alignment: AlignmentDirectional.centerStart, - constraints: BoxConstraints(minHeight: ZefyrToolbar.kToolbarHeight), - padding: const EdgeInsets.all(10.0), - child: Text( - value, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: theme.textTheme.subhead - .copyWith(color: toolbarTheme.disabledIconColor), - ), - ) - ], - ), - ); - if (onTap != null) { - widget = GestureDetector( - child: widget, - onTap: onTap, - ); - } - return widget; - } -} diff --git a/zefyr/lib/src/widgets/caret.dart b/zefyr/lib/src/widgets/caret.dart deleted file mode 100644 index 895cd3b4..00000000 --- a/zefyr/lib/src/widgets/caret.dart +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'dart:ui'; - -import 'package:flutter/material.dart'; - -/// Helper class responsible for cursor layout and painting. -class CursorPainter { - static const double _kCaretHeightOffset = 2.0; // pixels - static const double _kCaretWidth = 1.0; // pixels - - static Rect buildPrototype(double lineHeight) { - return new Rect.fromLTWH( - 0.0, 0.0, _kCaretWidth, lineHeight - _kCaretHeightOffset); - } - - CursorPainter(Color color) - : assert(color != null), - _color = color; - - Rect _prototype; - - Rect get prototype => _prototype; - - Color _color; - Color get color => _color; - set color(Color value) { - assert(value != null); - _color = value; - } - - void layout(double lineHeight) { - _prototype = buildPrototype(lineHeight); - } - - void paint(Canvas canvas, Offset offset) { - final Paint paint = new Paint()..color = _color; - final Rect caretRect = _prototype.shift(offset); - canvas.drawRect(caretRect, paint); - } -} diff --git a/zefyr/lib/src/widgets/code.dart b/zefyr/lib/src/widgets/code.dart deleted file mode 100644 index 1c5c6050..00000000 --- a/zefyr/lib/src/widgets/code.dart +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'package:flutter/material.dart'; -import 'package:notus/notus.dart'; - -import 'common.dart'; -import 'theme.dart'; - -/// Represents a code snippet in Zefyr editor. -class ZefyrCode extends StatelessWidget { - const ZefyrCode({Key key, @required this.node}) : super(key: key); - - /// Document node represented by this widget. - final BlockNode node; - - @override - Widget build(BuildContext context) { - final theme = ZefyrTheme.of(context); - - List items = []; - for (var line in node.children) { - items.add(_buildLine(line, theme.blockTheme.code.textStyle)); - } - - return new Padding( - padding: theme.blockTheme.code.padding, - child: new Container( - // TODO: make decorations configurable - decoration: BoxDecoration( - color: Colors.blueGrey.shade50, - borderRadius: BorderRadius.circular(3.0), - ), - padding: const EdgeInsets.all(16.0), - child: new Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: items, - ), - ), - ); - } - - Widget _buildLine(Node node, TextStyle style) { - LineNode line = node; - return new RawZefyrLine(node: line, style: style); - } -} diff --git a/zefyr/lib/src/widgets/common.dart b/zefyr/lib/src/widgets/common.dart deleted file mode 100644 index aabc8b8f..00000000 --- a/zefyr/lib/src/widgets/common.dart +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; -import 'package:notus/notus.dart'; - -import 'editable_box.dart'; -import 'horizontal_rule.dart'; -import 'image.dart'; -import 'rich_text.dart'; -import 'scope.dart'; -import 'theme.dart'; - -/// Raw widget representing a single line of rich text document in Zefyr editor. -/// -/// See [ZefyrParagraph] and [ZefyrHeading] which wrap this widget and -/// integrate it with current [ZefyrTheme]. -class RawZefyrLine extends StatefulWidget { - const RawZefyrLine({ - Key key, - @required this.node, - this.style, - this.padding, - }) : super(key: key); - - /// Line in the document represented by this widget. - final LineNode node; - - /// Style to apply to this line. Required for lines with text contents, - /// ignored for lines containing embeds. - final TextStyle style; - - /// Padding to add around this paragraph. - final EdgeInsets padding; - - @override - _RawZefyrLineState createState() => new _RawZefyrLineState(); -} - -class _RawZefyrLineState extends State { - final LayerLink _link = new LayerLink(); - - @override - Widget build(BuildContext context) { - final scope = ZefyrScope.of(context); - if (scope.isEditable) { - ensureVisible(context, scope); - } - final theme = ZefyrTheme.of(context); - - Widget content; - if (widget.node.hasEmbed) { - content = buildEmbed(context, scope); - } else { - assert(widget.style != null); - content = ZefyrRichText( - node: widget.node, - text: buildText(context), - ); - } - - if (scope.isEditable) { - content = EditableBox( - child: content, - node: widget.node, - layerLink: _link, - renderContext: scope.renderContext, - showCursor: scope.showCursor, - selection: scope.selection, - selectionColor: theme.selectionColor, - cursorColor: theme.cursorColor, - ); - content = CompositedTransformTarget(link: _link, child: content); - } - - if (widget.padding != null) { - return Padding(padding: widget.padding, child: content); - } - return content; - } - - void ensureVisible(BuildContext context, ZefyrScope scope) { - if (scope.selection.isCollapsed && - widget.node.containsOffset(scope.selection.extentOffset)) { - WidgetsBinding.instance.addPostFrameCallback((_) { - bringIntoView(context); - }); - } - } - - void bringIntoView(BuildContext context) { - ScrollableState scrollable = Scrollable.of(context); - final object = context.findRenderObject(); - assert(object.attached); - final RenderAbstractViewport viewport = RenderAbstractViewport.of(object); - assert(viewport != null); - - final double offset = scrollable.position.pixels; - double target = viewport.getOffsetToReveal(object, 0.0).offset; - if (target - offset < 0.0) { - scrollable.position.jumpTo(target); - return; - } - target = viewport.getOffsetToReveal(object, 1.0).offset; - if (target - offset > 0.0) { - scrollable.position.jumpTo(target); - } - } - - TextSpan buildText(BuildContext context) { - final theme = ZefyrTheme.of(context); - final List children = widget.node.children - .map((node) => _segmentToTextSpan(node, theme)) - .toList(growable: false); - return new TextSpan(style: widget.style, children: children); - } - - TextSpan _segmentToTextSpan(Node node, ZefyrThemeData theme) { - final TextNode segment = node; - final attrs = segment.style; - - return new TextSpan( - text: segment.value, - style: _getTextStyle(attrs, theme), - ); - } - - TextStyle _getTextStyle(NotusStyle style, ZefyrThemeData theme) { - TextStyle result = new TextStyle(); - if (style.containsSame(NotusAttribute.bold)) { - result = result.merge(theme.boldStyle); - } - if (style.containsSame(NotusAttribute.italic)) { - result = result.merge(theme.italicStyle); - } - if (style.contains(NotusAttribute.link)) { - result = result.merge(theme.linkStyle); - } - return result; - } - - Widget buildEmbed(BuildContext context, ZefyrScope scope) { - EmbedNode node = widget.node.children.single; - EmbedAttribute embed = node.style.get(NotusAttribute.embed); - - if (embed.type == EmbedType.horizontalRule) { - return ZefyrHorizontalRule(node: node); - } else if (embed.type == EmbedType.image) { - return ZefyrImage(node: node, delegate: scope.imageDelegate); - } else { - throw new UnimplementedError('Unimplemented embed type ${embed.type}'); - } - } -} diff --git a/zefyr/lib/src/widgets/controller.dart b/zefyr/lib/src/widgets/controller.dart deleted file mode 100644 index 325769d3..00000000 --- a/zefyr/lib/src/widgets/controller.dart +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'dart:math' as math; - -import 'package:flutter/widgets.dart'; -import 'package:notus/notus.dart'; -import 'package:quill_delta/quill_delta.dart'; -import 'package:zefyr/util.dart'; - -const TextSelection _kZeroSelection = const TextSelection.collapsed( - offset: 0, - affinity: TextAffinity.upstream, -); - -/// Owner of focus. -enum FocusOwner { - /// Current owner is the editor. - editor, - - /// Current owner is the toolbar. - toolbar, - - /// No focus owner. - none, -} - -/// Controls instance of [ZefyrEditor]. -class ZefyrController extends ChangeNotifier { - ZefyrController(NotusDocument document) - : assert(document != null), - _document = document; - - /// Zefyr document managed by this controller. - NotusDocument get document => _document; - NotusDocument _document; - - /// Currently selected text within the [document]. - TextSelection get selection => _selection; - TextSelection _selection = _kZeroSelection; - - ChangeSource _lastChangeSource; - - /// Source of the last text or selection change. - ChangeSource get lastChangeSource => _lastChangeSource; - - /// Updates selection with specified [value]. - /// - /// [value] and [source] cannot be `null`. - void updateSelection(TextSelection value, - {ChangeSource source: ChangeSource.remote}) { - _updateSelectionSilent(value, source: source); - notifyListeners(); - } - - // Updates selection without triggering notifications to listeners. - void _updateSelectionSilent(TextSelection value, - {ChangeSource source: ChangeSource.remote}) { - assert(value != null && source != null); - _selection = value; - _lastChangeSource = source; - _ensureSelectionBeforeLastBreak(); - } - - @override - void dispose() { - _document.close(); - super.dispose(); - } - - /// Composes [change] into document managed by this controller. - /// - /// This method does not apply any adjustments or heuristic rules to - /// provided [change] and it is caller's responsibility to ensure this change - /// can be composed without errors. - /// - /// If composing this change fails then this method throws [ComposeError]. - void compose(Delta change, - {TextSelection selection, ChangeSource source: ChangeSource.remote}) { - if (change.isNotEmpty) { - _document.compose(change, source); - } - if (selection != null) { - _updateSelectionSilent(selection, source: source); - } else { - // Transform selection against the composed change and give priority to - // current position (force: false). - final base = - change.transformPosition(_selection.baseOffset, force: false); - final extent = - change.transformPosition(_selection.extentOffset, force: false); - selection = _selection.copyWith(baseOffset: base, extentOffset: extent); - if (_selection != selection) { - _updateSelectionSilent(selection, source: source); - } - } - _lastChangeSource = source; - notifyListeners(); - } - - void replaceText(int index, int length, String text, - {TextSelection selection}) { - Delta delta; - - if (length > 0 || text.isNotEmpty) { - delta = document.replace(index, length, text); - } - - if (selection != null) { - if (delta == null) { - _updateSelectionSilent(selection, source: ChangeSource.local); - } else { - // need to transform selection position in case actual delta - // is different from user's version (in deletes and inserts). - Delta user = new Delta() - ..retain(index) - ..insert(text) - ..delete(length); - int positionDelta = getPositionDelta(user, delta); - _updateSelectionSilent( - selection.copyWith( - baseOffset: selection.baseOffset + positionDelta, - extentOffset: selection.extentOffset + positionDelta, - ), - source: ChangeSource.local, - ); - } - } - _lastChangeSource = ChangeSource.local; - notifyListeners(); - } - - void formatText(int index, int length, NotusAttribute attribute) { - final change = document.format(index, length, attribute); - _lastChangeSource = ChangeSource.local; - // Transform selection against the composed change and give priority to - // the change. This is needed in cases when format operation actually - // inserts data into the document (e.g. embeds). - final base = change.transformPosition(_selection.baseOffset); - final extent = - change.transformPosition(_selection.extentOffset); - final adjustedSelection = - _selection.copyWith(baseOffset: base, extentOffset: extent); - if (_selection != adjustedSelection) { - _updateSelectionSilent(adjustedSelection, source: _lastChangeSource); - } - notifyListeners(); - } - - /// Formats current selection with [attribute]. - void formatSelection(NotusAttribute attribute) { - int index = _selection.start; - int length = _selection.end - index; - formatText(index, length, attribute); - } - - NotusStyle getSelectionStyle() { - int start = _selection.start; - int length = _selection.end - start; - return _document.collectStyle(start, length); - } - - TextEditingValue get plainTextEditingValue { - return new TextEditingValue( - text: document.toPlainText(), - selection: selection, - composing: new TextRange.collapsed(0), - ); - } - - void _ensureSelectionBeforeLastBreak() { - final end = _document.length - 1; - final base = math.min(_selection.baseOffset, end); - final extent = math.min(_selection.extentOffset, end); - _selection = _selection.copyWith(baseOffset: base, extentOffset: extent); - } -} diff --git a/zefyr/lib/src/widgets/cursor_timer.dart b/zefyr/lib/src/widgets/cursor_timer.dart deleted file mode 100644 index 947dc5ab..00000000 --- a/zefyr/lib/src/widgets/cursor_timer.dart +++ /dev/null @@ -1,42 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -/// Helper class that keeps state relevant to the editing cursor. -class CursorTimer { - static const _kCursorBlinkHalfPeriod = const Duration(milliseconds: 500); - - Timer _timer; - final ValueNotifier _showCursor = new ValueNotifier(false); - - ValueNotifier get value => _showCursor; - - void _cursorTick(Timer timer) { - _showCursor.value = !_showCursor.value; - } - - /// Starts cursor timer. - void start() { - _showCursor.value = true; - _timer = new Timer.periodic(_kCursorBlinkHalfPeriod, _cursorTick); - } - - /// Stops cursor timer. - void stop() { - _timer?.cancel(); - _timer = null; - _showCursor.value = false; - } - - /// Starts or stops cursor timer based on current state of [focusNode] - /// and [selection]. - void startOrStop(FocusNode focusNode, TextSelection selection) { - final hasFocus = focusNode.hasFocus; - final selectionCollapsed = selection.isCollapsed; - if (_timer == null && hasFocus && selectionCollapsed) { - start(); - } else if (_timer != null && (!hasFocus || !selectionCollapsed)) { - stop(); - } - } -} diff --git a/zefyr/lib/src/widgets/editable_box.dart b/zefyr/lib/src/widgets/editable_box.dart deleted file mode 100644 index 71c52f48..00000000 --- a/zefyr/lib/src/widgets/editable_box.dart +++ /dev/null @@ -1,341 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'dart:math' as math; -import 'dart:ui' as ui; - -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; -import 'package:notus/notus.dart'; - -import 'caret.dart'; -import 'render_context.dart'; - -class EditableBox extends SingleChildRenderObjectWidget { - EditableBox({ - @required Widget child, - @required this.node, - @required this.layerLink, - @required this.renderContext, - @required this.showCursor, - @required this.selection, - @required this.selectionColor, - @required this.cursorColor, - }) : super(child: child); - - final ContainerNode node; - final LayerLink layerLink; - final ZefyrRenderContext renderContext; - final ValueNotifier showCursor; - final TextSelection selection; - final Color selectionColor; - final Color cursorColor; - - @override - RenderEditableProxyBox createRenderObject(BuildContext context) { - return new RenderEditableProxyBox( - node: node, - layerLink: layerLink, - renderContext: renderContext, - showCursor: showCursor, - selection: selection, - selectionColor: selectionColor, - cursorColor: cursorColor, - ); - } - - @override - void updateRenderObject( - BuildContext context, RenderEditableProxyBox renderObject) { - renderObject - ..node = node - ..layerLink = layerLink - ..renderContext = renderContext - ..showCursor = showCursor - ..selection = selection - ..selectionColor = selectionColor - ..cursorColor = cursorColor; - } -} - -class RenderEditableProxyBox extends RenderBox - with - RenderObjectWithChildMixin, - RenderProxyBoxMixin - implements RenderEditableBox { - RenderEditableProxyBox({ - RenderEditableBox child, - @required ContainerNode node, - @required LayerLink layerLink, - @required ZefyrRenderContext renderContext, - @required ValueNotifier showCursor, - @required TextSelection selection, - @required Color selectionColor, - @required Color cursorColor, - }) : _node = node, - _layerLink = layerLink, - _renderContext = renderContext, - _showCursor = showCursor, - _selection = selection, - _selectionColor = selectionColor, - super() { - this.child = child; - _cursorPainter = CursorPainter(cursorColor); - } - - CursorPainter _cursorPainter; - - set cursorColor(Color value) { - if (_cursorPainter.color != value) { - _cursorPainter.color = value; - markNeedsPaint(); - } - } - - bool _isDirty = true; - - ContainerNode get node => _node; - ContainerNode _node; - void set node(ContainerNode value) { - _node = value; - } - - LayerLink get layerLink => _layerLink; - LayerLink _layerLink; - void set layerLink(LayerLink value) { - if (_layerLink == value) return; - _layerLink = value; - } - - ZefyrRenderContext _renderContext; - void set renderContext(ZefyrRenderContext value) { - if (_renderContext == value) return; - if (attached) _renderContext.removeBox(this); - _renderContext = value; - if (attached) _renderContext.addBox(this); - } - - ValueNotifier _showCursor; - set showCursor(ValueNotifier value) { - assert(value != null); - if (_showCursor == value) return; - if (attached) _showCursor.removeListener(markNeedsCursorPaint); - _showCursor = value; - if (attached) _showCursor.addListener(markNeedsCursorPaint); - markNeedsPaint(); - } - - /// Current document selection. - TextSelection get selection => _selection; - TextSelection _selection; - set selection(TextSelection value) { - if (_selection == value) return; - // TODO: check if selection affects this block (also check previous value) - _selection = value; - markNeedsPaint(); - } - - /// Color of selection. - Color get selectionColor => _selectionColor; - Color _selectionColor; - set selectionColor(Color value) { - if (_selectionColor == value) return; - _selectionColor = value; - markNeedsPaint(); - } - - /// Returns `true` if current selection is collapsed, located within - /// this paragraph and is visible according to tick timer. - bool get isCaretVisible { - return _showCursor.value && containsCaret; - } - - /// Returns `true` if current selection is collapsed and located - /// within this paragraph. - bool get containsCaret { - if (!_selection.isCollapsed) return false; - - final int start = node.documentOffset; - final int end = start + node.length; - final int caretOffset = _selection.extentOffset; - return caretOffset >= start && caretOffset < end; - } - - /// Returns `true` if selection is not collapsed and intersects with this - /// paragraph. - bool get isSelectionVisible { - if (_selection.isCollapsed) return false; - return intersectsWithSelection(_selection); - } - - void markNeedsCursorPaint() { - if (containsCaret) { - markNeedsPaint(); - } - } - - // - // Overridden members of RenderBox - // - - @override - void attach(PipelineOwner owner) { - super.attach(owner); - _showCursor.addListener(markNeedsCursorPaint); - _renderContext.addBox(this); - _renderContext.markDirty(this, _isDirty); - } - - @override - void detach() { - _showCursor.removeListener(markNeedsCursorPaint); - _renderContext.removeBox(this); - super.detach(); - } - - @override - @mustCallSuper - void performLayout() { - super.performLayout(); - _cursorPainter.layout(preferredLineHeight); - // Indicate to render context that this object can be used by other - // layers (selection overlay, for instance). - _isDirty = false; - _renderContext.markDirty(this, false); - } - - @override - void markNeedsLayout() { - // Temporarily remove this object from the render context. - _isDirty = true; - _renderContext.markDirty(this, true); - super.markNeedsLayout(); - } - - @override - void paint(PaintingContext context, Offset offset) { - if (selectionOrder == SelectionOrder.background && isSelectionVisible) { - paintSelection(context, offset, selection, selectionColor); - } - super.paint(context, offset); - if (selectionOrder == SelectionOrder.foreground && isSelectionVisible) { - paintSelection(context, offset, selection, selectionColor); - } - if (isCaretVisible) { - _paintCursor(context, offset); - } - } - - void _paintCursor(PaintingContext context, Offset offset) { - Offset caretOffset = - getOffsetForCaret(_selection.extent, _cursorPainter.prototype); - _cursorPainter.paint(context.canvas, caretOffset + offset); - } - - @override - bool hitTestSelf(Offset position) => true; - - @override - bool hitTest(HitTestResult result, {Offset position}) { - if (size.contains(position)) { - result.add(new BoxHitTestEntry(this, position)); - return true; - } - return false; - } - - // - // Proxy methods - // - - @override - double get preferredLineHeight => child.preferredLineHeight; - - @override - SelectionOrder get selectionOrder => child.selectionOrder; - - @override - void paintSelection(PaintingContext context, Offset offset, - TextSelection selection, Color selectionColor) => - child.paintSelection(context, offset, selection, selectionColor); - - @override - Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) => - child.getOffsetForCaret(position, caretPrototype); - - @override - TextSelection getLocalSelection(TextSelection documentSelection) => - child.getLocalSelection(documentSelection); - - bool intersectsWithSelection(TextSelection selection) => - child.intersectsWithSelection(selection); - - @override - List getEndpointsForSelection(TextSelection selection) => - child.getEndpointsForSelection(selection); - - @override - ui.TextPosition getPositionForOffset(ui.Offset offset) => - child.getPositionForOffset(offset); - - @override - TextRange getWordBoundary(ui.TextPosition position) => - child.getWordBoundary(position); -} - -enum SelectionOrder { - /// Background selection is painted before primary content of editable box. - background, - - /// Foreground selection is painted after primary content of editable box. - foreground, -} - -abstract class RenderEditableBox extends RenderBox { - Node get node; - double get preferredLineHeight; - - TextPosition getPositionForOffset(Offset offset); - List getEndpointsForSelection(TextSelection selection); - - /// Returns the text range of the word at the given offset. Characters not - /// part of a word, such as spaces, symbols, and punctuation, have word breaks - /// on both sides. In such cases, this method will return a text range that - /// contains the given text position. - /// - /// Word boundaries are defined more precisely in Unicode Standard Annex #29 - /// . - /// - /// Valid only after [layout]. - TextRange getWordBoundary(TextPosition position); - - /// Paint order of selection in this editable box. - SelectionOrder get selectionOrder; - - void paintSelection(PaintingContext context, Offset offset, - TextSelection selection, Color selectionColor); - - Offset getOffsetForCaret(TextPosition position, Rect caretPrototype); - - /// Returns part of [documentSelection] local to this box. May return - /// `null`. - /// - /// [documentSelection] must not be collapsed. - TextSelection getLocalSelection(TextSelection documentSelection) { - if (!intersectsWithSelection(documentSelection)) return null; - - int nodeBase = node.documentOffset; - int nodeExtent = nodeBase + node.length; - int base = math.max(0, documentSelection.baseOffset - nodeBase); - int extent = - math.min(documentSelection.extentOffset, nodeExtent) - nodeBase; - return documentSelection.copyWith(baseOffset: base, extentOffset: extent); - } - - /// Returns `true` if this box intersects with document [selection]. - bool intersectsWithSelection(TextSelection selection) { - final int base = node.documentOffset; - final int extent = base + node.length; - return base <= selection.extentOffset && selection.baseOffset <= extent; - } -} diff --git a/zefyr/lib/src/widgets/editable_text.dart b/zefyr/lib/src/widgets/editable_text.dart deleted file mode 100644 index 37fdcf41..00000000 --- a/zefyr/lib/src/widgets/editable_text.dart +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'package:flutter/cupertino.dart'; -import 'package:flutter/widgets.dart'; -import 'package:notus/notus.dart'; - -import 'code.dart'; -import 'common.dart'; -import 'controller.dart'; -import 'cursor_timer.dart'; -import 'editor.dart'; -import 'image.dart'; -import 'input.dart'; -import 'list.dart'; -import 'paragraph.dart'; -import 'quote.dart'; -import 'render_context.dart'; -import 'scope.dart'; -import 'selection.dart'; -import 'theme.dart'; - -/// Core widget responsible for editing Zefyr documents. -/// -/// Depends on presence of [ZefyrTheme] and [ZefyrScope] somewhere up the -/// widget tree. -/// -/// Consider using [ZefyrEditor] which wraps this widget and adds a toolbar to -/// edit style attributes. -class ZefyrEditableText extends StatefulWidget { - const ZefyrEditableText({ - Key key, - @required this.controller, - @required this.focusNode, - @required this.imageDelegate, - this.autofocus: true, - this.enabled: true, - this.padding: const EdgeInsets.symmetric(horizontal: 16.0), - this.physics, - }) : super(key: key); - - final ZefyrController controller; - final FocusNode focusNode; - final ZefyrImageDelegate imageDelegate; - final bool autofocus; - final bool enabled; - final ScrollPhysics physics; - - /// Padding around editable area. - final EdgeInsets padding; - - - - @override - _ZefyrEditableTextState createState() => new _ZefyrEditableTextState(); -} - -class _ZefyrEditableTextState extends State - with AutomaticKeepAliveClientMixin { - // - // New public members - // - - /// Focus node of this widget. - FocusNode get focusNode => widget.focusNode; - - /// Document controlled by this widget. - NotusDocument get document => widget.controller.document; - - /// Current text selection. - TextSelection get selection => widget.controller.selection; - - /// Express interest in interacting with the keyboard. - /// - /// If this control is already attached to the keyboard, this function will - /// request that the keyboard become visible. Otherwise, this function will - /// ask the focus system that it become focused. If successful in acquiring - /// focus, the control will then attach to the keyboard and request that the - /// keyboard become visible. - void requestKeyboard() { - if (focusNode.hasFocus) - _input.openConnection(widget.controller.plainTextEditingValue); - else - FocusScope.of(context).requestFocus(focusNode); - } - - void focusOrUnfocusIfNeeded() { - if (!_didAutoFocus && widget.autofocus && widget.enabled) { - FocusScope.of(context).autofocus(focusNode); - _didAutoFocus = true; - } - if (!widget.enabled && focusNode.hasFocus) { - _didAutoFocus = false; - focusNode.unfocus(); - } - } - - // - // Overridden members of State - // - - - - @override - Widget build(BuildContext context) { -// var reparentIfNeeded = FocusScope.of(context).reparentIfNeeded(focusNode); - - _nodeAttachment.reparent(); - super.build(context); // See AutomaticKeepAliveState. - - Widget body = ListBody(children: _buildChildren(context)); - if (widget.padding != null) { - body = new Padding(padding: widget.padding, child: body); - } - final scrollable = SingleChildScrollView( - physics: widget.physics, - controller: _scrollController, - child: body, - ); - - final overlay = Overlay.of(context, debugRequiredFor: widget); - final layers = [scrollable]; - if (widget.enabled) { - layers.add(ZefyrSelectionOverlay( - controller: widget.controller, - controls: cupertinoTextSelectionControls, - overlay: overlay, - )); - } - - return Stack(fit: StackFit.expand, children: layers); - } - - FocusAttachment _nodeAttachment; - @override - void initState() { - super.initState(); -// FocusScopeNode _node = focusNode; - _nodeAttachment = focusNode.attach(context); - _input = new InputConnectionController(_handleRemoteValueChange); - _updateSubscriptions(); - } - - @override - void didUpdateWidget(ZefyrEditableText oldWidget) { - super.didUpdateWidget(oldWidget); - _updateSubscriptions(oldWidget); - focusOrUnfocusIfNeeded(); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final scope = ZefyrScope.of(context); - if (_renderContext != scope.renderContext) { - _renderContext?.removeListener(_handleRenderContextChange); - _renderContext = scope.renderContext; - _renderContext.addListener(_handleRenderContextChange); - } - if (_cursorTimer != scope.cursorTimer) { - _cursorTimer?.stop(); - _cursorTimer = scope.cursorTimer; - _cursorTimer.startOrStop(focusNode, selection); - } - focusOrUnfocusIfNeeded(); - } - - @override - void dispose() { - _cancelSubscriptions(); - super.dispose(); - } - - // - // Overridden members of AutomaticKeepAliveClientMixin - // - - @override - bool get wantKeepAlive => focusNode.hasFocus; - - // - // Private members - // - - final ScrollController _scrollController = ScrollController(); - ZefyrRenderContext _renderContext; - CursorTimer _cursorTimer; - InputConnectionController _input; - bool _didAutoFocus = false; - - List _buildChildren(BuildContext context) { - final result = []; - for (var node in document.root.children) { - result.add(_defaultChildBuilder(context, node)); - } - return result; - } - - Widget _defaultChildBuilder(BuildContext context, Node node) { - if (node is LineNode) { - if (node.hasEmbed) { - return new RawZefyrLine(node: node); - } else if (node.style.contains(NotusAttribute.heading)) { - return new ZefyrHeading(node: node); - } - return new ZefyrParagraph(node: node); - } - - final BlockNode block = node; - final blockStyle = block.style.get(NotusAttribute.block); - if (blockStyle == NotusAttribute.block.code) { - return new ZefyrCode(node: block); - } else if (blockStyle == NotusAttribute.block.bulletList) { - return new ZefyrList(node: block); - } else if (blockStyle == NotusAttribute.block.numberList) { - return new ZefyrList(node: block); - } else if (blockStyle == NotusAttribute.block.quote) { - return new ZefyrQuote(node: block); - } - - throw new UnimplementedError('Block format $blockStyle.'); - } - - void _updateSubscriptions([ZefyrEditableText oldWidget]) { - if (oldWidget == null) { - widget.controller.addListener(_handleLocalValueChange); - focusNode.addListener(_handleFocusChange); - return; - } - - if (widget.controller != oldWidget.controller) { - oldWidget.controller.removeListener(_handleLocalValueChange); - widget.controller.addListener(_handleLocalValueChange); - _input.updateRemoteValue(widget.controller.plainTextEditingValue); - } - if (widget.focusNode != oldWidget.focusNode) { - oldWidget.focusNode.removeListener(_handleFocusChange); - widget.focusNode.addListener(_handleFocusChange); - updateKeepAlive(); - } - } - - void _cancelSubscriptions() { - _renderContext.removeListener(_handleRenderContextChange); - widget.controller.removeListener(_handleLocalValueChange); - focusNode.removeListener(_handleFocusChange); - _input.closeConnection(); - _cursorTimer.stop(); - } - - // Triggered for both text and selection changes. - void _handleLocalValueChange() { - if (widget.enabled && - widget.controller.lastChangeSource == ChangeSource.local) { - // Only request keyboard for user actions. - requestKeyboard(); - } - _input.updateRemoteValue(widget.controller.plainTextEditingValue); - _cursorTimer.startOrStop(focusNode, selection); - setState(() { - // nothing to update internally. - }); - } - - void _handleFocusChange() { - _input.openOrCloseConnection( - focusNode, widget.controller.plainTextEditingValue); - _cursorTimer.startOrStop(focusNode, selection); - updateKeepAlive(); - } - - void _handleRemoteValueChange( - int start, String deleted, String inserted, TextSelection selection) { - widget.controller - .replaceText(start, deleted.length, inserted, selection: selection); - } - - void _handleRenderContextChange() { - setState(() { - // nothing to update internally. - }); - } -} diff --git a/zefyr/lib/src/widgets/editor.dart b/zefyr/lib/src/widgets/editor.dart deleted file mode 100644 index 4a37337e..00000000 --- a/zefyr/lib/src/widgets/editor.dart +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'package:flutter/widgets.dart'; - -import 'controller.dart'; -import 'editable_text.dart'; -import 'image.dart'; -import 'scaffold.dart'; -import 'scope.dart'; -import 'theme.dart'; -import 'toolbar.dart'; - -/// Widget for editing Zefyr documents. -class ZefyrEditor extends StatefulWidget { - const ZefyrEditor({ - Key key, - @required this.controller, - @required this.focusNode, - this.autofocus: true, - this.enabled: true, - this.padding: const EdgeInsets.symmetric(horizontal: 16.0), - this.toolbarDelegate, - this.imageDelegate, - this.physics, - }) : super(key: key); - - final ZefyrController controller; - final FocusNode focusNode; - final bool autofocus; - final bool enabled; - final ZefyrToolbarDelegate toolbarDelegate; - final ZefyrImageDelegate imageDelegate; - final ScrollPhysics physics; - - /// Padding around editable area. - final EdgeInsets padding; - - @override - _ZefyrEditorState createState() => new _ZefyrEditorState(); -} - -class _ZefyrEditorState extends State { - ZefyrImageDelegate _imageDelegate; - ZefyrScope _scope; - ZefyrThemeData _themeData; - GlobalKey _toolbarKey; - ZefyrScaffoldState _scaffold; - - bool get hasToolbar => _toolbarKey != null; - - void showToolbar() { - assert(_toolbarKey == null); - _toolbarKey = GlobalKey(); - _scaffold.showToolbar(buildToolbar); - } - - void hideToolbar() { - if (_toolbarKey == null) return; - _scaffold.hideToolbar(); - _toolbarKey = null; - } - - Widget buildToolbar(BuildContext context) { - return ZefyrTheme( - data: _themeData, - child: ZefyrToolbar( - key: _toolbarKey, - editor: _scope, - delegate: widget.toolbarDelegate, - ), - ); - } - - void _handleChange() { - if (_scope.focusOwner == FocusOwner.none) { - hideToolbar(); - } else if (!hasToolbar) { - showToolbar(); - } else { - // TODO: is there a nicer way to do this? - WidgetsBinding.instance.addPostFrameCallback((_) { - _toolbarKey?.currentState?.markNeedsRebuild(); - }); - } - } - - @override - void initState() { - super.initState(); - _imageDelegate = widget.imageDelegate ?? new ZefyrDefaultImageDelegate(); - } - - @override - void didUpdateWidget(ZefyrEditor oldWidget) { - super.didUpdateWidget(oldWidget); - _scope.controller = widget.controller; - _scope.focusNode = widget.focusNode; - if (widget.imageDelegate != oldWidget.imageDelegate) { - _imageDelegate = widget.imageDelegate ?? new ZefyrDefaultImageDelegate(); - _scope.imageDelegate = _imageDelegate; - } - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final parentTheme = ZefyrTheme.of(context, nullOk: true); - final fallbackTheme = ZefyrThemeData.fallback(context); - _themeData = (parentTheme != null) - ? fallbackTheme.merge(parentTheme) - : fallbackTheme; - - if (_scope == null) { - _scope = ZefyrScope.editable( - imageDelegate: _imageDelegate, - controller: widget.controller, - focusNode: widget.focusNode, - focusScope: FocusScope.of(context), - ); - _scope.addListener(_handleChange); - } else { - final focusScope = FocusScope.of(context); - _scope.focusScope = focusScope; - } - - final scaffold = ZefyrScaffold.of(context); - if (_scaffold != scaffold) { - bool didHaveToolbar = hasToolbar; - hideToolbar(); - _scaffold = scaffold; - if (didHaveToolbar) showToolbar(); - } - } - - @override - void dispose() { - hideToolbar(); - _scope.removeListener(_handleChange); - _scope.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - Widget editable = new ZefyrEditableText( - controller: _scope.controller, - focusNode: _scope.focusNode, - imageDelegate: _scope.imageDelegate, - autofocus: widget.autofocus, - enabled: widget.enabled, - padding: widget.padding, - physics: widget.physics, - ); - - return ZefyrTheme( - data: _themeData, - child: ZefyrScopeAccess( - scope: _scope, - child: editable, - ), - ); - } -} diff --git a/zefyr/lib/src/widgets/field.dart b/zefyr/lib/src/widgets/field.dart deleted file mode 100644 index d23f3ca7..00000000 --- a/zefyr/lib/src/widgets/field.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'controller.dart'; -import 'editor.dart'; -import 'image.dart'; -import 'toolbar.dart'; - -/// Zefyr editor with material design decorations. -class ZefyrField extends StatefulWidget { - /// Decoration to paint around this editor. - final InputDecoration decoration; - - /// Height of this editor field. - final double height; - final ZefyrController controller; - final FocusNode focusNode; - final bool autofocus; - final bool enabled; - final ZefyrToolbarDelegate toolbarDelegate; - final ZefyrImageDelegate imageDelegate; - final ScrollPhysics physics; - - const ZefyrField({ - Key key, - this.decoration, - this.height, - this.controller, - this.focusNode, - this.autofocus: false, - this.enabled, - this.toolbarDelegate, - this.imageDelegate, - this.physics, - }) : super(key: key); - - @override - _ZefyrFieldState createState() => _ZefyrFieldState(); -} - -class _ZefyrFieldState extends State { - @override - Widget build(BuildContext context) { - Widget child = ZefyrEditor( - padding: EdgeInsets.symmetric(vertical: 6.0), - controller: widget.controller, - focusNode: widget.focusNode, - autofocus: widget.autofocus, - enabled: widget.enabled ?? true, - toolbarDelegate: widget.toolbarDelegate, - imageDelegate: widget.imageDelegate, - physics: widget.physics, - ); - - if (widget.height != null) { - child = ConstrainedBox( - constraints: BoxConstraints.tightFor(height: widget.height), - child: child, - ); - } - - return AnimatedBuilder( - animation: - Listenable.merge([widget.focusNode, widget.controller]), - builder: (BuildContext context, Widget child) { - return InputDecorator( - decoration: _getEffectiveDecoration(), - isFocused: widget.focusNode.hasFocus, - isEmpty: widget.controller.document.length == 1, - child: child, - ); - }, - child: child, - ); - } - - InputDecoration _getEffectiveDecoration() { - final InputDecoration effectiveDecoration = - (widget.decoration ?? const InputDecoration()) - .applyDefaults(Theme.of(context).inputDecorationTheme) - .copyWith( - enabled: widget.enabled ?? true, - ); - - return effectiveDecoration; - } -} diff --git a/zefyr/lib/src/widgets/horizontal_rule.dart b/zefyr/lib/src/widgets/horizontal_rule.dart deleted file mode 100644 index d9011f0e..00000000 --- a/zefyr/lib/src/widgets/horizontal_rule.dart +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'dart:ui' as ui; - -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; -import 'package:notus/notus.dart'; - -import 'editable_box.dart'; - -class ZefyrHorizontalRule extends LeafRenderObjectWidget { - ZefyrHorizontalRule({@required this.node}) : assert(node != null); - - final EmbedNode node; - - @override - RenderHorizontalRule createRenderObject(BuildContext context) { - return new RenderHorizontalRule(node: node); - } - - @override - void updateRenderObject( - BuildContext context, RenderHorizontalRule renderObject) { - renderObject..node = node; - } -} - -class RenderHorizontalRule extends RenderEditableBox { - static const _kPaddingBottom = 24.0; - static const _kThickness = 3.0; - static const _kHeight = _kThickness + _kPaddingBottom; - - RenderHorizontalRule({ - @required EmbedNode node, - }) : _node = node; - - @override - EmbedNode get node => _node; - EmbedNode _node; - set node(EmbedNode value) { - if (_node == value) return; - _node = value; - markNeedsPaint(); - } - - @override - double get preferredLineHeight => size.height; - - @override - SelectionOrder get selectionOrder => SelectionOrder.background; - - @override - List getEndpointsForSelection(TextSelection selection) { - TextSelection local = getLocalSelection(selection); - if (local.isCollapsed) { - final dx = local.extentOffset == 0 ? 0.0 : size.width; - return [ - new ui.TextBox.fromLTRBD(dx, 0.0, dx, size.height, TextDirection.ltr), - ]; - } - - return [ - new ui.TextBox.fromLTRBD(0.0, 0.0, 0.0, size.height, TextDirection.ltr), - new ui.TextBox.fromLTRBD( - size.width, 0.0, size.width, size.height, TextDirection.ltr), - ]; - } - - @override - void performLayout() { - assert(constraints.hasBoundedWidth); - size = new Size(constraints.maxWidth, _kHeight); - } - - @override - void paint(PaintingContext context, Offset offset) { - final rect = new Rect.fromLTWH(0.0, 0.0, size.width, _kThickness); - final paint = new ui.Paint()..color = Colors.grey.shade200; - context.canvas.drawRect(rect.shift(offset), paint); - } - - @override - TextPosition getPositionForOffset(Offset offset) { - int position = _node.documentOffset; - - if (offset.dx > size.width / 2) { - position++; - } - return new TextPosition(offset: position); - } - - @override - TextRange getWordBoundary(TextPosition position) { - final start = _node.documentOffset; - return new TextRange(start: start, end: start + 1); - } - - @override - void paintSelection(PaintingContext context, Offset offset, - TextSelection selection, Color selectionColor) { - final localSelection = getLocalSelection(selection); - assert(localSelection != null); - if (!localSelection.isCollapsed) { - final Paint paint = new Paint()..color = selectionColor; - final rect = new Rect.fromLTWH(0.0, 0.0, size.width, _kHeight); - context.canvas.drawRect(rect.shift(offset), paint); - } - } - - @override - Offset getOffsetForCaret(ui.TextPosition position, ui.Rect caretPrototype) { - final pos = position.offset - node.documentOffset; - Offset caretOffset = Offset.zero; - if (pos == 1) { - caretOffset = caretOffset + new Offset(size.width - 1.0, 0.0); - } - return caretOffset; - } -} diff --git a/zefyr/lib/src/widgets/image.dart b/zefyr/lib/src/widgets/image.dart deleted file mode 100644 index be096d06..00000000 --- a/zefyr/lib/src/widgets/image.dart +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'dart:async'; -import 'dart:io'; -import 'dart:math' as math; -import 'dart:ui' as ui; - -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; -import 'package:notus/notus.dart'; -import 'package:image_picker/image_picker.dart'; - -import 'editable_box.dart'; - -abstract class ZefyrImageDelegate { - /// Builds image widget for specified [imageSource] and [context]. - Widget buildImage(BuildContext context, String imageSource); - - /// Picks an image from specified [source]. - /// - /// Returns unique string key for the selected image. Returned key is stored - /// in the document. - Future pickImage(S source); -} - -class ZefyrDefaultImageDelegate implements ZefyrImageDelegate { - @override - Widget buildImage(BuildContext context, String imageSource) { - final file = new File.fromUri(Uri.parse(imageSource)); - final image = new FileImage(file); - return new Image(image: image); - } - - @override - Future pickImage(ImageSource source) async { - final file = await ImagePicker.pickImage(source: source); - if (file == null) return null; - return file.uri.toString(); - } -} - -class ZefyrImage extends StatefulWidget { - const ZefyrImage({Key key, @required this.node, @required this.delegate}) - : super(key: key); - - final EmbedNode node; - final ZefyrImageDelegate delegate; - - @override - _ZefyrImageState createState() => _ZefyrImageState(); -} - -class _ZefyrImageState extends State { - String get imageSource { - EmbedAttribute attribute = widget.node.style.get(NotusAttribute.embed); - return attribute.value['source'] as String; - } - - @override - Widget build(BuildContext context) { - final image = widget.delegate.buildImage(context, imageSource); - return _EditableImage( - child: image, - node: widget.node, - ); - } -} - -class _EditableImage extends SingleChildRenderObjectWidget { - _EditableImage({@required Widget child, @required this.node}) - : assert(node != null), - super(child: child); - - final EmbedNode node; - - @override - RenderEditableImage createRenderObject(BuildContext context) { - return new RenderEditableImage(node: node); - } - - @override - void updateRenderObject( - BuildContext context, RenderEditableImage renderObject) { - renderObject..node = node; - } -} - -class RenderEditableImage extends RenderBox - with RenderObjectWithChildMixin, RenderProxyBoxMixin - implements RenderEditableBox { - static const kPaddingBottom = 24.0; - - RenderEditableImage({ - RenderImage child, - @required EmbedNode node, - }) : _node = node { - this.child = child; - } - - @override - EmbedNode get node => _node; - EmbedNode _node; - void set node(EmbedNode value) { - _node = value; - } - - // TODO: Customize caret height offset instead of adjusting here by 2px. - @override - double get preferredLineHeight => size.height - kPaddingBottom + 2.0; - - @override - SelectionOrder get selectionOrder => SelectionOrder.foreground; - - @override - TextSelection getLocalSelection(TextSelection documentSelection) { - if (!intersectsWithSelection(documentSelection)) return null; - - int nodeBase = node.documentOffset; - int nodeExtent = nodeBase + node.length; - int base = math.max(0, documentSelection.baseOffset - nodeBase); - int extent = - math.min(documentSelection.extentOffset, nodeExtent) - nodeBase; - return documentSelection.copyWith(baseOffset: base, extentOffset: extent); - } - - @override - List getEndpointsForSelection(TextSelection selection) { - TextSelection local = getLocalSelection(selection); - if (local.isCollapsed) { - final dx = local.extentOffset == 0 ? _childOffset.dx : size.width; - return [ - new ui.TextBox.fromLTRBD( - dx, 0.0, dx, size.height - kPaddingBottom, TextDirection.ltr), - ]; - } - - final rect = _childRect; - return [ - new ui.TextBox.fromLTRBD( - rect.left, rect.top, rect.left, rect.bottom, TextDirection.ltr), - new ui.TextBox.fromLTRBD( - rect.right, rect.top, rect.right, rect.bottom, TextDirection.ltr), - ]; - } - - @override - TextPosition getPositionForOffset(Offset offset) { - int position = _node.documentOffset; - - if (offset.dx > size.width / 2) { - position++; - } - return new TextPosition(offset: position); - } - - @override - TextRange getWordBoundary(TextPosition position) { - final start = _node.documentOffset; - return new TextRange(start: start, end: start + 1); - } - - @override - bool intersectsWithSelection(TextSelection selection) { - final int base = node.documentOffset; - final int extent = base + node.length; - return base <= selection.extentOffset && selection.baseOffset <= extent; - } - - @override - Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) { - final pos = position.offset - node.documentOffset; - Offset caretOffset = _childOffset - new Offset(kHorizontalPadding, 0.0); - if (pos == 1) { - caretOffset = caretOffset + - new Offset(_lastChildSize.width + kHorizontalPadding, 0.0); - } - return caretOffset; - } - - @override - void paintSelection(PaintingContext context, Offset offset, - TextSelection selection, Color selectionColor) { - final localSelection = getLocalSelection(selection); - assert(localSelection != null); - if (!localSelection.isCollapsed) { - final Paint paint = new Paint() - ..color = selectionColor - ..style = PaintingStyle.stroke - ..strokeWidth = 3.0; - final rect = new Rect.fromLTWH( - 0.0, 0.0, _lastChildSize.width, _lastChildSize.height); - context.canvas.drawRect(rect.shift(offset + _childOffset), paint); - } - } - - void paint(PaintingContext context, Offset offset) { - super.paint(context, offset + _childOffset); - } - - static const double kHorizontalPadding = 1.0; - - Size _lastChildSize; - - Offset get _childOffset { - final dx = (size.width - _lastChildSize.width) / 2 + kHorizontalPadding; - final dy = (size.height - _lastChildSize.height - kPaddingBottom) / 2; - return new Offset(dx, dy); - } - - Rect get _childRect { - return new Rect.fromLTWH(_childOffset.dx, _childOffset.dy, - _lastChildSize.width, _lastChildSize.height); - } - - @override - void performLayout() { - assert(constraints.hasBoundedWidth); - if (child != null) { - // Make constraints use 16:9 aspect ratio. - final width = constraints.maxWidth - kHorizontalPadding * 2; - final childConstraints = constraints.copyWith( - minWidth: 0.0, - maxWidth: width, - minHeight: 0.0, - maxHeight: (width * 9 / 16).floorToDouble(), - ); - child.layout(childConstraints, parentUsesSize: true); - _lastChildSize = child.size; - size = new Size( - constraints.maxWidth, _lastChildSize.height + kPaddingBottom); - } else { - performResize(); - } - } -} diff --git a/zefyr/lib/src/widgets/input.dart b/zefyr/lib/src/widgets/input.dart deleted file mode 100644 index d006d976..00000000 --- a/zefyr/lib/src/widgets/input.dart +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'package:flutter/services.dart'; -import 'package:flutter/src/foundation/diagnostics.dart'; -import 'package:flutter/widgets.dart'; -import 'package:zefyr/util.dart'; - -typedef RemoteValueChanged = Function( - int start, String deleted, String inserted, TextSelection selection); - -class InputConnectionController implements TextInputClient { - InputConnectionController(this.onValueChanged) - : assert(onValueChanged != null); - - // - // New public members - // - - final RemoteValueChanged onValueChanged; - - /// Returns `true` if there is open input connection. - bool get hasConnection => - _textInputConnection != null && _textInputConnection.attached; - - /// Opens or closes input connection based on the current state of - /// [focusNode] and [value]. - void openOrCloseConnection(FocusNode focusNode, TextEditingValue value) { - if (focusNode.hasFocus && focusNode.consumeKeyboardToken()) { - openConnection(value); - } else if (!focusNode.hasFocus) { - closeConnection(); - } - } - - void openConnection(TextEditingValue value) { - if (!hasConnection) { - _lastKnownRemoteTextEditingValue = value; - _textInputConnection = TextInput.attach( - this, - new TextInputConfiguration( - inputType: TextInputType.multiline, - obscureText: false, - autocorrect: true, - inputAction: TextInputAction.newline, - textCapitalization: TextCapitalization.sentences, - ), - )..setEditingState(value); - _sentRemoteValues.add(value); - } - _textInputConnection.show(); - } - - /// Closes input connection if it's currently open. Otherwise does nothing. - void closeConnection() { - if (hasConnection) { - _textInputConnection.close(); - _textInputConnection = null; - _lastKnownRemoteTextEditingValue = null; - _sentRemoteValues.clear(); - } - } - - /// Updates remote value based on current state of [document] and - /// [selection]. - /// - /// This method may not actually send an update to native side if it thinks - /// remote value is up to date or identical. - void updateRemoteValue(TextEditingValue value) { - if (!hasConnection) return; - - // Since we don't keep track of composing range in value provided by - // ZefyrController we need to add it here manually before comparing - // with the last known remote value. - // It is important to prevent excessive remote updates as it can cause - // race conditions. - final actualValue = value.copyWith( - composing: _lastKnownRemoteTextEditingValue.composing, - ); - - if (actualValue == _lastKnownRemoteTextEditingValue) return; - - bool shouldRemember = value.text != _lastKnownRemoteTextEditingValue.text; - _lastKnownRemoteTextEditingValue = actualValue; - _textInputConnection.setEditingState(actualValue); - if (shouldRemember) { - // Only keep track if text changed (selection changes are not relevant) - _sentRemoteValues.add(actualValue); - } - } - - // - // Overridden members - // - - @override - void performAction(TextInputAction action) { - // no-op - } - - @override - void updateEditingValue(TextEditingValue value) { - if (_sentRemoteValues.contains(value)) { - /// There is a race condition in Flutter text input plugin where sending - /// updates to native side too often results in broken behavior. - /// TextInputConnection.setEditingValue is an async call to native side. - /// For each such call native side _always_ sends update which triggers - /// this method (updateEditingValue) with the same value we've sent it. - /// If multiple calls to setEditingValue happen too fast and we only - /// track the last sent value then there is no way for us to filter out - /// automatic callbacks from native side. - /// Therefore we have to keep track of all values we send to the native - /// side and when we see this same value appear here we skip it. - /// This is fragile but it's probably the only available option. - _sentRemoteValues.remove(value); - return; - } - - if (_lastKnownRemoteTextEditingValue == value) { - // There is no difference between this value and the last known value. - return; - } - - // Check if only composing range changed. - if (_lastKnownRemoteTextEditingValue.text == value.text && - _lastKnownRemoteTextEditingValue.selection == value.selection) { - // This update only modifies composing range. Since we don't keep track - // of composing range in Zefyr we just need to update last known value - // here. - // Note: this check fixes an issue on Android when it sends - // composing updates separately from regular changes for text and - // selection. - _lastKnownRemoteTextEditingValue = value; - return; - } - - // Note Flutter (unintentionally?) silences errors occurred during - // text input update, so we have to report it ourselves. - // For more details see https://github.com/flutter/flutter/issues/19191 - // TODO: remove try-catch when/if Flutter stops silencing these errors. - try { - final effectiveLastKnownValue = _lastKnownRemoteTextEditingValue; - _lastKnownRemoteTextEditingValue = value; - final oldText = effectiveLastKnownValue.text; - final text = value.text; - final cursorPosition = value.selection.extentOffset; - final diff = fastDiff(oldText, text, cursorPosition); - onValueChanged(diff.start, diff.deleted, diff.inserted, value.selection); - } catch (e, trace) { - FlutterError.reportError(new FlutterErrorDetails( - exception: e, - stack: trace, - library: 'Zefyr', -// context: 'while updating editing value', - context: new TextNode() - )); - rethrow; - } - } - - - // - // Private members - // - - final List _sentRemoteValues = []; - TextInputConnection _textInputConnection; - TextEditingValue _lastKnownRemoteTextEditingValue; - - @override - void updateFloatingCursor(RawFloatingCursorPoint point) { - // TODO: implement updateFloatingCursor - } -} - -class TextNode extends DiagnosticsNode{ - @override - List getChildren() { - // TODO: implement getChildren - return null; - } - - @override - List getProperties() { - // TODO: implement getProperties - return null; - } - - @override - String toDescription({TextTreeConfiguration parentConfiguration}) { - // TODO: implement toDescription - return null; - } - - @override - Object get value => 'while updating editing value'; - -} diff --git a/zefyr/lib/src/widgets/list.dart b/zefyr/lib/src/widgets/list.dart deleted file mode 100644 index a3479e58..00000000 --- a/zefyr/lib/src/widgets/list.dart +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'package:flutter/material.dart'; -import 'package:notus/notus.dart'; - -import 'common.dart'; -import 'paragraph.dart'; -import 'theme.dart'; - -/// Represents number lists and bullet lists in a Zefyr editor. -class ZefyrList extends StatelessWidget { - const ZefyrList({Key key, @required this.node}) : super(key: key); - - final BlockNode node; - - @override - Widget build(BuildContext context) { - final theme = ZefyrTheme.of(context); - List items = []; - int index = 1; - for (var line in node.children) { - items.add(_buildItem(line, index)); - index++; - } - - final isNumberList = - node.style.get(NotusAttribute.block) == NotusAttribute.block.numberList; - EdgeInsets padding = isNumberList - ? theme.blockTheme.numberList.padding - : theme.blockTheme.bulletList.padding; - padding = padding.copyWith(left: theme.indentSize); - - return new Padding( - padding: padding, - child: new Column(children: items), - ); - } - - Widget _buildItem(Node node, int index) { - LineNode line = node; - return new ZefyrListItem(index: index, node: line); - } -} - -/// An item in a [ZefyrList]. -class ZefyrListItem extends StatelessWidget { - ZefyrListItem({Key key, this.index, this.node}) : super(key: key); - - final int index; - final LineNode node; - - @override - Widget build(BuildContext context) { - final BlockNode block = node.parent; - final style = block.style.get(NotusAttribute.block); - final theme = ZefyrTheme.of(context); - final bulletText = - (style == NotusAttribute.block.bulletList) ? '•' : '$index.'; - - TextStyle textStyle; - Widget content; - EdgeInsets padding; - - if (node.style.contains(NotusAttribute.heading)) { - final headingTheme = ZefyrHeading.themeOf(node, context); - textStyle = headingTheme.textStyle; - padding = headingTheme.padding; - content = new ZefyrHeading(node: node); - } else { - textStyle = theme.paragraphTheme.textStyle; - content = new RawZefyrLine(node: node, style: textStyle); - } - - Widget bullet = - SizedBox(width: 24.0, child: Text(bulletText, style: textStyle)); - if (padding != null) { - bullet = Padding(padding: padding, child: bullet); - } - - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [bullet, Expanded(child: content)], - ); - } -} diff --git a/zefyr/lib/src/widgets/paragraph.dart b/zefyr/lib/src/widgets/paragraph.dart deleted file mode 100644 index 222b5bae..00000000 --- a/zefyr/lib/src/widgets/paragraph.dart +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'package:flutter/material.dart'; -import 'package:notus/notus.dart'; - -import 'common.dart'; -import 'theme.dart'; - -/// Represents regular paragraph line in a Zefyr editor. -class ZefyrParagraph extends StatelessWidget { - ZefyrParagraph({Key key, @required this.node, this.blockStyle}) - : super(key: key); - - final LineNode node; - final TextStyle blockStyle; - - @override - Widget build(BuildContext context) { - final theme = ZefyrTheme.of(context); - TextStyle style = theme.paragraphTheme.textStyle; - if (blockStyle != null) { - style = style.merge(blockStyle); - } - return new RawZefyrLine( - node: node, - style: style, - padding: theme.paragraphTheme.padding, - ); - } -} - -/// Represents heading-styled line in [ZefyrEditor]. -class ZefyrHeading extends StatelessWidget { - ZefyrHeading({Key key, @required this.node, this.blockStyle}) - : assert(node.style.contains(NotusAttribute.heading)), - super(key: key); - - final LineNode node; - final TextStyle blockStyle; - - @override - Widget build(BuildContext context) { - final theme = themeOf(node, context); - TextStyle style = theme.textStyle; - if (blockStyle != null) { - style = style.merge(blockStyle); - } - return new RawZefyrLine( - node: node, - style: style, - padding: theme.padding, - ); - } - - static StyleTheme themeOf(LineNode node, BuildContext context) { - final theme = ZefyrTheme.of(context); - final style = node.style.get(NotusAttribute.heading); - if (style == NotusAttribute.heading.level1) { - return theme.headingTheme.level1; - } else if (style == NotusAttribute.heading.level2) { - return theme.headingTheme.level2; - } else if (style == NotusAttribute.heading.level3) { - return theme.headingTheme.level3; - } - throw new UnimplementedError('Unsupported heading style $style'); - } -} diff --git a/zefyr/lib/src/widgets/quote.dart b/zefyr/lib/src/widgets/quote.dart deleted file mode 100644 index a9eacd13..00000000 --- a/zefyr/lib/src/widgets/quote.dart +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'package:flutter/material.dart'; -import 'package:notus/notus.dart'; - -import 'paragraph.dart'; -import 'theme.dart'; - -/// Represents a quote block in a Zefyr editor. -class ZefyrQuote extends StatelessWidget { - const ZefyrQuote({Key key, @required this.node}) : super(key: key); - - final BlockNode node; - - @override - Widget build(BuildContext context) { - final theme = ZefyrTheme.of(context); - final style = theme.blockTheme.quote.textStyle; - List items = []; - for (var line in node.children) { - items.add(_buildLine(line, style, theme.indentSize)); - } - - return Padding( - padding: theme.blockTheme.quote.padding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: items, - ), - ); - } - - Widget _buildLine(Node node, TextStyle blockStyle, double indentSize) { - LineNode line = node; - - Widget content; - if (line.style.contains(NotusAttribute.heading)) { - content = new ZefyrHeading(node: line, blockStyle: blockStyle); - } else { - content = new ZefyrParagraph(node: line, blockStyle: blockStyle); - } - - final row = Row(children: [Expanded(child: content)]); - return Container( - decoration: BoxDecoration( - border: Border( - left: BorderSide(width: 4.0, color: Colors.grey.shade300), - ), - ), - padding: EdgeInsets.only(left: indentSize), - child: row, - ); - } -} diff --git a/zefyr/lib/src/widgets/render_context.dart b/zefyr/lib/src/widgets/render_context.dart deleted file mode 100644 index 1e88aa53..00000000 --- a/zefyr/lib/src/widgets/render_context.dart +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; - -import 'editable_box.dart'; - -/// Registry of all [RenderEditableProxyBox]es inside a [ZefyrEditableText]. -/// -/// Provides access to all currently active [RenderEditableProxyBox] -/// instances of a [ZefyrEditableText]. -/// -/// Use [boxForTextOffset] or [boxForGlobalPoint] to retrieve a -/// specific box. -/// -/// The [addBox], [removeBox] and [markDirty] are intended to be -/// only used by [RenderEditableProxyBox] objects to register with a rendering -/// context. -/// -/// ### Life cycle details -/// -/// When a box object is attached to rendering pipeline it registers -/// itself with a render scope by calling [addBox]. At this point the context -/// treats this object as "dirty" and query methods like [boxForTextOffset] -/// still exclude this object from returned results. -/// -/// When this box considers itself initialized it calls [markDirty] with -/// `isDirty` set to `false` which activates it. At this point query methods -/// include this object in results. -/// -/// When a box is rebuilt it may deactivate itself by calling [markDirty] -/// again. -/// -/// When a box is detached from rendering pipeline it unregisters -/// itself by calling [removeBox]. -class ZefyrRenderContext extends ChangeNotifier { - final Set _dirtyBoxes = new Set(); - final Set _activeBoxes = new Set(); - - Set get dirty => _dirtyBoxes; - Set get active => _activeBoxes; - - bool _disposed = false; - - /// Adds [box] to this context. The box is considered "dirty" at - /// this point and is not included in query results of `boxFor*` - /// methods. - void addBox(RenderEditableProxyBox box) { - assert(!_disposed); - _dirtyBoxes.add(box); - } - - /// Removes [box] from this render context. - void removeBox(RenderEditableProxyBox box) { - assert(!_disposed); - _dirtyBoxes.remove(box); - _activeBoxes.remove(box); - notifyListeners(); - } - - void markDirty(RenderEditableProxyBox box, bool isDirty) { - assert(!_disposed); - - var collection = isDirty ? _dirtyBoxes : _activeBoxes; - if (collection.contains(box)) return; - - if (isDirty) { - _activeBoxes.remove(box); - _dirtyBoxes.add(box); - } else { - _dirtyBoxes.remove(box); - _activeBoxes.add(box); - } - notifyListeners(); - } - - /// Returns box containing character at specified document [offset]. - RenderEditableProxyBox boxForTextOffset(int offset) { - assert(!_disposed); - return _activeBoxes.firstWhere( - (p) => p.node.containsOffset(offset), - orElse: _null, - ); - } - - /// Returns box located at specified global [point] on the screen or - /// `null`. - RenderEditableProxyBox boxForGlobalPoint(Offset point) { - assert(!_disposed); - return _activeBoxes.firstWhere((p) { - final localPoint = p.globalToLocal(point); - return p.size.contains(localPoint); - }, orElse: _null); - } - - /// Returns closest render box to the specified global [point]. - /// - /// If [point] is inside of one of active render boxes that box is returned. - /// If no box found this method checks if [point] is to the left or to the right - /// side of a box, e.g. if vertical offset of this point is inside of one of - /// the active boxes. If it is then that box is returned. If not then this - /// method picks a box with shortest vertical distance to this [point]. - RenderEditableProxyBox closestBoxForGlobalPoint(Offset point) { - assert(!_disposed); - if (_activeBoxes.isEmpty) return null; - RenderEditableProxyBox box = boxForGlobalPoint(point); - if (box != null) return box; - - box = _activeBoxes.firstWhere((p) { - final localPoint = p.globalToLocal(point); - return (localPoint.dy >= 0 && localPoint.dy < p.size.height); - }, orElse: _null); - if (box != null) return box; - - box = _activeBoxes.map((p) { - final localPoint = p.globalToLocal(point); - final distance = localPoint.dy - p.size.height; - return new MapEntry(distance.abs(), p); - }).reduce((a, b) { - return (a.key <= b.key) ? a : b; - }).value; - - return box; - } - - static Null _null() => null; - - @override - void dispose() { - _disposed = true; - _activeBoxes.clear(); - _dirtyBoxes.clear(); - super.dispose(); - } - - @override - void notifyListeners() { - /// Ensures listeners are not notified during rendering phase where they - /// cannot react by updating their state or rebuilding. - WidgetsBinding.instance.addPostFrameCallback((_) { - if (_disposed) return; - super.notifyListeners(); - }); - } -} diff --git a/zefyr/lib/src/widgets/rich_text.dart b/zefyr/lib/src/widgets/rich_text.dart deleted file mode 100644 index f2eff9c9..00000000 --- a/zefyr/lib/src/widgets/rich_text.dart +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'dart:math' as math; -import 'dart:ui' as ui; - -import 'package:flutter/rendering.dart'; -import 'package:flutter/widgets.dart'; -import 'package:notus/notus.dart'; - -import 'caret.dart'; -import 'editable_box.dart'; - -/// Represents single paragraph of Zefyr rich-text. -class ZefyrRichText extends LeafRenderObjectWidget { - ZefyrRichText({ - @required this.node, - @required this.text, - }) : assert(node != null && text != null); - - final LineNode node; - final TextSpan text; - - @override - RenderObject createRenderObject(BuildContext context) { - return new RenderZefyrParagraph( - text, - node: node, - textDirection: Directionality.of(context), - ); - } - - @override - void updateRenderObject( - BuildContext context, RenderZefyrParagraph renderObject) { - renderObject - ..text = text - ..node = node; - } -} - -class RenderZefyrParagraph extends RenderParagraph - implements RenderEditableBox { - RenderZefyrParagraph( - TextSpan text, { - @required LineNode node, - TextAlign textAlign: TextAlign.start, - @required TextDirection textDirection, - bool softWrap: true, - TextOverflow overflow: TextOverflow.clip, - double textScaleFactor: 1.0, - int maxLines, - }) : _node = node, - _prototypePainter = new TextPainter( - text: new TextSpan(text: '.', style: text.style), - textAlign: textAlign, - textDirection: textDirection, - textScaleFactor: textScaleFactor, - ), - super( - text, - textAlign: textAlign, - textDirection: textDirection, - softWrap: softWrap, - overflow: overflow, - textScaleFactor: textScaleFactor, - maxLines: maxLines, - ); - - LineNode get node => _node; - LineNode _node; - void set node(LineNode value) { - _node = value; - } - - @override - double get preferredLineHeight => _prototypePainter.height; - - @override - SelectionOrder get selectionOrder => SelectionOrder.background; - - @override - TextSelection getLocalSelection(TextSelection documentSelection) { - if (!intersectsWithSelection(documentSelection)) return null; - - int nodeBase = node.documentOffset; - int nodeExtent = nodeBase + node.length; - int base = math.max(0, documentSelection.baseOffset - nodeBase); - int extent = - math.min(documentSelection.extentOffset, nodeExtent) - nodeBase; - return documentSelection.copyWith(baseOffset: base, extentOffset: extent); - } - - @override - TextPosition getPositionForOffset(Offset offset) { - final position = super.getPositionForOffset(offset); - return new TextPosition( - offset: _node.documentOffset + position.offset, - affinity: position.affinity, - ); - } - - @override - TextRange getWordBoundary(TextPosition position) { - final localPosition = new TextPosition( - offset: position.offset - _node.documentOffset, - affinity: position.affinity, - ); - final localRange = super.getWordBoundary(localPosition); - return new TextRange( - start: _node.documentOffset + localRange.start, - end: _node.documentOffset + localRange.end, - ); - } - - @override - Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) { - final localPosition = new TextPosition( - offset: position.offset - _node.documentOffset, - affinity: position.affinity, - ); - return super.getOffsetForCaret(localPosition, caretPrototype); - } - - // This method works around some issues in getBoxesForSelection and handles - // edge-case with our TextSpan objects not having last line-break character. - @override - List getEndpointsForSelection(TextSelection selection) { - TextSelection local = getLocalSelection(selection); - if (local.isCollapsed) { - final caret = CursorPainter.buildPrototype(preferredLineHeight); - final offset = getOffsetForCaret(local.extent, caret); - return [ - new ui.TextBox.fromLTRBD( - offset.dx, - offset.dy, - offset.dx, - offset.dy + caret.height, - TextDirection.ltr, - ) - ]; - } - - int isBaseShifted = 0; - bool isExtentShifted = false; - if (local.baseOffset == node.length - 1 && local.baseOffset > 0) { - // Since we exclude last line-break from rendered TextSpan we have to - // handle end-of-line selection explicitly. - local = local.copyWith(baseOffset: local.baseOffset - 1); - isBaseShifted = -1; - } else if (local.baseOffset == 0 && local.isCollapsed) { - // This takes care of beginning of line position. - local = local.copyWith(baseOffset: local.baseOffset + 1); - isBaseShifted = 1; - } - if (text.codeUnitAt(local.extentOffset - 1) == 0xA) { - // This takes care of the rest end-of-line scenarios, where there are - // actually line-breaks in the TextSpan (e.g. in code blocks). - local = local.copyWith(extentOffset: local.extentOffset + 1); - isExtentShifted = true; - } - final result = getBoxesForSelection(local).toList(); - if (isBaseShifted != 0) { - final box = result.first; - final dx = isBaseShifted == -1 ? box.right : box.left; - result.removeAt(0); - result.insert(0, - new ui.TextBox.fromLTRBD(dx, box.top, dx, box.bottom, box.direction)); - } - if (isExtentShifted) { - final box = result.last; - result.removeLast; - result.add(new ui.TextBox.fromLTRBD( - box.left, box.top, box.left, box.bottom, box.direction)); - } - return result; - } - - @override - void set text(InlineSpan value) { - _prototypePainter.text = new TextSpan(text: '.', style: value.style); - _selectionRects = null; - super.text = value; - } - - @override - void performLayout() { - super.performLayout(); - _prototypePainter.layout( - minWidth: constraints.minWidth, maxWidth: constraints.maxWidth); - } - - @override - void paint(PaintingContext context, Offset offset) { - super.paint(context, offset); - } - - final TextPainter _prototypePainter; - List _selectionRects; - - /// Returns `true` if this paragraph intersects with document [selection]. - @override - bool intersectsWithSelection(TextSelection selection) { - final int base = node.documentOffset; - final int extent = base + node.length; - return base <= selection.extentOffset && selection.baseOffset <= extent; - } - - TextSelection _lastPaintedSelection; - @override - void paintSelection(PaintingContext context, Offset offset, - TextSelection selection, Color selectionColor) { - if (_lastPaintedSelection != selection) { - _selectionRects = null; - } - _selectionRects ??= getBoxesForSelection(getLocalSelection(selection)); - final Paint paint = new Paint()..color = selectionColor; - for (ui.TextBox box in _selectionRects) { - context.canvas.drawRect(box.toRect().shift(offset), paint); - } - _lastPaintedSelection = selection; - } -} diff --git a/zefyr/lib/src/widgets/scaffold.dart b/zefyr/lib/src/widgets/scaffold.dart deleted file mode 100644 index f711070e..00000000 --- a/zefyr/lib/src/widgets/scaffold.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter/material.dart'; - -class ZefyrScaffold extends StatefulWidget { - final Widget child; - - const ZefyrScaffold({Key key, this.child}) : super(key: key); - - static ZefyrScaffoldState of(BuildContext context) { - final _ZefyrScaffoldAccess widget = - context.inheritFromWidgetOfExactType(_ZefyrScaffoldAccess); - return widget.scaffold; - } - - @override - ZefyrScaffoldState createState() => ZefyrScaffoldState(); -} - -class ZefyrScaffoldState extends State { - WidgetBuilder _toolbarBuilder; - - void showToolbar(WidgetBuilder builder) { - setState(() { - _toolbarBuilder = builder; - }); - } - - void hideToolbar() { - if (_toolbarBuilder != null) { - setState(() { - _toolbarBuilder = null; - }); - } - } - - @override - Widget build(BuildContext context) { - final toolbar = - (_toolbarBuilder == null) ? Container() : _toolbarBuilder(context); - return _ZefyrScaffoldAccess( - scaffold: this, - child: Column( - children: [ - Expanded(child: widget.child), - toolbar, - ], - ), - ); - } -} - -class _ZefyrScaffoldAccess extends InheritedWidget { - final ZefyrScaffoldState scaffold; - - _ZefyrScaffoldAccess({Widget child, this.scaffold}) : super(child: child); - - @override - bool updateShouldNotify(_ZefyrScaffoldAccess oldWidget) { - return oldWidget.scaffold != scaffold; - } -} diff --git a/zefyr/lib/src/widgets/scope.dart b/zefyr/lib/src/widgets/scope.dart deleted file mode 100644 index 2853a8a2..00000000 --- a/zefyr/lib/src/widgets/scope.dart +++ /dev/null @@ -1,232 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:notus/notus.dart'; - -import 'controller.dart'; -import 'cursor_timer.dart'; -import 'editor.dart'; -import 'image.dart'; -import 'render_context.dart'; -import 'view.dart'; - -/// Provides access to shared state of [ZefyrEditor] or [ZefyrView]. -/// -/// A scope object can be created by an editable widget like [ZefyrEditor] in -/// which case it provides access to editing state, including focus nodes, -/// selection and such. Editable scope can be created using -/// [ZefyrScope.editable] constructor. -/// -/// If a scope object is created by a view-only widget like [ZefyrView] then -/// it only provides access to [imageDelegate]. -/// -/// Can be retrieved using [ZefyrScope.of]. -class ZefyrScope extends ChangeNotifier { - /// Creates a view-only scope. - /// - /// Normally used in [ZefyrView]. - ZefyrScope.view({@required ZefyrImageDelegate imageDelegate}) - : assert(imageDelegate != null), - isEditable = false, - _imageDelegate = imageDelegate; - - /// Creates editable scope. - /// - /// Normally used in [ZefyrEditor]. - ZefyrScope.editable({ - @required ZefyrController controller, - @required ZefyrImageDelegate imageDelegate, - @required FocusNode focusNode, - @required FocusScopeNode focusScope, - }) : assert(controller != null), - assert(imageDelegate != null), - assert(focusNode != null), - assert(focusScope != null), - isEditable = true, - _controller = controller, - _imageDelegate = imageDelegate, - _focusNode = focusNode, - _focusScope = focusScope, - _cursorTimer = CursorTimer(), - _renderContext = ZefyrRenderContext() { - _selectionStyle = _controller.getSelectionStyle(); - _selection = _controller.selection; - _controller.addListener(_handleControllerChange); - _focusNode.addListener(_handleFocusChange); - } - - static ZefyrScope of(BuildContext context) { - final ZefyrScopeAccess widget = - context.inheritFromWidgetOfExactType(ZefyrScopeAccess); - return widget.scope; - } - - ZefyrImageDelegate _imageDelegate; - ZefyrImageDelegate get imageDelegate => _imageDelegate; - set imageDelegate(ZefyrImageDelegate value) { - assert(value != null); - if (_imageDelegate != value) { - _imageDelegate = value; - notifyListeners(); - } - } - - ZefyrController _controller; - ZefyrController get controller => _controller; - set controller(ZefyrController value) { - assert(isEditable && value != null); - if (_controller != value) { - _controller.removeListener(_handleControllerChange); - _controller = value; - _selectionStyle = _controller.getSelectionStyle(); - _selection = _controller.selection; - _controller.addListener(_handleControllerChange); - notifyListeners(); - } - } - - FocusNode _focusNode; - FocusNode get focusNode => _focusNode; - set focusNode(FocusNode value) { - assert(isEditable && value != null); - if (_focusNode != value) { - _focusNode.removeListener(_handleFocusChange); - _focusNode = value; - _focusNode.addListener(_handleFocusChange); - notifyListeners(); - } - } - - FocusScopeNode _focusScope; - FocusScopeNode get focusScope => _focusScope; - set focusScope(FocusScopeNode value) { - assert(isEditable && value != null); - if (_focusScope != value) { - _focusScope = value; - } - } - - CursorTimer _cursorTimer; - CursorTimer get cursorTimer => _cursorTimer; - ValueNotifier get showCursor => cursorTimer.value; - - ZefyrRenderContext _renderContext; - ZefyrRenderContext get renderContext => _renderContext; - - NotusStyle get selectionStyle => _selectionStyle; - NotusStyle _selectionStyle; - TextSelection get selection => _selection; - TextSelection _selection; - - bool _disposed = false; - FocusNode _toolbarFocusNode; - - /// Whether this scope is backed by editable Zefyr widgets or read-only view. - /// - /// Returns `true` if this scope provides Zefyr interface that allows editing - /// (e.g. created by [ZefyrEditor]). Returns `false` if this scope provides - /// read-only view (e.g. created by [ZefyrView]). - /// - /// Editable scope provides access to corresponding [controller], [focusNode], - /// [focusScope], [showCursor], [renderContext] and other shared objects. For - /// non-editable scopes these are set to `null`. You can still access - /// objects which are not dependent on editing flow, e.g. [imageDelegate]. - final bool isEditable; - - set toolbarFocusNode(FocusNode node) { - assert(isEditable); - assert(!_disposed || node == null); - if (_toolbarFocusNode != node) { - _toolbarFocusNode?.removeListener(_handleFocusChange); - _toolbarFocusNode = node; - _toolbarFocusNode?.addListener(_handleFocusChange); - // We do not notify listeners here because it will happen when - // focus state changes, see [_handleFocusChange]. - } - } - - FocusOwner get focusOwner { - assert(isEditable); - assert(!_disposed); - if (_focusNode.hasFocus) { - return FocusOwner.editor; - } else if (_toolbarFocusNode?.hasFocus == true) { - return FocusOwner.toolbar; - } else { - return FocusOwner.none; - } - } - - void updateSelection(TextSelection value, - {ChangeSource source: ChangeSource.remote}) { - assert(isEditable); - assert(!_disposed); - _controller.updateSelection(value, source: source); - } - - void formatSelection(NotusAttribute value) { - assert(isEditable); - assert(!_disposed); - _controller.formatSelection(value); - } - - void focus() { - assert(isEditable); - assert(!_disposed); - _focusScope.requestFocus(_focusNode); - } - - void hideKeyboard() { - assert(isEditable); - assert(!_disposed); - _focusNode.unfocus(); - } - - @override - void dispose() { - assert(!_disposed); - _controller?.removeListener(_handleControllerChange); - _focusNode?.removeListener(_handleFocusChange); - _disposed = true; - super.dispose(); - } - - void _handleControllerChange() { - assert(!_disposed); - final attrs = _controller.getSelectionStyle(); - final selection = _controller.selection; - if (_selectionStyle != attrs || _selection != selection) { - _selectionStyle = attrs; - _selection = selection; - notifyListeners(); - } - } - - void _handleFocusChange() { - assert(!_disposed); - if (focusOwner == FocusOwner.none && !_selection.isCollapsed) { - // Collapse selection if there is nothing focused. - _controller.updateSelection(_selection.copyWith( - baseOffset: _selection.extentOffset, - extentOffset: _selection.extentOffset, - )); - } - notifyListeners(); - } - - @override - String toString() { - return '$ZefyrScope#${shortHash(this)}'; - } -} - -class ZefyrScopeAccess extends InheritedWidget { - final ZefyrScope scope; - - ZefyrScopeAccess({Key key, @required this.scope, @required Widget child}) - : super(key: key, child: child); - - @override - bool updateShouldNotify(ZefyrScopeAccess oldWidget) { - return scope != oldWidget.scope; - } -} diff --git a/zefyr/lib/src/widgets/selection.dart b/zefyr/lib/src/widgets/selection.dart deleted file mode 100644 index febbd661..00000000 --- a/zefyr/lib/src/widgets/selection.dart +++ /dev/null @@ -1,512 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'dart:ui' as ui; - -import 'package:flutter/material.dart'; -import 'package:flutter/rendering.dart'; -import 'package:notus/notus.dart'; -import 'package:zefyr/util.dart'; - -import 'controller.dart'; -import 'editable_box.dart'; -import 'scope.dart'; - -RenderEditableBox _getEditableBox(HitTestResult result) { - for (var entry in result.path) { - if (entry.target is RenderEditableBox) { - return entry.target as RenderEditableBox; - } - } - return null; -} - -/// Selection overlay controls selection handles and other gestures. -class ZefyrSelectionOverlay extends StatefulWidget { - const ZefyrSelectionOverlay({ - Key key, - @required this.controller, - @required this.controls, - @required this.overlay, - }) : super(key: key); - - final ZefyrController controller; - final TextSelectionControls controls; - final OverlayState overlay; - - @override - _ZefyrSelectionOverlayState createState() => - new _ZefyrSelectionOverlayState(); -} - -class _ZefyrSelectionOverlayState extends State - implements TextSelectionDelegate { - @override - TextEditingValue get textEditingValue => - widget.controller.plainTextEditingValue; - - set textEditingValue(TextEditingValue value) { - final cursorPosition = value.selection.extentOffset; - final oldText = widget.controller.document.toPlainText(); - final newText = value.text; - final diff = fastDiff(oldText, newText, cursorPosition); - widget.controller.replaceText( - diff.start, diff.deleted.length, diff.inserted, - selection: value.selection); - } - - @override - void bringIntoView(ui.TextPosition position) { - // TODO: implement bringIntoView - } - - bool get isToolbarVisible => _toolbar != null; - bool get isToolbarHidden => _toolbar == null; - - @override - void hideToolbar() { - _didCaretTap = false; // reset double tap. - _toolbar?.remove(); - _toolbar = null; - _toolbarController.stop(); - } - - void showToolbar() { - final scope = ZefyrScope.of(context); - assert(scope != null); - final toolbarOpacity = _toolbarController.view; - _toolbar = new OverlayEntry( - builder: (context) => new FadeTransition( - opacity: toolbarOpacity, - child: new _SelectionToolbar( - scope: scope, - controls: widget.controls, - delegate: this, - ), - ), - ); - widget.overlay.insert(_toolbar); - _toolbarController.forward(from: 0.0); - } - - // - // Overridden members of State - // - - @override - void initState() { - super.initState(); - _toolbarController = new AnimationController( - duration: _kFadeDuration, vsync: widget.overlay); - } - - static const Duration _kFadeDuration = const Duration(milliseconds: 150); - - @override - void didUpdateWidget(ZefyrSelectionOverlay oldWidget) { - super.didUpdateWidget(oldWidget); - if (oldWidget.overlay != widget.overlay) { - hideToolbar(); - _toolbarController.dispose(); - _toolbarController = new AnimationController( - duration: _kFadeDuration, vsync: widget.overlay); - } - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final editor = ZefyrScope.of(context); - if (_editor != editor) { - _editor?.removeListener(_handleChange); - _editor = editor; - _editor.addListener(_handleChange); - _selection = _editor.selection; - _focusOwner = _editor.focusOwner; - } - } - - @override - void dispose() { - _editor.removeListener(_handleChange); - hideToolbar(); - _toolbarController.dispose(); - _toolbarController = null; - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final overlay = new GestureDetector( - behavior: HitTestBehavior.translucent, - onTapDown: _handleTapDown, - onTap: _handleTap, - onTapCancel: _handleTapCancel, - onLongPress: _handleLongPress, - child: new Stack( - fit: StackFit.expand, - children: [ - new SelectionHandleDriver( - position: _SelectionHandlePosition.base, - controls: widget.controls, - ), - new SelectionHandleDriver( - position: _SelectionHandlePosition.extent, - controls: widget.controls, - ), - ], - ), - ); - return new Container(child: overlay); - } - - // - // Private members - // - - /// Global position of last TapDown event. - Offset _lastTapDownPosition; - - /// Global position of last TapDown which is potentially a long press. - Offset _longPressPosition; - - OverlayEntry _toolbar; - AnimationController _toolbarController; - - ZefyrScope _editor; - TextSelection _selection; - FocusOwner _focusOwner; - - bool _didCaretTap = false; - - void _handleChange() { - if (_selection != _editor.selection || _focusOwner != _editor.focusOwner) { - _updateToolbar(); - } - } - - void _updateToolbar() { - if (!mounted) { - return; - } - - final selection = _editor.selection; - final focusOwner = _editor.focusOwner; - setState(() { - if (focusOwner != FocusOwner.editor) { - hideToolbar(); - } else { - if (_selection != selection) { - if (selection.isCollapsed && isToolbarVisible) hideToolbar(); - _toolbar?.markNeedsBuild(); - if (!selection.isCollapsed && isToolbarHidden) showToolbar(); - } else { - if (!selection.isCollapsed && isToolbarHidden) { - showToolbar(); - } else if (isToolbarVisible) { - _toolbar?.markNeedsBuild(); - } - } - } - _selection = selection; - _focusOwner = focusOwner; - }); - } - - void _handleTapDown(TapDownDetails details) { - _lastTapDownPosition = details.globalPosition; - } - - void _handleTapCancel() { - // longPress arrives after tapCancel, so remember the tap position. - _longPressPosition = _lastTapDownPosition; - _lastTapDownPosition = null; - } - - void _handleTap() { - assert(_lastTapDownPosition != null); - final globalPoint = _lastTapDownPosition; - _lastTapDownPosition = null; - HitTestResult result = new HitTestResult(); - WidgetsBinding.instance.hitTest(result, globalPoint); - - RenderEditableProxyBox box = _getEditableBox(result); - if (box == null) { - box = _editor.renderContext.closestBoxForGlobalPoint(globalPoint); - } - if (box == null) return null; - - final localPoint = box.globalToLocal(globalPoint); - final position = box.getPositionForOffset(localPoint); - final selection = new TextSelection.collapsed( - offset: position.offset, - affinity: position.affinity, - ); - if (_didCaretTap && _selection == selection) { - _didCaretTap = false; - if (isToolbarVisible) { - hideToolbar(); - } else { - showToolbar(); - } - } else { - _didCaretTap = true; - } - widget.controller.updateSelection(selection, source: ChangeSource.local); - } - - void _handleLongPress() { - final Offset globalPoint = _longPressPosition; - _longPressPosition = null; - HitTestResult result = new HitTestResult(); - WidgetsBinding.instance.hitTest(result, globalPoint); - final box = _getEditableBox(result); - if (box == null) { - return; - } - final localPoint = box.globalToLocal(globalPoint); - final position = box.getPositionForOffset(localPoint); - final word = box.getWordBoundary(position); - final selection = new TextSelection( - baseOffset: word.start, - extentOffset: word.end, - ); - widget.controller.updateSelection(selection, source: ChangeSource.local); - } - - @override - bool get copyEnabled => true; - - @override - bool get cutEnabled => true; - - @override - bool get pasteEnabled => true; - - @override - bool get selectAllEnabled => true; -} - -enum _SelectionHandlePosition { base, extent } - -class SelectionHandleDriver extends StatefulWidget { - const SelectionHandleDriver({ - Key key, - @required this.position, - @required this.controls, - }) : super(key: key); - - final _SelectionHandlePosition position; - final TextSelectionControls controls; - - @override - _SelectionHandleDriverState createState() => - new _SelectionHandleDriverState(); -} - -class _SelectionHandleDriverState extends State { - ZefyrScope _scope; - - /// Current document selection. - TextSelection get selection => _selection; - TextSelection _selection; - - /// Returns `true` if this handle is located at the baseOffset of selection. - bool get isBaseHandle => widget.position == _SelectionHandlePosition.base; - - /// Character offset of this handle in the document. - /// - /// For base handle this equals to [TextSelection.baseOffset] and for - /// extent handle - [TextSelection.extentOffset]. - int get documentOffset => - isBaseHandle ? selection.baseOffset : selection.extentOffset; - - /// Position in pixels of this selection handle within its paragraph [block]. - Offset getPosition(RenderEditableBox block) { - if (block == null) return null; - - final localSelection = block.getLocalSelection(selection); - assert(localSelection != null); - - final boxes = block.getEndpointsForSelection(selection); - assert(boxes.isNotEmpty, 'Got empty boxes for selection ${selection}'); - - final box = isBaseHandle ? boxes.first : boxes.last; - final dx = isBaseHandle ? box.start : box.end; - return new Offset(dx, box.bottom); - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final scope = ZefyrScope.of(context); - if (_scope != scope) { - _scope?.removeListener(_handleScopeChange); - _scope = scope; - _scope.addListener(_handleScopeChange); - } - _selection = _scope.selection; - } - - @override - void dispose() { - _scope?.removeListener(_handleScopeChange); - super.dispose(); - } - - // - // Overridden members - // - - @override - Widget build(BuildContext context) { - if (selection == null || - selection.isCollapsed || - widget.controls == null || - _scope.focusOwner != FocusOwner.editor) { - return new Container(); - } - final block = _scope.renderContext.boxForTextOffset(documentOffset); - final position = getPosition(block); - Widget handle; - if (position == null) { - handle = new Container(); - } else { - final handleType = isBaseHandle - ? TextSelectionHandleType.left - : TextSelectionHandleType.right; - handle = new Positioned( - left: position.dx, - top: position.dy, - child: widget.controls.buildHandle( - context, - handleType, - block.preferredLineHeight, - ), - ); - handle = new CompositedTransformFollower( - link: block.layerLink, - showWhenUnlinked: false, - child: new Stack( - overflow: Overflow.visible, - children: [handle], - ), - ); - } - // Always return this gesture detector even if handle is an empty container - // This way we prevent drag gesture from being canceled in case current - // position is somewhere outside of any visible paragraph block. - return new GestureDetector( - onPanStart: _handleDragStart, - onPanUpdate: _handleDragUpdate, - child: handle, - ); - } - - // - // Private members - // - - Offset _dragPosition; - - void _handleScopeChange() { - if (_selection != _scope.selection) { - setState(() { - _selection = _scope.selection; - }); - } - } - - void _handleDragStart(DragStartDetails details) { - _dragPosition = details.globalPosition; - } - - void _handleDragUpdate(DragUpdateDetails details) { - _dragPosition += details.delta; - final globalPoint = _dragPosition; - final paragraph = _scope.renderContext.boxForGlobalPoint(globalPoint); - if (paragraph == null) { - return; - } - - final localPoint = paragraph.globalToLocal(globalPoint); - final position = paragraph.getPositionForOffset(localPoint); - final newSelection = selection.copyWith( - baseOffset: isBaseHandle ? position.offset : selection.baseOffset, - extentOffset: isBaseHandle ? selection.extentOffset : position.offset, - ); - if (newSelection.baseOffset >= newSelection.extentOffset) { - // Don't allow reversed or collapsed selection. - return; - } - - if (newSelection != _selection) { - _scope.updateSelection(newSelection, source: ChangeSource.local); - } - } -} - -class _SelectionToolbar extends StatefulWidget { - const _SelectionToolbar({ - Key key, - @required this.scope, - @required this.controls, - @required this.delegate, - }) : super(key: key); - - final ZefyrScope scope; - final TextSelectionControls controls; - final TextSelectionDelegate delegate; - - @override - _SelectionToolbarState createState() => new _SelectionToolbarState(); -} - -class _SelectionToolbarState extends State<_SelectionToolbar> { - ZefyrScope get editable => widget.scope; - TextSelection get selection => widget.delegate.textEditingValue.selection; - - @override - Widget build(BuildContext context) { - return _buildToolbar(context); - } - - Widget _buildToolbar(BuildContext context) { - final base = selection.baseOffset; - // TODO: Editable is not refreshed and may contain stale renderContext instance. - final block = editable.renderContext.boxForTextOffset(base); - if (block == null) { - return Container(); - } - final boxes = block.getEndpointsForSelection(selection); - // Find the horizontal midpoint, just above the selected text. - final Offset midpoint = new Offset( - (boxes.length == 1) - ? (boxes[0].start + boxes[0].end) / 2.0 - : (boxes[0].start + boxes[1].start) / 2.0, - boxes[0].bottom - block.preferredLineHeight, - ); - - final Rect editingRegion = new Rect.fromPoints( - block.localToGlobal(Offset.zero), - block.localToGlobal(block.size.bottomRight(Offset.zero)), - ); -// final toolbar = widget.controls -// .buildToolbar(context, editingRegion, midpoint, widget.delegate); - final Offset endpoint = new Offset( - (boxes.length == 1) - ? (boxes[0].start + boxes[0].end) - : (boxes[0].start + boxes[1].start), - boxes[0].bottom - block.preferredLineHeight, - ); - final TextSelectionPoint textEndpoint = new TextSelectionPoint(endpoint, TextDirection.ltr); - final toolbar = widget.controls - .buildToolbar(context, editingRegion,0.0, midpoint, [textEndpoint], widget.delegate); - return new CompositedTransformFollower( - link: block.layerLink, - showWhenUnlinked: false, - offset: -editingRegion.topLeft, - child: toolbar, - ); - } -} diff --git a/zefyr/lib/src/widgets/theme.dart b/zefyr/lib/src/widgets/theme.dart deleted file mode 100644 index 0f7c1160..00000000 --- a/zefyr/lib/src/widgets/theme.dart +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'dart:io'; - -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; -import 'package:meta/meta.dart'; - -/// Applies a Zefyr editor theme to descendant widgets. -/// -/// Describes colors and typographic styles for an editor. -/// -/// Descendant widgets obtain the current theme's [ZefyrThemeData] object using -/// [ZefyrTheme.of]. -/// -/// See also: -/// -/// * [ZefyrThemeData], which describes actual configuration of a theme. -class ZefyrTheme extends InheritedWidget { - final ZefyrThemeData data; - - /// Applies the given theme [data] to [child]. - /// - /// The [data] and [child] arguments must not be null. - ZefyrTheme({ - Key key, - @required this.data, - @required Widget child, - }) : assert(data != null), - assert(child != null), - super(key: key, child: child); - - @override - bool updateShouldNotify(ZefyrTheme oldWidget) { - return data != oldWidget.data; - } - - /// The data from the closest [ZefyrTheme] instance that encloses the given - /// context. - /// - /// Returns `null` if there is no [ZefyrTheme] in the given build context - /// and [nullOk] is set to `true`. If [nullOk] is set to `false` (default) - /// then this method asserts. - static ZefyrThemeData of(BuildContext context, {bool nullOk: false}) { - final ZefyrTheme widget = context.inheritFromWidgetOfExactType(ZefyrTheme); - if (widget == null && nullOk) return null; - assert(widget != null, - '$ZefyrTheme.of() called with a context that does not contain a ZefyrEditor.'); - return widget.data; - } -} - -/// Holds colors and typography styles for [ZefyrEditor]. -class ZefyrThemeData { - final TextStyle boldStyle; - final TextStyle italicStyle; - final TextStyle linkStyle; - final StyleTheme paragraphTheme; - final HeadingTheme headingTheme; - final BlockTheme blockTheme; - final Color selectionColor; - final Color cursorColor; - - /// Size of indentation for blocks. - final double indentSize; - final ZefyrToolbarTheme toolbarTheme; - - factory ZefyrThemeData.fallback(BuildContext context) { - final defaultStyle = DefaultTextStyle.of(context); - final paragraphStyle = defaultStyle.style.copyWith( - fontSize: 16.0, - height: 1.25, - fontWeight: FontWeight.normal, - color: Colors.grey.shade800, - ); - final padding = const EdgeInsets.only(bottom: 16.0); - final boldStyle = new TextStyle(fontWeight: FontWeight.bold); - final italicStyle = new TextStyle(fontStyle: FontStyle.italic); - final linkStyle = - TextStyle(color: Colors.blue, decoration: TextDecoration.underline); - - return new ZefyrThemeData( - boldStyle: boldStyle, - italicStyle: italicStyle, - linkStyle: linkStyle, - paragraphTheme: - new StyleTheme(textStyle: paragraphStyle, padding: padding), - headingTheme: new HeadingTheme.fallback(), - blockTheme: new BlockTheme.fallback(), - selectionColor: Colors.lightBlueAccent.shade100, - cursorColor: Colors.black, - indentSize: 16.0, - toolbarTheme: new ZefyrToolbarTheme.fallback(context), - ); - } - - const ZefyrThemeData({ - this.boldStyle, - this.italicStyle, - this.linkStyle, - this.paragraphTheme, - this.headingTheme, - this.blockTheme, - this.selectionColor, - this.cursorColor, - this.indentSize, - this.toolbarTheme, - }); - - ZefyrThemeData copyWith({ - TextStyle textStyle, - TextStyle boldStyle, - TextStyle italicStyle, - TextStyle linkStyle, - StyleTheme paragraphTheme, - HeadingTheme headingTheme, - BlockTheme blockTheme, - Color selectionColor, - Color cursorColor, - double indentSize, - ZefyrToolbarTheme toolbarTheme, - }) { - return new ZefyrThemeData( - boldStyle: boldStyle ?? this.boldStyle, - italicStyle: italicStyle ?? this.italicStyle, - linkStyle: linkStyle ?? this.linkStyle, - paragraphTheme: paragraphTheme ?? this.paragraphTheme, - headingTheme: headingTheme ?? this.headingTheme, - blockTheme: blockTheme ?? this.blockTheme, - selectionColor: selectionColor ?? this.selectionColor, - cursorColor: cursorColor ?? this.cursorColor, - indentSize: indentSize ?? this.indentSize, - toolbarTheme: toolbarTheme ?? this.toolbarTheme, - ); - } - - ZefyrThemeData merge(ZefyrThemeData other) { - return copyWith( - boldStyle: other.boldStyle, - italicStyle: other.italicStyle, - linkStyle: other.linkStyle, - paragraphTheme: other.paragraphTheme, - headingTheme: other.headingTheme, - blockTheme: other.blockTheme, - selectionColor: other.selectionColor, - cursorColor: other.cursorColor, - indentSize: other.indentSize, - toolbarTheme: other.toolbarTheme, - ); - } -} - -/// Theme for heading-styled lines of text. -class HeadingTheme { - /// Style theme for level 1 headings. - final StyleTheme level1; - - /// Style theme for level 2 headings. - final StyleTheme level2; - - /// Style theme for level 3 headings. - final StyleTheme level3; - - HeadingTheme({ - @required this.level1, - @required this.level2, - @required this.level3, - }); - - /// Creates fallback theme for headings. - factory HeadingTheme.fallback() { - return HeadingTheme( - level1: StyleTheme( - textStyle: TextStyle( - fontSize: 30.0, - color: Colors.grey.shade800, - height: 1.25, - fontWeight: FontWeight.w600, - ), - padding: EdgeInsets.only(top: 16.0, bottom: 16.0), - ), - level2: StyleTheme( - textStyle: TextStyle( - fontSize: 24.0, - color: Colors.grey.shade800, - height: 1.25, - fontWeight: FontWeight.w600, - ), - padding: EdgeInsets.only(bottom: 8.0, top: 8.0), - ), - level3: StyleTheme( - textStyle: TextStyle( - fontSize: 20.0, - color: Colors.grey.shade800, - height: 1.25, - fontWeight: FontWeight.w600, - ), - padding: EdgeInsets.only(bottom: 8.0, top: 8.0), - ), - ); - } -} - -/// Theme for a block of lines in a document. -class BlockTheme { - /// Style theme for bullet lists. - final StyleTheme bulletList; - - /// Style theme for number lists. - final StyleTheme numberList; - - /// Style theme for code snippets. - final StyleTheme code; - - /// Style theme for quotes. - final StyleTheme quote; - - BlockTheme({ - @required this.bulletList, - @required this.numberList, - @required this.quote, - @required this.code, - }); - - /// Creates fallback theme for blocks. - factory BlockTheme.fallback() { - final padding = const EdgeInsets.only(bottom: 8.0); - return new BlockTheme( - bulletList: new StyleTheme(padding: padding), - numberList: new StyleTheme(padding: padding), - quote: new StyleTheme( - textStyle: new TextStyle(color: Colors.grey.shade700), - padding: padding, - ), - code: new StyleTheme( - textStyle: new TextStyle( - color: Colors.blueGrey.shade800, - fontFamily: Platform.isIOS ? 'Menlo' : 'Roboto Mono', - fontSize: 14.0, - height: 1.25, - ), - padding: padding, - ), - ); - } -} - -/// Theme for a specific attribute style. -/// -/// Used in [HeadingTheme] and [BlockTheme], as well as in -/// [ZefyrThemeData.paragraphTheme]. -class StyleTheme { - /// Text style of this theme. - final TextStyle textStyle; - - /// Padding to apply around lines of text. - final EdgeInsets padding; - - /// Creates a new [StyleTheme]. - StyleTheme({ - this.textStyle, - this.padding, - }); -} - -/// Defines styles and colors for [ZefyrToolbar]. -class ZefyrToolbarTheme { - /// The background color of toolbar. - final Color color; - - /// Color of buttons in toggled state. - final Color toggleColor; - - /// Color of button icons. - final Color iconColor; - - /// Color of button icons in disabled state. - final Color disabledIconColor; - - /// Creates fallback theme for editor toolbars. - factory ZefyrToolbarTheme.fallback(BuildContext context) { - final theme = Theme.of(context); - return ZefyrToolbarTheme._( - color: theme.primaryColorLight, - toggleColor: theme.primaryColor, - iconColor: theme.primaryIconTheme.color, - disabledIconColor: theme.primaryColor, - ); - } - - ZefyrToolbarTheme._({ - @required this.color, - @required this.toggleColor, - @required this.iconColor, - @required this.disabledIconColor, - }); - - ZefyrToolbarTheme copyWith({ - Color color, - Color toggleColor, - Color iconColor, - Color disabledIconColor, - }) { - return ZefyrToolbarTheme._( - color: color ?? this.color, - toggleColor: toggleColor ?? this.toggleColor, - iconColor: iconColor ?? this.iconColor, - disabledIconColor: disabledIconColor ?? this.disabledIconColor, - ); - } -} diff --git a/zefyr/lib/src/widgets/toolbar.dart b/zefyr/lib/src/widgets/toolbar.dart deleted file mode 100644 index d9364cbf..00000000 --- a/zefyr/lib/src/widgets/toolbar.dart +++ /dev/null @@ -1,398 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. -import 'dart:async'; -import 'dart:ui' as ui; - -import 'package:flutter/material.dart'; -import 'package:notus/notus.dart'; - -import 'buttons.dart'; -import 'scope.dart'; -import 'theme.dart'; - -/// List of all button actions supported by [ZefyrToolbar] buttons. -enum ZefyrToolbarAction { - bold, - italic, - link, - unlink, - clipboardCopy, - openInBrowser, - heading, - headingLevel1, - headingLevel2, - headingLevel3, - bulletList, - numberList, - code, - quote, - horizontalRule, - image, - cameraImage, - galleryImage, - hideKeyboard, - close, - confirm, -} - -final kZefyrToolbarAttributeActions = { - ZefyrToolbarAction.bold: NotusAttribute.bold, - ZefyrToolbarAction.italic: NotusAttribute.italic, - ZefyrToolbarAction.link: NotusAttribute.link, - ZefyrToolbarAction.heading: NotusAttribute.heading, - ZefyrToolbarAction.headingLevel1: NotusAttribute.heading.level1, - ZefyrToolbarAction.headingLevel2: NotusAttribute.heading.level2, - ZefyrToolbarAction.headingLevel3: NotusAttribute.heading.level3, - ZefyrToolbarAction.bulletList: NotusAttribute.block.bulletList, - ZefyrToolbarAction.numberList: NotusAttribute.block.numberList, - ZefyrToolbarAction.code: NotusAttribute.block.code, - ZefyrToolbarAction.quote: NotusAttribute.block.quote, - ZefyrToolbarAction.horizontalRule: NotusAttribute.embed.horizontalRule, -}; - -/// Allows customizing appearance of [ZefyrToolbar]. -abstract class ZefyrToolbarDelegate { - /// Builds toolbar button for specified [action]. - /// - /// Returned widget is usually an instance of [ZefyrButton]. - Widget buildButton(BuildContext context, ZefyrToolbarAction action, - {VoidCallback onPressed}); -} - -/// Scaffold for [ZefyrToolbar]. -class ZefyrToolbarScaffold extends StatelessWidget { - const ZefyrToolbarScaffold({ - Key key, - @required this.body, - this.trailing, - this.autoImplyTrailing: true, - }) : super(key: key); - - final Widget body; - final Widget trailing; - final bool autoImplyTrailing; - - @override - Widget build(BuildContext context) { - final theme = ZefyrTheme.of(context).toolbarTheme; - final toolbar = ZefyrToolbar.of(context); - final constraints = - BoxConstraints.tightFor(height: ZefyrToolbar.kToolbarHeight); - final children = [ - Expanded(child: body), - ]; - - if (trailing != null) { - children.add(trailing); - } else if (autoImplyTrailing) { - children.add(toolbar.buildButton(context, ZefyrToolbarAction.close)); - } - return new Container( - constraints: constraints, - child: Material(color: theme.color, child: Row(children: children)), - ); - } -} - -/// Toolbar for [ZefyrEditor]. -class ZefyrToolbar extends StatefulWidget implements PreferredSizeWidget { - static const kToolbarHeight = 50.0; - - const ZefyrToolbar({ - Key key, - @required this.editor, - this.autoHide: true, - this.delegate, - }) : super(key: key); - - final ZefyrToolbarDelegate delegate; - final ZefyrScope editor; - - /// Whether to automatically hide this toolbar when editor loses focus. - final bool autoHide; - - static ZefyrToolbarState of(BuildContext context) { - final _ZefyrToolbarScope scope = - context.inheritFromWidgetOfExactType(_ZefyrToolbarScope); - return scope?.toolbar; - } - - @override - ZefyrToolbarState createState() => ZefyrToolbarState(); - - @override - ui.Size get preferredSize => new Size.fromHeight(ZefyrToolbar.kToolbarHeight); -} - -class _ZefyrToolbarScope extends InheritedWidget { - _ZefyrToolbarScope({Key key, @required Widget child, @required this.toolbar}) - : super(key: key, child: child); - - final ZefyrToolbarState toolbar; - - @override - bool updateShouldNotify(_ZefyrToolbarScope oldWidget) { - return toolbar != oldWidget.toolbar; - } -} - -class ZefyrToolbarState extends State - with SingleTickerProviderStateMixin { - final Key _toolbarKey = UniqueKey(); - final Key _overlayKey = UniqueKey(); - - ZefyrToolbarDelegate _delegate; - AnimationController _overlayAnimation; - WidgetBuilder _overlayBuilder; - Completer _overlayCompleter; - - TextSelection _selection; - - void markNeedsRebuild() { - setState(() { - if (_selection != editor.selection) { - _selection = editor.selection; - closeOverlay(); - } - }); - } - - Widget buildButton(BuildContext context, ZefyrToolbarAction action, - {VoidCallback onPressed}) { - return _delegate.buildButton(context, action, onPressed: onPressed); - } - - Future showOverlay(WidgetBuilder builder) async { - assert(_overlayBuilder == null); - final completer = new Completer(); - setState(() { - _overlayBuilder = builder; - _overlayCompleter = completer; - _overlayAnimation.forward(); - }); - return completer.future; - } - - void closeOverlay() { - if (!hasOverlay) return; - _overlayAnimation.reverse().whenComplete(() { - setState(() { - _overlayBuilder = null; - _overlayCompleter?.complete(); - _overlayCompleter = null; - }); - }); - } - - bool get hasOverlay => _overlayBuilder != null; - - ZefyrScope get editor => widget.editor; - - @override - void initState() { - super.initState(); - _delegate = widget.delegate ?? new _DefaultZefyrToolbarDelegate(); - _overlayAnimation = new AnimationController( - vsync: this, duration: Duration(milliseconds: 100)); - _selection = editor.selection; - } - - @override - void didUpdateWidget(ZefyrToolbar oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.delegate != oldWidget.delegate) { - _delegate = widget.delegate ?? new _DefaultZefyrToolbarDelegate(); - } - } - - @override - void dispose() { - _overlayAnimation.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final layers = []; - - // Must set unique key for the toolbar to prevent it from reconstructing - // new state each time we toggle overlay. - final toolbar = ZefyrToolbarScaffold( - key: _toolbarKey, - body: ZefyrButtonList(buttons: _buildButtons(context)), - trailing: buildButton(context, ZefyrToolbarAction.hideKeyboard), - ); - - layers.add(toolbar); - - if (hasOverlay) { - Widget widget = new Builder(builder: _overlayBuilder); - assert(widget != null); - final overlay = FadeTransition( - key: _overlayKey, - opacity: _overlayAnimation, - child: widget, - ); - layers.add(overlay); - } - - final constraints = - BoxConstraints.tightFor(height: ZefyrToolbar.kToolbarHeight); - return _ZefyrToolbarScope( - toolbar: this, - child: Container( - constraints: constraints, - child: Stack(children: layers), - ), - ); - } - - List _buildButtons(BuildContext context) { - final buttons = [ - buildButton(context, ZefyrToolbarAction.bold), - buildButton(context, ZefyrToolbarAction.italic), - LinkButton(), - HeadingButton(), - buildButton(context, ZefyrToolbarAction.bulletList), - buildButton(context, ZefyrToolbarAction.numberList), - buildButton(context, ZefyrToolbarAction.quote), - buildButton(context, ZefyrToolbarAction.code), - buildButton(context, ZefyrToolbarAction.horizontalRule), - ImageButton(), - ]; - return buttons; - } -} - -/// Scrollable list of toolbar buttons. -class ZefyrButtonList extends StatefulWidget { - const ZefyrButtonList({Key key, @required this.buttons}) : super(key: key); - final List buttons; - - @override - _ZefyrButtonListState createState() => _ZefyrButtonListState(); -} - -class _ZefyrButtonListState extends State { - final ScrollController _controller = new ScrollController(); - bool _showLeftArrow = false; - bool _showRightArrow = false; - - @override - void initState() { - super.initState(); - _controller.addListener(_handleScroll); - // Workaround to allow scroll controller attach to our ListView so that - // we can detect if overflow arrows need to be shown on init. - // TODO: find a better way to detect overflow - Timer.run(_handleScroll); - } - - @override - Widget build(BuildContext context) { - final theme = ZefyrTheme.of(context).toolbarTheme; - final color = theme.iconColor; - final list = ListView( - scrollDirection: Axis.horizontal, - controller: _controller, - children: widget.buttons, - physics: ClampingScrollPhysics(), - ); - - final leftArrow = _showLeftArrow - ? Icon(Icons.arrow_left, size: 18.0, color: color) - : null; - final rightArrow = _showRightArrow - ? Icon(Icons.arrow_right, size: 18.0, color: color) - : null; - return Row( - children: [ - SizedBox( - width: 12.0, - height: ZefyrToolbar.kToolbarHeight, - child: Container(child: leftArrow, color: theme.color), - ), - Expanded(child: ClipRect(child: list)), - SizedBox( - width: 12.0, - height: ZefyrToolbar.kToolbarHeight, - child: Container(child: rightArrow, color: theme.color), - ), - ], - ); - } - - void _handleScroll() { - setState(() { - _showLeftArrow = - _controller.position.minScrollExtent != _controller.position.pixels; - _showRightArrow = - _controller.position.maxScrollExtent != _controller.position.pixels; - }); - } -} - -class _DefaultZefyrToolbarDelegate implements ZefyrToolbarDelegate { - static const kDefaultButtonIcons = { - ZefyrToolbarAction.bold: Icons.format_bold, - ZefyrToolbarAction.italic: Icons.format_italic, - ZefyrToolbarAction.link: Icons.link, - ZefyrToolbarAction.unlink: Icons.link_off, - ZefyrToolbarAction.clipboardCopy: Icons.content_copy, - ZefyrToolbarAction.openInBrowser: Icons.open_in_new, - ZefyrToolbarAction.heading: Icons.format_size, - ZefyrToolbarAction.bulletList: Icons.format_list_bulleted, - ZefyrToolbarAction.numberList: Icons.format_list_numbered, - ZefyrToolbarAction.code: Icons.code, - ZefyrToolbarAction.quote: Icons.format_quote, - ZefyrToolbarAction.horizontalRule: Icons.remove, - ZefyrToolbarAction.image: Icons.photo, - ZefyrToolbarAction.cameraImage: Icons.photo_camera, - ZefyrToolbarAction.galleryImage: Icons.photo_library, - ZefyrToolbarAction.hideKeyboard: Icons.keyboard_hide, - ZefyrToolbarAction.close: Icons.close, - ZefyrToolbarAction.confirm: Icons.check, - }; - - static const kSpecialIconSizes = { - ZefyrToolbarAction.unlink: 20.0, - ZefyrToolbarAction.clipboardCopy: 20.0, - ZefyrToolbarAction.openInBrowser: 20.0, - ZefyrToolbarAction.close: 20.0, - ZefyrToolbarAction.confirm: 20.0, - }; - - static const kDefaultButtonTexts = { - ZefyrToolbarAction.headingLevel1: 'H1', - ZefyrToolbarAction.headingLevel2: 'H2', - ZefyrToolbarAction.headingLevel3: 'H3', - }; - - @override - Widget buildButton(BuildContext context, ZefyrToolbarAction action, - {VoidCallback onPressed}) { - final theme = Theme.of(context); - if (kDefaultButtonIcons.containsKey(action)) { - final icon = kDefaultButtonIcons[action]; - final size = kSpecialIconSizes[action]; - return ZefyrButton.icon( - action: action, - icon: icon, - iconSize: size, - onPressed: onPressed, - ); - } else { - final text = kDefaultButtonTexts[action]; - assert(text != null); - final style = theme.textTheme.caption - .copyWith(fontWeight: FontWeight.bold, fontSize: 14.0); - return ZefyrButton.text( - action: action, - text: text, - style: style, - onPressed: onPressed, - ); - } - } -} diff --git a/zefyr/lib/src/widgets/view.dart b/zefyr/lib/src/widgets/view.dart deleted file mode 100644 index d7b7b13e..00000000 --- a/zefyr/lib/src/widgets/view.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:meta/meta.dart'; -import 'package:notus/notus.dart'; - -import 'code.dart'; -import 'common.dart'; -import 'image.dart'; -import 'list.dart'; -import 'paragraph.dart'; -import 'quote.dart'; -import 'scope.dart'; -import 'theme.dart'; - -/// Non-scrollable read-only view of Notus rich text documents. -@experimental -class ZefyrView extends StatefulWidget { - final NotusDocument document; - final ZefyrImageDelegate imageDelegate; - - const ZefyrView({Key key, @required this.document, this.imageDelegate}) - : super(key: key); - - @override - ZefyrViewState createState() => ZefyrViewState(); -} - -class ZefyrViewState extends State { - ZefyrScope _scope; - ZefyrThemeData _themeData; - - ZefyrImageDelegate get imageDelegate => widget.imageDelegate; - - @override - void initState() { - super.initState(); - _scope = ZefyrScope.view(imageDelegate: widget.imageDelegate); - } - - @override - void didUpdateWidget(ZefyrView oldWidget) { - super.didUpdateWidget(oldWidget); - _scope.imageDelegate = widget.imageDelegate; - } - - @override - void didChangeDependencies() { - super.didChangeDependencies(); - final parentTheme = ZefyrTheme.of(context, nullOk: true); - final fallbackTheme = ZefyrThemeData.fallback(context); - _themeData = (parentTheme != null) - ? fallbackTheme.merge(parentTheme) - : fallbackTheme; - } - - @override - void dispose() { - _scope.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return ZefyrTheme( - data: _themeData, - child: ZefyrScopeAccess( - scope: _scope, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: _buildChildren(context), - ), - ), - ); - } - - List _buildChildren(BuildContext context) { - final result = []; - for (var node in widget.document.root.children) { - result.add(_defaultChildBuilder(context, node)); - } - return result; - } - - Widget _defaultChildBuilder(BuildContext context, Node node) { - if (node is LineNode) { - if (node.hasEmbed) { - return new RawZefyrLine(node: node); - } else if (node.style.contains(NotusAttribute.heading)) { - return new ZefyrHeading(node: node); - } - return new ZefyrParagraph(node: node); - } - - final BlockNode block = node; - final blockStyle = block.style.get(NotusAttribute.block); - if (blockStyle == NotusAttribute.block.code) { - return new ZefyrCode(node: block); - } else if (blockStyle == NotusAttribute.block.bulletList) { - return new ZefyrList(node: block); - } else if (blockStyle == NotusAttribute.block.numberList) { - return new ZefyrList(node: block); - } else if (blockStyle == NotusAttribute.block.quote) { - return new ZefyrQuote(node: block); - } - - throw new UnimplementedError('Block format $blockStyle.'); - } -} diff --git a/zefyr/lib/util.dart b/zefyr/lib/util.dart deleted file mode 100644 index 7ef640e8..00000000 --- a/zefyr/lib/util.dart +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) 2018, the Zefyr project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -/// Utility functions for Zefyr. -library zefyr.util; - -import 'dart:math' as math; - -import 'package:quill_delta/quill_delta.dart'; - -export 'src/fast_diff.dart'; - -int getPositionDelta(Delta user, Delta actual) { - final userIter = new DeltaIterator(user); - final actualIter = new DeltaIterator(actual); - int diff = 0; - while (userIter.hasNext || actualIter.hasNext) { - num length = math.min(userIter.peekLength(), actualIter.peekLength()); - final userOp = userIter.next(length); - final actualOp = actualIter.next(length); - assert(userOp.length == actualOp.length); - if (userOp.key == actualOp.key) continue; - if (userOp.isInsert && actualOp.isRetain) { - diff -= userOp.length; - } else if (userOp.isDelete && actualOp.isRetain) { - diff += userOp.length; - } else if (userOp.isRetain && actualOp.isInsert) { - if (actualOp.data.startsWith('\n') ) { - // At this point user input reached its end (retain). If a heuristic - // rule inserts a new line we should keep cursor on it's original position. - continue; - } - diff += actualOp.length; - } else { - // TODO: this likely needs to cover more edge cases. - } - } - return diff; -} diff --git a/zefyr/lib/zefyr.dart b/zefyr/lib/zefyr.dart deleted file mode 100644 index 846ba5f5..00000000 --- a/zefyr/lib/zefyr.dart +++ /dev/null @@ -1,22 +0,0 @@ -library zefyr; - -export 'package:notus/notus.dart'; - -export 'src/widgets/buttons.dart' hide HeadingButton, LinkButton; -export 'src/widgets/code.dart'; -export 'src/widgets/common.dart'; -export 'src/widgets/controller.dart'; -export 'src/widgets/editable_text.dart'; -export 'src/widgets/editor.dart'; -export 'src/widgets/field.dart'; -export 'src/widgets/horizontal_rule.dart'; -export 'src/widgets/image.dart'; -export 'src/widgets/list.dart'; -export 'src/widgets/paragraph.dart'; -export 'src/widgets/quote.dart'; -export 'src/widgets/scaffold.dart'; -export 'src/widgets/scope.dart' hide ZefyrScopeAccess; -export 'src/widgets/selection.dart' hide SelectionHandleDriver; -export 'src/widgets/theme.dart'; -export 'src/widgets/toolbar.dart'; -export 'src/widgets/view.dart'; \ No newline at end of file diff --git a/zefyr/pubspec.yaml b/zefyr/pubspec.yaml deleted file mode 100644 index 24625589..00000000 --- a/zefyr/pubspec.yaml +++ /dev/null @@ -1,59 +0,0 @@ -name: zefyr -description: A new Flutter package project. -version: 0.0.1 -author: Anatoly Pulyaevskiy -homepage: - -environment: - sdk: ">=2.1.0 <3.0.0" - -dependencies: - flutter: - sdk: flutter - collection: ^1.14.6 - url_launcher: ^5.0.0 - image_picker: ^0.5.0 - quill_delta: ^1.0.0-dev.1.0 - notus: ^0.1.0 - meta: ^1.1.0 - -dev_dependencies: - flutter_test: - sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/zefyr/test/zefyr_test.dart b/zefyr/test/zefyr_test.dart deleted file mode 100644 index f9502254..00000000 --- a/zefyr/test/zefyr_test.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; - -import 'package:zefyr/zefyr.dart'; - -void main() { -// test('adds one to input values', () { -// final calculator = Calculator(); -// expect(calculator.addOne(2), 3); -// expect(calculator.addOne(-7), -6); -// expect(calculator.addOne(0), 1); -// expect(() => calculator.addOne(null), throwsNoSuchMethodError); -// }); -}