add search & block
This commit is contained in:
parent
03b6c1b129
commit
0adc26a5fb
|
@ -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
|
||||
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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'),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
28
pubspec.lock
28
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
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue