mirror of
https://github.com/git-touch/git-touch
synced 2025-01-27 14:19:24 +01:00
feat: login screen style, add settings screen, extract simple scaffold
This commit is contained in:
parent
ef03caa135
commit
434781a27e
@ -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<Home> {
|
||||
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: <Widget>[
|
||||
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<Home> {
|
||||
title: Text('Search'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person),
|
||||
icon: Icon(Icons.person_outline),
|
||||
title: Text('Me'),
|
||||
),
|
||||
];
|
||||
@ -69,7 +69,7 @@ class _HomeState extends State<Home> {
|
||||
case 2:
|
||||
return SearchScreen();
|
||||
case 3:
|
||||
return ProfileScreen();
|
||||
return MeScreen();
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,7 +82,7 @@ class _HomeState extends State<Home> {
|
||||
}
|
||||
|
||||
if (settings.activeLogin == null) {
|
||||
return LoginScreen();
|
||||
return MaterialApp(home: LoginScreen());
|
||||
}
|
||||
|
||||
switch (settings.theme) {
|
||||
|
@ -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<String, dynamic> toJson() {
|
||||
return {
|
||||
'avatarUrl': avatarUrl,
|
||||
'token': token,
|
||||
};
|
||||
var data = {'avatarUrl': avatarUrl, 'token': token};
|
||||
if (domain != null) {
|
||||
data['domain'] = domain;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,8 +7,7 @@ import '../widgets/loading.dart';
|
||||
typedef RefreshCallback = Future<void> 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;
|
||||
|
46
lib/scaffolds/simple.dart
Normal file
46
lib/scaffolds/simple.dart
Normal file
@ -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<void> Function();
|
||||
typedef WidgetBuilder = Widget Function();
|
||||
|
||||
class SimpleScaffold extends StatelessWidget {
|
||||
final Widget title;
|
||||
final WidgetBuilder bodyBuilder;
|
||||
final Widget trailing;
|
||||
final List<Widget> 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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -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<LoginScreen> {
|
||||
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<Widget>((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: <Widget>[
|
||||
CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
backgroundImage: NetworkImage(entry.value.avatarUrl),
|
||||
radius: 24,
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(left: 10)),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
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: <Widget>[
|
||||
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<LoginScreen> {
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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');
|
||||
|
@ -43,10 +43,8 @@ class _SearchScreenState extends State<SearchScreen> {
|
||||
var user = users[index];
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Image.network(
|
||||
user['avatarUrl'],
|
||||
),
|
||||
Text(user['login'])
|
||||
Image.network(user['avatarUrl']),
|
||||
Text(user['login']),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
19
lib/screens/settings.dart
Normal file
19
lib/screens/settings.dart
Normal file
@ -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<SettingsScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SimpleScaffold(
|
||||
title: Text('Settings'),
|
||||
bodyBuilder: () {
|
||||
return Text('body');
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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<RefreshIndicatorState> _refreshIndicatorKey =
|
||||
GlobalKey<RefreshIndicatorState>();
|
||||
|
||||
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<UserScreen> {
|
||||
avatarUrl
|
||||
bio
|
||||
email
|
||||
company
|
||||
location
|
||||
starredRepositories {
|
||||
totalCount
|
||||
}
|
||||
@ -117,11 +120,66 @@ class _UserScreenState extends State<UserScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildEmail() {
|
||||
// TODO: redesign the UI to show all information
|
||||
String email = payload['email'] ?? '';
|
||||
if (email.isNotEmpty) {
|
||||
return Link(
|
||||
child: Row(children: <Widget>[
|
||||
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: <Widget>[
|
||||
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: <Widget>[
|
||||
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<UserScreen> {
|
||||
Text(payload['name'] ?? widget.login,
|
||||
style: TextStyle(height: 1.2)),
|
||||
Padding(padding: EdgeInsets.only(top: 10)),
|
||||
Row(children: <Widget>[
|
||||
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(),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -31,7 +31,7 @@ class Loading extends StatelessWidget {
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 40),
|
||||
padding: EdgeInsets.symmetric(vertical: 100),
|
||||
child: _buildIndicator(context),
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user