mirror of
https://github.com/git-touch/git-touch
synced 2025-01-31 08:04:51 +01:00
feat: add actions to repo, user and issue screen
This commit is contained in:
parent
925472f151
commit
d9bec759e3
@ -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() {
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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),
|
||||
|
@ -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>[
|
||||
|
@ -161,6 +161,7 @@ author {
|
||||
avatarUrl
|
||||
}
|
||||
closed
|
||||
url
|
||||
''';
|
||||
|
||||
var graghqlChunk = '''
|
||||
|
Loading…
x
Reference in New Issue
Block a user