Merge pull request #34 from krawieck/integration-and-polish

Integration and polish
This commit is contained in:
Marcin Wojnarowski 2020-09-10 15:41:44 +02:00 committed by GitHub
commit c5e90346fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 520 additions and 199 deletions

View File

@ -45,6 +45,8 @@ class MyApp extends StatelessWidget {
scaffoldBackgroundColor: maybeAmoledColor,
backgroundColor: maybeAmoledColor,
canvasColor: maybeAmoledColor,
cardColor: maybeAmoledColor,
splashColor: maybeAmoledColor,
),
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,

View File

@ -2,6 +2,8 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../util/api_extensions.dart';
import '../util/goto.dart';
import '../widgets/markdown_text.dart';
class CommunitiesListPage extends StatelessWidget {
@ -12,10 +14,6 @@ class CommunitiesListPage extends StatelessWidget {
: assert(communities != null),
super(key: key);
void goToCommunity(BuildContext context, int id) {
print('GO TO COMMUNITY $id');
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@ -36,7 +34,8 @@ class CommunitiesListPage extends StatelessWidget {
child: MarkdownText(communities[i].description),
)
: null,
onTap: () => goToCommunity(context, communities[i].id),
onTap: () => goToCommunity.byId(
context, communities[i].instanceUrl, communities[i].id),
leading: communities[i].icon != null
? CachedNetworkImage(
height: 50,

View File

@ -1,12 +1,18 @@
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';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:url_launcher/url_launcher.dart' as ul;
import '../util/api_extensions.dart';
import '../util/goto.dart';
import '../util/intl.dart';
import '../util/text_color.dart';
import '../widgets/badge.dart';
import '../widgets/bottom_modal.dart';
import '../widgets/markdown_text.dart';
class CommunityPage extends HookWidget {
@ -28,27 +34,15 @@ class CommunityPage extends HookWidget {
LemmyApi(instanceUrl).v1.getCommunity(id: communityId),
_community = null;
CommunityPage.fromCommunityView(this._community)
: instanceUrl = _community.actorId.split('/')[2],
_fullCommunityFuture = LemmyApi(_community.actorId.split('/')[2])
: instanceUrl = _community.instanceUrl,
_fullCommunityFuture = LemmyApi(_community.instanceUrl)
.v1
.getCommunity(name: _community.name);
void _goToInstance() {
print('GO TO INSTANCE');
}
void _subscribe() {
print('SUBSCRIBE');
}
void _share() {
print('SHARE');
}
void _openMoreMenu() {
print('OPEN MORE MENU');
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@ -66,10 +60,13 @@ class CommunityPage extends HookWidget {
}
}();
// FALLBACK
if (community == null) {
return Scaffold(
appBar: AppBar(
iconTheme: theme.iconTheme,
brightness: theme.brightness,
backgroundColor: theme.cardColor,
elevation: 0,
),
@ -91,6 +88,64 @@ class CommunityPage extends HookWidget {
);
}
// FUNCTIONS
void _share() =>
Share.text('Share instance', community.actorId, 'text/plain');
void _openMoreMenu() {
showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => BottomModal(
child: Column(
children: [
ListTile(
leading: Icon(Icons.open_in_browser),
title: Text('Open in browser'),
onTap: () async => await ul.canLaunch(community.actorId)
? ul.launch(community.actorId)
: Scaffold.of(context).showSnackBar(
SnackBar(content: Text("can't open in browser"))),
),
ListTile(
leading: Icon(Icons.info_outline),
title: Text('Nerd stuff'),
onTap: () {
showDialog(
context: context,
child: SimpleDialog(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 15,
),
children: [
Table(
children: [
TableRow(children: [
Text('created by:'),
Text('@${community.creatorName}'),
]),
TableRow(children: [
Text('hot rank:'),
Text(community.hotRank.toString()),
]),
TableRow(children: [
Text('published:'),
Text(
'''${DateFormat.yMMMd().format(community.published)}'''
''' ${DateFormat.Hms().format(community.published)}'''),
]),
],
),
]));
},
),
],
),
),
);
}
return Scaffold(
body: DefaultTabController(
length: 3,
@ -103,6 +158,7 @@ class CommunityPage extends HookWidget {
pinned: true,
elevation: 0,
backgroundColor: theme.cardColor,
brightness: theme.brightness,
iconTheme: theme.iconTheme,
title: Text('!${community.name}',
style: TextStyle(color: colorOnCard)),
@ -115,7 +171,6 @@ class CommunityPage extends HookWidget {
background: _CommunityOverview(
community,
instanceUrl: instanceUrl,
goToInstance: _goToInstance,
subscribe: _subscribe,
),
),
@ -162,13 +217,11 @@ class CommunityPage extends HookWidget {
class _CommunityOverview extends StatelessWidget {
final CommunityView community;
final String instanceUrl;
final void Function() goToInstance;
final void Function() subscribe;
_CommunityOverview(
this.community, {
@required this.instanceUrl,
@required this.goToInstance,
@required this.subscribe,
}) : assert(instanceUrl != null),
assert(goToInstance != null),
@ -252,7 +305,7 @@ class _CommunityOverview extends StatelessWidget {
text: instanceUrl,
style: TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = goToInstance),
..onTap = () => goToInstance(context, instanceUrl)),
],
),
),
@ -362,10 +415,6 @@ class _AboutTab extends StatelessWidget {
@required this.moderators,
}) : super(key: key);
void goToUser(int id) {
print('GO TO USER $id');
}
void goToModlog() {
print('GO TO MODLOG');
}
@ -441,7 +490,7 @@ class _AboutTab extends StatelessWidget {
for (final mod in moderators)
ListTile(
title: Text(mod.userPreferredUsername ?? '@${mod.userName}'),
onTap: () => goToUser(mod.id),
onTap: () => goToUser.byId(context, mod.instanceUrl, mod.id),
),
]
],

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../util/api_extensions.dart';
import '../widgets/comment_section.dart';
import '../widgets/post.dart';
@ -16,88 +17,73 @@ class FullPostPage extends HookWidget {
fullPost = LemmyApi(instanceUrl).v1.getPost(id: id),
post = null;
FullPostPage.fromPostView(this.post)
: fullPost = LemmyApi(post.communityActorId.split('/')[2])
.v1
.getPost(id: post.id);
void sharePost() => Share.text('Share post', post.apId, 'text/plain');
void savePost() {
//
}
: fullPost = LemmyApi(post.instanceUrl).v1.getPost(id: post.id);
@override
Widget build(BuildContext context) {
final fullPostSnap = useFuture(this.fullPost);
// FALLBACK VIEW
if (!fullPostSnap.hasData && this.post == null) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (fullPostSnap.hasError)
Text(fullPostSnap.error.toString())
else
const CircularProgressIndicator(),
],
),
),
);
}
// VARIABLES
final post = fullPostSnap.hasData ? fullPostSnap.data.post : this.post;
final fullPost = fullPostSnap.data;
final savedIcon = () {
if (fullPostSnap.hasData) {
if (fullPost.post.saved == null || !fullPost.post.saved) {
return Icons.bookmark_border;
} else {
return Icons.bookmark;
}
}
final savedIcon = (post.saved == null || !post.saved)
? Icons.bookmark_border
: Icons.bookmark;
if (post != null) {
if (post.saved == null || !post.saved) {
return Icons.bookmark_border;
} else {
return Icons.bookmark;
}
}
// FUNCTIONS
return Icons.bookmark_border;
}();
sharePost() => Share.text('Share post', post.apId, 'text/plain');
savePost() {
print('SAVE POST');
}
return Scaffold(
appBar: AppBar(
leading: BackButton(),
actions: [
IconButton(icon: Icon(Icons.share), onPressed: sharePost),
IconButton(icon: Icon(savedIcon), onPressed: savePost),
IconButton(
icon: Icon(Icons.more_vert), onPressed: () {}), // TODO: more menu
],
),
body: fullPostSnap.hasData || post != null
// FUTURE SUCCESS
? ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: [
if (fullPostSnap.hasData)
Post(fullPost.post, fullPost: true)
else if (post != null)
Post(post, fullPost: true)
else
CircularProgressIndicator(),
if (fullPostSnap.hasData)
CommentSection(fullPost.comments,
postCreatorId: fullPost.post.creatorId)
else
Container(
child: Center(child: CircularProgressIndicator()),
padding: EdgeInsets.only(top: 40),
),
],
)
: fullPostSnap.hasError
// FUTURE FAILURE
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, size: 30),
Padding(padding: EdgeInsets.all(5)),
Text('ERROR: ${fullPostSnap.error.toString()}'),
],
),
)
// FUTURE IN PROGRESS
: Container(
child: Center(child: CircularProgressIndicator()),
color: Theme.of(context).canvasColor),
);
appBar: AppBar(
leading: BackButton(),
actions: [
IconButton(icon: Icon(Icons.share), onPressed: sharePost),
IconButton(icon: Icon(savedIcon), onPressed: savePost),
IconButton(
icon: Icon(Icons.more_vert),
onPressed: () => Post.showMoreMenu(context, post)),
],
),
body: ListView(
physics: const AlwaysScrollableScrollPhysics(),
children: [
Post(post, fullPost: true),
if (fullPostSnap.hasData)
CommentSection(fullPost.comments,
postCreatorId: fullPost.post.creatorId)
else
Container(
child: Center(child: CircularProgressIndicator()),
padding: EdgeInsets.only(top: 40),
),
],
));
}
}

View File

@ -6,6 +6,8 @@ import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:url_launcher/url_launcher.dart' as ul;
import '../util/api_extensions.dart';
import '../util/goto.dart';
import '../util/text_color.dart';
import '../widgets/badge.dart';
import '../widgets/bottom_modal.dart';
@ -37,6 +39,7 @@ class InstancePage extends HookWidget {
return Scaffold(
appBar: AppBar(
iconTheme: theme.iconTheme,
brightness: theme.brightness,
backgroundColor: theme.cardColor,
elevation: 0,
),
@ -136,6 +139,7 @@ class InstancePage extends HookWidget {
child: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => <Widget>[
SliverAppBar(
brightness: theme.brightness,
expandedHeight: 200,
floating: false,
pinned: true,
@ -256,10 +260,6 @@ class _AboutTab extends HookWidget {
));
}
void goToCommunity(int id) {
print('GO TO COMMUNITY $id');
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
@ -309,7 +309,8 @@ class _AboutTab extends HookWidget {
),
if (commSnap.hasData)
...commSnap.data.getRange(0, 6).map((e) => ListTile(
onTap: () => goToCommunity(e.id),
onTap: () =>
goToCommunity.byId(context, e.instanceUrl, e.id),
title: Text(e.name),
leading: e.icon != null
? CachedNetworkImage(

View File

@ -4,6 +4,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import '../stores/accounts_store.dart';
import '../util/api_extensions.dart';
import '../widgets/bottom_modal.dart';
import '../widgets/user_profile.dart';
import 'settings.dart';
@ -81,7 +82,7 @@ class UserProfileTab extends HookWidget {
return Observer(
builder: (ctx) {
var user = ctx.watch<AccountsStore>().defaultUser;
var instanceUrl = user.actorId.split('/')[2];
var instanceUrl = user.instanceUrl;
return BottomModal(
title: 'account',
@ -120,7 +121,7 @@ class UserProfileTab extends HookWidget {
),
body: UserProfile(
userId: user.id,
instanceUrl: user.actorId.split('/')[2],
instanceUrl: user.instanceUrl,
),
);
},

View File

@ -0,0 +1,26 @@
import 'package:lemmy_api_client/lemmy_api_client.dart';
extension GetInstanceCommunityView on CommunityView {
// TODO: change it to something more robust? regex?
String get instanceUrl => actorId.split('/')[2];
}
extension GetInstanceUserView on UserView {
String get instanceUrl => actorId.split('/')[2];
}
extension GetInstanceCommunityModeratorView on CommunityModeratorView {
String get instanceUrl => userActorId.split('/')[2];
}
extension GetInstancePostView on PostView {
String get instanceUrl => apId.split('/')[2];
}
extension GetInstanceUser on User {
String get instanceUrl => actorId.split('/')[2];
}
extension GetInstanceCommentView on CommentView {
String get instanceUrl => apId.split('/')[2];
}

45
lib/util/goto.dart Normal file
View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import '../pages/community.dart';
import '../pages/full_post.dart';
import '../pages/instance.dart';
import '../pages/user.dart';
void goToInstance(BuildContext context, String instanceUrl) =>
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => InstancePage(instanceUrl: instanceUrl),
));
// ignore: camel_case_types
abstract class goToCommunity {
/// Navigates to `CommunityPage`
static void byId(BuildContext context, String instanceUrl, int communityId) =>
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CommunityPage.fromId(
instanceUrl: instanceUrl, communityId: communityId),
));
static void byName(
BuildContext context, String instanceUrl, String communityName) =>
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => CommunityPage.fromName(
instanceUrl: instanceUrl, communityName: communityName),
));
}
// ignore: camel_case_types
abstract class goToUser {
static void byId(BuildContext context, String instanceUrl, int userId) =>
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
UserPage(instanceUrl: instanceUrl, userId: userId)));
static void byName(
BuildContext context, String instanceUrl, String userName) =>
throw UnimplementedError('need to create UserProfile constructor first');
}
void goToPost(BuildContext context, String instanceUrl, int postId) =>
Navigator.of(context).push(MaterialPageRoute(
builder: (context) =>
FullPostPage(instanceUrl: instanceUrl, id: postId)));

View File

@ -15,7 +15,7 @@ class BottomModal extends StatelessWidget {
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.only(top: 10),
padding: title != null ? const EdgeInsets.only(top: 10) : null,
decoration: BoxDecoration(
color: theme.scaffoldBackgroundColor,
borderRadius: BorderRadius.all(const Radius.circular(10.0)),

View File

@ -1,28 +1,74 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:url_launcher/url_launcher.dart' as ul;
import '../comment_tree.dart';
import '../util/api_extensions.dart';
import '../util/goto.dart';
import '../util/text_color.dart';
import 'bottom_modal.dart';
import 'markdown_text.dart';
class Comment extends StatelessWidget {
final int indent;
final int postCreatorId;
final CommentTree commentTree;
static const colors = [
Colors.pink,
Colors.green,
Colors.amber,
Colors.cyan,
Colors.indigo,
];
Comment(
this.commentTree, {
this.indent = 0,
@required this.postCreatorId,
});
void _openMoreMenu() {
print('OPEN MORE MENU');
}
void _goToUser() {
print('GO TO USER');
void _openMoreMenu(BuildContext context) {
final com = commentTree.comment;
showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => BottomModal(
child: Column(
children: [
ListTile(
leading: Icon(Icons.open_in_browser),
title: Text('Open in browser'),
onTap: () async => await ul.canLaunch(com.apId)
? ul.launch(com.apId)
: Scaffold.of(context).showSnackBar(
SnackBar(content: Text("can't open in browser"))),
),
ListTile(
leading: Icon(Icons.share),
title: Text('Share url'),
onTap: () =>
Share.text('Share comment url', com.apId, 'text/plain'),
),
ListTile(
leading: Icon(Icons.share),
title: Text('Share text'),
onTap: () =>
Share.text('Share comment text', com.content, 'text/plain'),
),
ListTile(
leading: Icon(Icons.info_outline),
title: Text('Nerd stuff'),
onTap: () => _showCommentInfo(context),
),
],
),
),
);
}
void _save(bool save) {
@ -37,6 +83,60 @@ class Comment extends StatelessWidget {
print('COMMENT VOTE: ${vote.toString()}');
}
_showCommentInfo(BuildContext context) {
final com = commentTree.comment;
showDialog(
context: context,
child: SimpleDialog(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 15,
),
children: [
Table(
children: [
TableRow(children: [
Text('upvotes:'),
Text(com.upvotes.toString()),
]),
TableRow(children: [
Text('downvotes:'),
Text(com.downvotes.toString()),
]),
TableRow(children: [
Text('score:'),
Text(com.score.toString()),
]),
TableRow(children: [
Text('% of upvotes:'),
Text(
'''${(100 * (com.upvotes / (com.upvotes + com.downvotes))).toInt()}%'''),
]),
TableRow(children: [
Text('hotrank:'),
Text(com.hotRank.toString()),
]),
TableRow(children: [
Text('hotrank active:'),
Text(com.hotRankActive.toString()),
]),
TableRow(children: [
Text('published:'),
Text('''${DateFormat.yMMMd().format(com.published)}'''
''' ${DateFormat.Hms().format(com.published)}'''),
]),
TableRow(children: [
Text('updated:'),
Text(com.updated != null
? '''${DateFormat.yMMMd().format(com.updated)}'''
''' ${DateFormat.Hms().format(com.updated)}'''
: 'never'),
]),
],
),
]));
}
bool get isOP => commentTree.comment.creatorId == postCreatorId;
@override
@ -83,7 +183,8 @@ class Comment extends StatelessWidget {
Padding(
padding: const EdgeInsets.only(right: 5),
child: InkWell(
onTap: _goToUser,
onTap: () => goToUser.byId(
context, comment.instanceUrl, comment.creatorId),
child: CachedNetworkImage(
imageUrl: comment.creatorAvatar,
height: 20,
@ -105,23 +206,31 @@ class Comment extends StatelessWidget {
style: TextStyle(
color: Theme.of(context).accentColor,
)),
onLongPress: _goToUser,
onTap: () => goToUser.byId(
context, comment.instanceUrl, comment.creatorId),
),
if (isOP) _CommentTag('OP', Theme.of(context).accentColor),
if (comment.banned) _CommentTag('BANNED', Colors.red),
if (comment.bannedFromCommunity)
_CommentTag('BANNED FROM COMMUNITY', Colors.red),
Spacer(),
Text(comment.score.toString()),
Text(' · '),
Text(timeago.format(comment.published, locale: 'en_short')),
InkWell(
onTap: () => _showCommentInfo(context),
child: Row(
children: [
Text(comment.score.toString()),
Text(' · '),
Text(timeago.format(comment.published)),
],
),
)
]),
Row(children: [body]),
Row(children: [
Spacer(),
_CommentAction(
icon: Icons.more_horiz,
onPressed: _openMoreMenu,
onPressed: () => _openMoreMenu(context),
tooltip: 'more',
),
_CommentAction(
@ -152,7 +261,8 @@ class Comment extends StatelessWidget {
decoration: BoxDecoration(
border: Border(
left: indent > 0
? BorderSide(color: Colors.red, width: 5)
? BorderSide(
color: colors[indent % colors.length], width: 5)
: BorderSide.none,
top: BorderSide(width: 0.2))),
),

View File

@ -1,8 +1,10 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import '../comment_tree.dart';
import 'bottom_modal.dart';
import 'comment.dart';
/// Manages comments section, sorts them
@ -12,6 +14,14 @@ class CommentSection extends HookWidget {
final int postCreatorId;
final CommentSortType sortType;
static const sortPairs = {
CommentSortType.hot: [Icons.whatshot, 'Hot'],
CommentSortType.new_: [Icons.new_releases, 'New'],
CommentSortType.old: [Icons.calendar_today, 'Old'],
CommentSortType.top: [Icons.trending_up, 'Top'],
CommentSortType.chat: [Icons.chat, 'Chat'],
};
CommentSection(
List<CommentView> rawComments, {
@required this.postCreatorId,
@ -41,29 +51,38 @@ class CommentSection extends HookWidget {
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 15),
child: Row(
children: [
Container(
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
decoration: BoxDecoration(
border: Border.all(color: Colors.black45),
borderRadius: BorderRadius.all(Radius.circular(10)),
OutlineButton(
onPressed: () {
showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => BottomModal(
title: 'sort by',
child: Column(
children: [
for (final e in sortPairs.entries)
ListTile(
leading: Icon(e.value[0]),
title: Text(e.value[1]),
trailing: sorting.value == e.key
? Icon(Icons.check)
: null,
onTap: () {
Navigator.of(context).pop();
sortComments(e.key);
},
)
],
),
));
},
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
child: DropdownButton(
// TODO: change it to universal BottomModal
underline: Container(),
isDense: true,
onChanged: sortComments,
value: sorting.value,
items: [
DropdownMenuItem(
child: Text('Hot'), value: CommentSortType.hot),
DropdownMenuItem(
child: Text('Top'), value: CommentSortType.top),
DropdownMenuItem(
child: Text('New'), value: CommentSortType.new_),
DropdownMenuItem(
child: Text('Old'), value: CommentSortType.old),
DropdownMenuItem(
child: Text('Chat'), value: CommentSortType.chat),
child: Row(
children: [
Text(sortPairs[sorting.value][1]),
Icon(Icons.arrow_drop_down),
],
),
),

View File

@ -5,9 +5,13 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:timeago/timeago.dart' as timeago;
import 'package:url_launcher/url_launcher.dart' as ul;
import '../pages/full_post.dart';
import '../url_launcher.dart';
import '../util/api_extensions.dart';
import '../util/goto.dart';
import 'bottom_modal.dart';
import 'markdown_text.dart';
enum MediaType {
@ -32,38 +36,14 @@ class Post extends StatelessWidget {
final String instanceUrl;
final bool fullPost;
/// nullable
final String postUrlDomain;
Post(this.post, {this.fullPost = false})
: instanceUrl = post.communityActorId.split('/')[2],
postUrlDomain = post.url != null ? post.url.split('/')[2] : null;
Post(this.post, {this.fullPost = false}) : instanceUrl = post.instanceUrl;
// == ACTIONS ==
void _openLink() {
print('OPEN LINK');
urlLauncher(post.url);
}
void _goToUser() {
print('GO TO USER');
}
void _goToPost(BuildContext context) {
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => FullPostPage.fromPostView(post)));
}
void _goToCommunity() {
print('GO TO COMMUNITY');
}
void _goToInstance() {
print('GO TO INSTANCE');
}
void _openLink() => urlLauncher(post.url);
void _openFullImage() {
// TODO: fullscreen media view
print('OPEN FULL IMAGE');
}
@ -81,8 +61,84 @@ class Post extends StatelessWidget {
print('DOWNVOTE POST');
}
void _showMoreMenu() {
print('SHOW MORE MENU');
static void showMoreMenu(BuildContext context, PostView post) {
showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
builder: (context) => BottomModal(
child: Column(
children: [
ListTile(
leading: Icon(Icons.open_in_browser),
title: Text('Open in browser'),
onTap: () async => await ul.canLaunch(post.apId)
? ul.launch(post.apId)
: Scaffold.of(context).showSnackBar(
SnackBar(content: Text("can't open in browser"))),
),
ListTile(
leading: Icon(Icons.info_outline),
title: Text('Nerd stuff'),
onTap: () {
showDialog(
context: context,
child: SimpleDialog(
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 15,
),
children: [
Table(
children: [
TableRow(children: [
Text('upvotes:'),
Text(post.upvotes.toString()),
]),
TableRow(children: [
Text('downvotes:'),
Text(post.downvotes.toString()),
]),
TableRow(children: [
Text('score:'),
Text(post.score.toString()),
]),
TableRow(children: [
Text('% of upvotes:'),
Text(
'''${(100 * (post.upvotes / (post.upvotes + post.downvotes))).toInt()}%'''),
]),
TableRow(children: [
Text('hotrank:'),
Text(post.hotRank.toString()),
]),
TableRow(children: [
Text('hotrank active:'),
Text(post.hotRankActive.toString()),
]),
TableRow(children: [
Text('published:'),
Text(
'''${DateFormat.yMMMd().format(post.published)}'''
''' ${DateFormat.Hms().format(post.published)}'''),
]),
TableRow(children: [
Text('updated:'),
Text(post.updated != null
? '''${DateFormat.yMMMd().format(post.updated)}'''
''' ${DateFormat.Hms().format(post.updated)}'''
: 'never'),
]),
],
),
],
),
);
},
),
],
),
),
);
}
// == UI ==
@ -91,6 +147,14 @@ class Post extends StatelessWidget {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final urlDomain = () {
if (post.url == null) return null;
var url = post.url.split('/')[2];
if (url.startsWith('www.')) return url.substring(4);
return url;
}();
// TODO: add NSFW, locked, removed, deleted, stickied
/// assemble info section
Widget info() => Column(children: [
@ -104,7 +168,8 @@ class Post extends StatelessWidget {
Padding(
padding: const EdgeInsets.only(right: 10),
child: InkWell(
onTap: _goToCommunity,
onTap: () => goToCommunity.byId(
context, instanceUrl, post.communityId),
child: SizedBox(
height: 40,
width: 40,
@ -144,7 +209,8 @@ class Post extends StatelessWidget {
text: post.communityName,
style: TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = _goToCommunity),
..onTap = () => goToCommunity.byId(
context, instanceUrl, post.communityId)),
TextSpan(
text: '@',
style: TextStyle(fontWeight: FontWeight.w300)),
@ -152,7 +218,8 @@ class Post extends StatelessWidget {
text: instanceUrl,
style: TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = _goToInstance),
..onTap =
() => goToInstance(context, instanceUrl)),
],
),
)
@ -173,7 +240,8 @@ class Post extends StatelessWidget {
''' ${post.creatorPreferredUsername ?? post.creatorName}''',
style: TextStyle(fontWeight: FontWeight.w600),
recognizer: TapGestureRecognizer()
..onTap = _goToUser,
..onTap = () => goToUser.byId(
context, post.instanceUrl, post.creatorId),
),
TextSpan(
text:
@ -185,8 +253,8 @@ class Post extends StatelessWidget {
TextSpan(
text: 'NSFW',
style: TextStyle(color: Colors.red)),
if (postUrlDomain != null)
TextSpan(text: ' · $postUrlDomain'),
if (urlDomain != null)
TextSpan(text: ' · $urlDomain'),
if (post.removed) TextSpan(text: ' · REMOVED'),
if (post.deleted) TextSpan(text: ' · DELETED'),
],
@ -200,7 +268,7 @@ class Post extends StatelessWidget {
Column(
children: [
IconButton(
onPressed: _showMoreMenu,
onPressed: () => showMoreMenu(context, post),
icon: Icon(Icons.more_vert),
)
],
@ -214,7 +282,8 @@ class Post extends StatelessWidget {
padding: const EdgeInsets.only(left: 10, right: 10, bottom: 10),
child: Row(
children: [
Flexible(
Expanded(
flex: 100,
child: Text(
'${post.name}',
textAlign: TextAlign.left,
@ -224,11 +293,8 @@ class Post extends StatelessWidget {
),
if (post.url != null &&
whatType(post.url) == MediaType.other &&
post.thumbnailUrl != null)
post.thumbnailUrl != null) ...[
Spacer(),
if (post.url != null &&
whatType(post.url) == MediaType.other &&
post.thumbnailUrl != null)
InkWell(
onTap: _openLink,
child: Stack(children: [
@ -252,6 +318,7 @@ class Post extends StatelessWidget {
)
]),
)
]
],
),
);
@ -260,11 +327,6 @@ class Post extends StatelessWidget {
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(
@ -281,18 +343,21 @@ class Post extends StatelessWidget {
children: [
Row(children: [
Spacer(),
Text('$url ',
Text('$urlDomain ',
style: theme.textTheme.caption
.apply(fontStyle: FontStyle.italic)),
Icon(Icons.launch, size: 12),
]),
Row(children: [
Flexible(
child: Text(post.embedTitle,
child: Text('${post.embedTitle}',
style: theme.textTheme.subtitle1
.apply(fontWeightDelta: 2)))
]),
Row(children: [Flexible(child: Text(post.embedDescription))]),
if (post.embedDescription != null)
Row(children: [
Flexible(child: Text(post.embedDescription))
]),
],
),
),
@ -353,11 +418,15 @@ class Post extends StatelessWidget {
return Container(
decoration: BoxDecoration(
boxShadow: [BoxShadow(blurRadius: 10, color: Colors.black54)],
color: theme.colorScheme.surface,
color: theme.cardColor,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: InkWell(
onTap: fullPost ? null : () => _goToPost(context),
onTap: fullPost
? null
: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => FullPostPage.fromPostView(
post))), //, instanceUrl, post.id),
child: Column(
children: [
info(),

View File

@ -5,6 +5,8 @@ import 'package:intl/intl.dart';
import 'package:lemmy_api_client/lemmy_api_client.dart';
import 'package:timeago/timeago.dart' as timeago;
import '../util/api_extensions.dart';
import '../util/goto.dart';
import '../util/intl.dart';
import '../util/text_color.dart';
import 'badge.dart';
@ -23,7 +25,7 @@ class UserProfile extends HookWidget {
UserProfile.fromUserView(UserView userView)
: _userView = Future.value(userView),
instanceUrl = userView.actorId.split('/')[2];
instanceUrl = userView.instanceUrl;
@override
Widget build(BuildContext context) {
@ -173,9 +175,21 @@ class UserProfile extends HookWidget {
),
Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Text(
'@${userViewSnap.data?.name ?? ''}@$instanceUrl',
style: theme.textTheme.caption,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'@${userViewSnap.data?.name ?? ''}@',
style: theme.textTheme.caption,
),
InkWell(
onTap: () => goToInstance(context, instanceUrl),
child: Text(
'$instanceUrl',
style: theme.textTheme.caption,
),
)
],
),
),
Padding(