add search & block

This commit is contained in:
Filip Krawczyk 2021-11-05 16:00:03 +01:00
parent 03b6c1b129
commit 0adc26a5fb
7 changed files with 320 additions and 31 deletions

View File

@ -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

View File

@ -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<PersonViewSafe>(
suggestionsCallback: (pattern) async {
if (pattern.trim().isEmpty) return const Iterable.empty();
return LemmyApiV3(context.read<BlocksStore>().instanceHost)
.run(Search(
q: pattern,
auth: context.read<BlocksStore>().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<PersonViewSafe?> show(BuildContext context) async {
final store = context.read<BlocksStore>();
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<CommunityView>(
suggestionsCallback: (pattern) async {
if (pattern.trim().isEmpty) return const Iterable.empty();
return LemmyApiV3(context.read<BlocksStore>().instanceHost)
.run(Search(
q: pattern,
auth: context.read<BlocksStore>().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<CommunityView?> show(BuildContext context) async {
final store = context.read<BlocksStore>();
return showDialog(
context: context,
builder: (context) => Provider.value(
value: store,
child: const BlockCommunityDialog(),
),
);
}
}

View File

@ -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<BlocksStore>().blocksState,
final loggedInAction =
useLoggedInAction(context.read<BlocksStore>().instanceHost);
return Nested(
children: [
AsyncStoreListener(
asyncStore: context.read<BlocksStore>().blocksState,
),
AsyncStoreListener(
asyncStore: context.read<BlocksStore>().communityBlockingState,
),
AsyncStoreListener(
asyncStore: context.read<BlocksStore>().userBlockingState,
),
],
child: ObserverBuilder<BlocksStore>(
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'),
),
],
],
),

View File

@ -16,13 +16,16 @@ abstract class _BlocksStore with Store {
_BlocksStore({required this.instanceHost, required this.token});
@observable
List<UserBlockStore>? _blockedUsers;
ObservableList<UserBlockStore>? _blockedUsers;
@observable
List<CommunityBlockStore>? _blockedCommunities;
ObservableList<CommunityBlockStore>? _blockedCommunities;
final blocksState = AsyncStore<FullSiteView>();
final userBlockingState = AsyncStore<BlockedPerson>();
final communityBlockingState = AsyncStore<BlockedCommunity>();
@computed
Iterable<UserBlockStore>? 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<void> 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<void> 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<void> 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();
}
}
}

View File

@ -35,13 +35,13 @@ mixin _$BlocksStore on _BlocksStore, Store {
final _$_blockedUsersAtom = Atom(name: '_BlocksStore._blockedUsers');
@override
List<UserBlockStore>? get _blockedUsers {
ObservableList<UserBlockStore>? get _blockedUsers {
_$_blockedUsersAtom.reportRead();
return super._blockedUsers;
}
@override
set _blockedUsers(List<UserBlockStore>? value) {
set _blockedUsers(ObservableList<UserBlockStore>? value) {
_$_blockedUsersAtom.reportWrite(value, super._blockedUsers, () {
super._blockedUsers = value;
});
@ -51,18 +51,34 @@ mixin _$BlocksStore on _BlocksStore, Store {
Atom(name: '_BlocksStore._blockedCommunities');
@override
List<CommunityBlockStore>? get _blockedCommunities {
ObservableList<CommunityBlockStore>? get _blockedCommunities {
_$_blockedCommunitiesAtom.reportRead();
return super._blockedCommunities;
}
@override
set _blockedCommunities(List<CommunityBlockStore>? value) {
set _blockedCommunities(ObservableList<CommunityBlockStore>? value) {
_$_blockedCommunitiesAtom.reportWrite(value, super._blockedCommunities, () {
super._blockedCommunities = value;
});
}
final _$blockUserAsyncAction = AsyncAction('_BlocksStore.blockUser');
@override
Future<void> blockUser(Jwt token, int id) {
return _$blockUserAsyncAction.run(() => super.blockUser(token, id));
}
final _$blockCommunityAsyncAction =
AsyncAction('_BlocksStore.blockCommunity');
@override
Future<void> blockCommunity(Jwt token, int id) {
return _$blockCommunityAsyncAction
.run(() => super.blockCommunity(token, id));
}
final _$refreshAsyncAction = AsyncAction('_BlocksStore.refresh');
@override

View File

@ -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

View File

@ -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: