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:http/http.dart' as http;
|
||||||
import 'package:interstellar/src/utils/utils.dart';
|
import 'package:interstellar/src/utils/utils.dart';
|
||||||
|
import 'package:interstellar/src/widgets/redirect_listen.dart';
|
||||||
|
|
||||||
const oauthName = 'Interstellar';
|
const oauthName = 'Interstellar';
|
||||||
const oauthContact = 'contact@kbin.earth';
|
const oauthContact = 'contact@kbin.earth';
|
||||||
const redirectUri = 'http://localhost:46837';
|
|
||||||
const oauthGrants = ['authorization_code', 'refresh_token'];
|
const oauthGrants = ['authorization_code', 'refresh_token'];
|
||||||
const oauthScopes = [
|
const oauthScopes = [
|
||||||
'read',
|
'read',
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:interstellar/src/api/comments.dart' as api_comments;
|
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/screens/settings/settings_controller.dart';
|
||||||
import 'package:interstellar/src/utils/utils.dart';
|
import 'package:interstellar/src/utils/utils.dart';
|
||||||
import 'package:interstellar/src/widgets/action_bar.dart';
|
import 'package:interstellar/src/widgets/content_item.dart';
|
||||||
import 'package:interstellar/src/widgets/display_name.dart';
|
|
||||||
import 'package:interstellar/src/widgets/markdown.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class EntryComment extends StatefulWidget {
|
class EntryComment extends StatefulWidget {
|
||||||
|
@ -19,132 +16,92 @@ class EntryComment extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EntryCommentState extends State<EntryComment> {
|
class _EntryCommentState extends State<EntryComment> {
|
||||||
bool _isCollapsed = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
child: Padding(
|
child: ContentItem(
|
||||||
padding: const EdgeInsets.fromLTRB(8, 8, 0, 1),
|
body: widget.comment.body,
|
||||||
child: Column(
|
createdAt: widget.comment.createdAt,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
user: widget.comment.user.username,
|
||||||
children: [
|
userIcon: widget.comment.user.avatar?.storageUrl,
|
||||||
Row(
|
userIdOnClick: widget.comment.user.userId,
|
||||||
children: [
|
boosts: widget.comment.uv,
|
||||||
DisplayName(
|
isBoosted: widget.comment.userVote == 1,
|
||||||
widget.comment.user.username,
|
onBoost: whenLoggedIn(context, () async {
|
||||||
onTap: () {
|
var newValue = await api_comments.putVote(
|
||||||
Navigator.of(context).push(
|
context.read<SettingsController>().httpClient,
|
||||||
MaterialPageRoute(
|
context.read<SettingsController>().instanceHost,
|
||||||
builder: (context) => UserScreen(
|
widget.comment.commentId,
|
||||||
widget.comment.user.userId,
|
1,
|
||||||
),
|
);
|
||||||
),
|
newValue.childCount = widget.comment.childCount;
|
||||||
);
|
newValue.children = widget.comment.children;
|
||||||
},
|
widget.onUpdate(newValue);
|
||||||
),
|
}),
|
||||||
Padding(
|
upVotes: widget.comment.favourites,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
isUpVoted: widget.comment.isFavourited == true,
|
||||||
child: Text(
|
onUpVote: whenLoggedIn(context, () async {
|
||||||
timeDiffFormat(widget.comment.createdAt),
|
var newValue = await api_comments.putFavorite(
|
||||||
style: const TextStyle(fontWeight: FontWeight.w300),
|
context.read<SettingsController>().httpClient,
|
||||||
),
|
context.read<SettingsController>().instanceHost,
|
||||||
)
|
widget.comment.commentId,
|
||||||
],
|
);
|
||||||
),
|
newValue.childCount = widget.comment.childCount;
|
||||||
Padding(
|
newValue.children = widget.comment.children;
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
widget.onUpdate(newValue);
|
||||||
child: Markdown(widget.comment.body),
|
}),
|
||||||
),
|
downVotes: widget.comment.dv,
|
||||||
Padding(
|
isDownVoted: widget.comment.userVote == -1,
|
||||||
padding: const EdgeInsets.all(8.0),
|
onDownVote: whenLoggedIn(context, () async {
|
||||||
child: ActionBar(
|
var newValue = await api_comments.putVote(
|
||||||
boosts: widget.comment.uv,
|
context.read<SettingsController>().httpClient,
|
||||||
upVotes: widget.comment.favourites,
|
context.read<SettingsController>().instanceHost,
|
||||||
downVotes: widget.comment.dv,
|
widget.comment.commentId,
|
||||||
isBoosted: widget.comment.userVote == 1,
|
-1,
|
||||||
isUpVoted: widget.comment.isFavourited == true,
|
);
|
||||||
isDownVoted: widget.comment.userVote == -1,
|
newValue.childCount = widget.comment.childCount;
|
||||||
isCollapsed: _isCollapsed,
|
newValue.children = widget.comment.children;
|
||||||
onBoost: whenLoggedIn(context, () async {
|
widget.onUpdate(newValue);
|
||||||
var newValue = await api_comments.putVote(
|
}),
|
||||||
context.read<SettingsController>().httpClient,
|
showCollapse: true,
|
||||||
context.read<SettingsController>().instanceHost,
|
onReply: (body) async {
|
||||||
widget.comment.commentId,
|
var newSubComment = await api_comments.postComment(
|
||||||
1,
|
context.read<SettingsController>().httpClient,
|
||||||
);
|
context.read<SettingsController>().instanceHost,
|
||||||
newValue.childCount = widget.comment.childCount;
|
body,
|
||||||
newValue.children = widget.comment.children;
|
widget.comment.entryId,
|
||||||
widget.onUpdate(newValue);
|
parentCommentId: widget.comment.commentId,
|
||||||
}),
|
);
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
var newComment = widget.comment;
|
var newComment = widget.comment;
|
||||||
newComment.childCount += 1;
|
newComment.childCount += 1;
|
||||||
newComment.children!.insert(0, newSubComment);
|
newComment.children!.insert(0, newSubComment);
|
||||||
widget.onUpdate(newComment);
|
widget.onUpdate(newComment);
|
||||||
},
|
},
|
||||||
onEdit: whenLoggedIn(context, (body) async {
|
onEdit: whenLoggedIn(context, (body) async {
|
||||||
var newComment = await api_comments.editComment(
|
var newComment = await api_comments.editComment(
|
||||||
context.read<SettingsController>().httpClient,
|
context.read<SettingsController>().httpClient,
|
||||||
context.read<SettingsController>().instanceHost,
|
context.read<SettingsController>().instanceHost,
|
||||||
widget.comment.commentId,
|
widget.comment.commentId,
|
||||||
body,
|
body,
|
||||||
widget.comment.lang,
|
widget.comment.lang,
|
||||||
widget.comment.isAdult
|
widget.comment.isAdult);
|
||||||
);
|
setState(() {
|
||||||
setState(() {
|
widget.comment.body = newComment.body;
|
||||||
widget.comment.body = newComment.body;
|
});
|
||||||
});
|
}, matchesUsername: widget.comment.user.username),
|
||||||
}),
|
onDelete: whenLoggedIn(context, () async {
|
||||||
onDelete: whenLoggedIn(context, () async {
|
await api_comments.deleteComment(
|
||||||
await api_comments.deleteComment(
|
context.read<SettingsController>().httpClient,
|
||||||
context.read<SettingsController>().httpClient,
|
context.read<SettingsController>().instanceHost,
|
||||||
context.read<SettingsController>().instanceHost,
|
widget.comment.commentId,
|
||||||
widget.comment.commentId,
|
);
|
||||||
);
|
setState(() {
|
||||||
setState(() {
|
widget.comment.body = "deleted";
|
||||||
widget.comment.body = "deleted";
|
});
|
||||||
});
|
}, matchesUsername: widget.comment.user.username),
|
||||||
}),
|
child: widget.comment.childCount > 0
|
||||||
),
|
? Column(
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
if (!_isCollapsed && widget.comment.childCount > 0)
|
|
||||||
Column(
|
|
||||||
children: widget.comment.children!
|
children: widget.comment.children!
|
||||||
.asMap()
|
.asMap()
|
||||||
.entries
|
.entries
|
||||||
|
@ -155,8 +112,7 @@ class _EntryCommentState extends State<EntryComment> {
|
||||||
}))
|
}))
|
||||||
.toList(),
|
.toList(),
|
||||||
)
|
)
|
||||||
],
|
: null,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:interstellar/src/api/entries.dart' as api_entries;
|
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/screens/settings/settings_controller.dart';
|
||||||
import 'package:interstellar/src/utils/utils.dart';
|
import 'package:interstellar/src/utils/utils.dart';
|
||||||
import 'package:interstellar/src/widgets/action_bar.dart';
|
import 'package:interstellar/src/widgets/content_item.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/video.dart';
|
import 'package:interstellar/src/widgets/video.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
@ -30,212 +24,68 @@ class EntryItem extends StatelessWidget {
|
||||||
final Future<void> Function()? onDelete;
|
final Future<void> Function()? onDelete;
|
||||||
final bool isPreview;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isVideo = item.url != null && isSupportedVideo(item.url!);
|
final isVideo = item.url != null && isSupportedVideo(item.url!);
|
||||||
|
|
||||||
return Column(
|
return ContentItem(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
title: item.title,
|
||||||
children: <Widget>[
|
image: item.image?.storageUrl,
|
||||||
if (!isPreview && isVideo) VideoPlayer(Uri.parse(item.url!)),
|
link: item.url != null ? Uri.parse(item.url!) : null,
|
||||||
if (item.image?.storageUrl != null && !(!isPreview && isVideo))
|
video: isVideo ? Uri.parse(item.url!) : null,
|
||||||
isPreview
|
body: item.body,
|
||||||
? (isVideo
|
createdAt: item.createdAt,
|
||||||
? Image.network(
|
isPreview: isPreview,
|
||||||
item.image!.storageUrl,
|
showMagazineFirst: true,
|
||||||
height: 160,
|
user: item.user.username,
|
||||||
width: double.infinity,
|
userIcon: item.user.avatar?.storageUrl,
|
||||||
fit: BoxFit.cover,
|
userIdOnClick: item.user.userId,
|
||||||
)
|
magazine: item.magazine.name,
|
||||||
: InkWell(
|
magazineIcon: item.magazine.icon?.storageUrl,
|
||||||
onTap: () => _onImageClick(context),
|
magazineIdOnClick: item.magazine.magazineId,
|
||||||
child: Image.network(
|
domain: item.domain.name,
|
||||||
item.image!.storageUrl,
|
domainIdOnClick: item.domain.domainId,
|
||||||
height: 160,
|
boosts: item.uv,
|
||||||
width: double.infinity,
|
isBoosted: item.userVote == 1,
|
||||||
fit: BoxFit.cover,
|
onBoost: whenLoggedIn(context, () async {
|
||||||
),
|
onUpdate(await api_entries.putVote(
|
||||||
))
|
context.read<SettingsController>().httpClient,
|
||||||
: Container(
|
context.read<SettingsController>().instanceHost,
|
||||||
constraints: BoxConstraints(
|
item.entryId,
|
||||||
maxHeight: MediaQuery.of(context).size.height / 2,
|
1,
|
||||||
),
|
));
|
||||||
child: InkWell(
|
}),
|
||||||
onTap: () => _onImageClick(context),
|
upVotes: item.favourites,
|
||||||
child: Image.network(
|
isUpVoted: item.isFavourited == true,
|
||||||
item.image!.storageUrl,
|
onUpVote: whenLoggedIn(context, () async {
|
||||||
),
|
onUpdate(await api_entries.putFavorite(
|
||||||
)),
|
context.read<SettingsController>().httpClient,
|
||||||
Container(
|
context.read<SettingsController>().instanceHost,
|
||||||
padding: const EdgeInsets.all(16),
|
item.entryId,
|
||||||
child: Column(
|
));
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
}),
|
||||||
children: <Widget>[
|
downVotes: item.dv,
|
||||||
item.url != null
|
isDownVoted: item.userVote == -1,
|
||||||
? InkWell(
|
onDownVote: whenLoggedIn(context, () async {
|
||||||
child: Text(
|
onUpdate(await api_entries.putVote(
|
||||||
item.title,
|
context.read<SettingsController>().httpClient,
|
||||||
style: Theme.of(context)
|
context.read<SettingsController>().instanceHost,
|
||||||
.textTheme
|
item.entryId,
|
||||||
.titleLarge!
|
-1,
|
||||||
.apply(decoration: TextDecoration.underline),
|
));
|
||||||
),
|
}),
|
||||||
onTap: () {
|
onReply: onReply,
|
||||||
openWebpage(context, Uri.parse(item.url!));
|
onEdit: whenLoggedIn(
|
||||||
},
|
context,
|
||||||
)
|
onEdit,
|
||||||
: Text(
|
matchesUsername: item.user.username,
|
||||||
item.title,
|
),
|
||||||
style: Theme.of(context).textTheme.titleLarge,
|
onDelete: whenLoggedIn(
|
||||||
),
|
context,
|
||||||
const SizedBox(height: 10),
|
onDelete,
|
||||||
Row(
|
matchesUsername: item.user.username,
|
||||||
children: [
|
),
|
||||||
DisplayName(
|
numComments: item.numComments,
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:interstellar/src/api/post_comments.dart' as api_comments;
|
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/screens/settings/settings_controller.dart';
|
||||||
import 'package:interstellar/src/utils/utils.dart';
|
import 'package:interstellar/src/utils/utils.dart';
|
||||||
import 'package:interstellar/src/widgets/action_bar.dart';
|
import 'package:interstellar/src/widgets/content_item.dart';
|
||||||
import 'package:interstellar/src/widgets/display_name.dart';
|
|
||||||
import 'package:interstellar/src/widgets/markdown.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class PostComment extends StatefulWidget {
|
class PostComment extends StatefulWidget {
|
||||||
|
@ -19,132 +16,92 @@ class PostComment extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EntryCommentState extends State<PostComment> {
|
class _EntryCommentState extends State<PostComment> {
|
||||||
bool _isCollapsed = false;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
child: Padding(
|
child: ContentItem(
|
||||||
padding: const EdgeInsets.fromLTRB(8, 8, 0, 1),
|
body: widget.comment.body,
|
||||||
child: Column(
|
createdAt: widget.comment.createdAt,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
user: widget.comment.user.username,
|
||||||
children: [
|
userIcon: widget.comment.user.avatar?.storageUrl,
|
||||||
Row(
|
userIdOnClick: widget.comment.user.userId,
|
||||||
children: [
|
boosts: widget.comment.uv,
|
||||||
DisplayName(
|
isBoosted: widget.comment.userVote == 1,
|
||||||
widget.comment.user.username,
|
onBoost: whenLoggedIn(context, () async {
|
||||||
onTap: () {
|
var newValue = await api_comments.putVote(
|
||||||
Navigator.of(context).push(
|
context.read<SettingsController>().httpClient,
|
||||||
MaterialPageRoute(
|
context.read<SettingsController>().instanceHost,
|
||||||
builder: (context) => UserScreen(
|
widget.comment.commentId,
|
||||||
widget.comment.user.userId,
|
1,
|
||||||
),
|
);
|
||||||
),
|
newValue.childCount = widget.comment.childCount;
|
||||||
);
|
newValue.children = widget.comment.children;
|
||||||
},
|
widget.onUpdate(newValue);
|
||||||
),
|
}),
|
||||||
Padding(
|
upVotes: widget.comment.favourites,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
onUpVote: whenLoggedIn(context, () async {
|
||||||
child: Text(
|
var newValue = await api_comments.putFavorite(
|
||||||
timeDiffFormat(widget.comment.createdAt),
|
context.read<SettingsController>().httpClient,
|
||||||
style: const TextStyle(fontWeight: FontWeight.w300),
|
context.read<SettingsController>().instanceHost,
|
||||||
),
|
widget.comment.commentId,
|
||||||
)
|
);
|
||||||
],
|
newValue.childCount = widget.comment.childCount;
|
||||||
),
|
newValue.children = widget.comment.children;
|
||||||
Padding(
|
widget.onUpdate(newValue);
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
}),
|
||||||
child: Markdown(widget.comment.body),
|
isUpVoted: widget.comment.isFavourited == true,
|
||||||
),
|
downVotes: widget.comment.dv,
|
||||||
Padding(
|
isDownVoted: widget.comment.userVote == -1,
|
||||||
padding: const EdgeInsets.all(8.0),
|
onDownVote: whenLoggedIn(context, () async {
|
||||||
child: ActionBar(
|
var newValue = await api_comments.putVote(
|
||||||
boosts: widget.comment.uv,
|
context.read<SettingsController>().httpClient,
|
||||||
upVotes: widget.comment.favourites,
|
context.read<SettingsController>().instanceHost,
|
||||||
downVotes: widget.comment.dv,
|
widget.comment.commentId,
|
||||||
isBoosted: widget.comment.userVote == 1,
|
-1,
|
||||||
isUpVoted: widget.comment.isFavourited == true,
|
);
|
||||||
isDownVoted: widget.comment.userVote == -1,
|
newValue.childCount = widget.comment.childCount;
|
||||||
isCollapsed: _isCollapsed,
|
newValue.children = widget.comment.children;
|
||||||
onBoost: whenLoggedIn(context, () async {
|
widget.onUpdate(newValue);
|
||||||
var newValue = await api_comments.putVote(
|
}),
|
||||||
context.read<SettingsController>().httpClient,
|
showCollapse: true,
|
||||||
context.read<SettingsController>().instanceHost,
|
onReply: (body) async {
|
||||||
widget.comment.commentId,
|
var newSubComment = await api_comments.postComment(
|
||||||
1,
|
context.read<SettingsController>().httpClient,
|
||||||
);
|
context.read<SettingsController>().instanceHost,
|
||||||
newValue.childCount = widget.comment.childCount;
|
body,
|
||||||
newValue.children = widget.comment.children;
|
widget.comment.postId,
|
||||||
widget.onUpdate(newValue);
|
parentCommentId: widget.comment.commentId,
|
||||||
}),
|
);
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
var newComment = widget.comment;
|
var newComment = widget.comment;
|
||||||
newComment.childCount += 1;
|
newComment.childCount += 1;
|
||||||
newComment.children!.insert(0, newSubComment);
|
newComment.children!.insert(0, newSubComment);
|
||||||
widget.onUpdate(newComment);
|
widget.onUpdate(newComment);
|
||||||
},
|
},
|
||||||
onEdit: whenLoggedIn(context, (body) async {
|
onEdit: whenLoggedIn(context, (body) async {
|
||||||
var newComment = await api_comments.editComment(
|
var newComment = await api_comments.editComment(
|
||||||
context.read<SettingsController>().httpClient,
|
context.read<SettingsController>().httpClient,
|
||||||
context.read<SettingsController>().instanceHost,
|
context.read<SettingsController>().instanceHost,
|
||||||
widget.comment.commentId,
|
widget.comment.commentId,
|
||||||
body,
|
body,
|
||||||
widget.comment.lang,
|
widget.comment.lang,
|
||||||
widget.comment.isAdult
|
widget.comment.isAdult);
|
||||||
);
|
setState(() {
|
||||||
setState(() {
|
widget.comment.body = newComment.body;
|
||||||
widget.comment.body = newComment.body;
|
});
|
||||||
});
|
}),
|
||||||
}),
|
onDelete: whenLoggedIn(context, () async {
|
||||||
onDelete: whenLoggedIn(context, () async {
|
await api_comments.deleteComment(
|
||||||
await api_comments.deleteComment(
|
context.read<SettingsController>().httpClient,
|
||||||
context.read<SettingsController>().httpClient,
|
context.read<SettingsController>().instanceHost,
|
||||||
context.read<SettingsController>().instanceHost,
|
widget.comment.commentId,
|
||||||
widget.comment.commentId,
|
);
|
||||||
);
|
setState(() {
|
||||||
setState(() {
|
widget.comment.body = "deleted";
|
||||||
widget.comment.body = "deleted";
|
});
|
||||||
});
|
}),
|
||||||
}),
|
child: widget.comment.childCount > 0
|
||||||
),
|
? Column(
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
if (!_isCollapsed && widget.comment.childCount > 0)
|
|
||||||
Column(
|
|
||||||
children: widget.comment.children!
|
children: widget.comment.children!
|
||||||
.asMap()
|
.asMap()
|
||||||
.entries
|
.entries
|
||||||
|
@ -155,8 +112,7 @@ class _EntryCommentState extends State<PostComment> {
|
||||||
}))
|
}))
|
||||||
.toList(),
|
.toList(),
|
||||||
)
|
)
|
||||||
],
|
: null,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:interstellar/src/api/posts.dart' as api_posts;
|
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/screens/settings/settings_controller.dart';
|
||||||
import 'package:interstellar/src/utils/utils.dart';
|
import 'package:interstellar/src/utils/utils.dart';
|
||||||
import 'package:interstellar/src/widgets/action_bar.dart';
|
import 'package:interstellar/src/widgets/content_item.dart';
|
||||||
import 'package:interstellar/src/widgets/display_name.dart';
|
|
||||||
import 'package:interstellar/src/widgets/markdown.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class PostItem extends StatelessWidget {
|
class PostItem extends StatelessWidget {
|
||||||
|
@ -27,165 +23,56 @@ class PostItem extends StatelessWidget {
|
||||||
final Future<void> Function()? onDelete;
|
final Future<void> Function()? onDelete;
|
||||||
final bool isPreview;
|
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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(
|
return ContentItem(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
body: item.body,
|
||||||
children: <Widget>[
|
image: item.image?.storageUrl,
|
||||||
if (item.image?.storageUrl != null)
|
createdAt: item.createdAt,
|
||||||
isPreview
|
user: item.user.username,
|
||||||
? (InkWell(
|
userIcon: item.user.avatar?.storageUrl,
|
||||||
onTap: () => _onImageClick(context),
|
userIdOnClick: item.user.userId,
|
||||||
child: Image.network(
|
boosts: item.uv,
|
||||||
item.image!.storageUrl,
|
isBoosted: item.userVote == 1,
|
||||||
height: 160,
|
onBoost: whenLoggedIn(context, () async {
|
||||||
width: double.infinity,
|
onUpdate(await api_posts.putVote(
|
||||||
fit: BoxFit.cover,
|
context.read<SettingsController>().httpClient,
|
||||||
),
|
context.read<SettingsController>().instanceHost,
|
||||||
))
|
item.postId,
|
||||||
: Container(
|
1,
|
||||||
constraints: BoxConstraints(
|
));
|
||||||
maxHeight: MediaQuery.of(context).size.height / 2,
|
}),
|
||||||
),
|
upVotes: item.favourites,
|
||||||
child: InkWell(
|
isUpVoted: item.isFavourited == true,
|
||||||
onTap: () => _onImageClick(context),
|
onUpVote: whenLoggedIn(context, () async {
|
||||||
child: Image.network(
|
onUpdate(await api_posts.putFavorite(
|
||||||
item.image!.storageUrl,
|
context.read<SettingsController>().httpClient,
|
||||||
),
|
context.read<SettingsController>().instanceHost,
|
||||||
)),
|
item.postId,
|
||||||
Container(
|
));
|
||||||
padding: const EdgeInsets.all(16),
|
}),
|
||||||
child: Column(
|
downVotes: item.dv,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
isDownVoted: item.userVote == -1,
|
||||||
children: <Widget>[
|
onDownVote: whenLoggedIn(context, () async {
|
||||||
const SizedBox(height: 10),
|
onUpdate(await api_posts.putVote(
|
||||||
Row(
|
context.read<SettingsController>().httpClient,
|
||||||
children: [
|
context.read<SettingsController>().instanceHost,
|
||||||
DisplayName(
|
item.postId,
|
||||||
item.user.username,
|
-1,
|
||||||
icon: item.user.avatar?.storageUrl,
|
));
|
||||||
onTap: () {
|
}),
|
||||||
Navigator.of(context).push(
|
onReply: onReply,
|
||||||
MaterialPageRoute(
|
onEdit: whenLoggedIn(
|
||||||
builder: (context) => UserScreen(item.user.userId),
|
context,
|
||||||
),
|
onEdit,
|
||||||
);
|
matchesUsername: item.user.username,
|
||||||
},
|
),
|
||||||
),
|
onDelete: whenLoggedIn(
|
||||||
Padding(
|
context,
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
onDelete,
|
||||||
child: Text(
|
matchesUsername: item.user.username,
|
||||||
timeDiffFormat(item.createdAt),
|
),
|
||||||
style: const TextStyle(fontWeight: FontWeight.w300),
|
numComments: item.numComments,
|
||||||
),
|
|
||||||
),
|
|
||||||
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),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 _redirectHost = 'localhost';
|
||||||
const _redirectPort = 46837;
|
const _redirectPort = 46837;
|
||||||
const _redirectUri = 'http://$_redirectHost:$_redirectPort';
|
const redirectUri = 'http://$_redirectHost:$_redirectPort';
|
||||||
|
|
||||||
class RedirectListener extends StatefulWidget {
|
class RedirectListener extends StatefulWidget {
|
||||||
final Uri initUri;
|
final Uri initUri;
|
||||||
|
@ -52,7 +52,7 @@ class _RedirectListenerState extends State<RedirectListener> {
|
||||||
..setNavigationDelegate(
|
..setNavigationDelegate(
|
||||||
NavigationDelegate(
|
NavigationDelegate(
|
||||||
onNavigationRequest: (NavigationRequest request) {
|
onNavigationRequest: (NavigationRequest request) {
|
||||||
if (request.url.startsWith(_redirectUri)) {
|
if (request.url.startsWith(redirectUri)) {
|
||||||
WebViewCookieManager().clearCookies();
|
WebViewCookieManager().clearCookies();
|
||||||
Navigator.pop(context, Uri.parse(request.url).queryParameters);
|
Navigator.pop(context, Uri.parse(request.url).queryParameters);
|
||||||
return NavigationDecision.prevent;
|
return NavigationDecision.prevent;
|
||||||
|
|
Loading…
Reference in New Issue