feat: issue screen style

This commit is contained in:
Rongjian Zhang
2019-09-07 17:48:59 +08:00
parent 321ab0a60a
commit 1108346163
6 changed files with 156 additions and 145 deletions

View File

@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:git_touch/models/theme.dart'; import 'package:git_touch/models/theme.dart';
import 'package:git_touch/utils/utils.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../widgets/loading.dart'; import '../widgets/loading.dart';
import '../widgets/link.dart'; import '../widgets/link.dart';
@@ -102,11 +103,7 @@ class _LongListScaffoldState<T, K> extends State<LongListScaffold<T, K>> {
Widget _buildItem(BuildContext context, int index) { Widget _buildItem(BuildContext context, int index) {
if (index % 2 == 1) { if (index % 2 == 1) {
return Container( return BorderView();
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: Colors.black12)),
),
);
} }
int realIndex = index ~/ 2; int realIndex = index ~/ 2;

View File

@@ -360,40 +360,41 @@ mutation {
); );
}, },
headerBuilder: (payload) { headerBuilder: (payload) {
return Column(children: <Widget>[ return Column(
Container( crossAxisAlignment: CrossAxisAlignment.stretch,
padding: EdgeInsets.all(10), children: <Widget>[
decoration: BoxDecoration( Container(
border: Border(bottom: BorderSide(color: Colors.black12)), padding: EdgeInsets.all(10),
), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
children: <Widget>[ Row(
Row( mainAxisAlignment: MainAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[
children: <Widget>[ Expanded(
Expanded( child: Text(
child: Text( payload['title'],
payload['title'], style: TextStyle(
style: TextStyle( fontSize: 20,
fontSize: 20, fontWeight: FontWeight.bold,
fontWeight: FontWeight.bold, ),
), ),
), ),
), Padding(padding: EdgeInsets.only(right: 8)),
Padding(padding: EdgeInsets.only(right: 8)), StateLabel(_getLabelStatus(payload))
StateLabel(_getLabelStatus(payload)) ],
], ),
), Padding(padding: EdgeInsets.only(bottom: 16)),
Padding(padding: EdgeInsets.only(bottom: 16)), CommentItem(
CommentItem( payload,
payload, onReaction: _handleReaction(payload),
onReaction: _handleReaction(payload), ),
), ],
], ),
), ),
) BorderView(),
]); ],
);
}, },
itemBuilder: (itemPayload) => itemBuilder: (itemPayload) =>
TimelineItem(itemPayload, onReaction: _handleReaction(itemPayload)), TimelineItem(itemPayload, onReaction: _handleReaction(itemPayload)),

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:git_touch/screens/user.dart';
import 'package:primer/primer.dart'; import 'package:primer/primer.dart';
import '../providers/settings.dart'; import '../providers/settings.dart';
import '../screens/repo.dart'; import '../screens/repo.dart';
@@ -41,6 +42,7 @@ TextSpan createLinkSpan(BuildContext context, String text, Function handle) {
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () { ..onTap = () {
Navigator.of(context).push( Navigator.of(context).push(
// FIXME: Material route
CupertinoPageRoute( CupertinoPageRoute(
builder: (context) { builder: (context) {
return handle(); return handle();
@@ -51,6 +53,10 @@ TextSpan createLinkSpan(BuildContext context, String text, Function handle) {
); );
} }
TextSpan createUserSpan(BuildContext context, String login) {
return createLinkSpan(context, login, () => UserScreen(login));
}
TextSpan createRepoLinkSpan(BuildContext context, String owner, String name) { TextSpan createRepoLinkSpan(BuildContext context, String owner, String name) {
return createLinkSpan(context, '$owner/$name', () => RepoScreen(owner, name)); return createLinkSpan(context, '$owner/$name', () => RepoScreen(owner, name));
} }

View File

@@ -48,13 +48,13 @@ class CommentItem extends StatelessWidget {
size: 16, size: 16,
login: payload['author']['login'], login: payload['author']['login'],
), ),
Padding(padding: EdgeInsets.only(left: 6)), SizedBox(width: 8),
Expanded( Expanded(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
UserName(payload['author']['login']), UserName(payload['author']['login']),
Padding(padding: EdgeInsets.only(bottom: 2)), SizedBox(height: 2),
Text( Text(
timeago.format(DateTime.parse(payload['createdAt'])), timeago.format(DateTime.parse(payload['createdAt'])),
style: TextStyle(color: Colors.black54, fontSize: 13), style: TextStyle(color: Colors.black54, fontSize: 13),
@@ -63,72 +63,72 @@ class CommentItem extends StatelessWidget {
), ),
), ),
]), ]),
Padding( SizedBox(height: 12),
padding: const EdgeInsets.only(top: 12), MarkdownBody(
child: MarkdownBody( data: payload['body'] as String,
data: payload['body'], // styleSheet: MarkdownStyleSheet(code: TextStyle(fontSize: 14)),
// styleSheet: MarkdownStyleSheet(code: TextStyle(fontSize: 14)),
),
), ),
SizedBox(height: 12),
Wrap( Wrap(
children: emojiMap.entries crossAxisAlignment: WrapCrossAlignment.center,
.where((entry) => payload[entry.key]['totalCount'] as int != 0) children: [
.map<Widget>((entry) { ...emojiMap.entries
var emojiKey = entry.key; .where((entry) => payload[entry.key]['totalCount'] as int != 0)
var emoji = entry.value; .map<Widget>((entry) {
var count = payload[entry.key]['totalCount'] as int; var emojiKey = entry.key;
var emoji = entry.value;
var count = payload[entry.key]['totalCount'] as int;
return Link( return Link(
onTap: () { onTap: () {
onReaction(emojiKey, _hasReacted(emojiKey)); onReaction(emojiKey, _hasReacted(emojiKey));
}, },
child: Container( child: Container(
padding: EdgeInsets.all(6), padding: EdgeInsets.all(4),
decoration: _getDecorationByKey(emojiKey), decoration: _getDecorationByKey(emojiKey),
child: RichText( child: Wrap(
text: TextSpan( crossAxisAlignment: WrapCrossAlignment.center,
style: TextStyle(fontSize: 16), children: <Widget>[
children: [ Text(emoji, style: TextStyle(fontSize: 18)),
TextSpan(text: emoji), SizedBox(width: 4),
TextSpan(text: ' '), Text(count.toString(),
TextSpan( style: TextStyle(
text: count.toString(), color: PrimerColors.blue500, fontSize: 14))
style: TextStyle(color: PrimerColors.blue500),
),
], ],
), ),
), ),
), );
); }),
}).toList() Link(
..add( onTap: () async {
Link( var result =
onTap: () async { await Provider.of<ThemeModel>(context).showDialogOptions(
var result = await Provider.of<ThemeModel>(context) context,
.showDialogOptions( emojiMap.entries.map((entry) {
context, var emojiKey = entry.key;
emojiMap.entries.map((entry) { return DialogOption(
var emojiKey = entry.key; value: emojiKey,
return DialogOption( widget: Container(
value: emojiKey, decoration: _getDecorationByKey(emojiKey),
widget: Container( child: Text(emojiKey + ' ' + entry.value),
decoration: _getDecorationByKey(emojiKey), ),
child: Text(emojiKey + ' ' + entry.value), );
), }).toList());
); onReaction(result, _hasReacted(result));
}).toList()); },
onReaction(result, _hasReacted(result)); child: Container(
}, padding: EdgeInsets.all(4),
child: Container( child: Wrap(
padding: EdgeInsets.all(12), crossAxisAlignment: WrapCrossAlignment.center,
child: Icon( children: <Widget>[
Octicons.smiley, Text('+', style: TextStyle(color: PrimerColors.blue500)),
color: PrimerColors.blue500, Icon(Octicons.smiley,
size: 16, color: PrimerColors.blue500, size: 18),
), ],
),
),
), ),
),
),
],
), ),
], ],
); );

View File

@@ -6,6 +6,43 @@ import '../utils/utils.dart';
import 'comment_item.dart'; import 'comment_item.dart';
import 'user_name.dart'; import 'user_name.dart';
class TimelineEventItem extends StatelessWidget {
final String actor;
final IconData iconData;
final Color iconColor;
final TextSpan textSpan;
final item;
TimelineEventItem({
this.actor,
this.iconData = Octicons.octoface,
this.iconColor = PrimerColors.gray400,
this.textSpan,
this.item,
});
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
SizedBox(width: 6),
Icon(iconData, color: iconColor, size: 20),
SizedBox(width: 12),
Expanded(
child: RichText(
text: TextSpan(style: TextStyle(color: Colors.black), children: [
// TODO: actor is null
createUserSpan(context, actor),
textSpan,
// TextSpan(text: ' ' + TimeAgo.formatFromString(item['createdAt']))
]),
),
),
],
);
}
}
class TimelineItem extends StatelessWidget { class TimelineItem extends StatelessWidget {
final Map<String, dynamic> payload; final Map<String, dynamic> payload;
final Function(String emojiKey, bool isRemove) onReaction; final Function(String emojiKey, bool isRemove) onReaction;
@@ -23,32 +60,6 @@ class TimelineItem extends StatelessWidget {
} }
} }
Widget _buildItem({
String actor,
IconData iconData = Octicons.octoface,
Color iconColor = PrimerColors.gray400,
TextSpan textSpan,
item,
}) {
return Row(
children: <Widget>[
Padding(padding: EdgeInsets.only(left: 6)),
Icon(iconData, color: iconColor, size: 20),
Padding(padding: EdgeInsets.only(left: 12)),
Expanded(
child: RichText(
text: TextSpan(style: TextStyle(color: Colors.black), children: [
// TODO: actor is null
createUserSpan(actor),
textSpan,
// TextSpan(text: ' ' + TimeAgo.formatFromString(item['createdAt']))
]),
),
),
],
);
}
TextSpan _buildLabel(item) { TextSpan _buildLabel(item) {
var color = convertColor(item['label']['color']); var color = convertColor(item['label']['color']);
var grayscale = color.red * 0.3 + color.green * 0.59 + color.blue * 0.11; var grayscale = color.red * 0.3 + color.green * 0.59 + color.blue * 0.11;
@@ -73,7 +84,7 @@ class TimelineItem extends StatelessWidget {
Widget _buildByType(BuildContext context) { Widget _buildByType(BuildContext context) {
String type = payload['__typename']; String type = payload['__typename'];
var defaultItem = _buildItem( var defaultItem = TimelineEventItem(
actor: '', actor: '',
iconData: Octicons.octoface, iconData: Octicons.octoface,
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
@@ -85,7 +96,7 @@ class TimelineItem extends StatelessWidget {
switch (type) { switch (type) {
// common types // common types
case 'Commit': case 'Commit':
return _buildItem( return TimelineEventItem(
actor: payload['author']['user'] == null actor: payload['author']['user'] == null
? null ? null
: payload['author']['user']['login'], : payload['author']['user']['login'],
@@ -99,7 +110,7 @@ class TimelineItem extends StatelessWidget {
case 'IssueComment': case 'IssueComment':
return CommentItem(payload, onReaction: onReaction); return CommentItem(payload, onReaction: onReaction);
case 'CrossReferencedEvent': case 'CrossReferencedEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.primitive_dot, iconData: Octicons.primitive_dot,
iconColor: Palette.green, iconColor: Palette.green,
@@ -109,7 +120,7 @@ class TimelineItem extends StatelessWidget {
item: payload, item: payload,
); );
case 'ClosedEvent': case 'ClosedEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.circle_slash, iconData: Octicons.circle_slash,
iconColor: PrimerColors.red600, iconColor: PrimerColors.red600,
@@ -118,7 +129,7 @@ class TimelineItem extends StatelessWidget {
); );
case 'ReopenedEvent': case 'ReopenedEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.primitive_dot, iconData: Octicons.primitive_dot,
iconColor: Palette.green, iconColor: Palette.green,
@@ -134,7 +145,7 @@ class TimelineItem extends StatelessWidget {
return Container(); return Container();
} }
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.bookmark, iconData: Octicons.bookmark,
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
@@ -144,7 +155,7 @@ class TimelineItem extends StatelessWidget {
item: payload, item: payload,
); );
case 'AssignedEvent': case 'AssignedEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.key, iconData: Octicons.key,
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
@@ -156,7 +167,7 @@ class TimelineItem extends StatelessWidget {
case 'UnassignedEvent': case 'UnassignedEvent':
return defaultItem; // TODO: return defaultItem; // TODO:
case 'LabeledEvent': case 'LabeledEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.tag, iconData: Octicons.tag,
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
@@ -167,7 +178,7 @@ class TimelineItem extends StatelessWidget {
item: payload, item: payload,
); );
case 'UnlabeledEvent': case 'UnlabeledEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.tag, iconData: Octicons.tag,
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
@@ -179,7 +190,7 @@ class TimelineItem extends StatelessWidget {
); );
case 'MilestonedEvent': case 'MilestonedEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.milestone, iconData: Octicons.milestone,
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
@@ -192,7 +203,7 @@ class TimelineItem extends StatelessWidget {
case 'DemilestonedEvent': case 'DemilestonedEvent':
return defaultItem; // TODO: return defaultItem; // TODO:
case 'RenamedTitleEvent': case 'RenamedTitleEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.pencil, iconData: Octicons.pencil,
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
@@ -207,7 +218,7 @@ class TimelineItem extends StatelessWidget {
item: payload, item: payload,
); );
case 'LockedEvent': case 'LockedEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.lock, iconData: Octicons.lock,
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
@@ -216,7 +227,7 @@ class TimelineItem extends StatelessWidget {
item: payload, item: payload,
); );
case 'UnlockedEvent': case 'UnlockedEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.key, iconData: Octicons.key,
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
@@ -233,7 +244,7 @@ class TimelineItem extends StatelessWidget {
case 'CommitCommentThread': case 'CommitCommentThread':
return defaultItem; // TODO: return defaultItem; // TODO:
case 'PullRequestReview': case 'PullRequestReview':
return _buildItem( return TimelineEventItem(
actor: payload['author']['login'], actor: payload['author']['login'],
iconColor: Color(0xff28a745), iconColor: Color(0xff28a745),
iconData: Octicons.check, iconData: Octicons.check,
@@ -244,7 +255,7 @@ class TimelineItem extends StatelessWidget {
case 'PullRequestReviewComment': case 'PullRequestReviewComment':
return defaultItem; // TODO: return defaultItem; // TODO:
case 'MergedEvent': case 'MergedEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.git_merge, iconData: Octicons.git_merge,
iconColor: Color(0xff6f42c1), iconColor: Color(0xff6f42c1),
@@ -260,7 +271,7 @@ class TimelineItem extends StatelessWidget {
case 'DeploymentEnvironmentChangedEvent': case 'DeploymentEnvironmentChangedEvent':
return defaultItem; // TODO: return defaultItem; // TODO:
case 'HeadRefDeletedEvent': case 'HeadRefDeletedEvent':
return _buildItem( return TimelineEventItem(
actor: payload['actor']['login'], actor: payload['actor']['login'],
iconData: Octicons.git_branch, iconData: Octicons.git_branch,
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
@@ -275,14 +286,14 @@ class TimelineItem extends StatelessWidget {
case 'BaseRefForcePushedEvent': case 'BaseRefForcePushedEvent':
return defaultItem; // TODO: return defaultItem; // TODO:
case 'ReviewRequestedEvent': case 'ReviewRequestedEvent':
return _buildItem( return TimelineEventItem(
iconData: Octicons.eye, iconData: Octicons.eye,
// actor: payload['author']['login'], // actor: payload['author']['login'],
// TODO: // TODO:
actor: 'test', actor: 'test',
textSpan: TextSpan(children: [ textSpan: TextSpan(children: [
TextSpan(text: ' requested a review from '), TextSpan(text: ' requested a review from '),
createUserSpan(payload['requestedReviewer']['login']), createUserSpan(context, payload['requestedReviewer']['login']),
]), ]),
item: payload, item: payload,
); );

View File

@@ -5,10 +5,6 @@ import 'link.dart';
final style = TextStyle(fontWeight: FontWeight.w600); final style = TextStyle(fontWeight: FontWeight.w600);
TextSpan createUserSpan(String login) {
return TextSpan(text: login, style: style);
}
class UserName extends StatelessWidget { class UserName extends StatelessWidget {
final String login; final String login;