feat: add actions to repo, user and issue screen

This commit is contained in:
Rongjian Zhang 2019-02-10 18:50:40 +08:00
parent 925472f151
commit d9bec759e3
8 changed files with 337 additions and 29 deletions

View File

@ -221,6 +221,7 @@ class _SettingsProviderState extends State<SettingsProvider> {
.timeout(_timeoutDuration);
final data = json.decode(res.body);
print(data);
if (data['errors'] != null) {
throw new Exception(data['errors'].toString());
@ -257,12 +258,21 @@ class _SettingsProviderState extends State<SettingsProvider> {
.put(prefix + url, headers: headers, body: body ?? {})
.timeout(_timeoutDuration);
// print(res.body);
print(res.body);
// final data = json.decode(res.body);
// return data;
return true;
}
Future<dynamic> deleteWithCredentials(String url) async {
var headers = {HttpHeaders.authorizationHeader: 'token $token'};
final res = await http
.delete(prefix + url, headers: headers)
.timeout(_timeoutDuration);
print(res.body);
return true;
}
String randomString;
generateRandomString() {

View File

@ -28,8 +28,8 @@ class LongListPayload<T, K> {
// e.g. https://github.com/reactjs/rfcs/pull/68
class LongListScaffold<T, K> extends StatefulWidget {
final Widget title;
final List<Widget> actions;
final Widget trailing;
final List<Widget> Function(T headerPayload) actionsBuilder;
final Widget Function(T headerPayload) trailingBuilder;
final Widget Function(T headerPayload) headerBuilder;
final Widget Function(K itemPayload) itemBuilder;
final Future<LongListPayload<T, K>> Function() onRefresh;
@ -37,8 +37,8 @@ class LongListScaffold<T, K> extends StatefulWidget {
LongListScaffold({
@required this.title,
this.actions,
this.trailing,
this.actionsBuilder,
this.trailingBuilder,
@required this.headerBuilder,
@required this.itemBuilder,
@required this.onRefresh,
@ -208,21 +208,28 @@ class _LongListScaffoldState<T, K> extends State<LongListScaffold<T, K>> {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: widget.title,
trailing: widget.trailing,
trailing:
payload == null ? null : widget.trailingBuilder(payload.header),
),
child: SafeArea(
child: CustomScrollView(slivers: slivers),
),
);
default:
List<Widget> children = [];
if (payload != null) {
children.add(widget.headerBuilder(payload.header));
}
children.add(_buildBody());
return Scaffold(
appBar: AppBar(
title: widget.title,
actions: widget.actions,
actions:
payload == null ? null : widget.actionsBuilder(payload.header),
),
body: RefreshIndicator(
onRefresh: widget.onRefresh,
child: _buildBody(),
child: Column(children: children),
),
);
}

View File

@ -11,16 +11,16 @@ class RefreshScaffold<T> extends StatefulWidget {
final Widget title;
final Widget Function(T payload) bodyBuilder;
final Future<T> Function() onRefresh;
final Widget trailing;
final List<Widget> actions;
final Widget Function(T payload) trailingBuilder;
final List<Widget> Function(T payload) actionsBuilder;
final PreferredSizeWidget bottom;
RefreshScaffold({
@required this.title,
@required this.bodyBuilder,
@required this.onRefresh,
this.trailing,
this.actions,
this.trailingBuilder,
this.actionsBuilder,
this.bottom,
});
@ -73,7 +73,9 @@ class _RefreshScaffoldState<T> extends State<RefreshScaffold<T>> {
case ThemeMap.cupertino:
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: widget.title, trailing: widget.trailing),
middle: widget.title,
trailing: widget.trailingBuilder(payload),
),
child: SafeArea(
child: CustomScrollView(
slivers: <Widget>[
@ -87,7 +89,7 @@ class _RefreshScaffoldState<T> extends State<RefreshScaffold<T>> {
return Scaffold(
appBar: AppBar(
title: widget.title,
actions: widget.actions,
actions: widget.actionsBuilder(payload),
bottom: widget.bottom,
),
body: RefreshIndicator(

View File

@ -1,10 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:share/share.dart';
import 'package:url_launcher/url_launcher.dart';
import '../utils/utils.dart';
import '../scaffolds/long_list.dart';
import '../widgets/timeline_item.dart';
import '../widgets/comment_item.dart';
import '../providers/settings.dart';
import '../widgets/link.dart';
class IssueScreen extends StatefulWidget {
final int number;
@ -130,10 +133,68 @@ class _IssueScreenState extends State<IssueScreen> {
);
}
Future<void> _openActions(payload) async {
if (payload == null) return;
var _actionMap = {
2: 'Share',
3: 'Open in Browser',
};
var value = await showCupertinoModalPopup<int>(
context: context,
builder: (BuildContext context) {
return CupertinoActionSheet(
title: Text('Issue Actions'),
actions: _actionMap.entries.map((entry) {
return CupertinoActionSheetAction(
child: Text(entry.value),
onPressed: () {
Navigator.pop(context, entry.key);
},
);
}).toList(),
cancelButton: CupertinoActionSheetAction(
child: const Text('Cancel'),
isDefaultAction: true,
onPressed: () {
Navigator.pop(context);
},
),
);
},
);
switch (value) {
case 2:
Share.share(payload['url']);
break;
case 3:
launch(payload['url']);
break;
default:
}
}
@override
Widget build(BuildContext context) {
return LongListScaffold(
title: Text(_fullName + ' #' + widget.number.toString()),
trailingBuilder: (payload) {
return Link(
child: Icon(Icons.more_vert, size: 24),
material: false,
beforeRedirect: () => _openActions(payload),
);
},
actionsBuilder: (payload) {
return [
Link(
iconButton: Icon(Icons.more_vert),
beforeRedirect: () => _openActions(payload),
),
];
},
headerBuilder: (payload) {
return Column(children: <Widget>[
Container(

View File

@ -1,10 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:share/share.dart';
import 'package:url_launcher/url_launcher.dart';
import '../providers/settings.dart';
import '../utils/utils.dart';
import '../scaffolds/long_list.dart';
import '../widgets/timeline_item.dart';
import '../widgets/comment_item.dart';
import '../widgets/link.dart';
class PullRequestScreen extends StatefulWidget {
final int number;
@ -181,10 +184,68 @@ class _PullRequestScreenState extends State<PullRequestScreen> {
get _fullName => widget.owner + '/' + widget.name;
Future<void> _openActions(payload) async {
if (payload == null) return;
var _actionMap = {
2: 'Share',
3: 'Open in Browser',
};
var value = await showCupertinoModalPopup<int>(
context: context,
builder: (BuildContext context) {
return CupertinoActionSheet(
title: Text('Issue Actions'),
actions: _actionMap.entries.map((entry) {
return CupertinoActionSheetAction(
child: Text(entry.value),
onPressed: () {
Navigator.pop(context, entry.key);
},
);
}).toList(),
cancelButton: CupertinoActionSheetAction(
child: const Text('Cancel'),
isDefaultAction: true,
onPressed: () {
Navigator.pop(context);
},
),
);
},
);
switch (value) {
case 2:
Share.share(payload['url']);
break;
case 3:
launch(payload['url']);
break;
default:
}
}
@override
Widget build(BuildContext context) {
return LongListScaffold(
title: Text(_fullName + ' #' + widget.number.toString()),
trailingBuilder: (payload) {
return Link(
child: Icon(Icons.more_vert, size: 24),
material: false,
beforeRedirect: () => _openActions(payload),
);
},
actionsBuilder: (payload) {
return [
Link(
iconButton: Icon(Icons.more_vert),
beforeRedirect: () => _openActions(payload),
),
];
},
headerBuilder: (payload) {
return Column(children: <Widget>[
Container(

View File

@ -2,11 +2,14 @@ import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:share/share.dart';
import 'package:url_launcher/url_launcher.dart';
import '../providers/settings.dart';
import '../scaffolds/refresh.dart';
import '../widgets/repo_item.dart';
import '../widgets/entry_item.dart';
import '../screens/issues.dart';
import '../widgets/link.dart';
class RepoScreen extends StatefulWidget {
final String owner;
@ -19,12 +22,14 @@ class RepoScreen extends StatefulWidget {
}
class _RepoScreenState extends State<RepoScreen> {
get owner => widget.owner;
get name => widget.name;
Future queryRepo(BuildContext context) async {
var owner = widget.owner;
var name = widget.name;
var data = await SettingsProvider.of(context).query('''
{
repository(owner: "$owner", name: "$name") {
id
owner {
login
}
@ -52,6 +57,8 @@ class _RepoScreenState extends State<RepoScreen> {
defaultBranchRef {
name
}
viewerHasStarred
viewerSubscription
}
}
@ -69,10 +76,84 @@ class _RepoScreenState extends State<RepoScreen> {
return str;
}
Future<void> _openActions(data) async {
if (data == null) return;
var payload = data[0];
var _actionMap = {
0: payload['viewerHasStarred'] ? 'Unstar' : 'Star',
// 1: 'Watch', TODO:
2: 'Share',
3: 'Open in Browser',
};
var value = await showCupertinoModalPopup<int>(
context: context,
builder: (BuildContext context) {
return CupertinoActionSheet(
title: Text('Repository Actions'),
actions: _actionMap.entries.map((entry) {
return CupertinoActionSheetAction(
child: Text(entry.value),
onPressed: () {
Navigator.pop(context, entry.key);
},
);
}).toList(),
cancelButton: CupertinoActionSheetAction(
child: const Text('Cancel'),
isDefaultAction: true,
onPressed: () {
Navigator.pop(context);
},
),
);
},
);
switch (value) {
case 0:
if (payload['viewerHasStarred']) {
await SettingsProvider.of(context)
.deleteWithCredentials('/user/starred/$owner/$name');
payload['viewerHasStarred'] = false;
} else {
SettingsProvider.of(context)
.putWithCredentials('/user/starred/$owner/$name');
payload['viewerHasStarred'] = true;
}
break;
// case 1:
// break;
case 2:
Share.share(payload['url']);
break;
case 3:
launch(payload['url']);
break;
default:
}
}
@override
Widget build(BuildContext context) {
return RefreshScaffold(
title: Text(widget.owner + '/' + widget.name),
trailingBuilder: (payload) {
return Link(
child: Icon(Icons.more_vert, size: 24),
material: false,
beforeRedirect: () => _openActions(payload),
);
},
actionsBuilder: (payload) {
return [
Link(
iconButton: Icon(Icons.more_vert),
beforeRedirect: () => _openActions(payload),
),
];
},
onRefresh: () => Future.wait([
queryRepo(context),
fetchReadme(context),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:share/share.dart';
import '../providers/settings.dart';
import '../scaffolds/refresh.dart';
import '../widgets/avatar.dart';
@ -54,6 +55,9 @@ class _UserScreenState extends State<UserScreen> {
$repoChunk
}
}
viewerCanFollow
viewerIsFollowing
url
}
}
''');
@ -83,6 +87,7 @@ class _UserScreenState extends State<UserScreen> {
String email = payload['email'] ?? '';
if (email.isNotEmpty) {
return Link(
material: false,
child: Row(children: <Widget>[
Icon(
Octicons.mail,
@ -127,24 +132,104 @@ class _UserScreenState extends State<UserScreen> {
return Container();
}
Future<void> _openActions(payload) async {
if (payload == null) return;
var _actionMap = {};
if (payload['viewerCanFollow']) {
_actionMap[0] = payload['viewerIsFollowing'] ? 'Unfollow' : 'Follow';
}
_actionMap[2] = 'Share';
_actionMap[3] = 'Open in Browser';
var value = await showCupertinoModalPopup<int>(
context: context,
builder: (BuildContext context) {
return CupertinoActionSheet(
title: Text('User Actions'),
actions: _actionMap.entries.map((entry) {
return CupertinoActionSheetAction(
child: Text(entry.value),
onPressed: () {
Navigator.pop(context, entry.key);
},
);
}).toList(),
cancelButton: CupertinoActionSheetAction(
child: const Text('Cancel'),
isDefaultAction: true,
onPressed: () {
Navigator.pop(context);
},
),
);
},
);
switch (value) {
case 0:
if (payload['viewerIsFollowing']) {
await SettingsProvider.of(context)
.deleteWithCredentials('/user/following/${widget.login}');
payload['viewerIsFollowing'] = false;
} else {
SettingsProvider.of(context)
.putWithCredentials('/user/following/${widget.login}');
payload['viewerIsFollowing'] = true;
}
break;
// case 1:
// break;
case 2:
Share.share(payload['url']);
break;
case 3:
launch(payload['url']);
break;
default:
}
}
@override
Widget build(BuildContext context) {
return RefreshScaffold(
onRefresh: () => queryUser(context),
title: Text(widget.login),
trailing: Link(
child: Icon(Icons.settings, size: 24),
screenBuilder: (_) => SettingsScreen(),
material: false,
fullscreenDialog: true,
),
actions: <Widget>[
Link(
iconButton: Icon(Icons.settings),
screenBuilder: (_) => SettingsScreen(),
fullscreenDialog: true,
),
],
trailingBuilder: (payload) {
if (widget.showSettings) {
return Link(
child: Icon(Icons.settings, size: 24),
screenBuilder: (_) => SettingsScreen(),
material: false,
fullscreenDialog: true,
);
} else {
return Link(
child: Icon(Icons.more_vert, size: 24),
material: false,
beforeRedirect: () => _openActions(payload),
);
}
},
actionsBuilder: (payload) {
if (widget.showSettings) {
return [
Link(
iconButton: Icon(Icons.settings),
screenBuilder: (_) => SettingsScreen(),
fullscreenDialog: true,
)
];
} else {
return [
Link(
iconButton: Icon(Icons.more_vert),
material: false,
beforeRedirect: () => _openActions(payload),
)
];
}
},
bodyBuilder: (payload) {
return Column(
children: <Widget>[

View File

@ -161,6 +161,7 @@ author {
avatarUrl
}
closed
url
''';
var graghqlChunk = '''