2020-08-27 23:27:27 +02:00
|
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
|
|
|
import 'package:esys_flutter_share/esys_flutter_share.dart';
|
|
|
|
import 'package:flutter/gestures.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
2020-08-29 21:01:01 +02:00
|
|
|
import 'package:intl/intl.dart';
|
2020-08-28 13:47:52 +02:00
|
|
|
import 'package:lemmy_api_client/lemmy_api_client.dart';
|
2020-08-27 23:27:27 +02:00
|
|
|
import 'package:timeago/timeago.dart' as timeago;
|
|
|
|
|
2020-08-31 12:22:29 +02:00
|
|
|
import 'markdown_text.dart';
|
2020-08-30 16:49:59 +02:00
|
|
|
|
2020-08-29 21:01:01 +02:00
|
|
|
enum MediaType {
|
|
|
|
image,
|
|
|
|
gallery,
|
|
|
|
video,
|
|
|
|
other,
|
|
|
|
}
|
|
|
|
|
|
|
|
MediaType whatType(String url) {
|
|
|
|
if (url == null) return MediaType.other;
|
|
|
|
|
2020-08-30 19:29:12 +02:00
|
|
|
// TODO: make detection more nuanced
|
2020-08-29 21:01:01 +02:00
|
|
|
if (url.endsWith('.jpg') || url.endsWith('.png') || url.endsWith('.gif')) {
|
|
|
|
return MediaType.image;
|
|
|
|
}
|
|
|
|
return MediaType.other;
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:27:27 +02:00
|
|
|
class PostWidget extends StatelessWidget {
|
|
|
|
final PostView post;
|
|
|
|
final String hostUrl;
|
|
|
|
|
|
|
|
/// nullable
|
|
|
|
final String linkPostDomain;
|
|
|
|
|
|
|
|
ThemeData _theme;
|
2020-08-30 16:49:59 +02:00
|
|
|
BuildContext _context;
|
2020-08-28 13:30:42 +02:00
|
|
|
|
2020-08-27 23:27:27 +02:00
|
|
|
PostWidget(this.post)
|
|
|
|
: hostUrl = post.communityActorId.split('/')[2],
|
|
|
|
linkPostDomain = post.url != null ? post.url.split('/')[2] : null;
|
|
|
|
|
2020-08-28 13:30:42 +02:00
|
|
|
// == ACTIONS ==
|
|
|
|
|
|
|
|
void _openLink() {
|
|
|
|
print('OPEN LINK');
|
|
|
|
}
|
|
|
|
|
|
|
|
void _goToUser() {
|
|
|
|
print('GO TO USER');
|
|
|
|
}
|
|
|
|
|
|
|
|
void _goToPost() {
|
|
|
|
print('GO OT POST');
|
|
|
|
}
|
|
|
|
|
|
|
|
void _goToCommunity() {
|
|
|
|
print('GO TO COMMUNITY');
|
|
|
|
}
|
|
|
|
|
|
|
|
void _goToInstance() {
|
|
|
|
print('GO TO INSTANCE');
|
|
|
|
}
|
|
|
|
|
2020-08-29 21:01:01 +02:00
|
|
|
void _openFullImage() {
|
|
|
|
print('OPEN FULL IMAGE');
|
|
|
|
}
|
|
|
|
|
2020-08-28 13:30:42 +02:00
|
|
|
// POST ACTIONS
|
|
|
|
|
|
|
|
void _savePost() {
|
|
|
|
print('SAVE POST');
|
|
|
|
}
|
|
|
|
|
|
|
|
void _upvotePost() {
|
|
|
|
print('UPVOTE POST');
|
|
|
|
}
|
|
|
|
|
|
|
|
void _downvotePost() {
|
|
|
|
print('DOWNVOTE POST');
|
|
|
|
}
|
|
|
|
|
|
|
|
void _showMoreMenu() {
|
|
|
|
print('SHOW MORE MENU');
|
|
|
|
}
|
|
|
|
|
|
|
|
// == UI ==
|
|
|
|
|
2020-08-27 23:27:27 +02:00
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
_theme = Theme.of(context);
|
2020-08-30 16:49:59 +02:00
|
|
|
_context = context;
|
2020-08-28 13:30:42 +02:00
|
|
|
|
2020-08-27 23:27:27 +02:00
|
|
|
return Container(
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
boxShadow: [BoxShadow(blurRadius: 10, color: Colors.black54)],
|
|
|
|
color: _theme.colorScheme.surface,
|
|
|
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
|
|
|
),
|
|
|
|
child: InkWell(
|
2020-08-29 21:01:01 +02:00
|
|
|
onTap: _goToPost,
|
2020-08-27 23:27:27 +02:00
|
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
_info(),
|
|
|
|
_title(),
|
2020-08-29 21:01:01 +02:00
|
|
|
if (whatType(post.url) != MediaType.other)
|
|
|
|
_postImage()
|
|
|
|
else if (post.url != null)
|
|
|
|
_linkPreview(),
|
|
|
|
if (post.body != null) _textBody(),
|
2020-08-27 23:27:27 +02:00
|
|
|
_actions(),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget _info() {
|
|
|
|
return Column(children: [
|
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.all(10),
|
|
|
|
child: Row(children: [
|
|
|
|
Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
|
|
|
if (post.communityIcon != null)
|
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.only(right: 10),
|
|
|
|
child: InkWell(
|
2020-08-28 13:30:42 +02:00
|
|
|
onTap: _goToCommunity,
|
2020-08-27 23:27:27 +02:00
|
|
|
child: SizedBox(
|
|
|
|
height: 40,
|
|
|
|
width: 40,
|
|
|
|
child: CachedNetworkImage(
|
|
|
|
imageBuilder: (context, imageProvider) => Container(
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
shape: BoxShape.circle,
|
|
|
|
image: DecorationImage(
|
|
|
|
fit: BoxFit.cover,
|
|
|
|
image: imageProvider,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
imageUrl: post.communityIcon,
|
|
|
|
errorWidget: (context, url, error) =>
|
|
|
|
Text(error.toString()),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
Column(
|
|
|
|
children: [
|
|
|
|
Row(children: [
|
|
|
|
RichText(
|
2020-08-30 19:29:12 +02:00
|
|
|
overflow: TextOverflow.ellipsis, // TODO: fix overflowing
|
2020-08-27 23:27:27 +02:00
|
|
|
text: TextSpan(
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: 15, color: _theme.textTheme.bodyText1.color),
|
|
|
|
children: [
|
|
|
|
TextSpan(
|
|
|
|
text: '!',
|
|
|
|
style: TextStyle(fontWeight: FontWeight.w300)),
|
|
|
|
TextSpan(
|
|
|
|
text: post.communityName,
|
|
|
|
style: TextStyle(fontWeight: FontWeight.w600),
|
|
|
|
recognizer: TapGestureRecognizer()
|
2020-08-28 13:30:42 +02:00
|
|
|
..onTap = _goToCommunity),
|
2020-08-27 23:27:27 +02:00
|
|
|
TextSpan(
|
|
|
|
text: '@',
|
|
|
|
style: TextStyle(fontWeight: FontWeight.w300)),
|
|
|
|
TextSpan(
|
|
|
|
text: hostUrl,
|
|
|
|
style: TextStyle(fontWeight: FontWeight.w600),
|
|
|
|
recognizer: TapGestureRecognizer()
|
2020-08-28 13:30:42 +02:00
|
|
|
..onTap = _goToInstance),
|
2020-08-27 23:27:27 +02:00
|
|
|
],
|
|
|
|
),
|
|
|
|
)
|
|
|
|
]),
|
|
|
|
Row(children: [
|
|
|
|
RichText(
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
text: TextSpan(
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: 13,
|
|
|
|
color: _theme.textTheme.bodyText1.color),
|
|
|
|
children: [
|
|
|
|
TextSpan(
|
|
|
|
text: 'by',
|
|
|
|
style: TextStyle(fontWeight: FontWeight.w300)),
|
|
|
|
TextSpan(
|
|
|
|
text:
|
|
|
|
''' ${post.creatorPreferredUsername ?? post.creatorName}''',
|
|
|
|
style: TextStyle(fontWeight: FontWeight.w600),
|
2020-08-28 13:30:42 +02:00
|
|
|
recognizer: TapGestureRecognizer()..onTap = _goToUser,
|
2020-08-27 23:27:27 +02:00
|
|
|
),
|
|
|
|
TextSpan(
|
|
|
|
text:
|
|
|
|
''' · ${timeago.format(post.published, locale: 'en_short')}'''),
|
|
|
|
if (linkPostDomain != null)
|
|
|
|
TextSpan(text: ' · $linkPostDomain'),
|
|
|
|
if (post.locked) TextSpan(text: ' · 🔒'),
|
|
|
|
],
|
|
|
|
))
|
|
|
|
]),
|
|
|
|
],
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
),
|
|
|
|
Spacer(),
|
|
|
|
Column(
|
|
|
|
children: [
|
|
|
|
IconButton(
|
2020-08-28 13:30:42 +02:00
|
|
|
onPressed: _showMoreMenu,
|
2020-08-27 23:27:27 +02:00
|
|
|
icon: Icon(Icons.more_vert),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
)
|
|
|
|
]),
|
|
|
|
),
|
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget _title() {
|
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10),
|
|
|
|
child: Row(
|
|
|
|
children: [
|
|
|
|
Flexible(
|
|
|
|
child: Text(
|
|
|
|
'${post.name}',
|
|
|
|
textAlign: TextAlign.left,
|
|
|
|
softWrap: true,
|
|
|
|
style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
|
|
|
|
),
|
|
|
|
),
|
2020-08-29 21:01:01 +02:00
|
|
|
if (post.url != null &&
|
|
|
|
!(whatType(post.url) != MediaType.other) &&
|
|
|
|
post.thumbnailUrl != null)
|
|
|
|
Spacer(),
|
|
|
|
if (post.url != null &&
|
|
|
|
!(whatType(post.url) != MediaType.other) &&
|
|
|
|
post.thumbnailUrl != null)
|
2020-08-27 23:27:27 +02:00
|
|
|
InkWell(
|
2020-08-28 13:30:42 +02:00
|
|
|
onTap: _openLink,
|
2020-08-27 23:27:27 +02:00
|
|
|
child: Stack(children: [
|
|
|
|
ClipRRect(
|
|
|
|
borderRadius: BorderRadius.circular(20),
|
|
|
|
child: CachedNetworkImage(
|
|
|
|
imageUrl: post.thumbnailUrl,
|
|
|
|
width: 70,
|
|
|
|
height: 70,
|
|
|
|
fit: BoxFit.cover,
|
|
|
|
errorWidget: (context, url, error) =>
|
|
|
|
Text(error.toString()),
|
|
|
|
)),
|
|
|
|
Positioned(
|
|
|
|
top: 8,
|
|
|
|
right: 8,
|
|
|
|
child: Icon(
|
|
|
|
Icons.launch,
|
|
|
|
size: 20,
|
|
|
|
),
|
|
|
|
)
|
|
|
|
]),
|
|
|
|
)
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-08-29 21:01:01 +02:00
|
|
|
Widget _postImage() {
|
|
|
|
assert(post.url != null);
|
|
|
|
|
|
|
|
return InkWell(
|
|
|
|
onTap: _openFullImage,
|
|
|
|
child: CachedNetworkImage(
|
2020-08-27 23:27:27 +02:00
|
|
|
imageUrl: post.url,
|
|
|
|
progressIndicatorBuilder: (context, url, progress) =>
|
|
|
|
CircularProgressIndicator(value: progress.progress),
|
2020-08-29 21:01:01 +02:00
|
|
|
),
|
|
|
|
);
|
2020-08-27 23:27:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Widget _linkPreview() {
|
|
|
|
assert(post.url != null);
|
|
|
|
|
|
|
|
var url = post.url.split('/')[2];
|
|
|
|
if (url.startsWith('www.')) {
|
|
|
|
url = url.substring(4);
|
|
|
|
}
|
|
|
|
|
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.all(10),
|
|
|
|
child: InkWell(
|
2020-08-28 13:30:42 +02:00
|
|
|
onTap: _openLink,
|
2020-08-27 23:27:27 +02:00
|
|
|
child: Container(
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
border: Border.all(width: 1),
|
|
|
|
borderRadius: BorderRadius.circular(5)),
|
|
|
|
child: Padding(
|
|
|
|
padding: const EdgeInsets.all(10),
|
|
|
|
child: Column(
|
|
|
|
children: [
|
|
|
|
Row(children: [
|
|
|
|
Spacer(),
|
|
|
|
Text('$url ',
|
|
|
|
style: _theme.textTheme.caption
|
|
|
|
.apply(fontStyle: FontStyle.italic)),
|
|
|
|
Icon(Icons.launch, size: 12),
|
|
|
|
]),
|
|
|
|
Row(children: [
|
|
|
|
Flexible(
|
|
|
|
child: Text(post.embedTitle,
|
|
|
|
style: _theme.textTheme.subtitle1
|
|
|
|
.apply(fontWeightDelta: 2)))
|
|
|
|
]),
|
|
|
|
Row(children: [Flexible(child: Text(post.embedDescription))]),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-08-29 21:01:01 +02:00
|
|
|
Widget _textBody() {
|
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.all(10),
|
2020-08-31 12:22:29 +02:00
|
|
|
child: MarkdownText(post.body, _context),
|
2020-08-29 21:01:01 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2020-08-27 23:27:27 +02:00
|
|
|
Widget _actions() {
|
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
|
|
|
|
child: Row(
|
|
|
|
children: [
|
|
|
|
Icon(Icons.comment),
|
2020-08-30 19:31:55 +02:00
|
|
|
Expanded(
|
|
|
|
flex: 999,
|
|
|
|
child: Text(
|
|
|
|
''' ${NumberFormat.compact().format(post.numberOfComments)} comment${post.numberOfComments == 1 ? '' : 's'}''',
|
|
|
|
overflow: TextOverflow.fade,
|
|
|
|
softWrap: false,
|
2020-08-29 20:31:53 +02:00
|
|
|
),
|
2020-08-30 19:31:55 +02:00
|
|
|
),
|
2020-08-27 23:27:27 +02:00
|
|
|
Spacer(),
|
|
|
|
IconButton(
|
|
|
|
icon: Icon(Icons.share),
|
|
|
|
onPressed: () => Share.text('Share post url', post.apId,
|
2020-08-30 19:29:12 +02:00
|
|
|
'text/plain')), // TODO: find a way to mark it as url
|
2020-08-27 23:27:27 +02:00
|
|
|
IconButton(
|
|
|
|
icon: post.saved == true
|
|
|
|
? Icon(Icons.bookmark)
|
|
|
|
: Icon(Icons.bookmark_border),
|
2020-08-28 13:30:42 +02:00
|
|
|
onPressed: _savePost),
|
|
|
|
IconButton(icon: Icon(Icons.arrow_upward), onPressed: _upvotePost),
|
2020-08-30 19:31:55 +02:00
|
|
|
Text(NumberFormat.compact().format(post.score)),
|
2020-08-27 23:27:27 +02:00
|
|
|
IconButton(
|
2020-08-28 13:30:42 +02:00
|
|
|
icon: Icon(Icons.arrow_downward), onPressed: _downvotePost),
|
2020-08-27 23:27:27 +02:00
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|