Refactor entry/post items/comments into a single widget
This commit is contained in:
parent
f339a9035c
commit
6d1fe886b3
|
@ -2,10 +2,10 @@ import 'dart:convert';
|
|||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/redirect_listen.dart';
|
||||
|
||||
const oauthName = 'Interstellar';
|
||||
const oauthContact = 'contact@kbin.earth';
|
||||
const redirectUri = 'http://localhost:46837';
|
||||
const oauthGrants = ['authorization_code', 'refresh_token'];
|
||||
const oauthScopes = [
|
||||
'read',
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:interstellar/src/api/comments.dart' as api_comments;
|
||||
import 'package:interstellar/src/screens/explore/user_screen.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/action_bar.dart';
|
||||
import 'package:interstellar/src/widgets/display_name.dart';
|
||||
import 'package:interstellar/src/widgets/markdown.dart';
|
||||
import 'package:interstellar/src/widgets/content_item.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class EntryComment extends StatefulWidget {
|
||||
|
@ -19,132 +16,92 @@ class EntryComment extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _EntryCommentState extends State<EntryComment> {
|
||||
bool _isCollapsed = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 8, 0, 1),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
DisplayName(
|
||||
widget.comment.user.username,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => UserScreen(
|
||||
widget.comment.user.userId,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Text(
|
||||
timeDiffFormat(widget.comment.createdAt),
|
||||
style: const TextStyle(fontWeight: FontWeight.w300),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Markdown(widget.comment.body),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ActionBar(
|
||||
boosts: widget.comment.uv,
|
||||
upVotes: widget.comment.favourites,
|
||||
downVotes: widget.comment.dv,
|
||||
isBoosted: widget.comment.userVote == 1,
|
||||
isUpVoted: widget.comment.isFavourited == true,
|
||||
isDownVoted: widget.comment.userVote == -1,
|
||||
isCollapsed: _isCollapsed,
|
||||
onBoost: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
1,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
onUpVote: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putFavorite(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
onDownVote: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
-1,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
onCollapse: widget.comment.childCount > 0
|
||||
? () => setState(() {
|
||||
_isCollapsed = !_isCollapsed;
|
||||
})
|
||||
: null,
|
||||
onReply: (body) async {
|
||||
var newSubComment = await api_comments.postComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
body,
|
||||
widget.comment.entryId,
|
||||
parentCommentId: widget.comment.commentId,
|
||||
);
|
||||
child: ContentItem(
|
||||
body: widget.comment.body,
|
||||
createdAt: widget.comment.createdAt,
|
||||
user: widget.comment.user.username,
|
||||
userIcon: widget.comment.user.avatar?.storageUrl,
|
||||
userIdOnClick: widget.comment.user.userId,
|
||||
boosts: widget.comment.uv,
|
||||
isBoosted: widget.comment.userVote == 1,
|
||||
onBoost: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
1,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
upVotes: widget.comment.favourites,
|
||||
isUpVoted: widget.comment.isFavourited == true,
|
||||
onUpVote: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putFavorite(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
downVotes: widget.comment.dv,
|
||||
isDownVoted: widget.comment.userVote == -1,
|
||||
onDownVote: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
-1,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
showCollapse: true,
|
||||
onReply: (body) async {
|
||||
var newSubComment = await api_comments.postComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
body,
|
||||
widget.comment.entryId,
|
||||
parentCommentId: widget.comment.commentId,
|
||||
);
|
||||
|
||||
var newComment = widget.comment;
|
||||
newComment.childCount += 1;
|
||||
newComment.children!.insert(0, newSubComment);
|
||||
widget.onUpdate(newComment);
|
||||
},
|
||||
onEdit: whenLoggedIn(context, (body) async {
|
||||
var newComment = await api_comments.editComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
body,
|
||||
widget.comment.lang,
|
||||
widget.comment.isAdult
|
||||
);
|
||||
setState(() {
|
||||
widget.comment.body = newComment.body;
|
||||
});
|
||||
}),
|
||||
onDelete: whenLoggedIn(context, () async {
|
||||
await api_comments.deleteComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
);
|
||||
setState(() {
|
||||
widget.comment.body = "deleted";
|
||||
});
|
||||
}),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (!_isCollapsed && widget.comment.childCount > 0)
|
||||
Column(
|
||||
var newComment = widget.comment;
|
||||
newComment.childCount += 1;
|
||||
newComment.children!.insert(0, newSubComment);
|
||||
widget.onUpdate(newComment);
|
||||
},
|
||||
onEdit: whenLoggedIn(context, (body) async {
|
||||
var newComment = await api_comments.editComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
body,
|
||||
widget.comment.lang,
|
||||
widget.comment.isAdult);
|
||||
setState(() {
|
||||
widget.comment.body = newComment.body;
|
||||
});
|
||||
}, matchesUsername: widget.comment.user.username),
|
||||
onDelete: whenLoggedIn(context, () async {
|
||||
await api_comments.deleteComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
);
|
||||
setState(() {
|
||||
widget.comment.body = "deleted";
|
||||
});
|
||||
}, matchesUsername: widget.comment.user.username),
|
||||
child: widget.comment.childCount > 0
|
||||
? Column(
|
||||
children: widget.comment.children!
|
||||
.asMap()
|
||||
.entries
|
||||
|
@ -155,8 +112,7 @@ class _EntryCommentState extends State<EntryComment> {
|
|||
}))
|
||||
.toList(),
|
||||
)
|
||||
],
|
||||
),
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:interstellar/src/api/entries.dart' as api_entries;
|
||||
import 'package:interstellar/src/screens/explore/domain_screen.dart';
|
||||
import 'package:interstellar/src/screens/explore/magazine_screen.dart';
|
||||
import 'package:interstellar/src/screens/explore/user_screen.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/action_bar.dart';
|
||||
import 'package:interstellar/src/widgets/display_name.dart';
|
||||
import 'package:interstellar/src/widgets/markdown.dart';
|
||||
import 'package:interstellar/src/widgets/open_webpage.dart';
|
||||
import 'package:interstellar/src/widgets/content_item.dart';
|
||||
import 'package:interstellar/src/widgets/video.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -30,212 +24,68 @@ class EntryItem extends StatelessWidget {
|
|||
final Future<void> Function()? onDelete;
|
||||
final bool isPreview;
|
||||
|
||||
_onImageClick(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
title: Text(item.title),
|
||||
backgroundColor: const Color(0x66000000),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: InteractiveViewer(
|
||||
child: Image.network(
|
||||
item.image!.storageUrl,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isVideo = item.url != null && isSupportedVideo(item.url!);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (!isPreview && isVideo) VideoPlayer(Uri.parse(item.url!)),
|
||||
if (item.image?.storageUrl != null && !(!isPreview && isVideo))
|
||||
isPreview
|
||||
? (isVideo
|
||||
? Image.network(
|
||||
item.image!.storageUrl,
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: InkWell(
|
||||
onTap: () => _onImageClick(context),
|
||||
child: Image.network(
|
||||
item.image!.storageUrl,
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
))
|
||||
: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height / 2,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _onImageClick(context),
|
||||
child: Image.network(
|
||||
item.image!.storageUrl,
|
||||
),
|
||||
)),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
item.url != null
|
||||
? InkWell(
|
||||
child: Text(
|
||||
item.title,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge!
|
||||
.apply(decoration: TextDecoration.underline),
|
||||
),
|
||||
onTap: () {
|
||||
openWebpage(context, Uri.parse(item.url!));
|
||||
},
|
||||
)
|
||||
: Text(
|
||||
item.title,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
DisplayName(
|
||||
item.magazine.name,
|
||||
icon: item.magazine.icon?.storageUrl,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MagazineScreen(
|
||||
item.magazine.magazineId,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Text(
|
||||
timeDiffFormat(item.createdAt),
|
||||
style: const TextStyle(fontWeight: FontWeight.w300),
|
||||
),
|
||||
),
|
||||
DisplayName(
|
||||
item.user.username,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => UserScreen(item.user.userId),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: IconButton(
|
||||
tooltip: item.domain.name,
|
||||
onPressed: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DomainScreen(
|
||||
item.domain.domainId,
|
||||
data: item.domain,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.public),
|
||||
iconSize: 16,
|
||||
style: const ButtonStyle(
|
||||
minimumSize:
|
||||
MaterialStatePropertyAll(Size.fromRadius(16))),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (item.body != null && item.body!.isNotEmpty)
|
||||
const SizedBox(height: 10),
|
||||
if (item.body != null && item.body!.isNotEmpty)
|
||||
isPreview
|
||||
? Text(
|
||||
item.body!,
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: Markdown(item.body!),
|
||||
const SizedBox(height: 10),
|
||||
ActionBar(
|
||||
boosts: item.uv,
|
||||
upVotes: item.favourites,
|
||||
downVotes: item.dv,
|
||||
isBoosted: item.userVote == 1,
|
||||
isUpVoted: item.isFavourited == true,
|
||||
isDownVoted: item.userVote == -1,
|
||||
onBoost: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_entries.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.entryId,
|
||||
1,
|
||||
));
|
||||
}),
|
||||
onUpVote: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_entries.putFavorite(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.entryId,
|
||||
));
|
||||
}),
|
||||
onDownVote: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_entries.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.entryId,
|
||||
-1,
|
||||
));
|
||||
}),
|
||||
onReply: onReply,
|
||||
onEdit: whenLoggedIn(
|
||||
context,
|
||||
onEdit,
|
||||
matchesUsername: item.user.username,
|
||||
),
|
||||
onDelete: whenLoggedIn(
|
||||
context,
|
||||
onDelete,
|
||||
matchesUsername: item.user.username,
|
||||
),
|
||||
initEdit: () {
|
||||
return item.body;
|
||||
},
|
||||
leadingWidgets: [
|
||||
const Icon(Icons.comment),
|
||||
const SizedBox(width: 4),
|
||||
Text(intFormat(item.numComments)),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
return ContentItem(
|
||||
title: item.title,
|
||||
image: item.image?.storageUrl,
|
||||
link: item.url != null ? Uri.parse(item.url!) : null,
|
||||
video: isVideo ? Uri.parse(item.url!) : null,
|
||||
body: item.body,
|
||||
createdAt: item.createdAt,
|
||||
isPreview: isPreview,
|
||||
showMagazineFirst: true,
|
||||
user: item.user.username,
|
||||
userIcon: item.user.avatar?.storageUrl,
|
||||
userIdOnClick: item.user.userId,
|
||||
magazine: item.magazine.name,
|
||||
magazineIcon: item.magazine.icon?.storageUrl,
|
||||
magazineIdOnClick: item.magazine.magazineId,
|
||||
domain: item.domain.name,
|
||||
domainIdOnClick: item.domain.domainId,
|
||||
boosts: item.uv,
|
||||
isBoosted: item.userVote == 1,
|
||||
onBoost: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_entries.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.entryId,
|
||||
1,
|
||||
));
|
||||
}),
|
||||
upVotes: item.favourites,
|
||||
isUpVoted: item.isFavourited == true,
|
||||
onUpVote: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_entries.putFavorite(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.entryId,
|
||||
));
|
||||
}),
|
||||
downVotes: item.dv,
|
||||
isDownVoted: item.userVote == -1,
|
||||
onDownVote: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_entries.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.entryId,
|
||||
-1,
|
||||
));
|
||||
}),
|
||||
onReply: onReply,
|
||||
onEdit: whenLoggedIn(
|
||||
context,
|
||||
onEdit,
|
||||
matchesUsername: item.user.username,
|
||||
),
|
||||
onDelete: whenLoggedIn(
|
||||
context,
|
||||
onDelete,
|
||||
matchesUsername: item.user.username,
|
||||
),
|
||||
numComments: item.numComments,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:interstellar/src/api/post_comments.dart' as api_comments;
|
||||
import 'package:interstellar/src/screens/explore/user_screen.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/action_bar.dart';
|
||||
import 'package:interstellar/src/widgets/display_name.dart';
|
||||
import 'package:interstellar/src/widgets/markdown.dart';
|
||||
import 'package:interstellar/src/widgets/content_item.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PostComment extends StatefulWidget {
|
||||
|
@ -19,132 +16,92 @@ class PostComment extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _EntryCommentState extends State<PostComment> {
|
||||
bool _isCollapsed = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 8, 0, 1),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
DisplayName(
|
||||
widget.comment.user.username,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => UserScreen(
|
||||
widget.comment.user.userId,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Text(
|
||||
timeDiffFormat(widget.comment.createdAt),
|
||||
style: const TextStyle(fontWeight: FontWeight.w300),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: Markdown(widget.comment.body),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ActionBar(
|
||||
boosts: widget.comment.uv,
|
||||
upVotes: widget.comment.favourites,
|
||||
downVotes: widget.comment.dv,
|
||||
isBoosted: widget.comment.userVote == 1,
|
||||
isUpVoted: widget.comment.isFavourited == true,
|
||||
isDownVoted: widget.comment.userVote == -1,
|
||||
isCollapsed: _isCollapsed,
|
||||
onBoost: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
1,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
onUpVote: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putFavorite(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
onDownVote: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
-1,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
onCollapse: widget.comment.childCount > 0
|
||||
? () => setState(() {
|
||||
_isCollapsed = !_isCollapsed;
|
||||
})
|
||||
: null,
|
||||
onReply: (body) async {
|
||||
var newSubComment = await api_comments.postComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
body,
|
||||
widget.comment.postId,
|
||||
parentCommentId: widget.comment.commentId,
|
||||
);
|
||||
child: ContentItem(
|
||||
body: widget.comment.body,
|
||||
createdAt: widget.comment.createdAt,
|
||||
user: widget.comment.user.username,
|
||||
userIcon: widget.comment.user.avatar?.storageUrl,
|
||||
userIdOnClick: widget.comment.user.userId,
|
||||
boosts: widget.comment.uv,
|
||||
isBoosted: widget.comment.userVote == 1,
|
||||
onBoost: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
1,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
upVotes: widget.comment.favourites,
|
||||
onUpVote: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putFavorite(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
isUpVoted: widget.comment.isFavourited == true,
|
||||
downVotes: widget.comment.dv,
|
||||
isDownVoted: widget.comment.userVote == -1,
|
||||
onDownVote: whenLoggedIn(context, () async {
|
||||
var newValue = await api_comments.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
-1,
|
||||
);
|
||||
newValue.childCount = widget.comment.childCount;
|
||||
newValue.children = widget.comment.children;
|
||||
widget.onUpdate(newValue);
|
||||
}),
|
||||
showCollapse: true,
|
||||
onReply: (body) async {
|
||||
var newSubComment = await api_comments.postComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
body,
|
||||
widget.comment.postId,
|
||||
parentCommentId: widget.comment.commentId,
|
||||
);
|
||||
|
||||
var newComment = widget.comment;
|
||||
newComment.childCount += 1;
|
||||
newComment.children!.insert(0, newSubComment);
|
||||
widget.onUpdate(newComment);
|
||||
},
|
||||
onEdit: whenLoggedIn(context, (body) async {
|
||||
var newComment = await api_comments.editComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
body,
|
||||
widget.comment.lang,
|
||||
widget.comment.isAdult
|
||||
);
|
||||
setState(() {
|
||||
widget.comment.body = newComment.body;
|
||||
});
|
||||
}),
|
||||
onDelete: whenLoggedIn(context, () async {
|
||||
await api_comments.deleteComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
);
|
||||
setState(() {
|
||||
widget.comment.body = "deleted";
|
||||
});
|
||||
}),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
if (!_isCollapsed && widget.comment.childCount > 0)
|
||||
Column(
|
||||
var newComment = widget.comment;
|
||||
newComment.childCount += 1;
|
||||
newComment.children!.insert(0, newSubComment);
|
||||
widget.onUpdate(newComment);
|
||||
},
|
||||
onEdit: whenLoggedIn(context, (body) async {
|
||||
var newComment = await api_comments.editComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
body,
|
||||
widget.comment.lang,
|
||||
widget.comment.isAdult);
|
||||
setState(() {
|
||||
widget.comment.body = newComment.body;
|
||||
});
|
||||
}),
|
||||
onDelete: whenLoggedIn(context, () async {
|
||||
await api_comments.deleteComment(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
widget.comment.commentId,
|
||||
);
|
||||
setState(() {
|
||||
widget.comment.body = "deleted";
|
||||
});
|
||||
}),
|
||||
child: widget.comment.childCount > 0
|
||||
? Column(
|
||||
children: widget.comment.children!
|
||||
.asMap()
|
||||
.entries
|
||||
|
@ -155,8 +112,7 @@ class _EntryCommentState extends State<PostComment> {
|
|||
}))
|
||||
.toList(),
|
||||
)
|
||||
],
|
||||
),
|
||||
: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:interstellar/src/api/posts.dart' as api_posts;
|
||||
import 'package:interstellar/src/screens/explore/magazine_screen.dart';
|
||||
import 'package:interstellar/src/screens/explore/user_screen.dart';
|
||||
import 'package:interstellar/src/screens/settings/settings_controller.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/action_bar.dart';
|
||||
import 'package:interstellar/src/widgets/display_name.dart';
|
||||
import 'package:interstellar/src/widgets/markdown.dart';
|
||||
import 'package:interstellar/src/widgets/content_item.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class PostItem extends StatelessWidget {
|
||||
|
@ -27,165 +23,56 @@ class PostItem extends StatelessWidget {
|
|||
final Future<void> Function()? onDelete;
|
||||
final bool isPreview;
|
||||
|
||||
_onImageClick(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
title: Text(item.user.username),
|
||||
backgroundColor: const Color(0x66000000),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: InteractiveViewer(
|
||||
child: Image.network(
|
||||
item.image!.storageUrl,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (item.image?.storageUrl != null)
|
||||
isPreview
|
||||
? (InkWell(
|
||||
onTap: () => _onImageClick(context),
|
||||
child: Image.network(
|
||||
item.image!.storageUrl,
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
))
|
||||
: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height / 2,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _onImageClick(context),
|
||||
child: Image.network(
|
||||
item.image!.storageUrl,
|
||||
),
|
||||
)),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: [
|
||||
DisplayName(
|
||||
item.user.username,
|
||||
icon: item.user.avatar?.storageUrl,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => UserScreen(item.user.userId),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: Text(
|
||||
timeDiffFormat(item.createdAt),
|
||||
style: const TextStyle(fontWeight: FontWeight.w300),
|
||||
),
|
||||
),
|
||||
DisplayName(
|
||||
item.magazine.name,
|
||||
icon: item.magazine.icon?.storageUrl,
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MagazineScreen(
|
||||
item.magazine.magazineId,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (item.body != null && item.body!.isNotEmpty)
|
||||
const SizedBox(height: 10),
|
||||
if (item.body != null && item.body!.isNotEmpty)
|
||||
isPreview
|
||||
? Text(
|
||||
item.body!,
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: Markdown(item.body!),
|
||||
const SizedBox(height: 10),
|
||||
ActionBar(
|
||||
boosts: item.uv,
|
||||
upVotes: item.favourites,
|
||||
downVotes: item.dv,
|
||||
isBoosted: item.userVote == 1,
|
||||
isUpVoted: item.isFavourited == true,
|
||||
isDownVoted: item.userVote == -1,
|
||||
onBoost: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_posts.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.postId,
|
||||
1,
|
||||
));
|
||||
}),
|
||||
onUpVote: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_posts.putFavorite(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.postId,
|
||||
));
|
||||
}),
|
||||
onDownVote: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_posts.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.postId,
|
||||
-1,
|
||||
));
|
||||
}),
|
||||
onReply: onReply,
|
||||
onEdit: whenLoggedIn(
|
||||
context,
|
||||
onEdit,
|
||||
matchesUsername: item.user.username,
|
||||
),
|
||||
onDelete: whenLoggedIn(
|
||||
context,
|
||||
onDelete,
|
||||
matchesUsername: item.user.username,
|
||||
),
|
||||
initEdit: () {
|
||||
return item.body;
|
||||
},
|
||||
leadingWidgets: [
|
||||
const Icon(Icons.comment),
|
||||
const SizedBox(width: 4),
|
||||
Text(intFormat(item.numComments)),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
return ContentItem(
|
||||
body: item.body,
|
||||
image: item.image?.storageUrl,
|
||||
createdAt: item.createdAt,
|
||||
user: item.user.username,
|
||||
userIcon: item.user.avatar?.storageUrl,
|
||||
userIdOnClick: item.user.userId,
|
||||
boosts: item.uv,
|
||||
isBoosted: item.userVote == 1,
|
||||
onBoost: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_posts.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.postId,
|
||||
1,
|
||||
));
|
||||
}),
|
||||
upVotes: item.favourites,
|
||||
isUpVoted: item.isFavourited == true,
|
||||
onUpVote: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_posts.putFavorite(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.postId,
|
||||
));
|
||||
}),
|
||||
downVotes: item.dv,
|
||||
isDownVoted: item.userVote == -1,
|
||||
onDownVote: whenLoggedIn(context, () async {
|
||||
onUpdate(await api_posts.putVote(
|
||||
context.read<SettingsController>().httpClient,
|
||||
context.read<SettingsController>().instanceHost,
|
||||
item.postId,
|
||||
-1,
|
||||
));
|
||||
}),
|
||||
onReply: onReply,
|
||||
onEdit: whenLoggedIn(
|
||||
context,
|
||||
onEdit,
|
||||
matchesUsername: item.user.username,
|
||||
),
|
||||
onDelete: whenLoggedIn(
|
||||
context,
|
||||
onDelete,
|
||||
matchesUsername: item.user.username,
|
||||
),
|
||||
numComments: item.numComments,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/markdown_editor.dart';
|
||||
|
||||
class ActionBar extends StatefulWidget {
|
||||
final int? boosts;
|
||||
final int? upVotes;
|
||||
final int? downVotes;
|
||||
|
||||
final bool isBoosted;
|
||||
final bool isUpVoted;
|
||||
final bool isDownVoted;
|
||||
final bool isCollapsed;
|
||||
|
||||
final void Function()? onBoost;
|
||||
final void Function()? onUpVote;
|
||||
final void Function()? onDownVote;
|
||||
final void Function()? onCollapse;
|
||||
final Future<void> Function(String)? onReply;
|
||||
final Future<void> Function(String)? onEdit;
|
||||
final void Function()? onDelete;
|
||||
final String? Function()? initEdit;
|
||||
|
||||
final List<Widget>? leadingWidgets;
|
||||
|
||||
const ActionBar({
|
||||
super.key,
|
||||
this.boosts,
|
||||
this.upVotes,
|
||||
this.downVotes,
|
||||
this.isBoosted = false,
|
||||
this.isUpVoted = false,
|
||||
this.isDownVoted = false,
|
||||
this.isCollapsed = false,
|
||||
this.onBoost,
|
||||
this.onUpVote,
|
||||
this.onDownVote,
|
||||
this.onReply,
|
||||
this.onCollapse,
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
this.initEdit,
|
||||
this.leadingWidgets,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ActionBar> createState() => _ActionBarState();
|
||||
}
|
||||
|
||||
class _ActionBarState extends State<ActionBar> {
|
||||
TextEditingController? _replyTextController;
|
||||
TextEditingController? _editTextController;
|
||||
final MenuController _menuController = MenuController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children: [
|
||||
Row(
|
||||
children: <Widget>[
|
||||
...(widget.leadingWidgets ?? []),
|
||||
if (widget.onReply != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.reply),
|
||||
onPressed: () => setState(() {
|
||||
_replyTextController = TextEditingController();
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (widget.onCollapse != null)
|
||||
IconButton(
|
||||
tooltip: widget.isCollapsed ? 'Expand' : 'Collapse',
|
||||
onPressed: widget.onCollapse,
|
||||
icon: widget.isCollapsed
|
||||
? const Icon(Icons.expand_more)
|
||||
: const Icon(Icons.expand_less)),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: MenuAnchor(
|
||||
builder: (BuildContext context, MenuController controller, Widget? child) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {
|
||||
if (_menuController.isOpen) {
|
||||
_menuController.close();
|
||||
} else {
|
||||
_menuController.open();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
controller: _menuController,
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
onPressed: widget.onEdit != null ? () => setState(() {
|
||||
_editTextController = TextEditingController();
|
||||
}) : null,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Edit")
|
||||
),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: widget.onDelete,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Delete")
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
),
|
||||
if (widget.boosts != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.rocket_launch),
|
||||
color: widget.isBoosted ? Colors.purple.shade400 : null,
|
||||
onPressed: widget.onBoost,
|
||||
),
|
||||
Text(intFormat(widget.boosts!))
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.upVotes != null || widget.downVotes != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.upVotes != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_upward),
|
||||
color: widget.isUpVoted ? Colors.green.shade400 : null,
|
||||
onPressed: widget.onUpVote,
|
||||
),
|
||||
Text(intFormat(
|
||||
(widget.upVotes ?? 0) - (widget.downVotes ?? 0))),
|
||||
if (widget.downVotes != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_downward),
|
||||
color: widget.isDownVoted ? Colors.red.shade400 : null,
|
||||
onPressed: widget.onDownVote,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (widget.onReply != null && _replyTextController != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
MarkdownEditor(_replyTextController!),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () => setState(() {
|
||||
_replyTextController!.dispose();
|
||||
_replyTextController = null;
|
||||
}),
|
||||
child: const Text('Cancel')),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
// Wait in case of errors before closing
|
||||
await widget.onReply!(_replyTextController!.text);
|
||||
|
||||
setState(() {
|
||||
_replyTextController!.dispose();
|
||||
_replyTextController = null;
|
||||
});
|
||||
},
|
||||
child: const Text('Submit'))
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.onEdit != null && _editTextController != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
MarkdownEditor(_editTextController!..text = (widget.initEdit != null ? widget.initEdit!() : "")!),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () => setState(() {
|
||||
_editTextController!.dispose();
|
||||
_editTextController = null;
|
||||
}),
|
||||
child: const Text('Cancel')),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
// Wait in case of errors before closing
|
||||
await widget.onEdit!(_editTextController!.text);
|
||||
|
||||
setState(() {
|
||||
_editTextController!.dispose();
|
||||
_editTextController = null;
|
||||
});
|
||||
},
|
||||
child: const Text('Submit'))
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,464 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:interstellar/src/screens/explore/domain_screen.dart';
|
||||
import 'package:interstellar/src/screens/explore/magazine_screen.dart';
|
||||
import 'package:interstellar/src/screens/explore/user_screen.dart';
|
||||
import 'package:interstellar/src/utils/utils.dart';
|
||||
import 'package:interstellar/src/widgets/display_name.dart';
|
||||
import 'package:interstellar/src/widgets/markdown.dart';
|
||||
import 'package:interstellar/src/widgets/markdown_editor.dart';
|
||||
import 'package:interstellar/src/widgets/open_webpage.dart';
|
||||
import 'package:interstellar/src/widgets/video.dart';
|
||||
|
||||
class ContentItem extends StatefulWidget {
|
||||
final String? title;
|
||||
final String? image;
|
||||
final Uri? link;
|
||||
final Uri? video;
|
||||
final String? body;
|
||||
final DateTime? createdAt;
|
||||
|
||||
final bool isPreview;
|
||||
final bool showCollapse;
|
||||
final bool showMagazineFirst;
|
||||
|
||||
final String? user;
|
||||
final String? userIcon;
|
||||
final int? userIdOnClick;
|
||||
|
||||
final String? magazine;
|
||||
final String? magazineIcon;
|
||||
final int? magazineIdOnClick;
|
||||
|
||||
final String? domain;
|
||||
final int? domainIdOnClick;
|
||||
|
||||
final int? boosts;
|
||||
final bool isBoosted;
|
||||
final void Function()? onBoost;
|
||||
|
||||
final int? upVotes;
|
||||
final bool isUpVoted;
|
||||
final void Function()? onUpVote;
|
||||
|
||||
final int? downVotes;
|
||||
final bool isDownVoted;
|
||||
final void Function()? onDownVote;
|
||||
|
||||
final int? numComments;
|
||||
final Future<void> Function(String)? onReply;
|
||||
final Future<void> Function(String)? onEdit;
|
||||
final Future<void> Function()? onDelete;
|
||||
|
||||
final Widget? child;
|
||||
|
||||
const ContentItem(
|
||||
{this.title,
|
||||
this.image,
|
||||
this.link,
|
||||
this.video,
|
||||
this.body,
|
||||
this.createdAt,
|
||||
this.isPreview = false,
|
||||
this.showCollapse = false,
|
||||
this.showMagazineFirst = false,
|
||||
this.user,
|
||||
this.userIcon,
|
||||
this.userIdOnClick,
|
||||
this.magazine,
|
||||
this.magazineIcon,
|
||||
this.magazineIdOnClick,
|
||||
this.domain,
|
||||
this.domainIdOnClick,
|
||||
this.boosts,
|
||||
this.isBoosted = false,
|
||||
this.onBoost,
|
||||
this.upVotes,
|
||||
this.isUpVoted = false,
|
||||
this.onUpVote,
|
||||
this.downVotes,
|
||||
this.isDownVoted = false,
|
||||
this.onDownVote,
|
||||
this.numComments,
|
||||
this.onReply,
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
this.child,
|
||||
super.key});
|
||||
|
||||
@override
|
||||
State<ContentItem> createState() => _ContentItemState();
|
||||
}
|
||||
|
||||
class _ContentItemState extends State<ContentItem> {
|
||||
bool _isCollapsed = false;
|
||||
TextEditingController? _replyTextController;
|
||||
TextEditingController? _editTextController;
|
||||
final MenuController _menuController = MenuController();
|
||||
|
||||
_onImageClick(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
extendBodyBehindAppBar: true,
|
||||
appBar: AppBar(
|
||||
title: widget.title != null ? Text(widget.title!) : null,
|
||||
backgroundColor: const Color(0x66000000),
|
||||
),
|
||||
body: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Expanded(
|
||||
child: InteractiveViewer(
|
||||
child: Image.network(widget.image!),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Widget? userWidget = widget.user != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: DisplayName(
|
||||
widget.user!,
|
||||
icon: widget.userIcon,
|
||||
onTap: widget.userIdOnClick != null
|
||||
? () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => UserScreen(
|
||||
widget.userIdOnClick!,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
)
|
||||
: null;
|
||||
final Widget? magazineWidget = widget.magazine != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: DisplayName(
|
||||
widget.magazine!,
|
||||
icon: widget.magazineIcon,
|
||||
onTap: widget.magazineIdOnClick != null
|
||||
? () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => MagazineScreen(
|
||||
widget.magazineIdOnClick!,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
)
|
||||
: null;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (!widget.isPreview && widget.video != null)
|
||||
VideoPlayer(widget.video!),
|
||||
if (widget.image != null &&
|
||||
!(!widget.isPreview && widget.video != null))
|
||||
widget.isPreview
|
||||
? (widget.video != null
|
||||
? Image.network(
|
||||
widget.image!,
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: InkWell(
|
||||
onTap: () => _onImageClick(context),
|
||||
child: Image.network(
|
||||
widget.image!,
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
))
|
||||
: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height / 2,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: () => _onImageClick(context),
|
||||
child: Image.network(widget.image!),
|
||||
)),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (widget.title != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
child: widget.link != null
|
||||
? InkWell(
|
||||
child: Text(
|
||||
widget.title!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge!
|
||||
.apply(decoration: TextDecoration.underline),
|
||||
),
|
||||
onTap: () {
|
||||
openWebpage(context, widget.link!);
|
||||
},
|
||||
)
|
||||
: Text(
|
||||
widget.title!,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if (!widget.showMagazineFirst && userWidget != null)
|
||||
userWidget,
|
||||
if (widget.showMagazineFirst && magazineWidget != null)
|
||||
magazineWidget,
|
||||
if (widget.createdAt != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: Text(
|
||||
timeDiffFormat(widget.createdAt!),
|
||||
style: const TextStyle(fontWeight: FontWeight.w300),
|
||||
),
|
||||
),
|
||||
if (widget.showMagazineFirst && userWidget != null)
|
||||
userWidget,
|
||||
if (!widget.showMagazineFirst && magazineWidget != null)
|
||||
magazineWidget,
|
||||
if (widget.domain != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: IconButton(
|
||||
tooltip: widget.domain,
|
||||
onPressed: widget.domainIdOnClick != null
|
||||
? () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DomainScreen(
|
||||
widget.domainIdOnClick!,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
icon: const Icon(Icons.public),
|
||||
iconSize: 16,
|
||||
style: const ButtonStyle(
|
||||
minimumSize:
|
||||
MaterialStatePropertyAll(Size.fromRadius(16))),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.body != null) const SizedBox(height: 10),
|
||||
if (widget.body != null)
|
||||
widget.isPreview
|
||||
? Text(
|
||||
widget.body!,
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: Markdown(widget.body!),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
if (widget.numComments != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.comment),
|
||||
const SizedBox(width: 4),
|
||||
Text(intFormat(widget.numComments!))
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.onReply != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 12),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.reply),
|
||||
onPressed: () => setState(() {
|
||||
_replyTextController = TextEditingController();
|
||||
}),
|
||||
),
|
||||
),
|
||||
if (widget.showCollapse && widget.child != null)
|
||||
IconButton(
|
||||
tooltip: _isCollapsed ? 'Expand' : 'Collapse',
|
||||
onPressed: () => setState(() {
|
||||
_isCollapsed = !_isCollapsed;
|
||||
}),
|
||||
icon: _isCollapsed
|
||||
? const Icon(Icons.expand_more)
|
||||
: const Icon(Icons.expand_less)),
|
||||
const Spacer(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: MenuAnchor(
|
||||
builder: (BuildContext context,
|
||||
MenuController controller, Widget? child) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
onPressed: () {
|
||||
if (_menuController.isOpen) {
|
||||
_menuController.close();
|
||||
} else {
|
||||
_menuController.open();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
controller: _menuController,
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
onPressed: widget.onEdit != null
|
||||
? () => setState(() {
|
||||
_editTextController =
|
||||
TextEditingController();
|
||||
})
|
||||
: null,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Edit")),
|
||||
),
|
||||
MenuItemButton(
|
||||
onPressed: widget.onDelete,
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Delete")),
|
||||
),
|
||||
]),
|
||||
),
|
||||
if (widget.boosts != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.rocket_launch),
|
||||
color: widget.isBoosted
|
||||
? Colors.purple.shade400
|
||||
: null,
|
||||
onPressed: widget.onBoost,
|
||||
),
|
||||
Text(intFormat(widget.boosts!))
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.upVotes != null || widget.downVotes != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
if (widget.upVotes != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_upward),
|
||||
color: widget.isUpVoted
|
||||
? Colors.green.shade400
|
||||
: null,
|
||||
onPressed: widget.onUpVote,
|
||||
),
|
||||
Text(intFormat(
|
||||
(widget.upVotes ?? 0) - (widget.downVotes ?? 0))),
|
||||
if (widget.downVotes != null)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_downward),
|
||||
color: widget.isDownVoted
|
||||
? Colors.red.shade400
|
||||
: null,
|
||||
onPressed: widget.onDownVote,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
if (widget.onReply != null && _replyTextController != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
MarkdownEditor(_replyTextController!),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () => setState(() {
|
||||
_replyTextController!.dispose();
|
||||
_replyTextController = null;
|
||||
}),
|
||||
child: const Text('Cancel')),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
// Wait in case of errors before closing
|
||||
await widget
|
||||
.onReply!(_replyTextController!.text);
|
||||
|
||||
setState(() {
|
||||
_replyTextController!.dispose();
|
||||
_replyTextController = null;
|
||||
});
|
||||
},
|
||||
child: const Text('Submit'))
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.onEdit != null && _editTextController != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
children: [
|
||||
MarkdownEditor(
|
||||
_editTextController!..text = widget.body ?? '',
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
OutlinedButton(
|
||||
onPressed: () => setState(() {
|
||||
_editTextController!.dispose();
|
||||
_editTextController = null;
|
||||
}),
|
||||
child: const Text('Cancel')),
|
||||
const SizedBox(width: 8),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
// Wait in case of errors before closing
|
||||
await widget.onEdit!(_editTextController!.text);
|
||||
|
||||
setState(() {
|
||||
_editTextController!.dispose();
|
||||
_editTextController = null;
|
||||
});
|
||||
},
|
||||
child: const Text('Submit'))
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
if (widget.child != null && !_isCollapsed)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: widget.child,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ import 'package:webview_flutter/webview_flutter.dart';
|
|||
|
||||
const _redirectHost = 'localhost';
|
||||
const _redirectPort = 46837;
|
||||
const _redirectUri = 'http://$_redirectHost:$_redirectPort';
|
||||
const redirectUri = 'http://$_redirectHost:$_redirectPort';
|
||||
|
||||
class RedirectListener extends StatefulWidget {
|
||||
final Uri initUri;
|
||||
|
@ -52,7 +52,7 @@ class _RedirectListenerState extends State<RedirectListener> {
|
|||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
onNavigationRequest: (NavigationRequest request) {
|
||||
if (request.url.startsWith(_redirectUri)) {
|
||||
if (request.url.startsWith(redirectUri)) {
|
||||
WebViewCookieManager().clearCookies();
|
||||
Navigator.pop(context, Uri.parse(request.url).queryParameters);
|
||||
return NavigationDecision.prevent;
|
||||
|
|
Loading…
Reference in New Issue