Add post apperance options along with layout presets, mark NSFW and OC posts (#41)
* Initial work on compact mode, mark NSFW and OC posts * Change content title style * Split compact mode into separate settings * Add limit title preview option * Update preset snackbar text
This commit is contained in:
parent
13d3010976
commit
8e0811796c
|
@ -54,7 +54,6 @@ class CommentModel with _$CommentModel {
|
|||
required int? boosts,
|
||||
required int? myVote,
|
||||
required bool? myBoost,
|
||||
required bool? isAdult,
|
||||
required DateTime createdAt,
|
||||
required DateTime? editedAt,
|
||||
required List<CommentModel>? children,
|
||||
|
@ -82,7 +81,6 @@ class CommentModel with _$CommentModel {
|
|||
? 1
|
||||
: ((json['userVote'] as int?) == -1 ? -1 : 0),
|
||||
myBoost: (json['userVote'] as int?) == 1,
|
||||
isAdult: json['isAdult'] as bool,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
editedAt: optionalDateTime(json['editedAt'] as String?),
|
||||
children: (json['children'] as List<dynamic>)
|
||||
|
@ -136,7 +134,6 @@ class CommentModel with _$CommentModel {
|
|||
boosts: null,
|
||||
myVote: json['my_vote'] as int?,
|
||||
myBoost: null,
|
||||
isAdult: null,
|
||||
createdAt: DateTime.parse(lemmyComment['published'] as String),
|
||||
editedAt: optionalDateTime(json['updated'] as String?),
|
||||
children: children,
|
||||
|
|
|
@ -61,8 +61,8 @@ class PostModel with _$PostModel {
|
|||
required int? boosts,
|
||||
required int? myVote,
|
||||
required bool? myBoost,
|
||||
required bool? isOc,
|
||||
required bool isAdult,
|
||||
required bool? isOC,
|
||||
required bool isNSFW,
|
||||
required bool isPinned,
|
||||
required DateTime createdAt,
|
||||
required DateTime? editedAt,
|
||||
|
@ -90,8 +90,8 @@ class PostModel with _$PostModel {
|
|||
? 1
|
||||
: ((json['userVote'] as int?) == -1 ? -1 : 0),
|
||||
myBoost: (json['userVote'] as int?) == 1,
|
||||
isOc: json['isOc'] as bool,
|
||||
isAdult: json['isAdult'] as bool,
|
||||
isOC: json['isOc'] as bool,
|
||||
isNSFW: json['isAdult'] as bool,
|
||||
isPinned: json['isPinned'] as bool,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
editedAt: optionalDateTime(json['editedAt'] as String?),
|
||||
|
@ -119,8 +119,8 @@ class PostModel with _$PostModel {
|
|||
? 1
|
||||
: ((json['userVote'] as int?) == -1 ? -1 : 0),
|
||||
myBoost: (json['userVote'] as int?) == 1,
|
||||
isOc: null,
|
||||
isAdult: json['isAdult'] as bool,
|
||||
isOC: null,
|
||||
isNSFW: json['isAdult'] as bool,
|
||||
isPinned: json['isPinned'] as bool,
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
editedAt: optionalDateTime(json['editedAt'] as String?),
|
||||
|
@ -150,10 +150,10 @@ class PostModel with _$PostModel {
|
|||
boosts: null,
|
||||
myVote: json['my_vote'] as int?,
|
||||
myBoost: null,
|
||||
isOc: null,
|
||||
isAdult: lemmyPost['nsfw'] as bool,
|
||||
isPinned: (lemmyPost['featured_community'] as bool ||
|
||||
lemmyPost['featured_local'] as bool),
|
||||
isOC: null,
|
||||
isNSFW: lemmyPost['nsfw'] as bool,
|
||||
isPinned: lemmyPost['featured_community'] as bool ||
|
||||
lemmyPost['featured_local'] as bool,
|
||||
createdAt: DateTime.parse(lemmyPost['published'] as String),
|
||||
editedAt: optionalDateTime(lemmyPost['updated'] as String?),
|
||||
lastActive: DateTime.parse(lemmyCounts['newest_comment_time'] as String),
|
||||
|
|
|
@ -113,7 +113,7 @@ class _MagazinesScreenState extends State<MagazinesScreen> {
|
|||
],
|
||||
),
|
||||
),
|
||||
...(context.read<SettingsController>().serverSoftware ==
|
||||
...(context.watch<SettingsController>().serverSoftware ==
|
||||
ServerSoftware.lemmy ||
|
||||
filter == APIMagazinesFilter.all ||
|
||||
filter == APIMagazinesFilter.local
|
||||
|
|
|
@ -71,8 +71,8 @@ class _FeedScreenState extends State<FeedScreen> {
|
|||
|
||||
final actions = [
|
||||
feedActionCreatePost.withProps(
|
||||
context.read<SettingsController>().isLoggedIn
|
||||
? context.read<SettingsController>().feedActionCreatePost
|
||||
context.watch<SettingsController>().isLoggedIn
|
||||
? context.watch<SettingsController>().feedActionCreatePost
|
||||
: ActionLocation.hide,
|
||||
() async {
|
||||
await Navigator.of(context).push(
|
||||
|
@ -92,7 +92,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
|||
: parseEnum(
|
||||
ActionLocation.values,
|
||||
ActionLocation.hide,
|
||||
context.read<SettingsController>().feedActionSetFilter.name,
|
||||
context.watch<SettingsController>().feedActionSetFilter.name,
|
||||
),
|
||||
() async {
|
||||
final newFilter =
|
||||
|
@ -109,7 +109,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
|||
parseEnum(
|
||||
ActionLocation.values,
|
||||
ActionLocation.hide,
|
||||
context.read<SettingsController>().feedActionSetSort.name,
|
||||
context.watch<SettingsController>().feedActionSetSort.name,
|
||||
),
|
||||
() async {
|
||||
final newSort = await feedSortSelect.askSelection(context, _sort);
|
||||
|
@ -123,13 +123,13 @@ class _FeedScreenState extends State<FeedScreen> {
|
|||
),
|
||||
feedActionSetType.withProps(
|
||||
widget.source == FeedSource.domain &&
|
||||
context.read<SettingsController>().serverSoftware ==
|
||||
context.watch<SettingsController>().serverSoftware ==
|
||||
ServerSoftware.lemmy
|
||||
? ActionLocation.hide
|
||||
: parseEnum(
|
||||
ActionLocation.values,
|
||||
ActionLocation.hide,
|
||||
context.read<SettingsController>().feedActionSetType.name,
|
||||
context.watch<SettingsController>().feedActionSetType.name,
|
||||
),
|
||||
() async {
|
||||
final newMode = await feedTypeSelect.askSelection(context, _mode);
|
||||
|
@ -149,7 +149,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
|||
},
|
||||
),
|
||||
feedActionRefresh.withProps(
|
||||
context.read<SettingsController>().feedActionRefresh,
|
||||
context.watch<SettingsController>().feedActionRefresh,
|
||||
() {
|
||||
for (var key in _feedKeyList) {
|
||||
key.currentState?.refresh();
|
||||
|
@ -157,7 +157,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
|||
},
|
||||
),
|
||||
feedActionBackToTop.withProps(
|
||||
context.read<SettingsController>().feedActionBackToTop,
|
||||
context.watch<SettingsController>().feedActionBackToTop,
|
||||
() {
|
||||
for (var key in _feedKeyList) {
|
||||
key.currentState?.backToTop();
|
||||
|
@ -165,7 +165,7 @@ class _FeedScreenState extends State<FeedScreen> {
|
|||
},
|
||||
),
|
||||
feedActionExpandFab.withProps(
|
||||
context.read<SettingsController>().feedActionExpandFab,
|
||||
context.watch<SettingsController>().feedActionExpandFab,
|
||||
() {
|
||||
_fabKey.currentState?.toggle();
|
||||
},
|
||||
|
@ -173,12 +173,12 @@ class _FeedScreenState extends State<FeedScreen> {
|
|||
];
|
||||
|
||||
final tabsAction = [
|
||||
if (context.read<SettingsController>().feedActionSetFilter ==
|
||||
if (context.watch<SettingsController>().feedActionSetFilter ==
|
||||
ActionLocationWithTabs.tabs &&
|
||||
widget.source == null &&
|
||||
context.read<SettingsController>().isLoggedIn)
|
||||
context.watch<SettingsController>().isLoggedIn)
|
||||
actions.firstWhere((action) => action.name == feedActionSetFilter.name),
|
||||
if (context.read<SettingsController>().feedActionSetType ==
|
||||
if (context.watch<SettingsController>().feedActionSetType ==
|
||||
ActionLocationWithTabs.tabs)
|
||||
actions.firstWhere((action) => action.name == feedActionSetType.name),
|
||||
].firstOrNull;
|
||||
|
@ -194,9 +194,9 @@ class _FeedScreenState extends State<FeedScreen> {
|
|||
.entries
|
||||
.firstWhere((entry) =>
|
||||
entry.value.value ==
|
||||
(context.read<SettingsController>().serverSoftware !=
|
||||
(context.watch<SettingsController>().serverSoftware !=
|
||||
ServerSoftware.lemmy
|
||||
? context.read<SettingsController>().defaultFeedType
|
||||
? context.watch<SettingsController>().defaultFeedType
|
||||
: PostType.thread))
|
||||
.key,
|
||||
_ => 0
|
||||
|
@ -215,8 +215,8 @@ class _FeedScreenState extends State<FeedScreen> {
|
|||
contentPadding: EdgeInsets.zero,
|
||||
title: Text(
|
||||
widget.title ??
|
||||
context.read<SettingsController>().selectedAccount +
|
||||
(context.read<SettingsController>().isLoggedIn
|
||||
context.watch<SettingsController>().selectedAccount +
|
||||
(context.watch<SettingsController>().isLoggedIn
|
||||
? ''
|
||||
: ' (Guest)'),
|
||||
maxLines: 1,
|
||||
|
@ -534,7 +534,12 @@ class _FeedScreenBodyState extends State<FeedScreenBody> {
|
|||
@override
|
||||
void didUpdateWidget(covariant FeedScreenBody oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
_pagingController.refresh();
|
||||
if (widget.mode != oldWidget.mode ||
|
||||
widget.sort != oldWidget.sort ||
|
||||
widget.source != oldWidget.source ||
|
||||
widget.sourceId != oldWidget.sourceId) {
|
||||
_pagingController.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -553,10 +558,8 @@ class _FeedScreenBodyState extends State<FeedScreenBody> {
|
|||
PagedSliverList(
|
||||
pagingController: _pagingController,
|
||||
builderDelegate: PagedChildBuilderDelegate<PostModel>(
|
||||
itemBuilder: (context, item, index) => Card(
|
||||
margin: const EdgeInsets.all(12),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: InkWell(
|
||||
itemBuilder: (context, item, index) {
|
||||
final inner = InkWell(
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
|
@ -584,8 +587,24 @@ class _FeedScreenBodyState extends State<FeedScreenBody> {
|
|||
},
|
||||
isPreview: item.type == PostType.thread,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return context.watch<SettingsController>().postUseCardPreview
|
||||
? Card(
|
||||
margin: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: inner,
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
inner,
|
||||
const Divider(height: 1),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
|
|
|
@ -44,6 +44,7 @@ class _EntryCommentState extends State<PostComment> {
|
|||
originInstance: getNameHost(context, widget.comment.user.name),
|
||||
body: widget.comment.body ?? '_comment deleted_',
|
||||
createdAt: widget.comment.createdAt,
|
||||
editedAt: widget.comment.editedAt,
|
||||
user: widget.comment.user.name,
|
||||
userIcon: widget.comment.user.avatar,
|
||||
userIdOnClick: widget.comment.user.id,
|
||||
|
@ -155,8 +156,8 @@ class _EntryCommentState extends State<PostComment> {
|
|||
})
|
||||
: null,
|
||||
openLinkUri: Uri.https(
|
||||
context.read<SettingsController>().instanceHost,
|
||||
context.read<SettingsController>().serverSoftware ==
|
||||
context.watch<SettingsController>().instanceHost,
|
||||
context.watch<SettingsController>().serverSoftware ==
|
||||
ServerSoftware.lemmy
|
||||
? '/comment/${widget.comment.id}'
|
||||
: '/m/${widget.comment.magazine.name}/${switch (widget.comment.postType) {
|
||||
|
@ -186,12 +187,12 @@ class _EntryCommentState extends State<PostComment> {
|
|||
if (widget.comment.childCount > 0 && !_isCollapsed)
|
||||
Container(
|
||||
margin: const EdgeInsets.only(left: 1),
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
padding: const EdgeInsets.only(left: 9),
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
left: BorderSide(
|
||||
color: Theme.of(context).colorScheme.outlineVariant,
|
||||
width: 1,
|
||||
width: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -36,8 +36,11 @@ class PostItem extends StatelessWidget {
|
|||
video: isVideo ? Uri.parse(item.url!) : null,
|
||||
body: item.body,
|
||||
createdAt: item.createdAt,
|
||||
editedAt: item.editedAt,
|
||||
isPreview: isPreview,
|
||||
showMagazineFirst: item.type == PostType.thread,
|
||||
isNSFW: item.isNSFW,
|
||||
isOC: item.isOC == true,
|
||||
user: item.user.name,
|
||||
userIcon: item.user.avatar,
|
||||
userIdOnClick: item.user.id,
|
||||
|
@ -111,8 +114,8 @@ class PostItem extends StatelessWidget {
|
|||
onDelete: onDelete,
|
||||
numComments: item.numComments,
|
||||
openLinkUri: Uri.https(
|
||||
context.read<SettingsController>().instanceHost,
|
||||
context.read<SettingsController>().serverSoftware ==
|
||||
context.watch<SettingsController>().instanceHost,
|
||||
context.watch<SettingsController>().serverSoftware ==
|
||||
ServerSoftware.lemmy
|
||||
? '/post/${item.id}'
|
||||
: '/m/${item.magazine.name}/${switch (item.type) {
|
||||
|
|
|
@ -186,10 +186,10 @@ class _PostPageState extends State<PostPage> {
|
|||
.edit(
|
||||
post.id,
|
||||
post.title!,
|
||||
post.isOc!,
|
||||
post.isOC!,
|
||||
body,
|
||||
post.lang!,
|
||||
post.isAdult,
|
||||
post.isNSFW,
|
||||
),
|
||||
PostType.microblog => context
|
||||
.read<SettingsController>()
|
||||
|
@ -199,7 +199,7 @@ class _PostPageState extends State<PostPage> {
|
|||
post.id,
|
||||
body,
|
||||
post.lang!,
|
||||
post.isAdult,
|
||||
post.isNSFW,
|
||||
),
|
||||
};
|
||||
_onUpdate(newPost);
|
||||
|
|
|
@ -54,7 +54,7 @@ class MessageItem extends StatelessWidget {
|
|||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: Markdown(item.messages.first.body,
|
||||
context.read<SettingsController>().instanceHost),
|
||||
context.watch<SettingsController>().instanceHost),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -11,7 +11,6 @@ import '../../widgets/image_selector.dart';
|
|||
import '../settings/settings_controller.dart';
|
||||
|
||||
class ProfileEditScreen extends StatefulWidget {
|
||||
|
||||
final DetailedUserModel user;
|
||||
final void Function(DetailedUserModel?) onUpdate;
|
||||
|
||||
|
@ -22,7 +21,6 @@ class ProfileEditScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _ProfileEditScreen extends State<ProfileEditScreen> {
|
||||
|
||||
TextEditingController? _aboutTextController;
|
||||
XFile? _avatarFile;
|
||||
bool _deleteAvatar = false;
|
||||
|
@ -40,7 +38,8 @@ class _ProfileEditScreen extends State<ProfileEditScreen> {
|
|||
}
|
||||
|
||||
void _initSettings() async {
|
||||
final settings = await context.read<SettingsController>().api.users.getUserSettings();
|
||||
final settings =
|
||||
await context.read<SettingsController>().api.users.getUserSettings();
|
||||
setState(() {
|
||||
_settings = settings;
|
||||
});
|
||||
|
@ -52,332 +51,354 @@ class _ProfileEditScreen extends State<ProfileEditScreen> {
|
|||
appBar: AppBar(
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
if (_settingsChanged) {
|
||||
_settings = await context.read<SettingsController>().api.users.saveUserSettings(_settings!);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
onPressed: () async {
|
||||
if (_settingsChanged) {
|
||||
_settings = await context
|
||||
.read<SettingsController>()
|
||||
.api
|
||||
.users
|
||||
.saveUserSettings(_settings!);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
|
||||
var user = await context.read<SettingsController>().api.users
|
||||
.updateProfile(_aboutTextController!.text);
|
||||
var user = await context
|
||||
.read<SettingsController>()
|
||||
.api
|
||||
.users
|
||||
.updateProfile(_aboutTextController!.text);
|
||||
|
||||
if (!context.mounted) return;
|
||||
if (_deleteAvatar) {
|
||||
user = await context.read<SettingsController>().api.users
|
||||
.deleteAvatar();
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
if (_deleteCover) {
|
||||
user = await context.read<SettingsController>().api.users
|
||||
.deleteCover();
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
if (_deleteAvatar) {
|
||||
user = await context
|
||||
.read<SettingsController>()
|
||||
.api
|
||||
.users
|
||||
.deleteAvatar();
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
if (_deleteCover) {
|
||||
user = await context
|
||||
.read<SettingsController>()
|
||||
.api
|
||||
.users
|
||||
.deleteCover();
|
||||
}
|
||||
|
||||
if (!context.mounted) return;
|
||||
if (_avatarFile != null) {
|
||||
user = await context.read<SettingsController>().api.users
|
||||
.updateAvatar(_avatarFile!);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
if (_coverFile != null) {
|
||||
user = await context.read<SettingsController>().api.users
|
||||
.updateCover(_coverFile!);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
if (!context.mounted) return;
|
||||
if (_avatarFile != null) {
|
||||
user = await context
|
||||
.read<SettingsController>()
|
||||
.api
|
||||
.users
|
||||
.updateAvatar(_avatarFile!);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
if (_coverFile != null) {
|
||||
user = await context
|
||||
.read<SettingsController>()
|
||||
.api
|
||||
.users
|
||||
.updateCover(_coverFile!);
|
||||
}
|
||||
if (!context.mounted) return;
|
||||
|
||||
widget.onUpdate(user);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.send)
|
||||
widget.onUpdate(user);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
icon: const Icon(Icons.send),
|
||||
)
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height / 3,
|
||||
),
|
||||
height: widget.user.cover == null ? 100 : null,
|
||||
child: _coverFile != null
|
||||
? Image.file(File(_coverFile!.path))
|
||||
: widget.user.cover != null
|
||||
child: Column(
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: MediaQuery.of(context).size.height / 3,
|
||||
),
|
||||
height: widget.user.cover == null ? 100 : null,
|
||||
child: _coverFile != null
|
||||
? Image.file(File(_coverFile!.path))
|
||||
: widget.user.cover != null
|
||||
? _deleteCover
|
||||
? null
|
||||
: Image.network(
|
||||
widget.user.cover!,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
? null
|
||||
: Image.network(
|
||||
widget.user.cover!,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Avatar(
|
||||
_deleteAvatar ? null : widget.user.avatar,
|
||||
radius: 32,
|
||||
borderRadius: 4,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
bottom: 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Avatar(
|
||||
_deleteAvatar ? null : widget.user.avatar,
|
||||
radius: 32,
|
||||
borderRadius: 4,
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
widget.user.displayName ??
|
||||
widget.user.name.split('@').first,
|
||||
style:
|
||||
Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
Text(
|
||||
widget.user.name.contains('@')
|
||||
? '@${widget.user.name}'
|
||||
: '@${widget.user.name}@${context.read<SettingsController>().instanceHost}',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Text("Select Avatar"),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ImageSelector(
|
||||
_avatarFile,
|
||||
(file) => setState(() {
|
||||
_avatarFile = file;
|
||||
})),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_deleteAvatar = true;
|
||||
});
|
||||
},
|
||||
child: const Text("Delete")
|
||||
)
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Text("Select Cover"),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ImageSelector(
|
||||
_coverFile,
|
||||
(file) => setState(() {
|
||||
_coverFile = file;
|
||||
}),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_deleteCover = true;
|
||||
});
|
||||
},
|
||||
child: const Text("Delete"),
|
||||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: TextEditor(
|
||||
_aboutTextController!,
|
||||
label: "About",
|
||||
isMarkdown: true,
|
||||
),
|
||||
),
|
||||
if (_settings != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 30),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Settings",
|
||||
style: Theme.of(context).textTheme.titleLarge
|
||||
widget.user.displayName ??
|
||||
widget.user.name.split('@').first,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text("Show NSFW"),
|
||||
value: _settings!.showNSFW,
|
||||
onChanged: (bool value) {
|
||||
setState(() {
|
||||
_settings!.showNSFW = value;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
Text(
|
||||
widget.user.name.contains('@')
|
||||
? '@${widget.user.name}'
|
||||
: '@${widget.user.name}@${context.watch<SettingsController>().instanceHost}',
|
||||
),
|
||||
if (_settings!.blurNSFW != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Blur NSFW"),
|
||||
value: _settings!.blurNSFW!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.blurNSFW = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.showReadPosts != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show read posts"),
|
||||
value: _settings!.showReadPosts!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showReadPosts = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.showSubscribedUsers != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show subscribed users"),
|
||||
value: _settings!.showSubscribedUsers!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showSubscribedUsers = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.showSubscribedMagazines != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show subscribed magazines"),
|
||||
value: _settings!.showSubscribedMagazines!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showSubscribedMagazines = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.showSubscribedDomains != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show subscribed domains"),
|
||||
value: _settings!.showSubscribedDomains!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showSubscribedDomains = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.showProfileSubscriptions != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show profile subscriptions"),
|
||||
value: _settings!.showProfileSubscriptions!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showProfileSubscriptions = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.showProfileFollowings != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show profile followings"),
|
||||
value: _settings!.showProfileFollowings!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showProfileFollowings = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.notifyOnNewEntry != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Notify on new threads in subscribed magazines"),
|
||||
value: _settings!.notifyOnNewEntry!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewEntry = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.notifyOnNewPost != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Notify on new micropost in subscribed magazines"),
|
||||
value: _settings!.notifyOnNewPost!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewPost = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.notifyOnNewEntryReply != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Notify on comments in authored threads"),
|
||||
value: _settings!.notifyOnNewEntryReply!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewEntryReply = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.notifyOnNewEntryCommentReply != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Notify on thread comment reply"),
|
||||
value: _settings!.notifyOnNewEntryCommentReply!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewEntryCommentReply = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.notifyOnNewPostReply != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Notify on comments in authored microposts"),
|
||||
value: _settings!.notifyOnNewPostReply!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewPostReply = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
if (_settings!.notifyOnNewPostCommentReply != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Notify on micropost comment reply"),
|
||||
value: _settings!.notifyOnNewPostCommentReply!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewPostCommentReply = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
}
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
]),
|
||||
Row(
|
||||
children: [
|
||||
const Text("Select Avatar"),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ImageSelector(
|
||||
_avatarFile,
|
||||
(file) => setState(() {
|
||||
_avatarFile = file;
|
||||
})),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_deleteAvatar = true;
|
||||
});
|
||||
},
|
||||
child: const Text("Delete"),
|
||||
)
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const Text("Select Cover"),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: ImageSelector(
|
||||
_coverFile,
|
||||
(file) => setState(() {
|
||||
_coverFile = file;
|
||||
}),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_deleteCover = true;
|
||||
});
|
||||
},
|
||||
child: const Text("Delete"),
|
||||
)
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: TextEditor(
|
||||
_aboutTextController!,
|
||||
label: "About",
|
||||
isMarkdown: true,
|
||||
),
|
||||
),
|
||||
if (_settings != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 30),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Settings",
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
SwitchListTile(
|
||||
title: const Text("Show NSFW"),
|
||||
value: _settings!.showNSFW,
|
||||
onChanged: (bool value) {
|
||||
setState(() {
|
||||
_settings!.showNSFW = value;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.blurNSFW != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Blur NSFW"),
|
||||
value: _settings!.blurNSFW!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.blurNSFW = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.showReadPosts != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show read posts"),
|
||||
value: _settings!.showReadPosts!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showReadPosts = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.showSubscribedUsers != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show subscribed users"),
|
||||
value: _settings!.showSubscribedUsers!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showSubscribedUsers = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.showSubscribedMagazines != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show subscribed magazines"),
|
||||
value: _settings!.showSubscribedMagazines!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showSubscribedMagazines = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.showSubscribedDomains != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show subscribed domains"),
|
||||
value: _settings!.showSubscribedDomains!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showSubscribedDomains = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.showProfileSubscriptions != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show profile subscriptions"),
|
||||
value: _settings!.showProfileSubscriptions!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showProfileSubscriptions = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.showProfileFollowings != null)
|
||||
SwitchListTile(
|
||||
title: const Text("Show profile followings"),
|
||||
value: _settings!.showProfileFollowings!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.showProfileFollowings = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.notifyOnNewEntry != null)
|
||||
SwitchListTile(
|
||||
title: const Text(
|
||||
"Notify on new threads in subscribed magazines"),
|
||||
value: _settings!.notifyOnNewEntry!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewEntry = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.notifyOnNewPost != null)
|
||||
SwitchListTile(
|
||||
title: const Text(
|
||||
"Notify on new microblog in subscribed magazines"),
|
||||
value: _settings!.notifyOnNewPost!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewPost = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.notifyOnNewEntryReply != null)
|
||||
SwitchListTile(
|
||||
title: const Text(
|
||||
"Notify on comments in authored threads"),
|
||||
value: _settings!.notifyOnNewEntryReply!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewEntryReply = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.notifyOnNewEntryCommentReply != null)
|
||||
SwitchListTile(
|
||||
title:
|
||||
const Text("Notify on thread comment reply"),
|
||||
value: _settings!.notifyOnNewEntryCommentReply!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewEntryCommentReply =
|
||||
value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.notifyOnNewPostReply != null)
|
||||
SwitchListTile(
|
||||
title: const Text(
|
||||
"Notify on comments in authored microposts"),
|
||||
value: _settings!.notifyOnNewPostReply!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewPostReply = value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (_settings!.notifyOnNewPostCommentReply != null)
|
||||
SwitchListTile(
|
||||
title: const Text(
|
||||
"Notify on microblog comment reply"),
|
||||
value: _settings!.notifyOnNewPostCommentReply!,
|
||||
onChanged: (bool? value) {
|
||||
setState(() {
|
||||
_settings!.notifyOnNewPostCommentReply =
|
||||
value!;
|
||||
_settingsChanged = true;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
))
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
),
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@ class GeneralScreen extends StatelessWidget {
|
|||
final currentThemeMode = themeModeSelect.getOption(controller.themeMode);
|
||||
final currentTheme = themeSelect.getOption(controller.accentColor);
|
||||
|
||||
final currentPostLayout = postLayoutSelect.getOption(controller.postLayout);
|
||||
final currentPostImagePosition =
|
||||
postLayoutSelect.getOption(controller.postImagePosition);
|
||||
|
||||
final customLanguageFilterEnabled =
|
||||
!controller.useAccountLangFilter && !isLemmy;
|
||||
|
@ -86,6 +87,67 @@ class GeneralScreen extends StatelessWidget {
|
|||
),
|
||||
enabled: !controller.useDynamicColor,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text('Post Appearance',
|
||||
style: Theme.of(context).textTheme.titleMedium),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Image Position'),
|
||||
leading: const Icon(Icons.image),
|
||||
onTap: () async {
|
||||
controller.updatePostImagePosition(
|
||||
await postLayoutSelect.askSelection(
|
||||
context,
|
||||
controller.postImagePosition,
|
||||
),
|
||||
);
|
||||
},
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(currentPostImagePosition.icon),
|
||||
const SizedBox(width: 4),
|
||||
Text(currentPostImagePosition.title),
|
||||
],
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Limit Title Preview'),
|
||||
leading: const Icon(Icons.title),
|
||||
onTap: () {
|
||||
controller.updatePostLimitTitlePreview(
|
||||
!controller.postLimitTitlePreview);
|
||||
},
|
||||
trailing: Switch(
|
||||
value: controller.postLimitTitlePreview,
|
||||
onChanged: controller.updatePostLimitTitlePreview,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Show Text Preview'),
|
||||
leading: const Icon(Icons.description),
|
||||
onTap: () {
|
||||
controller
|
||||
.updatePostShowTextPreview(!controller.postShowTextPreview);
|
||||
},
|
||||
trailing: Switch(
|
||||
value: controller.postShowTextPreview,
|
||||
onChanged: controller.updatePostShowTextPreview,
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Use Card Preview'),
|
||||
leading: const Icon(Icons.view_agenda),
|
||||
onTap: () {
|
||||
controller
|
||||
.updatePostUseCardPreview(!controller.postUseCardPreview);
|
||||
},
|
||||
trailing: Switch(
|
||||
value: controller.postUseCardPreview,
|
||||
onChanged: controller.updatePostUseCardPreview,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text('Language',
|
||||
|
@ -179,26 +241,6 @@ class GeneralScreen extends StatelessWidget {
|
|||
subtitle: const Text(
|
||||
'When enabled, the instance of a user/magazine will always display instead of an @ button'),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Post Layout'),
|
||||
leading: const Icon(Icons.view_list),
|
||||
onTap: () async {
|
||||
controller.updatePostLayout(
|
||||
await postLayoutSelect.askSelection(
|
||||
context,
|
||||
controller.postLayout,
|
||||
),
|
||||
);
|
||||
},
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(currentPostLayout.icon),
|
||||
const SizedBox(width: 4),
|
||||
Text(currentPostLayout.title),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -238,22 +280,22 @@ SelectionMenu<String> themeSelect = SelectionMenu(
|
|||
.toList(),
|
||||
);
|
||||
|
||||
const SelectionMenu<PostLayout> postLayoutSelect = SelectionMenu(
|
||||
'Post Layout',
|
||||
const SelectionMenu<PostImagePosition> postLayoutSelect = SelectionMenu(
|
||||
'Post Image Position',
|
||||
[
|
||||
SelectionMenuItem(
|
||||
value: PostLayout.auto,
|
||||
value: PostImagePosition.auto,
|
||||
title: 'Auto',
|
||||
icon: Icons.auto_mode,
|
||||
),
|
||||
SelectionMenuItem(
|
||||
value: PostLayout.narrow,
|
||||
title: 'Narrow',
|
||||
value: PostImagePosition.top,
|
||||
title: 'Top',
|
||||
icon: Icons.smartphone,
|
||||
),
|
||||
SelectionMenuItem(
|
||||
value: PostLayout.wide,
|
||||
title: 'Wide',
|
||||
value: PostImagePosition.right,
|
||||
title: 'Right',
|
||||
icon: Icons.tablet,
|
||||
),
|
||||
],
|
||||
|
|
|
@ -17,7 +17,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
|||
|
||||
enum ServerSoftware { kbin, mbin, lemmy }
|
||||
|
||||
enum PostLayout { auto, narrow, wide }
|
||||
enum PostImagePosition { auto, top, right }
|
||||
|
||||
class Server {
|
||||
final ServerSoftware software;
|
||||
|
@ -65,10 +65,17 @@ class SettingsController with ChangeNotifier {
|
|||
ThemeInfo get theme =>
|
||||
themes.firstWhere((theme) => theme.name == _accentColor);
|
||||
|
||||
late PostImagePosition _postImagePosition;
|
||||
PostImagePosition get postImagePosition => _postImagePosition;
|
||||
late bool _postLimitTitlePreview;
|
||||
bool get postLimitTitlePreview => _postLimitTitlePreview;
|
||||
late bool _postShowTextPreview;
|
||||
bool get postShowTextPreview => _postShowTextPreview;
|
||||
late bool _postUseCardPreview;
|
||||
bool get postUseCardPreview => _postUseCardPreview;
|
||||
|
||||
late bool _alwaysShowInstance;
|
||||
bool get alwaysShowInstance => _alwaysShowInstance;
|
||||
late PostLayout _postLayout;
|
||||
PostLayout get postLayout => _postLayout;
|
||||
|
||||
late ActionLocation _feedActionBackToTop;
|
||||
ActionLocation get feedActionBackToTop => _feedActionBackToTop;
|
||||
|
@ -124,21 +131,19 @@ class SettingsController with ChangeNotifier {
|
|||
ThemeMode.system,
|
||||
prefs.getString("themeMode"),
|
||||
);
|
||||
_useDynamicColor = prefs.getBool("useDynamicColor") != null
|
||||
? prefs.getBool("useDynamicColor")!
|
||||
: true;
|
||||
_accentColor = prefs.getString("accentColor") != null
|
||||
? prefs.getString("accentColor")!
|
||||
: "Default";
|
||||
_useDynamicColor = prefs.getBool("useDynamicColor") ?? true;
|
||||
_accentColor = prefs.getString("accentColor") ?? "Default";
|
||||
|
||||
_alwaysShowInstance = prefs.getBool("alwaysShowInstance") != null
|
||||
? prefs.getBool("alwaysShowInstance")!
|
||||
: false;
|
||||
_postLayout = parseEnum(
|
||||
PostLayout.values,
|
||||
PostLayout.auto,
|
||||
prefs.getString("postLayout"),
|
||||
_alwaysShowInstance = prefs.getBool("alwaysShowInstance") ?? false;
|
||||
|
||||
_postImagePosition = parseEnum(
|
||||
PostImagePosition.values,
|
||||
PostImagePosition.auto,
|
||||
prefs.getString("postImagePosition"),
|
||||
);
|
||||
_postLimitTitlePreview = prefs.getBool("postLimitTitlePreview") ?? false;
|
||||
_postShowTextPreview = prefs.getBool("postShowTextPreview") ?? true;
|
||||
_postUseCardPreview = prefs.getBool("postUseCardPreview") ?? true;
|
||||
|
||||
_feedActionBackToTop = parseEnum(
|
||||
ActionLocation.values,
|
||||
|
@ -202,15 +207,9 @@ class SettingsController with ChangeNotifier {
|
|||
prefs.getString("defaultCommentSort"),
|
||||
);
|
||||
|
||||
_useAccountLangFilter = prefs.getBool("useAccountLangFilter") != null
|
||||
? prefs.getBool("useAccountLangFilter")!
|
||||
: true;
|
||||
_langFilter = prefs.getStringList("langFilter") != null
|
||||
? prefs.getStringList("langFilter")!.toSet()
|
||||
: {};
|
||||
_defaultCreateLang = prefs.getString("defaultCreateLang") != null
|
||||
? prefs.getString("defaultCreateLang")!
|
||||
: 'en';
|
||||
_useAccountLangFilter = prefs.getBool("useAccountLangFilter") ?? true;
|
||||
_langFilter = prefs.getStringList("langFilter")?.toSet() ?? {};
|
||||
_defaultCreateLang = prefs.getString("defaultCreateLang") ?? 'en';
|
||||
|
||||
_servers = (jsonDecode(prefs.getString('servers') ??
|
||||
'{"kbin.earth":{"software":"mbin"}}') as Map<String, dynamic>)
|
||||
|
@ -224,144 +223,208 @@ class SettingsController with ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> updateThemeMode(ThemeMode? newThemeMode) async {
|
||||
if (newThemeMode == null) return;
|
||||
if (newThemeMode == _themeMode) return;
|
||||
|
||||
_themeMode = newThemeMode;
|
||||
Future<void> presetClassic() async {
|
||||
_postImagePosition = PostImagePosition.auto;
|
||||
_postLimitTitlePreview = false;
|
||||
_postShowTextPreview = true;
|
||||
_postUseCardPreview = true;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('themeMode', newThemeMode.name);
|
||||
await prefs.setString('postImagePosition', _postImagePosition.name);
|
||||
await prefs.setBool('postLimitTitlePreview', _postLimitTitlePreview);
|
||||
await prefs.setBool('postShowTextPreview', _postShowTextPreview);
|
||||
await prefs.setBool('postUseCardPreview', _postUseCardPreview);
|
||||
}
|
||||
|
||||
Future<void> updateUseDynamicColor(bool? newUseDynamicColor) async {
|
||||
if (newUseDynamicColor == null) return;
|
||||
if (newUseDynamicColor == _useDynamicColor) return;
|
||||
|
||||
_useDynamicColor = newUseDynamicColor;
|
||||
Future<void> presetCompact() async {
|
||||
_postImagePosition = PostImagePosition.right;
|
||||
_postLimitTitlePreview = true;
|
||||
_postShowTextPreview = false;
|
||||
_postUseCardPreview = false;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('useDynamicColor', newUseDynamicColor);
|
||||
await prefs.setString('postImagePosition', _postImagePosition.name);
|
||||
await prefs.setBool('postLimitTitlePreview', _postLimitTitlePreview);
|
||||
await prefs.setBool('postShowTextPreview', _postShowTextPreview);
|
||||
await prefs.setBool('postUseCardPreview', _postUseCardPreview);
|
||||
}
|
||||
|
||||
Future<void> updateAccentColor(String? newThemeAccent) async {
|
||||
if (newThemeAccent == null) return;
|
||||
if (newThemeAccent == _accentColor) return;
|
||||
Future<void> updateThemeMode(ThemeMode? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _themeMode) return;
|
||||
|
||||
_accentColor = newThemeAccent;
|
||||
_themeMode = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('accentColor', newThemeAccent);
|
||||
await prefs.setString('themeMode', newValue.name);
|
||||
}
|
||||
|
||||
Future<void> updateAlwaysShowInstance(bool? newShowDisplayInstance) async {
|
||||
if (newShowDisplayInstance == null) return;
|
||||
if (newShowDisplayInstance == _alwaysShowInstance) return;
|
||||
Future<void> updateUseDynamicColor(bool? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _useDynamicColor) return;
|
||||
|
||||
_alwaysShowInstance = newShowDisplayInstance;
|
||||
_useDynamicColor = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('alwaysShowInstance', newShowDisplayInstance);
|
||||
await prefs.setBool('useDynamicColor', newValue);
|
||||
}
|
||||
|
||||
Future<void> updatePostLayout(PostLayout? newPostLayout) async {
|
||||
if (newPostLayout == null) return;
|
||||
if (newPostLayout == _postLayout) return;
|
||||
Future<void> updateAccentColor(String? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _accentColor) return;
|
||||
|
||||
_postLayout = newPostLayout;
|
||||
_accentColor = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('postLayout', newPostLayout.name);
|
||||
await prefs.setString('accentColor', newValue);
|
||||
}
|
||||
|
||||
Future<void> updateDefaultFeedType(PostType? newDefaultFeedMode) async {
|
||||
if (newDefaultFeedMode == null) return;
|
||||
if (newDefaultFeedMode == _defaultFeedType) return;
|
||||
Future<void> updatePostImagePosition(PostImagePosition? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _postImagePosition) return;
|
||||
|
||||
_defaultFeedType = newDefaultFeedMode;
|
||||
_postImagePosition = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('defaultFeedType', newDefaultFeedMode.name);
|
||||
await prefs.setString('postImagePosition', newValue.name);
|
||||
}
|
||||
|
||||
Future<void> updateDefaultEntriesFeedSort(
|
||||
FeedSort? newDefaultFeedSort) async {
|
||||
if (newDefaultFeedSort == null) return;
|
||||
if (newDefaultFeedSort == _defaultEntriesFeedSort) return;
|
||||
Future<void> updatePostLimitTitlePreview(bool? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _postLimitTitlePreview) return;
|
||||
|
||||
_defaultEntriesFeedSort = newDefaultFeedSort;
|
||||
_postLimitTitlePreview = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('defaultFeedSortEntries', newDefaultFeedSort.name);
|
||||
await prefs.setBool('postLimitTitlePreview', newValue);
|
||||
}
|
||||
|
||||
Future<void> updateDefaultPostsFeedSort(FeedSort? newDefaultFeedSort) async {
|
||||
if (newDefaultFeedSort == null) return;
|
||||
if (newDefaultFeedSort == _defaultPostsFeedSort) return;
|
||||
Future<void> updatePostShowTextPreview(bool? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _postShowTextPreview) return;
|
||||
|
||||
_defaultPostsFeedSort = newDefaultFeedSort;
|
||||
_postShowTextPreview = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('defaultPostsFeedSort', newDefaultFeedSort.name);
|
||||
await prefs.setBool('postShowTextPreview', newValue);
|
||||
}
|
||||
|
||||
Future<void> updatePostUseCardPreview(bool? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _postUseCardPreview) return;
|
||||
|
||||
_postUseCardPreview = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('postUseCardPreview', newValue);
|
||||
}
|
||||
|
||||
Future<void> updateAlwaysShowInstance(bool? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _alwaysShowInstance) return;
|
||||
|
||||
_alwaysShowInstance = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('alwaysShowInstance', newValue);
|
||||
}
|
||||
|
||||
Future<void> updateDefaultFeedType(PostType? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _defaultFeedType) return;
|
||||
|
||||
_defaultFeedType = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('defaultFeedType', newValue.name);
|
||||
}
|
||||
|
||||
Future<void> updateDefaultEntriesFeedSort(FeedSort? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _defaultEntriesFeedSort) return;
|
||||
|
||||
_defaultEntriesFeedSort = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('defaultFeedSortEntries', newValue.name);
|
||||
}
|
||||
|
||||
Future<void> updateDefaultPostsFeedSort(FeedSort? newValue) async {
|
||||
if (newValue == null) return;
|
||||
if (newValue == _defaultPostsFeedSort) return;
|
||||
|
||||
_defaultPostsFeedSort = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('defaultPostsFeedSort', newValue.name);
|
||||
}
|
||||
|
||||
Future<void> updateDefaultExploreFeedSort(
|
||||
FeedSort? newDefaultExploreFeedSort,
|
||||
FeedSort? newValue,
|
||||
) async {
|
||||
if (newDefaultExploreFeedSort == null) return;
|
||||
if (newDefaultExploreFeedSort == _defaultExploreFeedSort) return;
|
||||
if (newValue == null) return;
|
||||
if (newValue == _defaultExploreFeedSort) return;
|
||||
|
||||
_defaultExploreFeedSort = newDefaultExploreFeedSort;
|
||||
_defaultExploreFeedSort = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(
|
||||
'defaultExploreFeedSort', newDefaultExploreFeedSort.name);
|
||||
await prefs.setString('defaultExploreFeedSort', newValue.name);
|
||||
}
|
||||
|
||||
Future<void> updateDefaultCommentSort(
|
||||
CommentSort? newDefaultCommentSort,
|
||||
CommentSort? newValue,
|
||||
) async {
|
||||
if (newDefaultCommentSort == null) return;
|
||||
if (newDefaultCommentSort == _defaultCommentSort) return;
|
||||
if (newValue == null) return;
|
||||
if (newValue == _defaultCommentSort) return;
|
||||
|
||||
_defaultCommentSort = newDefaultCommentSort;
|
||||
_defaultCommentSort = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('defaultCommentSort', newDefaultCommentSort.name);
|
||||
await prefs.setString('defaultCommentSort', newValue.name);
|
||||
}
|
||||
|
||||
Future<void> updateUseAccountLangFilter(
|
||||
bool? newUseAccountLangFilter,
|
||||
bool? newValue,
|
||||
) async {
|
||||
if (newUseAccountLangFilter == null) return;
|
||||
if (newUseAccountLangFilter == _useAccountLangFilter) return;
|
||||
if (newValue == null) return;
|
||||
if (newValue == _useAccountLangFilter) return;
|
||||
|
||||
_useAccountLangFilter = newUseAccountLangFilter;
|
||||
_useAccountLangFilter = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setBool('useAccountLangFilter', newUseAccountLangFilter);
|
||||
await prefs.setBool('useAccountLangFilter', newValue);
|
||||
}
|
||||
|
||||
Future<void> addLangFilter(
|
||||
|
@ -393,17 +456,17 @@ class SettingsController with ChangeNotifier {
|
|||
}
|
||||
|
||||
Future<void> updateDefaultCreateLang(
|
||||
String? newDefaultCreateLang,
|
||||
String? newValue,
|
||||
) async {
|
||||
if (newDefaultCreateLang == null) return;
|
||||
if (newDefaultCreateLang == _defaultCreateLang) return;
|
||||
if (newValue == null) return;
|
||||
if (newValue == _defaultCreateLang) return;
|
||||
|
||||
_defaultCreateLang = newDefaultCreateLang;
|
||||
_defaultCreateLang = newValue;
|
||||
|
||||
notifyListeners();
|
||||
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('defaultCreateLang', newDefaultCreateLang);
|
||||
await prefs.setString('defaultCreateLang', newValue);
|
||||
}
|
||||
|
||||
Future<void> saveServer(ServerSoftware software, String server) async {
|
||||
|
|
|
@ -44,6 +44,31 @@ class SettingsScreen extends StatelessWidget {
|
|||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child:
|
||||
Text('Presets', style: Theme.of(context).textTheme.titleMedium),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Classic Layout'),
|
||||
onTap: () async {
|
||||
controller.presetClassic();
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content: Text('Preset applied'),
|
||||
));
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('Compact Layout'),
|
||||
onTap: () async {
|
||||
controller.presetCompact();
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
|
||||
content: Text('Preset applied'),
|
||||
));
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Text('Accounts',
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Blur extends StatelessWidget {
|
||||
const Blur(
|
||||
this.child, {
|
||||
super.key,
|
||||
this.blur = 16,
|
||||
this.blurColor = Colors.white,
|
||||
this.borderRadius,
|
||||
this.colorOpacity = 0.2,
|
||||
this.overlay,
|
||||
this.alignment = Alignment.center,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final double blur;
|
||||
final Color blurColor;
|
||||
final BorderRadius? borderRadius;
|
||||
final double colorOpacity;
|
||||
final Widget? overlay;
|
||||
final AlignmentGeometry alignment;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ClipRRect(
|
||||
borderRadius: borderRadius ?? BorderRadius.zero,
|
||||
child: Stack(
|
||||
children: [
|
||||
child,
|
||||
Positioned.fill(
|
||||
child: BackdropFilter(
|
||||
filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: blurColor.withOpacity(colorOpacity),
|
||||
),
|
||||
alignment: alignment,
|
||||
child: overlay,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ 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/blur.dart';
|
||||
import 'package:interstellar/src/widgets/display_name.dart';
|
||||
import 'package:interstellar/src/widgets/markdown.dart';
|
||||
import 'package:interstellar/src/widgets/open_webpage.dart';
|
||||
|
@ -22,10 +23,14 @@ class ContentItem extends StatefulWidget {
|
|||
final Uri? video;
|
||||
final String? body;
|
||||
final DateTime? createdAt;
|
||||
final DateTime? editedAt;
|
||||
|
||||
final bool isPreview;
|
||||
final bool showMagazineFirst;
|
||||
|
||||
final bool isNSFW;
|
||||
final bool isOC;
|
||||
|
||||
final String? user;
|
||||
final String? userIcon;
|
||||
final int? userIdOnClick;
|
||||
|
@ -69,8 +74,11 @@ class ContentItem extends StatefulWidget {
|
|||
this.video,
|
||||
this.body,
|
||||
this.createdAt,
|
||||
this.editedAt,
|
||||
this.isPreview = false,
|
||||
this.showMagazineFirst = false,
|
||||
this.isNSFW = false,
|
||||
this.isOC = false,
|
||||
this.user,
|
||||
this.userIcon,
|
||||
this.userIdOnClick,
|
||||
|
@ -139,18 +147,38 @@ class _ContentItemState extends State<ContentItem> {
|
|||
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!,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
DisplayName(
|
||||
widget.user!,
|
||||
icon: widget.userIcon,
|
||||
onTap: widget.userIdOnClick != null
|
||||
? () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => UserScreen(
|
||||
widget.userIdOnClick!,
|
||||
),
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
if (widget.opUserId == widget.userIdOnClick)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
child: Tooltip(
|
||||
message: 'Original Poster',
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
child: Text(
|
||||
'OP',
|
||||
style: TextStyle(
|
||||
color: Colors.blue,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: null;
|
||||
|
@ -174,15 +202,56 @@ class _ContentItemState extends State<ContentItem> {
|
|||
: null;
|
||||
|
||||
return LayoutBuilder(builder: (context, constrains) {
|
||||
final isWide = switch (context.watch<SettingsController>().postLayout) {
|
||||
PostLayout.auto => constrains.maxWidth > 800,
|
||||
PostLayout.narrow => false,
|
||||
PostLayout.wide => true,
|
||||
final hasWideSize = constrains.maxWidth > 800;
|
||||
final isRightImage =
|
||||
switch (context.watch<SettingsController>().postImagePosition) {
|
||||
PostImagePosition.auto => hasWideSize,
|
||||
PostImagePosition.top => false,
|
||||
PostImagePosition.right => true,
|
||||
};
|
||||
|
||||
final double rightImageSize = hasWideSize ? 128 : 64;
|
||||
|
||||
final imageWidget = widget.image == null
|
||||
? null
|
||||
: Wrapper(
|
||||
shouldWrap: widget.video == null,
|
||||
parentBuilder: (child) => InkWell(
|
||||
onTap: () => _onImageClick(context),
|
||||
child: child,
|
||||
),
|
||||
child: Wrapper(
|
||||
shouldWrap: widget.isNSFW,
|
||||
parentBuilder: (child) => Blur(child),
|
||||
child: isRightImage
|
||||
? Image.network(
|
||||
widget.image!,
|
||||
height: rightImageSize,
|
||||
width: rightImageSize,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: (widget.isPreview
|
||||
? Image.network(
|
||||
widget.image!,
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Image.network(widget.image!)),
|
||||
),
|
||||
);
|
||||
|
||||
final titleStyle = hasWideSize
|
||||
? Theme.of(context).textTheme.titleLarge!
|
||||
: Theme.of(context).textTheme.titleMedium!;
|
||||
final titleOverflow = widget.isPreview &&
|
||||
context.watch<SettingsController>().postLimitTitlePreview
|
||||
? TextOverflow.ellipsis
|
||||
: null;
|
||||
|
||||
return Column(
|
||||
children: <Widget>[
|
||||
if ((!isWide && widget.image != null) ||
|
||||
if ((!isRightImage && imageWidget != null) ||
|
||||
(!widget.isPreview && widget.video != null))
|
||||
Wrapper(
|
||||
shouldWrap: !widget.isPreview,
|
||||
|
@ -193,21 +262,7 @@ class _ContentItemState extends State<ContentItem> {
|
|||
child: child),
|
||||
child: (!widget.isPreview && widget.video != null)
|
||||
? VideoPlayer(widget.video!)
|
||||
: Wrapper(
|
||||
shouldWrap: widget.video == null,
|
||||
parentBuilder: (child) => InkWell(
|
||||
onTap: () => _onImageClick(context),
|
||||
child: child,
|
||||
),
|
||||
child: widget.isPreview
|
||||
? Image.network(
|
||||
widget.image!,
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
)
|
||||
: Image.network(widget.image!),
|
||||
),
|
||||
: imageWidget!,
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
|
@ -216,7 +271,7 @@ class _ContentItemState extends State<ContentItem> {
|
|||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (widget.title != null)
|
||||
Padding(
|
||||
|
@ -225,12 +280,9 @@ class _ContentItemState extends State<ContentItem> {
|
|||
? InkWell(
|
||||
child: Text(
|
||||
widget.title!,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.titleLarge!
|
||||
.apply(
|
||||
decoration:
|
||||
TextDecoration.underline),
|
||||
style: titleStyle.apply(
|
||||
decoration: TextDecoration.underline),
|
||||
overflow: titleOverflow,
|
||||
),
|
||||
onTap: () {
|
||||
openWebpage(context, widget.link!);
|
||||
|
@ -238,37 +290,63 @@ class _ContentItemState extends State<ContentItem> {
|
|||
)
|
||||
: Text(
|
||||
widget.title!,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
style: titleStyle,
|
||||
overflow: titleOverflow,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if (widget.isNSFW)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
child: Tooltip(
|
||||
message: 'Not Safe For Work',
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
child: Text(
|
||||
'NSFW',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.isOC)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
child: Tooltip(
|
||||
message: 'Original Content',
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
child: Text(
|
||||
'OC',
|
||||
style: TextStyle(
|
||||
color: Colors.lightGreen,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!widget.showMagazineFirst && userWidget != null)
|
||||
userWidget,
|
||||
if (!widget.showMagazineFirst &&
|
||||
widget.opUserId == widget.userIdOnClick)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
child: Text("OP")),
|
||||
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),
|
||||
child: Tooltip(
|
||||
message:
|
||||
'Created: ${widget.createdAt!.toIso8601String()}${widget.editedAt == null ? '' : '\nEdited: ${widget.editedAt!.toIso8601String()}'}',
|
||||
triggerMode: TooltipTriggerMode.tap,
|
||||
child: Text(
|
||||
timeDiffFormat(widget.createdAt!),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w300),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.showMagazineFirst && userWidget != null)
|
||||
userWidget,
|
||||
if (widget.showMagazineFirst &&
|
||||
widget.opUserId == widget.userIdOnClick)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
child: Text("OP")),
|
||||
if (!widget.showMagazineFirst &&
|
||||
magazineWidget != null)
|
||||
magazineWidget,
|
||||
|
@ -295,210 +373,222 @@ class _ContentItemState extends State<ContentItem> {
|
|||
),
|
||||
],
|
||||
),
|
||||
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!, widget.originInstance),
|
||||
const SizedBox(height: 10),
|
||||
LayoutBuilder(builder: (context, constrains) {
|
||||
final votingWidgets = [
|
||||
if (widget.boosts != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: Row(
|
||||
if (widget.body != null &&
|
||||
widget.body!.isNotEmpty &&
|
||||
(!widget.isPreview ||
|
||||
context
|
||||
.watch<SettingsController>()
|
||||
.postShowTextPreview))
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: widget.isPreview
|
||||
? Text(
|
||||
widget.body!,
|
||||
maxLines: 4,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
)
|
||||
: Markdown(
|
||||
widget.body!, widget.originInstance)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 10),
|
||||
child: LayoutBuilder(builder: (context, constrains) {
|
||||
final votingWidgets = [
|
||||
if (widget.boosts != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
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)
|
||||
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)
|
||||
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.upVotes != null ||
|
||||
widget.downVotes != null)
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
final commentWidgets = [
|
||||
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!))
|
||||
],
|
||||
];
|
||||
final commentWidgets = [
|
||||
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: 8),
|
||||
child: IconButton(
|
||||
icon: const Icon(Icons.reply),
|
||||
onPressed: () => setState(() {
|
||||
_replyTextController =
|
||||
TextEditingController();
|
||||
}),
|
||||
if (widget.onReply != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
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)),
|
||||
];
|
||||
final menuWidgets = [
|
||||
if (widget.openLinkUri != null ||
|
||||
widget.onReport != null ||
|
||||
widget.onEdit != null ||
|
||||
widget.onDelete != null)
|
||||
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: [
|
||||
if (widget.openLinkUri != null)
|
||||
MenuItemButton(
|
||||
onPressed: () => openWebpage(
|
||||
context, widget.openLinkUri!),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Open Link")),
|
||||
),
|
||||
if (widget.onReport != null)
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final reportReason = await reportContent(
|
||||
context, widget.contentTypeName);
|
||||
|
||||
if (reportReason != null) {
|
||||
await widget.onReport!(reportReason);
|
||||
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)),
|
||||
];
|
||||
final menuWidgets = [
|
||||
if (widget.openLinkUri != null ||
|
||||
widget.onReport != null ||
|
||||
widget.onEdit != null ||
|
||||
widget.onDelete != null)
|
||||
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();
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Report")),
|
||||
),
|
||||
if (widget.onEdit != null)
|
||||
MenuItemButton(
|
||||
onPressed: () => setState(() {
|
||||
_editTextController =
|
||||
TextEditingController(
|
||||
text: widget.body);
|
||||
}),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Edit")),
|
||||
),
|
||||
if (widget.onDelete != null)
|
||||
MenuItemButton(
|
||||
onPressed: () => showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
AlertDialog(
|
||||
title: Text(
|
||||
'Delete ${widget.contentTypeName}'),
|
||||
actions: <Widget>[
|
||||
OutlinedButton(
|
||||
onPressed: () =>
|
||||
Navigator.pop(context),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
|
||||
widget.onDelete!();
|
||||
},
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
],
|
||||
actionsOverflowAlignment:
|
||||
OverflowBarAlignment.center,
|
||||
actionsOverflowButtonSpacing: 8,
|
||||
actionsOverflowDirection:
|
||||
VerticalDirection.up,
|
||||
),
|
||||
);
|
||||
},
|
||||
controller: _menuController,
|
||||
menuChildren: [
|
||||
if (widget.openLinkUri != null)
|
||||
MenuItemButton(
|
||||
onPressed: () => openWebpage(
|
||||
context, widget.openLinkUri!),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Open Link")),
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Delete")),
|
||||
),
|
||||
],
|
||||
),
|
||||
];
|
||||
if (widget.onReport != null)
|
||||
MenuItemButton(
|
||||
onPressed: () async {
|
||||
final reportReason =
|
||||
await reportContent(context,
|
||||
widget.contentTypeName);
|
||||
|
||||
return constrains.maxWidth < 300
|
||||
? Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: votingWidgets,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
...commentWidgets,
|
||||
const Spacer(),
|
||||
...menuWidgets,
|
||||
],
|
||||
),
|
||||
if (reportReason != null) {
|
||||
await widget.onReport!(reportReason);
|
||||
}
|
||||
},
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Report")),
|
||||
),
|
||||
if (widget.onEdit != null)
|
||||
MenuItemButton(
|
||||
onPressed: () => setState(() {
|
||||
_editTextController =
|
||||
TextEditingController(
|
||||
text: widget.body);
|
||||
}),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Edit")),
|
||||
),
|
||||
if (widget.onDelete != null)
|
||||
MenuItemButton(
|
||||
onPressed: () => showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
AlertDialog(
|
||||
title: Text(
|
||||
'Delete ${widget.contentTypeName}'),
|
||||
actions: <Widget>[
|
||||
OutlinedButton(
|
||||
onPressed: () =>
|
||||
Navigator.pop(context),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
|
||||
widget.onDelete!();
|
||||
},
|
||||
child: const Text('Delete'),
|
||||
),
|
||||
],
|
||||
actionsOverflowAlignment:
|
||||
OverflowBarAlignment.center,
|
||||
actionsOverflowButtonSpacing: 8,
|
||||
actionsOverflowDirection:
|
||||
VerticalDirection.up,
|
||||
),
|
||||
),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Text("Delete")),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
children: <Widget>[
|
||||
...commentWidgets,
|
||||
const Spacer(),
|
||||
...menuWidgets,
|
||||
const SizedBox(width: 8),
|
||||
...votingWidgets,
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
];
|
||||
|
||||
return constrains.maxWidth < 300
|
||||
? Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.center,
|
||||
children: votingWidgets,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
...commentWidgets,
|
||||
const Spacer(),
|
||||
...menuWidgets,
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
: Row(
|
||||
children: <Widget>[
|
||||
...commentWidgets,
|
||||
const Spacer(),
|
||||
...menuWidgets,
|
||||
const SizedBox(width: 8),
|
||||
...votingWidgets,
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
if (widget.onReply != null &&
|
||||
_replyTextController != null)
|
||||
Padding(
|
||||
|
@ -574,24 +664,12 @@ class _ContentItemState extends State<ContentItem> {
|
|||
],
|
||||
),
|
||||
),
|
||||
if (isWide && widget.image != null)
|
||||
if (isRightImage && imageWidget != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 16),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Wrapper(
|
||||
shouldWrap: widget.video == null,
|
||||
parentBuilder: (child) => InkWell(
|
||||
onTap: () => _onImageClick(context),
|
||||
child: child,
|
||||
),
|
||||
child: Image.network(
|
||||
widget.image!,
|
||||
height: 128,
|
||||
width: 128,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
child: imageWidget,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue