Merge pull request #272 from krawieck/feature/report

This commit is contained in:
Filip Krawczyk 2021-10-25 20:53:21 +02:00 committed by GitHub
commit 8f34591111
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 145 additions and 50 deletions

View File

@ -7,8 +7,8 @@ part of 'config_store.dart';
// ************************************************************************** // **************************************************************************
ConfigStore _$ConfigStoreFromJson(Map<String, dynamic> json) => ConfigStore() ConfigStore _$ConfigStoreFromJson(Map<String, dynamic> json) => ConfigStore()
..theme = _$enumDecodeNullable(_$ThemeModeEnumMap, json['theme']) ?? ..theme =
ThemeMode.system $enumDecodeNullable(_$ThemeModeEnumMap, json['theme']) ?? ThemeMode.system
..amoledDarkMode = json['amoledDarkMode'] as bool? ?? false ..amoledDarkMode = json['amoledDarkMode'] as bool? ?? false
..locale = LocaleSerde.fromJson(json['locale'] as String?) ..locale = LocaleSerde.fromJson(json['locale'] as String?)
..showAvatars = json['showAvatars'] as bool? ?? true ..showAvatars = json['showAvatars'] as bool? ?? true
@ -28,43 +28,6 @@ Map<String, dynamic> _$ConfigStoreToJson(ConfigStore instance) =>
'defaultListingType': instance.defaultListingType, 'defaultListingType': instance.defaultListingType,
}; };
K _$enumDecode<K, V>(
Map<K, V> 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<K, V>(
Map<K, V> enumValues,
dynamic source, {
K? unknownValue,
}) {
if (source == null) {
return null;
}
return _$enumDecode<K, V>(enumValues, source, unknownValue: unknownValue);
}
const _$ThemeModeEnumMap = { const _$ThemeModeEnumMap = {
ThemeMode.system: 'system', ThemeMode.system: 'system',
ThemeMode.light: 'light', ThemeMode.light: 'light',

View File

@ -85,6 +85,11 @@ ThemeData _themeFactory({bool dark = false, bool amoled = false}) {
), ),
), ),
), ),
dialogTheme: DialogTheme(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
); );
} }

View File

@ -102,7 +102,11 @@ class CommentWidget extends StatelessWidget {
asyncStore: context.read<CommentStore>().deletingState, asyncStore: context.read<CommentStore>().deletingState,
child: AsyncStoreListener( child: AsyncStoreListener(
asyncStore: context.read<CommentStore>().savingState, asyncStore: context.read<CommentStore>().savingState,
child: const _CommentWidget(), child: AsyncStoreListener<CommentReportView>(
asyncStore: context.read<CommentStore>().reportingState,
successMessageBuilder: (context, data) => 'Comment reported',
child: const _CommentWidget(),
),
), ),
), ),
), ),

View File

@ -10,6 +10,7 @@ import '../../util/observer_consumers.dart';
import '../../util/share.dart'; import '../../util/share.dart';
import '../bottom_modal.dart'; import '../bottom_modal.dart';
import '../markdown_mode_icon.dart'; import '../markdown_mode_icon.dart';
import '../report_dialog.dart';
import '../tile_action.dart'; import '../tile_action.dart';
import '../write_comment.dart'; import '../write_comment.dart';
import 'comment.dart'; import 'comment.dart';
@ -155,6 +156,23 @@ class _CommentMoreMenuPopup extends HookWidget {
store.block(token); 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( ListTile(
leading: const Icon(Icons.info_outline), leading: const Icon(Icons.info_outline),
title: const Text('Nerd stuff'), title: const Text('Nerd stuff'),

View File

@ -36,6 +36,7 @@ abstract class _CommentStore with Store {
final markPersonMentionAsReadState = AsyncStore<PersonMentionView>(); final markPersonMentionAsReadState = AsyncStore<PersonMentionView>();
final markAsReadState = AsyncStore<FullCommentView>(); final markAsReadState = AsyncStore<FullCommentView>();
final blockingState = AsyncStore<BlockedPerson>(); final blockingState = AsyncStore<BlockedPerson>();
final reportingState = AsyncStore<CommentReportView>();
@computed @computed
bool get isMine => bool get isMine =>
@ -76,6 +77,20 @@ abstract class _CommentStore with Store {
collapsed = !collapsed; collapsed = !collapsed;
} }
@action
Future<void> 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 @action
Future<void> delete(Jwt token) async { Future<void> delete(Jwt token) async {
final result = await deletingState.runLemmy( final result = await deletingState.runLemmy(

View File

@ -88,6 +88,13 @@ mixin _$CommentStore on _CommentStore, Store {
}); });
} }
final _$reportAsyncAction = AsyncAction('_CommentStore.report');
@override
Future<void> report(Jwt token, String reason) {
return _$reportAsyncAction.run(() => super.report(token, reason));
}
final _$deleteAsyncAction = AsyncAction('_CommentStore.delete'); final _$deleteAsyncAction = AsyncAction('_CommentStore.delete');
@override @override

View File

@ -41,7 +41,11 @@ class PostTile extends StatelessWidget {
final name = state.personView.person.preferredName; final name = state.personView.person.preferredName;
return state.blocked ? '$name blocked' : '$name unblocked'; return state.blocked ? '$name blocked' : '$name unblocked';
}, },
child: const _Post(), child: AsyncStoreListener<PostReportView>(
asyncStore: context.read<PostStore>().reportingState,
successMessageBuilder: (context, data) => 'Post reported',
child: const _Post(),
),
), ),
), ),
); );

View File

@ -12,6 +12,7 @@ import '../../util/icons.dart';
import '../../util/observer_consumers.dart'; import '../../util/observer_consumers.dart';
import '../bottom_modal.dart'; import '../bottom_modal.dart';
import '../info_table_popup.dart'; import '../info_table_popup.dart';
import '../report_dialog.dart';
import 'post_store.dart'; import 'post_store.dart';
class PostMoreMenuButton extends StatelessWidget { 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( ListTile(
leading: const Icon(Icons.info_outline), leading: const Icon(Icons.info_outline),
title: const Text('Nerd stuff'), title: const Text('Nerd stuff'),

View File

@ -14,6 +14,7 @@ abstract class _PostStore with Store {
final votingState = AsyncStore<PostView>(); final votingState = AsyncStore<PostView>();
final savingState = AsyncStore<PostView>(); final savingState = AsyncStore<PostView>();
final userBlockingState = AsyncStore<BlockedPerson>(); final userBlockingState = AsyncStore<BlockedPerson>();
final reportingState = AsyncStore<PostReportView>();
@observable @observable
PostView postView; PostView postView;
@ -55,6 +56,20 @@ abstract class _PostStore with Store {
if (result != null) postView = result; if (result != null) postView = result;
} }
@action
Future<void> 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 @action
Future<void> blockUser(Jwt token) async { Future<void> blockUser(Jwt token) async {
final result = await userBlockingState.runLemmy( final result = await userBlockingState.runLemmy(

View File

@ -45,6 +45,13 @@ mixin _$PostStore on _PostStore, Store {
return _$saveAsyncAction.run(() => super.save(token)); return _$saveAsyncAction.run(() => super.save(token));
} }
final _$reportAsyncAction = AsyncAction('_PostStore.report');
@override
Future<void> report(Jwt token, String reason) {
return _$reportAsyncAction.run(() => super.report(token, reason));
}
final _$blockUserAsyncAction = AsyncAction('_PostStore.blockUser'); final _$blockUserAsyncAction = AsyncAction('_PostStore.blockUser');
@override @override

View File

@ -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<String?> show(BuildContext context) async =>
showDialog(context: context, builder: (context) => const ReportDialog());
}

View File

@ -300,7 +300,7 @@ packages:
name: freezed_annotation name: freezed_annotation
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.14.3" version: "0.15.0"
frontend_server_client: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -412,14 +412,14 @@ packages:
name: json_annotation name: json_annotation
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.1.0" version: "4.3.0"
json_serializable: json_serializable:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: json_serializable name: json_serializable
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.0.0" version: "6.0.1"
keyboard_dismisser: keyboard_dismisser:
dependency: "direct main" dependency: "direct main"
description: description:
@ -440,7 +440,7 @@ packages:
name: lemmy_api_client name: lemmy_api_client
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.16.0" version: "0.17.0"
logging: logging:
dependency: "direct main" dependency: "direct main"
description: description:
@ -790,7 +790,7 @@ packages:
name: source_helper name: source_helper
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.1" version: "1.3.0"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:

View File

@ -49,12 +49,12 @@ dependencies:
# utils # utils
timeago: ^3.0.2 timeago: ^3.0.2
fuzzy: ^0.4.0-nullsafety.0 fuzzy: ^0.4.0-nullsafety.0
lemmy_api_client: ^0.16.0 lemmy_api_client: ^0.17.0
intl: ^0.17.0 intl: ^0.17.0
matrix4_transform: ^2.0.0 matrix4_transform: ^2.0.0
json_annotation: ^4.1.0 json_annotation: ^4.3.0
keyboard_dismisser: ^2.0.0 keyboard_dismisser: ^2.0.0
freezed_annotation: ^0.14.3 freezed_annotation: ^0.15.0
logging: ^1.0.1 logging: ^1.0.1
flutter: flutter:
@ -70,7 +70,7 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_launcher_icons: ^0.9.2 flutter_launcher_icons: ^0.9.2
json_serializable: ^5.0.0 json_serializable: ^6.0.0
build_runner: ^2.1.2 build_runner: ^2.1.2
mobx_codegen: ^2.0.2 mobx_codegen: ^2.0.2
freezed: ^0.14.1+2 freezed: ^0.14.1+2