Merge pull request #34 from krawieck/integration-and-polish
Integration and polish
This commit is contained in:
commit
c5e90346fe
|
@ -45,6 +45,8 @@ class MyApp extends StatelessWidget {
|
|||
scaffoldBackgroundColor: maybeAmoledColor,
|
||||
backgroundColor: maybeAmoledColor,
|
||||
canvasColor: maybeAmoledColor,
|
||||
cardColor: maybeAmoledColor,
|
||||
splashColor: maybeAmoledColor,
|
||||
),
|
||||
theme: ThemeData(
|
||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
]
|
||||
],
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
|
|
@ -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];
|
||||
}
|
|
@ -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)));
|
|
@ -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)),
|
||||
|
|
|
@ -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))),
|
||||
),
|
||||
|
|
|
@ -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),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue