From 434781a27e9d99aecb201c488a1f0a8d951c51bd Mon Sep 17 00:00:00 2001 From: Rongjian Zhang Date: Fri, 8 Feb 2019 19:34:07 +0800 Subject: [PATCH] feat: login screen style, add settings screen, extract simple scaffold --- lib/main.dart | 12 ++-- lib/providers/settings.dart | 19 +++++-- lib/scaffolds/refresh.dart | 3 +- lib/scaffolds/simple.dart | 46 +++++++++++++++ lib/screens/login.dart | 61 ++++++++++++++++---- lib/screens/{profile.dart => me.dart} | 7 ++- lib/screens/notifications.dart | 1 + lib/screens/search.dart | 6 +- lib/screens/settings.dart | 19 +++++++ lib/screens/user.dart | 80 +++++++++++++++++++++------ lib/widgets/link.dart | 52 +++++++++++------ lib/widgets/loading.dart | 2 +- 12 files changed, 242 insertions(+), 66 deletions(-) create mode 100644 lib/scaffolds/simple.dart rename lib/screens/{profile.dart => me.dart} (58%) create mode 100644 lib/screens/settings.dart diff --git a/lib/main.dart b/lib/main.dart index 9c924a8..d147802 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,7 +5,7 @@ import 'providers/settings.dart'; import 'screens/news.dart'; import 'screens/notifications.dart'; import 'screens/search.dart'; -import 'screens/profile.dart'; +import 'screens/me.dart'; import 'screens/login.dart'; import 'screens/pull_request.dart'; import 'screens/issue.dart'; @@ -21,14 +21,14 @@ class _HomeState extends State { Widget _buildNotificationIcon(BuildContext context) { int count = NotificationProvider.of(context).count; if (count == 0) { - return Icon(Icons.notifications); + return Icon(Icons.notifications_none); } // String text = count > 99 ? '99+' : count.toString(); // https://stackoverflow.com/a/45434404 return new Stack(children: [ - new Icon(Icons.notifications), + new Icon(Icons.notifications_none), new Positioned( // draw a red marble top: 0.0, @@ -53,7 +53,7 @@ class _HomeState extends State { title: Text('Search'), ), BottomNavigationBarItem( - icon: Icon(Icons.person), + icon: Icon(Icons.person_outline), title: Text('Me'), ), ]; @@ -69,7 +69,7 @@ class _HomeState extends State { case 2: return SearchScreen(); case 3: - return ProfileScreen(); + return MeScreen(); } } @@ -82,7 +82,7 @@ class _HomeState extends State { } if (settings.activeLogin == null) { - return LoginScreen(); + return MaterialApp(home: LoginScreen()); } switch (settings.theme) { diff --git a/lib/providers/settings.dart b/lib/providers/settings.dart index 0ca8434..2ae9faa 100644 --- a/lib/providers/settings.dart +++ b/lib/providers/settings.dart @@ -21,18 +21,27 @@ class Account { String avatarUrl; String token; - Account({this.avatarUrl, this.token}); + /// for github enterprise + String domain; + + Account({ + @required this.avatarUrl, + @required this.token, + this.domain, + }); Account.fromJson(input) { avatarUrl = input['avatarUrl']; token = input['token']; + domain = input['domain']; } Map toJson() { - return { - 'avatarUrl': avatarUrl, - 'token': token, - }; + var data = {'avatarUrl': avatarUrl, 'token': token}; + if (domain != null) { + data['domain'] = domain; + } + return data; } } diff --git a/lib/scaffolds/refresh.dart b/lib/scaffolds/refresh.dart index 719ece3..335eeee 100644 --- a/lib/scaffolds/refresh.dart +++ b/lib/scaffolds/refresh.dart @@ -7,8 +7,7 @@ import '../widgets/loading.dart'; typedef RefreshCallback = Future Function(); typedef WidgetBuilder = Widget Function(); -// This is a scaffold for normal screens -// Users can pull to refresh +// This is a scaffold for pull to refresh class RefreshScaffold extends StatelessWidget { final Widget title; final WidgetBuilder bodyBuilder; diff --git a/lib/scaffolds/simple.dart b/lib/scaffolds/simple.dart new file mode 100644 index 0000000..8ed2a47 --- /dev/null +++ b/lib/scaffolds/simple.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/widgets.dart'; +import '../providers/settings.dart'; + +typedef RefreshCallback = Future Function(); +typedef WidgetBuilder = Widget Function(); + +class SimpleScaffold extends StatelessWidget { + final Widget title; + final WidgetBuilder bodyBuilder; + final Widget trailing; + final List actions; + final PreferredSizeWidget bottom; + + SimpleScaffold({ + @required this.title, + @required this.bodyBuilder, + this.trailing, + this.actions, + this.bottom, + }); + + @override + Widget build(BuildContext context) { + switch (SettingsProvider.of(context).theme) { + case ThemeMap.cupertino: + return CupertinoPageScaffold( + navigationBar: + CupertinoNavigationBar(middle: title, trailing: trailing), + child: SafeArea( + child: SingleChildScrollView(child: bodyBuilder()), + ), + ); + default: + return Scaffold( + appBar: AppBar( + title: title, + actions: actions, + bottom: bottom, + ), + body: SingleChildScrollView(child: bodyBuilder()), + ); + } + } +} diff --git a/lib/screens/login.dart b/lib/screens/login.dart index d9ad411..e49fb3a 100644 --- a/lib/screens/login.dart +++ b/lib/screens/login.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; import '../providers/settings.dart'; +import '../scaffolds/simple.dart'; import '../utils/utils.dart'; +import '../widgets/link.dart'; class LoginScreen extends StatefulWidget { @override @@ -13,23 +15,58 @@ class _LoginScreenState extends State { Widget build(BuildContext context) { var settings = SettingsProvider.of(context); - return MaterialApp( - home: Scaffold( - body: Container( - padding: EdgeInsets.only(top: 200), + return SimpleScaffold( + title: Text('Accounts'), + bodyBuilder: () { + return Container( child: Column( children: settings.githubAccountMap.entries.map((entry) { - return RaisedButton( - child: Text(entry.key), - onPressed: () { + return Link( + beforeRedirect: () { settings.setActiveAccount(entry.key); }, + child: Container( + padding: EdgeInsets.all(10), + decoration: BoxDecoration( + border: Border(bottom: BorderSide(color: Colors.black12)), + ), + child: Row(children: [ + CircleAvatar( + backgroundColor: Colors.transparent, + backgroundImage: NetworkImage(entry.value.avatarUrl), + radius: 24, + ), + Padding(padding: EdgeInsets.only(left: 10)), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(entry.key, style: TextStyle(fontSize: 20)), + Padding(padding: EdgeInsets.only(top: 6)), + Text(entry.value.domain ?? 'https://github.com') + ], + ), + ), + settings.activeLogin == entry.key + ? Icon(Icons.check) + : Container(), + ]), + ), ); }).toList() ..add( - RaisedButton( - child: Text('Login'), - onPressed: () { + Link( + child: Container( + padding: EdgeInsets.symmetric(vertical: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.add), + Text('Add account', style: TextStyle(fontSize: 16)), + ], + ), + ), + beforeRedirect: () { var state = settings.generateRandomString(); launch( 'https://github.com/login/oauth/authorize?client_id=$clientId&redirect_uri=gittouch://login&scope=user%20repo&state=$state', @@ -39,8 +76,8 @@ class _LoginScreenState extends State { ), ), ), - ), - ), + ); + }, ); } } diff --git a/lib/screens/profile.dart b/lib/screens/me.dart similarity index 58% rename from lib/screens/profile.dart rename to lib/screens/me.dart index b982800..e33c400 100644 --- a/lib/screens/profile.dart +++ b/lib/screens/me.dart @@ -3,9 +3,12 @@ import 'package:flutter/cupertino.dart'; import '../providers/settings.dart'; import '../screens/user.dart'; -class ProfileScreen extends StatelessWidget { +class MeScreen extends StatelessWidget { @override Widget build(BuildContext context) { - return UserScreen(SettingsProvider.of(context).activeLogin); + return UserScreen( + SettingsProvider.of(context).activeLogin, + showSettings: true, + ); } } diff --git a/lib/screens/notifications.dart b/lib/screens/notifications.dart index 83df062..6726e18 100644 --- a/lib/screens/notifications.dart +++ b/lib/screens/notifications.dart @@ -125,6 +125,7 @@ $key: pullRequest(number: ${item.number}) { style: TextStyle(color: Colors.black, fontSize: 15), ), Link( + material: false, beforeRedirect: () async { await SettingsProvider.of(context) .putWithCredentials('/repos/$repo/notifications'); diff --git a/lib/screens/search.dart b/lib/screens/search.dart index 7762f7b..a09b6cc 100644 --- a/lib/screens/search.dart +++ b/lib/screens/search.dart @@ -43,10 +43,8 @@ class _SearchScreenState extends State { var user = users[index]; return Row( children: [ - Image.network( - user['avatarUrl'], - ), - Text(user['login']) + Image.network(user['avatarUrl']), + Text(user['login']), ], ); }, diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart new file mode 100644 index 0000000..4b45e41 --- /dev/null +++ b/lib/screens/settings.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; +import '../scaffolds/simple.dart'; + +class SettingsScreen extends StatefulWidget { + @override + _SettingsScreenState createState() => _SettingsScreenState(); +} + +class _SettingsScreenState extends State { + @override + Widget build(BuildContext context) { + return SimpleScaffold( + title: Text('Settings'), + bodyBuilder: () { + return Text('body'); + }, + ); + } +} diff --git a/lib/screens/user.dart b/lib/screens/user.dart index 2b8ae4c..168150d 100644 --- a/lib/screens/user.dart +++ b/lib/screens/user.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; +import 'package:url_launcher/url_launcher.dart'; import '../providers/settings.dart'; import '../scaffolds/refresh.dart'; import '../widgets/avatar.dart'; import '../widgets/entry_item.dart'; import '../widgets/list_group.dart'; import '../widgets/repo_item.dart'; +import '../widgets/link.dart'; import '../screens/repos.dart'; import '../screens/users.dart'; +import '../screens/settings.dart'; import '../utils/utils.dart'; var repoChunk = ''' @@ -30,13 +33,11 @@ primaryLanguage { } '''; -final GlobalKey _refreshIndicatorKey = - GlobalKey(); - class UserScreen extends StatefulWidget { final String login; + final bool showSettings; - UserScreen(this.login); + UserScreen(this.login, {this.showSettings = false}); _UserScreenState createState() => _UserScreenState(); } @@ -60,6 +61,8 @@ class _UserScreenState extends State { avatarUrl bio email + company + location starredRepositories { totalCount } @@ -117,11 +120,66 @@ class _UserScreenState extends State { } } + Widget _buildEmail() { + // TODO: redesign the UI to show all information + String email = payload['email'] ?? ''; + if (email.isNotEmpty) { + return Link( + child: Row(children: [ + Icon( + Octicons.mail, + color: Colors.black54, + size: 16, + ), + Padding(padding: EdgeInsets.only(left: 4)), + Text(email, style: TextStyle(color: Colors.black54, fontSize: 16)) + ]), + beforeRedirect: () { + launch('mailto:' + email); + }, + ); + } + + String company = payload['company'] ?? ''; + if (company.isNotEmpty) { + return Row(children: [ + Icon( + Octicons.organization, + color: Colors.black54, + size: 16, + ), + Padding(padding: EdgeInsets.only(left: 4)), + Text(company, style: TextStyle(color: Colors.black54, fontSize: 16)) + ]); + } + + String location = payload['location'] ?? ''; + if (location.isNotEmpty) { + return Row(children: [ + Icon( + Octicons.location, + color: Colors.black54, + size: 16, + ), + Padding(padding: EdgeInsets.only(left: 4)), + Text(location, style: TextStyle(color: Colors.black54, fontSize: 16)) + ]); + } + + return Container(); + } + @override Widget build(BuildContext context) { return RefreshScaffold( onRefresh: _refresh, title: Text(widget.login), + trailing: Link( + child: Icon(Icons.settings, size: 24), + screenBuilder: (_) => SettingsScreen(), + material: false, + fullscreenDialog: true, + ), loading: loading, bodyBuilder: () { return Column( @@ -144,19 +202,7 @@ class _UserScreenState extends State { Text(payload['name'] ?? widget.login, style: TextStyle(height: 1.2)), Padding(padding: EdgeInsets.only(top: 10)), - Row(children: [ - Icon( - Octicons.mail, - color: Colors.black54, - size: 16, - ), - Padding(padding: EdgeInsets.only(left: 4)), - Text( - payload['email'], - style: - TextStyle(color: Colors.black54, fontSize: 16), - ) - ]) + _buildEmail(), ], ), ) diff --git a/lib/widgets/link.dart b/lib/widgets/link.dart index cc0f64d..295a6e7 100644 --- a/lib/widgets/link.dart +++ b/lib/widgets/link.dart @@ -7,40 +7,58 @@ class Link extends StatelessWidget { final WidgetBuilder screenBuilder; final Function beforeRedirect; final Color bgColor; + final bool material; + final bool fullscreenDialog; Link({ @required this.child, this.screenBuilder, this.beforeRedirect, this.bgColor, + this.material = true, + this.fullscreenDialog = false, }); + void _onTap(BuildContext context, int theme) { + if (beforeRedirect != null) { + beforeRedirect(); + } + + if (screenBuilder != null) { + switch (theme) { + case ThemeMap.cupertino: + Navigator.of(context).push(CupertinoPageRoute( + builder: screenBuilder, + fullscreenDialog: fullscreenDialog, + )); + break; + default: + Navigator.of(context).push(MaterialPageRoute( + builder: screenBuilder, + fullscreenDialog: fullscreenDialog, + )); + } + } + } + @override Widget build(BuildContext context) { var theme = SettingsProvider.of(context).theme; + + if (!material) { + return GestureDetector( + child: child, + onTap: () => _onTap(context, theme), + ); + } + return Material( child: Ink( color: bgColor ?? Colors.white, child: InkWell( child: child, splashColor: theme == ThemeMap.cupertino ? Colors.transparent : null, - onTap: () { - if (beforeRedirect != null) { - beforeRedirect(); - } - - if (screenBuilder != null) { - switch (theme) { - case ThemeMap.cupertino: - Navigator.of(context) - .push(CupertinoPageRoute(builder: screenBuilder)); - break; - default: - Navigator.of(context) - .push(MaterialPageRoute(builder: screenBuilder)); - } - } - }, + onTap: () => _onTap(context, theme), ), ), ); diff --git a/lib/widgets/loading.dart b/lib/widgets/loading.dart index 5867d21..246577c 100644 --- a/lib/widgets/loading.dart +++ b/lib/widgets/loading.dart @@ -31,7 +31,7 @@ class Loading extends StatelessWidget { ); } else { return Padding( - padding: EdgeInsets.symmetric(vertical: 40), + padding: EdgeInsets.symmetric(vertical: 100), child: _buildIndicator(context), ); }