diff --git a/assets/images/ali_connors.png b/assets/images/ali_connors.png new file mode 100644 index 00000000..b7e34322 Binary files /dev/null and b/assets/images/ali_connors.png differ diff --git a/flutter_01.png b/flutter_01.png new file mode 100644 index 00000000..16a118fc Binary files /dev/null and b/flutter_01.png differ diff --git a/lib/blocs/bak/search_api.dart b/lib/blocs/bak/search_api.dart new file mode 100644 index 00000000..0548d34e --- /dev/null +++ b/lib/blocs/bak/search_api.dart @@ -0,0 +1,35 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 3:20 PM + * email: zhu.yan@alibaba-inc.com + * tartget: FlatButton 的示例 + */ +import 'dart:async'; +import 'package:dio/dio.dart'; +import 'dart:convert'; +import './search_result.dart'; +import 'package:html/parser.dart' show parse; + +var dio = new Dio(); +class Api { + Future> search(name) async { + print('=========>>>'); + var response = await dio.get("https://www.so.com/s?ie=utf-8&q=$name"); +// var document = parse(response.data); +// var app = document.querySelectorAll('.res-title a'); + List res = []; +// app.forEach((f) { +// res.add( +// SearchResult( +// title: f.text, +// source: f.attributes["data-url"] ?? f.attributes["href"], +// ), +// ); +// }); + return res; + } +} + +Api api = Api(); \ No newline at end of file diff --git a/lib/blocs/bak/search_bloc.dart b/lib/blocs/bak/search_bloc.dart new file mode 100644 index 00000000..08d526c8 --- /dev/null +++ b/lib/blocs/bak/search_bloc.dart @@ -0,0 +1,35 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 7:17 PM + * email: zhu.yan@alibaba-inc.com + * tartget: + */ +import 'dart:async'; +import 'package:bloc/bloc.dart'; + +import './search_event.dart'; +import './search_state.dart'; +import './search_api.dart'; + + +/// 这里导入api类与上面的SearchEvent与SearchState文件 + +class SearchBloc extends Bloc { + @override + SearchState get initialState => SearchUninitialized(); + + @override + Stream mapEventToState(SearchEvent event,) async* { + if (event is SearchFetch) { + try { + yield SearchLoading(); + final res = await api.search(event.query); + yield SearchLoaded(res: res); + } catch (_) { + yield SearchError(); + } + } + } +} \ No newline at end of file diff --git a/lib/blocs/bak/search_event.dart b/lib/blocs/bak/search_event.dart new file mode 100644 index 00000000..0ecc0b44 --- /dev/null +++ b/lib/blocs/bak/search_event.dart @@ -0,0 +1,18 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 7:18 PM + * email: zhu.yan@alibaba-inc.com + * tartget: + */ +abstract class SearchEvent {} + +class SearchFetch extends SearchEvent { + final String query; + + SearchFetch({this.query}); + + @override + String toString() => 'SearchFetch:获取搜索结果事件'; +} \ No newline at end of file diff --git a/lib/blocs/bak/search_result.dart b/lib/blocs/bak/search_result.dart new file mode 100644 index 00000000..8001e49a --- /dev/null +++ b/lib/blocs/bak/search_result.dart @@ -0,0 +1,14 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 7:11 PM + * email: zhu.yan@alibaba-inc.com + * tartget: + */ +class SearchResult { + String title; + String source; + + SearchResult({this.title, this.source}); +} \ No newline at end of file diff --git a/lib/blocs/bak/search_state.dart b/lib/blocs/bak/search_state.dart new file mode 100644 index 00000000..85d85791 --- /dev/null +++ b/lib/blocs/bak/search_state.dart @@ -0,0 +1,37 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 7:13 PM + * email: zhu.yan@alibaba-inc.com + * tartget: + */ +import './search_result.dart'; + +abstract class SearchState {} + +class SearchError extends SearchState { + @override + String toString() => 'SearchError:获取失败'; +} + +class SearchUninitialized extends SearchState { + @override + String toString() => 'SearchUninitialized:未初始化'; +} + +class SearchLoading extends SearchState { + @override + String toString() => 'SearchLoading :正在加载'; +} + +class SearchLoaded extends SearchState { + final List res; + + SearchLoaded({ + this.res, + }); + + @override + String toString() => 'SearchLoaded:加载完毕'; +} \ No newline at end of file diff --git a/lib/blocs/bak/search_widget.dart b/lib/blocs/bak/search_widget.dart new file mode 100644 index 00000000..1e29848a --- /dev/null +++ b/lib/blocs/bak/search_widget.dart @@ -0,0 +1,80 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 7:19 PM + * email: zhu.yan@alibaba-inc.com + * tartget: + */ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +//import 'package:suiyi/blocs/search/bloc.dart'; +import './search_event.dart'; +import './search_state.dart'; +import './search_bloc.dart'; + + +class SearchWidget extends StatefulWidget { + final SearchDelegate delegate; + final String query; + SearchWidget({this.delegate, this.query}); + @override + _SearchWidgetState createState() => _SearchWidgetState(); +} + +class _SearchWidgetState extends State { + final SearchBloc _search = SearchBloc(); + String old; + @override + void dispose() { + _search.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + print('1:${old}'); + print('2:${widget.query}'); + if (old != widget.query) { + _search.dispatch(SearchFetch(query: widget.query)); + old = widget.query; + } + return BlocBuilder( + bloc: _search, + builder: (BuildContext context, SearchState state) { + print('-------${state}'); + if (state is SearchUninitialized || state is SearchLoading) { + return Center( + child: CircularProgressIndicator(), + ); + } else if (state is SearchError) { + return Center( + child: Text('获取失败'), + ); + } else if (state is SearchLoaded) { + return ListView.builder( + itemBuilder: (BuildContext context, int index) { + return ListTile( + dense: true, + leading: Icon( + Icons.bookmark_border, + size: 32, + ), + title: Text( + state.res[index].title, + overflow: TextOverflow.ellipsis, + ), + subtitle: Text(state.res[index].source), + onTap: () { + // 在这里对选中的结果进行解析,因为我目前是用golang实现的,所以就没贴代码了。 + print(state.res[index].source); + }, + ); + }, + itemCount: state.res.length, + ); + } + }, + ); + } +} \ No newline at end of file diff --git a/lib/blocs/industry_api.dart b/lib/blocs/industry_api.dart new file mode 100644 index 00000000..fc8b2967 --- /dev/null +++ b/lib/blocs/industry_api.dart @@ -0,0 +1,71 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 3:20 PM + * email: zhu.yan@alibaba-inc.com + * tartget: FlatButton 的示例 + */ +import 'dart:async'; +import 'package:dio/dio.dart'; +import 'dart:convert'; +import 'package:html/parser.dart' show parse; +import './industry_model.dart'; +import './search_result.dart'; + +var dio = new Dio(); +//class Api2 { +// /// 关键字提示(起点) +// Future> suggestion(String query) async { +//// http.Response response = await http.get( +//// "https://www.qidian.com/ajax/Search/AutoComplete?siteid=1&query=$query"); +// var response = await dio.get("https://www.qidian.com/ajax/Search/AutoComplete?siteid=1&query=$query", data: {}); +// //var response = await dio.get("https://www.so.com/s?ie=utf-8&q=$query"); +// print('1=====>${query}'); +// print('2=====>${response.data}'); +// //var data = Suggestion.fromJson(json.decode(response.body)); +// //var data = Suggestion.fromJson(json.decode(response.data)); +// var data = Suggestion.fromJson(json.decode(response.data)); +// List suggestion = []; +// data.suggestions.forEach((k) { +// //print('=====>${k.value}'); +// suggestion.add(k.value); +// }); +// +// return Future.delayed(Duration(seconds:2), () { +// return suggestion; +// }); +// //return suggestion; +// } +//} +class Api { + /// 关键字提示(起点) + Future> suggestion(String query) async { +// http.Response response = await http.get( +// "https://www.qidian.com/ajax/Search/AutoComplete?siteid=1&query=$query"); + /// var response = await dio.get("https://www.qidian.com/ajax/Search/AutoComplete?siteid=1&query=$query", data: {}); + var response = await dio.get("https://www.so.com/s?ie=utf-8&q=$query flutter"); + var document = parse(response.data); + var app = document.querySelectorAll('.res-title a'); + ///print('1=====>${query}'); + ///print('2=====>${response.data}'); + ////print('3=====>${app}'); + List res = []; + app.forEach((f) { + ///print('f==>${f}'); + res.add( + SearchResult( + title: f.text, + source: f.attributes["data-url"] ?? f.attributes["href"], + ), + ); + }); + + return Future.delayed(Duration(seconds:2), () { + return res; + }); + //return suggestion; + } +} + +Api api = Api(); \ No newline at end of file diff --git a/lib/blocs/industry_bloc.dart b/lib/blocs/industry_bloc.dart new file mode 100644 index 00000000..3bcd464c --- /dev/null +++ b/lib/blocs/industry_bloc.dart @@ -0,0 +1,41 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 5:19 PM + * email: zhu.yan@alibaba-inc.com + * tartget: + */ +import 'dart:async'; +import 'package:bloc/bloc.dart'; +import './industry_api.dart'; +import './industry_event.dart'; +import './industry_state.dart'; + +class SuggestionBloc extends Bloc { + @override + SuggestionState get initialState => SuggestionUninitialized(); + @override + Stream mapEventToState(SuggestionEvent event)async* { + //Stream mapEventToState(SuggestionState currentState, SuggestionEvent event,) async* { + if (event is SuggestionFetch) { + //print('event==>${event}'); + try { + yield SuggestionLoading(); + final res = await api.suggestion(event.query); + print('res====>${res}'); + yield SuggestionLoaded(res: res); + } catch (_) { + yield SuggestionError(); + } + } + if (event is SuggestionClearFetch) { + //print('event==>${event}'); + try { + yield SuggestionUninitialized(); + } catch (_) { + yield SuggestionError(); + } + } + } +} diff --git a/lib/blocs/industry_event.dart b/lib/blocs/industry_event.dart new file mode 100644 index 00000000..641720b2 --- /dev/null +++ b/lib/blocs/industry_event.dart @@ -0,0 +1,26 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 3:35 PM + * email: zhu.yan@alibaba-inc.com + */ +abstract class SuggestionEvent {} + +class SuggestionFetch extends SuggestionEvent { + final String query; + + SuggestionFetch({this.query}); + + @override + String toString() => 'SuggestionFetch:获取关键字提示事件'; +} + +class SuggestionClearFetch extends SuggestionEvent { + final String query; + + SuggestionClearFetch({this.query}); + + @override + String toString() => 'SuggestionClearFetch:清空界面'; +} \ No newline at end of file diff --git a/lib/blocs/industry_main.dart b/lib/blocs/industry_main.dart new file mode 100644 index 00000000..22dfbc0a --- /dev/null +++ b/lib/blocs/industry_main.dart @@ -0,0 +1,79 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 3:52 PM + * email: zhu.yan@alibaba-inc.com + */ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import './industry_bloc.dart'; +import './industry_event.dart'; +import './industry_state.dart'; + +final SuggestionBloc suggestion = SuggestionBloc(); + +class IndustryPage extends StatefulWidget { + final Function itemTitle; + IndustryPage({Key key,this.itemTitle}) : super(key: key); + + @override + _IndustryState createState() => _IndustryState(); +} + +class _IndustryState extends State { + @override + Widget build(BuildContext context) { + return Material( + child: Column( + children: [ +// TextField( +// autofocus: true, +// textAlign: TextAlign.center, +// onSubmitted: (text) { +// print('onSubmitted:${text}'); +// suggestion.dispatch(SuggestionFetch(query: text)); +// }, +// ), + Expanded( + child: BlocBuilder( + bloc: suggestion, + builder: (BuildContext context, SuggestionState state) { + print('BlocBuilder----${state}'); + if (state is SuggestionUninitialized) { + return Center( + child: Text('暂无内容'), + ); + } else if (state is SuggestionLoading) { + return Center( + child: CircularProgressIndicator(), + ); + } else if (state is SuggestionError) { + return Center( + child: Text('出现错误'), + ); + } else if (state is SuggestionLoaded) { + if (state.res.length == 0) { + return Center( + child: Text('没有适合的结果,更换查询条件试试'), + ); + }else { + if (widget.itemTitle is Function) { + return widget.itemTitle(state); + } + } + } + }, + ), + ), + ], + ), + ); + } + + @override + void dispose() { + //suggestion.dispose();//添加这个第二次进入会失灵 + super.dispose(); + } +} \ No newline at end of file diff --git a/lib/blocs/industry_model.dart b/lib/blocs/industry_model.dart new file mode 100644 index 00000000..ea6cb081 --- /dev/null +++ b/lib/blocs/industry_model.dart @@ -0,0 +1,72 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 3:19 PM + * email: zhu.yan@alibaba-inc.com + */ +class Suggestion { + String query; + List suggestions; + int code; + + Suggestion({this.query, this.suggestions, this.code}); + + Suggestion.fromJson(Map json) { + query = json['query']; + if (json['suggestions'] != null) { + suggestions = new List(); + json['suggestions'].forEach((v) { + suggestions.add(new Suggestions.fromJson(v)); + }); + } + code = json['code']; + } + + Map toJson() { + final Map data = new Map(); + data['query'] = this.query; + if (this.suggestions != null) { + data['suggestions'] = this.suggestions.map((v) => v.toJson()).toList(); + } + data['code'] = this.code; + return data; + } +} + +class Suggestions { + Data data; + String value; + + Suggestions({this.data, this.value}); + + Suggestions.fromJson(Map json) { + data = json['data'] != null ? new Data.fromJson(json['data']) : null; + value = json['value']; + } + + Map toJson() { + final Map data = new Map(); + if (this.data != null) { + data['data'] = this.data.toJson(); + } + data['value'] = this.value; + return data; + } +} + +class Data { + String category; + + Data({this.category}); + + Data.fromJson(Map json) { + category = json['category']; + } + + Map toJson() { + final Map data = new Map(); + data['category'] = this.category; + return data; + } +} \ No newline at end of file diff --git a/lib/blocs/industry_state.dart b/lib/blocs/industry_state.dart new file mode 100644 index 00000000..b129830c --- /dev/null +++ b/lib/blocs/industry_state.dart @@ -0,0 +1,34 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 3:37 PM + * email: zhu.yan@alibaba-inc.com + */ +abstract class SuggestionState {} + +class SuggestionError extends SuggestionState { + @override + String toString() => 'SuggestionError:获取失败'; +} + +class SuggestionUninitialized extends SuggestionState { + @override + String toString() => 'SuggestionUninitialized:未初始化'; +} + +class SuggestionLoading extends SuggestionState { + @override + String toString() => 'SuggestionLoading :正在加载'; +} + +class SuggestionLoaded extends SuggestionState { + final List res; + + SuggestionLoaded({ + this.res, + }); + + @override + String toString() => 'SuggestionLoaded:加载完毕'; +} diff --git a/lib/blocs/search_result.dart b/lib/blocs/search_result.dart new file mode 100644 index 00000000..8001e49a --- /dev/null +++ b/lib/blocs/search_result.dart @@ -0,0 +1,14 @@ +/** + * Created with Android Studio. + * User: 一晟 + * Date: 2019/4/28 + * Time: 7:11 PM + * email: zhu.yan@alibaba-inc.com + * tartget: + */ +class SearchResult { + String title; + String source; + + SearchResult({this.title, this.source}); +} \ No newline at end of file diff --git a/lib/components/home_banner.dart b/lib/components/home_banner.dart index 5dd335fd..36345cbe 100644 --- a/lib/components/home_banner.dart +++ b/lib/components/home_banner.dart @@ -42,6 +42,18 @@ class _BannerState extends State { timer.cancel(); } + /// pagination 的计数器 + Widget _numberIndicator(BuildContext context, int index, int itemCount) { + return Container( + decoration: BoxDecoration( + color: Colors.black45, borderRadius: BorderRadius.circular(20.0)), + margin: EdgeInsets.only(top: 10.0, right: 5.0), + padding: EdgeInsets.symmetric(horizontal: 6.0, vertical: 2.0), + child: Text("${++index}/$itemCount", + style: TextStyle( color: Colors.white70, fontSize: 12.0, fontWeight:FontWeight.bold )), + ); + } + @override Widget build(BuildContext context) { return Container( @@ -54,6 +66,11 @@ class _BannerState extends State { onPageChanged: _onPageChanged, children: _buildItems(),), _buildIndicator(), // 下面的小点 + Positioned(//方法二 + top: 0.0, + right: 0.0, + child: _numberIndicator(context,virtualIndex,widget.bannerStories.length), + ) ]), ); } diff --git a/lib/main.dart b/lib/main.dart index 8f403125..5c6a8c1f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,7 +6,7 @@ import 'routers/application.dart'; import 'package:flutter_go/utils/provider.dart'; import 'package:flutter_go/utils/shared_preferences.dart'; -import 'package:flutter_go/views/first_page/home.dart'; +import 'package:flutter_go/views/home.dart'; import 'package:flutter_go/model/search_history.dart'; import 'package:flutter_go/utils/analytics.dart' as Analytics; //import 'views/welcome_page/index.dart'; diff --git a/lib/routers/router_handler.dart b/lib/routers/router_handler.dart index 01519cd5..6105301c 100644 --- a/lib/routers/router_handler.dart +++ b/lib/routers/router_handler.dart @@ -5,7 +5,7 @@ import 'package:flutter_go/components/category.dart'; import '../widgets/404.dart'; 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/first_page/home.dart'; +import 'package:flutter_go/views/home.dart'; // app的首页 var homeHandler = new Handler( diff --git a/lib/views/first_page/first_page.dart b/lib/views/first_page/first_page.dart index 714bed38..1fded132 100644 --- a/lib/views/first_page/first_page.dart +++ b/lib/views/first_page/first_page.dart @@ -80,8 +80,8 @@ class FirstPageState extends State with AutomaticKeepAliveClientMixin return result; } + /// 每个item的样式 Widget makeCard(index,item){ - var myTitle = '${item.title}'; var myUsername = '${'👲'}: ${item.username} '; var codeUrl = '${item.detailUrl}'; @@ -92,18 +92,18 @@ class FirstPageState extends State with AutomaticKeepAliveClientMixin return 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) - ), - ]), - SizedBox(height: 1, child:Container(color: Theme.of(context).primaryColor)), - SizedBox(height: 10), + Stack( + //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), ], ); diff --git a/lib/views/first_page/main_app_bar.dart b/lib/views/first_page/main_app_bar.dart new file mode 100644 index 00000000..c85e4cde --- /dev/null +++ b/lib/views/first_page/main_app_bar.dart @@ -0,0 +1,415 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +const double _kLeadingWidth = + kToolbarHeight; // So the leading button is square. + +// Bottom justify the kToolbarHeight child which may overflow the top. +class _ToolbarContainerLayout extends SingleChildLayoutDelegate { + const _ToolbarContainerLayout(); + + @override + BoxConstraints getConstraintsForChild(BoxConstraints constraints) { + return constraints.tighten(height: kToolbarHeight); + } + + @override + Size getSize(BoxConstraints constraints) { + return Size(constraints.maxWidth, kToolbarHeight); + } + + @override + Offset getPositionForChild(Size size, Size childSize) { + return Offset(0.0, size.height - childSize.height); + } + + @override + bool shouldRelayout(_ToolbarContainerLayout oldDelegate) => false; +} + +class MyAppBar extends StatefulWidget implements PreferredSizeWidget { + /// Creates a material design app bar. + /// + /// The arguments [elevation], [primary], [toolbarOpacity], [bottomOpacity] + /// and [automaticallyImplyLeading] must not be null. + /// + /// Typically used in the [Scaffold.appBar] property. + MyAppBar({ + Key key, + this.leading, + this.automaticallyImplyLeading = true, + this.title, + this.actions, + this.flexibleSpace, + this.bottom, + this.elevation = 4.0, + this.backgroundColor, + this.brightness, + this.iconTheme, + this.textTheme, + this.primary = true, + this.centerTitle, + this.titleSpacing = NavigationToolbar.kMiddleSpacing, + this.toolbarOpacity = 1.0, + this.bottomOpacity = 1.0, + }) : assert(automaticallyImplyLeading != null), + assert(elevation != null), + assert(primary != null), + assert(titleSpacing != null), + assert(toolbarOpacity != null), + assert(bottomOpacity != null), + preferredSize = Size.fromHeight( + kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)), + super(key: key); + + /// A widget to display before the [title]. + /// + /// If this is null and [automaticallyImplyLeading] is set to true, the + /// [AppBar] will imply an appropriate widget. For example, if the [AppBar] is + /// in a [Scaffold] that also has a [Drawer], the [Scaffold] will fill this + /// widget with an [IconButton] that opens the drawer (using [Icons.menu]). If + /// there's no [Drawer] and the parent [Navigator] can go back, the [AppBar] + /// will use a [BackButton] that calls [Navigator.maybePop]. + final Widget leading; + + /// Controls whether we should try to imply the leading widget if null. + /// + /// If true and [leading] is null, automatically try to deduce what the leading + /// widget should be. If false and [leading] is null, leading space is given to [title]. + /// If leading widget is not null, this parameter has no effect. + final bool automaticallyImplyLeading; + + /// The primary widget displayed in the appbar. + /// + /// Typically a [Text] widget containing a description of the current contents + /// of the app. + final Widget title; + + /// Widgets to display after the [title] widget. + /// + /// Typically these widgets are [IconButton]s representing common operations. + /// For less common operations, consider using a [PopupMenuButton] as the + /// last action. + /// + /// ## Sample code + /// + /// ```dart + /// Scaffold( + /// appBar: AppBar( + /// title: Text('Hello World'), + /// actions: [ + /// IconButton( + /// icon: Icon(Icons.shopping_cart), + /// tooltip: 'Open shopping cart', + /// onPressed: () { + /// // ... + /// }, + /// ), + /// ], + /// ), + /// ) + /// ``` + final List actions; + + /// This widget is stacked behind the toolbar and the tabbar. It's height will + /// be the same as the app bar's overall height. + /// + /// A flexible space isn't actually flexible unless the [AppBar]'s container + /// changes the [AppBar]'s size. A [SliverAppBar] in a [CustomScrollView] + /// changes the [AppBar]'s height when scrolled. + /// + /// Typically a [FlexibleSpaceBar]. See [FlexibleSpaceBar] for details. + final Widget flexibleSpace; + + /// This widget appears across the bottom of the app bar. + /// + /// Typically a [TabBar]. Only widgets that implement [PreferredSizeWidget] can + /// be used at the bottom of an app bar. + /// + /// See also: + /// + /// * [PreferredSize], which can be used to give an arbitrary widget a preferred size. + final PreferredSizeWidget bottom; + + /// The z-coordinate at which to place this app bar. This controls the size of + /// the shadow below the app bar. + /// + /// Defaults to 4, the appropriate elevation for app bars. + final double elevation; + + /// The color to use for the app bar's material. Typically this should be set + /// along with [brightness], [iconTheme], [textTheme]. + /// + /// Defaults to [ThemeData.primaryColor]. + final Color backgroundColor; + + /// The brightness of the app bar's material. Typically this is set along + /// with [backgroundColor], [iconTheme], [textTheme]. + /// + /// Defaults to [ThemeData.primaryColorBrightness]. + final Brightness brightness; + + /// The color, opacity, and size to use for app bar icons. Typically this + /// is set along with [backgroundColor], [brightness], [textTheme]. + /// + /// Defaults to [ThemeData.primaryIconTheme]. + final IconThemeData iconTheme; + + /// The typographic styles to use for text in the app bar. Typically this is + /// set along with [brightness] [backgroundColor], [iconTheme]. + /// + /// Defaults to [ThemeData.primaryTextTheme]. + final TextTheme textTheme; + + /// Whether this app bar is being displayed at the top of the screen. + /// + /// If true, the appbar's toolbar elements and [bottom] widget will be + /// padded on top by the height of the system status bar. The layout + /// of the [flexibleSpace] is not affected by the [primary] property. + final bool primary; + + /// Whether the title should be centered. + /// + /// Defaults to being adapted to the current [TargetPlatform]. + final bool centerTitle; + + /// The spacing around [title] content on the horizontal axis. This spacing is + /// applied even if there is no [leading] content or [actions]. If you want + /// [title] to take all the space available, set this value to 0.0. + /// + /// Defaults to [NavigationToolbar.kMiddleSpacing]. + final double titleSpacing; + + /// How opaque the toolbar part of the app bar is. + /// + /// A value of 1.0 is fully opaque, and a value of 0.0 is fully transparent. + /// + /// Typically, this value is not changed from its default value (1.0). It is + /// used by [SliverAppBar] to animate the opacity of the toolbar when the app + /// bar is scrolled. + final double toolbarOpacity; + + /// How opaque the bottom part of the app bar is. + /// + /// A value of 1.0 is fully opaque, and a value of 0.0 is fully transparent. + /// + /// Typically, this value is not changed from its default value (1.0). It is + /// used by [SliverAppBar] to animate the opacity of the toolbar when the app + /// bar is scrolled. + final double bottomOpacity; + + /// A size whose height is the sum of [kToolbarHeight] and the [bottom] widget's + /// preferred height. + /// + /// [Scaffold] uses this this size to set its app bar's height. + @override + final Size preferredSize; + + bool _getEffectiveCenterTitle(ThemeData themeData) { + if (centerTitle != null) return centerTitle; + assert(themeData.platform != null); + switch (themeData.platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + return false; + case TargetPlatform.iOS: + return actions == null || actions.length < 2; + } + return null; + } + + @override + _MyAppBarState createState() => _MyAppBarState(); +} + +class _MyAppBarState extends State { + void _handleDrawerButton() { + Scaffold.of(context).openDrawer(); + } + + void _handleDrawerButtonEnd() { + Scaffold.of(context).openEndDrawer(); + } + + @override + Widget build(BuildContext context) { + assert(!widget.primary || debugCheckHasMediaQuery(context)); + assert(debugCheckHasMaterialLocalizations(context)); + final ThemeData themeData = Theme.of(context); + final ScaffoldState scaffold = Scaffold.of(context, nullOk: true); + final ModalRoute parentRoute = ModalRoute.of(context); + + final bool hasDrawer = scaffold?.hasDrawer ?? false; + final bool hasEndDrawer = scaffold?.hasEndDrawer ?? false; + final bool canPop = parentRoute?.canPop ?? false; + final bool useCloseButton = + parentRoute is PageRoute && parentRoute.fullscreenDialog; + + IconThemeData appBarIconTheme = + widget.iconTheme ?? themeData.primaryIconTheme; + TextStyle centerStyle = + widget.textTheme?.title ?? themeData.primaryTextTheme.title; + TextStyle sideStyle = + widget.textTheme?.body1 ?? themeData.primaryTextTheme.body1; + + if (widget.toolbarOpacity != 1.0) { + final double opacity = + const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn) + .transform(widget.toolbarOpacity); + if (centerStyle?.color != null) + centerStyle = + centerStyle.copyWith(color: centerStyle.color.withOpacity(opacity)); + if (sideStyle?.color != null) + sideStyle = + sideStyle.copyWith(color: sideStyle.color.withOpacity(opacity)); + appBarIconTheme = appBarIconTheme.copyWith( + opacity: opacity * (appBarIconTheme.opacity ?? 1.0)); + } + + Widget leading = widget.leading; + //if (leading == null && widget.automaticallyImplyLeading) { + if (widget.automaticallyImplyLeading) { + if (hasDrawer) { + leading = IconButton( + //icon: const Icon(Icons.menu), + icon: leading ?? const Icon(Icons.menu), + onPressed: _handleDrawerButton, + tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip, + ); + } else { + if (canPop) + leading = useCloseButton ? const CloseButton() : const BackButton(); + } + } + if (leading != null) { + leading = ConstrainedBox( + constraints: const BoxConstraints.tightFor(width: _kLeadingWidth), + child: leading, + ); + } + + Widget title = widget.title; + if (title != null) { + bool namesRoute; + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + namesRoute = true; + break; + case TargetPlatform.iOS: + break; + } + title = DefaultTextStyle( + style: centerStyle, + softWrap: false, + overflow: TextOverflow.ellipsis, + child: Semantics( + namesRoute: namesRoute, + child: title, + header: true, + ), + ); + } + + Widget actions; + if (widget.actions != null && widget.actions.isNotEmpty) { + actions = Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: widget.actions, + ); + } else if (hasEndDrawer) { + actions = IconButton( + icon: const Icon(Icons.menu), + onPressed: _handleDrawerButtonEnd, + tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip, + ); + } + + final Widget toolbar = NavigationToolbar( + leading: leading, + middle: title, + trailing: actions, + centerMiddle: widget._getEffectiveCenterTitle(themeData), + middleSpacing: widget.titleSpacing, + ); + + // If the toolbar is allocated less than kToolbarHeight make it + // appear to scroll upwards within its shrinking container. + Widget appBar = ClipRect( + child: CustomSingleChildLayout( + delegate: const _ToolbarContainerLayout(), + child: IconTheme.merge( + data: appBarIconTheme, + child: DefaultTextStyle( + style: sideStyle, + child: toolbar, + ), + ), + ), + ); + if (widget.bottom != null) { + appBar = Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible( + child: ConstrainedBox( + constraints: const BoxConstraints(maxHeight: kToolbarHeight), + child: appBar, + ), + ), + widget.bottomOpacity == 1.0 + ? widget.bottom + : Opacity( + opacity: + const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn) + .transform(widget.bottomOpacity), + child: widget.bottom, + ), + ], + ); + } + + // The padding applies to the toolbar and tabbar, not the flexible space. + if (widget.primary) { + appBar = SafeArea( + top: true, + child: appBar, + ); + } + + appBar = Align( + alignment: Alignment.topCenter, + child: appBar, + ); + + if (widget.flexibleSpace != null) { + appBar = Stack( + fit: StackFit.passthrough, + children: [ + widget.flexibleSpace, + appBar, + ], + ); + } + final Brightness brightness = + widget.brightness ?? themeData.primaryColorBrightness; + final SystemUiOverlayStyle overlayStyle = brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark; + + return Semantics( + container: true, + explicitChildNodes: true, + child: AnnotatedRegion( + value: overlayStyle, + child: Material( + color: widget.backgroundColor ?? themeData.primaryColor, + elevation: widget.elevation, + child: appBar, + ), + ), + ); + } +} diff --git a/lib/views/first_page/main_page.dart b/lib/views/first_page/main_page.dart new file mode 100644 index 00000000..43383a85 --- /dev/null +++ b/lib/views/first_page/main_page.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import './first_page.dart'; +import './sub_page.dart'; +import './main_app_bar.dart'; +import './search_page.dart'; + +class _Page { + final String labelId; + + _Page(this.labelId); +} + +final List<_Page> _allPages = <_Page>[ + _Page('项目1'), + _Page('项目2'), + _Page('项目3'), + _Page('项目4'), +]; + +class MainPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + print("MainPagess build......"); + return DefaultTabController( + length: _allPages.length, + child: Scaffold( + appBar: new MyAppBar( + leading: Container( + child: new ClipOval( + child: Image.network( + 'https://hbimg.huabanimg.com/9bfa0fad3b1284d652d370fa0a8155e1222c62c0bf9d-YjG0Vt_fw658', + scale: 15.0, + ), + ) + ), + centerTitle: true, + title: TabLayout(), + actions: [ + IconButton( + icon: Icon(Icons.search), + onPressed: () { + pushPage(context, SearchPage(), pageName: "SearchPage"); + }) + ], + ), + body: TabBarViewLayout(), +// drawer: Drawer( +// child: MainLeftPage(), +// ), + )); + } +} + +void pushPage(BuildContext context, Widget page, {String pageName}) { + if (context == null || page == null) return; + Navigator.push(context, CupertinoPageRoute(builder: (ctx) => page)); +} + +class TabLayout extends StatelessWidget { + @override + Widget build(BuildContext context) { + return TabBar( + isScrollable: true, + //labelPadding: EdgeInsets.all(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(), + ); + } +} + +class TabBarViewLayout extends StatelessWidget { + Widget buildTabView(BuildContext context, _Page page) { + 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; + } + } + + @override + Widget build(BuildContext context) { + print("TabBarViewLayout build......."); + return TabBarView( + children: _allPages.map((_Page page) { + return buildTabView(context, page); + }).toList()); + } +} diff --git a/lib/views/first_page/search_page.dart b/lib/views/first_page/search_page.dart new file mode 100644 index 00000000..6d519738 --- /dev/null +++ b/lib/views/first_page/search_page.dart @@ -0,0 +1,189 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter/cupertino.dart'; +import 'dart:ui'; +import 'dart:async'; +import 'package:flutter_go/blocs/industry_main.dart' as Industry; +import 'package:flutter_go/blocs/industry_event.dart'; +import 'package:flutter_go/routers/application.dart'; +import 'package:flutter_go/routers/routers.dart'; + +final _industryPage = Industry.IndustryPage(itemTitle: (state){ + return ListView.builder( + itemBuilder: (BuildContext context, int index) { + return ListTile( + dense: true, + leading: Icon( + Icons.bookmark_border, + size: 32, + ), + title: Text( + state.res[index].title, + overflow: TextOverflow.ellipsis, + ), + 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; + Application.router.navigateTo(context, '${Routes.webViewPage}?title=${Uri.encodeComponent(itemTitle)}&url=${Uri.encodeComponent(itemUrl)}'); + }, + ); + }, + itemCount: state.res.length, + ); +}); + +final suggestion = Industry.suggestion; +final searchBarPage = SearchBarPage(); + +class SearchPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + /// print('suggestion::${Industry.suggestion}'); + return Scaffold( + appBar: PreferredSize( + preferredSize: Size(double.infinity, 52), // 44 is the height + child: AppBar(title: searchBarPage) + ), + //body: ProgressView(), + body: _industryPage, + floatingActionButton: FloatingActionButton( + child: const Icon(Icons.search), + onPressed: () { + //print('searchBarPage=====>${controller.text}'); + //print('searchBarPage=====>${that.getResultsDebounced}'); + if(that is _SearchBarPageState && that.getResultsDebounced is Function && controller.text is String ) { + that.getResultsDebounced(controller.text); + } + }, + ), + ); + } +} + +/// 加载loading +class ProgressView extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Center( + child: SizedBox( + width: 24.0, + height: 24.0, + child: CircularProgressIndicator( + strokeWidth: 2.0, + ), + ), + ); + } +} + +class SearchBarPage extends StatefulWidget{ + @override + State createState() => _SearchBarPageState(); +} + + +final TextEditingController controller = TextEditingController(); +var that; +class _SearchBarPageState extends State { + @override + void initState() { + super.initState(); + that = this; + } + + Timer _resultsTimer; + bool _loading = false; + String oldKey; + /// 防抖函数 + Future getResultsDebounced(String text) async { + if(oldKey == text){ + print('请求内容重复'); + return; + } + if (text == '' || !mounted) { + return; + } + _loading = true; + if (_resultsTimer != null && _resultsTimer.isActive) { + _resultsTimer.cancel(); + } + _resultsTimer = new Timer(new Duration(milliseconds: 400), () async { + _loading = true; + if(mounted){ + suggestion.dispatch(SuggestionFetch(query: text)); + } + oldKey = text; + }); + } + + + void onSearchTextChanged(String text){ + print('onSearchTextChanged:${text}'); + //suggestion.dispatch(SuggestionFetch(query: text)); + getResultsDebounced(text); + } + + @override + void dispose() { + print('dispose'); + suggestion.dispatch(SuggestionClearFetch()); + //controller.dispose(); + super.dispose(); + } + + + Widget build(BuildContext context) { + return Container( + color: Theme.of(context).primaryColor, + //color: Colors.amber, + child: Padding( + //padding: EdgeInsets.only(top: MediaQueryData.fromWindow(window).padding.top,), + padding: EdgeInsets.all(0.0), + child: Container( + //height: 162.0, + child: Padding( + padding: const EdgeInsets.all(6.0), + child: Card( + child: Container( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(width: 5.0,), + Icon(Icons.search, color: Colors.grey,size: 18.0,), + Expanded( + child: Container( + alignment: Alignment.center, + child: TextField( + controller: controller, + decoration: InputDecoration( + contentPadding: EdgeInsets.only(top: 0.0), + hintText: '搜索全局flutter知识库', + hintStyle:TextStyle(fontSize: 12.0), + border: InputBorder.none + ), + onChanged: onSearchTextChanged, + ), + ), + ), + IconButton( + icon: Icon(Icons.cancel), + color: Colors.grey, + iconSize: 18.0, + onPressed: () { + controller.clear(); + // onSearchTextChanged(''); + }, + ), + ], + ), + ) + ) + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/views/first_page/sub_page.dart b/lib/views/first_page/sub_page.dart new file mode 100644 index 00000000..387b55bf --- /dev/null +++ b/lib/views/first_page/sub_page.dart @@ -0,0 +1,125 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import 'package:flutter_go/components/list_view_item.dart'; +import 'package:flutter_go/components/list_refresh.dart' as listComp; +import 'package:flutter_go/components/pagination.dart'; +import 'package:flutter_go/views/first_page/first_page_item.dart'; +import 'package:flutter_go/components/disclaimer_msg.dart'; +import 'package:flutter_go/utils/net_utils.dart'; + +// ValueKey key; + +class SubPage extends StatefulWidget { + @override + SubPageState createState() => SubPageState(); +} + +class SubPageState extends State with AutomaticKeepAliveClientMixin{ + Future _prefs = SharedPreferences.getInstance(); + Future _unKnow; + GlobalKey key; + + @override + bool get wantKeepAlive => true; + + + @override + void initState() { + super.initState(); + if (key == null) { + key = GlobalKey(); + // key = const Key('__RIKEY1__'); + //获取sharePre + _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); + } + }); + }); + } + } + + + Future getIndexListData([Map params]) async { + 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 = []; + var pageTotal = 0; + + try{ + var response = await NetUtils.get(juejin_flutter, params: _param); + responseList = response['d']['entrylist']; + pageTotal = response['d']['total']; + if (!(pageTotal is int) || pageTotal <= 0) { + pageTotal = 0; + } + }catch(e){ + + } + pageIndex += 1; + List resultList = new List(); + for (int i = 0; i < responseList.length; i++) { + try { + FirstPageItem cellData = new FirstPageItem.fromJson(responseList[i]); + resultList.add(cellData); + } catch (e) { + // No specified type, handles all + } + } + Map result = {"list":resultList, 'total':pageTotal, 'pageIndex':pageIndex}; + return result; + } + + Widget makeCard(index,item){ + + var myTitle = '${item.title}'; + var myUsername = '${'👲'}: ${item.username} '; + var codeUrl = '${item.detailUrl}'; + return new ListViewItem(itemUrl:codeUrl,itemTitle: myTitle,data: myUsername,); + } + + headerView(){ + return + 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) + ), + ]), + SizedBox(height: 1, child:Container(color: Theme.of(context).primaryColor)), + SizedBox(height: 10), + ], + ); + + } + + @override + Widget build(BuildContext context) { + super.build(context); + return new Column( + children: [ + new Expanded( + child: listComp.ListRefresh(getIndexListData,makeCard) + ) + ] + ); + } +} + + diff --git a/lib/views/first_page/home.dart b/lib/views/home.dart similarity index 93% rename from lib/views/first_page/home.dart rename to lib/views/home.dart index 6d05dd52..59dbd737 100644 --- a/lib/views/first_page/home.dart +++ b/lib/views/home.dart @@ -89,12 +89,14 @@ class _MyHomePageState extends State }); searchHistoryList .add(SearchHistory(name: targetName, targetRouter: targetRouter)); - print("searchHistoryList ${searchHistoryList.toString()}"); + print("searchHistoryList1 ${searchHistoryList.toString()}"); + 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 != '') { List list = await widgetControl.search(value); return list @@ -103,7 +105,7 @@ class _MyHomePageState extends State icon: WidgetName2Icon.icons[item.name] ?? null, text: 'widget', onTap: () { - onWidgetTap(item, context); + onWidgetTap(item, context); }, )) .toList(); @@ -113,6 +115,7 @@ class _MyHomePageState extends State }, (value) {}, () {}); } + @override Widget build(BuildContext context) { return new Scaffold( @@ -124,7 +127,7 @@ class _MyHomePageState extends State currentIndex: _currentIndex, //修改 页面 onTap: _ItemTapped, - //shifting :按钮点击移动效果,超过5个button不支持 + //shifting :按钮点击移动效果 //fixed:固定 type: BottomNavigationBarType.fixed, diff --git a/pubspec.lock b/pubspec.lock index 5ae4bbf2..33eb9622 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -15,6 +15,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.8" + bloc: + dependency: "direct main" + description: + name: bloc + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.0" boolean_selector: dependency: transitive description: @@ -50,6 +57,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.0.8" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.16.0" cupertino_icons: dependency: "direct main" description: @@ -97,6 +111,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.1" flutter_markdown: dependency: "direct main" description: @@ -116,6 +137,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.3.4" + html: + dependency: "direct main" + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.0+2" image_picker: dependency: "direct main" description: @@ -179,6 +207,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + rxdart: + dependency: transitive + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.21.0" shared_preferences: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 4e6e268e..85ab1bfb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -7,7 +7,7 @@ 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: 0.0.5firebase_auth +version: 1.0.0 environment: sdk: ">=2.0.0-dev.68.0 <3.0.0" @@ -35,6 +35,9 @@ dependencies: firebase_analytics: ^1.1.0 #firebase_auth: ^0.8.3 #auth firebase_core: ^0.3.4 # add dependency for Firebase Core + flutter_bloc: ^0.11.1 + bloc: ^0.12.0 + html: ^0.14.0+2 dev_dependencies: flutter_test: