diff --git a/lib/stores/config_store.g.dart b/lib/stores/config_store.g.dart index 1f82534..63510ea 100644 --- a/lib/stores/config_store.g.dart +++ b/lib/stores/config_store.g.dart @@ -7,8 +7,8 @@ part of 'config_store.dart'; // ************************************************************************** ConfigStore _$ConfigStoreFromJson(Map json) => ConfigStore() - ..theme = _$enumDecodeNullable(_$ThemeModeEnumMap, json['theme']) ?? - ThemeMode.system + ..theme = + $enumDecodeNullable(_$ThemeModeEnumMap, json['theme']) ?? ThemeMode.system ..amoledDarkMode = json['amoledDarkMode'] as bool? ?? false ..locale = LocaleSerde.fromJson(json['locale'] as String?) ..showAvatars = json['showAvatars'] as bool? ?? true @@ -28,43 +28,6 @@ Map _$ConfigStoreToJson(ConfigStore instance) => 'defaultListingType': instance.defaultListingType, }; -K _$enumDecode( - Map enumValues, - Object? source, { - K? unknownValue, -}) { - if (source == null) { - throw ArgumentError( - 'A value must be provided. Supported values: ' - '${enumValues.values.join(', ')}', - ); - } - - return enumValues.entries.singleWhere( - (e) => e.value == source, - orElse: () { - if (unknownValue == null) { - throw ArgumentError( - '`$source` is not one of the supported values: ' - '${enumValues.values.join(', ')}', - ); - } - return MapEntry(unknownValue, enumValues.values.first); - }, - ).key; -} - -K? _$enumDecodeNullable( - Map enumValues, - dynamic source, { - K? unknownValue, -}) { - if (source == null) { - return null; - } - return _$enumDecode(enumValues, source, unknownValue: unknownValue); -} - const _$ThemeModeEnumMap = { ThemeMode.system: 'system', ThemeMode.light: 'light', diff --git a/lib/theme.dart b/lib/theme.dart index 07e3b3b..6897fe4 100644 --- a/lib/theme.dart +++ b/lib/theme.dart @@ -85,6 +85,11 @@ ThemeData _themeFactory({bool dark = false, bool amoled = false}) { ), ), ), + dialogTheme: DialogTheme( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), ); } diff --git a/lib/widgets/comment/comment.dart b/lib/widgets/comment/comment.dart index 60907b2..bf42abc 100644 --- a/lib/widgets/comment/comment.dart +++ b/lib/widgets/comment/comment.dart @@ -102,7 +102,11 @@ class CommentWidget extends StatelessWidget { asyncStore: context.read().deletingState, child: AsyncStoreListener( asyncStore: context.read().savingState, - child: const _CommentWidget(), + child: AsyncStoreListener( + asyncStore: context.read().reportingState, + successMessageBuilder: (context, data) => 'Comment reported', + child: const _CommentWidget(), + ), ), ), ), diff --git a/lib/widgets/comment/comment_more_menu_button.dart b/lib/widgets/comment/comment_more_menu_button.dart index 9dba60c..9e68c31 100644 --- a/lib/widgets/comment/comment_more_menu_button.dart +++ b/lib/widgets/comment/comment_more_menu_button.dart @@ -10,6 +10,7 @@ import '../../util/observer_consumers.dart'; import '../../util/share.dart'; import '../bottom_modal.dart'; import '../markdown_mode_icon.dart'; +import '../report_dialog.dart'; import '../tile_action.dart'; import '../write_comment.dart'; import 'comment.dart'; @@ -155,6 +156,23 @@ class _CommentMoreMenuPopup extends HookWidget { store.block(token); }), ), + ListTile( + leading: store.reportingState.isLoading + ? const CircularProgressIndicator.adaptive() + : const Icon(Icons.flag), + title: const Text('Report'), + onTap: store.reportingState.isLoading + ? null + : () { + Navigator.of(context).pop(); + loggedInAction((token) async { + final reason = await ReportDialog.show(context); + if (reason != null) { + await store.report(token, reason); + } + })(); + }, + ), ListTile( leading: const Icon(Icons.info_outline), title: const Text('Nerd stuff'), diff --git a/lib/widgets/comment/comment_store.dart b/lib/widgets/comment/comment_store.dart index 685dfc3..f04575a 100644 --- a/lib/widgets/comment/comment_store.dart +++ b/lib/widgets/comment/comment_store.dart @@ -36,6 +36,7 @@ abstract class _CommentStore with Store { final markPersonMentionAsReadState = AsyncStore(); final markAsReadState = AsyncStore(); final blockingState = AsyncStore(); + final reportingState = AsyncStore(); @computed bool get isMine => @@ -76,6 +77,20 @@ abstract class _CommentStore with Store { collapsed = !collapsed; } + @action + Future report(Jwt token, String reason) async { + if (reason.trim().isEmpty) throw ArgumentError('reason must not be empty'); + + await reportingState.runLemmy( + comment.instanceHost, + CreateCommentReport( + commentId: comment.comment.id, + reason: reason, + auth: token.raw, + ), + ); + } + @action Future delete(Jwt token) async { final result = await deletingState.runLemmy( diff --git a/lib/widgets/comment/comment_store.g.dart b/lib/widgets/comment/comment_store.g.dart index 371a9b4..ecaf626 100644 --- a/lib/widgets/comment/comment_store.g.dart +++ b/lib/widgets/comment/comment_store.g.dart @@ -88,6 +88,13 @@ mixin _$CommentStore on _CommentStore, Store { }); } + final _$reportAsyncAction = AsyncAction('_CommentStore.report'); + + @override + Future report(Jwt token, String reason) { + return _$reportAsyncAction.run(() => super.report(token, reason)); + } + final _$deleteAsyncAction = AsyncAction('_CommentStore.delete'); @override diff --git a/lib/widgets/post/post.dart b/lib/widgets/post/post.dart index 442fd77..3f3eab7 100644 --- a/lib/widgets/post/post.dart +++ b/lib/widgets/post/post.dart @@ -41,7 +41,11 @@ class PostTile extends StatelessWidget { final name = state.personView.person.preferredName; return state.blocked ? '$name blocked' : '$name unblocked'; }, - child: const _Post(), + child: AsyncStoreListener( + asyncStore: context.read().reportingState, + successMessageBuilder: (context, data) => 'Post reported', + child: const _Post(), + ), ), ), ); diff --git a/lib/widgets/post/post_more_menu.dart b/lib/widgets/post/post_more_menu.dart index a7a1d61..b3c3321 100644 --- a/lib/widgets/post/post_more_menu.dart +++ b/lib/widgets/post/post_more_menu.dart @@ -12,6 +12,7 @@ import '../../util/icons.dart'; import '../../util/observer_consumers.dart'; import '../bottom_modal.dart'; import '../info_table_popup.dart'; +import '../report_dialog.dart'; import 'post_store.dart'; class PostMoreMenuButton extends StatelessWidget { @@ -121,6 +122,23 @@ class PostMoreMenu extends HookWidget { ); }, ), + ListTile( + leading: store.reportingState.isLoading + ? const CircularProgressIndicator.adaptive() + : const Icon(Icons.flag), + title: const Text('Report'), + onTap: store.reportingState.isLoading + ? null + : () { + Navigator.of(context).pop(); + loggedInAction((token) async { + final reason = await ReportDialog.show(context); + if (reason != null) { + await store.report(token, reason); + } + })(); + }, + ), ListTile( leading: const Icon(Icons.info_outline), title: const Text('Nerd stuff'), diff --git a/lib/widgets/post/post_store.dart b/lib/widgets/post/post_store.dart index 48cb524..daa0eee 100644 --- a/lib/widgets/post/post_store.dart +++ b/lib/widgets/post/post_store.dart @@ -14,6 +14,7 @@ abstract class _PostStore with Store { final votingState = AsyncStore(); final savingState = AsyncStore(); final userBlockingState = AsyncStore(); + final reportingState = AsyncStore(); @observable PostView postView; @@ -55,6 +56,20 @@ abstract class _PostStore with Store { if (result != null) postView = result; } + @action + Future report(Jwt token, String reason) async { + if (reason.trim().isEmpty) throw ArgumentError('reason must not be empty'); + + await reportingState.runLemmy( + postView.instanceHost, + CreatePostReport( + postId: postView.post.id, + reason: reason, + auth: token.raw, + ), + ); + } + @action Future blockUser(Jwt token) async { final result = await userBlockingState.runLemmy( diff --git a/lib/widgets/post/post_store.g.dart b/lib/widgets/post/post_store.g.dart index f17f89f..583c01e 100644 --- a/lib/widgets/post/post_store.g.dart +++ b/lib/widgets/post/post_store.g.dart @@ -45,6 +45,13 @@ mixin _$PostStore on _PostStore, Store { return _$saveAsyncAction.run(() => super.save(token)); } + final _$reportAsyncAction = AsyncAction('_PostStore.report'); + + @override + Future report(Jwt token, String reason) { + return _$reportAsyncAction.run(() => super.report(token, reason)); + } + final _$blockUserAsyncAction = AsyncAction('_PostStore.blockUser'); @override diff --git a/lib/widgets/report_dialog.dart b/lib/widgets/report_dialog.dart new file mode 100644 index 0000000..541f9b0 --- /dev/null +++ b/lib/widgets/report_dialog.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; + +class ReportDialog extends HookWidget { + const ReportDialog(); + + @override + Widget build(BuildContext context) { + final controller = useListenable(useTextEditingController()); + + return AlertDialog( + title: const Text('Report'), + content: TextField( + autofocus: true, + controller: controller, + decoration: const InputDecoration( + label: Text('reason'), + ), + minLines: 1, + maxLines: 3, + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('cancel'), + ), + TextButton( + onPressed: controller.text.trim().isEmpty + ? null + : () => Navigator.of(context).pop(controller.text.trim()), + child: const Text('report'), + ), + ], + ); + } + + static Future show(BuildContext context) async => + showDialog(context: context, builder: (context) => const ReportDialog()); +} diff --git a/pubspec.lock b/pubspec.lock index 9a5a53b..59d3134 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -300,7 +300,7 @@ packages: name: freezed_annotation url: "https://pub.dartlang.org" source: hosted - version: "0.14.3" + version: "0.15.0" frontend_server_client: dependency: transitive description: @@ -412,14 +412,14 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.1.0" + version: "4.3.0" json_serializable: dependency: "direct dev" description: name: json_serializable url: "https://pub.dartlang.org" source: hosted - version: "5.0.0" + version: "6.0.1" keyboard_dismisser: dependency: "direct main" description: @@ -440,7 +440,7 @@ packages: name: lemmy_api_client url: "https://pub.dartlang.org" source: hosted - version: "0.16.0" + version: "0.17.0" logging: dependency: "direct main" description: @@ -790,7 +790,7 @@ packages: name: source_helper url: "https://pub.dartlang.org" source: hosted - version: "1.2.1" + version: "1.3.0" source_span: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 96abd48..92eb9c0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,12 +49,12 @@ dependencies: # utils timeago: ^3.0.2 fuzzy: ^0.4.0-nullsafety.0 - lemmy_api_client: ^0.16.0 + lemmy_api_client: ^0.17.0 intl: ^0.17.0 matrix4_transform: ^2.0.0 - json_annotation: ^4.1.0 + json_annotation: ^4.3.0 keyboard_dismisser: ^2.0.0 - freezed_annotation: ^0.14.3 + freezed_annotation: ^0.15.0 logging: ^1.0.1 flutter: @@ -70,7 +70,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_launcher_icons: ^0.9.2 - json_serializable: ^5.0.0 + json_serializable: ^6.0.0 build_runner: ^2.1.2 mobx_codegen: ^2.0.2 freezed: ^0.14.1+2