From 0adc26a5fbb92d6c6f31c1e12291264b61f3de87 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 5 Nov 2021 16:00:03 +0100 Subject: [PATCH 1/6] add search & block --- ios/Podfile.lock | 14 +- lib/pages/settings/blocks/block_dialog.dart | 134 ++++++++++++++++++ lib/pages/settings/blocks/blocks.dart | 83 ++++++++--- lib/pages/settings/blocks/blocks_store.dart | 67 ++++++++- lib/pages/settings/blocks/blocks_store.g.dart | 24 +++- pubspec.lock | 28 ++++ pubspec.yaml | 1 + 7 files changed, 320 insertions(+), 31 deletions(-) create mode 100644 lib/pages/settings/blocks/block_dialog.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 7701645..fb933c6 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,5 +1,7 @@ PODS: - Flutter (1.0.0) + - flutter_keyboard_visibility (0.0.1): + - Flutter - image_picker (0.0.1): - Flutter - package_info_plus (0.4.5): @@ -15,6 +17,7 @@ PODS: DEPENDENCIES: - Flutter (from `Flutter`) + - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) - image_picker (from `.symlinks/plugins/image_picker/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider (from `.symlinks/plugins/path_provider/ios`) @@ -25,6 +28,8 @@ DEPENDENCIES: EXTERNAL SOURCES: Flutter: :path: Flutter + flutter_keyboard_visibility: + :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" image_picker: :path: ".symlinks/plugins/image_picker/ios" package_info_plus: @@ -40,12 +45,13 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a - image_picker: e06f7a68f000bd36f552c1847e33cda96ed31f1f + flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 + image_picker: 9aa50e1d8cdacdbed739e925b7eea16d014367e6 package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c + path_provider: d1e9807085df1f9cc9318206cd649dc0b76be3de share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 - shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d - url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef + shared_preferences: 5033afbb22d372e15aff8ff766df9021b845f273 + url_launcher: b6e016d912f04be9f5bf6e8e82dc599b7ba59649 PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c diff --git a/lib/pages/settings/blocks/block_dialog.dart b/lib/pages/settings/blocks/block_dialog.dart new file mode 100644 index 0000000..5bcf40d --- /dev/null +++ b/lib/pages/settings/blocks/block_dialog.dart @@ -0,0 +1,134 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:lemmy_api_client/v3.dart'; +import 'package:provider/provider.dart'; + +import '../../../util/extensions/api.dart'; +import '../../../widgets/avatar.dart'; +import 'blocks_store.dart'; + +class BlockPersonDialog extends StatelessWidget { + const BlockPersonDialog(); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Row( + children: const [ + Expanded(child: Text('Block User')), + ], + ), + content: TypeAheadField( + suggestionsCallback: (pattern) async { + if (pattern.trim().isEmpty) return const Iterable.empty(); + return LemmyApiV3(context.read().instanceHost) + .run(Search( + q: pattern, + auth: context.read().token.raw, + type: SearchType.users, + limit: 10, + )) + .then((value) => value.users); + }, + itemBuilder: (context, user) { + return ListTile( + leading: Avatar( + url: user.person.avatar, + radius: 20, + ), + title: Text(user.person.originPreferredName), + ); + }, + onSuggestionSelected: (suggestion) => + Navigator.of(context).pop(suggestion), + loadingBuilder: (context) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Padding( + padding: EdgeInsets.all(16), + child: CircularProgressIndicator.adaptive(), + ), + ], + ), + keepSuggestionsOnLoading: false, + noItemsFoundBuilder: (context) => const SizedBox(), + hideOnEmpty: true, + textFieldConfiguration: const TextFieldConfiguration(autofocus: true), + ), + ); + } + + static Future show(BuildContext context) async { + final store = context.read(); + return showDialog( + context: context, + builder: (context) => Provider.value( + value: store, + child: const BlockPersonDialog(), + ), + ); + } +} + +class BlockCommunityDialog extends StatelessWidget { + const BlockCommunityDialog(); + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Row( + children: const [ + Expanded(child: Text('Block Community')), + ], + ), + content: TypeAheadField( + suggestionsCallback: (pattern) async { + if (pattern.trim().isEmpty) return const Iterable.empty(); + return LemmyApiV3(context.read().instanceHost) + .run(Search( + q: pattern, + auth: context.read().token.raw, + type: SearchType.communities, + limit: 10, + )) + .then((value) => value.communities); + }, + itemBuilder: (context, community) { + return ListTile( + leading: Avatar( + url: community.community.icon, + radius: 20, + ), + title: Text(community.community.originPreferredName), + ); + }, + onSuggestionSelected: (suggestion) => + Navigator.of(context).pop(suggestion), + loadingBuilder: (context) => Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Padding( + padding: EdgeInsets.all(16), + child: CircularProgressIndicator.adaptive(), + ), + ], + ), + keepSuggestionsOnLoading: false, + noItemsFoundBuilder: (context) => const SizedBox(), + hideOnEmpty: true, + textFieldConfiguration: const TextFieldConfiguration(autofocus: true), + ), + ); + } + + static Future show(BuildContext context) async { + final store = context.read(); + return showDialog( + context: context, + builder: (context) => Provider.value( + value: store, + child: const BlockCommunityDialog(), + ), + ); + } +} diff --git a/lib/pages/settings/blocks/blocks.dart b/lib/pages/settings/blocks/blocks.dart index 89b22ee..a5d7630 100644 --- a/lib/pages/settings/blocks/blocks.dart +++ b/lib/pages/settings/blocks/blocks.dart @@ -1,11 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:nested/nested.dart'; +import '../../../hooks/logged_in_action.dart'; import '../../../hooks/stores.dart'; import '../../../l10n/l10n_from_string.dart'; import '../../../stores/accounts_store.dart'; import '../../../util/async_store_listener.dart'; import '../../../util/observer_consumers.dart'; +import 'block_dialog.dart'; import 'block_tile.dart'; import 'blocks_store.dart'; @@ -70,13 +73,26 @@ class _UserBlocksWrapper extends StatelessWidget { } } -class _UserBlocks extends StatelessWidget { +class _UserBlocks extends HookWidget { const _UserBlocks(); @override Widget build(BuildContext context) { - return AsyncStoreListener( - asyncStore: context.read().blocksState, + final loggedInAction = + useLoggedInAction(context.read().instanceHost); + + return Nested( + children: [ + AsyncStoreListener( + asyncStore: context.read().blocksState, + ), + AsyncStoreListener( + asyncStore: context.read().communityBlockingState, + ), + AsyncStoreListener( + asyncStore: context.read().userBlockingState, + ), + ], child: ObserverBuilder( builder: (context, store) { return RefreshIndicator( @@ -114,14 +130,27 @@ class _UserBlocks extends StatelessWidget { ), ), // TODO: add user search & block - // ListTile( - // leading: const Padding( - // padding: EdgeInsets.only(left: 16, right: 10), - // child: Icon(Icons.add), - // ), - // onTap: () {}, - // title: const Text('Block User'), - // ), + ListTile( + leading: Padding( + padding: const EdgeInsets.only(left: 16, right: 10), + child: store.userBlockingState.isLoading + ? const CircularProgressIndicator.adaptive() + : const Icon(Icons.add), + ), + onTap: store.userBlockingState.isLoading + ? null + : loggedInAction( + (token) async { + final person = + await BlockPersonDialog.show(context); + + if (person != null) { + await store.blockUser(token, person.person.id); + } + }, + ), + title: const Text('Block User'), + ), const Divider(), for (final community in store.blockedCommunities!) Provider( @@ -136,14 +165,30 @@ class _UserBlocks extends StatelessWidget { ), ), // TODO: add community search & block - // const ListTile( - // leading: Padding( - // padding: EdgeInsets.only(left: 16, right: 10), - // child: Icon(Icons.add), - // ), - // onTap: () {}, - // title: Text('Block Community'), - // ), + ListTile( + leading: Padding( + padding: const EdgeInsets.only(left: 16, right: 10), + child: store.communityBlockingState.isLoading + ? const CircularProgressIndicator.adaptive() + : const Icon(Icons.add), + ), + onTap: store.communityBlockingState.isLoading + ? null + : loggedInAction( + (token) async { + final community = + await BlockCommunityDialog.show(context); + + if (community != null) { + await store.blockCommunity( + token, + community.community.id, + ); + } + }, + ), + title: const Text('Block Community'), + ), ], ], ), diff --git a/lib/pages/settings/blocks/blocks_store.dart b/lib/pages/settings/blocks/blocks_store.dart index 02ddd0b..7b50c18 100644 --- a/lib/pages/settings/blocks/blocks_store.dart +++ b/lib/pages/settings/blocks/blocks_store.dart @@ -16,13 +16,16 @@ abstract class _BlocksStore with Store { _BlocksStore({required this.instanceHost, required this.token}); @observable - List? _blockedUsers; + ObservableList? _blockedUsers; @observable - List? _blockedCommunities; + ObservableList? _blockedCommunities; final blocksState = AsyncStore(); + final userBlockingState = AsyncStore(); + final communityBlockingState = AsyncStore(); + @computed Iterable? get blockedUsers => _blockedUsers?.where((u) => u.blocked); @@ -34,6 +37,60 @@ abstract class _BlocksStore with Store { @computed bool get isUsable => blockedUsers != null && blockedCommunities != null; + @action + Future blockUser(Jwt token, int id) async { + if (_blockedUsers == null) { + throw StateError("_blockedUsers can't be null at this moment"); + } + final res = await userBlockingState.runLemmy( + instanceHost, + BlockPerson( + personId: id, + block: true, + auth: token.raw, + ), + ); + + if (res != null && + _blockedUsers!.indexWhere((element) => element.person.id == id) == -1) { + _blockedUsers!.add( + UserBlockStore( + instanceHost: instanceHost, + person: res.personView.person, + token: token, + ), + ); + } + } + + @action + Future blockCommunity(Jwt token, int id) async { + if (_blockedCommunities == null) { + throw StateError("_blockedCommunities can't be null at this moment"); + } + final res = await communityBlockingState.runLemmy( + instanceHost, + BlockCommunity( + communityId: id, + block: true, + auth: token.raw, + ), + ); + + if (res != null && + _blockedCommunities! + .indexWhere((element) => element.community.id == id) == + -1) { + _blockedCommunities!.add( + CommunityBlockStore( + instanceHost: instanceHost, + community: res.communityView.community, + token: token, + ), + ); + } + } + @action Future refresh() async { final result = @@ -43,11 +100,13 @@ abstract class _BlocksStore with Store { _blockedUsers = result.myUser!.personBlocks .map((e) => UserBlockStore( instanceHost: instanceHost, token: token, person: e.target)) - .toList(); + .toList() + .asObservable(); _blockedCommunities = result.myUser!.communityBlocks .map((e) => CommunityBlockStore( instanceHost: instanceHost, token: token, community: e.community)) - .toList(); + .toList() + .asObservable(); } } } diff --git a/lib/pages/settings/blocks/blocks_store.g.dart b/lib/pages/settings/blocks/blocks_store.g.dart index 3785571..b344023 100644 --- a/lib/pages/settings/blocks/blocks_store.g.dart +++ b/lib/pages/settings/blocks/blocks_store.g.dart @@ -35,13 +35,13 @@ mixin _$BlocksStore on _BlocksStore, Store { final _$_blockedUsersAtom = Atom(name: '_BlocksStore._blockedUsers'); @override - List? get _blockedUsers { + ObservableList? get _blockedUsers { _$_blockedUsersAtom.reportRead(); return super._blockedUsers; } @override - set _blockedUsers(List? value) { + set _blockedUsers(ObservableList? value) { _$_blockedUsersAtom.reportWrite(value, super._blockedUsers, () { super._blockedUsers = value; }); @@ -51,18 +51,34 @@ mixin _$BlocksStore on _BlocksStore, Store { Atom(name: '_BlocksStore._blockedCommunities'); @override - List? get _blockedCommunities { + ObservableList? get _blockedCommunities { _$_blockedCommunitiesAtom.reportRead(); return super._blockedCommunities; } @override - set _blockedCommunities(List? value) { + set _blockedCommunities(ObservableList? value) { _$_blockedCommunitiesAtom.reportWrite(value, super._blockedCommunities, () { super._blockedCommunities = value; }); } + final _$blockUserAsyncAction = AsyncAction('_BlocksStore.blockUser'); + + @override + Future blockUser(Jwt token, int id) { + return _$blockUserAsyncAction.run(() => super.blockUser(token, id)); + } + + final _$blockCommunityAsyncAction = + AsyncAction('_BlocksStore.blockCommunity'); + + @override + Future blockCommunity(Jwt token, int id) { + return _$blockCommunityAsyncAction + .run(() => super.blockCommunity(token, id)); + } + final _$refreshAsyncAction = AsyncAction('_BlocksStore.refresh'); @override diff --git a/pubspec.lock b/pubspec.lock index fd00986..79d5bad 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -237,6 +237,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.18.0" + flutter_keyboard_visibility: + dependency: transitive + description: + name: flutter_keyboard_visibility + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" flutter_launcher_icons: dependency: "direct dev" description: @@ -282,6 +303,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_typeahead: + dependency: "direct main" + description: + name: flutter_typeahead + url: "https://pub.dartlang.org" + source: hosted + version: "3.2.1" flutter_web_plugins: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 6fa7a61..d58cd84 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,6 +62,7 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + flutter_typeahead: ^3.2.1 dev_dependencies: flutter_test: From a8d91f7ba8fbdf2b467bc26e359c7e804f55d244 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 5 Nov 2021 23:54:00 +0100 Subject: [PATCH 2/6] Update lib/pages/settings/blocks/blocks_store.dart Co-authored-by: Marcin Wojnarowski --- lib/pages/settings/blocks/blocks_store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/settings/blocks/blocks_store.dart b/lib/pages/settings/blocks/blocks_store.dart index 7b50c18..219eae5 100644 --- a/lib/pages/settings/blocks/blocks_store.dart +++ b/lib/pages/settings/blocks/blocks_store.dart @@ -52,7 +52,7 @@ abstract class _BlocksStore with Store { ); if (res != null && - _blockedUsers!.indexWhere((element) => element.person.id == id) == -1) { + !_blockedUsers!.any((element) => element.person.id == id)) { _blockedUsers!.add( UserBlockStore( instanceHost: instanceHost, From 43bf9449246715f2b88acd1a99530244ba2f28c5 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Fri, 5 Nov 2021 23:54:10 +0100 Subject: [PATCH 3/6] Update lib/pages/settings/blocks/blocks_store.dart Co-authored-by: Marcin Wojnarowski --- lib/pages/settings/blocks/blocks_store.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/pages/settings/blocks/blocks_store.dart b/lib/pages/settings/blocks/blocks_store.dart index 219eae5..b13ecbb 100644 --- a/lib/pages/settings/blocks/blocks_store.dart +++ b/lib/pages/settings/blocks/blocks_store.dart @@ -78,9 +78,7 @@ abstract class _BlocksStore with Store { ); if (res != null && - _blockedCommunities! - .indexWhere((element) => element.community.id == id) == - -1) { + !_blockedCommunities!.any((element) => element.community.id == id)) { _blockedCommunities!.add( CommunityBlockStore( instanceHost: instanceHost, From 716d757d62c0610e31ff5699c04ccb55d0a12e91 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Sat, 6 Nov 2021 17:15:07 +0100 Subject: [PATCH 4/6] changes addressing CR * remove outdated TODOs * remove unneeded Expanded and Rows in 2 places --- lib/pages/settings/blocks/block_dialog.dart | 12 ++---------- lib/pages/settings/blocks/blocks.dart | 2 -- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/lib/pages/settings/blocks/block_dialog.dart b/lib/pages/settings/blocks/block_dialog.dart index 5bcf40d..98a6f39 100644 --- a/lib/pages/settings/blocks/block_dialog.dart +++ b/lib/pages/settings/blocks/block_dialog.dart @@ -13,11 +13,7 @@ class BlockPersonDialog extends StatelessWidget { @override Widget build(BuildContext context) { return AlertDialog( - title: Row( - children: const [ - Expanded(child: Text('Block User')), - ], - ), + title: const Text('Block User'), content: TypeAheadField( suggestionsCallback: (pattern) async { if (pattern.trim().isEmpty) return const Iterable.empty(); @@ -76,11 +72,7 @@ class BlockCommunityDialog extends StatelessWidget { @override Widget build(BuildContext context) { return AlertDialog( - title: Row( - children: const [ - Expanded(child: Text('Block Community')), - ], - ), + title: const Text('Block Community'), content: TypeAheadField( suggestionsCallback: (pattern) async { if (pattern.trim().isEmpty) return const Iterable.empty(); diff --git a/lib/pages/settings/blocks/blocks.dart b/lib/pages/settings/blocks/blocks.dart index a5d7630..3678c2e 100644 --- a/lib/pages/settings/blocks/blocks.dart +++ b/lib/pages/settings/blocks/blocks.dart @@ -129,7 +129,6 @@ class _UserBlocks extends HookWidget { child: Text('No users blocked'), ), ), - // TODO: add user search & block ListTile( leading: Padding( padding: const EdgeInsets.only(left: 16, right: 10), @@ -164,7 +163,6 @@ class _UserBlocks extends HookWidget { child: Text('No communities blocked'), ), ), - // TODO: add community search & block ListTile( leading: Padding( padding: const EdgeInsets.only(left: 16, right: 10), From f51432d76e96be40577689d8544e5df8d6e7a6f8 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Sat, 6 Nov 2021 17:19:39 +0100 Subject: [PATCH 5/6] formatting --- lib/pages/settings/blocks/blocks_store.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pages/settings/blocks/blocks_store.dart b/lib/pages/settings/blocks/blocks_store.dart index b13ecbb..92788d1 100644 --- a/lib/pages/settings/blocks/blocks_store.dart +++ b/lib/pages/settings/blocks/blocks_store.dart @@ -78,7 +78,7 @@ abstract class _BlocksStore with Store { ); if (res != null && - !_blockedCommunities!.any((element) => element.community.id == id)) { + !_blockedCommunities!.any((element) => element.community.id == id)) { _blockedCommunities!.add( CommunityBlockStore( instanceHost: instanceHost, From 9d004cefd7b35292e90b78e94f3e594f4c97e3a1 Mon Sep 17 00:00:00 2001 From: Filip Krawczyk Date: Sat, 6 Nov 2021 18:49:45 +0100 Subject: [PATCH 6/6] Provider: nah, pass as arg: yah --- lib/pages/settings/blocks/block_dialog.dart | 26 ++++++++++----------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/pages/settings/blocks/block_dialog.dart b/lib/pages/settings/blocks/block_dialog.dart index 98a6f39..1775af7 100644 --- a/lib/pages/settings/blocks/block_dialog.dart +++ b/lib/pages/settings/blocks/block_dialog.dart @@ -8,7 +8,9 @@ import '../../../widgets/avatar.dart'; import 'blocks_store.dart'; class BlockPersonDialog extends StatelessWidget { - const BlockPersonDialog(); + final BlocksStore store; + + const BlockPersonDialog(this.store); @override Widget build(BuildContext context) { @@ -17,10 +19,10 @@ class BlockPersonDialog extends StatelessWidget { content: TypeAheadField( suggestionsCallback: (pattern) async { if (pattern.trim().isEmpty) return const Iterable.empty(); - return LemmyApiV3(context.read().instanceHost) + return LemmyApiV3(store.instanceHost) .run(Search( q: pattern, - auth: context.read().token.raw, + auth: store.token.raw, type: SearchType.users, limit: 10, )) @@ -58,16 +60,15 @@ class BlockPersonDialog extends StatelessWidget { final store = context.read(); return showDialog( context: context, - builder: (context) => Provider.value( - value: store, - child: const BlockPersonDialog(), - ), + builder: (context) => BlockPersonDialog(store), ); } } class BlockCommunityDialog extends StatelessWidget { - const BlockCommunityDialog(); + final BlocksStore store; + + const BlockCommunityDialog(this.store); @override Widget build(BuildContext context) { @@ -76,10 +77,10 @@ class BlockCommunityDialog extends StatelessWidget { content: TypeAheadField( suggestionsCallback: (pattern) async { if (pattern.trim().isEmpty) return const Iterable.empty(); - return LemmyApiV3(context.read().instanceHost) + return LemmyApiV3(store.instanceHost) .run(Search( q: pattern, - auth: context.read().token.raw, + auth: store.token.raw, type: SearchType.communities, limit: 10, )) @@ -117,10 +118,7 @@ class BlockCommunityDialog extends StatelessWidget { final store = context.read(); return showDialog( context: context, - builder: (context) => Provider.value( - value: store, - child: const BlockCommunityDialog(), - ), + builder: (context) => BlockCommunityDialog(store), ); } }