From 20b2d99bed2f523c21212f990d1996b74287795e Mon Sep 17 00:00:00 2001 From: olorin99 Date: Sat, 20 Dec 2025 15:25:05 +1000 Subject: [PATCH 01/18] Get and display modlog from mbin and lemmy. --- lib/src/api/moderation.dart | 80 +++++++++ lib/src/models/comment.dart | 10 +- lib/src/models/community.dart | 13 ++ lib/src/models/modlog.dart | 184 +++++++++++++++++++++ lib/src/models/post.dart | 12 +- lib/src/screens/explore/mod_log.dart | 149 +++++++++++++++++ lib/src/screens/settings/about_screen.dart | 9 + lib/src/widgets/menus/community_menu.dart | 9 + 8 files changed, 455 insertions(+), 11 deletions(-) create mode 100644 lib/src/models/modlog.dart create mode 100644 lib/src/screens/explore/mod_log.dart diff --git a/lib/src/api/moderation.dart b/lib/src/api/moderation.dart index 5fb9d032..a81979e9 100644 --- a/lib/src/api/moderation.dart +++ b/lib/src/api/moderation.dart @@ -1,6 +1,7 @@ import 'package:interstellar/src/api/client.dart'; import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/comment.dart'; +import 'package:interstellar/src/models/modlog.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/utils/utils.dart'; @@ -10,6 +11,42 @@ const _postTypeMbinComment = { PostType.microblog: 'post-comment', }; +enum ModLogType { + all, + entry_deleted, + entry_restored, + entry_comment_deleted, + entry_comment_restored, + entry_pinned, + entry_unpinned, + post_deleted, + post_restored, + post_comment_deleted, + post_comment_restored, + ban, + unban, + moderator_add, + moderator_remove; + + String get toLemmy => switch (this) { + ModLogType.all => 'All', + ModLogType.entry_deleted => 'ModRemovePost', + ModLogType.entry_restored => 'All', + ModLogType.entry_comment_deleted => 'ModRemoveComment', + ModLogType.entry_comment_restored => 'All', + ModLogType.entry_pinned => 'ModFeaturePost', + ModLogType.entry_unpinned => 'All', + ModLogType.post_deleted => 'ModRemovePost', + ModLogType.post_restored => 'All', + ModLogType.post_comment_deleted => 'ModRemoveComment', + ModLogType.post_comment_restored => 'All', + ModLogType.ban => 'ModBan', + ModLogType.unban => 'All', + ModLogType.moderator_add => 'ModAdd', + ModLogType.moderator_remove => 'All', + }; +} + class APIModeration { final ServerClient client; @@ -152,4 +189,47 @@ class APIModeration { ); } } + + + + Future modLog({ + int? communityId, + int? userId, + ModLogType type = ModLogType.all, + String? page, + }) async { + switch (client.software) { + case ServerSoftware.mbin: + + if (communityId != null) { + final path = '/magazine/$communityId/log'; + final query = { + 'p': page, + }; + final response = await client.get(path, queryParams: query); + return ModlogListModel.fromMbin(response.bodyJson); + } + final path = '/modlog'; + final query = { + 'p': page, + }; + final response = await client.get(path, queryParams: query); + return ModlogListModel.fromMbin(response.bodyJson); + + case ServerSoftware.lemmy: + const path = '/modlog'; + final query = { + if (communityId != null) + 'community_id': communityId.toString(), + 'page': page, + 'type_': type.toLemmy, + }; + final response = await client.get(path, queryParams: query); + final json = response.bodyJson; + return ModlogListModel.fromLemmy(json, langCodeIdPairs: await client.languageCodeIdPairs()); + + case ServerSoftware.piefed: + throw UnimplementedError('Not yet implemented for PieFed'); + } + } } diff --git a/lib/src/models/comment.dart b/lib/src/models/comment.dart index 26f585db..9e1c8ede 100644 --- a/lib/src/models/comment.dart +++ b/lib/src/models/comment.dart @@ -173,7 +173,7 @@ abstract class CommentModel with _$CommentModel { required List<(String, int)> langCodeIdPairs, }) { final lemmyComment = json['comment'] as JsonMap; - final lemmyCounts = json['counts'] as JsonMap; + final lemmyCounts = json['counts'] as JsonMap?; final lemmyPath = lemmyComment['path'] as String; final lemmyPathSegments = lemmyPath @@ -216,21 +216,21 @@ abstract class CommentModel with _$CommentModel { .where((pair) => pair.$2 == lemmyComment['language_id'] as int) .firstOrNull ?.$1, - upvotes: lemmyCounts['upvotes'] as int, - downvotes: lemmyCounts['downvotes'] as int, + upvotes: lemmyCounts?['upvotes'] as int? ?? 0, + downvotes: lemmyCounts?['downvotes'] as int? ?? 0, boosts: null, myVote: json['my_vote'] as int?, myBoost: null, createdAt: DateTime.parse(lemmyComment['published'] as String), editedAt: optionalDateTime(json['updated'] as String?), children: children, - childCount: lemmyCounts['child_count'] as int, + childCount: lemmyCounts?['child_count'] as int? ?? 0, visibility: 'visible', canAuthUserModerate: null, notificationControlStatus: null, bookmarks: [ // Empty string indicates comment is saved. No string indicates comment is not saved. - if (json['saved'] as bool) '', + if (((json['saved'] as bool?) != null) ? json['saved'] as bool : false) '', ], apId: lemmyComment['ap_id'] as String, ); diff --git a/lib/src/models/community.dart b/lib/src/models/community.dart index a3ac3845..43e830b1 100644 --- a/lib/src/models/community.dart +++ b/lib/src/models/community.dart @@ -275,6 +275,19 @@ abstract class CommunityBanModel with _$CommunityBanModel { expired: json['expired'] as bool, ); + factory CommunityBanModel.fromLemmy(JsonMap json) { + final expiration = json['expires'] != null ? DateTime.parse(json['expires'] as String) : null; + + return CommunityBanModel( + reason: json['reason'] as String?, + expiresAt: expiration, + community: CommunityModel.fromLemmy(json['community'] as JsonMap), + bannedUser: UserModel.fromLemmy(json['banned_person'] as JsonMap), + bannedBy: UserModel.fromLemmy(json['moderator'] as JsonMap), + expired: expiration?.isBefore(DateTime.now()) ?? false, + ); + } + factory CommunityBanModel.fromPiefed(JsonMap json) => CommunityBanModel( reason: json['reason'] as String?, expiresAt: optionalDateTime(json['expires_at'] as String?), diff --git a/lib/src/models/modlog.dart b/lib/src/models/modlog.dart new file mode 100644 index 00000000..75cd2201 --- /dev/null +++ b/lib/src/models/modlog.dart @@ -0,0 +1,184 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:interstellar/src/api/community_moderation.dart'; +import 'package:interstellar/src/models/comment.dart'; +import 'package:interstellar/src/models/community.dart'; +import 'package:interstellar/src/models/post.dart'; +import 'package:interstellar/src/models/user.dart'; +import 'package:interstellar/src/utils/models.dart'; +import 'package:interstellar/src/utils/utils.dart'; + +import '../api/moderation.dart'; + +part 'modlog.freezed.dart'; + +@freezed +abstract class ModlogItemModel with _$ModlogItemModel { + const factory ModlogItemModel({ + required ModLogType type, + required DateTime createdAt, + required String? reason, + required CommunityModel community, + required DetailedUserModel moderator, + required PostModel? post, + required CommentModel? comment, + required CommunityBanModel? ban, + }) = _ModlogItemModel; + + factory ModlogItemModel.fromMbin(JsonMap json) { + final type = ModLogType.values.byName((json['type'] as String).substring(4)); + + + return ModlogItemModel( + type: type, + createdAt: DateTime.parse(json['createdAt'] as String), + reason: '', + community: CommunityModel.fromMbin(json['magazine'] as JsonMap), + moderator: DetailedUserModel.fromMbin(json['moderator'] as JsonMap), + post: switch (type) { + ModLogType.all => null, + ModLogType.entry_deleted => PostModel.fromMbinEntry(json['subject'] as JsonMap), + ModLogType.entry_restored => PostModel.fromMbinEntry(json['subject'] as JsonMap), + ModLogType.entry_comment_deleted => null, + ModLogType.entry_comment_restored => null, + ModLogType.entry_pinned => null, + ModLogType.entry_unpinned => null, + ModLogType.post_deleted => PostModel.fromMbinPost(json['subject'] as JsonMap), + ModLogType.post_restored => PostModel.fromMbinPost(json['subject'] as JsonMap), + ModLogType.post_comment_deleted => null, + ModLogType.post_comment_restored => null, + ModLogType.ban => null, + ModLogType.unban => null, + ModLogType.moderator_add => null, + ModLogType.moderator_remove => null, + }, + comment: switch (type) { + ModLogType.all => null, + ModLogType.entry_deleted => null, + ModLogType.entry_restored => null, + ModLogType.entry_comment_deleted => CommentModel.fromMbin(json['subject'] as JsonMap), + ModLogType.entry_comment_restored => CommentModel.fromMbin(json['subject'] as JsonMap), + ModLogType.entry_pinned => null, + ModLogType.entry_unpinned => null, + ModLogType.post_deleted => null, + ModLogType.post_restored => null, + ModLogType.post_comment_deleted => CommentModel.fromMbin(json['subject'] as JsonMap), + ModLogType.post_comment_restored => CommentModel.fromMbin(json['subject'] as JsonMap), + ModLogType.ban => null, + ModLogType.unban => null, + ModLogType.moderator_add => null, + ModLogType.moderator_remove => null, + }, + ban: switch (type) { + ModLogType.all => null, + ModLogType.entry_deleted => null, + ModLogType.entry_restored => null, + ModLogType.entry_comment_deleted => null, + ModLogType.entry_comment_restored => null, + ModLogType.entry_pinned => null, + ModLogType.entry_unpinned => null, + ModLogType.post_deleted => null, + ModLogType.post_restored => null, + ModLogType.post_comment_deleted => null, + ModLogType.post_comment_restored => null, + ModLogType.ban => CommunityBanModel.fromMbin(json['subject'] as JsonMap), + ModLogType.unban => CommunityBanModel.fromMbin(json['subject'] as JsonMap), + ModLogType.moderator_add => null, + ModLogType.moderator_remove => null, + } + ); + } + + factory ModlogItemModel.fromLemmy(JsonMap json, { + required List<(String, int)> langCodeIdPairs, + }) { + + final type = ModLogType.values.byName(json['type'] as String); + + return ModlogItemModel( + type: type, + createdAt: DateTime.parse(json['createdAt'] as String), + reason: json['reason'] as String?, + community: CommunityModel.fromLemmy(json['community'] as JsonMap), + moderator: DetailedUserModel.fromLemmy(json['moderator'] as JsonMap), + post: json['post'] != null ? PostModel.fromLemmy({ + 'post_view': { + 'post': json['post'] as JsonMap, + 'community': json['community'] as JsonMap, + 'creator': json['moderator'] as JsonMap, + } + }, langCodeIdPairs: langCodeIdPairs) : null, + comment: json['comment'] != null ? CommentModel.fromLemmy(json, langCodeIdPairs: langCodeIdPairs) : null, + ban: type == ModLogType.ban ? CommunityBanModel.fromLemmy(json) : null, + ); + } +} + +@freezed +abstract class ModlogListModel with _$ModlogListModel { + const factory ModlogListModel({ + required List items, + required String? nextPage, + }) = _ModlogListModel; + + factory ModlogListModel.fromMbin(JsonMap json) => ModlogListModel( + items: (json['items'] as List) + .map((item) => ModlogItemModel.fromMbin(item as JsonMap)).toList(), + nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap) + ); + + factory ModlogListModel.fromLemmy(JsonMap json, { + required List<(String, int)> langCodeIdPairs, + }) { + + final removedPosts = (json['removed_posts'] as List) + .map((item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.entry_deleted.name, + 'createdAt': (item['mod_remove_post'] as JsonMap)['when_'] as String, + 'reason': (item['mod_remove_post'] as JsonMap)['reason'] as String, + ...item, + }, langCodeIdPairs: langCodeIdPairs)).toList(); + + final lockedPosts = (json['locked_posts'] as List).map((item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.entry_restored.name, + 'createdAt': (item['mod_lock_post'] as JsonMap)['when_'] as String, + ...item, + }, langCodeIdPairs: langCodeIdPairs)); + + final featuredPosts = (json['featured_posts'] as List).map((item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.entry_pinned.name, + 'createdAt': (item['mod_feature_post'] as JsonMap)['when_'] as String, + ...item, + }, langCodeIdPairs: langCodeIdPairs)); + + final removedComments = (json['removed_comments'] as List).map((item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.entry_comment_deleted.name, + 'createdAt': (item['mod_remove_comment'] as JsonMap)['when_'] as String, + 'reason': (item['mod_remove_comment'] as JsonMap)['reason'] as String, + ...item as JsonMap, + 'creator': {'person': item['commenter'] as JsonMap}, + }, langCodeIdPairs: langCodeIdPairs)); + + final modBannedCommunity = (json['banned_from_community'] as List).map((item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.ban.name, + 'createdAt': (item['mod_ban_from_community'] as JsonMap)['when_'] as String, + ...item, + 'reason': (item['mod_ban_from_community'] as JsonMap)['reason'] as String?, + 'expires': (item['mod_ban_from_community'] as JsonMap)['expires'] as String?, + }, langCodeIdPairs: langCodeIdPairs)); + + final items = [ + ...removedPosts, + ...lockedPosts, + ...featuredPosts, + ...removedComments, + ...modBannedCommunity, + ]; + + items.sort((a, b) => b.createdAt.compareTo(a.createdAt)); + + return ModlogListModel( + items: items, + nextPage: json['next_page'] as String? + ); + } +} \ No newline at end of file diff --git a/lib/src/models/post.dart b/lib/src/models/post.dart index dee4431c..78822d45 100644 --- a/lib/src/models/post.dart +++ b/lib/src/models/post.dart @@ -199,7 +199,7 @@ abstract class PostModel with _$PostModel { }) { final postView = json['post_view'] as JsonMap; final lemmyPost = postView['post'] as JsonMap; - final lemmyCounts = postView['counts'] as JsonMap; + final lemmyCounts = postView['counts'] as JsonMap?; final isImagePost = ((lemmyPost['url_content_type'] != null && @@ -231,9 +231,9 @@ abstract class PostModel with _$PostModel { .where((pair) => pair.$2 == lemmyPost['language_id'] as int) .firstOrNull ?.$1, - numComments: lemmyCounts['comments'] as int, - upvotes: lemmyCounts['upvotes'] as int, - downvotes: lemmyCounts['downvotes'] as int, + numComments: lemmyCounts?['comments'] as int? ?? 0, + upvotes: lemmyCounts?['upvotes'] as int? ?? 0, + downvotes: lemmyCounts?['downvotes'] as int? ?? 0, boosts: null, myVote: postView['my_vote'] as int?, myBoost: null, @@ -244,13 +244,13 @@ abstract class PostModel with _$PostModel { 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), + lastActive: lemmyCounts == null ? DateTime.now() : DateTime.parse(lemmyCounts['newest_comment_time'] as String), visibility: 'visible', canAuthUserModerate: null, notificationControlStatus: null, bookmarks: [ // Empty string indicates post is saved. No string indicates post is not saved. - if (postView['saved'] as bool) '', + if (((postView['saved'] as bool?) != null) ? postView['saved'] as bool : false) '', ], read: postView['read'] as bool? ?? false, crossPosts: diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart new file mode 100644 index 00000000..abe08a36 --- /dev/null +++ b/lib/src/screens/explore/mod_log.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/models/modlog.dart'; +import 'package:interstellar/src/screens/explore/community_screen.dart'; +import 'package:interstellar/src/screens/explore/user_item.dart'; +import 'package:interstellar/src/screens/explore/user_screen.dart'; +import 'package:interstellar/src/screens/feed/post_comment.dart'; +import 'package:interstellar/src/screens/feed/post_item.dart'; +import 'package:interstellar/src/screens/feed/post_page.dart'; +import 'package:interstellar/src/utils/utils.dart'; +import 'package:interstellar/src/widgets/content_item/content_info.dart'; +import 'package:interstellar/src/widgets/display_name.dart'; +import 'package:interstellar/src/widgets/paging.dart'; +import 'package:provider/provider.dart'; + +import '../../api/moderation.dart'; + +class ModLog extends StatefulWidget { + const ModLog({super.key, this.communityId}); + + final int? communityId; + + @override + State createState() => _ModLogState(); +} + +class _ModLogState extends State { + late final _pagingController = + AdvancedPagingController( + logger: context.read().logger, + firstPageKey: '', + getItemId: (item) => item.hashCode, + fetchPage: (pageKey) async { + final ac = context.read(); + + final newPage = await ac.api.moderation.modLog( + communityId: widget.communityId, + page: pageKey, + ); + + return (newPage.items, newPage.nextPage); + }, + ); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: Text('Modlog')), + body: AdvancedPagedScrollView( + controller: _pagingController, + itemBuilder: (context, item, index) { + return Card( + margin: const EdgeInsets.all(8), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + // mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: switch (item.type) { + ModLogType.all => Colors.white, + ModLogType.entry_deleted => Colors.red, + ModLogType.entry_restored => Colors.green, + ModLogType.entry_comment_deleted => Colors.red, + ModLogType.entry_comment_restored => Colors.green, + ModLogType.entry_pinned => Colors.orange, + ModLogType.entry_unpinned => Colors.orange, + ModLogType.post_deleted => Colors.red, + ModLogType.post_restored => Colors.green, + ModLogType.post_comment_deleted => Colors.red, + ModLogType.post_comment_restored => Colors.green, + ModLogType.ban => Colors.red, + ModLogType.unban => Colors.green, + ModLogType.moderator_add => Colors.orange, + ModLogType.moderator_remove => Colors.orange, + }, + borderRadius: BorderRadius.circular(10), + ), + child: Text(switch (item.type) { + ModLogType.all => '', + ModLogType.entry_deleted => 'Deleted post', + ModLogType.entry_restored => 'Restored post', + ModLogType.entry_comment_deleted => + 'Deleted comment', + ModLogType.entry_comment_restored => + 'Restored comment', + ModLogType.entry_pinned => 'Pinned post', + ModLogType.entry_unpinned => 'Unpinned post', + ModLogType.post_deleted => 'Deleted post', + ModLogType.post_restored => 'Restored post', + ModLogType.post_comment_deleted => + 'Deleted comment', + ModLogType.post_comment_restored => + 'Restored comment', + ModLogType.ban => 'Banned user', + ModLogType.unban => 'Unbanned user', + ModLogType.moderator_add => 'Added moderator', + ModLogType.moderator_remove => 'Removed moderator', + }), + ), + Padding( + padding: const EdgeInsets.only(left: 10), + child: ContentInfo( + user: item.moderator, + community: item.community, + createdAt: item.createdAt, + ), + ), + ], + ), + ), + if (item.post != null && item.comment == null) + PostItem( + item.post!, + (post) {}, + isCompact: false, + isPreview: true, + isTopLevel: true, + onTap: () => pushRoute( + context, + builder: (context) => PostPage( + postType: item.post!.type, + postId: item.post!.id, + initData: item.post, + ), + ), + ), + if (item.comment != null) + PostComment(item.comment!, (post) {}), + if (item.ban != null) UserItemSimple(item.ban!.bannedUser), + if (item.reason != null && item.reason!.isNotEmpty) + Text('Reason: ${item.reason!}'), + ], + ), + ), + ); + }, + ), + ); + } +} diff --git a/lib/src/screens/settings/about_screen.dart b/lib/src/screens/settings/about_screen.dart index c52afd26..b2528153 100644 --- a/lib/src/screens/settings/about_screen.dart +++ b/lib/src/screens/settings/about_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/screens/explore/community_screen.dart'; +import 'package:interstellar/src/screens/explore/mod_log.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/utils/globals.dart'; import 'package:interstellar/src/widgets/open_webpage.dart'; @@ -43,6 +44,14 @@ class _AboutScreenState extends State { builder: (context) => const DebugSettingsScreen(), ), ), + ListTile( + leading: const Icon(Symbols.add_moderator_rounded), + title: Text('Modlog'), + onTap: () => pushRoute( + context, + builder: (context) => ModLog() + ), + ), ListTile( leading: const Icon(Symbols.favorite_rounded), title: Text(l(context).settings_donate), diff --git a/lib/src/widgets/menus/community_menu.dart b/lib/src/widgets/menus/community_menu.dart index 49dda2c9..708cc33c 100644 --- a/lib/src/widgets/menus/community_menu.dart +++ b/lib/src/widgets/menus/community_menu.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:interstellar/src/api/community_moderation.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/models/community.dart'; +import 'package:interstellar/src/screens/explore/mod_log.dart'; import 'package:interstellar/src/screens/explore/community_screen.dart'; import 'package:interstellar/src/screens/explore/explore_screen.dart'; import 'package:interstellar/src/screens/explore/user_item.dart'; @@ -163,6 +165,13 @@ Future showCommunityMenu( ), ), ), + ContextMenuItem( + title: 'Modlog', + onTap: () => pushRoute( + context, + builder: (context) => ModLog(communityId: detailedCommunity?.id ?? community!.id) + ) + ), ], ).openMenu(context); } From e019e0f05233daa89423757fd325018f4b6ffe8d Mon Sep 17 00:00:00 2001 From: olorin99 Date: Sat, 20 Dec 2025 15:32:21 +1000 Subject: [PATCH 02/18] Add localisations. --- lib/l10n/app_en.arb | 21 +++++++++++- lib/src/models/modlog.dart | 1 - lib/src/screens/explore/mod_log.dart | 39 +++++++++------------- lib/src/screens/settings/about_screen.dart | 4 +-- lib/src/widgets/menus/community_menu.dart | 1 - 5 files changed, 38 insertions(+), 28 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index bbe891c3..5cc91cfd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -836,5 +836,24 @@ } }, "pushNotificationsDialog_selectDistributor": "Select push distributor", - "crossPost": "Cross post" + "crossPost": "Cross post", + "modlog": "Modlog", + "modlog_deletedPost": "Deleted post", + "modlog_restoredPost": "Restored post", + "modlog_deletedComment": "Deleted comment", + "modlog_restoredComment": "Restored comment", + "modlog_pinnedPost": "Pinned post", + "modlog_unpinnedPost": "Unpinned post", + "modlog_bannedUser": "Banned user", + "modlog_unbannedUser": "Unbanned user", + "modlog_addModerator": "Added moderator", + "modlog_removedModerator": "Removed moderator", + "modlog_reason": "Reason: {reason}", + "@modlog_reason": { + "placeholders": { + "reason": { + "type": "String" + } + } + } } diff --git a/lib/src/models/modlog.dart b/lib/src/models/modlog.dart index 75cd2201..c6963d56 100644 --- a/lib/src/models/modlog.dart +++ b/lib/src/models/modlog.dart @@ -1,5 +1,4 @@ import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:interstellar/src/api/community_moderation.dart'; import 'package:interstellar/src/models/comment.dart'; import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/models/post.dart'; diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index abe08a36..b1460e79 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -1,15 +1,12 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/models/modlog.dart'; -import 'package:interstellar/src/screens/explore/community_screen.dart'; import 'package:interstellar/src/screens/explore/user_item.dart'; -import 'package:interstellar/src/screens/explore/user_screen.dart'; import 'package:interstellar/src/screens/feed/post_comment.dart'; import 'package:interstellar/src/screens/feed/post_item.dart'; import 'package:interstellar/src/screens/feed/post_page.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/content_item/content_info.dart'; -import 'package:interstellar/src/widgets/display_name.dart'; import 'package:interstellar/src/widgets/paging.dart'; import 'package:provider/provider.dart'; @@ -45,7 +42,7 @@ class _ModLogState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text('Modlog')), + appBar: AppBar(title: Text(l(context).modlog)), body: AdvancedPagedScrollView( controller: _pagingController, itemBuilder: (context, item, index) { @@ -86,24 +83,20 @@ class _ModLogState extends State { ), child: Text(switch (item.type) { ModLogType.all => '', - ModLogType.entry_deleted => 'Deleted post', - ModLogType.entry_restored => 'Restored post', - ModLogType.entry_comment_deleted => - 'Deleted comment', - ModLogType.entry_comment_restored => - 'Restored comment', - ModLogType.entry_pinned => 'Pinned post', - ModLogType.entry_unpinned => 'Unpinned post', - ModLogType.post_deleted => 'Deleted post', - ModLogType.post_restored => 'Restored post', - ModLogType.post_comment_deleted => - 'Deleted comment', - ModLogType.post_comment_restored => - 'Restored comment', - ModLogType.ban => 'Banned user', - ModLogType.unban => 'Unbanned user', - ModLogType.moderator_add => 'Added moderator', - ModLogType.moderator_remove => 'Removed moderator', + ModLogType.entry_deleted => l(context).modlog_deletedPost, + ModLogType.entry_restored => l(context).modlog_restoredPost, + ModLogType.entry_comment_deleted => l(context).modlog_deletedComment, + ModLogType.entry_comment_restored => l(context).modlog_restoredComment, + ModLogType.entry_pinned => l(context).modlog_pinnedPost, + ModLogType.entry_unpinned => l(context).modlog_unpinnedPost, + ModLogType.post_deleted => l(context).modlog_deletedPost, + ModLogType.post_restored => l(context).modlog_restoredPost, + ModLogType.post_comment_deleted => l(context).modlog_deletedComment, + ModLogType.post_comment_restored => l(context).modlog_restoredComment, + ModLogType.ban => l(context).modlog_bannedUser, + ModLogType.unban => l(context).modlog_unbannedUser, + ModLogType.moderator_add => l(context).modlog_addModerator, + ModLogType.moderator_remove => l(context).modlog_removedModerator, }), ), Padding( @@ -137,7 +130,7 @@ class _ModLogState extends State { PostComment(item.comment!, (post) {}), if (item.ban != null) UserItemSimple(item.ban!.bannedUser), if (item.reason != null && item.reason!.isNotEmpty) - Text('Reason: ${item.reason!}'), + Text(l(context).modlog_reason(item.reason!)), ], ), ), diff --git a/lib/src/screens/settings/about_screen.dart b/lib/src/screens/settings/about_screen.dart index b2528153..ce4ecb70 100644 --- a/lib/src/screens/settings/about_screen.dart +++ b/lib/src/screens/settings/about_screen.dart @@ -45,8 +45,8 @@ class _AboutScreenState extends State { ), ), ListTile( - leading: const Icon(Symbols.add_moderator_rounded), - title: Text('Modlog'), + leading: const Icon(Symbols.shield_rounded), + title: Text(l(context).modlog), onTap: () => pushRoute( context, builder: (context) => ModLog() diff --git a/lib/src/widgets/menus/community_menu.dart b/lib/src/widgets/menus/community_menu.dart index 708cc33c..0647be42 100644 --- a/lib/src/widgets/menus/community_menu.dart +++ b/lib/src/widgets/menus/community_menu.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:interstellar/src/api/community_moderation.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/models/community.dart'; From cc39c3fa7d43580b1c1f2f4203d25088f4c6e4f2 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Mon, 22 Dec 2025 14:01:51 +1000 Subject: [PATCH 03/18] Change so works better with lemmy. --- lib/src/api/moderation.dart | 95 ++++++--- lib/src/models/community.dart | 4 +- lib/src/models/modlog.dart | 308 +++++++++++++++++---------- lib/src/screens/explore/mod_log.dart | 90 +++++--- 4 files changed, 325 insertions(+), 172 deletions(-) diff --git a/lib/src/api/moderation.dart b/lib/src/api/moderation.dart index a81979e9..5319f215 100644 --- a/lib/src/api/moderation.dart +++ b/lib/src/api/moderation.dart @@ -13,37 +13,79 @@ const _postTypeMbinComment = { enum ModLogType { all, - entry_deleted, - entry_restored, - entry_comment_deleted, - entry_comment_restored, - entry_pinned, - entry_unpinned, + postDeleted, + postRestored, + commentDeleted, + commentRestored, + postPinned, + postUnpinned, post_deleted, post_restored, post_comment_deleted, post_comment_restored, ban, unban, - moderator_add, - moderator_remove; + moderatorAdded, + moderatorRemoved, + communityAdded, + communityRemoved; + + static ModLogType fromMbin(String type) => switch (type) { + 'log_entry_deleted' => ModLogType.postDeleted, + 'log_entry_restored' => ModLogType.postRestored, + 'log_entry_comment_deleted' => ModLogType.commentDeleted, + 'log_entry_comment_restored' => ModLogType.commentRestored, + 'log_entry_pinned' => ModLogType.postPinned, + 'log_entry_unpinned' => ModLogType.postUnpinned, + 'log_post_deleted' => ModLogType.post_deleted, + 'log_post_restored' => ModLogType.post_restored, + 'log_post_comment_deleted' => ModLogType.post_comment_deleted, + 'log_post_comment_restored' => ModLogType.post_comment_restored, + 'log_ban' => ModLogType.ban, + 'log_unban' => ModLogType.unban, + 'log_moderator_add' => ModLogType.moderatorAdded, + 'log_moderator_remove' => ModLogType.moderatorRemoved, + String() => ModLogType.all, + }; + + String get toMbin => switch (this) { + ModLogType.all => 'all', + ModLogType.postDeleted => 'entry_deleted', + ModLogType.postRestored => 'entry_restored', + ModLogType.commentDeleted => 'entry_comment_deleted', + ModLogType.commentRestored => 'entry_comment_restored', + ModLogType.postPinned => 'entry_pinned', + ModLogType.postUnpinned => 'entry_unpinned', + ModLogType.post_deleted => 'post_deleted', + ModLogType.post_restored => 'post_restored', + ModLogType.post_comment_deleted => 'post_comment_deleted', + ModLogType.post_comment_restored => 'post_comment_restored', + ModLogType.ban => 'ban', + ModLogType.unban => 'unban', + ModLogType.moderatorAdded => 'moderator_add', + ModLogType.moderatorRemoved => 'moderator_remove', + ModLogType.communityAdded => 'all', + ModLogType.communityRemoved => 'all', + }; String get toLemmy => switch (this) { ModLogType.all => 'All', - ModLogType.entry_deleted => 'ModRemovePost', - ModLogType.entry_restored => 'All', - ModLogType.entry_comment_deleted => 'ModRemoveComment', - ModLogType.entry_comment_restored => 'All', - ModLogType.entry_pinned => 'ModFeaturePost', - ModLogType.entry_unpinned => 'All', + ModLogType.postDeleted => 'ModRemovePost', + ModLogType.postRestored => 'All', + ModLogType.commentDeleted => 'ModRemoveComment', + ModLogType.commentRestored => 'All', + ModLogType.postPinned => 'ModFeaturePost', + ModLogType.postUnpinned => 'All', ModLogType.post_deleted => 'ModRemovePost', ModLogType.post_restored => 'All', ModLogType.post_comment_deleted => 'ModRemoveComment', ModLogType.post_comment_restored => 'All', ModLogType.ban => 'ModBan', ModLogType.unban => 'All', - ModLogType.moderator_add => 'ModAdd', - ModLogType.moderator_remove => 'All', + ModLogType.moderatorAdded => 'ModAdd', + ModLogType.moderatorRemoved => 'All', + ModLogType.communityAdded => 'ModAddCommunity', + ModLogType.communityRemoved => 'ModRemoveCommunity', }; } @@ -190,8 +232,6 @@ class APIModeration { } } - - Future modLog({ int? communityId, int? userId, @@ -200,33 +240,32 @@ class APIModeration { }) async { switch (client.software) { case ServerSoftware.mbin: - if (communityId != null) { final path = '/magazine/$communityId/log'; - final query = { - 'p': page, - }; + final query = {'p': page}; final response = await client.get(path, queryParams: query); return ModlogListModel.fromMbin(response.bodyJson); } final path = '/modlog'; - final query = { - 'p': page, - }; + final query = {'p': page}; final response = await client.get(path, queryParams: query); return ModlogListModel.fromMbin(response.bodyJson); case ServerSoftware.lemmy: const path = '/modlog'; final query = { - if (communityId != null) - 'community_id': communityId.toString(), + if (communityId != null) 'community_id': communityId.toString(), 'page': page, 'type_': type.toLemmy, }; final response = await client.get(path, queryParams: query); final json = response.bodyJson; - return ModlogListModel.fromLemmy(json, langCodeIdPairs: await client.languageCodeIdPairs()); + return ModlogListModel.fromLemmy({ + 'next_page': + (int.parse(((page?.isNotEmpty ?? false) ? page : '0') ?? '0') + 1) + .toString(), + ...json, + }, langCodeIdPairs: await client.languageCodeIdPairs()); case ServerSoftware.piefed: throw UnimplementedError('Not yet implemented for PieFed'); diff --git a/lib/src/models/community.dart b/lib/src/models/community.dart index 43e830b1..aa1501a5 100644 --- a/lib/src/models/community.dart +++ b/lib/src/models/community.dart @@ -262,7 +262,7 @@ abstract class CommunityBanModel with _$CommunityBanModel { required DateTime? expiresAt, required CommunityModel community, required UserModel bannedUser, - required UserModel bannedBy, + required UserModel? bannedBy, required bool expired, }) = _CommunityBanModel; @@ -283,7 +283,7 @@ abstract class CommunityBanModel with _$CommunityBanModel { expiresAt: expiration, community: CommunityModel.fromLemmy(json['community'] as JsonMap), bannedUser: UserModel.fromLemmy(json['banned_person'] as JsonMap), - bannedBy: UserModel.fromLemmy(json['moderator'] as JsonMap), + bannedBy: json['moderator'] != null ? UserModel.fromLemmy(json['moderator'] as JsonMap) : null, expired: expiration?.isBefore(DateTime.now()) ?? false, ); } diff --git a/lib/src/models/modlog.dart b/lib/src/models/modlog.dart index c6963d56..cab14dd9 100644 --- a/lib/src/models/modlog.dart +++ b/lib/src/models/modlog.dart @@ -1,7 +1,6 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:interstellar/src/models/comment.dart'; import 'package:interstellar/src/models/community.dart'; -import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/utils/models.dart'; import 'package:interstellar/src/utils/utils.dart'; @@ -17,97 +16,141 @@ abstract class ModlogItemModel with _$ModlogItemModel { required DateTime createdAt, required String? reason, required CommunityModel community, - required DetailedUserModel moderator, - required PostModel? post, + required DetailedUserModel? moderator, + required int? postId, + required String? postTitle, required CommentModel? comment, required CommunityBanModel? ban, }) = _ModlogItemModel; factory ModlogItemModel.fromMbin(JsonMap json) { - final type = ModLogType.values.byName((json['type'] as String).substring(4)); - + final type = ModLogType.fromMbin(json['type'] as String); return ModlogItemModel( - type: type, - createdAt: DateTime.parse(json['createdAt'] as String), - reason: '', - community: CommunityModel.fromMbin(json['magazine'] as JsonMap), - moderator: DetailedUserModel.fromMbin(json['moderator'] as JsonMap), - post: switch (type) { - ModLogType.all => null, - ModLogType.entry_deleted => PostModel.fromMbinEntry(json['subject'] as JsonMap), - ModLogType.entry_restored => PostModel.fromMbinEntry(json['subject'] as JsonMap), - ModLogType.entry_comment_deleted => null, - ModLogType.entry_comment_restored => null, - ModLogType.entry_pinned => null, - ModLogType.entry_unpinned => null, - ModLogType.post_deleted => PostModel.fromMbinPost(json['subject'] as JsonMap), - ModLogType.post_restored => PostModel.fromMbinPost(json['subject'] as JsonMap), - ModLogType.post_comment_deleted => null, - ModLogType.post_comment_restored => null, - ModLogType.ban => null, - ModLogType.unban => null, - ModLogType.moderator_add => null, - ModLogType.moderator_remove => null, - }, - comment: switch (type) { - ModLogType.all => null, - ModLogType.entry_deleted => null, - ModLogType.entry_restored => null, - ModLogType.entry_comment_deleted => CommentModel.fromMbin(json['subject'] as JsonMap), - ModLogType.entry_comment_restored => CommentModel.fromMbin(json['subject'] as JsonMap), - ModLogType.entry_pinned => null, - ModLogType.entry_unpinned => null, - ModLogType.post_deleted => null, - ModLogType.post_restored => null, - ModLogType.post_comment_deleted => CommentModel.fromMbin(json['subject'] as JsonMap), - ModLogType.post_comment_restored => CommentModel.fromMbin(json['subject'] as JsonMap), - ModLogType.ban => null, - ModLogType.unban => null, - ModLogType.moderator_add => null, - ModLogType.moderator_remove => null, - }, + type: type, + createdAt: DateTime.parse(json['createdAt'] as String), + reason: '', + community: CommunityModel.fromMbin(json['magazine'] as JsonMap), + moderator: DetailedUserModel.fromMbin(json['moderator'] as JsonMap), + postId: switch (type) { + ModLogType.all => null, + ModLogType.postDeleted => + (json['subject'] as JsonMap)['entryId'] as int, + ModLogType.postRestored => + (json['subject'] as JsonMap)['entryId'] as int, + ModLogType.commentDeleted => null, + ModLogType.commentRestored => null, + ModLogType.postPinned => null, + ModLogType.postUnpinned => null, + ModLogType.post_deleted => + (json['subject'] as JsonMap)['postId'] as int, + ModLogType.post_restored => + (json['subject'] as JsonMap)['postId'] as int, + ModLogType.post_comment_deleted => null, + ModLogType.post_comment_restored => null, + ModLogType.ban => null, + ModLogType.unban => null, + ModLogType.moderatorAdded => null, + ModLogType.moderatorRemoved => null, + ModLogType.communityAdded => null, + ModLogType.communityRemoved => null, + }, + postTitle: switch (type) { + ModLogType.all => null, + ModLogType.postDeleted => + (json['subject'] as JsonMap)['title'] as String, + ModLogType.postRestored => + (json['subject'] as JsonMap)['title'] as String, + ModLogType.commentDeleted => null, + ModLogType.commentRestored => null, + ModLogType.postPinned => null, + ModLogType.postUnpinned => null, + ModLogType.post_deleted => + (json['subject'] as JsonMap)['title'] as String, + ModLogType.post_restored => + (json['subject'] as JsonMap)['title'] as String, + ModLogType.post_comment_deleted => null, + ModLogType.post_comment_restored => null, + ModLogType.ban => null, + ModLogType.unban => null, + ModLogType.moderatorAdded => null, + ModLogType.moderatorRemoved => null, + ModLogType.communityAdded => null, + ModLogType.communityRemoved => null, + }, + comment: switch (type) { + ModLogType.all => null, + ModLogType.postDeleted => null, + ModLogType.postRestored => null, + ModLogType.commentDeleted => CommentModel.fromMbin( + json['subject'] as JsonMap, + ), + ModLogType.commentRestored => CommentModel.fromMbin( + json['subject'] as JsonMap, + ), + ModLogType.postPinned => null, + ModLogType.postUnpinned => null, + ModLogType.post_deleted => null, + ModLogType.post_restored => null, + ModLogType.post_comment_deleted => CommentModel.fromMbin( + json['subject'] as JsonMap, + ), + ModLogType.post_comment_restored => CommentModel.fromMbin( + json['subject'] as JsonMap, + ), + ModLogType.ban => null, + ModLogType.unban => null, + ModLogType.moderatorAdded => null, + ModLogType.moderatorRemoved => null, + ModLogType.communityAdded => null, + ModLogType.communityRemoved => null, + }, ban: switch (type) { ModLogType.all => null, - ModLogType.entry_deleted => null, - ModLogType.entry_restored => null, - ModLogType.entry_comment_deleted => null, - ModLogType.entry_comment_restored => null, - ModLogType.entry_pinned => null, - ModLogType.entry_unpinned => null, + ModLogType.postDeleted => null, + ModLogType.postRestored => null, + ModLogType.commentDeleted => null, + ModLogType.commentRestored => null, + ModLogType.postPinned => null, + ModLogType.postUnpinned => null, ModLogType.post_deleted => null, ModLogType.post_restored => null, ModLogType.post_comment_deleted => null, ModLogType.post_comment_restored => null, - ModLogType.ban => CommunityBanModel.fromMbin(json['subject'] as JsonMap), - ModLogType.unban => CommunityBanModel.fromMbin(json['subject'] as JsonMap), - ModLogType.moderator_add => null, - ModLogType.moderator_remove => null, - } + ModLogType.ban => CommunityBanModel.fromMbin( + json['subject'] as JsonMap, + ), + ModLogType.unban => CommunityBanModel.fromMbin( + json['subject'] as JsonMap, + ), + ModLogType.moderatorAdded => null, + ModLogType.moderatorRemoved => null, + ModLogType.communityAdded => null, + ModLogType.communityRemoved => null, + }, ); } - factory ModlogItemModel.fromLemmy(JsonMap json, { + factory ModlogItemModel.fromLemmy( + JsonMap json, { required List<(String, int)> langCodeIdPairs, }) { - final type = ModLogType.values.byName(json['type'] as String); return ModlogItemModel( - type: type, - createdAt: DateTime.parse(json['createdAt'] as String), - reason: json['reason'] as String?, - community: CommunityModel.fromLemmy(json['community'] as JsonMap), - moderator: DetailedUserModel.fromLemmy(json['moderator'] as JsonMap), - post: json['post'] != null ? PostModel.fromLemmy({ - 'post_view': { - 'post': json['post'] as JsonMap, - 'community': json['community'] as JsonMap, - 'creator': json['moderator'] as JsonMap, - } - }, langCodeIdPairs: langCodeIdPairs) : null, - comment: json['comment'] != null ? CommentModel.fromLemmy(json, langCodeIdPairs: langCodeIdPairs) : null, - ban: type == ModLogType.ban ? CommunityBanModel.fromLemmy(json) : null, + type: type, + createdAt: DateTime.parse(json['createdAt'] as String), + reason: json['reason'] as String?, + community: CommunityModel.fromLemmy(json['community'] as JsonMap), + moderator: json['moderator'] != null + ? DetailedUserModel.fromLemmy(json['moderator'] as JsonMap) + : null, + postId: (json['post'] as JsonMap?)?['id'] as int?, + postTitle: (json['post'] as JsonMap?)?['name'] as String?, + comment: json['comment'] != null + ? CommentModel.fromLemmy(json, langCodeIdPairs: langCodeIdPairs) + : null, + ban: type == ModLogType.ban ? CommunityBanModel.fromLemmy(json) : null, ); } } @@ -121,63 +164,106 @@ abstract class ModlogListModel with _$ModlogListModel { factory ModlogListModel.fromMbin(JsonMap json) => ModlogListModel( items: (json['items'] as List) - .map((item) => ModlogItemModel.fromMbin(item as JsonMap)).toList(), - nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap) + .map((item) => ModlogItemModel.fromMbin(item as JsonMap)) + .toList(), + nextPage: mbinCalcNextPaginationPage(json['pagination'] as JsonMap), ); - factory ModlogListModel.fromLemmy(JsonMap json, { + factory ModlogListModel.fromLemmy( + JsonMap json, { required List<(String, int)> langCodeIdPairs, }) { - final removedPosts = (json['removed_posts'] as List) - .map((item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.entry_deleted.name, - 'createdAt': (item['mod_remove_post'] as JsonMap)['when_'] as String, - 'reason': (item['mod_remove_post'] as JsonMap)['reason'] as String, - ...item, - }, langCodeIdPairs: langCodeIdPairs)).toList(); - - final lockedPosts = (json['locked_posts'] as List).map((item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.entry_restored.name, - 'createdAt': (item['mod_lock_post'] as JsonMap)['when_'] as String, - ...item, - }, langCodeIdPairs: langCodeIdPairs)); - - final featuredPosts = (json['featured_posts'] as List).map((item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.entry_pinned.name, - 'createdAt': (item['mod_feature_post'] as JsonMap)['when_'] as String, - ...item, - }, langCodeIdPairs: langCodeIdPairs)); - - final removedComments = (json['removed_comments'] as List).map((item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.entry_comment_deleted.name, - 'createdAt': (item['mod_remove_comment'] as JsonMap)['when_'] as String, - 'reason': (item['mod_remove_comment'] as JsonMap)['reason'] as String, - ...item as JsonMap, - 'creator': {'person': item['commenter'] as JsonMap}, - }, langCodeIdPairs: langCodeIdPairs)); - - final modBannedCommunity = (json['banned_from_community'] as List).map((item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.ban.name, - 'createdAt': (item['mod_ban_from_community'] as JsonMap)['when_'] as String, - ...item, - 'reason': (item['mod_ban_from_community'] as JsonMap)['reason'] as String?, - 'expires': (item['mod_ban_from_community'] as JsonMap)['expires'] as String?, - }, langCodeIdPairs: langCodeIdPairs)); + .map( + (item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.postDeleted.name, + 'createdAt': + (item['mod_remove_post'] as JsonMap)['when_'] as String, + 'reason': (item['mod_remove_post'] as JsonMap)['reason'] as String?, + ...item, + }, langCodeIdPairs: langCodeIdPairs), + ) + .toList(); + + final lockedPosts = (json['locked_posts'] as List).map( + (item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.postRestored.name, + 'createdAt': (item['mod_lock_post'] as JsonMap)['when_'] as String, + ...item, + }, langCodeIdPairs: langCodeIdPairs), + ); + + final featuredPosts = (json['featured_posts'] as List).map( + (item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.postPinned.name, + 'createdAt': (item['mod_feature_post'] as JsonMap)['when_'] as String, + ...item, + }, langCodeIdPairs: langCodeIdPairs), + ); + + final removedComments = (json['removed_comments'] as List).map( + (item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.commentDeleted.name, + 'createdAt': (item['mod_remove_comment'] as JsonMap)['when_'] as String, + 'reason': (item['mod_remove_comment'] as JsonMap)['reason'] as String, + ...item as JsonMap, + 'creator': {'person': item['commenter'] as JsonMap}, + }, langCodeIdPairs: langCodeIdPairs), + ); + + final removedCommunities = (json['removed_communities'] as List) + .map( + (item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.communityRemoved.name, + 'createdAt': + (item['mod_remove_community'] as JsonMap)['when_'] as String, + ...item as JsonMap, + }, langCodeIdPairs: langCodeIdPairs), + ); + + final modBannedCommunity = (json['banned_from_community'] as List) + .map( + (item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.ban.name, + 'createdAt': + (item['mod_ban_from_community'] as JsonMap)['when_'] as String, + ...item, + 'reason': + (item['mod_ban_from_community'] as JsonMap)['reason'] + as String?, + 'expires': + (item['mod_ban_from_community'] as JsonMap)['expires'] + as String?, + }, langCodeIdPairs: langCodeIdPairs), + ); + + final modAddedToCommunity = (json['added_to_community'] as List) + .map( + (item) => ModlogItemModel.fromLemmy({ + 'type': ModLogType.moderatorAdded.name, + 'createdAt': + (item['mod_add_community'] as JsonMap)['when_'] as String, + ...item, + 'expires': + (item['mod_add_community'] as JsonMap)['expires'] as String?, + }, langCodeIdPairs: langCodeIdPairs), + ); final items = [ ...removedPosts, ...lockedPosts, ...featuredPosts, ...removedComments, + ...removedCommunities, ...modBannedCommunity, + ...modAddedToCommunity, ]; items.sort((a, b) => b.createdAt.compareTo(a.createdAt)); return ModlogListModel( items: items, - nextPage: json['next_page'] as String? + nextPage: json['next_page'] as String?, ); } -} \ No newline at end of file +} diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index b1460e79..c68bd273 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/models/modlog.dart'; +import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/screens/explore/user_item.dart'; import 'package:interstellar/src/screens/feed/post_comment.dart'; -import 'package:interstellar/src/screens/feed/post_item.dart'; import 'package:interstellar/src/screens/feed/post_page.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/content_item/content_info.dart'; @@ -64,39 +64,67 @@ class _ModLogState extends State { decoration: BoxDecoration( color: switch (item.type) { ModLogType.all => Colors.white, - ModLogType.entry_deleted => Colors.red, - ModLogType.entry_restored => Colors.green, - ModLogType.entry_comment_deleted => Colors.red, - ModLogType.entry_comment_restored => Colors.green, - ModLogType.entry_pinned => Colors.orange, - ModLogType.entry_unpinned => Colors.orange, + ModLogType.postDeleted => Colors.red, + ModLogType.postRestored => Colors.green, + ModLogType.commentDeleted => Colors.red, + ModLogType.commentRestored => Colors.green, + ModLogType.postPinned => Colors.orange, + ModLogType.postUnpinned => Colors.orange, ModLogType.post_deleted => Colors.red, ModLogType.post_restored => Colors.green, ModLogType.post_comment_deleted => Colors.red, ModLogType.post_comment_restored => Colors.green, ModLogType.ban => Colors.red, ModLogType.unban => Colors.green, - ModLogType.moderator_add => Colors.orange, - ModLogType.moderator_remove => Colors.orange, + ModLogType.moderatorAdded => Colors.orange, + ModLogType.moderatorRemoved => Colors.orange, + ModLogType.communityAdded => Colors.green, + ModLogType.communityRemoved => Colors.red, }, borderRadius: BorderRadius.circular(10), ), child: Text(switch (item.type) { ModLogType.all => '', - ModLogType.entry_deleted => l(context).modlog_deletedPost, - ModLogType.entry_restored => l(context).modlog_restoredPost, - ModLogType.entry_comment_deleted => l(context).modlog_deletedComment, - ModLogType.entry_comment_restored => l(context).modlog_restoredComment, - ModLogType.entry_pinned => l(context).modlog_pinnedPost, - ModLogType.entry_unpinned => l(context).modlog_unpinnedPost, - ModLogType.post_deleted => l(context).modlog_deletedPost, - ModLogType.post_restored => l(context).modlog_restoredPost, - ModLogType.post_comment_deleted => l(context).modlog_deletedComment, - ModLogType.post_comment_restored => l(context).modlog_restoredComment, + ModLogType.postDeleted => l( + context, + ).modlog_deletedPost, + ModLogType.postRestored => l( + context, + ).modlog_restoredPost, + ModLogType.commentDeleted => l( + context, + ).modlog_deletedComment, + ModLogType.commentRestored => l( + context, + ).modlog_restoredComment, + ModLogType.postPinned => l( + context, + ).modlog_pinnedPost, + ModLogType.postUnpinned => l( + context, + ).modlog_unpinnedPost, + ModLogType.post_deleted => l( + context, + ).modlog_deletedPost, + ModLogType.post_restored => l( + context, + ).modlog_restoredPost, + ModLogType.post_comment_deleted => l( + context, + ).modlog_deletedComment, + ModLogType.post_comment_restored => l( + context, + ).modlog_restoredComment, ModLogType.ban => l(context).modlog_bannedUser, ModLogType.unban => l(context).modlog_unbannedUser, - ModLogType.moderator_add => l(context).modlog_addModerator, - ModLogType.moderator_remove => l(context).modlog_removedModerator, + ModLogType.moderatorAdded => l( + context, + ).modlog_addModerator, + ModLogType.moderatorRemoved => l( + context, + ).modlog_removedModerator, + ModLogType.communityAdded => 'Community added', + ModLogType.communityRemoved => 'Community removed', }), ), Padding( @@ -110,19 +138,19 @@ class _ModLogState extends State { ], ), ), - if (item.post != null && item.comment == null) - PostItem( - item.post!, - (post) {}, - isCompact: false, - isPreview: true, - isTopLevel: true, + if (item.postId != null && + item.postTitle != null && + item.comment == null) + InkWell( + child: Text( + item.postTitle!, + style: Theme.of(context).textTheme.titleLarge, + ), onTap: () => pushRoute( context, builder: (context) => PostPage( - postType: item.post!.type, - postId: item.post!.id, - initData: item.post, + postType: PostType.thread, + postId: item.postId, ), ), ), From 0e613ccfdb82e16a0a1b3c9bbc9e3602dfdf9bc0 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Mon, 22 Dec 2025 15:36:24 +1000 Subject: [PATCH 04/18] Make modlog item card tappable. --- lib/src/models/modlog.dart | 2 +- lib/src/screens/explore/mod_log.dart | 318 ++++++++++++++++--------- lib/src/screens/explore/user_item.dart | 2 +- 3 files changed, 214 insertions(+), 108 deletions(-) diff --git a/lib/src/models/modlog.dart b/lib/src/models/modlog.dart index cab14dd9..6ff920c2 100644 --- a/lib/src/models/modlog.dart +++ b/lib/src/models/modlog.dart @@ -205,7 +205,7 @@ abstract class ModlogListModel with _$ModlogListModel { (item) => ModlogItemModel.fromLemmy({ 'type': ModLogType.commentDeleted.name, 'createdAt': (item['mod_remove_comment'] as JsonMap)['when_'] as String, - 'reason': (item['mod_remove_comment'] as JsonMap)['reason'] as String, + 'reason': (item['mod_remove_comment'] as JsonMap)['reason'] as String?, ...item as JsonMap, 'creator': {'person': item['commenter'] as JsonMap}, }, langCodeIdPairs: langCodeIdPairs), diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index c68bd273..0069dd0c 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -3,7 +3,8 @@ import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/models/modlog.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/screens/explore/user_item.dart'; -import 'package:interstellar/src/screens/feed/post_comment.dart'; +import 'package:interstellar/src/screens/explore/user_screen.dart'; +import 'package:interstellar/src/screens/feed/post_comment_screen.dart'; import 'package:interstellar/src/screens/feed/post_page.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/content_item/content_info.dart'; @@ -39,6 +40,108 @@ class _ModLogState extends State { }, ); + Function()? _itemOnTap(ModlogItemModel item) => switch (item.type) { + ModLogType.all => null, + ModLogType.postDeleted => + item.postId == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostPage(postType: PostType.thread, postId: item.postId), + ), + ModLogType.postRestored => + item.postId == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostPage(postType: PostType.thread, postId: item.postId), + ), + ModLogType.commentDeleted => + item.comment == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostCommentScreen(PostType.thread, item.comment!.id), + ), + ModLogType.commentRestored => + item.comment == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostCommentScreen(PostType.thread, item.comment!.id), + ), + ModLogType.postPinned => + item.postId == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostPage(postType: PostType.thread, postId: item.postId), + ), + ModLogType.postUnpinned => + item.postId == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostPage(postType: PostType.thread, postId: item.postId), + ), + ModLogType.post_deleted => + item.postId == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostPage(postType: PostType.thread, postId: item.postId), + ), + ModLogType.post_restored => + item.postId == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostPage(postType: PostType.thread, postId: item.postId), + ), + ModLogType.post_comment_deleted => + item.comment == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostCommentScreen(PostType.thread, item.comment!.id), + ), + ModLogType.post_comment_restored => + item.comment == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostCommentScreen(PostType.thread, item.comment!.id), + ), + ModLogType.ban => + item.ban == null + ? null + : () => pushRoute( + context, + builder: (context) => UserScreen(item.ban!.bannedUser.id), + ), + ModLogType.unban => + item.ban == null + ? null + : () => pushRoute( + context, + builder: (context) => UserScreen(item.ban!.bannedUser.id), + ), + ModLogType.moderatorAdded => null, + ModLogType.moderatorRemoved => null, + ModLogType.communityAdded => null, + ModLogType.communityRemoved => null, + }; + @override Widget build(BuildContext context) { return Scaffold( @@ -48,118 +151,121 @@ class _ModLogState extends State { itemBuilder: (context, item, index) { return Card( margin: const EdgeInsets.all(8), - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 8), - child: Row( - // mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: switch (item.type) { - ModLogType.all => Colors.white, - ModLogType.postDeleted => Colors.red, - ModLogType.postRestored => Colors.green, - ModLogType.commentDeleted => Colors.red, - ModLogType.commentRestored => Colors.green, - ModLogType.postPinned => Colors.orange, - ModLogType.postUnpinned => Colors.orange, - ModLogType.post_deleted => Colors.red, - ModLogType.post_restored => Colors.green, - ModLogType.post_comment_deleted => Colors.red, - ModLogType.post_comment_restored => Colors.green, - ModLogType.ban => Colors.red, - ModLogType.unban => Colors.green, - ModLogType.moderatorAdded => Colors.orange, - ModLogType.moderatorRemoved => Colors.orange, - ModLogType.communityAdded => Colors.green, - ModLogType.communityRemoved => Colors.red, - }, - borderRadius: BorderRadius.circular(10), + child: InkWell( + onTap: _itemOnTap(item), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Row( + // mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: switch (item.type) { + ModLogType.all => Colors.white, + ModLogType.postDeleted => Colors.red, + ModLogType.postRestored => Colors.green, + ModLogType.commentDeleted => Colors.red, + ModLogType.commentRestored => Colors.green, + ModLogType.postPinned => Colors.orange, + ModLogType.postUnpinned => Colors.orange, + ModLogType.post_deleted => Colors.red, + ModLogType.post_restored => Colors.green, + ModLogType.post_comment_deleted => Colors.red, + ModLogType.post_comment_restored => + Colors.green, + ModLogType.ban => Colors.red, + ModLogType.unban => Colors.green, + ModLogType.moderatorAdded => Colors.orange, + ModLogType.moderatorRemoved => Colors.orange, + ModLogType.communityAdded => Colors.green, + ModLogType.communityRemoved => Colors.red, + }, + borderRadius: BorderRadius.circular(10), + ), + child: Text(switch (item.type) { + ModLogType.all => '', + ModLogType.postDeleted => l( + context, + ).modlog_deletedPost, + ModLogType.postRestored => l( + context, + ).modlog_restoredPost, + ModLogType.commentDeleted => l( + context, + ).modlog_deletedComment, + ModLogType.commentRestored => l( + context, + ).modlog_restoredComment, + ModLogType.postPinned => l( + context, + ).modlog_pinnedPost, + ModLogType.postUnpinned => l( + context, + ).modlog_unpinnedPost, + ModLogType.post_deleted => l( + context, + ).modlog_deletedPost, + ModLogType.post_restored => l( + context, + ).modlog_restoredPost, + ModLogType.post_comment_deleted => l( + context, + ).modlog_deletedComment, + ModLogType.post_comment_restored => l( + context, + ).modlog_restoredComment, + ModLogType.ban => l(context).modlog_bannedUser, + ModLogType.unban => l( + context, + ).modlog_unbannedUser, + ModLogType.moderatorAdded => l( + context, + ).modlog_addModerator, + ModLogType.moderatorRemoved => l( + context, + ).modlog_removedModerator, + ModLogType.communityAdded => 'Community added', + ModLogType.communityRemoved => + 'Community removed', + }), ), - child: Text(switch (item.type) { - ModLogType.all => '', - ModLogType.postDeleted => l( - context, - ).modlog_deletedPost, - ModLogType.postRestored => l( - context, - ).modlog_restoredPost, - ModLogType.commentDeleted => l( - context, - ).modlog_deletedComment, - ModLogType.commentRestored => l( - context, - ).modlog_restoredComment, - ModLogType.postPinned => l( - context, - ).modlog_pinnedPost, - ModLogType.postUnpinned => l( - context, - ).modlog_unpinnedPost, - ModLogType.post_deleted => l( - context, - ).modlog_deletedPost, - ModLogType.post_restored => l( - context, - ).modlog_restoredPost, - ModLogType.post_comment_deleted => l( - context, - ).modlog_deletedComment, - ModLogType.post_comment_restored => l( - context, - ).modlog_restoredComment, - ModLogType.ban => l(context).modlog_bannedUser, - ModLogType.unban => l(context).modlog_unbannedUser, - ModLogType.moderatorAdded => l( - context, - ).modlog_addModerator, - ModLogType.moderatorRemoved => l( - context, - ).modlog_removedModerator, - ModLogType.communityAdded => 'Community added', - ModLogType.communityRemoved => 'Community removed', - }), - ), - Padding( - padding: const EdgeInsets.only(left: 10), - child: ContentInfo( - user: item.moderator, - community: item.community, - createdAt: item.createdAt, + Padding( + padding: const EdgeInsets.only(left: 10), + child: ContentInfo( + user: item.moderator, + community: item.community, + createdAt: item.createdAt, + ), ), - ), - ], + ], + ), ), - ), - if (item.postId != null && - item.postTitle != null && - item.comment == null) - InkWell( - child: Text( + if (item.postId != null && + item.postTitle != null && + item.comment == null) + Text( item.postTitle!, style: Theme.of(context).textTheme.titleLarge, ), - onTap: () => pushRoute( - context, - builder: (context) => PostPage( - postType: PostType.thread, - postId: item.postId, - ), + if (item.comment != null) + Text( + item.comment!.body ?? l(context).modlog_deletedComment, + style: Theme.of(context).textTheme.bodyLarge, ), - ), - if (item.comment != null) - PostComment(item.comment!, (post) {}), - if (item.ban != null) UserItemSimple(item.ban!.bannedUser), - if (item.reason != null && item.reason!.isNotEmpty) - Text(l(context).modlog_reason(item.reason!)), - ], + // PostComment(item.comment!, (post) {}), + if (item.ban != null) + UserItemSimple(item.ban!.bannedUser, noTap: true), + if (item.reason != null && item.reason!.isNotEmpty) + Text(l(context).modlog_reason(item.reason!)), + ], + ), ), ), ); diff --git a/lib/src/screens/explore/user_item.dart b/lib/src/screens/explore/user_item.dart index 1fdd8d77..cc219e7e 100644 --- a/lib/src/screens/explore/user_item.dart +++ b/lib/src/screens/explore/user_item.dart @@ -21,7 +21,7 @@ class UserItemSimple extends StatelessWidget { @override Widget build(BuildContext context) { return InkWell( - onTap: () => + onTap: noTap ? null : () => pushRoute(context, builder: (context) => UserScreen(user.id)), child: Padding( padding: const EdgeInsets.all(12), From 42526da15b9223987f626d85c5e4acba9f437acf Mon Sep 17 00:00:00 2001 From: olorin99 Date: Mon, 22 Dec 2025 15:39:46 +1000 Subject: [PATCH 05/18] Add modlog to user menu when lemmy. --- lib/src/api/moderation.dart | 1 + lib/src/screens/explore/mod_log.dart | 4 +++- lib/src/widgets/menus/community_menu.dart | 16 +++++++++------- lib/src/widgets/menus/user_menu.dart | 9 +++++++++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/lib/src/api/moderation.dart b/lib/src/api/moderation.dart index 5319f215..e2a712b1 100644 --- a/lib/src/api/moderation.dart +++ b/lib/src/api/moderation.dart @@ -255,6 +255,7 @@ class APIModeration { const path = '/modlog'; final query = { if (communityId != null) 'community_id': communityId.toString(), + if (userId != null) 'mod_person_id': userId.toString(), 'page': page, 'type_': type.toLemmy, }; diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index 0069dd0c..cea3c1fe 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -14,9 +14,10 @@ import 'package:provider/provider.dart'; import '../../api/moderation.dart'; class ModLog extends StatefulWidget { - const ModLog({super.key, this.communityId}); + const ModLog({super.key, this.communityId, this.userId}); final int? communityId; + final int? userId; @override State createState() => _ModLogState(); @@ -33,6 +34,7 @@ class _ModLogState extends State { final newPage = await ac.api.moderation.modLog( communityId: widget.communityId, + userId: widget.userId, page: pageKey, ); diff --git a/lib/src/widgets/menus/community_menu.dart b/lib/src/widgets/menus/community_menu.dart index 0647be42..acbaed0a 100644 --- a/lib/src/widgets/menus/community_menu.dart +++ b/lib/src/widgets/menus/community_menu.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/community.dart'; import 'package:interstellar/src/screens/explore/mod_log.dart'; import 'package:interstellar/src/screens/explore/community_screen.dart'; @@ -164,13 +165,14 @@ Future showCommunityMenu( ), ), ), - ContextMenuItem( - title: 'Modlog', - onTap: () => pushRoute( - context, - builder: (context) => ModLog(communityId: detailedCommunity?.id ?? community!.id) - ) - ), + if (ac.serverSoftware != ServerSoftware.piefed) + ContextMenuItem( + title: l(context).modlog, + onTap: () => pushRoute( + context, + builder: (context) => ModLog(communityId: detailedCommunity?.id ?? community!.id) + ) + ), ], ).openMenu(context); } diff --git a/lib/src/widgets/menus/user_menu.dart b/lib/src/widgets/menus/user_menu.dart index 6557efb3..fa3a5aea 100644 --- a/lib/src/widgets/menus/user_menu.dart +++ b/lib/src/widgets/menus/user_menu.dart @@ -4,6 +4,7 @@ import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/api/feed_source.dart'; import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/screens/explore/explore_screen.dart'; +import 'package:interstellar/src/screens/explore/mod_log.dart'; import 'package:interstellar/src/screens/explore/user_screen.dart'; import 'package:interstellar/src/utils/ap_urls.dart'; import 'package:interstellar/src/utils/utils.dart'; @@ -131,6 +132,14 @@ Future showUserMenu( ); } ), + if (ac.serverSoftware == ServerSoftware.lemmy) + ContextMenuItem( + title: l(context).modlog, + onTap: () => pushRoute( + context, + builder: (context) => ModLog(userId: user.id), + ) + ), ], ).openMenu(context); } From 761a3a0bcfd1b9a827729de2d222a755e099b329 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Mon, 22 Dec 2025 15:41:36 +1000 Subject: [PATCH 06/18] Fix lemmy modlog next_page. --- lib/src/models/modlog.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/models/modlog.dart b/lib/src/models/modlog.dart index 6ff920c2..a00a04b7 100644 --- a/lib/src/models/modlog.dart +++ b/lib/src/models/modlog.dart @@ -263,7 +263,7 @@ abstract class ModlogListModel with _$ModlogListModel { return ModlogListModel( items: items, - nextPage: json['next_page'] as String?, + nextPage: items.isNotEmpty ? json['next_page'] as String? : null, ); } } From 0c03b2cd5d28301dda5b7ba12d091a0c994affa5 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Mon, 22 Dec 2025 15:43:10 +1000 Subject: [PATCH 07/18] Hide modlog on piefed. --- lib/src/screens/settings/about_screen.dart | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/src/screens/settings/about_screen.dart b/lib/src/screens/settings/about_screen.dart index ce4ecb70..19f47d5b 100644 --- a/lib/src/screens/settings/about_screen.dart +++ b/lib/src/screens/settings/about_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/screens/explore/community_screen.dart'; import 'package:interstellar/src/screens/explore/mod_log.dart'; import 'package:interstellar/src/utils/utils.dart'; @@ -44,14 +45,15 @@ class _AboutScreenState extends State { builder: (context) => const DebugSettingsScreen(), ), ), - ListTile( - leading: const Icon(Symbols.shield_rounded), - title: Text(l(context).modlog), - onTap: () => pushRoute( - context, - builder: (context) => ModLog() + if (context.read().serverSoftware != ServerSoftware.piefed) + ListTile( + leading: const Icon(Symbols.shield_rounded), + title: Text(l(context).modlog), + onTap: () => pushRoute( + context, + builder: (context) => ModLog() + ), ), - ), ListTile( leading: const Icon(Symbols.favorite_rounded), title: Text(l(context).settings_donate), From cd6a29089f50b6aa21c7d709792fda649dbc43f5 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Mon, 22 Dec 2025 22:52:25 +1000 Subject: [PATCH 08/18] Add filter to modlog screen. Check add/remove status for lemmy log types. --- lib/l10n/app_en.arb | 7 +- lib/src/api/moderation.dart | 37 ++++---- lib/src/models/modlog.dart | 37 ++++++-- lib/src/screens/explore/mod_log.dart | 128 ++++++++++++++++++++++++++- 4 files changed, 181 insertions(+), 28 deletions(-) diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5cc91cfd..52ac86bc 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -855,5 +855,10 @@ "type": "String" } } - } + }, + "modlog_all": "All", + "modlog_communityAdded": "Community added", + "modlog_communityRemoved": "Community removed", + "modlog_postLocked": "Post locked", + "modlog_postUnlocked": "Post unlocked" } diff --git a/lib/src/api/moderation.dart b/lib/src/api/moderation.dart index e2a712b1..eaf8402b 100644 --- a/lib/src/api/moderation.dart +++ b/lib/src/api/moderation.dart @@ -28,7 +28,9 @@ enum ModLogType { moderatorAdded, moderatorRemoved, communityAdded, - communityRemoved; + communityRemoved, + postLocked, + postUnlocked; static ModLogType fromMbin(String type) => switch (type) { 'log_entry_deleted' => ModLogType.postDeleted, @@ -66,26 +68,30 @@ enum ModLogType { ModLogType.moderatorRemoved => 'moderator_remove', ModLogType.communityAdded => 'all', ModLogType.communityRemoved => 'all', + ModLogType.postLocked => 'all', + ModLogType.postUnlocked => 'all', }; String get toLemmy => switch (this) { ModLogType.all => 'All', ModLogType.postDeleted => 'ModRemovePost', - ModLogType.postRestored => 'All', + ModLogType.postRestored => 'ModRemovePost', ModLogType.commentDeleted => 'ModRemoveComment', - ModLogType.commentRestored => 'All', + ModLogType.commentRestored => 'ModRemoveComment', ModLogType.postPinned => 'ModFeaturePost', - ModLogType.postUnpinned => 'All', + ModLogType.postUnpinned => 'ModFeaturePost', ModLogType.post_deleted => 'ModRemovePost', - ModLogType.post_restored => 'All', + ModLogType.post_restored => 'ModRemovePost', ModLogType.post_comment_deleted => 'ModRemoveComment', - ModLogType.post_comment_restored => 'All', + ModLogType.post_comment_restored => 'ModRemoveComment', ModLogType.ban => 'ModBan', - ModLogType.unban => 'All', + ModLogType.unban => 'ModBan', ModLogType.moderatorAdded => 'ModAdd', - ModLogType.moderatorRemoved => 'All', + ModLogType.moderatorRemoved => 'ModAdd', ModLogType.communityAdded => 'ModAddCommunity', ModLogType.communityRemoved => 'ModRemoveCommunity', + ModLogType.postLocked => 'ModLockPost', + ModLogType.postUnlocked => 'ModLockPost', }; } @@ -240,14 +246,13 @@ class APIModeration { }) async { switch (client.software) { case ServerSoftware.mbin: - if (communityId != null) { - final path = '/magazine/$communityId/log'; - final query = {'p': page}; - final response = await client.get(path, queryParams: query); - return ModlogListModel.fromMbin(response.bodyJson); - } - final path = '/modlog'; - final query = {'p': page}; + final path = communityId != null ? '/magazine/$communityId/log' : '/modlog'; + final query = { + 'p': page, + if (type != ModLogType.all) + 'types[0]': type.toMbin, + }; + final response = await client.get(path, queryParams: query); return ModlogListModel.fromMbin(response.bodyJson); diff --git a/lib/src/models/modlog.dart b/lib/src/models/modlog.dart index a00a04b7..5fc79757 100644 --- a/lib/src/models/modlog.dart +++ b/lib/src/models/modlog.dart @@ -54,6 +54,8 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.moderatorRemoved => null, ModLogType.communityAdded => null, ModLogType.communityRemoved => null, + ModLogType.postLocked => null, + ModLogType.postUnlocked => null, }, postTitle: switch (type) { ModLogType.all => null, @@ -77,6 +79,8 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.moderatorRemoved => null, ModLogType.communityAdded => null, ModLogType.communityRemoved => null, + ModLogType.postLocked => null, + ModLogType.postUnlocked => null, }, comment: switch (type) { ModLogType.all => null, @@ -104,6 +108,8 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.moderatorRemoved => null, ModLogType.communityAdded => null, ModLogType.communityRemoved => null, + ModLogType.postLocked => null, + ModLogType.postUnlocked => null, }, ban: switch (type) { ModLogType.all => null, @@ -127,6 +133,8 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.moderatorRemoved => null, ModLogType.communityAdded => null, ModLogType.communityRemoved => null, + ModLogType.postLocked => null, + ModLogType.postUnlocked => null, }, ); } @@ -176,7 +184,9 @@ abstract class ModlogListModel with _$ModlogListModel { final removedPosts = (json['removed_posts'] as List) .map( (item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.postDeleted.name, + 'type': (item['mod_remove_post'] as JsonMap)['removed'] as bool + ? ModLogType.postDeleted.name + : ModLogType.postRestored.name, 'createdAt': (item['mod_remove_post'] as JsonMap)['when_'] as String, 'reason': (item['mod_remove_post'] as JsonMap)['reason'] as String?, @@ -187,7 +197,9 @@ abstract class ModlogListModel with _$ModlogListModel { final lockedPosts = (json['locked_posts'] as List).map( (item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.postRestored.name, + 'type': (item['mod_lock_post'] as JsonMap)['locked'] as bool + ? ModLogType.postLocked.name + : ModLogType.postUnlocked.name, 'createdAt': (item['mod_lock_post'] as JsonMap)['when_'] as String, ...item, }, langCodeIdPairs: langCodeIdPairs), @@ -195,7 +207,9 @@ abstract class ModlogListModel with _$ModlogListModel { final featuredPosts = (json['featured_posts'] as List).map( (item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.postPinned.name, + 'type': (item['mod_feature_post'] as JsonMap)['featured'] as bool + ? ModLogType.postPinned.name + : ModLogType.postUnpinned.name, 'createdAt': (item['mod_feature_post'] as JsonMap)['when_'] as String, ...item, }, langCodeIdPairs: langCodeIdPairs), @@ -203,7 +217,9 @@ abstract class ModlogListModel with _$ModlogListModel { final removedComments = (json['removed_comments'] as List).map( (item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.commentDeleted.name, + 'type': (item['mod_remove_comment'] as JsonMap)['removed'] as bool + ? ModLogType.commentDeleted.name + : ModLogType.commentRestored.name, 'createdAt': (item['mod_remove_comment'] as JsonMap)['when_'] as String, 'reason': (item['mod_remove_comment'] as JsonMap)['reason'] as String?, ...item as JsonMap, @@ -214,7 +230,9 @@ abstract class ModlogListModel with _$ModlogListModel { final removedCommunities = (json['removed_communities'] as List) .map( (item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.communityRemoved.name, + 'type': (item['mod_remove_community'] as JsonMap)['removed'] as bool + ? ModLogType.communityRemoved.name + : ModLogType.communityAdded.name, 'createdAt': (item['mod_remove_community'] as JsonMap)['when_'] as String, ...item as JsonMap, @@ -224,7 +242,10 @@ abstract class ModlogListModel with _$ModlogListModel { final modBannedCommunity = (json['banned_from_community'] as List) .map( (item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.ban.name, + 'type': + (item['mod_ban_from_community'] as JsonMap)['banned'] as bool + ? ModLogType.ban.name + : ModLogType.unban.name, 'createdAt': (item['mod_ban_from_community'] as JsonMap)['when_'] as String, ...item, @@ -240,7 +261,9 @@ abstract class ModlogListModel with _$ModlogListModel { final modAddedToCommunity = (json['added_to_community'] as List) .map( (item) => ModlogItemModel.fromLemmy({ - 'type': ModLogType.moderatorAdded.name, + 'type': (item['mod_add_community'] as JsonMap)['removed'] as bool + ? ModLogType.moderatorRemoved.name + : ModLogType.moderatorAdded.name, 'createdAt': (item['mod_add_community'] as JsonMap)['when_'] as String, ...item, diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index cea3c1fe..bb0eb44c 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:interstellar/src/controller/controller.dart'; +import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/modlog.dart'; import 'package:interstellar/src/models/post.dart'; import 'package:interstellar/src/screens/explore/user_item.dart'; @@ -9,6 +10,8 @@ import 'package:interstellar/src/screens/feed/post_page.dart'; import 'package:interstellar/src/utils/utils.dart'; import 'package:interstellar/src/widgets/content_item/content_info.dart'; import 'package:interstellar/src/widgets/paging.dart'; +import 'package:interstellar/src/widgets/selection_menu.dart'; +import 'package:material_symbols_icons/material_symbols_icons.dart'; import 'package:provider/provider.dart'; import '../../api/moderation.dart'; @@ -35,12 +38,14 @@ class _ModLogState extends State { final newPage = await ac.api.moderation.modLog( communityId: widget.communityId, userId: widget.userId, + type: _filter, page: pageKey, ); return (newPage.items, newPage.nextPage); }, ); + ModLogType _filter = ModLogType.all; Function()? _itemOnTap(ModlogItemModel item) => switch (item.type) { ModLogType.all => null, @@ -142,12 +147,31 @@ class _ModLogState extends State { ModLogType.moderatorRemoved => null, ModLogType.communityAdded => null, ModLogType.communityRemoved => null, + ModLogType.postLocked => null, + ModLogType.postUnlocked => null, }; @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(l(context).modlog)), + appBar: AppBar( + title: Text(l(context).modlog), + actions: [ + IconButton( + onPressed: () async { + final filter = await modlogFilterType( + context, + ).askSelection(context, _filter); + if (filter == null) return; + setState(() { + _filter = filter; + }); + _pagingController.refresh(); + }, + icon: const Icon(Symbols.sort_rounded), + ), + ], + ), body: AdvancedPagedScrollView( controller: _pagingController, itemBuilder: (context, item, index) { @@ -188,6 +212,8 @@ class _ModLogState extends State { ModLogType.moderatorRemoved => Colors.orange, ModLogType.communityAdded => Colors.green, ModLogType.communityRemoved => Colors.red, + ModLogType.postLocked => Colors.orange, + ModLogType.postUnlocked => Colors.orange, }, borderRadius: BorderRadius.circular(10), ), @@ -233,9 +259,18 @@ class _ModLogState extends State { ModLogType.moderatorRemoved => l( context, ).modlog_removedModerator, - ModLogType.communityAdded => 'Community added', - ModLogType.communityRemoved => - 'Community removed', + ModLogType.communityAdded => l( + context, + ).modlog_communityAdded, + ModLogType.communityRemoved => l( + context, + ).modlog_communityRemoved, + ModLogType.postLocked => l( + context, + ).modlog_postLocked, + ModLogType.postUnlocked => l( + context, + ).modlog_postUnlocked, }), ), Padding( @@ -276,3 +311,88 @@ class _ModLogState extends State { ); } } + +SelectionMenu modlogFilterType(BuildContext context) { + final software = context.read().serverSoftware; + return SelectionMenu(l(context).sortComments, [ + SelectionMenuItem(value: ModLogType.all, title: l(context).modlog_all), + SelectionMenuItem( + value: ModLogType.postDeleted, + title: l(context).modlog_deletedPost, + ), + if (software == ServerSoftware.mbin) + SelectionMenuItem( + value: ModLogType.postRestored, + title: l(context).modlog_restoredPost, + ), + SelectionMenuItem( + value: ModLogType.commentDeleted, + title: l(context).modlog_deletedComment, + ), + if (software == ServerSoftware.mbin) + SelectionMenuItem( + value: ModLogType.commentRestored, + title: l(context).modlog_restoredComment, + ), + SelectionMenuItem( + value: ModLogType.postPinned, + title: l(context).modlog_pinnedPost, + ), + SelectionMenuItem( + value: ModLogType.postUnpinned, + title: l(context).modlog_unpinnedPost, + ), + if (software == ServerSoftware.mbin) ...[ + SelectionMenuItem( + value: ModLogType.post_deleted, + title: l(context).modlog_deletedPost, + ), + SelectionMenuItem( + value: ModLogType.post_restored, + title: l(context).modlog_restoredPost, + ), + SelectionMenuItem( + value: ModLogType.post_comment_deleted, + title: l(context).modlog_deletedComment, + ), + SelectionMenuItem( + value: ModLogType.post_comment_restored, + title: l(context).modlog_restoredComment, + ), + ], + SelectionMenuItem( + value: ModLogType.ban, + title: l(context).modlog_bannedUser, + ), + SelectionMenuItem( + value: ModLogType.unban, + title: l(context).modlog_unbannedUser, + ), + SelectionMenuItem( + value: ModLogType.moderatorAdded, + title: l(context).modlog_addModerator, + ), + SelectionMenuItem( + value: ModLogType.moderatorRemoved, + title: l(context).modlog_removedModerator, + ), + if (software != ServerSoftware.mbin) ...[ + SelectionMenuItem( + value: ModLogType.communityAdded, + title: l(context).modlog_communityAdded, + ), + SelectionMenuItem( + value: ModLogType.communityRemoved, + title: l(context).modlog_communityRemoved, + ), + SelectionMenuItem( + value: ModLogType.postLocked, + title: l(context).modlog_postLocked, + ), + SelectionMenuItem( + value: ModLogType.postUnlocked, + title: l(context).modlog_postUnlocked, + ), + ], + ]); +} From 5f12b34b4438c2697e6ec40164868398abd795cc Mon Sep 17 00:00:00 2001 From: olorin99 Date: Tue, 23 Dec 2025 15:24:12 +1000 Subject: [PATCH 09/18] Fix attempting to get title from microblog. --- lib/src/models/modlog.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/models/modlog.dart b/lib/src/models/modlog.dart index 5fc79757..54b2ecc3 100644 --- a/lib/src/models/modlog.dart +++ b/lib/src/models/modlog.dart @@ -68,9 +68,9 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.postPinned => null, ModLogType.postUnpinned => null, ModLogType.post_deleted => - (json['subject'] as JsonMap)['title'] as String, + (json['subject'] as JsonMap)['body'] as String, ModLogType.post_restored => - (json['subject'] as JsonMap)['title'] as String, + (json['subject'] as JsonMap)['body'] as String, ModLogType.post_comment_deleted => null, ModLogType.post_comment_restored => null, ModLogType.ban => null, From 23cd07b912a4dd233648936b86994d36a13f1ff4 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Tue, 23 Dec 2025 21:40:48 +1000 Subject: [PATCH 10/18] Move modlog item moderator/community info onto separate line for more space. --- lib/src/screens/explore/mod_log.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index bb0eb44c..e6c421d1 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -273,17 +273,17 @@ class _ModLogState extends State { ).modlog_postUnlocked, }), ), - Padding( - padding: const EdgeInsets.only(left: 10), - child: ContentInfo( - user: item.moderator, - community: item.community, - createdAt: item.createdAt, - ), - ), ], ), ), + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: ContentInfo( + user: item.moderator, + community: item.community, + createdAt: item.createdAt, + ), + ), if (item.postId != null && item.postTitle != null && item.comment == null) From 91c43ddd19db28f558d7d9bdfd887a81a2d1ca88 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Sat, 27 Dec 2025 23:57:28 +1000 Subject: [PATCH 11/18] Add callback for "locked posts". Change layout of modlog items - Remove card. - Add dividers between sections. --- lib/src/screens/explore/mod_log.dart | 254 ++++++++++++++------------- 1 file changed, 130 insertions(+), 124 deletions(-) diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index e6c421d1..8f5a0bfd 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -147,8 +147,22 @@ class _ModLogState extends State { ModLogType.moderatorRemoved => null, ModLogType.communityAdded => null, ModLogType.communityRemoved => null, - ModLogType.postLocked => null, - ModLogType.postUnlocked => null, + ModLogType.postLocked => + item.postId == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostPage(postType: PostType.thread, postId: item.postId), + ), + ModLogType.postUnlocked => + item.postId == null + ? null + : () => pushRoute( + context, + builder: (context) => + PostPage(postType: PostType.thread, postId: item.postId), + ), }; @override @@ -175,136 +189,128 @@ class _ModLogState extends State { body: AdvancedPagedScrollView( controller: _pagingController, itemBuilder: (context, item, index) { - return Card( - margin: const EdgeInsets.all(8), - child: InkWell( - onTap: _itemOnTap(item), - child: Padding( - padding: const EdgeInsets.all(8), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.only(bottom: 8), - child: Row( - // mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - padding: const EdgeInsets.all(5), - decoration: BoxDecoration( - color: switch (item.type) { - ModLogType.all => Colors.white, - ModLogType.postDeleted => Colors.red, - ModLogType.postRestored => Colors.green, - ModLogType.commentDeleted => Colors.red, - ModLogType.commentRestored => Colors.green, - ModLogType.postPinned => Colors.orange, - ModLogType.postUnpinned => Colors.orange, - ModLogType.post_deleted => Colors.red, - ModLogType.post_restored => Colors.green, - ModLogType.post_comment_deleted => Colors.red, - ModLogType.post_comment_restored => - Colors.green, - ModLogType.ban => Colors.red, - ModLogType.unban => Colors.green, - ModLogType.moderatorAdded => Colors.orange, - ModLogType.moderatorRemoved => Colors.orange, - ModLogType.communityAdded => Colors.green, - ModLogType.communityRemoved => Colors.red, - ModLogType.postLocked => Colors.orange, - ModLogType.postUnlocked => Colors.orange, - }, - borderRadius: BorderRadius.circular(10), - ), - child: Text(switch (item.type) { - ModLogType.all => '', - ModLogType.postDeleted => l( - context, - ).modlog_deletedPost, - ModLogType.postRestored => l( - context, - ).modlog_restoredPost, - ModLogType.commentDeleted => l( - context, - ).modlog_deletedComment, - ModLogType.commentRestored => l( - context, - ).modlog_restoredComment, - ModLogType.postPinned => l( - context, - ).modlog_pinnedPost, - ModLogType.postUnpinned => l( - context, - ).modlog_unpinnedPost, - ModLogType.post_deleted => l( - context, - ).modlog_deletedPost, - ModLogType.post_restored => l( - context, - ).modlog_restoredPost, - ModLogType.post_comment_deleted => l( - context, - ).modlog_deletedComment, - ModLogType.post_comment_restored => l( - context, - ).modlog_restoredComment, - ModLogType.ban => l(context).modlog_bannedUser, - ModLogType.unban => l( - context, - ).modlog_unbannedUser, - ModLogType.moderatorAdded => l( - context, - ).modlog_addModerator, - ModLogType.moderatorRemoved => l( - context, - ).modlog_removedModerator, - ModLogType.communityAdded => l( - context, - ).modlog_communityAdded, - ModLogType.communityRemoved => l( - context, - ).modlog_communityRemoved, - ModLogType.postLocked => l( - context, - ).modlog_postLocked, - ModLogType.postUnlocked => l( - context, - ).modlog_postUnlocked, - }), + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: _itemOnTap(item), + child: Padding( + padding: const EdgeInsets.all(8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 8), + child: Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: switch (item.type) { + ModLogType.all => Colors.white, + ModLogType.postDeleted => Colors.red, + ModLogType.postRestored => Colors.green, + ModLogType.commentDeleted => Colors.red, + ModLogType.commentRestored => Colors.green, + ModLogType.postPinned => Colors.orange, + ModLogType.postUnpinned => Colors.orange, + ModLogType.post_deleted => Colors.red, + ModLogType.post_restored => Colors.green, + ModLogType.post_comment_deleted => Colors.red, + ModLogType.post_comment_restored => Colors.green, + ModLogType.ban => Colors.red, + ModLogType.unban => Colors.green, + ModLogType.moderatorAdded => Colors.orange, + ModLogType.moderatorRemoved => Colors.orange, + ModLogType.communityAdded => Colors.green, + ModLogType.communityRemoved => Colors.red, + ModLogType.postLocked => Colors.orange, + ModLogType.postUnlocked => Colors.orange, + }, + borderRadius: BorderRadius.circular(10), ), - ], + child: Text(switch (item.type) { + ModLogType.all => '', + ModLogType.postDeleted => l( + context, + ).modlog_deletedPost, + ModLogType.postRestored => l( + context, + ).modlog_restoredPost, + ModLogType.commentDeleted => l( + context, + ).modlog_deletedComment, + ModLogType.commentRestored => l( + context, + ).modlog_restoredComment, + ModLogType.postPinned => l( + context, + ).modlog_pinnedPost, + ModLogType.postUnpinned => l( + context, + ).modlog_unpinnedPost, + ModLogType.post_deleted => l( + context, + ).modlog_deletedPost, + ModLogType.post_restored => l( + context, + ).modlog_restoredPost, + ModLogType.post_comment_deleted => l( + context, + ).modlog_deletedComment, + ModLogType.post_comment_restored => l( + context, + ).modlog_restoredComment, + ModLogType.ban => l(context).modlog_bannedUser, + ModLogType.unban => l(context).modlog_unbannedUser, + ModLogType.moderatorAdded => l( + context, + ).modlog_addModerator, + ModLogType.moderatorRemoved => l( + context, + ).modlog_removedModerator, + ModLogType.communityAdded => l( + context, + ).modlog_communityAdded, + ModLogType.communityRemoved => l( + context, + ).modlog_communityRemoved, + ModLogType.postLocked => l( + context, + ).modlog_postLocked, + ModLogType.postUnlocked => l( + context, + ).modlog_postUnlocked, + }), + ), ), - ), - Padding( - padding: const EdgeInsets.only(bottom: 8), - child: ContentInfo( + ContentInfo( user: item.moderator, community: item.community, createdAt: item.createdAt, ), - ), - if (item.postId != null && - item.postTitle != null && - item.comment == null) - Text( - item.postTitle!, - style: Theme.of(context).textTheme.titleLarge, - ), - if (item.comment != null) - Text( - item.comment!.body ?? l(context).modlog_deletedComment, - style: Theme.of(context).textTheme.bodyLarge, - ), - // PostComment(item.comment!, (post) {}), - if (item.ban != null) - UserItemSimple(item.ban!.bannedUser, noTap: true), - if (item.reason != null && item.reason!.isNotEmpty) - Text(l(context).modlog_reason(item.reason!)), - ], + if (item.postId != null || + item.comment != null || + item.ban != null || + (item.reason != null && item.reason!.isNotEmpty)) + const Divider(), + if (item.postId != null || item.comment != null) + Text( + 'Content: ${item.postId != null ? item.postTitle ?? l(context).modlog_deletedPost : item.comment?.body ?? l(context).modlog_deletedComment}', + maxLines: 4, + overflow: TextOverflow.ellipsis, + ), + if (item.ban != null) + UserItemSimple(item.ban!.bannedUser, noTap: true), + if (item.reason != null && item.reason!.isNotEmpty) + Text( + l(context).modlog_reason(item.reason!), + style: TextStyle(fontStyle: FontStyle.italic), + ), + ], + ), ), ), - ), + const Divider(thickness: 0, height: 0), + ], ); }, ), From d021a434d8827c65abf4cc5c998a28c5f67f2be1 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Tue, 30 Dec 2025 01:03:50 +1000 Subject: [PATCH 12/18] Remove divider inside report. --- lib/src/screens/explore/mod_log.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index 8f5a0bfd..fb7ba1a8 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -287,11 +287,6 @@ class _ModLogState extends State { community: item.community, createdAt: item.createdAt, ), - if (item.postId != null || - item.comment != null || - item.ban != null || - (item.reason != null && item.reason!.isNotEmpty)) - const Divider(), if (item.postId != null || item.comment != null) Text( 'Content: ${item.postId != null ? item.postTitle ?? l(context).modlog_deletedPost : item.comment?.body ?? l(context).modlog_deletedComment}', From e52488aa609f8b78bc7463ea761617130b7770c4 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Wed, 31 Dec 2025 23:08:18 +1000 Subject: [PATCH 13/18] Add extra onTap actions for mod log item. Fix lemmy ban type enum. Fix modlog filter icon. --- lib/src/api/moderation.dart | 4 +-- lib/src/screens/explore/mod_log.dart | 49 +++++++++++++++++++++++----- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/lib/src/api/moderation.dart b/lib/src/api/moderation.dart index eaf8402b..f7e4d779 100644 --- a/lib/src/api/moderation.dart +++ b/lib/src/api/moderation.dart @@ -84,8 +84,8 @@ enum ModLogType { ModLogType.post_restored => 'ModRemovePost', ModLogType.post_comment_deleted => 'ModRemoveComment', ModLogType.post_comment_restored => 'ModRemoveComment', - ModLogType.ban => 'ModBan', - ModLogType.unban => 'ModBan', + ModLogType.ban => 'ModBanFromCommunity', + ModLogType.unban => 'ModBanFromCommunity', ModLogType.moderatorAdded => 'ModAdd', ModLogType.moderatorRemoved => 'ModAdd', ModLogType.communityAdded => 'ModAddCommunity', diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index fb7ba1a8..c749006b 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -3,6 +3,7 @@ import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/modlog.dart'; import 'package:interstellar/src/models/post.dart'; +import 'package:interstellar/src/screens/explore/community_screen.dart'; import 'package:interstellar/src/screens/explore/user_item.dart'; import 'package:interstellar/src/screens/explore/user_screen.dart'; import 'package:interstellar/src/screens/feed/post_comment_screen.dart'; @@ -42,7 +43,19 @@ class _ModLogState extends State { page: pageKey, ); - return (newPage.items, newPage.nextPage); + final newItems = switch (ac.serverSoftware) { + ServerSoftware.mbin => newPage.items, + ServerSoftware.lemmy => + _filter != ModLogType.all + ? newPage.items.where((item) => item.type == _filter).toList() + : newPage.items, + // Lemmy API returns both positive and negative mod action types for each filter type. + // e.g. passing PinnedPost to the API returns both pinned and unpinned actions. + // So we do a little extra filtering here to narrow it down further. + ServerSoftware.piefed => throw UnimplementedError(), + }; + + return (newItems, newPage.nextPage); }, ); ModLogType _filter = ModLogType.all; @@ -143,10 +156,28 @@ class _ModLogState extends State { context, builder: (context) => UserScreen(item.ban!.bannedUser.id), ), - ModLogType.moderatorAdded => null, - ModLogType.moderatorRemoved => null, - ModLogType.communityAdded => null, - ModLogType.communityRemoved => null, + ModLogType.moderatorAdded => + item.moderator == null + ? null + : () => pushRoute( + context, + builder: (context) => UserScreen(item.moderator!.id), + ), + ModLogType.moderatorRemoved => + item.moderator == null + ? null + : () => pushRoute( + context, + builder: (context) => UserScreen(item.moderator!.id), + ), + ModLogType.communityAdded => () => pushRoute( + context, + builder: (context) => CommunityScreen(item.community.id), + ), + ModLogType.communityRemoved => () => pushRoute( + context, + builder: (context) => CommunityScreen(item.community.id), + ), ModLogType.postLocked => item.postId == null ? null @@ -182,7 +213,7 @@ class _ModLogState extends State { }); _pagingController.refresh(); }, - icon: const Icon(Symbols.sort_rounded), + icon: const Icon(Symbols.filter_alt_rounded), ), ], ), @@ -289,7 +320,9 @@ class _ModLogState extends State { ), if (item.postId != null || item.comment != null) Text( - 'Content: ${item.postId != null ? item.postTitle ?? l(context).modlog_deletedPost : item.comment?.body ?? l(context).modlog_deletedComment}', + 'Content: ${item.postId != null + ? item.postTitle ?? l(context).modlog_deletedPost + : item.comment?.body ?? l(context).modlog_deletedComment}', maxLines: 4, overflow: TextOverflow.ellipsis, ), @@ -315,7 +348,7 @@ class _ModLogState extends State { SelectionMenu modlogFilterType(BuildContext context) { final software = context.read().serverSoftware; - return SelectionMenu(l(context).sortComments, [ + return SelectionMenu(l(context).modlog, [ SelectionMenuItem(value: ModLogType.all, title: l(context).modlog_all), SelectionMenuItem( value: ModLogType.postDeleted, From 73dbb331eded8e15bfa8b331d70375bd8dcabb1b Mon Sep 17 00:00:00 2001 From: olorin99 Date: Wed, 31 Dec 2025 23:36:57 +1000 Subject: [PATCH 14/18] Modlog item holds user object rather than ban. Fix navigate to mod added/removed. --- lib/src/api/moderation.dart | 6 ++--- lib/src/models/modlog.dart | 34 ++++++++++++++++++++-------- lib/src/screens/explore/mod_log.dart | 21 +++++++++-------- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/lib/src/api/moderation.dart b/lib/src/api/moderation.dart index f7e4d779..a9d8d994 100644 --- a/lib/src/api/moderation.dart +++ b/lib/src/api/moderation.dart @@ -86,9 +86,9 @@ enum ModLogType { ModLogType.post_comment_restored => 'ModRemoveComment', ModLogType.ban => 'ModBanFromCommunity', ModLogType.unban => 'ModBanFromCommunity', - ModLogType.moderatorAdded => 'ModAdd', - ModLogType.moderatorRemoved => 'ModAdd', - ModLogType.communityAdded => 'ModAddCommunity', + ModLogType.moderatorAdded => 'ModAddCommunity', + ModLogType.moderatorRemoved => 'ModAddCommunity', + ModLogType.communityAdded => 'ModRemoveCommunity', ModLogType.communityRemoved => 'ModRemoveCommunity', ModLogType.postLocked => 'ModLockPost', ModLogType.postUnlocked => 'ModLockPost', diff --git a/lib/src/models/modlog.dart b/lib/src/models/modlog.dart index 54b2ecc3..533b9dbd 100644 --- a/lib/src/models/modlog.dart +++ b/lib/src/models/modlog.dart @@ -20,7 +20,7 @@ abstract class ModlogItemModel with _$ModlogItemModel { required int? postId, required String? postTitle, required CommentModel? comment, - required CommunityBanModel? ban, + required DetailedUserModel? user, }) = _ModlogItemModel; factory ModlogItemModel.fromMbin(JsonMap json) { @@ -111,7 +111,7 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.postLocked => null, ModLogType.postUnlocked => null, }, - ban: switch (type) { + user: switch (type) { ModLogType.all => null, ModLogType.postDeleted => null, ModLogType.postRestored => null, @@ -123,12 +123,8 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.post_restored => null, ModLogType.post_comment_deleted => null, ModLogType.post_comment_restored => null, - ModLogType.ban => CommunityBanModel.fromMbin( - json['subject'] as JsonMap, - ), - ModLogType.unban => CommunityBanModel.fromMbin( - json['subject'] as JsonMap, - ), + ModLogType.ban => DetailedUserModel.fromMbin((json['subject'] as JsonMap)['bannedUser'] as JsonMap), + ModLogType.unban => DetailedUserModel.fromMbin((json['subject'] as JsonMap)['bannedUser'] as JsonMap), ModLogType.moderatorAdded => null, ModLogType.moderatorRemoved => null, ModLogType.communityAdded => null, @@ -158,7 +154,27 @@ abstract class ModlogItemModel with _$ModlogItemModel { comment: json['comment'] != null ? CommentModel.fromLemmy(json, langCodeIdPairs: langCodeIdPairs) : null, - ban: type == ModLogType.ban ? CommunityBanModel.fromLemmy(json) : null, + user: switch (type) { + ModLogType.all => null, + ModLogType.postDeleted => null, + ModLogType.postRestored => null, + ModLogType.commentDeleted => null, + ModLogType.commentRestored => null, + ModLogType.postPinned => null, + ModLogType.postUnpinned => null, + ModLogType.post_deleted => null, + ModLogType.post_restored => null, + ModLogType.post_comment_deleted => null, + ModLogType.post_comment_restored => null, + ModLogType.ban => DetailedUserModel.fromLemmy(json['banned_person'] as JsonMap), + ModLogType.unban => DetailedUserModel.fromLemmy(json['banned_person'] as JsonMap), + ModLogType.moderatorAdded => DetailedUserModel.fromLemmy(json['modded_person'] as JsonMap), + ModLogType.moderatorRemoved => DetailedUserModel.fromLemmy(json['modded_person'] as JsonMap), + ModLogType.communityAdded => null, + ModLogType.communityRemoved => null, + ModLogType.postLocked => null, + ModLogType.postUnlocked => null, + }, ); } } diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index c749006b..879fef6a 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -3,6 +3,7 @@ import 'package:interstellar/src/controller/controller.dart'; import 'package:interstellar/src/controller/server.dart'; import 'package:interstellar/src/models/modlog.dart'; import 'package:interstellar/src/models/post.dart'; +import 'package:interstellar/src/models/user.dart'; import 'package:interstellar/src/screens/explore/community_screen.dart'; import 'package:interstellar/src/screens/explore/user_item.dart'; import 'package:interstellar/src/screens/explore/user_screen.dart'; @@ -143,32 +144,32 @@ class _ModLogState extends State { PostCommentScreen(PostType.thread, item.comment!.id), ), ModLogType.ban => - item.ban == null + item.user == null ? null : () => pushRoute( context, - builder: (context) => UserScreen(item.ban!.bannedUser.id), + builder: (context) => UserScreen(item.user!.id), ), ModLogType.unban => - item.ban == null + item.user == null ? null : () => pushRoute( context, - builder: (context) => UserScreen(item.ban!.bannedUser.id), + builder: (context) => UserScreen(item.user!.id), ), ModLogType.moderatorAdded => - item.moderator == null + item.user == null ? null : () => pushRoute( context, - builder: (context) => UserScreen(item.moderator!.id), + builder: (context) => UserScreen(item.user!.id), ), ModLogType.moderatorRemoved => - item.moderator == null + item.user == null ? null : () => pushRoute( context, - builder: (context) => UserScreen(item.moderator!.id), + builder: (context) => UserScreen(item.user!.id), ), ModLogType.communityAdded => () => pushRoute( context, @@ -326,8 +327,8 @@ class _ModLogState extends State { maxLines: 4, overflow: TextOverflow.ellipsis, ), - if (item.ban != null) - UserItemSimple(item.ban!.bannedUser, noTap: true), + if (item.user != null) + UserItemSimple(UserModel.fromDetailedUser(item.user!), noTap: true), if (item.reason != null && item.reason!.isNotEmpty) Text( l(context).modlog_reason(item.reason!), From 85dc51ac907a9c58a4c9cd5cb6126da4443aba01 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Sat, 3 Jan 2026 15:08:13 +1000 Subject: [PATCH 15/18] Fix mbin microblog modtype enums not in camel case. Include microblog modlog types with corresponding post types. --- lib/src/api/moderation.dart | 42 +++++++++++++++++----------- lib/src/models/modlog.dart | 40 +++++++++++++------------- lib/src/screens/explore/mod_log.dart | 42 ++++++++-------------------- 3 files changed, 58 insertions(+), 66 deletions(-) diff --git a/lib/src/api/moderation.dart b/lib/src/api/moderation.dart index a9d8d994..a0ea379a 100644 --- a/lib/src/api/moderation.dart +++ b/lib/src/api/moderation.dart @@ -19,10 +19,10 @@ enum ModLogType { commentRestored, postPinned, postUnpinned, - post_deleted, - post_restored, - post_comment_deleted, - post_comment_restored, + microblogPostDeleted, + microblogPostRestored, + microblogCommentDeleted, + microblogCommentRestored, ban, unban, moderatorAdded, @@ -39,10 +39,10 @@ enum ModLogType { 'log_entry_comment_restored' => ModLogType.commentRestored, 'log_entry_pinned' => ModLogType.postPinned, 'log_entry_unpinned' => ModLogType.postUnpinned, - 'log_post_deleted' => ModLogType.post_deleted, - 'log_post_restored' => ModLogType.post_restored, - 'log_post_comment_deleted' => ModLogType.post_comment_deleted, - 'log_post_comment_restored' => ModLogType.post_comment_restored, + 'log_post_deleted' => ModLogType.microblogPostDeleted, + 'log_post_restored' => ModLogType.microblogPostRestored, + 'log_post_comment_deleted' => ModLogType.microblogCommentDeleted, + 'log_post_comment_restored' => ModLogType.microblogCommentRestored, 'log_ban' => ModLogType.ban, 'log_unban' => ModLogType.unban, 'log_moderator_add' => ModLogType.moderatorAdded, @@ -58,10 +58,10 @@ enum ModLogType { ModLogType.commentRestored => 'entry_comment_restored', ModLogType.postPinned => 'entry_pinned', ModLogType.postUnpinned => 'entry_unpinned', - ModLogType.post_deleted => 'post_deleted', - ModLogType.post_restored => 'post_restored', - ModLogType.post_comment_deleted => 'post_comment_deleted', - ModLogType.post_comment_restored => 'post_comment_restored', + ModLogType.microblogPostDeleted => 'post_deleted', + ModLogType.microblogPostRestored => 'post_restored', + ModLogType.microblogCommentDeleted => 'post_comment_deleted', + ModLogType.microblogCommentRestored => 'post_comment_restored', ModLogType.ban => 'ban', ModLogType.unban => 'unban', ModLogType.moderatorAdded => 'moderator_add', @@ -80,10 +80,10 @@ enum ModLogType { ModLogType.commentRestored => 'ModRemoveComment', ModLogType.postPinned => 'ModFeaturePost', ModLogType.postUnpinned => 'ModFeaturePost', - ModLogType.post_deleted => 'ModRemovePost', - ModLogType.post_restored => 'ModRemovePost', - ModLogType.post_comment_deleted => 'ModRemoveComment', - ModLogType.post_comment_restored => 'ModRemoveComment', + ModLogType.microblogPostDeleted => 'ModRemovePost', + ModLogType.microblogPostRestored => 'ModRemovePost', + ModLogType.microblogCommentDeleted => 'ModRemoveComment', + ModLogType.microblogCommentRestored => 'ModRemoveComment', ModLogType.ban => 'ModBanFromCommunity', ModLogType.unban => 'ModBanFromCommunity', ModLogType.moderatorAdded => 'ModAddCommunity', @@ -251,6 +251,16 @@ class APIModeration { 'p': page, if (type != ModLogType.all) 'types[0]': type.toMbin, + + // if type set to post or comment also include the corresponding type for microblogs + if (type == ModLogType.postDeleted) + 'types[1]': ModLogType.microblogPostDeleted.toMbin, + if (type == ModLogType.postRestored) + 'types[1]': ModLogType.microblogPostRestored.toMbin, + if (type == ModLogType.commentDeleted) + 'types[1]': ModLogType.microblogCommentDeleted.toMbin, + if (type == ModLogType.commentRestored) + 'types[1]': ModLogType.microblogCommentRestored.toMbin, }; final response = await client.get(path, queryParams: query); diff --git a/lib/src/models/modlog.dart b/lib/src/models/modlog.dart index 533b9dbd..a727c9e5 100644 --- a/lib/src/models/modlog.dart +++ b/lib/src/models/modlog.dart @@ -42,12 +42,12 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.commentRestored => null, ModLogType.postPinned => null, ModLogType.postUnpinned => null, - ModLogType.post_deleted => + ModLogType.microblogPostDeleted => (json['subject'] as JsonMap)['postId'] as int, - ModLogType.post_restored => + ModLogType.microblogPostRestored => (json['subject'] as JsonMap)['postId'] as int, - ModLogType.post_comment_deleted => null, - ModLogType.post_comment_restored => null, + ModLogType.microblogCommentDeleted => null, + ModLogType.microblogCommentRestored => null, ModLogType.ban => null, ModLogType.unban => null, ModLogType.moderatorAdded => null, @@ -67,12 +67,12 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.commentRestored => null, ModLogType.postPinned => null, ModLogType.postUnpinned => null, - ModLogType.post_deleted => + ModLogType.microblogPostDeleted => (json['subject'] as JsonMap)['body'] as String, - ModLogType.post_restored => + ModLogType.microblogPostRestored => (json['subject'] as JsonMap)['body'] as String, - ModLogType.post_comment_deleted => null, - ModLogType.post_comment_restored => null, + ModLogType.microblogCommentDeleted => null, + ModLogType.microblogCommentRestored => null, ModLogType.ban => null, ModLogType.unban => null, ModLogType.moderatorAdded => null, @@ -94,12 +94,12 @@ abstract class ModlogItemModel with _$ModlogItemModel { ), ModLogType.postPinned => null, ModLogType.postUnpinned => null, - ModLogType.post_deleted => null, - ModLogType.post_restored => null, - ModLogType.post_comment_deleted => CommentModel.fromMbin( + ModLogType.microblogPostDeleted => null, + ModLogType.microblogPostRestored => null, + ModLogType.microblogCommentDeleted => CommentModel.fromMbin( json['subject'] as JsonMap, ), - ModLogType.post_comment_restored => CommentModel.fromMbin( + ModLogType.microblogCommentRestored => CommentModel.fromMbin( json['subject'] as JsonMap, ), ModLogType.ban => null, @@ -119,10 +119,10 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.commentRestored => null, ModLogType.postPinned => null, ModLogType.postUnpinned => null, - ModLogType.post_deleted => null, - ModLogType.post_restored => null, - ModLogType.post_comment_deleted => null, - ModLogType.post_comment_restored => null, + ModLogType.microblogPostDeleted => null, + ModLogType.microblogPostRestored => null, + ModLogType.microblogCommentDeleted => null, + ModLogType.microblogCommentRestored => null, ModLogType.ban => DetailedUserModel.fromMbin((json['subject'] as JsonMap)['bannedUser'] as JsonMap), ModLogType.unban => DetailedUserModel.fromMbin((json['subject'] as JsonMap)['bannedUser'] as JsonMap), ModLogType.moderatorAdded => null, @@ -162,10 +162,10 @@ abstract class ModlogItemModel with _$ModlogItemModel { ModLogType.commentRestored => null, ModLogType.postPinned => null, ModLogType.postUnpinned => null, - ModLogType.post_deleted => null, - ModLogType.post_restored => null, - ModLogType.post_comment_deleted => null, - ModLogType.post_comment_restored => null, + ModLogType.microblogPostDeleted => null, + ModLogType.microblogPostRestored => null, + ModLogType.microblogCommentDeleted => null, + ModLogType.microblogCommentRestored => null, ModLogType.ban => DetailedUserModel.fromLemmy(json['banned_person'] as JsonMap), ModLogType.unban => DetailedUserModel.fromLemmy(json['banned_person'] as JsonMap), ModLogType.moderatorAdded => DetailedUserModel.fromLemmy(json['modded_person'] as JsonMap), diff --git a/lib/src/screens/explore/mod_log.dart b/lib/src/screens/explore/mod_log.dart index 879fef6a..094eea6a 100644 --- a/lib/src/screens/explore/mod_log.dart +++ b/lib/src/screens/explore/mod_log.dart @@ -111,7 +111,7 @@ class _ModLogState extends State { builder: (context) => PostPage(postType: PostType.thread, postId: item.postId), ), - ModLogType.post_deleted => + ModLogType.microblogPostDeleted => item.postId == null ? null : () => pushRoute( @@ -119,7 +119,7 @@ class _ModLogState extends State { builder: (context) => PostPage(postType: PostType.thread, postId: item.postId), ), - ModLogType.post_restored => + ModLogType.microblogPostRestored => item.postId == null ? null : () => pushRoute( @@ -127,7 +127,7 @@ class _ModLogState extends State { builder: (context) => PostPage(postType: PostType.thread, postId: item.postId), ), - ModLogType.post_comment_deleted => + ModLogType.microblogCommentDeleted => item.comment == null ? null : () => pushRoute( @@ -135,7 +135,7 @@ class _ModLogState extends State { builder: (context) => PostCommentScreen(PostType.thread, item.comment!.id), ), - ModLogType.post_comment_restored => + ModLogType.microblogCommentRestored => item.comment == null ? null : () => pushRoute( @@ -244,10 +244,10 @@ class _ModLogState extends State { ModLogType.commentRestored => Colors.green, ModLogType.postPinned => Colors.orange, ModLogType.postUnpinned => Colors.orange, - ModLogType.post_deleted => Colors.red, - ModLogType.post_restored => Colors.green, - ModLogType.post_comment_deleted => Colors.red, - ModLogType.post_comment_restored => Colors.green, + ModLogType.microblogPostDeleted => Colors.red, + ModLogType.microblogPostRestored => Colors.green, + ModLogType.microblogCommentDeleted => Colors.red, + ModLogType.microblogCommentRestored => Colors.green, ModLogType.ban => Colors.red, ModLogType.unban => Colors.green, ModLogType.moderatorAdded => Colors.orange, @@ -279,16 +279,16 @@ class _ModLogState extends State { ModLogType.postUnpinned => l( context, ).modlog_unpinnedPost, - ModLogType.post_deleted => l( + ModLogType.microblogPostDeleted => l( context, ).modlog_deletedPost, - ModLogType.post_restored => l( + ModLogType.microblogPostRestored => l( context, ).modlog_restoredPost, - ModLogType.post_comment_deleted => l( + ModLogType.microblogCommentDeleted => l( context, ).modlog_deletedComment, - ModLogType.post_comment_restored => l( + ModLogType.microblogCommentRestored => l( context, ).modlog_restoredComment, ModLogType.ban => l(context).modlog_bannedUser, @@ -377,24 +377,6 @@ SelectionMenu modlogFilterType(BuildContext context) { value: ModLogType.postUnpinned, title: l(context).modlog_unpinnedPost, ), - if (software == ServerSoftware.mbin) ...[ - SelectionMenuItem( - value: ModLogType.post_deleted, - title: l(context).modlog_deletedPost, - ), - SelectionMenuItem( - value: ModLogType.post_restored, - title: l(context).modlog_restoredPost, - ), - SelectionMenuItem( - value: ModLogType.post_comment_deleted, - title: l(context).modlog_deletedComment, - ), - SelectionMenuItem( - value: ModLogType.post_comment_restored, - title: l(context).modlog_restoredComment, - ), - ], SelectionMenuItem( value: ModLogType.ban, title: l(context).modlog_bannedUser, From 3ab08517f95465e61f690b9b17eed1885c635e00 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Sat, 3 Jan 2026 22:07:51 +1000 Subject: [PATCH 16/18] Put content info in a wrap instead of row. --- .../widgets/content_item/content_info.dart | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/lib/src/widgets/content_item/content_info.dart b/lib/src/widgets/content_item/content_info.dart index 3a525be9..307f3f04 100644 --- a/lib/src/widgets/content_item/content_info.dart +++ b/lib/src/widgets/content_item/content_info.dart @@ -203,20 +203,25 @@ class ContentInfo extends StatelessWidget { // return Row( final internal = Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - ?warning, - ?pinned, - ?nsfw, - ?oc, - ?langWidget, - if (showCommunityFirst) ?communityWidget, - if (!showCommunityFirst) ?userWidget, - ?created, - if (!showCommunityFirst) ?communityWidget, - if (showCommunityFirst) ?userWidget, - ], + Flexible( + child: Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + runSpacing: 4, + children: [ + ?warning, + ?pinned, + ?nsfw, + ?oc, + ?langWidget, + if (showCommunityFirst) ?communityWidget, + if (!showCommunityFirst) ?userWidget, + ?created, + if (!showCommunityFirst) ?communityWidget, + if (showCommunityFirst) ?userWidget, + ], + ), ), ?menuWidget, ], From d3f198cc65dfc8cbce18d2a1abdbc626bc2d4868 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Mon, 5 Jan 2026 17:13:39 +1000 Subject: [PATCH 17/18] Revert "Put content info in a wrap instead of row." This reverts commit 3ab08517f95465e61f690b9b17eed1885c635e00. --- .../widgets/content_item/content_info.dart | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/lib/src/widgets/content_item/content_info.dart b/lib/src/widgets/content_item/content_info.dart index 307f3f04..3a525be9 100644 --- a/lib/src/widgets/content_item/content_info.dart +++ b/lib/src/widgets/content_item/content_info.dart @@ -203,25 +203,20 @@ class ContentInfo extends StatelessWidget { // return Row( final internal = Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Flexible( - child: Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - runSpacing: 4, - children: [ - ?warning, - ?pinned, - ?nsfw, - ?oc, - ?langWidget, - if (showCommunityFirst) ?communityWidget, - if (!showCommunityFirst) ?userWidget, - ?created, - if (!showCommunityFirst) ?communityWidget, - if (showCommunityFirst) ?userWidget, - ], - ), + Row( + children: [ + ?warning, + ?pinned, + ?nsfw, + ?oc, + ?langWidget, + if (showCommunityFirst) ?communityWidget, + if (!showCommunityFirst) ?userWidget, + ?created, + if (!showCommunityFirst) ?communityWidget, + if (showCommunityFirst) ?userWidget, + ], ), ?menuWidget, ], From 5f4a54f5c6e3fff73858b128f3ab45f5f1a15141 Mon Sep 17 00:00:00 2001 From: olorin99 Date: Mon, 5 Jan 2026 17:28:51 +1000 Subject: [PATCH 18/18] Have user/community names fade when overflowing. Switch from manual padding of content info items to using row spacing. --- .../widgets/content_item/content_info.dart | 145 ++++++++---------- 1 file changed, 62 insertions(+), 83 deletions(-) diff --git a/lib/src/widgets/content_item/content_info.dart b/lib/src/widgets/content_item/content_info.dart index 3a525be9..1db1b29d 100644 --- a/lib/src/widgets/content_item/content_info.dart +++ b/lib/src/widgets/content_item/content_info.dart @@ -52,61 +52,46 @@ class ContentInfo extends StatelessWidget { Widget build(BuildContext context) { final warning = filterListWarnings == null || filterListWarnings!.isEmpty ? null - : Padding( - padding: const EdgeInsets.only(right: 10), - child: Tooltip( - message: l( - context, - ).filterListWarningX(filterListWarnings!.join(', ')), - triggerMode: TooltipTriggerMode.tap, - child: const Icon( - Symbols.warning_amber_rounded, - color: Colors.red, - ), - ), + : Tooltip( + message: l( + context, + ).filterListWarningX(filterListWarnings!.join(', ')), + triggerMode: TooltipTriggerMode.tap, + child: const Icon(Symbols.warning_amber_rounded, color: Colors.red), ); final pinned = !isPinned ? null - : Padding( - padding: const EdgeInsets.only(right: 10), - child: Tooltip( - message: l(context).pinnedInCommunity, - triggerMode: TooltipTriggerMode.tap, - child: const Icon(Symbols.push_pin_rounded, size: 20), - ), + : Tooltip( + message: l(context).pinnedInCommunity, + triggerMode: TooltipTriggerMode.tap, + child: const Icon(Symbols.push_pin_rounded, size: 20), ); final nsfw = !isNSFW ? null - : Padding( - padding: const EdgeInsets.only(right: 10), - child: Tooltip( - message: l(context).notSafeForWork_long, - triggerMode: TooltipTriggerMode.tap, - child: Text( - l(context).notSafeForWork_short, - style: const TextStyle( - color: Colors.red, - fontWeight: FontWeight.bold, - ), + : Tooltip( + message: l(context).notSafeForWork_long, + triggerMode: TooltipTriggerMode.tap, + child: Text( + l(context).notSafeForWork_short, + style: const TextStyle( + color: Colors.red, + fontWeight: FontWeight.bold, ), ), ); final oc = !isOC ? null - : Padding( - padding: const EdgeInsets.only(right: 10), - child: Tooltip( - message: l(context).originalContent_long, - triggerMode: TooltipTriggerMode.tap, - child: Text( - l(context).originalContent_short, - style: const TextStyle( - color: Colors.lightGreen, - fontWeight: FontWeight.bold, - ), + : Tooltip( + message: l(context).originalContent_long, + triggerMode: TooltipTriggerMode.tap, + child: Text( + l(context).originalContent_short, + style: const TextStyle( + color: Colors.lightGreen, + fontWeight: FontWeight.bold, ), ), ); @@ -115,43 +100,36 @@ class ContentInfo extends StatelessWidget { lang == null || lang == context.read().profile.defaultCreateLanguage ? null - : Padding( - padding: const EdgeInsets.only(right: 10), - child: Tooltip( - message: getLanguageName(context, lang!), - triggerMode: TooltipTriggerMode.tap, - child: Text( - lang!, - style: const TextStyle( - color: Colors.purple, - fontWeight: FontWeight.bold, - ), + : Tooltip( + message: getLanguageName(context, lang!), + triggerMode: TooltipTriggerMode.tap, + child: Text( + lang!, + style: const TextStyle( + color: Colors.purple, + fontWeight: FontWeight.bold, ), ), ); final created = createdAt == null ? null - : Padding( - padding: const EdgeInsets.only(right: 10), - child: Tooltip( - message: - l(context).createdAt(dateTimeFormat(createdAt!)) + - (editedAt == null - ? '' - : '\n${l(context).editedAt(dateTimeFormat(editedAt!))}'), - triggerMode: TooltipTriggerMode.tap, - child: Text( - dateDiffFormat(createdAt!), - style: const TextStyle(fontWeight: FontWeight.w300), - ), + : Tooltip( + message: + l(context).createdAt(dateTimeFormat(createdAt!)) + + (editedAt == null + ? '' + : '\n${l(context).editedAt(dateTimeFormat(editedAt!))}'), + triggerMode: TooltipTriggerMode.tap, + child: Text( + dateDiffFormat(createdAt!), + style: const TextStyle(fontWeight: FontWeight.w300), ), ); final userWidget = user == null ? null - : Padding( - padding: const EdgeInsets.only(right: 10), + : Flexible( child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -188,8 +166,7 @@ class ContentInfo extends StatelessWidget { final communityWidget = community == null ? null - : Padding( - padding: const EdgeInsets.only(right: 10), + : Flexible( child: DisplayName( community!.name, icon: community!.icon, @@ -200,23 +177,25 @@ class ContentInfo extends StatelessWidget { ), ); - // return Row( final internal = Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - ?warning, - ?pinned, - ?nsfw, - ?oc, - ?langWidget, - if (showCommunityFirst) ?communityWidget, - if (!showCommunityFirst) ?userWidget, - ?created, - if (!showCommunityFirst) ?communityWidget, - if (showCommunityFirst) ?userWidget, - ], + Expanded( + child: Row( + spacing: 10, + children: [ + ?warning, + ?pinned, + ?nsfw, + ?oc, + ?langWidget, + if (showCommunityFirst) ?communityWidget, + if (!showCommunityFirst) ?userWidget, + ?created, + if (!showCommunityFirst) ?communityWidget, + if (showCommunityFirst) ?userWidget, + ], + ), ), ?menuWidget, ],