diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 2e9846d..53ae800 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ + package="com.flutter.beer" + android:installLocation="preferExternal"> + + + + + + + + + + + + + diff --git a/docs/widget/gestures/gesturedetector/index.md b/docs/widget/gestures/gesturedetector/index.md index 83532f6..043bdcd 100644 --- a/docs/widget/gestures/gesturedetector/index.md +++ b/docs/widget/gestures/gesturedetector/index.md @@ -1 +1,80 @@ -## **文档完善中** \ No newline at end of file +## **GestureDetector** +> 该组件可监听触摸事件,可包裹需要监听的组件后使用带有的触摸事件。 + +### 构造函数 +``` +GestureDetector({ + Key key, + this.child, + this.onTapDown, + this.onTapUp, + this.onTap, + this.onTapCancel, + this.onDoubleTap, + this.onLongPress, + this.onLongPressUp, + this.onLongPressDragStart, + this.onLongPressDragUpdate, + this.onLongPressDragUp, + this.onVerticalDragDown, + this.onVerticalDragStart, + this.onVerticalDragUpdate, + this.onVerticalDragEnd, + this.onVerticalDragCancel, + this.onHorizontalDragDown, + this.onHorizontalDragStart, + this.onHorizontalDragUpdate, + this.onHorizontalDragEnd, + this.onHorizontalDragCancel, + this.onForcePressStart, + this.onForcePressPeak, + this.onForcePressUpdate, + this.onForcePressEnd, + this.onPanDown, + this.onPanStart, + this.onPanUpdate, + this.onPanEnd, + this.onPanCancel, + this.onScaleStart, + this.onScaleUpdate, + this.onScaleEnd, + this.behavior, + this.excludeFromSemantics = false, + this.dragStartBehavior = DragStartBehavior.down, +}) +``` + +### 属性介绍 +> 点击事件可用Tap属性,执行顺序如下罗列 +- onTapDown: 触摸时触发 +- onTapUp: 触摸离开时触发 +- onTap: 点击后触发 +- onTapCancel: 触发时取消 +- onDoubleTap: 200毫秒内触摸时触发,但不触发onTap +> 拖动事件可用Pan属性,执行顺序如下罗列,拖动时返回是手势位移数据 +- onPanDown: +- onPanStart: (DragStartDetails e) {} 返回相对屏幕位置 +- onPanUpdate: (DragUpdateDetails e) {} 回调函数中返回是手势位移数据 +- onPanEnd: (DragEndDetails e) {} 回调函数中返回Velocity,手势瞬时速度,可结合动画使用 +- onPanCancel: 回调取消 +> 以下也可执行拖动事件,使用后不会触发Tap/Pan事件,执行顺序如下罗列,拖动时返回相对整个屏幕距离数据 +- onForcePressStart: (ForcePressDetails e) {} +- onForcePressPeak: (ForcePressDetails e) {} +- onForcePressUpdate: (ForcePressDetails e) {} +- onForcePressEnd: (ForcePressDetails e) {} +> 监听垂直或水平方向 +- onVerticalDragDown +- onVerticalDragStart +- onVerticalDragUpdate +- onVerticalDragEnd +- onVerticalDragCancel +- onHorizontalDragDown +- onHorizontalDragStart +- onHorizontalDragUpdate +- onHorizontalDragEnd +- onHorizontalDragCancel +> 手势放大 +- onScaleStart +- onScaleUpdate +- onScaleEnd + diff --git a/docs/widget/vision/transform/index.md b/docs/widget/vision/transform/index.md index 83532f6..dc2da45 100644 --- a/docs/widget/vision/transform/index.md +++ b/docs/widget/vision/transform/index.md @@ -1 +1,24 @@ -## **文档完善中** \ No newline at end of file +## **Transform** + +> 在绘制子元素前应用转换的组件 + +### 构造方法 +``` +Transform({ + Key key, + @required Matrix4 transform, + Offset origin, + AlignmentGeometry alignment, + bool transformHitTests: true, + Widget child +}) +``` + +### 属性介绍 +origin:坐标系的原点(相对于此渲染对象的左上角)应用矩阵的原点 +alignment:原点的对齐方式 +transform: 在绘制过程中改变子元素的矩阵 +transformHitTests:在测试时是否执行转换 + + +### 实例 diff --git a/lib/components/exampleComp.dart b/lib/components/exampleComp.dart index 37b53f7..314a926 100644 --- a/lib/components/exampleComp.dart +++ b/lib/components/exampleComp.dart @@ -12,18 +12,18 @@ class Index extends StatelessWidget { @override Widget build(BuildContext context) { Size size = MediaQuery.of(context).size; - double _dp = 1.5; return Store.connect( builder: (context, child, MainStateModel model) { return Center( child: Container( - width: size.width, - height: size.height / _dp, - margin: EdgeInsets.all(30 / _dp), + margin: EdgeInsets.all(10), decoration: BoxDecoration( border: Border.all(color: Color(AppTheme.mainColor), width: 1.0), ), - child: this.child, + child: SizedBox.fromSize( + size: size / 1.3, + child: this.child, + ), ), ); }, diff --git a/lib/components/expansionTile.dart b/lib/components/expansionTile.dart new file mode 100644 index 0000000..21e3089 --- /dev/null +++ b/lib/components/expansionTile.dart @@ -0,0 +1,222 @@ +// Copyright 2017 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'; + +const Duration _kExpand = Duration(milliseconds: 200); + +/// A single-line [ListTile] with a trailing button that expands or collapses +/// the tile to reveal or hide the [children]. +/// +/// This widget is typically used with [ListView] to create an +/// "expand / collapse" list entry. When used with scrolling widgets like +/// [ListView], a unique [PageStorageKey] must be specified to enable the +/// [ExpansionTile] to save and restore its expanded state when it is scrolled +/// in and out of view. +/// +/// See also: +/// +/// * [ListTile], useful for creating expansion tile [children] when the +/// expansion tile represents a sublist. +/// * The "Expand/collapse" section of +/// . +class ExpansionTile extends StatefulWidget { + /// Creates a single-line [ListTile] with a trailing button that expands or collapses + /// the tile to reveal or hide the [children]. The [initiallyExpanded] property must + /// be non-null. + const ExpansionTile({ + Key key, + this.headerBackgroundColor, + this.leading, + @required this.title, + this.backgroundColor, + this.iconColor, + this.onExpansionChanged, + this.children = const [], + this.trailing, + this.initiallyExpanded = false, + }) : assert(initiallyExpanded != null), + super(key: key); + + /// A widget to display before the title. + /// + /// Typically a [CircleAvatar] widget. + final Widget leading; + + /// The primary content of the list item. + /// + /// Typically a [Text] widget. + final Widget title; + + /// Called when the tile expands or collapses. + /// + /// When the tile starts expanding, this function is called with the value + /// true. When the tile starts collapsing, this function is called with + /// the value false. + final ValueChanged onExpansionChanged; + + /// The widgets that are displayed when the tile expands. + /// + /// Typically [ListTile] widgets. + final List children; + + /// The color to display behind the sublist when expanded. + final Color backgroundColor; + + /// The color to display the background of the header. + final Color headerBackgroundColor; + + /// The color to display the icon of the header. + final Color iconColor; + + /// A widget to display instead of a rotating arrow icon. + final Widget trailing; + + /// Specifies if the list tile is initially expanded (true) or collapsed (false, the default). + final bool initiallyExpanded; + + @override + _ExpansionTileState createState() => _ExpansionTileState(); +} + +class _ExpansionTileState extends State + with SingleTickerProviderStateMixin { + static final Animatable _easeOutTween = + CurveTween(curve: Curves.easeOut); + static final Animatable _easeInTween = + CurveTween(curve: Curves.easeIn); + static final Animatable _halfTween = + Tween(begin: 0.0, end: 0.5); + + final ColorTween _borderColorTween = ColorTween(); + final ColorTween _headerColorTween = ColorTween(); + final ColorTween _iconColorTween = ColorTween(); + final ColorTween _backgroundColorTween = ColorTween(); + + AnimationController _controller; + Animation _iconTurns; + Animation _heightFactor; + Animation _borderColor; + Animation _headerColor; + Animation _iconColor; + Animation _backgroundColor; + + bool _isExpanded = false; + + @override + void initState() { + super.initState(); + _controller = AnimationController(duration: _kExpand, vsync: this); + _heightFactor = _controller.drive(_easeInTween); + _iconTurns = _controller.drive(_halfTween.chain(_easeInTween)); + _borderColor = _controller.drive(_borderColorTween.chain(_easeOutTween)); + _headerColor = _controller.drive(_headerColorTween.chain(_easeInTween)); + _iconColor = _controller.drive(_iconColorTween.chain(_easeInTween)); + _backgroundColor = + _controller.drive(_backgroundColorTween.chain(_easeOutTween)); + + _isExpanded = + PageStorage.of(context)?.readState(context) ?? widget.initiallyExpanded; + if (_isExpanded) _controller.value = 1.0; + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + void _handleTap() { + setState(() { + _isExpanded = !_isExpanded; + if (_isExpanded) { + _controller.forward(); + } else { + _controller.reverse().then((void value) { + if (!mounted) return; + setState(() { + // Rebuild without widget.children. + }); + }); + } + PageStorage.of(context)?.writeState(context, _isExpanded); + }); + if (widget.onExpansionChanged != null) + widget.onExpansionChanged(_isExpanded); + } + + Widget _buildChildren(BuildContext context, Widget child) { + final Color borderSideColor = _borderColor.value ?? Colors.transparent; + final Color titleColor = _headerColor.value; + + return Container( + decoration: BoxDecoration( + color: _backgroundColor.value ?? Colors.transparent, + border: Border( + top: BorderSide(color: borderSideColor), + bottom: BorderSide(color: borderSideColor), + )), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + IconTheme.merge( + data: IconThemeData(color: _iconColor.value), + child: Container( + color: widget.headerBackgroundColor ?? Colors.black, + child: ListTile( + onTap: _handleTap, + leading: widget.leading, + title: DefaultTextStyle( + style: Theme.of(context) + .textTheme + .subhead + .copyWith(color: titleColor), + child: widget.title, + ), + trailing: widget.trailing ?? + RotationTransition( + turns: _iconTurns, + child: Icon( + Icons.expand_more, + color: widget.iconColor ?? Colors.grey, + ), + ), + ), + ), + ), + ClipRect( + child: Align( + heightFactor: _heightFactor.value, + child: child, + ), + ), + ], + ), + ); + } + + @override + void didChangeDependencies() { + final ThemeData theme = Theme.of(context); + _borderColorTween..end = theme.dividerColor; + _headerColorTween + ..begin = theme.textTheme.subhead.color + ..end = theme.primaryColor; + _iconColorTween + ..begin = theme.unselectedWidgetColor + ..end = theme.primaryColor; + _backgroundColorTween..end = widget.backgroundColor; + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + final bool closed = !_isExpanded && _controller.isDismissed; + return AnimatedBuilder( + animation: _controller.view, + builder: _buildChildren, + child: closed ? null : Column(children: widget.children), + ); + } +} diff --git a/lib/components/headerComp.dart b/lib/components/headerComp.dart index b1c3b52..f756d18 100644 --- a/lib/components/headerComp.dart +++ b/lib/components/headerComp.dart @@ -8,8 +8,8 @@ class Index extends StatelessWidget { return Text( this.text, style: TextStyle( - // fontStyle: FontStyle.normal, - ), + //color: Theme.of(context).primaryTextTheme.title.color + ), ); } } diff --git a/lib/components/markdownComp.dart b/lib/components/markdownComp.dart index ae61332..a2e2508 100644 --- a/lib/components/markdownComp.dart +++ b/lib/components/markdownComp.dart @@ -2,6 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart' as md; import 'package:efox_flutter/utils/syntaxHighlighter.dart' show DartSyntaxHighlighter; +import 'package:efox_flutter/config/color.dart' show materialColor; +import 'package:efox_flutter/config/theme.dart' show AppTheme; +import 'package:efox_flutter/store/index.dart' show model; class Index extends StatelessWidget { final String data; diff --git a/lib/components/webviewComp.dart b/lib/components/webviewComp.dart index 499d58d..60c68aa 100644 --- a/lib/components/webviewComp.dart +++ b/lib/components/webviewComp.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; -import 'headerComp.dart' as Header; -import 'package:flutter_webview_plugin/flutter_webview_plugin.dart' show FlutterWebviewPlugin, WebviewScaffold; +import 'package:flutter_webview_plugin/flutter_webview_plugin.dart' + show FlutterWebviewPlugin, WebviewScaffold; class Index extends StatelessWidget { final String url; @@ -18,17 +18,21 @@ class Index extends StatelessWidget { Widget build(BuildContext context) { return WebviewScaffold( url: this.url, - appBar: new AppBar( - title: Header.Index(this.title), + enableAppScheme: false, + appBar: AppBar( + elevation: 0, + title: Text( + this.title, + ), + leading: IconButton( + icon: Icon(Icons.arrow_back), + color: Theme.of(context).primaryTextTheme.title.color, + onPressed: () => Navigator.pop(context), + ), ), withZoom: true, withLocalStorage: true, hidden: true, - // initialChild: Container( - // child: const Center( - // child: CircularProgressIndicator(), - // ), - // ), ); } } diff --git a/lib/components/widgetComp.dart b/lib/components/widgetComp.dart index 45df4f3..e690299 100644 --- a/lib/components/widgetComp.dart +++ b/lib/components/widgetComp.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:efox_flutter/store/index.dart' show Store; -import 'headerComp.dart' as Header; import 'package:efox_flutter/components/markdownComp.dart' as MarkDownComp; import 'package:efox_flutter/lang/index.dart' show AppLocalizations; import 'package:efox_flutter/components/baseComp.dart' as BaseComp; @@ -85,12 +84,18 @@ class IndexState extends State { this.model = model; return Scaffold( appBar: AppBar( - /* title: Header.Index( - this.title, - ), */ + //title: Text(this.title), + elevation: 0, + backgroundColor: Color(AppTheme.secondColor), actions: this.getActions( context, ), + leading: IconButton( + icon: Icon(Icons.arrow_back), + //color: Theme.of(context).primaryTextTheme.title.color, + color: Color(AppTheme.blackColor), + onPressed: () => Navigator.pop(context), + ), ), body: this.loading ? this.renderLoading() : this.renderWidget(), ); @@ -127,6 +132,8 @@ class IndexState extends State { getActions(context) { return [ IconButton( + //color: Theme.of(context).primaryTextTheme.title.color, + color: Color(AppTheme.blackColor), icon: Icon( Icons.insert_link, ), @@ -137,16 +144,18 @@ class IndexState extends State { ); }, ), - IconButton( + /* IconButton( icon: Icon( Icons.code, ), onPressed: () async { this.openPage(context); }, - ), + ), */ IconButton( icon: Icon(Icons.share), + //color: Theme.of(context).primaryTextTheme.title.color, + color: Color(AppTheme.blackColor), onPressed: () { final String msg = this.model.config.state.env.githubAssetOrigin + this.mdUrl; diff --git a/lib/config/color.dart b/lib/config/color.dart new file mode 100644 index 0000000..1cbf1da --- /dev/null +++ b/lib/config/color.dart @@ -0,0 +1,26 @@ +Map materialColor = { + 'red': 0xFFF44336, + 'pink': 0xFFE91E63, + 'purple': 0xFF9C27B0, + 'deepPurple': 0xFF673AB7, + 'indigo': 0xFF3F51B5, + // + + 'blue': 0xFF2196F3, + 'lightBlue': 0xFF03A9F4, + 'cyan': 0xFF00BCD4, + 'teal': 0xFF009688, + 'green': 0xFF4CAF50, + // + 'lightGreen': 0xFF8BC34A, + 'lime': 0xFFCDDC39, + 'yellow': 0xFFFFEB3B, + 'amber': 0xFFFFC107, + 'orange': 0xFFFF9800, + // + 'deepOrange': 0xFFFF5722, + 'brown': 0xFF795548, + 'grey': 0xFF9E9E9E, + 'blueGrey': 0xFF607D8B, + 'black': 0xFF222222, +}; diff --git a/lib/config/theme.dart b/lib/config/theme.dart index 967ab66..0582100 100644 --- a/lib/config/theme.dart +++ b/lib/config/theme.dart @@ -1,31 +1,48 @@ import 'package:flutter/material.dart'; +import 'color.dart' show materialColor; +/** + * yello #FFEB3B + * red #F44336 + * blue #2196F3 + */ class AppTheme { - static int mainColor = 0xFFD32F2F; + //static int mainColor = 0xFFD32F2F; + static int mainColor = materialColor['red']; static int secondColor = 0xFFFFFFFF; static int thirdColor = 0xFFFAFAFA; static int greyColor = 0x8A000000; static int blackColor = 0xFF000000; static int lineColor = 0xFFEEEEEE; - static ThemeData themData = ThemeData( - textTheme: TextTheme( - body1: TextStyle( - // color: Colors.black, - // fontWeight: FontWeight.bold, - ), - ), - //platform: TargetPlatform.iOS, - iconTheme: IconThemeData( - size: 32, - color: Color(thirdColor), - opacity: 0.85, - ), - // primaryIconTheme 导航栏按钮颜色 - primaryIconTheme: IconThemeData( - color: Color(secondColor), - ), - accentColor: Colors.grey, // 选中颜色 - primaryColor: Color(mainColor), // appbar背景 - scaffoldBackgroundColor: Color(secondColor), // 整体的scaffold背景颜色 - ); + static getThemeData(String theme) { + //print('==================================getThemeData=$theme'); + mainColor = materialColor[theme]; + ThemeData themData = ThemeData( + textTheme: TextTheme( + body1: TextStyle( + // color: Colors.black, + // fontWeight: FontWeight.bold, + ), + ), + //platform: TargetPlatform.iOS, + iconTheme: IconThemeData( + size: 32, + color: Color(thirdColor), + opacity: 0.85, + ), + // primaryIconTheme 导航栏按钮颜色 + primaryIconTheme: IconThemeData( + color: Color(secondColor), + ), + accentColor: Colors.grey, // 选中颜色 + primaryColor: Color(mainColor), // appbar背景 + primaryTextTheme: TextTheme( + title: TextStyle( + // color: Colors.red + ), + button: TextStyle(color: Colors.red)), + scaffoldBackgroundColor: Color(secondColor), // 整体的scaffold背景颜色 + ); + return themData; + } } diff --git a/lib/lang/index.dart b/lib/lang/index.dart index c6ca568..a5f622b 100644 --- a/lib/lang/index.dart +++ b/lib/lang/index.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:convert'; import 'package:efox_flutter/lang/config.dart' as I18NConfig; import 'package:flutter/services.dart' show rootBundle; +import 'package:efox_flutter/utils/localstage.dart' show LocalStorage; class AppLocalizations { Locale _locale; // language @@ -21,7 +22,8 @@ class AppLocalizations { } // 设置语言切换代理 - static void setProxy(Function setState, AppLocalizationsDelegate delegate) async { + static void setProxy( + Function setState, AppLocalizationsDelegate delegate) async { _setState = setState; _delegate = delegate; print("_delegate = $_delegate"); @@ -29,17 +31,17 @@ class AppLocalizations { static get languageCode => _inst._locale.languageCode; - static void getLanguageJson([Locale locale]) async { + static Future getLanguageJson([Locale locale]) async { Locale _tmpLocale = _inst._locale; print(_tmpLocale.languageCode); String jsonLang; try { - jsonLang = await rootBundle - .loadString('locale/${_tmpLocale.languageCode}.json'); + jsonLang = + await rootBundle.loadString('locale/${_tmpLocale.languageCode}.json'); } catch (e) { _inst._locale = Locale(I18NConfig.ConfigLanguage.defualtLanguage.code); - jsonLang = await rootBundle - .loadString('locale/${I18NConfig.ConfigLanguage.defualtLanguage.code}.json'); + jsonLang = await rootBundle.loadString( + 'locale/${I18NConfig.ConfigLanguage.defualtLanguage.code}.json'); } json.decode(jsonLang); jsonLanguage = json.decode(jsonLang); @@ -54,10 +56,12 @@ class AppLocalizations { : Locale("zh", "CH"); } _inst._locale = locale; - getLanguageJson(); // 根据语言获取对应的国际化文件 - _setState(() { - _delegate = AppLocalizationsDelegate(locale); - }); + LocalStorage.set('lang', locale.languageCode); + getLanguageJson().then((v) { + _setState(() { + _delegate = AppLocalizationsDelegate(locale); + }); + }); // 根据语言获取对应的国际化文件 } // get local language @@ -95,17 +99,23 @@ class AppLocalizations { class AppLocalizationsDelegate extends LocalizationsDelegate { final Locale locale; - AppLocalizationsDelegate ([this.locale]); + AppLocalizationsDelegate([this.locale]); @override bool isSupported(Locale locale) { - return I18NConfig.ConfigLanguage.sopportLanguage.keys.toList().contains(locale.languageCode); + return I18NConfig.ConfigLanguage.sopportLanguage.keys + .toList() + .contains(locale.languageCode); } @override Future load(Locale _locale) async { - Locale _tmpLocale = locale ?? _locale; - return await AppLocalizations.init(_tmpLocale); + String lang = await LocalStorage.get('lang'); + Locale __locale = locale ?? _locale; + if (lang != null) { + __locale = Locale(lang); + } + return await AppLocalizations.init(__locale); } @override @@ -113,4 +123,4 @@ class AppLocalizationsDelegate extends LocalizationsDelegate { // false时 不执行上述重写函数 return false; } -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 90e8934..9eeeaa7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,22 +1,12 @@ import 'package:flutter/material.dart'; - -//语言包实例化 -import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; //语言包实例化 import 'package:efox_flutter/lang/index.dart' show AppLocalizationsDelegate, AppLocalizations; import 'package:efox_flutter/lang/config.dart' show ConfigLanguage; - -//引用Store 层 -import 'package:efox_flutter/store/index.dart' show model, Store; - -//路由 -import 'package:efox_flutter/router/index.dart' show FluroRouter; - -//主题 -import 'package:efox_flutter/config/theme.dart' show AppTheme; - -//统计 -import 'package:efox_flutter/utils/analytics.dart' as Analytics; +import 'package:efox_flutter/store/index.dart' show model, Store; //引用Store 层 +import 'package:efox_flutter/router/index.dart' show FluroRouter; //路由 +import 'package:efox_flutter/config/theme.dart' show AppTheme; //主题 +import 'package:efox_flutter/utils/analytics.dart' as Analytics; //统计 void main() => runApp(MainApp()); @@ -38,37 +28,40 @@ class MainAppState extends State { //实例化多语言 super.initState(); _delegate = AppLocalizationsDelegate(); + model.dispatch('config', 'getTheme'); } @override Widget build(BuildContext context) { return Store.init( model: model, - child: MaterialApp( - localeResolutionCallback: (deviceLocale, supportedLocales) { - print( - 'deviceLocale=$deviceLocale supportedLocales=$supportedLocales'); - Locale _locale = supportedLocales.contains(deviceLocale) - ? deviceLocale - : Locale('zh'); - return _locale; - }, - onGenerateTitle: (context) { - // 设置多语言代理 - AppLocalizations.setProxy(setState, _delegate); - return 'flutter'; - }, - localizationsDelegates: [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - _delegate, - ], - supportedLocales: ConfigLanguage.supportedLocales, + child: Store.connect(builder: (context, child, model) { + return MaterialApp( + localeResolutionCallback: (deviceLocale, supportedLocales) { + print( + 'deviceLocale=$deviceLocale supportedLocales=$supportedLocales'); + Locale _locale = supportedLocales.contains(deviceLocale) + ? deviceLocale + : Locale('zh'); + return _locale; + }, + onGenerateTitle: (context) { + // 设置多语言代理 + AppLocalizations.setProxy(setState, _delegate); + return 'flutter'; + }, + localizationsDelegates: [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + _delegate, + ], + supportedLocales: ConfigLanguage.supportedLocales, // title: 'Flutter Demo', - theme: AppTheme.themData, - onGenerateRoute: FluroRouter.router.generator, - navigatorObservers: [Analytics.observer], - ), + theme: AppTheme.getThemeData(model.config.state.theme), + onGenerateRoute: FluroRouter.router.generator, + navigatorObservers: [Analytics.observer], + ); + }), ); } } diff --git a/lib/page/component/tabs.dart b/lib/page/component/tabs.dart index c7192d2..334075c 100644 --- a/lib/page/component/tabs.dart +++ b/lib/page/component/tabs.dart @@ -5,7 +5,6 @@ import 'package:efox_flutter/config/theme.dart' show AppTheme; import 'package:efox_flutter/widget/index.dart' as WidgetRoot; import 'package:efox_flutter/router/index.dart' show FluroRouter; import 'package:efox_flutter/lang/index.dart' show AppLocalizations; -import 'package:efox_flutter/components/headerComp.dart' as Header; class Index extends StatefulWidget { final MainStateModel model; @@ -45,12 +44,6 @@ class _IndexState extends State Widget build(BuildContext context) { return Scaffold( key: _scaffoldKey, - /* appBar: AppBar( - title: Header.Index( - AppLocalizations.$t('nav_title_0'), - ), - actions: appBarActions(), - ), */ appBar: PreferredSize( preferredSize: Size.fromHeight(kToolbarHeight), child: Container( @@ -76,13 +69,6 @@ class _IndexState extends State tabs: _mapList.map((v) { return new Tab( text: AppLocalizations.$t(v.typeName), - /* icon: Icon( - IconData( - v.code, - fontFamily: 'MaterialIcons', - matchTextDirection: true, - ), - ), */ ); }).toList()); } @@ -152,36 +138,4 @@ class _IndexState extends State ), ); } - - List appBarActions() { - return [ - PopupMenuButton( - icon: Icon( - Icons.more_vert, - ), - onSelected: (local) { - AppLocalizations.changeLanguage(Locale(local)); - print('local=$local'); - }, - itemBuilder: (context) => [ - PopupMenuItem( - child: Row( - children: [ - Text('中文'), - ], - ), - value: 'zh', - ), - PopupMenuItem( - child: Row( - children: [ - Text('english'), - ], - ), - value: 'en', - ), - ], - ), - ]; - } } diff --git a/lib/page/home.dart b/lib/page/home.dart index 56ee524..cd9e83f 100644 --- a/lib/page/home.dart +++ b/lib/page/home.dart @@ -6,7 +6,9 @@ import 'package:efox_flutter/controller/index.dart' as Controller; //import 'component/index.dart' as TabIndex; // import 'mine/index.dart' as MyIndex; import 'component/tabs.dart' as TabIndex; -import 'mine/index_1.dart' as MyIndex; +import 'mine/index_2.dart' as MyIndex; + +import 'package:efox_flutter/utils/appVersion.dart' show AppVersion; class Index extends StatefulWidget { @override @@ -22,6 +24,7 @@ class _IndexState extends State { super.initState(); _pageController = PageController(); Controller.initState(); + AppVersion().check(context); } @override diff --git a/lib/page/mine/index_1.dart b/lib/page/mine/index_1.dart index c6c4e77..32f854d 100644 --- a/lib/page/mine/index_1.dart +++ b/lib/page/mine/index_1.dart @@ -67,7 +67,7 @@ class _IndexState extends State { child: Wrap( children: [ ListTile( - leading: Icon(Icons.label_outline), + //leading: Icon(Icons.label_outline), title: Text( AppLocalizations.$t('common_mine_1.cn'), ), @@ -77,7 +77,7 @@ class _IndexState extends State { }, ), ListTile( - leading: Icon(Icons.label_outline), + //leading: Icon(Icons.label_outline), title: Text(AppLocalizations.$t('common_mine_1.en')), onTap: () { AppLocalizations.changeLanguage(Locale('en')); @@ -102,7 +102,7 @@ class _IndexState extends State { child: Wrap( children: [ ListTile( - leading: Icon(Icons.label_outline), + //leading: Icon(Icons.label_outline), title: Text( AppLocalizations.$t('mine.loadNetwork'), ), @@ -112,7 +112,7 @@ class _IndexState extends State { }, ), ListTile( - leading: Icon(Icons.label_outline), + //leading: Icon(Icons.label_outline), title: Text(AppLocalizations.$t('mine.loadLocal')), onTap: () { widget.model.dispatch('config', 'setEnv', false); @@ -128,71 +128,49 @@ class _IndexState extends State { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(AppLocalizations.$t('nav_title_1')), - elevation: 0, - centerTitle: true, - ), - body: ListView.builder( - shrinkWrap: true, - itemCount: _getList().length * 2, - itemBuilder: (context, index) { - double _index = index / 2; - if (index % 2 == 0) { - dynamic item = _getList()[_index.toInt()]; - return ListTile( - onTap: () { - actionsEvent(item['index']); - }, - leading: Icon( - IconData( - item['icon'], - fontFamily: 'MaterialIcons', - matchTextDirection: true, - ), - ), - title: Text(item['name']), - ); - } else { - return Divider( - color: Color(AppTheme.lineColor), - ); - } - }, - ), - ); - /* return SingleChildScrollView( + return SingleChildScrollView( child: Column( children: [ Container( decoration: BoxDecoration( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(10), - bottomRight: Radius.circular(10) - ), - color: Colors.red, - ), + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10), + ), + color: Colors.red, + gradient: LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [Colors.red, Colors.blue])), height: 240, child: Stack( alignment: const FractionalOffset(0.8, 0.8), children: [ - Row( - children: [ - Image.network( - 'https://raw.githubusercontent.com/efoxTeam/flutter-ui/master/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png', - width: 80.0, - height: 80.0, - fit: BoxFit.cover, - ), - SizedBox( - width: 10, - ), - Text( - 'Hello Guest', - style: TextStyle(color: Colors.white), - ) - ], + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Opacity( + child: Image.network( + 'https://raw.githubusercontent.com/efoxTeam/flutter-ui/master/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png', + width: 80.0, + height: 80.0, + fit: BoxFit.cover, + ), + opacity: 1, + ), + SizedBox( + height: 10, + ), + Text( + "Flutter-UI", + style: TextStyle( + color: Colors.white, + fontSize: 26, + fontWeight: FontWeight.bold), + ) + ], + ), ) ], ), @@ -226,7 +204,7 @@ class _IndexState extends State { ), ], ), - ); */ + ); } } diff --git a/lib/page/mine/index_2.dart b/lib/page/mine/index_2.dart new file mode 100644 index 0000000..ca75f0c --- /dev/null +++ b/lib/page/mine/index_2.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'dart:io' show Platform; +import 'package:efox_flutter/lang/index.dart' show AppLocalizations; +//import 'package:efox_flutter/router/index.dart' show FluroRouter; +import 'package:efox_flutter/config/theme.dart' show AppTheme; +import 'package:efox_flutter/store/index.dart' show model; +import 'package:efox_flutter/config/color.dart' show materialColor; +import 'package:efox_flutter/utils/appVersion.dart' show AppVersion; +import 'package:efox_flutter/components/expansionTile.dart' as Comp; + +class _IndexState extends State { + @override + void initState() { + super.initState(); + } + + /** + * 国际化 + */ + void openLanguageSelectMenu() async { + await showModalBottomSheet( + context: context, + builder: (BuildContext bc) { + return Container( + child: Wrap( + children: [ + ListTile( + title: Text( + '中文', + ), + onTap: () { + AppLocalizations.changeLanguage(Locale('zh')); + Navigator.pop(context); + }, + ), + ListTile( + title: Text('English'), + onTap: () { + AppLocalizations.changeLanguage(Locale('en')); + Navigator.pop(context); + }, + ), + ], + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + List _EdageList = []; + materialColor.forEach((k, v) { + _EdageList.add(this.Edage(k, v)); + }); + return Scaffold( + appBar: AppBar( + elevation: 0, + centerTitle: true, + title: Text(AppLocalizations.$t('nav_title_1'))), + body: ListView( + children: [ + ListTile( + onTap: () => this.openLanguageSelectMenu(), + leading: Icon(Icons.language), + title: Text(AppLocalizations.$t('common_mine_1.language')), + trailing: Container( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(AppLocalizations.languageCode), + Icon(Icons.navigate_next) + ], + ), + )), + Divider( + color: Color(AppTheme.lineColor), + ), + Comp.ExpansionTile( + leading: Icon(Icons.color_lens), + headerBackgroundColor: Colors.transparent, + title: Row( + children: [ + Text(AppLocalizations.$t('common_mine_1.theme')), + Container( + margin: EdgeInsets.fromLTRB(5, 5, 0, 0), + child: Container( + color: Color(materialColor[model.config.state.theme]), + height: 15, + width: 15, + ), + ) + ], + ), + children: [ + Padding( + padding: EdgeInsets.all(10), + child: Wrap( + spacing: 10, + runSpacing: 5, + children: _EdageList, + ), + ) + ], + ), + Divider( + color: Color(AppTheme.lineColor), + ), + (Platform.isAndroid) + ? ListTile( + onTap: () { + AppVersion().check(context, showTips: true); + }, + leading: Icon(Icons.history), + title: Text(AppLocalizations.$t('common_mine_1.version')), + trailing: Container( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text(model.config.state.appVersion), + Icon(Icons.navigate_next) + ], + ), + )) + : Container(), + (Platform.isAndroid) + ? Divider( + color: Color(AppTheme.lineColor), + ) + : Container(), + ], + )); + } + + Widget Edage(name, color) { + return GestureDetector( + onTap: () { + model.dispatch('config', 'setTheme', name); + }, + child: Container( + color: Color(color), + height: 30, + width: 30, + ), + ); + } +} + +class Index extends StatefulWidget { + final dynamic model; + + Index({Key key, this.model}) : super(key: key); + + @override + _IndexState createState() => _IndexState(); +} diff --git a/lib/store/http.dart b/lib/store/http.dart index 0700e9a..04f73c5 100644 --- a/lib/store/http.dart +++ b/lib/store/http.dart @@ -6,7 +6,7 @@ Dio getDio([Options options]) { connectTimeout: 30 * 1000, receiveTimeout: 30 * 1000, )); // with default Options - dio.interceptors.add(LogInterceptor(responseBody: true)); + // dio.interceptors.add(LogInterceptor(responseBody: true)); return dio; } diff --git a/lib/store/models/config_state_model.dart b/lib/store/models/config_state_model.dart index 1e5adee..4c2b9c0 100644 --- a/lib/store/models/config_state_model.dart +++ b/lib/store/models/config_state_model.dart @@ -2,11 +2,15 @@ import 'dart:convert'; import 'package:efox_flutter/config/index.dart' as Config; import 'package:efox_flutter/store/index.dart' show model; import 'package:efox_flutter/utils/loadAsset.dart' show loadAssets; +import 'package:efox_flutter/utils/localstage.dart' show LocalStorage; +import 'package:package_info/package_info.dart' show PackageInfo; class ConfigInfo { bool isPro = Config.isPro; String version = '1.0'; dynamic env = Config.env; + String theme = 'red'; + String appVersion = '-'; } ConfigInfo _appConfigInfo = new ConfigInfo(); @@ -14,9 +18,22 @@ ConfigInfo _appConfigInfo = new ConfigInfo(); class ConfigModel { get state => _appConfigInfo; - dynamic getVersion () async { + Future getAppVersion() async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + _appConfigInfo.appVersion = await packageInfo.version; + } + + Future getTheme() async { + String theme = await LocalStorage.get('theme'); + if (theme != null) { + _appConfigInfo.theme = theme; + } + } + + dynamic getVersion() async { print('version ${model.config.state.env.versionUrl}'); - String _version = await loadAssets(model.config.state.env.versionUrl).then((resp) { + String _version = + await loadAssets(model.config.state.env.versionUrl).then((resp) { Map res = json.decode(resp); return res['version'].toString() ?? '0.1'; }).catchError((err) { @@ -37,6 +54,13 @@ class ConfigModel { case 'setVersion': _appConfigInfo.version = await this.getVersion(); break; + case 'setTheme': + _appConfigInfo.theme = payload; + LocalStorage.set('theme', payload); + break; + case 'getTheme': + await this.getTheme(); + break; } } } diff --git a/lib/store/models/main_state_model.dart b/lib/store/models/main_state_model.dart index 0808905..dc1ec67 100644 --- a/lib/store/models/main_state_model.dart +++ b/lib/store/models/main_state_model.dart @@ -21,6 +21,9 @@ class MainStateModel extends Model with UserModel { this.state = { 'config': config, }; + //init model data + config.getAppVersion(); + // } /** diff --git a/lib/utils/appVersion.dart b/lib/utils/appVersion.dart new file mode 100644 index 0000000..676a6c2 --- /dev/null +++ b/lib/utils/appVersion.dart @@ -0,0 +1,124 @@ +import 'dart:io'; +import 'dart:convert'; +import 'package:package_info/package_info.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:flutter_downloader/flutter_downloader.dart'; +import 'package:open_file/open_file.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:efox_flutter/store/http.dart' as Http; +import 'package:flutter/material.dart'; + +class AppVersion { + var _context; + Future _checkPermission() async { + PermissionStatus permission = await PermissionHandler() + .checkPermissionStatus(PermissionGroup.storage); + if (permission != PermissionStatus.granted) { + Map permissions = + await PermissionHandler() + .requestPermissions([PermissionGroup.storage]); + if (permissions[PermissionGroup.storage] == PermissionStatus.granted) { + return true; + } + } else { + return true; + } + return false; + } + + // 获取安装地址 + Future get _apkLocalPath async { + final directory = await getExternalStorageDirectory(); + return directory.path; + } + + Future check(context, {showTips: false}) async { + _context = context; + if (!Platform.isAndroid) return; + // permission Status + bool _permissisonReady = await this._checkPermission(); + if (!_permissisonReady) return; + // + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + String version = packageInfo.version; + String platform = Platform.isAndroid ? 'android' : 'ios'; + print('version=$version $platform'); + Map d = await checkVersion(version, platform); + print(d); + if (d['isNew']) { + this._showDialog(context, d); + } else if (showTips) { + Scaffold.of(context).showSnackBar(new SnackBar( + content: new Text('已经是最新版本'), + )); + } + } + + void _showDialog(context, d) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('升级提示'), + content: Text('发现新版本 ${d['version']}'), + actions: [ + FlatButton( + child: Text('取消'), + onPressed: () { + Navigator.pop(context); + }, + ), + FlatButton( + textColor: Theme.of(context).primaryColor, + child: Text('确定'), + onPressed: () async { + await _downAndInstall(d['version']); + Navigator.pop(context); + }, + ) + ], + ); + }); + } + + Future checkVersion(String version, String platform) async { + var res = await Http.get( + 'https://raw.githubusercontent.com/efoxTeam/flutter-ui/master/version.json'); + res = json.decode(res); + print('res=${res['version']}'); + String newVersion = (res['version'] != null) ? res['version'] : version; + //newVersion = '1.0.1'; //debug code + print('$newVersion $res $version'); + bool isNewestVersion = (newVersion == version) ? false : true; + Map d = { + 'version': newVersion, + 'isNew': isNewestVersion, + 'platform': platform + }; + return Future.value(d); + } + + Future _downAndInstall(String version) async { + String _finalApkPath = await _apkLocalPath; + String fileName = 'app-release.apk'; + final taskId = await FlutterDownloader.enqueue( + url: + 'https://github.com/efoxTeam/flutter-ui/releases/download/v$version/$fileName', + savedDir: _finalApkPath, + fileName: fileName, + showNotification: + true, // show download progress in status bar (for Android) + openFileFromNotification: + true, // click on notification to open downloaded file (for Android) + ); + await FlutterDownloader.loadTasks(); + FlutterDownloader.registerCallback((id, status, progress) { + print( + 'Download task ($id) is in status ($status) and process ($progress) status ${DownloadTaskStatus.complete} _finalApkPath=$_finalApkPath'); + if (taskId == id && status == DownloadTaskStatus.complete) { + OpenFile.open(_finalApkPath); + FlutterDownloader.open(taskId: id); + } + }); + } +} diff --git a/lib/utils/github.dart b/lib/utils/github.dart new file mode 100644 index 0000000..a6553d4 --- /dev/null +++ b/lib/utils/github.dart @@ -0,0 +1,38 @@ +/* import 'dart:io'; +import 'package:dio/dio.dart'; + + +Dio _dio([Options options]) { + Dio dio = new Dio(BaseOptions( + baseUrl: 'https://api.github.com/graphql', + contentType: ContentType.parse('application/json;charset=UTF-8'), + connectTimeout: 30 * 1000, + receiveTimeout: 30 * 1000, + )); // with default Options + dio.interceptors.add(LogInterceptor(responseBody: true)); // debug + dio.interceptors + .add(InterceptorsWrapper(onRequest: (RequestOptions options) async { + options.headers["Authorization"] = 'Bearer $token'; + return options; + }, onResponse: (Response response) { + return response; + }, onError: (DioError e) { + return e; + })); //debug + return dio; +} + +Future fetch(String query) async { + Map map = {'query': query}; + return _dio().post('', data: map); +} + + +github.fetch(''' +query { + viewer{ + login + } +} + '''); + */ diff --git a/lib/utils/localstage.dart b/lib/utils/localstage.dart new file mode 100644 index 0000000..22d0c3d --- /dev/null +++ b/lib/utils/localstage.dart @@ -0,0 +1,25 @@ +import 'dart:convert'; +import 'package:shared_preferences/shared_preferences.dart'; + +class LocalStorage { + static Future get(String key) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + return prefs.getString(key); + } + + static Future set(String key, String value) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.setString(key, value); + } + + static Future setJSON(String key, value) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + value = json.encode(value); + prefs.setString(key, value); + } + + static Future remove(String key) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + prefs.remove(key); + } +} diff --git a/lib/widget/gestures/gesturedetector/demo.dart b/lib/widget/gestures/gesturedetector/demo.dart index ae1ef05..0e72d49 100644 --- a/lib/widget/gestures/gesturedetector/demo.dart +++ b/lib/widget/gestures/gesturedetector/demo.dart @@ -1,24 +1,128 @@ import 'package:flutter/material.dart'; - class Index extends StatefulWidget { @override State createState() => _IndexState(); } class _IndexState extends State { + bool isOn = false; + String _value = ''; + String _value2 = ''; @override void initState() { super.initState(); } + updateText(txt) { + setState(() { + _value = txt; + }); + } + + updateText2(txt) { + setState(() { + _value2 = txt; + }); + } + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('GestureDetector'), + automaticallyImplyLeading: false, ), body: Center( - child: Text('更新中'), + child: Column( + children: [ + GestureDetector( + onTap: () { + Scaffold.of(context).showSnackBar( + SnackBar(content: Text('you click the button'))); + }, + child: Icon( + Icons.share, + color: Colors.red, + ), + ), + Divider( + height: 20, + ), + GestureDetector( + onDoubleTap: () { + updateText('onDoubleTap'); + }, + onTapDown: (TapDownDetails e) { + updateText('onTapDown'); + print(e.globalPosition); + }, + onTapCancel: () { + updateText('onTapCancel'); + }, + // 连接点击两次的话,不会触发onTap,只会触发 onDoubleTap + onTap: () { + updateText('onTap'); + setState(() { + isOn = !isOn; + }); + }, + child: Container( + margin: EdgeInsets.all(10), + color: Colors.red, + child: ListTile( + leading: Icon( + Icons.lightbulb_outline, + color: isOn ? Colors.yellow : Colors.grey, + ), + title: Text("Click Here To Change Light"), + ), + ), + ), + Text('Event: $_value'), + Text('Try to Double Click'), + Divider( + height: 40, + ), + Text("使用ForcePress相关属性将不会触发Tap属性"), + GestureDetector( + onForcePressEnd: (ev) { + updateText2('onForcePressEnd ${ev.globalPosition}'); + }, + onForcePressStart: (ev) { + updateText2('onForcePressStart ${ev.globalPosition}'); + }, + onForcePressUpdate: (ev) { + updateText2('onForcePressUpdate ${ev.globalPosition}'); + }, + onForcePressPeak: (ev) { + updateText2('onForcePressPeak ${ev.globalPosition}'); + }, + // 连接点击两次的话,不会触发onTap,只会触发 onDoubleTap + onTap: () { + updateText2('onTap'); + setState(() { + isOn = !isOn; + }); + }, + child: Container( + margin: EdgeInsets.all(10), + alignment: Alignment.center, + height: 50, + color: Colors.red, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Tap or Double Tap is not useful'), + ], + ), + ), + ), + Text(_value2), + Divider( + height: 20, + ), + ], + ), ), ); } diff --git a/lib/widget/gestures/gesturedetector/demo_force_press.dart b/lib/widget/gestures/gesturedetector/demo_force_press.dart new file mode 100644 index 0000000..3796dec --- /dev/null +++ b/lib/widget/gestures/gesturedetector/demo_force_press.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:random_pk/random_pk.dart' show RandomContainer; + +class Index extends StatefulWidget { + @override + State createState() => _IndexState(); +} + +class _IndexState extends State { + String _value1 = ''; + String _value2 = ''; + @override + void initState() { + super.initState(); + } + + updateText(txt) { + print(txt); + setState(() { + _value1 = txt; + }); + } + + updateText2(txt) { + print(txt); + setState(() { + _value2 = txt; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('GestureDetector'), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("使用ForcePress相关属性将不会触发Tap属性"), + GestureDetector( + onForcePressEnd: (ForcePressDetails ev) { + updateText('onForcePressEnd ${ev} ${ev.globalPosition}'); + }, + onForcePressStart: (ForcePressDetails ev) { + updateText('onForcePressStart ${ev} ${ev.globalPosition}'); + }, + onForcePressUpdate: (ForcePressDetails ev) { + updateText('onForcePressUpdate ${ev} ${ev.globalPosition}'); + }, + onForcePressPeak: (ForcePressDetails ev) { + updateText('onForcePressPeak ${ev} ${ev.globalPosition}'); + }, + child: RandomContainer( + height: 100, + width: 200, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Use onForcePressXX properties and Tap or DoubleTap are not useful'), + ], + ), + ), + ), + Divider( + height: 10, + ), + Text(_value1), + Divider( + height: 20, + ), + Text("监听水平或垂直滚动"), + Text('以下是水平滚动,垂直不变化为0'), + Divider( + height: 20, + ), + SizedBox( + height: 100, + width: 200, + child: GestureDetector( + onHorizontalDragDown: (DragDownDetails e) { + updateText2('onHorizontalDragDown $e ${e.globalPosition}'); + }, + onHorizontalDragStart: (DragStartDetails e) { + updateText2('onHorizontalDragStart $e ${e.globalPosition}'); + }, + onHorizontalDragUpdate: (DragUpdateDetails e) { + updateText2('onHorizontalDragUpdate $e ${e.globalPosition}'); + }, + onHorizontalDragEnd: (DragEndDetails e) { + updateText2('onHorizontalDragEnd $e ${e.velocity}'); + }, + onHorizontalDragCancel: () { + updateText2('onHorizontalDragCancel'); + }, + child: RandomContainer( + height: 100, + width: 100, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Use onForcePressXX properties and Tap or DoubleTap are not useful'), + ], + ), + ), + ), + ), + Text(_value2), + ], + ), + ), + ); + } +} diff --git a/lib/widget/gestures/gesturedetector/demo_pan.dart b/lib/widget/gestures/gesturedetector/demo_pan.dart new file mode 100644 index 0000000..bf85e9d --- /dev/null +++ b/lib/widget/gestures/gesturedetector/demo_pan.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; + +class Index extends StatefulWidget { + @override + State createState() => _IndexState(); +} + +class _IndexState extends State { + double _top = 0; + double _left = 0; + @override + void initState() { + super.initState(); + } + + setPanEvent(txt, [ev]) { + print('$txt $ev'); + + if (ev != null && ev.delta != null) { + setState(() { + _top += ev.delta.dy; + _left += ev.delta.dx; + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('GestureDetectorDrag'), + automaticallyImplyLeading: false, + ), + body: ConstrainedBox( + // 占位撑开全屏 + constraints: BoxConstraints.expand(), + child: Stack( + alignment: Alignment.center, + children: [ + Text('Top: $_top, Left: $_left'), + Positioned( + width: 80, + height: 80, + top: _top, + left: _left, + child: GestureDetector( + child: CircleAvatar( + child: Text("Drag"), + ), + onPanStart: (DragStartDetails ev) { + print('onPanStart $ev'); + }, + // DragEndDetails结束时用户滑动的瞬间速度 + onPanEnd: (DragEndDetails ev) { + print('end $ev'); + }, + onPanCancel: () { + setPanEvent('onPanCancel'); + }, + // DragDownDetails返回相对屏幕的位置 + onPanDown: (DragDownDetails ev) { + print('DragDownDetails ${ev.globalPosition}'); + }, + onPanUpdate: (DragUpdateDetails ev) { + setPanEvent('onPanUpdate', ev); + }, + ), + ), + ], + ), + )); + } +} diff --git a/lib/widget/gestures/gesturedetector/demo_scale.dart b/lib/widget/gestures/gesturedetector/demo_scale.dart new file mode 100644 index 0000000..9b8843b --- /dev/null +++ b/lib/widget/gestures/gesturedetector/demo_scale.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:random_pk/random_pk.dart' show RandomContainer; + +class Index extends StatefulWidget { + @override + State createState() => _IndexState(); +} + +class _IndexState extends State { + double _width = 100; + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('GestureDetector'), + automaticallyImplyLeading: false, + ), + body: Center( + child: RandomContainer( + changeOnRedraw: true, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("手势操作图片放大或缩小"), + Divider( + height: 10, + ), + GestureDetector( + child: Image.network( + 'https://avatars3.githubusercontent.com/u/15372930?s=460&v=4', + fit: BoxFit.contain, + width: _width, + ), + onScaleStart: (ScaleStartDetails ev) { + print('ScaleStartDetails $ev'); + }, + onScaleUpdate: (ScaleUpdateDetails ev) { + print("$ev"); + setState(() { + _width = 200 * ev.scale.clamp(.8, 10.0); + }); + }, + onScaleEnd: (ScaleEndDetails ev) { + print('ScaleEndDetails $ev'); + }, + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/widget/gestures/gesturedetector/demo_tap.dart b/lib/widget/gestures/gesturedetector/demo_tap.dart new file mode 100644 index 0000000..7173768 --- /dev/null +++ b/lib/widget/gestures/gesturedetector/demo_tap.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; + +class Index extends StatefulWidget { + @override + State createState() => _IndexState(); +} + +class _IndexState extends State { + bool isOn = false; + String _value = ''; + @override + void initState() { + super.initState(); + } + + updateText(txt) { + setState(() { + _value = txt; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('GestureDetector'), + ), + body: Center( + child: Column( + mainAxisAlignment:MainAxisAlignment.center, + children: [ + Text("点击时,会先触发Tap事件,再触发Pan事件"), + Text("触发外层滚动时,会触发onPanCancel事件"), + GestureDetector( + onPanDown: (DragDownDetails ev) { + print('onPanDown'); + updateText('onPanDown $ev'); + }, + onPanStart: (DragStartDetails ev) { + print('onPanStart'); + updateText('onPanStart $ev'); + }, + onPanUpdate: (DragUpdateDetails ev) { + print('onPanUpdate'); + updateText('onPanUpdate $ev'); + }, + onPanEnd: (DragEndDetails ev) { + print('onPanEnd'); + updateText('onPanEnd $ev'); + }, + onPanCancel: () { + print('onPanCancel'); + updateText('onPanCancel'); + }, + // 连接点击两次的话,不会触发onTap,只会触发 onDoubleTap + onTap: () { + updateText('onTap'); + setState(() { + isOn = !isOn; + }); + }, + child: Container( + margin: EdgeInsets.all(10), + color: Colors.red, + alignment: Alignment.center, + height: 50, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('TURN LIGHTS ON'), + Icon( + Icons.lightbulb_outline, + color: isOn ? Colors.yellow : Colors.grey, + ) + ], + ), + ), + ), + Text(_value), + ], + ), + ), + ); + } +} diff --git a/lib/widget/gestures/gesturedetector/index.dart b/lib/widget/gestures/gesturedetector/index.dart index e1b039b..7e27b43 100644 --- a/lib/widget/gestures/gesturedetector/index.dart +++ b/lib/widget/gestures/gesturedetector/index.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; import 'package:efox_flutter/components/widgetComp.dart' as WidgetComp; import 'demo.dart' as Demo; +import 'demo_tap.dart' as DemoTap; +import 'demo_pan.dart' as DemoPanDrag; +import 'demo_force_press.dart' as DemoForcePress; +import 'demo_scale.dart' as DemoScale; class Index extends StatefulWidget { static String title = 'GestureDetector'; @@ -20,6 +24,10 @@ class _IndexState extends State { mdUrl: Index.mdUrl, demoChild: [ Demo.Index(), + DemoTap.Index(), + DemoPanDrag.Index(), + DemoForcePress.Index(), + DemoScale.Index(), ], ); } diff --git a/lib/widget/vision/transform/demo.dart b/lib/widget/vision/transform/demo.dart index 7774d92..6307466 100644 --- a/lib/widget/vision/transform/demo.dart +++ b/lib/widget/vision/transform/demo.dart @@ -15,10 +15,16 @@ class _IndexState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('SingleChildScrollView'), + title: Text('Transform'), ), - body: Center( - child: Text('更新中'), + body: Transform( + alignment: Alignment.center, + transform: Matrix4.skewY(0.1), + child: Container( + padding: const EdgeInsets.all(8.0), + color: const Color(0xFFE8581C), + child: const Text('Apartment for rent!'), + ), ), ); } diff --git a/locale/en.json b/locale/en.json index de5a86f..46c2ff3 100644 --- a/locale/en.json +++ b/locale/en.json @@ -11,13 +11,15 @@ "changeVersion": "checkVersion", "compProgress": "Components Progress" }, - "common_mine_1" : { + "common_mine_1": { "cn": "CN", "en": "EN", "language": "Select Language", "environment": "Select Environment", "compProgress": "Components Progress", - "success": "Success To Change " + "success": "Success To Change ", + "theme": "Select Theme", + "version": "Version" }, "mine": { "loadNetwork": "Load Network Document Resources", diff --git a/locale/zh.json b/locale/zh.json index 1afd018..8a782c6 100644 --- a/locale/zh.json +++ b/locale/zh.json @@ -11,13 +11,15 @@ "changeVersion": "更新版本", "compProgress": "组件进度" }, - "common_mine_1" : { - "cn": "CN", - "en": "EN", + "common_mine_1": { + "cn": "中文", + "en": "英文", "language": "选择语言", "environment": "选择环境", "compProgress": "组件进度", - "success": "切换成功" + "success": "切换成功", + "theme": "选择主题", + "version": "版本" }, "mine": { "loadNetwork": "网络优良,可选择加载线上文档资源", diff --git a/pubspec.yaml b/pubspec.yaml index 0ac674d..44564e6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ description: A new Flutter project. # 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.1+1 +version: 1.0.2+1 environment: sdk: ">=2.1.0 <3.0.0" @@ -34,6 +34,12 @@ dependencies: fluro: ^1.4.0 flutter_screenutil: ^0.5.1 firebase_analytics: ^2.0.2+1 + random_pk: ^0.0.3 + device_info: ^0.4.0+1 + package_info: ^0.4.0+2 + flutter_downloader: ^1.1.6 + open_file: ^2.0.1+1 + permission_handler: ^3.0.0 dev_dependencies: flutter_test: diff --git a/readme.md b/readme.md index 557e220..f28ff36 100644 --- a/readme.md +++ b/readme.md @@ -1,178 +1,33 @@ -# Flutter UI -![android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png](android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png) +# Flutter UI v1.0.2 -> flutter ui 开发者社区 提供各种flutter相关开发教程 与 demo -## 项目背景 -* Google推出Flutter跨平台解决方案,渐渐地受到了开发者们的关注,结合dart使用,能够用一套代码实现开发iOS与Android两套应用,同时也是谷歌的下一代操作系统 Fuchsia 的开发框架,未来还将可以直接编译成桌面应用。 -* Flutter拥有丰富的组件库,多样化的主题与UI风格,让开发者更简单的上手完成界面交互,从而提高了开发效率。 -* 此套组件库在几次没有硝烟的研讨中,命名为"Flutter UI",使命为"知识千万点,学习第一条。代码快点敲,bug无数行"。没错,就是这么不押韵。 +> flutter 开发者社区 +## 功能清单 ++ widget 组件教程 ++ 多语言切换 ++ 多主题切换 ++ 自动更新检测 ++ firebase 崩溃监控 + +## apk 下载 +![安卓包下载](readme/apk.png) +[安卓包下载](https://github.com/efoxTeam/flutter-ui/releases/download/v1.0.2/app-release.apk) -## Release Apk 安装包 -![apk download](readme/apk.png) -[历史版本](https://github.com/efoxTeam/flutter-ui/releases) +## Demo 预览 + + -## 开发者如何参与完善控件 - +## 项目相关 ++ [apk包历史版本](https://github.com/efoxTeam/flutter-ui/releases) ++ [组件开发进度](readme/widget_progress.md) ++ [贡献PR参考](readme/pr.md) -欢迎有兴趣的小伙伴QQ扫描以上二维码参与进来,一同完善组件 -同时也可以把相关问题通过[issues](https://github.com/efoxTeam/flutter-ui/issues)方式与我们联系 -[开发者如何参与完善控件](readme/pr.md) +## 项目交流 + -## 环境与构建 -* 自行完成flutter环境配置 -* 通过 fork 项目master分支代码,进入项目 -* 切换flutter到master分支,步骤如下(若已切换可跳过) - 1. flutter channel master // 选择master分支 - 2. flutter upgrade // 更新代码 -* 运行模拟器或真机 -* flutter run //运行程序 -* flutter build apk //生成apk -## app预览 -![Alt 预览](readme/flutter_ui2.gif) -![Alt 预览](readme/flutter_ui3.gif) -## 目录 -``` -Flutter UI - ├─assets 静态资源 - ├─docs 教程文件 - ├─locale 语言包 - ├─lib dart执行代码 - ├─components - ├─config 配置文件 - ├─controller - ├─lang 多语言控制类 - ├─page 路由关联页面 - │ ├─component - │ └─mine - ├─router 路由 - ├─store 数据管理 - │ ├─models - │ └─objects - ├─utils 项目工具类 - └─widget 项目组件库 - ├─animate - │ ├─animatedbuilder 【✔️ v1.0】 - │ ├─animatedcontainer - │ ├─animatedcrossfade - │ ├─animateddefaulttextstyle - │ ├─animatedliststate - │ ├─animatedmodalbarrier - │ ├─animatedopacity - │ ├─animatedphysicalmodel - │ ├─animatedpositioned - │ ├─animatedsize - │ ├─animatedwidget - │ ├─animatedwidgetbasestate - │ ├─animationcontroller - │ ├─decoratedboxtransition - │ ├─fadetransition - │ ├─hero - │ ├─positionedtransition - │ ├─rotationtransition - │ ├─scaletransition - │ ├─sizetransition - │ └─slidetransition - ├─bulletbox - │ ├─alertdialog 【✔️ v1.0】 - │ ├─bottomsheet 【✔️ v1.0】 - │ ├─expansionPanel 【✔️ v1.0】 - │ ├─simpledialog 【✔️ v1.0】 - │ └─snackbar 【✔️ v1.0】 - ├─common - │ ├─assetbundle - │ ├─buttonbar - │ ├─chip - │ ├─container 【✔️ v1.0】 - │ ├─divider 【✔️ v1.0】 - │ ├─flatbutton 【✔️ v1.0】 - │ ├─icon 【✔️ v1.0】 - │ ├─iconbutton - │ ├─image - │ ├─listtile - │ ├─placeholder - │ ├─raisedbutton - │ ├─rawimage - │ ├─stepper - │ ├─text 【✔️ v1.0】 - │ └─tooltip - ├─form - │ ├─checkbox 【✔️ v1.0】 - │ ├─checkboxlisttile 【✔️ v1.0】 - │ ├─slider 【✔️ v1.0】 - │ ├─switch 【✔️ v1.0】 - │ ├─switchListTile 【✔️ v1.0】 - │ ├─daypicker 【✔️ v1.0】 - │ ├─radio 【✔️ v1.0】 - │ ├─radioListTile 【✔️ v1.0】 - │ ├─form - │ ├─formfield - │ ├─rawkeyboard - │ ├─textfield 【✔️ v1.0】 - │ └─textinput - ├─gestures - │ ├─absorbpointer - │ ├─dismissible - │ ├─dragtarget - │ ├─gesturedetector - │ ├─ignorepointer - │ └─longpressdraggable 【✔️ v1.0】 - ├─navigator - │ ├─appbar 【✔️ v1.0】 - │ ├─bottomnavigationbar 【✔️ v1.0】 - │ ├─drawer 【✔️ v1.0】 - │ ├─floatingactionbutton 【✔️ v1.0】 - │ ├─materialapp - │ ├─navigator - │ ├─popupmenubutton - │ ├─scaffold 【✔️ v1.0】 - │ ├─tabbar - │ ├─tabbarview - │ └─widgetsapp - ├─regular - │ ├─align 【✔️ v1.0】 - │ ├─aspectratio 【✔️ v1.0】 - │ ├─center 【✔️ v1.0】 - │ ├─column 【✔️ v1.0】 - │ ├─constrainedbox 【✔️ v1.0】 - │ ├─container 【✔️ v1.0】 - │ ├─fittedbox 【✔️ v1.0】 - │ ├─flow 【✔️ v1.0】 - │ ├─layoutbuilder 【✔️ v1.0】 - │ ├─listbody 【✔️ v1.0】 - │ ├─listview 【✔️ v1.0】 - │ ├─padding 【✔️ v1.0】 - │ ├─row 【✔️ v1.0】 - │ ├─stack 【✔️ v1.0】 - │ ├─table 【✔️ v1.0】 - │ └─wrap 【✔️ v1.0】 - ├─scrollview - │ ├─customscrollview 【✔️ v1.0】 - │ ├─gridview 【✔️ v1.0】 - │ ├─listview 【✔️ v1.0】 - │ ├─nestedscrollview 【✔️ v1.0】 - │ ├─scrollable 【✔️ v1.0】 - │ ├─scrollbar 【✔️ v1.0】 - │ ├─scrollcontroller 【✔️ v1.0】 - │ └─singlechildscrollview 【✔️ v1.0】 - └─vision - ├─backdropfilter - ├─clipoval - ├─clippath - ├─cliprect - ├─custompaint - ├─decoratedbox - ├─fractionaltranslation - ├─mediaquery - ├─opacity - ├─rotatedbox - ├─theme - └─transform -``` - diff --git a/readme/1.0.2/1.jpg b/readme/1.0.2/1.jpg new file mode 100644 index 0000000..4ff83ed Binary files /dev/null and b/readme/1.0.2/1.jpg differ diff --git a/readme/1.0.2/2.jpg b/readme/1.0.2/2.jpg new file mode 100644 index 0000000..ad90299 Binary files /dev/null and b/readme/1.0.2/2.jpg differ diff --git a/readme/1.0.2/3.jpg b/readme/1.0.2/3.jpg new file mode 100644 index 0000000..4db129e Binary files /dev/null and b/readme/1.0.2/3.jpg differ diff --git a/readme/1.0.2/4.jpg b/readme/1.0.2/4.jpg new file mode 100644 index 0000000..9d1b4dc Binary files /dev/null and b/readme/1.0.2/4.jpg differ diff --git a/readme/1.0.2/5.jpg b/readme/1.0.2/5.jpg new file mode 100644 index 0000000..0875087 Binary files /dev/null and b/readme/1.0.2/5.jpg differ diff --git a/readme/1.0.2/6.jpg b/readme/1.0.2/6.jpg new file mode 100644 index 0000000..0070e0f Binary files /dev/null and b/readme/1.0.2/6.jpg differ diff --git a/readme/apk.png b/readme/apk.png index da4584b..2d9503f 100644 Binary files a/readme/apk.png and b/readme/apk.png differ diff --git a/readme/background.md b/readme/background.md new file mode 100644 index 0000000..52da3d6 --- /dev/null +++ b/readme/background.md @@ -0,0 +1,4 @@ +# 项目背景 +* Google推出Flutter跨平台解决方案,渐渐地受到了开发者们的关注,结合dart使用,能够用一套代码实现开发iOS与Android两套应用,同时也是谷歌的下一代操作系统 Fuchsia 的开发框架,未来还将可以直接编译成桌面应用。 +* Flutter拥有丰富的组件库,多样化的主题与UI风格,让开发者更简单的上手完成界面交互,从而提高了开发效率。 +* 此套组件库在几次没有硝烟的研讨中,命名为"Flutter UI",使命为"知识千万点,学习第一条。代码快点敲,bug无数行"。没错,就是这么不押韵。 \ No newline at end of file diff --git a/readme/pr.md b/readme/pr.md index 25deb2c..6101f11 100644 --- a/readme/pr.md +++ b/readme/pr.md @@ -4,6 +4,7 @@ * fork 一份到自己的仓库 * git clone (您的仓库地址) 拉取项目到本地 * 打开项目 flutter-ui/lib/config/index.dart,修改里面isPro变量为false,(目的加载本地文件) +* ### 完善文档 * 完善控件文档的,如要完善Animate下的AnimationController控件的文档,您可打开flutter-ui/docs/widget/animate/animationcontroller/index.md,构建AnimationController控件的说明文档,该文档包括但不限于(控件介绍,控件构造方法,控件属性介绍) @@ -41,3 +42,13 @@ + test:增加测试 + chore:构建过程或辅助工具的变动 +## 环境与构建 +* 自行完成flutter环境配置 +* 通过 fork 项目master分支代码,进入项目 +* 切换flutter到master分支,步骤如下(若已切换可跳过) + 1. flutter channel master // 选择master分支 + 2. flutter upgrade // 更新代码 +* 运行模拟器或真机 +* flutter run //运行程序 +* flutter build apk //生成apk + diff --git a/readme/qq.md b/readme/qq.md new file mode 100644 index 0000000..e0f248d --- /dev/null +++ b/readme/qq.md @@ -0,0 +1,5 @@ +# 开发者如何参与完善控件 + + +欢迎有兴趣的小伙伴QQ扫描以上二维码参与进来,一同完善组件 +同时也可以把相关问题通过[issues](https://github.com/efoxTeam/flutter-ui/issues)方式与我们联系 diff --git a/readme/v1.0.0_preview.md b/readme/v1.0.0_preview.md new file mode 100644 index 0000000..afc9e73 --- /dev/null +++ b/readme/v1.0.0_preview.md @@ -0,0 +1,5 @@ + +# app预览 + +![Alt 预览](flutter_ui2.gif) +![Alt 预览](flutter_ui3.gif) \ No newline at end of file diff --git a/readme/widget_progress.md b/readme/widget_progress.md new file mode 100644 index 0000000..a107eaa --- /dev/null +++ b/readme/widget_progress.md @@ -0,0 +1,138 @@ +# 组件开发进度 +``` +Flutter UI + ├─assets 静态资源 + ├─docs 教程文件 + ├─locale 语言包 + ├─lib dart执行代码 + ├─components + ├─config 配置文件 + ├─controller + ├─lang 多语言控制类 + ├─page 路由关联页面 + │ ├─component + │ └─mine + ├─router 路由 + ├─store 数据管理 + │ ├─models + │ └─objects + ├─utils 项目工具类 + └─widget 项目组件库 + ├─animate + │ ├─animatedbuilder 【✔️ v1.0】 + │ ├─animatedcontainer + │ ├─animatedcrossfade + │ ├─animateddefaulttextstyle + │ ├─animatedliststate + │ ├─animatedmodalbarrier + │ ├─animatedopacity + │ ├─animatedphysicalmodel + │ ├─animatedpositioned + │ ├─animatedsize + │ ├─animatedwidget + │ ├─animatedwidgetbasestate + │ ├─animationcontroller + │ ├─decoratedboxtransition + │ ├─fadetransition + │ ├─hero + │ ├─positionedtransition + │ ├─rotationtransition + │ ├─scaletransition + │ ├─sizetransition + │ └─slidetransition + ├─bulletbox + │ ├─alertdialog 【✔️ v1.0】 + │ ├─bottomsheet 【✔️ v1.0】 + │ ├─expansionPanel 【✔️ v1.0】 + │ ├─simpledialog 【✔️ v1.0】 + │ └─snackbar 【✔️ v1.0】 + ├─common + │ ├─assetbundle + │ ├─buttonbar + │ ├─chip + │ ├─container 【✔️ v1.0】 + │ ├─divider 【✔️ v1.0】 + │ ├─flatbutton 【✔️ v1.0】 + │ ├─icon 【✔️ v1.0】 + │ ├─iconbutton + │ ├─image + │ ├─listtile + │ ├─placeholder + │ ├─raisedbutton + │ ├─rawimage + │ ├─stepper + │ ├─text 【✔️ v1.0】 + │ └─tooltip + ├─form + │ ├─checkbox 【✔️ v1.0】 + │ ├─checkboxlisttile 【✔️ v1.0】 + │ ├─slider 【✔️ v1.0】 + │ ├─switch 【✔️ v1.0】 + │ ├─switchListTile 【✔️ v1.0】 + │ ├─daypicker 【✔️ v1.0】 + │ ├─radio 【✔️ v1.0】 + │ ├─radioListTile 【✔️ v1.0】 + │ ├─form + │ ├─formfield + │ ├─rawkeyboard + │ ├─textfield 【✔️ v1.0】 + │ └─textinput + ├─gestures + │ ├─absorbpointer + │ ├─dismissible + │ ├─dragtarget + │ ├─gesturedetector + │ ├─ignorepointer + │ └─longpressdraggable 【✔️ v1.0】 + ├─navigator + │ ├─appbar 【✔️ v1.0】 + │ ├─bottomnavigationbar 【✔️ v1.0】 + │ ├─drawer 【✔️ v1.0】 + │ ├─floatingactionbutton 【✔️ v1.0】 + │ ├─materialapp + │ ├─navigator + │ ├─popupmenubutton + │ ├─scaffold 【✔️ v1.0】 + │ ├─tabbar + │ ├─tabbarview + │ └─widgetsapp + ├─regular + │ ├─align 【✔️ v1.0】 + │ ├─aspectratio 【✔️ v1.0】 + │ ├─center 【✔️ v1.0】 + │ ├─column 【✔️ v1.0】 + │ ├─constrainedbox 【✔️ v1.0】 + │ ├─container 【✔️ v1.0】 + │ ├─fittedbox 【✔️ v1.0】 + │ ├─flow 【✔️ v1.0】 + │ ├─layoutbuilder 【✔️ v1.0】 + │ ├─listbody 【✔️ v1.0】 + │ ├─listview 【✔️ v1.0】 + │ ├─padding 【✔️ v1.0】 + │ ├─row 【✔️ v1.0】 + │ ├─stack 【✔️ v1.0】 + │ ├─table 【✔️ v1.0】 + │ └─wrap 【✔️ v1.0】 + ├─scrollview + │ ├─customscrollview 【✔️ v1.0】 + │ ├─gridview 【✔️ v1.0】 + │ ├─listview 【✔️ v1.0】 + │ ├─nestedscrollview 【✔️ v1.0】 + │ ├─scrollable 【✔️ v1.0】 + │ ├─scrollbar 【✔️ v1.0】 + │ ├─scrollcontroller 【✔️ v1.0】 + │ └─singlechildscrollview 【✔️ v1.0】 + └─vision + ├─backdropfilter + ├─clipoval + ├─clippath + ├─cliprect + ├─custompaint + ├─decoratedbox + ├─fractionaltranslation + ├─mediaquery + ├─opacity + ├─rotatedbox + ├─theme + └─transform +``` \ No newline at end of file