mirror of
https://github.com/git-touch/git-touch
synced 2025-02-12 09:30:38 +01:00
feat: notification mark as read
This commit is contained in:
parent
8e678a37d4
commit
c0629c5739
@ -52,22 +52,18 @@ class _HomeState extends State<Home> {
|
||||
|
||||
List<BottomNavigationBarItem> _buildNavigationItems() {
|
||||
return [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.inbox),
|
||||
title: Text('Inbox'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.rss_feed),
|
||||
title: Text('News'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.search),
|
||||
title: Text('Search'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: _buildNotificationIcon(context),
|
||||
title: Text('Notification'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.search),
|
||||
title: Text('Search'),
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.person),
|
||||
title: Text('Me'),
|
||||
@ -78,14 +74,12 @@ class _HomeState extends State<Home> {
|
||||
_buildScreen(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return InboxScreen();
|
||||
case 1:
|
||||
return NewsScreen();
|
||||
case 1:
|
||||
return NotificationScreen();
|
||||
case 2:
|
||||
return SearchScreen();
|
||||
case 3:
|
||||
return NotificationScreen();
|
||||
case 4:
|
||||
return ProfileScreen();
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ class _SettingsProviderState extends State<SettingsProvider> {
|
||||
if (Platform.isIOS) {
|
||||
layout = LayoutMap.cupertino;
|
||||
}
|
||||
layout = LayoutMap.material;
|
||||
// layout = LayoutMap.material;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1,8 +1,136 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import '../widgets/list_scaffold.dart';
|
||||
import '../widgets/notification_item.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../screens/issue.dart';
|
||||
import '../screens/pull_request.dart';
|
||||
import '../widgets/link.dart';
|
||||
|
||||
class NotificationPayload {
|
||||
String type;
|
||||
String owner;
|
||||
String name;
|
||||
int number;
|
||||
String title;
|
||||
String updateAt;
|
||||
bool unread;
|
||||
|
||||
NotificationPayload.fromJson(input) {
|
||||
type = input['subject']['type'];
|
||||
name = input['repository']['name'];
|
||||
owner = input['repository']['owner']['login'];
|
||||
|
||||
String url = input['subject']['url'];
|
||||
String numberStr = url.split('/').lastWhere((_) => true);
|
||||
number = int.parse(numberStr);
|
||||
|
||||
title = input['subject']['title'];
|
||||
updateAt = TimeAgo.formatFromString(input['updated_at']);
|
||||
unread = input['unread'];
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationItem extends StatelessWidget {
|
||||
const NotificationItem({
|
||||
Key key,
|
||||
@required this.payload,
|
||||
}) : super(key: key);
|
||||
|
||||
final NotificationPayload payload;
|
||||
|
||||
Widget _buildRoute() {
|
||||
switch (payload.type) {
|
||||
case 'Issue':
|
||||
return IssueScreen(payload.number, payload.owner, payload.name);
|
||||
case 'PullRequest':
|
||||
return PullRequestScreen(payload.number, payload.owner, payload.name);
|
||||
default:
|
||||
// throw new Exception('Unhandled notification type: $type');
|
||||
return Text('test');
|
||||
}
|
||||
}
|
||||
|
||||
IconData _buildIconData() {
|
||||
switch (payload.type) {
|
||||
case 'Issue':
|
||||
return Octicons.issue_opened;
|
||||
// color: Color.fromRGBO(0x28, 0xa7, 0x45, 1),
|
||||
case 'PullRequest':
|
||||
return Octicons.git_pull_request;
|
||||
// color: Color.fromRGBO(0x6f, 0x42, 0xc1, 1),
|
||||
default:
|
||||
return Octicons.person;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Link(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
CupertinoPageRoute(builder: (context) => _buildRoute()),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
// color: payload.unread ? Colors.white : Colors.black12,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(right: 8, top: 20),
|
||||
child: Icon(_buildIconData(), color: Colors.black45),
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
payload.owner +
|
||||
'/' +
|
||||
payload.name +
|
||||
' #' +
|
||||
payload.number.toString(),
|
||||
style: TextStyle(fontSize: 13, color: Colors.black54),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(top: 4)),
|
||||
Text(
|
||||
payload.title,
|
||||
style: TextStyle(fontSize: 15),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(top: 6)),
|
||||
Text(
|
||||
payload.updateAt,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
// fontWeight: FontWeight.w300,
|
||||
color: Colors.black54,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Column(
|
||||
children: <Widget>[
|
||||
Icon(Octicons.check, color: Colors.black45),
|
||||
Icon(Octicons.unmute, color: Colors.black45)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<NotificationPayload>> fetchNotifications(int page) async {
|
||||
List items =
|
||||
@ -10,6 +138,7 @@ Future<List<NotificationPayload>> fetchNotifications(int page) async {
|
||||
return items.map((item) => NotificationPayload.fromJson(item)).toList();
|
||||
}
|
||||
|
||||
/// [@deprecated]
|
||||
class InboxScreen extends StatefulWidget {
|
||||
@override
|
||||
_InboxScreenState createState() => _InboxScreenState();
|
||||
@ -27,17 +156,27 @@ class _InboxScreenState extends State<InboxScreen> {
|
||||
2: 'All',
|
||||
};
|
||||
|
||||
Future<void> _refresh() async {
|
||||
page = 1;
|
||||
var items = await fetchNotifications(page);
|
||||
setState(() {
|
||||
_items = items;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListScaffold(
|
||||
title: Text('Inbox'),
|
||||
onRefresh: () async {
|
||||
page = 1;
|
||||
var items = await fetchNotifications(page);
|
||||
setState(() {
|
||||
_items = items;
|
||||
});
|
||||
trailingIconData: Octicons.check,
|
||||
trailingOnTap: () async {
|
||||
bool answer = await showConfim(context, 'Mark all as read?');
|
||||
if (answer == true) {
|
||||
await putWithCredentials('/notifications');
|
||||
_refresh();
|
||||
}
|
||||
},
|
||||
onRefresh: _refresh,
|
||||
onLoadMore: () async {
|
||||
page = page + 1;
|
||||
var items = await fetchNotifications(page);
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../widgets/list_scaffold.dart';
|
||||
import '../widgets/timeline_item.dart';
|
||||
import '../widgets/comment_item.dart';
|
||||
|
||||
Future queryIssue(int id, String owner, String name) async {
|
||||
var data = await query('''
|
||||
@ -41,7 +42,25 @@ class _IssueScreenState extends State<IssueScreen> {
|
||||
Map<String, dynamic> payload;
|
||||
|
||||
Widget _buildHeader() {
|
||||
return Text('issue');
|
||||
return Column(children: <Widget>[
|
||||
Container(
|
||||
// padding: EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
payload['title'],
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
height: 1.2,
|
||||
),
|
||||
),
|
||||
CommentItem(payload),
|
||||
],
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
get _fullName => widget.owner + '/' + widget.name;
|
||||
|
@ -1,17 +1,22 @@
|
||||
import 'package:flutter/material.dart' hide Notification;
|
||||
import 'package:flutter/cupertino.dart' hide Notification;
|
||||
import '../providers/settings.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import '../widgets/refresh_scaffold.dart';
|
||||
import '../providers/notification.dart';
|
||||
import '../widgets/notification_item.dart';
|
||||
import '../widgets/loading.dart';
|
||||
import '../widgets/list_group.dart';
|
||||
import '../widgets/link.dart';
|
||||
import '../utils/utils.dart';
|
||||
|
||||
class NotificationGroup {
|
||||
String fullName;
|
||||
List<Notification> items = [];
|
||||
Future<List<NotificationPayload>> fetchNotifications([int page = 1]) async {
|
||||
List items = await getWithCredentials('/notifications?page=$page&all=true');
|
||||
return items.map((item) => NotificationPayload.fromJson(item)).toList();
|
||||
}
|
||||
|
||||
NotificationGroup(this.fullName);
|
||||
class NotificationGroup {
|
||||
String repo;
|
||||
List<NotificationPayload> items = [];
|
||||
|
||||
NotificationGroup(this.repo);
|
||||
}
|
||||
|
||||
class NotificationScreen extends StatefulWidget {
|
||||
@ -22,108 +27,98 @@ class NotificationScreen extends StatefulWidget {
|
||||
class NotificationScreenState extends State<NotificationScreen> {
|
||||
int active = 0;
|
||||
bool loading = false;
|
||||
List<NotificationGroup> groups = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
Future.delayed(Duration(seconds: 0)).then((_) {
|
||||
_onSwitchTab(context, 0);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildGroupItem(BuildContext context, int index) {
|
||||
if (loading) {
|
||||
return Loading(more: false);
|
||||
}
|
||||
|
||||
var group = groups[index];
|
||||
Map<String, NotificationGroup> groupMap = {};
|
||||
|
||||
Widget _buildGroupItem(String key, NotificationGroup group) {
|
||||
var repo = group.repo;
|
||||
return ListGroup(
|
||||
title: Text(
|
||||
group.fullName,
|
||||
style: TextStyle(color: Colors.black, fontSize: 15),
|
||||
),
|
||||
items: group.items,
|
||||
itemBuilder: (item) => NotificationItem(payload: item),
|
||||
);
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
repo,
|
||||
style: TextStyle(color: Colors.black, fontSize: 15),
|
||||
),
|
||||
Link(
|
||||
onTap: () async {
|
||||
await putWithCredentials('/repos/$repo/notifications');
|
||||
await _refresh();
|
||||
},
|
||||
child: Icon(
|
||||
Octicons.check,
|
||||
color: Colors.black45,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
items: group.items,
|
||||
itemBuilder: (item, index) {
|
||||
return NotificationItem(
|
||||
payload: item,
|
||||
markAsRead: () {
|
||||
setState(() {
|
||||
groupMap[key].items[index].unread = false;
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
void _onSwitchTab(BuildContext context, int index) async {
|
||||
setState(() {
|
||||
active = index;
|
||||
loading = true;
|
||||
});
|
||||
Future<void> _onSwitchTab(BuildContext context, int index) async {
|
||||
// setState(() {
|
||||
// active = index;
|
||||
// loading = true;
|
||||
// });
|
||||
|
||||
var ns = await ghClient.activity
|
||||
.listNotifications(all: index == 2, participating: index == 1)
|
||||
.toList();
|
||||
var ns = await fetchNotifications();
|
||||
|
||||
NotificationProvider.of(context).setCount(ns.length);
|
||||
// NotificationProvider.of(context).setCount(ns.length);
|
||||
|
||||
Map<String, NotificationGroup> groupMap = {};
|
||||
Map<String, NotificationGroup> _groupMap = {};
|
||||
ns.forEach((item) {
|
||||
String repo = item.repository.fullName;
|
||||
if (groupMap[repo] == null) {
|
||||
groupMap[repo] = NotificationGroup(repo);
|
||||
String repo = item.owner + '/' + item.name;
|
||||
if (_groupMap[repo] == null) {
|
||||
_groupMap[repo] = NotificationGroup(repo);
|
||||
}
|
||||
|
||||
groupMap[repo].items.add(item);
|
||||
_groupMap[repo].items.add(item);
|
||||
});
|
||||
|
||||
setState(() {
|
||||
groups = groupMap.values.toList();
|
||||
loading = false;
|
||||
groupMap = _groupMap;
|
||||
// loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: filter
|
||||
// CupertinoSegmentedControl(
|
||||
// groupValue: active,
|
||||
// onValueChanged: (index) => _onSwitchTab(context, index),
|
||||
// children: {
|
||||
// 0: Text('Unread'),
|
||||
// 1: Text('Paticipating'),
|
||||
// 2: Text('All')
|
||||
// },
|
||||
// )
|
||||
|
||||
Future<void> _refresh() async {
|
||||
print('onrefresh');
|
||||
await _onSwitchTab(context, active);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(context) {
|
||||
// NotificationBloc bloc = NotificationProvider.of(context);
|
||||
TextStyle _textStyle = DefaultTextStyle.of(context).style;
|
||||
return RefreshScaffold(
|
||||
title: Text('Notifications'),
|
||||
onRefresh: _refresh,
|
||||
bodyBuilder: () {
|
||||
var children = groupMap.entries
|
||||
.map((entry) => _buildGroupItem(entry.key, entry.value))
|
||||
.toList();
|
||||
|
||||
switch (SettingsProvider.of(context).layout) {
|
||||
case LayoutMap.cupertino:
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: SizedBox.expand(
|
||||
child: DefaultTextStyle(
|
||||
style: _textStyle,
|
||||
child: CupertinoSegmentedControl(
|
||||
groupValue: active,
|
||||
onValueChanged: (index) => _onSwitchTab(context, index),
|
||||
children: {
|
||||
0: Text('Unread'),
|
||||
1: Text('Paticipating'),
|
||||
2: Text('All')
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(slivers: [
|
||||
CupertinoSliverRefreshControl(),
|
||||
SliverSafeArea(
|
||||
top: false,
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
_buildGroupItem,
|
||||
childCount: groups.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('Notification')),
|
||||
body: ListView.builder(
|
||||
itemCount: groups.length,
|
||||
itemBuilder: _buildGroupItem,
|
||||
),
|
||||
);
|
||||
}
|
||||
return Column(children: children);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -98,11 +98,13 @@ class _PullRequestScreenState extends State<PullRequestScreen> {
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Icon(iconData, color: Colors.white, size: 15),
|
||||
Text(text,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
)),
|
||||
Text(
|
||||
text,
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -91,7 +91,7 @@ class _UserScreenState extends State<UserScreen> {
|
||||
return ListGroup(
|
||||
title: Text(title),
|
||||
items: items,
|
||||
itemBuilder: (item) => RepoItem(item),
|
||||
itemBuilder: (item, _) => RepoItem(item),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,23 @@ Future<dynamic> postWithCredentials(String url, String body,
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<dynamic> putWithCredentials(String url,
|
||||
{String contentType, String body}) async {
|
||||
var headers = {HttpHeaders.authorizationHeader: 'token $token'};
|
||||
if (contentType != null) {
|
||||
headers[HttpHeaders.contentTypeHeader] = contentType;
|
||||
}
|
||||
final res = await http.put(prefix + url, headers: headers, body: body ?? {});
|
||||
final data = json.decode(res.body);
|
||||
return data;
|
||||
}
|
||||
|
||||
Future<dynamic> patchWithCredentials(String url) async {
|
||||
var headers = {HttpHeaders.authorizationHeader: 'token $token'};
|
||||
await http.patch(prefix + url, headers: headers);
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<dynamic> query(String query) async {
|
||||
final res =
|
||||
await postWithCredentials('/graphql', json.encode({'query': query}));
|
||||
|
@ -20,6 +20,61 @@ class Option<T> {
|
||||
Option({this.value, this.widget});
|
||||
}
|
||||
|
||||
Future<bool> showConfim(BuildContext context, String text) {
|
||||
switch (SettingsProvider.of(context).layout) {
|
||||
case LayoutMap.cupertino:
|
||||
return showCupertinoDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return CupertinoAlertDialog(
|
||||
title: Text(text),
|
||||
actions: <Widget>[
|
||||
CupertinoDialogAction(
|
||||
child: const Text('cancel'),
|
||||
isDefaultAction: true,
|
||||
onPressed: () {
|
||||
Navigator.pop(context, false);
|
||||
},
|
||||
),
|
||||
CupertinoDialogAction(
|
||||
child: const Text('OK'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
default:
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
content: Text(
|
||||
text,
|
||||
// style: dialogTextStyle
|
||||
),
|
||||
actions: <Widget>[
|
||||
FlatButton(
|
||||
child: const Text('CANCEL'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, false);
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: const Text('OK'),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<T> showOptions<T>(BuildContext context, List<Option<T>> options) {
|
||||
var builder = (BuildContext context) {
|
||||
return CupertinoAlertDialog(
|
||||
|
@ -1,11 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import '../screens/screens.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../widgets/widgets.dart';
|
||||
import '../widgets/link.dart';
|
||||
|
||||
/// Events types:
|
||||
///
|
||||
|
@ -4,14 +4,15 @@ import '../providers/settings.dart';
|
||||
class Link extends StatelessWidget {
|
||||
final Widget child;
|
||||
final GestureTapCallback onTap;
|
||||
final Color bgColor;
|
||||
|
||||
Link({@required this.child, @required this.onTap});
|
||||
Link({@required this.child, @required this.onTap, this.bgColor});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Material(
|
||||
child: Ink(
|
||||
color: Colors.white,
|
||||
color: bgColor ?? Colors.white,
|
||||
child: InkWell(
|
||||
splashColor:
|
||||
SettingsProvider.of(context).layout == LayoutMap.cupertino
|
||||
|
@ -3,16 +3,16 @@ import 'package:flutter/material.dart';
|
||||
class ListGroup<T> extends StatelessWidget {
|
||||
final Widget title;
|
||||
final List<T> items;
|
||||
final Widget Function(T item) itemBuilder;
|
||||
final Widget Function(T item, int index) itemBuilder;
|
||||
|
||||
ListGroup({this.title, this.items, this.itemBuilder});
|
||||
|
||||
Widget _buildItem(T item) {
|
||||
Widget _buildItem(MapEntry<int, T> entry) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(bottom: BorderSide(color: Colors.black12)),
|
||||
),
|
||||
child: itemBuilder(item),
|
||||
child: itemBuilder(entry.value, entry.key),
|
||||
);
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ class ListGroup<T> extends StatelessWidget {
|
||||
color: Color(0x10000000),
|
||||
child: title,
|
||||
),
|
||||
Column(children: items.map(_buildItem).toList())
|
||||
Column(children: items.asMap().entries.map(_buildItem).toList())
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -2,12 +2,15 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import '../providers/settings.dart';
|
||||
import '../widgets/link.dart';
|
||||
import 'loading.dart';
|
||||
|
||||
typedef RefreshCallback = Future<void> Function();
|
||||
|
||||
class ListScaffold extends StatefulWidget {
|
||||
final Widget title;
|
||||
final IconData trailingIconData;
|
||||
final Function trailingOnTap;
|
||||
final Widget header;
|
||||
final int itemCount;
|
||||
final IndexedWidgetBuilder itemBuilder;
|
||||
@ -16,6 +19,8 @@ class ListScaffold extends StatefulWidget {
|
||||
|
||||
ListScaffold({
|
||||
@required this.title,
|
||||
this.trailingIconData,
|
||||
this.trailingOnTap,
|
||||
this.header,
|
||||
@required this.itemCount,
|
||||
@required this.itemBuilder,
|
||||
@ -47,7 +52,7 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
}
|
||||
|
||||
Future<void> _refresh() async {
|
||||
print('refresh');
|
||||
print('list scaffold refresh');
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
@ -63,7 +68,7 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
}
|
||||
|
||||
Future<void> _loadMore() async {
|
||||
print('more');
|
||||
print('list scaffold load more');
|
||||
setState(() {
|
||||
loadingMore = true;
|
||||
});
|
||||
@ -130,7 +135,18 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
slivers.add(_buildSliver(context));
|
||||
|
||||
return CupertinoPageScaffold(
|
||||
navigationBar: CupertinoNavigationBar(middle: widget.title),
|
||||
navigationBar: CupertinoNavigationBar(
|
||||
middle: widget.title,
|
||||
trailing: Link(
|
||||
child: Icon(
|
||||
widget.trailingIconData,
|
||||
size: 24,
|
||||
color: Colors.blueAccent,
|
||||
),
|
||||
onTap: widget.trailingOnTap,
|
||||
bgColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(
|
||||
controller: _controller,
|
||||
@ -140,7 +156,17 @@ class _ListScaffoldState extends State<ListScaffold> {
|
||||
);
|
||||
default:
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: widget.title),
|
||||
appBar: AppBar(
|
||||
title: widget.title,
|
||||
actions: widget.trailingIconData == null
|
||||
? []
|
||||
: <Widget>[
|
||||
IconButton(
|
||||
icon: Icon(widget.trailingIconData),
|
||||
onPressed: widget.trailingOnTap,
|
||||
)
|
||||
],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: widget.onRefresh,
|
||||
child: _buildBody(context),
|
||||
|
@ -1,12 +1,12 @@
|
||||
import 'dart:core';
|
||||
import 'package:flutter/material.dart' hide Notification;
|
||||
import 'package:flutter/cupertino.dart' hide Notification;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import '../utils/utils.dart';
|
||||
import '../screens/issue.dart';
|
||||
import '../screens/pull_request.dart';
|
||||
import 'link.dart';
|
||||
|
||||
class NotificationPayload {
|
||||
String id;
|
||||
String type;
|
||||
String owner;
|
||||
String name;
|
||||
@ -16,6 +16,7 @@ class NotificationPayload {
|
||||
bool unread;
|
||||
|
||||
NotificationPayload.fromJson(input) {
|
||||
id = input['id'];
|
||||
type = input['subject']['type'];
|
||||
name = input['repository']['name'];
|
||||
owner = input['repository']['owner']['login'];
|
||||
@ -30,13 +31,23 @@ class NotificationPayload {
|
||||
}
|
||||
}
|
||||
|
||||
class NotificationItem extends StatelessWidget {
|
||||
const NotificationItem({
|
||||
class NotificationItem extends StatefulWidget {
|
||||
final NotificationPayload payload;
|
||||
final Function markAsRead;
|
||||
|
||||
NotificationItem({
|
||||
Key key,
|
||||
@required this.payload,
|
||||
@required this.markAsRead,
|
||||
}) : super(key: key);
|
||||
|
||||
final NotificationPayload payload;
|
||||
@override
|
||||
_NotificationItemState createState() => _NotificationItemState();
|
||||
}
|
||||
|
||||
class _NotificationItemState extends State<NotificationItem> {
|
||||
NotificationPayload get payload => widget.payload;
|
||||
bool loading = false;
|
||||
|
||||
Widget _buildRoute() {
|
||||
switch (payload.type) {
|
||||
@ -63,6 +74,14 @@ class NotificationItem extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildCheckIcon() {
|
||||
return Icon(
|
||||
payload.unread ? Octicons.check : Octicons.primitive_dot,
|
||||
color: loading ? Colors.black12 : Colors.black45,
|
||||
size: 20,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Link(
|
||||
@ -71,59 +90,43 @@ class NotificationItem extends StatelessWidget {
|
||||
CupertinoPageRoute(builder: (context) => _buildRoute()),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
color: payload.unread ? Colors.white : Colors.black12,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(right: 8, top: 20),
|
||||
child: Icon(_buildIconData(), color: Colors.black45),
|
||||
child: Opacity(
|
||||
opacity: payload.unread ? 1 : 0.5,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: Icon(_buildIconData(), color: Colors.black45, size: 20),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(
|
||||
payload.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Expanded(
|
||||
child: Container(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
payload.owner +
|
||||
'/' +
|
||||
payload.name +
|
||||
' #' +
|
||||
payload.number.toString(),
|
||||
style: TextStyle(fontSize: 13, color: Colors.black54),
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(top: 4)),
|
||||
Text(
|
||||
payload.title,
|
||||
style: TextStyle(fontSize: 15),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Padding(padding: EdgeInsets.only(top: 6)),
|
||||
Text(
|
||||
payload.updateAt,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
// fontWeight: FontWeight.w300,
|
||||
color: Colors.black54,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Icon(Octicons.check, color: Colors.black45),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Link(
|
||||
child: _buildCheckIcon(),
|
||||
onTap: () async {
|
||||
if (payload.unread && !loading) {
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
try {
|
||||
await patchWithCredentials(
|
||||
'/notifications/threads/' + payload.id);
|
||||
widget.markAsRead();
|
||||
} finally {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -36,15 +36,15 @@ class _RefreshScaffoldState extends State<RefreshScaffold> {
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
try {
|
||||
await widget.onRefresh();
|
||||
} catch (err) {
|
||||
print(err);
|
||||
} finally {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
// try {
|
||||
await widget.onRefresh();
|
||||
// } catch (err) {
|
||||
// print(err);
|
||||
// } finally {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
// }
|
||||
}
|
||||
|
||||
Widget _buildBody(BuildContext context) {
|
||||
@ -64,7 +64,7 @@ class _RefreshScaffoldState extends State<RefreshScaffold> {
|
||||
child: SafeArea(
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
CupertinoSliverRefreshControl(onRefresh: widget.onRefresh),
|
||||
CupertinoSliverRefreshControl(onRefresh: _refresh),
|
||||
SliverToBoxAdapter(child: _buildBody(context))
|
||||
],
|
||||
),
|
||||
@ -74,8 +74,8 @@ class _RefreshScaffoldState extends State<RefreshScaffold> {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: widget.title),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: widget.onRefresh,
|
||||
child: _buildBody(context),
|
||||
onRefresh: _refresh,
|
||||
child: SingleChildScrollView(child: _buildBody(context)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user