diff --git a/.gitignore b/.gitignore index d94933c..75f4982 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,6 @@ .pub/ /build/ /lib/tools/ - +/lib/res/constant/github_client_config.dart # Exceptions to above rules. !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4d701f0..c5c9284 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -5,6 +5,11 @@ In most cases you can leave this as-is, but you if you want to provide additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> + + + + + authorizations({String base64}) async { +// var result = await HttpUtil.getInstance().post("/authorizations", +// options: Options(headers: {"Authorization": "Basic $base64"}), +// data: { +// "scopes": ["user", "repo", "gist", "notifications"], +// "note": "admin_script", +// "client_id": GithubClientConfig.clientId, +// "client_secret": GithubClientConfig.clientSecret +// }); +// +// return result; +// } +} diff --git a/lib/app/api/github/issues_api.dart b/lib/app/api/github/issues_api.dart new file mode 100644 index 0000000..5d0b25f --- /dev/null +++ b/lib/app/api/github/issues_api.dart @@ -0,0 +1,43 @@ +import 'package:flutter_unit/app/utils/http/http_util.dart'; +import 'package:flutter_unit/app/utils/http/rep_result.dart'; +import 'package:flutter_unit/model/github/issue.dart'; + +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +class IssuesApi { + static getIssues({ + String username="toly1994328", + String repository="FlutterUnit", + String sort = 'created', + String direction = 'desc', + int page = 0, + int pageSize = 10, + String state = 'all', + String labels = "all", + }) async { + var url = "/repos/$username/$repository/issues"; + + var param = { + "state": state, + "sort": sort, + "direction": direction, + "labels": labels, + "page": page, + "per_page": pageSize, + }; + + var res = await HttpUtil.getInstance().get(url, param: param); + + if (res != null && res.status) { + var data = res.data as List; + + if (data == null) { + return RepResult(null, false, -1); + } + + return RepResult(data.map(Issue.fromJson).toList(), true, 1); + } + } +} diff --git a/lib/app/api/github/repos_api.dart b/lib/app/api/github/repos_api.dart new file mode 100644 index 0000000..9e49750 --- /dev/null +++ b/lib/app/api/github/repos_api.dart @@ -0,0 +1,20 @@ +import 'package:flutter_unit/app/utils/http/http_util.dart'; +import 'package:flutter_unit/app/utils/http/rep_result.dart'; +import 'package:flutter_unit/model/github/repository.dart'; + +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +class ReposApi{ + + static Future getRepos({String username="toly1994328",String repository="FlutterUnit"}) async { + + var result = await HttpUtil.getInstance().get("/repos/$username/$repository"); + + result.data = Repository.fromJson(result.data); + + return result; + + } +} \ No newline at end of file diff --git a/lib/app/res/cons.dart b/lib/app/res/cons.dart index b288798..3cbb40c 100644 --- a/lib/app/res/cons.dart +++ b/lib/app/res/cons.dart @@ -13,16 +13,6 @@ class Cons { // "我的": Icons.account_circle, }; - static const rainbow = [ - 0xffff0000, - 0xffFF7F00, - 0xffFFFF00, - 0xff00FF00, - 0xff00FFFF, - 0xff0000FF, - 0xff8B00FF - ]; - static const tabColors = [ 0xff44D1FD, 0xffFD4F43, @@ -32,6 +22,7 @@ class Cons { 0xFF00F1F1, 0xFFDBD83F ]; + static const tabs = [ 'Stles', 'Stful', diff --git a/lib/app/res/constant/github_client_config.dart b/lib/app/res/constant/github_client_config.dart new file mode 100644 index 0000000..28145f6 --- /dev/null +++ b/lib/app/res/constant/github_client_config.dart @@ -0,0 +1,8 @@ +/// create by 张风捷特烈 on 2020/6/16 +/// contact me by email 1981462002@qq.com +/// 说明: + +class GithubClientConfig { + static const clientId = "7f2e92c1db43b9434585"; + static const clientSecret = "f492b1d894d9dc638391e7b874b613cf54238eb4"; +} \ No newline at end of file diff --git a/lib/app/router.dart b/lib/app/router.dart index f72ffa5..e209b8a 100644 --- a/lib/app/router.dart +++ b/lib/app/router.dart @@ -5,20 +5,21 @@ import 'package:flutter_unit/views/pages/about/about_app_page.dart'; import 'package:flutter_unit/views/pages/about/version_info.dart'; import 'package:flutter_unit/views/pages/category/category_detail.dart'; import 'package:flutter_unit/views/pages/category/collect_page.dart'; +import 'package:flutter_unit/views/pages/issues/main.dart'; +import 'package:flutter_unit/views/pages/login/login_page.dart'; import 'package:flutter_unit/views/pages/search/serach_page.dart'; import 'package:flutter_unit/views/pages/setting/code_style_setting.dart'; import 'package:flutter_unit/views/pages/setting/font_setting.dart'; import 'package:flutter_unit/views/pages/setting/item_style_setting.dart'; import 'package:flutter_unit/views/pages/setting/theme_color_setting.dart'; import 'package:flutter_unit/views/pages/unit_todo/attr_unit_page.dart'; -import 'package:flutter_unit/views/pages/unit_todo/bug_unit_page.dart'; +import 'package:flutter_unit/views/pages/unit_todo/point_unit_page.dart'; import 'package:flutter_unit/views/pages/detail/widget_detail_page.dart'; import 'package:flutter_unit/views/pages/unit_todo/layout_unit_page.dart'; import 'package:flutter_unit/views/pages/unit_todo/paint_unit_page.dart'; import 'package:flutter_unit/views/pages/setting/setting_page.dart'; - import 'utils/router_utils.dart'; class Router { @@ -36,9 +37,10 @@ class Router { static const String code_style_setting = 'CodeStyleSettingPage'; static const String item_style_setting = 'ItemStyleSettingPage'; static const String version_info = 'VersionInfo'; - + static const String login = 'login'; static const String category_show = 'CategoryShow'; + static const String issues_point = 'IssuesPointPage'; static const String attr = 'AttrUnitPage'; static const String bug = 'BugUnitPage'; @@ -51,7 +53,10 @@ class Router { switch (settings.name) { //根据名称跳转相应页面 case widget_detail: - return Right2LeftRouter(child: WidgetDetailPage(model: settings.arguments,)); + return Right2LeftRouter( + child: WidgetDetailPage( + model: settings.arguments, + )); case search: return Right2LeftRouter(child: SearchPage()); case collect: @@ -68,8 +73,14 @@ class Router { return Right2LeftRouter(child: CodeStyleSettingPage()); case item_style_setting: return Right2LeftRouter(child: ItemStyleSettingPage()); - case version_info: + + case version_info: return Right2LeftRouter(child: VersionInfo()); + case issues_point: + return Right2LeftRouter(child: IssuesPointPage()); + + case login: + return Right2LeftRouter(child: LoginPage()); case attr: return Right2LeftRouter(child: AttrUnitPage()); @@ -84,8 +95,11 @@ class Router { case about_me: return Right2LeftRouter(child: AboutMePage()); - case category_show: - return Right2LeftRouter(child: CategoryShow(model: settings.arguments,)); + case category_show: + return Right2LeftRouter( + child: CategoryShow( + model: settings.arguments, + )); default: return MaterialPageRoute( diff --git a/lib/app/style/TolyIcon.dart b/lib/app/style/TolyIcon.dart index 681afdc..1caf6a8 100644 --- a/lib/app/style/TolyIcon.dart +++ b/lib/app/style/TolyIcon.dart @@ -4,6 +4,13 @@ import 'package:flutter/widgets.dart'; class TolyIcon { TolyIcon._(); +static const IconData icon_common = const IconData( 0xe634, fontFamily: "TolyIcon"); +static const IconData icon_see = const IconData( 0xe608, fontFamily: "TolyIcon"); +static const IconData icon_issues = const IconData( 0xe7a7, fontFamily: "TolyIcon"); +static const IconData icon_fork = const IconData( 0xe623, fontFamily: "TolyIcon"); +static const IconData icon_github_star = const IconData( 0xe7df, fontFamily: "TolyIcon"); +static const IconData icon_show = const IconData( 0xe648, fontFamily: "TolyIcon"); +static const IconData icon_hide = const IconData( 0xe649, fontFamily: "TolyIcon"); static const IconData icon_email = const IconData( 0xe694, fontFamily: "TolyIcon"); static const IconData icon_github = const IconData( 0xe689, fontFamily: "TolyIcon"); static const IconData icon_juejin = const IconData( 0xe601, fontFamily: "TolyIcon"); diff --git a/lib/app/utils/convert_man.dart b/lib/app/utils/convert_man.dart new file mode 100644 index 0000000..f6386ed --- /dev/null +++ b/lib/app/utils/convert_man.dart @@ -0,0 +1,47 @@ +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +final double _kMillisLimit = 1000.0; + +final double _kSecondsLimit = 60 * _kMillisLimit; + +final double _kMinutesLimit = 60 * _kSecondsLimit; + +final double _kHourLimit = 24 * _kMinutesLimit; + +final double _kDaysLimit = 30 * _kHourLimit; + +class ConvertMan { + + ///日期格式转换 + static String time2string(DateTime date, {bool just = false}) { + if (just) { + return _getDateStr(date); + } + int subTimes = + DateTime.now().millisecondsSinceEpoch - date.millisecondsSinceEpoch; + if (subTimes < _kMillisLimit) { + return "刚刚"; + } else if (subTimes < _kSecondsLimit) { + return (subTimes / _kMillisLimit).round().toString() + " 秒前"; + } else if (subTimes < _kMinutesLimit) { + return (subTimes / _kSecondsLimit).round().toString() + "分钟前"; + } else if (subTimes < _kHourLimit) { + return (subTimes / _kMinutesLimit).round().toString() + "小时前"; + } else if (subTimes < _kDaysLimit) { + return (subTimes / _kHourLimit).round().toString() + "天前"; + } else { + return _getDateStr(date); + } + } + + static String _getDateStr(DateTime date) { + if (date == null || date.toString() == null) { + return ""; + } else if (date.toString().length < 10) { + return date.toString(); + } + return date.toString().substring(0, 10); + } +} diff --git a/lib/app/utils/http/code.dart b/lib/app/utils/http/code.dart new file mode 100644 index 0000000..e85d8a3 --- /dev/null +++ b/lib/app/utils/http/code.dart @@ -0,0 +1,30 @@ +//import 'package:gsy_github_app_flutter/common/event/http_error_event.dart'; +//import 'package:gsy_github_app_flutter/common/event/index.dart'; + +///错误编码 +class Code { + ///网络错误 + static const NETWORK_ERROR = -1; + + ///网络超时 + static const NETWORK_TIMEOUT = -2; + + ///网络返回数据格式化一次 + static const NETWORK_JSON_EXCEPTION = -3; + + ///Github APi Connection refused + static const GITHUB_API_REFUSED = -4; + + static const SUCCESS = 200; + + static errorHandleFunction(code, message, noTip) { + if (noTip) { + return message; + } + if(message != null && message is String && (message.contains("Connection refused") || message.contains("Connection reset"))) { + code = GITHUB_API_REFUSED; + } +// eventBus.fire(new HttpErrorEvent(code, message)); + return message; + } +} diff --git a/lib/app/utils/http/http_util.dart b/lib/app/utils/http/http_util.dart new file mode 100644 index 0000000..9a17d2b --- /dev/null +++ b/lib/app/utils/http/http_util.dart @@ -0,0 +1,133 @@ +import 'dart:collection'; + +import 'package:dio/dio.dart'; +import 'rep_result.dart'; + +import 'code.dart'; +import 'interceptors/log_interceptor.dart'; +import 'interceptors/response_interceptor.dart'; +import 'interceptors/token_interceptor.dart'; + +const String _kBaseUrl = "https://api.github.com"; +const int _kReceiveTimeout = 15000; +const int _kSendTimeout = 15000; +const int _kConnectTimeout = 15000; +const String _kTokenKey = 'Authorization'; +const String _kTokenPrefix = 'Bearer '; + +///http请求 +class HttpUtil { + static const CONTENT_TYPE_JSON = "application/json"; + static const CONTENT_TYPE_FORM = "application/x-www-form-urlencoded"; + + static HttpUtil _instance = HttpUtil._internal(); + + Dio _dio; + + void setToken(String token) { + _tokenInterceptors.token = token; + } + + void clearToken(String token) { + setToken(null); + } + + ///通用全局单例,第一次使用时初始化 + HttpUtil._internal({String token}) { + if (null == _dio) { + _dio = Dio(BaseOptions( + baseUrl: _kBaseUrl, + connectTimeout: _kReceiveTimeout, + receiveTimeout: _kConnectTimeout, + sendTimeout: _kSendTimeout, + )); + + _dio.interceptors.add(LogsInterceptors()); + _dio.interceptors.add(ResponseInterceptors()); + _dio.interceptors.add(_tokenInterceptors); + } + } + + static HttpUtil getInstance() { + return _instance; + } + + void rebase(String baseUrl) { + _dio.options.baseUrl = baseUrl; + } + + final TokenInterceptor _tokenInterceptors = TokenInterceptor(); + + Future get(String url, + {Map header, Map param}) async { + Response response; + try { + response = await _dio.get(url, queryParameters: param); + } on DioError catch (e) { + return resultError(e); + } + if (response.data is DioError) { + return resultError(response.data); + } + return response.data; + } + + Future post(String url, + {Map param, data, Options options,}) async { + Response response; + try { + response = await _dio.post( + url, data: data, queryParameters: param, options: options); + } on DioError catch (e) { + return resultError(e); + } + if (response.data is DioError) { + return resultError(response.data); + } + return response.data; + } + + Future put(String url, {Map param, data}) async { + Response response; + try { + response = await _dio.put(url, data: data, queryParameters: param); + } on DioError catch (e) { + return resultError(e); + } + if (response.data is DioError) { + return resultError(response.data); + } + return response.data; + } + + Future delete(String url, + {Map param, data}) async { + Response response; + try { + response = await _dio.delete(url, data: data, queryParameters: param); + } on DioError catch (e) { + return resultError(e); + } + if (response.data is DioError) { + return resultError(response.data); + } + return response.data; + } + + resultError(DioError e) { + Response errorResponse; + if (e.response != null) { + errorResponse = e.response; + } else { + errorResponse = Response(statusCode: 666); + } + if (e.type == DioErrorType.CONNECT_TIMEOUT || + e.type == DioErrorType.RECEIVE_TIMEOUT) { + errorResponse.statusCode = Code.NETWORK_TIMEOUT; + } + return RepResult( + Code.errorHandleFunction(errorResponse.statusCode, e.message, false), + false, + errorResponse.statusCode); + } +} \ No newline at end of file diff --git a/lib/app/utils/http/interceptors/error_interceptor.dart b/lib/app/utils/http/interceptors/error_interceptor.dart new file mode 100644 index 0000000..eaa28e5 --- /dev/null +++ b/lib/app/utils/http/interceptors/error_interceptor.dart @@ -0,0 +1,32 @@ +import 'package:connectivity/connectivity.dart'; +import 'package:dio/dio.dart'; + +import '../code.dart'; +import '../rep_result.dart'; + +/// create by 张风捷特烈 on 2020/4/28 +/// contact me by email 1981462002@qq.com +/// 说明: + +///是否需要弹提示 +const NOT_TIP_KEY = "noTip"; + + +class ErrorInterceptors extends InterceptorsWrapper { + final Dio _dio; + + ErrorInterceptors(this._dio); + + @override + onRequest(RequestOptions options) async { + //没有网络 + var connectivityResult = await (new Connectivity().checkConnectivity()); + if (connectivityResult == ConnectivityResult.none) { + return _dio.resolve(new RepResult( + Code.errorHandleFunction(Code.NETWORK_ERROR, "", false), + false, + Code.NETWORK_ERROR)); + } + return options; + } +} diff --git a/lib/app/utils/http/interceptors/header_interceptor.dart b/lib/app/utils/http/interceptors/header_interceptor.dart new file mode 100644 index 0000000..dd78477 --- /dev/null +++ b/lib/app/utils/http/interceptors/header_interceptor.dart @@ -0,0 +1,16 @@ +import 'package:dio/dio.dart'; + +/// create by 张风捷特烈 on 2020/4/28 +/// contact me by email 1981462002@qq.com +/// 说明: +/// +class HeaderInterceptors extends InterceptorsWrapper { + @override + onRequest(RequestOptions options) async { + ///超时 + options.connectTimeout = 30000; + options.receiveTimeout = 30000; + + return options; + } +} diff --git a/lib/app/utils/http/interceptors/log_interceptor.dart b/lib/app/utils/http/interceptors/log_interceptor.dart new file mode 100644 index 0000000..058e8c5 --- /dev/null +++ b/lib/app/utils/http/interceptors/log_interceptor.dart @@ -0,0 +1,112 @@ +import 'dart:convert'; +import 'package:dio/dio.dart'; + +/// create by 张风捷特烈 on 2020/4/28 +/// contact me by email 1981462002@qq.com +/// 说明: +/// +class LogsInterceptors extends InterceptorsWrapper { + static bool debug = true; + + static List sHttpResponses = new List(); + static List sResponsesHttpUrl = new List(); + + static List> sHttpRequest = + new List>(); + static List sRequestHttpUrl = new List(); + + static List> sHttpError = + new List>(); + static List sHttpErrorUrl = new List(); + + @override + onRequest(RequestOptions options) async { + if (debug) { + print("请求url:${options.path}"); + print('请求头: ' + options.headers.toString()); + if (options.data != null) { + print('请求参数: ' + options.data.toString()); + } + } + try { + addLogic(sRequestHttpUrl, options.path ?? ""); + var data; + if (options.data is Map) { + data = options.data; + } else { + data = Map(); + } + var map = { + "header:": {...options.headers}, + }; + if (options.method == "POST") { + map["data"] = data; + } + addLogic(sHttpRequest, map); + } catch (e) { + print(e); + } + return options; + } + + @override + onResponse(Response response) async { + if (debug) { + if (response != null) { + print('返回参数: ' + response.toString()); + } + } + if (response.data is Map || response.data is List) { + try { + var data = Map(); + data["data"] = response.data; + addLogic(sResponsesHttpUrl, response?.request?.uri?.toString() ?? ""); + addLogic(sHttpResponses, data); + } catch (e) { + print(e); + } + } else if (response.data is String) { + try { + var data = Map(); + data["data"] = response.data; + addLogic(sResponsesHttpUrl, response?.request?.uri.toString() ?? ""); + addLogic(sHttpResponses, data); + } catch (e) { + print(e); + } + } else if (response.data != null) { + try { + String data = response.data.toJson(); + addLogic(sResponsesHttpUrl, response?.request?.uri.toString() ?? ""); + addLogic(sHttpResponses, json.decode(data)); + } catch (e) { + print(e); + } + } + return response; // continue + } + + @override + onError(DioError err) async { + if (debug) { + print('请求异常: ' + err.toString()); + print('请求异常信息: ' + (err.response?.toString() ?? "")); + } + try { + addLogic(sHttpErrorUrl, err.request.path ?? "null"); + var errors = Map(); + errors["error"] = err.message; + addLogic(sHttpError, errors); + } catch (e) { + print(e); + } + return err; // continue; + } + + static addLogic(List list, data) { + if (list.length > 20) { + list.removeAt(0); + } + list.add(data); + } +} diff --git a/lib/app/utils/http/interceptors/response_interceptor.dart b/lib/app/utils/http/interceptors/response_interceptor.dart new file mode 100644 index 0000000..a29b482 --- /dev/null +++ b/lib/app/utils/http/interceptors/response_interceptor.dart @@ -0,0 +1,27 @@ +import 'package:dio/dio.dart'; +import '../code.dart'; +import '../rep_result.dart'; + +/// create by 张风捷特烈 on 2020/4/28 +/// contact me by email 1981462002@qq.com +/// 说明: +/// +class ResponseInterceptors extends InterceptorsWrapper { + @override + onResponse(Response response) async { + RequestOptions option = response.request; + var value; + try { + var header = response.headers[Headers.contentTypeHeader]; + if ((header != null && header.toString().contains("text"))) { + value = new RepResult(response.data, true, Code.SUCCESS); + } else if (response.statusCode >= 200 && response.statusCode < 300) { + value = new RepResult(response.data, true, Code.SUCCESS,); + } + } catch (e) { + print(e.toString() + option.path); + value = new RepResult(response.data, false, response.statusCode,msg: e.toString()); + } + return value; + } +} diff --git a/lib/app/utils/http/interceptors/token_interceptor.dart b/lib/app/utils/http/interceptors/token_interceptor.dart new file mode 100644 index 0000000..8a1fb12 --- /dev/null +++ b/lib/app/utils/http/interceptors/token_interceptor.dart @@ -0,0 +1,64 @@ +import 'package:dio/dio.dart'; + +/// create by 张风捷特烈 on 2020/4/28 +/// contact me by email 1981462002@qq.com +/// 说明: + + +const String _kTokenKey = 'Authorization'; +const String _kTokenPrefix = 'token '; + +class TokenInterceptor extends InterceptorsWrapper { + String _token; + + set token(String value) { + _token = value; + } + + @override + onRequest(RequestOptions options) async { + //_token非空,拦截请求,添加token + if(_token!=null&&_token.isNotEmpty){ + options.headers[_kTokenKey] = '$_kTokenPrefix$_token'; + } + return options; + } + + @override + onResponse(Response response) async { +// try { +// var responseJson = response.data; +// if (response.statusCode == 201 && responseJson["token"] != null) { +// _token = 'token ' + responseJson["token"]; +// await LocalStorage.save(Config.TOKEN_KEY, _token); +// } +// } catch (e) { +// print(e); +// } + return response; + } + + ///清除授权 + clearAuthorization() { +// this._token = null; +// LocalStorage.remove(Config.TOKEN_KEY); +// releaseClient(); + } + + ///获取授权token + getAuthorization() async { +// String token = await LocalStorage.get(Config.TOKEN_KEY); +// if (token == null) { +// String basic = await LocalStorage.get(Config.USER_BASIC_CODE); +// if (basic == null) { +// //提示输入账号密码 +// } else { +// //通过 basic 去获取token,获取到设置,返回token +// return "Basic $basic"; +// } +// } else { +// this._token = token; +// return token; +// } + } +} diff --git a/lib/app/utils/http/rep_result.dart b/lib/app/utils/http/rep_result.dart new file mode 100644 index 0000000..eae6094 --- /dev/null +++ b/lib/app/utils/http/rep_result.dart @@ -0,0 +1,28 @@ + +class RepResult { + var data; + bool status; + int code; + String msg; + + RepResult(this.data, this.status, this.code, {this.msg=""}); + + @override + String toString() { + return 'RepResult{data: $data, result: $status, code: $code, msg: $msg}'; + } +} + +//class RepResult { +// T data; +// bool status; +// int code; +// +// RepResult(this.data, this.status, this.code); +// +// @override +// String toString() { +// return 'RepResult{data: $data, status: $status, code:$code}'; +// } +// +//} \ No newline at end of file diff --git a/lib/blocs/github_authentic/bloc.dart b/lib/blocs/github_authentic/bloc.dart new file mode 100644 index 0000000..c65e763 --- /dev/null +++ b/lib/blocs/github_authentic/bloc.dart @@ -0,0 +1,73 @@ +import 'dart:async'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_unit/app/utils/http/rep_result.dart'; +import 'package:flutter_unit/model/github/github_user.dart'; +import 'package:flutter_unit/repositories/github_authentic_repository.dart'; +import 'package:flutter_unit/repositories/github_user_repository.dart'; +import 'package:meta/meta.dart'; +import 'package:bloc/bloc.dart'; + +part 'event.dart'; + +part 'state.dart'; + +// 验证的bloc 需要一个 UserRepository + +// 在程序开始时会触发 AppStarted 事件 +// 此时bloc会用 UserRepository 查看是否存在token +// 来返回状态AuthSuccess 或 AuthFailure +// AuthFailure之后出现登录界面, AuthSuccess后会到主页 + +// 在登录完成后会触发LoginOver事件 +// 此时bloc会通过userRepository.persistToken对token进行持久化 + +class GithubAuthenticBloc extends Bloc { + final GithubAuthenticRepository authenticationRepository; + final GithubUserRepository userRepository; + + GithubAuthenticBloc( + {@required this.authenticationRepository, + @required this.userRepository}) + : assert(authenticationRepository != null); + + @override + AuthenticState get initialState => AuthInitial(); + + String get token => authenticationRepository.token; + + GithubUser get user { + if (state is AuthSuccess) { + return (state as AuthSuccess).user; + } + return null; + } + + @override + Stream mapEventToState( + GithubAuthEvent event, + ) async* { + if (event is CheckGithubAuth) { + final bool hasToken = await authenticationRepository.hasToken(); +// await LocalDao.dao.initLocalStorage(); + if (hasToken) { + var user = await userRepository.getLocalUser(); + yield AuthSuccess(token: authenticationRepository.token,user: user); + } else { + yield AuthFailure(); + } + } + + if (event is LoginOver) { + yield AuthLoading(); + await authenticationRepository.persistToken(event.result.msg); + yield AuthSuccess(user: event.result.data,token: event.result.msg); + } + + if (event is LoggedOut) { + yield AuthLoading(); + await userRepository.deleteLocalUser(); + await authenticationRepository.deleteToken(); + yield AuthFailure(); + } + } +} diff --git a/lib/blocs/github_authentic/event.dart b/lib/blocs/github_authentic/event.dart new file mode 100644 index 0000000..15b2705 --- /dev/null +++ b/lib/blocs/github_authentic/event.dart @@ -0,0 +1,26 @@ + +part of 'bloc.dart'; + +///********************************验证行为******************************** + +abstract class GithubAuthEvent extends Equatable { + const GithubAuthEvent(); + + @override + List get props => []; +} + +class CheckGithubAuth extends GithubAuthEvent {} + +class LoginOver extends GithubAuthEvent { + final RepResult result; + const LoginOver({this.result}); + + @override + List get props => [result]; + + @override + String toString() => 'LoggedIn { token: $result }'; +} + +class LoggedOut extends GithubAuthEvent {} \ No newline at end of file diff --git a/lib/blocs/github_authentic/state.dart b/lib/blocs/github_authentic/state.dart new file mode 100644 index 0000000..9550c34 --- /dev/null +++ b/lib/blocs/github_authentic/state.dart @@ -0,0 +1,25 @@ + + +part of 'bloc.dart'; + +///********************************校验状态******************************** +// +abstract class AuthenticState extends Equatable { + const AuthenticState(); + + @override + List get props => []; +} + + +class AuthInitial extends AuthenticState {} + +class AuthSuccess extends AuthenticState { + final String token; + final GithubUser user; + const AuthSuccess({this.token,this.user}); +} + +class AuthFailure extends AuthenticState {} + +class AuthLoading extends AuthenticState {} \ No newline at end of file diff --git a/lib/blocs/github_login/api.dart b/lib/blocs/github_login/api.dart new file mode 100644 index 0000000..f299e68 --- /dev/null +++ b/lib/blocs/github_login/api.dart @@ -0,0 +1,19 @@ +import 'dart:convert'; + +import 'package:flutter_unit/app/utils/http/http_util.dart'; +import 'package:flutter_unit/repositories/github_user_repository.dart'; + +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +main() async { + + var username ="toly1994328"; + var password ="@#1994328zfjtl"; + + var repository = GithubUserRepository(); + var result = await repository.authenticate(username: username, password: password); + + print(result); +} \ No newline at end of file diff --git a/lib/blocs/github_login/bloc.dart b/lib/blocs/github_login/bloc.dart new file mode 100644 index 0000000..d9918bf --- /dev/null +++ b/lib/blocs/github_login/bloc.dart @@ -0,0 +1,51 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter_unit/repositories/github_user_repository.dart'; +import '../github_authentic/bloc.dart'; + +import 'package:meta/meta.dart'; +import 'package:equatable/equatable.dart'; + +part 'event.dart'; + +part 'state.dart'; + +class LoginBloc extends Bloc { + final GithubAuthenticBloc authenticationBloc; + + LoginBloc({ + @required this.authenticationBloc, + }):assert(authenticationBloc != null); + + @override + LoginState get initialState => LoginInitial(); + + GithubUserRepository get userRepository => authenticationBloc.userRepository; + + @override + Stream mapEventToState( + LoginEvent event, + ) async* { + if (event is EventLogin) { + yield LoginLoading(); + try { + final result = await userRepository.authenticate( + username: event.username, + password: event.password, + ); + if (result.status) { + authenticationBloc.add(LoginOver(result: result)); + // 本地存储用户信息 +// userRepository.setLocalUser(result.user); + }else{ + yield LoginFailure(error: result.msg); + } + yield LoginInitial(); + } catch (error) { + yield LoginFailure(error: error.toString()); + } + } + } +} diff --git a/lib/blocs/github_login/event.dart b/lib/blocs/github_login/event.dart new file mode 100644 index 0000000..2b7996c --- /dev/null +++ b/lib/blocs/github_login/event.dart @@ -0,0 +1,21 @@ +part of 'bloc.dart'; + +abstract class LoginEvent extends Equatable { + const LoginEvent(); +} + +class EventLogin extends LoginEvent { + final String username; + final String password; + + const EventLogin({ + @required this.username, + @required this.password + }); + + @override + List get props => [username, password]; + + @override + String toString() => 'EventLogin { username: $username, password: $password }'; +} diff --git a/lib/blocs/github_login/state.dart b/lib/blocs/github_login/state.dart new file mode 100644 index 0000000..f242b6e --- /dev/null +++ b/lib/blocs/github_login/state.dart @@ -0,0 +1,24 @@ +part of 'bloc.dart'; + +abstract class LoginState extends Equatable { + const LoginState(); + + @override + List get props => []; +} + +class LoginInitial extends LoginState {} + +class LoginLoading extends LoginState {} + +class LoginFailure extends LoginState { + final String error; + + const LoginFailure({@required this.error}); + + @override + List get props => [error]; + + @override + String toString() => ' LoginFailure { error: $error }'; +} diff --git a/lib/blocs/issues/event.dart b/lib/blocs/issues/event.dart new file mode 100644 index 0000000..59dee6b --- /dev/null +++ b/lib/blocs/issues/event.dart @@ -0,0 +1,41 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +abstract class IssuesEvent extends Equatable {} + +class LoadIssues extends IssuesEvent { + final String username; + final String repository; + final String sort; + + final String direction; + final int page; + final int pageSize; + + final String state; + + final String labels; + + LoadIssues( + {@required this.username, + @required this.repository, + this.sort, + this.direction, + this.page, + this.pageSize, + this.state, + this.labels}); + + @override + List get props => + [username, repository, sort, direction, page, pageSize, state, labels]; + + @override + String toString() { + return 'LoadIssues{username: $username, repository: $repository, sort: $sort, direction: $direction, page: $page, pageSize: $pageSize, state: $state, labels: $labels}'; + } +} diff --git a/lib/blocs/issues/state.dart b/lib/blocs/issues/state.dart new file mode 100644 index 0000000..f11e510 --- /dev/null +++ b/lib/blocs/issues/state.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_unit/model/github/issue.dart'; + +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +abstract class IssuesState extends Equatable{ + +} + +class IssuesLoading extends IssuesState{ + @override + List get props =>[]; + + @override + String toString() { + return 'IssuesLoading{}'; + } +} + +class IssuesLoaded extends IssuesState{ + final List issues; + + IssuesLoaded(this.issues); + + @override + List get props => [issues]; + + @override + String toString() { + return 'IssuesLoaded{issues: $issues}'; + } +} + +class IssuesLoadFailure extends IssuesState{ + final String error; + + IssuesLoadFailure(this.error); + + @override + List get props => [error]; + + @override + String toString() { + return 'IssuesLoadFailure{error: $error}'; + } +} \ No newline at end of file diff --git a/lib/model/github/g/github_user.g.dart b/lib/model/github/g/github_user.g.dart new file mode 100644 index 0000000..3e9dfb7 --- /dev/null +++ b/lib/model/github/g/github_user.g.dart @@ -0,0 +1,93 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../github_user.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GithubUser _$UserFromJson(Map json) { + return GithubUser( + json['login'] as String, + json['id'] as int, + json['node_id'] as String, + json['avatar_url'] as String, + json['gravatar_id'] as String, + json['url'] as String, + json['html_url'] as String, + json['followers_url'] as String, + json['following_url'] as String, + json['gists_url'] as String, + json['starred_url'] as String, + json['subscriptions_url'] as String, + json['organizations_url'] as String, + json['repos_url'] as String, + json['events_url'] as String, + json['received_events_url'] as String, + json['type'] as String, + json['site_admin'] as bool, + json['name'] as String, + json['company'] as String, + json['blog'] as String, + json['location'] as String, + json['email'] as String, + json['starred'] as String, + json['bio'] as String, + json['public_repos'] as int, + json['public_gists'] as int, + json['followers'] as int, + json['following'] as int, + json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + json['private_gists'] as int, + json['total_private_repos'] as int, + json['owned_private_repos'] as int, + json['disk_usage'] as int, + json['collaborators'] as int, + json['two_factor_authentication'] as bool, + ); +} + +Map _$UserToJson(GithubUser instance) => { + 'login': instance.login, + 'id': instance.id, + 'node_id': instance.node_id, + 'avatar_url': instance.avatar_url, + 'gravatar_id': instance.gravatar_id, + 'url': instance.url, + 'html_url': instance.html_url, + 'followers_url': instance.followers_url, + 'following_url': instance.following_url, + 'gists_url': instance.gists_url, + 'starred_url': instance.starred_url, + 'subscriptions_url': instance.subscriptions_url, + 'organizations_url': instance.organizations_url, + 'repos_url': instance.repos_url, + 'events_url': instance.events_url, + 'received_events_url': instance.received_events_url, + 'type': instance.type, + 'site_admin': instance.site_admin, + 'name': instance.name, + 'company': instance.company, + 'blog': instance.blog, + 'location': instance.location, + 'email': instance.email, + 'starred': instance.starred, + 'bio': instance.bio, + 'public_repos': instance.public_repos, + 'public_gists': instance.public_gists, + 'followers': instance.followers, + 'following': instance.following, + 'created_at': instance.created_at?.toIso8601String(), + 'updated_at': instance.updated_at?.toIso8601String(), + 'private_gists': instance.private_gists, + 'total_private_repos': instance.total_private_repos, + 'owned_private_repos': instance.owned_private_repos, + 'disk_usage': instance.disk_usage, + 'collaborators': instance.collaborators, + 'two_factor_authentication': instance.two_factor_authentication, +}; diff --git a/lib/model/github/g/issue.g.dart b/lib/model/github/g/issue.g.dart new file mode 100644 index 0000000..193380c --- /dev/null +++ b/lib/model/github/g/issue.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../issue.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Issue _$IssueFromJson(dynamic json) { + return Issue( + json['id'] as int, + json['number'] as int, + json['title'] as String, + json['state'] as String, + json['locked'] as bool, + json['comments'] as int, + json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + json['closed_at'] == null + ? null + : DateTime.parse(json['closed_at'] as String), + json['body'] as String, + json['body_html'] as String, + json['user'] == null + ? null + : GithubUser.fromJson(json['user'] as Map), + json['repository_url'] as String, + json['html_url'] as String, + json['closed_by'] == null + ? null + : GithubUser.fromJson(json['closed_by'] as Map), + ); +} + +Map _$IssueToJson(Issue instance) => { + 'id': instance.id, + 'number': instance.number, + 'title': instance.title, + 'state': instance.state, + 'locked': instance.locked, + 'comments': instance.commentNum, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'closed_at': instance.closedAt?.toIso8601String(), + 'body': instance.body, + 'body_html': instance.bodyHtml, + 'user': instance.user, + 'repository_url': instance.repoUrl, + 'html_url': instance.htmlUrl, + 'closed_by': instance.closeBy, + }; diff --git a/lib/model/github/g/license.g.dart b/lib/model/github/g/license.g.dart new file mode 100644 index 0000000..a38d6f0 --- /dev/null +++ b/lib/model/github/g/license.g.dart @@ -0,0 +1,19 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../license.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +License _$LicenseFromJson(Map json) { + return License( + json['name'] as String, + json['spdx_id'] as String, + ); +} + +Map _$LicenseToJson(License instance) => { + 'name': instance.name, + 'spdx_id': instance.spdxId, + }; diff --git a/lib/model/github/g/repository.g.dart b/lib/model/github/g/repository.g.dart new file mode 100644 index 0000000..089d10f --- /dev/null +++ b/lib/model/github/g/repository.g.dart @@ -0,0 +1,96 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../repository.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Repository _$RepositoryFromJson(Map json) { + return Repository( + json['id'] as int, + json['size'] as int, + json['name'] as String, + json['full_name'] as String, + json['html_url'] as String, + json['description'] as String, + json['language'] as String, + json['license'] == null + ? null + : License.fromJson(json['license'] as Map), + json['default_branch'] as String, + json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + json['pushed_at'] == null + ? null + : DateTime.parse(json['pushed_at'] as String), + json['git_url'] as String, + json['ssh_url'] as String, + json['clone_url'] as String, + json['svn_url'] as String, + json['stargazers_count'] as int, + json['watchers_count'] as int, + json['forks_count'] as int, + json['open_issues_count'] as int, + json['subscribers_count'] as int, + json['private'] as bool, + json['fork'] as bool, + json['has_issues'] as bool, + json['has_projects'] as bool, + json['has_downloads'] as bool, + json['has_wiki'] as bool, + json['has_pages'] as bool, + json['owner'] == null + ? null + : GithubUser.fromJson(json['owner'] as Map), + json['parent'] == null + ? null + : Repository.fromJson(json['parent'] as Map), + json['permissions'] == null + ? null + : RepositoryPermissions.fromJson( + json['permissions'] as Map), + (json['topics'] as List)?.map((e) => e as String)?.toList(), + )..allIssueCount = json['allIssueCount'] as int; +} + +Map _$RepositoryToJson(Repository instance) => + { + 'id': instance.id, + 'size': instance.size, + 'name': instance.name, + 'full_name': instance.fullName, + 'html_url': instance.htmlUrl, + 'description': instance.description, + 'language': instance.language, + 'default_branch': instance.defaultBranch, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'pushed_at': instance.pushedAt?.toIso8601String(), + 'git_url': instance.gitUrl, + 'ssh_url': instance.sshUrl, + 'clone_url': instance.cloneUrl, + 'svn_url': instance.svnUrl, + 'stargazers_count': instance.stargazersCount, + 'watchers_count': instance.watchersCount, + 'forks_count': instance.forksCount, + 'open_issues_count': instance.openIssuesCount, + 'subscribers_count': instance.subscribersCount, + 'private': instance.private, + 'fork': instance.fork, + 'has_issues': instance.hasIssues, + 'has_projects': instance.hasProjects, + 'has_downloads': instance.hasDownloads, + 'has_wiki': instance.hasWiki, + 'has_pages': instance.hasPages, + 'owner': instance.owner, + 'license': instance.license, + 'parent': instance.parent, + 'permissions': instance.permissions, + 'topics': instance.topics, + 'allIssueCount': instance.allIssueCount, + }; diff --git a/lib/model/github/g/repository_permissions.g.dart b/lib/model/github/g/repository_permissions.g.dart new file mode 100644 index 0000000..699ed7c --- /dev/null +++ b/lib/model/github/g/repository_permissions.g.dart @@ -0,0 +1,24 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../repository_permissions.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RepositoryPermissions _$RepositoryPermissionsFromJson( + Map json) { + return RepositoryPermissions( + json['admin'] as bool, + json['push'] as bool, + json['pull'] as bool, + ); +} + +Map _$RepositoryPermissionsToJson( + RepositoryPermissions instance) => + { + 'admin': instance.admin, + 'push': instance.push, + 'pull': instance.pull, + }; diff --git a/lib/model/github/github_user.dart b/lib/model/github/github_user.dart new file mode 100644 index 0000000..5ece15b --- /dev/null +++ b/lib/model/github/github_user.dart @@ -0,0 +1,94 @@ +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +part 'g/github_user.g.dart'; + +class GithubUser { + GithubUser( + this.login, + this.id, + this.node_id, + this.avatar_url, + this.gravatar_id, + this.url, + this.html_url, + this.followers_url, + this.following_url, + this.gists_url, + this.starred_url, + this.subscriptions_url, + this.organizations_url, + this.repos_url, + this.events_url, + this.received_events_url, + this.type, + this.site_admin, + this.name, + this.company, + this.blog, + this.location, + this.email, + this.starred, + this.bio, + this.public_repos, + this.public_gists, + this.followers, + this.following, + this.created_at, + this.updated_at, + this.private_gists, + this.total_private_repos, + this.owned_private_repos, + this.disk_usage, + this.collaborators, + this.two_factor_authentication); + + String login; + int id; + String node_id; + String avatar_url; + String gravatar_id; + String url; + String html_url; + String followers_url; + String following_url; + String gists_url; + String starred_url; + String subscriptions_url; + String organizations_url; + String repos_url; + String events_url; + String received_events_url; + String type; + bool site_admin; + String name; + String company; + String blog; + String location; + String email; + String starred; + String bio; + int public_repos; + int public_gists; + int followers; + int following; + DateTime created_at; + DateTime updated_at; + int private_gists; + int total_private_repos; + int owned_private_repos; + int disk_usage; + int collaborators; + bool two_factor_authentication; + + + factory GithubUser.fromJson(Map json) => _$UserFromJson(json); + + + Map toJson() => _$UserToJson(this); + + // 命名构造函数 + GithubUser.empty(); + +} diff --git a/lib/model/github/issue.dart b/lib/model/github/issue.dart new file mode 100644 index 0000000..38c636d --- /dev/null +++ b/lib/model/github/issue.dart @@ -0,0 +1,77 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_unit/model/github/github_user.dart'; + +part 'g/issue.g.dart'; + +class Issue extends Equatable { + int id; + int number; + String title; + String state; + bool locked; + + int commentNum; + + DateTime createdAt; + + DateTime updatedAt; + + DateTime closedAt; + String body; + + String bodyHtml; + + GithubUser user; + + String repoUrl; + + String htmlUrl; + + GithubUser closeBy; + + Issue( + this.id, + this.number, + this.title, + this.state, + this.locked, + this.commentNum, + this.createdAt, + this.updatedAt, + this.closedAt, + this.body, + this.bodyHtml, + this.user, + this.repoUrl, + this.htmlUrl, + this.closeBy, + ); + + static Issue fromJson(dynamic json) => _$IssueFromJson(json); + + Map toJson() => _$IssueToJson(this); + + @override + String toString() { + return 'Issue{id: $id, number: $number, title: $title, state: $state, locked: $locked, commentNum: $commentNum, createdAt: $createdAt, updatedAt: $updatedAt, closedAt: $closedAt, body: $body, bodyHtml: $bodyHtml, user: $user, repoUrl: $repoUrl, htmlUrl: $htmlUrl, closeBy: $closeBy}'; + } + + @override + List get props => [ + id, + number, + title, + state, + locked, + commentNum, + createdAt, + updatedAt, + closedAt, + body, + bodyHtml, + user, + repoUrl, + htmlUrl, + closeBy, + ]; +} diff --git a/lib/model/github/license.dart b/lib/model/github/license.dart new file mode 100644 index 0000000..d9f70d1 --- /dev/null +++ b/lib/model/github/license.dart @@ -0,0 +1,14 @@ + +part 'g/license.g.dart'; + +class License { + + String name; + String spdxId; + + License(this.name,this.spdxId); + + factory License.fromJson(Map json) => _$LicenseFromJson(json); + + Map toJson() => _$LicenseToJson(this); +} diff --git a/lib/model/github/repository.dart b/lib/model/github/repository.dart new file mode 100644 index 0000000..1ab7931 --- /dev/null +++ b/lib/model/github/repository.dart @@ -0,0 +1,137 @@ + + +import 'package:flutter_unit/model/github/github_user.dart'; + +import 'license.dart'; +import 'repository_permissions.dart'; + +part 'g/repository.g.dart'; + +class Repository { + int id; + + int size; + + String name; + + String fullName; + + + String htmlUrl; + + String description; + + String language; + + + String defaultBranch; + + + DateTime createdAt; + + + DateTime updatedAt; + + DateTime pushedAt; + + + String gitUrl; + + + String sshUrl; + + + String cloneUrl; + + + String svnUrl; + + + int stargazersCount; + + + int watchersCount; + + + int forksCount; + + + int openIssuesCount; + + + int subscribersCount; + + + bool private; + + bool fork; + + bool hasIssues; + + bool hasProjects; + + bool hasDownloads; + + bool hasWiki; + + bool hasPages; + + GithubUser owner; + + License license; + + Repository parent; + + RepositoryPermissions permissions; + + List topics; + + int allIssueCount; + + Repository( + this.id, + this.size, + this.name, + this.fullName, + this.htmlUrl, + this.description, + this.language, + this.license, + this.defaultBranch, + this.createdAt, + this.updatedAt, + this.pushedAt, + this.gitUrl, + this.sshUrl, + this.cloneUrl, + this.svnUrl, + this.stargazersCount, + this.watchersCount, + this.forksCount, + this.openIssuesCount, + this.subscribersCount, + this.private, + this.fork, + this.hasIssues, + this.hasProjects, + this.hasDownloads, + this.hasWiki, + this.hasPages, + this.owner, + this.parent, + this.permissions, + this.topics, + ); + + factory Repository.fromJson(Map json) => _$RepositoryFromJson(json); + + + @override + String toString() { + return 'Repository{id: $id, size: $size, name: $name, fullName: $fullName, htmlUrl: $htmlUrl, description: $description, language: $language, defaultBranch: $defaultBranch, createdAt: $createdAt, updatedAt: $updatedAt, pushedAt: $pushedAt, gitUrl: $gitUrl, sshUrl: $sshUrl, cloneUrl: $cloneUrl, svnUrl: $svnUrl, stargazersCount: $stargazersCount, watchersCount: $watchersCount, forksCount: $forksCount, openIssuesCount: $openIssuesCount, subscribersCount: $subscribersCount, private: $private, fork: $fork, hasIssues: $hasIssues, hasProjects: $hasProjects, hasDownloads: $hasDownloads, hasWiki: $hasWiki, hasPages: $hasPages, owner: $owner, license: $license, parent: $parent, permissions: $permissions, topics: $topics, allIssueCount: $allIssueCount}'; + } + + Map toJson() => _$RepositoryToJson(this); + + Repository.empty(); +} diff --git a/lib/model/github/repository_permissions.dart b/lib/model/github/repository_permissions.dart new file mode 100644 index 0000000..5372306 --- /dev/null +++ b/lib/model/github/repository_permissions.dart @@ -0,0 +1,18 @@ + + +part 'g/repository_permissions.g.dart'; + +class RepositoryPermissions { + bool admin; + bool push; + bool pull; + + RepositoryPermissions( + this.admin, + this.push, + this.pull, + ); + + factory RepositoryPermissions.fromJson(Map json) => _$RepositoryPermissionsFromJson(json); + Map toJson() => _$RepositoryPermissionsToJson(this); +} diff --git a/lib/repositories/github_authentic_repository.dart b/lib/repositories/github_authentic_repository.dart new file mode 100644 index 0000000..f03a312 --- /dev/null +++ b/lib/repositories/github_authentic_repository.dart @@ -0,0 +1,28 @@ +import 'dart:async'; + +import 'package:flutter_unit/storage/dao/local_storage.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class GithubAuthenticRepository { + String _token; + + String get token => _token; + + //删除token + Future deleteToken() async { + var success = await LocalStorage.remove(LocalStorage.tokenKey); + return success; + } + + //固化token + Future persistToken(String token) async { + var success = await LocalStorage.save(LocalStorage.tokenKey,token); + return success; + } + + //检查是否有token + Future hasToken() async { + var token = await LocalStorage.get(LocalStorage.tokenKey); + return token == null; + } +} diff --git a/lib/repositories/github_user_repository.dart b/lib/repositories/github_user_repository.dart new file mode 100644 index 0000000..6e212c1 --- /dev/null +++ b/lib/repositories/github_user_repository.dart @@ -0,0 +1,54 @@ +import 'dart:convert'; +import 'package:flutter_unit/app/utils/http/rep_result.dart'; +import 'package:flutter_unit/model/github/github_user.dart'; + + +/// create by 张风捷特烈 on 2020/6/1 +/// contact me by email 1981462002@qq.com +/// 说明: + +class GithubUserRepository { + Future getLocalUser() async { +// return await LocalDao.dao.queryUser(); + } + + Future setLocalUser(GithubUser userBean) async { +// await LocalDao.dao.insert(userBean); + } + + Future authenticate({ + String username, + String password, + }) async { + String type = username + ":" + password; + var bytes = utf8.encode(type); + var base64Str = base64.encode(bytes); + +// var result = await GithubApi.authorizations(base64: base64Str); + +// result.msg = result.data["token"]; + +// return result; + +// // var result = await Api.login(username,password); +// // 通过result获取token和用户信息 +// await Future.delayed(Duration(seconds: 1));//模拟登陆请求耗时 +// if(username=='toly'&& password =='111111'){ +// return LoginResult( +// status: true, +// msg: '你获得的token值', +// user: UserBean(name: 'toly'), +// ); +// }else{ +// return LoginResult( +// status: false, +// msg: '用户名密码错误', +// user: null, +// ); +// } + } + + Future deleteLocalUser() async { +// return await LocalDao.dao.remove(); + } +} diff --git a/lib/storage/dao/local_storage.dart b/lib/storage/dao/local_storage.dart new file mode 100644 index 0000000..257821e --- /dev/null +++ b/lib/storage/dao/local_storage.dart @@ -0,0 +1,32 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +class LocalStorage { + + static String tokenKey= "token_key"; + + static SharedPreferences _sp; + + // 如果_sp已存在,直接返回,为null时创建 + static Future get sp async { + if (_sp == null) { + _sp = await SharedPreferences.getInstance(); + } + return _sp; + } + + static Future save(String key, String value) async { + return (await sp).setString(key, value); + } + + static dynamic get(String key) async { + return (await sp).get(key); + } + + static Future remove(String key) async { + return (await sp).remove(key); + } +} \ No newline at end of file diff --git a/lib/views/pages/category/collect_page.dart b/lib/views/pages/category/collect_page.dart index d6398a9..7ea031c 100644 --- a/lib/views/pages/category/collect_page.dart +++ b/lib/views/pages/category/collect_page.dart @@ -1,8 +1,10 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_unit/app/router.dart'; import 'package:flutter_unit/blocs/bloc_exp.dart'; import 'package:flutter_unit/components/permanent/circle_image.dart'; +import 'package:flutter_unit/components/permanent/feedback_widget.dart'; import 'category_page.dart'; import 'default_collect_page.dart'; @@ -53,9 +55,14 @@ class _CollectPageState extends State return SliverAppBar( leading: Container( margin: EdgeInsets.all(10), - child: CircleImage( - image: AssetImage('assets/images/icon_head.png'), - borderSize: 1.5, + child: FeedbackWidget( + onPressed: (){ + Navigator.of(context).pushNamed(Router.login); + }, + child: CircleImage( + image: AssetImage('assets/images/icon_head.png'), + borderSize: 1.5, + ), )), backgroundColor: BlocProvider.of(context).state.homeColor, actions: [_buildAddActionBuilder(context)], diff --git a/lib/views/pages/home/home_drawer.dart b/lib/views/pages/home/home_drawer.dart index 1ae3995..b49f2f3 100644 --- a/lib/views/pages/home/home_drawer.dart +++ b/lib/views/pages/home/home_drawer.dart @@ -50,7 +50,7 @@ class HomeDrawer extends StatelessWidget { _buildItem(context, TolyIcon.icon_tag, '属性集录', Router.attr), _buildItem(context, Icons.palette, '绘画集录', Router.paint), _buildItem(context, Icons.widgets, '布局集录', Router.layout), - _buildItem(context, TolyIcon.icon_bug, 'bug/feature 集录', Router.bug), + _buildItem(context, TolyIcon.icon_bug, '要点集录', Router.bug), ], ); diff --git a/lib/views/pages/issues/main.dart b/lib/views/pages/issues/main.dart new file mode 100644 index 0000000..85028a9 --- /dev/null +++ b/lib/views/pages/issues/main.dart @@ -0,0 +1,449 @@ +import 'dart:convert'; +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; + +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; +import 'package:flutter_unit/app/api/github/issues_api.dart'; +import 'package:flutter_unit/app/style/TolyIcon.dart'; +import 'package:flutter_unit/app/utils/convert_man.dart'; +import 'package:flutter_unit/app/utils/http/rep_result.dart'; +import 'package:flutter_unit/components/permanent/circle_image.dart'; +import 'package:flutter_unit/model/github/issue.dart'; +import 'package:flutter_unit/model/github/repository.dart'; + + +class IssuesPointPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( +// appBar: AppBar( +// title: Text("Flutter Unit Point"), +// ), + body: IssuesPointContent() +// ListView( +// children: [ +// RepoWidget(), +// IssuesPointContent(), +// ], +// ), + ); + } +} + +class RepoWidget extends StatelessWidget { + final Repository repository; + + RepoWidget({this.repository}); + + @override + Widget build(BuildContext context) { + return Container( + margin: EdgeInsets.only(top: 56 + 24.0, bottom: 5), + padding: EdgeInsets.all(10), +// alignment: Alignment.bottomCenter, + decoration: BoxDecoration( + boxShadow: [ + BoxShadow(color: Colors.grey, offset: Offset(0, .5), blurRadius: 3) + ], + color: Colors.white, + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10), + )), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Text( + repository.fullName, + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + WrapColor( + child:Text( + repository.license.spdxId, + style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white, fontSize: 12), + ), + ), + Spacer(), + Text( + "创建:" + + ConvertMan.time2string(repository.createdAt, just: true), + style: TextStyle(color: Colors.grey), + ), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Text( + repository.description, + style: TextStyle(color: Colors.grey), + ), + ), + Divider(), + Wrap( + children: [], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Icon(Icons.star_border), + Text(repository.stargazersCount.toString()), + ]), + Padding( + padding: const EdgeInsets.symmetric(vertical:8.0), + child: Text( + "|", + style: TextStyle(fontSize: 20,color: Colors.blue), + ), + ), + Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Icon(TolyIcon.icon_show), + SizedBox(width: 5,), + Text(repository.subscribersCount.toString()), + ]), + Padding( + padding: const EdgeInsets.symmetric(vertical:8.0), + child: Text( + "|", + style: TextStyle(fontSize: 20,color: Colors.blue), + ), + ), + Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Icon(TolyIcon.icon_fork), + Text(repository.forksCount.toString()), + ]), + Padding( + padding: const EdgeInsets.symmetric(vertical:8.0), + child: Text( + "|", + style: TextStyle(fontSize: 20,color: Colors.blue), + ), + ), + Wrap( + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Icon(TolyIcon.icon_issues), + SizedBox(width: 5,), + Text(repository.openIssuesCount.toString()), + ]), + ], + ) + ], + ), + ); + } +} + +class IssuesPointContent extends StatefulWidget { + @override + _IssuesPointContentState createState() => _IssuesPointContentState(); +} + +class _IssuesPointContentState extends State { + List _issues; + + @override + void initState() { + super.initState(); + _loadIssues(); + } + + @override + Widget build(BuildContext context) { + Widget content; + + if (_issues == null) { + content = SliverPadding( + padding: EdgeInsets.only(top: 40), + sliver: SliverToBoxAdapter( + child: SpinKitWave( + color: Colors.blue, + ) + ), + ); + } else { + content = SliverList( + delegate: SliverChildBuilderDelegate( + (_, int index) => IssueItem(issue: _issues[index]), + childCount: _issues.length), + ); + } + return Container( + child: RefreshIndicator( + onRefresh: _loadIssues, + child: CustomScrollView(slivers: [_buildSliverAppBar(), content] +// ListView.separated( +// shrinkWrap: true, +// separatorBuilder: (ctx, index) => Divider( +// height: 1, +// ), +// itemBuilder: (ctx, index) => IssueItem( +// issue: _issues[index], +// ), +// itemCount: _issues.length, +// ), + ), + ), + ); + } + + Widget _buildSliverAppBar() { + var repos = Repository.empty(); + + var jsonStr = """ + { + "id": 248628088, + "node_id": "MDEwOlJlcG9zaXRvcnkyNDg2MjgwODg=", + "name": "FlutterUnit", + "full_name": "toly1994328/FlutterUnit", + "private": false, + "owner": { + "login": "toly1994328", + "id": 26687012, + "node_id": "MDQ6VXNlcjI2Njg3MDEy", + "avatar_url": "https://avatars3.githubusercontent.com/u/26687012?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/toly1994328", + "html_url": "https://github.com/toly1994328", + "followers_url": "https://api.github.com/users/toly1994328/followers", + "following_url": "https://api.github.com/users/toly1994328/following{/other_user}", + "gists_url": "https://api.github.com/users/toly1994328/gists{/gist_id}", + "starred_url": "https://api.github.com/users/toly1994328/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/toly1994328/subscriptions", + "organizations_url": "https://api.github.com/users/toly1994328/orgs", + "repos_url": "https://api.github.com/users/toly1994328/repos", + "events_url": "https://api.github.com/users/toly1994328/events{/privacy}", + "received_events_url": "https://api.github.com/users/toly1994328/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/toly1994328/FlutterUnit", + "description": "【Flutter 集录指南 App】The unity of flutter, The unity of coder.", + "fork": false, + "url": "https://api.github.com/repos/toly1994328/FlutterUnit", + "forks_url": "https://api.github.com/repos/toly1994328/FlutterUnit/forks", + "keys_url": "https://api.github.com/repos/toly1994328/FlutterUnit/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/toly1994328/FlutterUnit/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/toly1994328/FlutterUnit/teams", + "hooks_url": "https://api.github.com/repos/toly1994328/FlutterUnit/hooks", + "issue_events_url": "https://api.github.com/repos/toly1994328/FlutterUnit/issues/events{/number}", + "events_url": "https://api.github.com/repos/toly1994328/FlutterUnit/events", + "assignees_url": "https://api.github.com/repos/toly1994328/FlutterUnit/assignees{/user}", + "branches_url": "https://api.github.com/repos/toly1994328/FlutterUnit/branches{/branch}", + "tags_url": "https://api.github.com/repos/toly1994328/FlutterUnit/tags", + "blobs_url": "https://api.github.com/repos/toly1994328/FlutterUnit/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/toly1994328/FlutterUnit/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/toly1994328/FlutterUnit/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/toly1994328/FlutterUnit/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/toly1994328/FlutterUnit/statuses/{sha}", + "languages_url": "https://api.github.com/repos/toly1994328/FlutterUnit/languages", + "stargazers_url": "https://api.github.com/repos/toly1994328/FlutterUnit/stargazers", + "contributors_url": "https://api.github.com/repos/toly1994328/FlutterUnit/contributors", + "subscribers_url": "https://api.github.com/repos/toly1994328/FlutterUnit/subscribers", + "subscription_url": "https://api.github.com/repos/toly1994328/FlutterUnit/subscription", + "commits_url": "https://api.github.com/repos/toly1994328/FlutterUnit/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/toly1994328/FlutterUnit/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/toly1994328/FlutterUnit/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/toly1994328/FlutterUnit/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/toly1994328/FlutterUnit/contents/{+path}", + "compare_url": "https://api.github.com/repos/toly1994328/FlutterUnit/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/toly1994328/FlutterUnit/merges", + "archive_url": "https://api.github.com/repos/toly1994328/FlutterUnit/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/toly1994328/FlutterUnit/downloads", + "issues_url": "https://api.github.com/repos/toly1994328/FlutterUnit/issues{/number}", + "pulls_url": "https://api.github.com/repos/toly1994328/FlutterUnit/pulls{/number}", + "milestones_url": "https://api.github.com/repos/toly1994328/FlutterUnit/milestones{/number}", + "notifications_url": "https://api.github.com/repos/toly1994328/FlutterUnit/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/toly1994328/FlutterUnit/labels{/name}", + "releases_url": "https://api.github.com/repos/toly1994328/FlutterUnit/releases{/id}", + "deployments_url": "https://api.github.com/repos/toly1994328/FlutterUnit/deployments", + "created_at": "2020-03-19T23:47:07Z", + "updated_at": "2020-06-16T19:39:41Z", + "pushed_at": "2020-06-16T05:15:27Z", + "git_url": "git://github.com/toly1994328/FlutterUnit.git", + "ssh_url": "git@github.com:toly1994328/FlutterUnit.git", + "clone_url": "https://github.com/toly1994328/FlutterUnit.git", + "svn_url": "https://github.com/toly1994328/FlutterUnit", + "homepage": "", + "size": 34927, + "stargazers_count": 1690, + "watchers_count": 1690, + "language": "Dart", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 225, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 26, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "forks": 225, + "open_issues": 26, + "watchers": 1690, + "default_branch": "master", + "temp_clone_token": null, + "network_count": 225, + "subscribers_count": 41 +} + """; + + repos.stargazersCount = 1000; + return SliverAppBar( + expandedHeight: 210.0, +// leading: _buildLeading(), + title: Text('Flutter要点集录'), +// actions: _buildActions(), + elevation: 5, + pinned: true, + backgroundColor: Colors.blue, + flexibleSpace: FlexibleSpaceBar( + //伸展处布局 + titlePadding: EdgeInsets.only(left: 55, bottom: 15), //标题边距 + collapseMode: CollapseMode.parallax, //视差效果 + background: RepoWidget( + repository: Repository.fromJson(json.decode(jsonStr)), + ), + ), + ); + } + + Future _loadIssues() async { + RepResult result = await IssuesApi.getIssues( + username: "toly1994328", + repository: "FlutterUnit", + labels: "point", + direction: "asc"); + print(result); + setState(() { + if (result != null) { + _issues = result.data; + } + }); + } +} + +class IssueItem extends StatelessWidget { + final Issue issue; + + IssueItem({this.issue}); + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5), + decoration: BoxDecoration( + border: Border( + bottom: BorderSide( + color: Theme.of(context).dividerColor, + width: 1 / window.devicePixelRatio))), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTop(), + Padding( + padding: const EdgeInsets.only(top: 5.0, bottom: 5.0, left: 10), + child: Text( + issue.title, + style: TextStyle(fontSize: 15, color: Colors.grey, shadows: [ + Shadow(color: Colors.white, offset: Offset(1, .5)) + ]), + ), + ), + Row( + children: [ + Spacer(), + WrapColor( + color: Colors.greenAccent, + child: Text(issue.commentNum.toString(),style: TextStyle(color: Colors.white),)), + SizedBox( + width: 5, + ), + Icon( + TolyIcon.icon_common, + size: 20, + ), + ], + ) + ], + ), + ); + } + + Widget _buildTop() { + return Row( + children: [ + CircleImage( + image: NetworkImage(issue.user.avatar_url), + size: 40, + borderSize: 2, + ), + SizedBox( + width: 10, + ), + WrapColor(child: Text("#${issue.number}",style: TextStyle(color: Colors.white),)), + SizedBox( + width: 10, + ), + Text( + issue.user.login, + style: TextStyle(fontWeight: FontWeight.bold), + ), + Spacer(), + Text(ConvertMan.time2string(issue.createdAt)), + ], + ); + } +} + +class WrapColor extends StatelessWidget { + final Widget child; + final Color color; + final double radius; + final EdgeInsetsGeometry padding; + + WrapColor( + {this.child, + this.color = Colors.blue, + this.radius = 5, + this.padding = + const EdgeInsets.only(left: 4, right: 4, top: 0, bottom: 0)}); + + @override + Widget build(BuildContext context) { + return Container( + child: child, + padding: padding, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.all(Radius.circular(radius))), + ); + } +} diff --git a/lib/views/pages/login/arc_clipper.dart b/lib/views/pages/login/arc_clipper.dart new file mode 100644 index 0000000..f4ff754 --- /dev/null +++ b/lib/views/pages/login/arc_clipper.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/4/27 +/// contact me by email 1981462002@qq.com +/// 说明: + +class ArcClipper extends CustomClipper { + final double factor; + + ArcClipper({this.factor = 0.618}); + + @override + Path getClip(Size size) => Path() + ..moveTo(0, 0) + ..relativeLineTo(size.width, 0) + ..relativeLineTo(0, 0.8 * size.height) + ..arcToPoint( + Offset(0.0, size.height * 0.618), + radius: const Radius.elliptical(50.0, 10.0), + rotation: 0.0, + ) + ..close(); + + @override + bool shouldReclip(ArcClipper oldClipper) => factor != oldClipper.factor; +} diff --git a/lib/views/pages/login/login_form.dart b/lib/views/pages/login/login_form.dart new file mode 100644 index 0000000..e9495a5 --- /dev/null +++ b/lib/views/pages/login/login_form.dart @@ -0,0 +1,202 @@ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_unit/app/style/TolyIcon.dart'; +import 'package:flutter_unit/components/permanent/feedback_widget.dart'; + + +class LoginFrom extends StatefulWidget { + @override + _LoginFromState createState() => _LoginFromState(); +} + +class _LoginFromState extends State { + final _usernameController = TextEditingController(text: 'toly1994328'); + final _passwordController = TextEditingController(text: '@#1994328zfjtl'); + + bool _showPwd = false; + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("FlutterUnit 登录",style: TextStyle(fontSize: 25),), + SizedBox(height: 10,), + Text("请使用github用户名登录",style: TextStyle(color: Colors.grey),), + SizedBox(height:20,), + buildUsernameInput(), + Stack( + alignment: Alignment(.8,0), + children: [ + buildPasswordInput(), + FeedbackWidget( + onPressed: ()=> setState(() => _showPwd=!_showPwd), + child: Icon(_showPwd?TolyIcon.icon_show:TolyIcon.icon_hide) + ) + ], + ), + Row( + children: [ + Checkbox(value: true, onChanged: (e) => {}), + Text( + "自动登录", + style: TextStyle(color: Color(0xff444444), fontSize: 14), + ), + Spacer(), + Text( + "如何注册?", + style: TextStyle( + color: Colors.blue, + fontSize: 14, + decoration: TextDecoration.underline), + ) + ], + ), + _buildBtn(), + buildOtherLogin() + ], + ); + } + + void _doLogIn() { + + print('---用户名:${_usernameController.text}------密码:${_passwordController.text}---'); + +// BlocProvider.of(context).add( +// EventLogin( +// username: _usernameController.text, +// password: _passwordController.text), +// ); + } + + Widget _buildBtn() => Container( + margin: EdgeInsets.only(top: 10, left: 10, right: 10,bottom: 10), + height: 40, + width: MediaQuery.of(context).size.width, + child: + RaisedButton( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(20))), + color: Colors.blue, + onPressed: _doLogIn, + child: Text("登 录", + style: TextStyle(color: Colors.white, fontSize: 18)), + ), + ); + + Widget buildUsernameInput(){ + return Column( + children: [ + Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.grey.withOpacity(0.5), + width: 1.0, + ), + borderRadius: BorderRadius.circular(15.0), + ), + margin: + const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), + child: Row( + children: [ + Padding( + padding: + EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), + child: Icon( + Icons.person_outline, + color: Colors.grey, + ), + ), + Container( + height: 30.0, + width: 1.0, + color: Colors.grey.withOpacity(0.5), + margin: const EdgeInsets.only(left: 00.0, right: 10.0), + ), + Expanded( + child: TextField( + controller: _usernameController, + decoration: InputDecoration( + border: InputBorder.none, + hintText: '请输入用户名...', + hintStyle: TextStyle(color: Colors.grey), + ), + ), + ) + ], + ), + ) + ], + ); + } + + Widget buildPasswordInput(){ + return Column( + children: [ + Container( + decoration: BoxDecoration( + border: Border.all( + color: Colors.grey.withOpacity(0.5), + width: 1.0, + ), + borderRadius: BorderRadius.circular(15.0), + ), + margin: + const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), + child: Row( + children: [ + Padding( + padding: + EdgeInsets.symmetric(vertical: 10.0, horizontal: 15.0), + child: Icon( + Icons.lock_outline, + color: Colors.grey, + ), + ), + Container( + height: 30.0, + width: 1.0, + color: Colors.grey.withOpacity(0.5), + margin: const EdgeInsets.only(left: 00.0, right: 10.0), + ), + Expanded( + child: TextField( + obscureText: !_showPwd, + controller: _passwordController, + decoration: InputDecoration( + border: InputBorder.none, + hintText: '请输入密码...', + hintStyle: TextStyle(color: Colors.grey), + ), + ), + ) + ], + ), + ) + ], + ); + } + Widget buildOtherLogin(){ + return Wrap( + alignment: WrapAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(top:30.0), + child: Row( + children: [ + Expanded(child: Divider(height: 20,)), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text('第三方登录',style: TextStyle(color: Colors.grey),), + ), + Expanded(child: Divider(height: 20,)), + ], + ), + ), + Icon(TolyIcon.icon_github,color: Colors.black, size: 30,) + ], + ); + } +} diff --git a/lib/views/pages/login/login_page.dart b/lib/views/pages/login/login_page.dart new file mode 100644 index 0000000..42c6dfe --- /dev/null +++ b/lib/views/pages/login/login_page.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_unit/app/style/TolyIcon.dart'; +//import 'package:santclient/app/global/SantIcon.dart'; +//import 'package:santclient/app/router/router.dart'; +//import 'package:santclient/bloc/authentic/bloc.dart'; +//import 'package:santclient/bloc/authentic/state.dart'; +//import 'package:santclient/bloc/bloc.dart'; +//import 'package:santclient/components/persistent/shape/arc_clipper.dart'; +//import 'package:santclient/components/project/error_msg.dart'; +//import 'package:santclient/components/project/loading_view.dart'; + +import 'arc_clipper.dart'; +import 'login_form.dart'; + +/// create by 张风捷特烈 on 2020/4/24 +/// contact me by email 1981462002@qq.com +/// 说明: + +class LoginPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return + +// BlocListener( +// listener: (context, state) { +// if (state is AuthSuccess) { +// Navigator.of(context).pushReplacementNamed(Router.nav); +// } +// }, +// child: + + Scaffold( + body: SingleChildScrollView( + child: Wrap(children: [ + arcBackground(), + Container( + width: MediaQuery.of(context).size.width, + padding: const EdgeInsets.only(left: 20.0, right: 20, top: 20), + child: +// +// BlocBuilder( +// builder: (_, state) { +// return + + Stack( + alignment: Alignment.center, + children: [ + LoginFrom(), +// if (state is LoginFailure) +// Positioned( +// bottom: 0, +// child: ErrorMsg( +// error: state.error, +// )), +// if (state is LoginLoading) +// LoadingView( +// text: "登录中...", +// ) + ], + + ) +// ); +// }, +// ), +// ) +// ] + )] +// ), + ), + )); + } + + Widget arcBackground() { + return ArcBackground( + image: AssetImage("assets/images/caver.jpeg"), + child: Padding( + padding: const EdgeInsets.all(50.0), + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: Colors.blue.withAlpha(88), shape: BoxShape.circle), + child: Icon(TolyIcon.icon_github,size: 100,) + ), + ), + ); + } +} + +class ArcBackground extends StatelessWidget { + final Widget child; + final ImageProvider image; + + ArcBackground({this.child, this.image}); + + @override + Widget build(BuildContext context) { + return ClipPath( + clipper: ArcClipper(), + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: image, + fit: BoxFit.cover, + ), + ), + alignment: Alignment.center, + child: child, + ), + ); + } +} diff --git a/lib/views/pages/search/serach_page.dart b/lib/views/pages/search/serach_page.dart index 7b3f91b..a38d4d3 100644 --- a/lib/views/pages/search/serach_page.dart +++ b/lib/views/pages/search/serach_page.dart @@ -110,7 +110,7 @@ class _SearchPageState extends State { Widget _buildSliverList(List models) => SliverList( delegate: SliverChildBuilderDelegate( (_, int index) => Container( - margin: EdgeInsets.symmetric(horizontal: 15, vertical: 5), +// margin: EdgeInsets.symmetric(horizontal: 15, vertical: 5), child: InkWell( onTap: () => _toDetailPage(models[index]), child: TechnoWidgetListItem( diff --git a/lib/views/pages/unit_todo/bug_unit_page.dart b/lib/views/pages/unit_todo/point_unit_page.dart similarity index 85% rename from lib/views/pages/unit_todo/bug_unit_page.dart rename to lib/views/pages/unit_todo/point_unit_page.dart index 4386ddc..088477c 100644 --- a/lib/views/pages/unit_todo/bug_unit_page.dart +++ b/lib/views/pages/unit_todo/point_unit_page.dart @@ -1,12 +1,14 @@ import 'package:flutter/material.dart'; +import 'package:flutter_unit/app/router.dart'; import 'package:flutter_unit/components/permanent/animated_text.dart'; import 'package:flutter_unit/components/permanent/circle_image.dart'; +import 'package:flutter_unit/components/permanent/feedback_widget.dart'; class BugUnitPage extends StatelessWidget { - final info = '【Flutter异常集录】是Unit项目计划的第二阶段的功能之一。' + final info = '【Flutter要点集录】是Unit项目计划的第二阶段的功能之一。' '将收录Flutter的常见异常及解决方案,也可以是Flutter中的特点或注意点,' '以供学习参考。本集录将支持异常/特色征集,愿开发者共同集录。'; @@ -14,7 +16,7 @@ class BugUnitPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text('bug/feature 集录'), + title: Text('要点集录'), ), body: Stack( alignment: Alignment.center, @@ -23,9 +25,14 @@ class BugUnitPage extends StatelessWidget { top: 50, child: Column( children: [ - CircleImage( - image: AssetImage('assets/images/icon_head.png'), - size: 80, + FeedbackWidget( + onPressed: (){ + Navigator.of(context).pushNamed(Router.issues_point); + }, + child: CircleImage( + image: AssetImage('assets/images/icon_head.png'), + size: 80, + ), ), SizedBox(height: 10,), Text( diff --git a/lib/views/widgets/Sliver/SliverOverlapAbsorber/node1_base.dart b/lib/views/widgets/Sliver/SliverOverlapAbsorber/node1_base.dart new file mode 100644 index 0000000..8f62261 --- /dev/null +++ b/lib/views/widgets/Sliver/SliverOverlapAbsorber/node1_base.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/6/16 +/// contact me by email 1981462002@qq.com +/// 说明: + +// { +// "widgetId": 307, +// "name": 'SliverOverlapAbsorber基本使用', +// "priority": 1, +// "subtitle": +// "【sliver】 : 子组件 【Widget】\n" +// "【handle】 : *处理器 【SliverOverlapAbsorberHandle】\n" +// "如果不使用SliverOverlapAbsorber和SliverOverlapInjector组件,NestedScrollView的内容会和头部栏重叠。", +// } + +class SliverOverlapAbsorberDemo extends StatelessWidget { + final _tabs = ['风神传', '封妖志', "幻将录", "永恒传说"]; + + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height - 200, + child: Scaffold( + body: DefaultTabController( + length: _tabs.length, + child: NestedScrollView( + headerSliverBuilder: + (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar( + title: const Text('旷古奇书'), + pinned: true, + elevation: 6, //影深 + expandedHeight: 220.0, + forceElevated: innerBoxIsScrolled, //为true时展开有阴影 + flexibleSpace: FlexibleSpaceBar( + background: Image.asset( + "assets/images/wy_300x200_filter.jpg", + fit: BoxFit.cover, + ), + ), + bottom: TabBar( + tabs: _tabs + .map((String name) => Tab(text: name,)) + .toList(), + ), + ), + ), + ]; + }, + body: _buildTabBarView(), + ), + ), + )); + } + + Widget _buildTabBarView() { + return TabBarView( + children: _tabs.map((String name) { + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (BuildContext context) { + return CustomScrollView( + key: PageStorageKey(name), + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverPadding( + padding: const EdgeInsets.all(8.0), + sliver: SliverFixedExtentList( + itemExtent: 48.0, + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return ListTile( + title: Text('《$name》 第 $index章'), + ); + }, + childCount: 50, + ), + ), + ), + ], + ); + }, + ), + ); + }).toList(), + ); + } +} diff --git a/lib/views/widgets/Sliver/SliverOverlapInjector/node1_base.dart b/lib/views/widgets/Sliver/SliverOverlapInjector/node1_base.dart new file mode 100644 index 0000000..4381e06 --- /dev/null +++ b/lib/views/widgets/Sliver/SliverOverlapInjector/node1_base.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/6/16 +/// contact me by email 1981462002@qq.com +/// 说明: + +// { +// "widgetId": 308, +// "name": 'SliverOverlapInjector基本使用', +// "priority": 1, +// "subtitle": +// "【sliver】 : 子组件 【Widget】\n" +// "【handle】 : *处理器 【SliverOverlapAbsorberHandle】\n" +// "如果不使用SliverOverlapAbsorber和SliverOverlapInjector组件,NestedScrollView的内容会和头部栏重叠。", +// } + +class SliverOverlapInjectorDemo extends StatelessWidget { + final _tabs = ['风神传', '封妖志', "幻将录", "永恒传说"]; + + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height - 200, + child: Scaffold( + body: DefaultTabController( + length: _tabs.length, + child: NestedScrollView( + headerSliverBuilder: + (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: SliverAppBar( + title: const Text('旷古奇书'), + pinned: true, + elevation: 6, //影深 + expandedHeight: 220.0, + forceElevated: innerBoxIsScrolled, //为true时展开有阴影 + flexibleSpace: FlexibleSpaceBar( + background: Image.asset( + "assets/images/wy_300x200_filter.jpg", + fit: BoxFit.cover, + ), + ), + bottom: TabBar( + tabs: _tabs + .map((String name) => Tab(text: name,)) + .toList(), + ), + ), + ), + ]; + }, + body: _buildTabBarView(), + ), + ), + )); + } + + Widget _buildTabBarView() { + return TabBarView( + children: _tabs.map((String name) { + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (BuildContext context) { + return CustomScrollView( + key: PageStorageKey(name), + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverPadding( + padding: const EdgeInsets.all(8.0), + sliver: SliverFixedExtentList( + itemExtent: 48.0, + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return ListTile( + title: Text('《$name》 第 $index章'), + ); + }, + childCount: 50, + ), + ), + ), + ], + ); + }, + ), + ); + }).toList(), + ); + } +} diff --git a/lib/views/widgets/StatefulWidget/NestedScrollView/node1_base.dart b/lib/views/widgets/StatefulWidget/NestedScrollView/node1_base.dart new file mode 100644 index 0000000..1c97db1 --- /dev/null +++ b/lib/views/widgets/StatefulWidget/NestedScrollView/node1_base.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; + +/// create by 张风捷特烈 on 2020/6/16 +/// contact me by email 1981462002@qq.com +/// 说明: + +// { +// "widgetId": 251, +// "name": 'NestedScrollView基本用法', +// "priority": 1, +// "subtitle": +// "【controller】 : 滑动控制器 【ScrollController】\n" +// "【scrollDirection】 : 滑动方向 【Axis】\n" +// "【reverse】 : 是否反向 【bool】\n" +// "【physics】 : 滑顶样式 【ScrollPhysics】\n" +// "【dragStartBehavior】 : 开始拖动行为 【DragStartBehavior】\n" +// "【headerSliverBuilder】 : *头部构造器 【NestedScrollViewHeaderSliversBuilder】\n" +// "【body】 : *内容 【Widget】", +// } + +class NestedScrollViewDemo extends StatelessWidget { + final _tabs = ['风神传', '封妖志', "幻将录", "永恒传说"]; + + @override + Widget build(BuildContext context) { + return Container( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height - 200, + child: Scaffold( + body: DefaultTabController( + length: _tabs.length, + child: NestedScrollView( + headerSliverBuilder: + (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor( + context), + sliver: SliverAppBar( + title: const Text('旷古奇书'), + pinned: true, + elevation: 6, //影深 + expandedHeight: 220.0, + forceElevated: innerBoxIsScrolled, //为true时展开有阴影 + flexibleSpace: FlexibleSpaceBar( + background: Image.asset( + "assets/images/wy_300x200_filter.jpg", + fit: BoxFit.cover, + ), + ), + bottom: TabBar( + tabs: _tabs + .map((String name) => Tab(text: name,)) + .toList(), + ), + ), + ), + ]; + }, + body: _buildTabBarView(), + ), + ), + )); + } + + Widget _buildTabBarView() { + return TabBarView( + children: _tabs.map((String name) { + return SafeArea( + top: false, + bottom: false, + child: Builder( + builder: (BuildContext context) { + return CustomScrollView( + key: PageStorageKey(name), + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), + ), + SliverPadding( + padding: const EdgeInsets.all(8.0), + sliver: SliverFixedExtentList( + itemExtent: 48.0, + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return ListTile( + title: Text('《$name》 第 $index章'), + ); + }, + childCount: 50, + ), + ), + ), + ], + ); + }, + ), + ); + }).toList(), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index ad96f4d..357155a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -50,6 +50,27 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.14.12" + connectivity: + dependency: "direct main" + description: + name: connectivity + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.4.8+6" + connectivity_macos: + dependency: transitive + description: + name: connectivity_macos + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.0+3" + connectivity_platform_interface: + dependency: transitive + description: + name: connectivity_platform_interface + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.6" convert: dependency: transitive description: @@ -71,6 +92,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "0.1.3" + dio: + dependency: "direct main" + description: + name: dio + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.0.9" equatable: dependency: "direct main" description: @@ -90,6 +118,13 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "3.2.0" + flutter_spinkit: + dependency: "direct main" + description: + name: flutter_spinkit + url: "https://pub.flutter-io.cn" + source: hosted + version: "4.1.2+1" flutter_star: dependency: "direct main" description: @@ -107,6 +142,13 @@ packages: description: flutter source: sdk version: "0.0.0" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.flutter-io.cn" + source: hosted + version: "3.1.4" image: dependency: transitive description: @@ -345,4 +387,4 @@ packages: version: "3.6.1" sdks: dart: ">=2.7.0 <3.0.0" - flutter: ">=1.12.13+hotfix.4 <2.0.0" + flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 2fa5cc9..7cc2c35 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,9 @@ dependencies: url_launcher: ^5.4.2 # url share: ^0.6.3+6 intl: ^0.16.1 + connectivity: ^0.4.8+6 #网络状态 + flutter_spinkit: ^4.1.2+1 #loading + dio: ^3.0.9 #dio dev_dependencies: flutter_test: sdk: flutter diff --git a/test/github/issues.dart b/test/github/issues.dart new file mode 100644 index 0000000..9c03fe8 --- /dev/null +++ b/test/github/issues.dart @@ -0,0 +1,19 @@ +import 'package:flutter_unit/app/api/github/issues_api.dart'; + +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +main() { + getIssues(); +} + +void getIssues() async { + var result = await IssuesApi.getIssues( + username: "toly1994328", + repository: "FlutterUnit", + labels: "point", + pageSize: 2); + + print(result); +} diff --git a/test/github/repos.dart b/test/github/repos.dart new file mode 100644 index 0000000..2fb688f --- /dev/null +++ b/test/github/repos.dart @@ -0,0 +1,17 @@ +import 'package:flutter_unit/app/api/github/issues_api.dart'; +import 'package:flutter_unit/app/api/github/repos_api.dart'; + +/// create by 张风捷特烈 on 2020/6/17 +/// contact me by email 1981462002@qq.com +/// 说明: + +main() { + getRepos(); +} + +void getRepos() async { + var result = await ReposApi.getRepos( + username: "toly1994328", + repository: "FlutterUnit"); + print(result); +} diff --git a/test/widget_test.dart b/test/widget_test.dart index 93dbe92..53d91bb 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -26,6 +26,6 @@ // // Verify that our counter has incremented. // expect(find.text('0'), findsNothing); // expect(find.text('1'), findsOneWidget); - +// // }); //}