From 6739eac19dd2cdbee32baa93e13e6db1b0398aec Mon Sep 17 00:00:00 2001
From: ice-kreios <180917405+ice-kreios@users.noreply.github.com>
Date: Thu, 9 Jan 2025 16:25:55 +0200
Subject: [PATCH] feat: shared user picker modal (#517)
## Description
This PR introduces a new reusable UserPickerSheet component to
standardize user selection across the app.
## Additional Notes
N/A
## Type of Change
- [x] Bug fix
- [x] New feature
- [ ] Breaking change
- [x] Refactoring
- [ ] Documentation
- [ ] Chore
## Screenshots (if applicable)
---
.../following_user_list.dart | 47 --------
.../components/more_content_view.dart | 11 +-
.../add_admin_modal/add_admin_modal.dart | 102 +++++++-----------
.../pages/new_chat_modal/new_chat_modal.dart | 88 ++++++---------
.../pages/add_group_participants_modal.dart | 99 +++++++----------
.../share_profile_modal.dart | 28 +++++
.../contacts/pages/contacts_list_view.dart | 97 -----------------
.../components/follower_users.dart | 60 +++++++++++
.../components/following_users.dart | 46 ++++++++
.../components/no_user_view.dart | 43 ++++++++
.../components/searched_users.dart} | 29 +++--
.../components/selectable_user_list_item.dart | 54 ++++++++++
.../user_picker_sheet/user_picker_sheet.dart | 92 ++++++++++++++++
.../search_users_data_source_provider.c.dart} | 8 +-
.../components/send_coins_form.dart | 8 +-
.../pages/friends_modal/friends_modal.dart | 27 +++++
.../pages/send_nft_form/send_nft_form.dart | 8 +-
.../contacts/contacts_list_header.dart | 16 +--
lib/app/router/app_routes.c.dart | 3 +-
lib/app/router/chat_routes.dart | 10 +-
lib/app/router/wallet_routes.dart | 29 ++---
lib/l10n/app_en.arb | 5 +-
22 files changed, 513 insertions(+), 397 deletions(-)
delete mode 100644 lib/app/features/chat/components/following_user_list/following_user_list.dart
create mode 100644 lib/app/features/chat/views/pages/share_profile_modal/share_profile_modal.dart
delete mode 100644 lib/app/features/contacts/pages/contacts_list_view.dart
create mode 100644 lib/app/features/user/pages/user_picker_sheet/components/follower_users.dart
create mode 100644 lib/app/features/user/pages/user_picker_sheet/components/following_users.dart
create mode 100644 lib/app/features/user/pages/user_picker_sheet/components/no_user_view.dart
rename lib/app/features/{chat/components/searched_user_list/searched_user_list.dart => user/pages/user_picker_sheet/components/searched_users.dart} (64%)
create mode 100644 lib/app/features/user/pages/user_picker_sheet/components/selectable_user_list_item.dart
create mode 100644 lib/app/features/user/pages/user_picker_sheet/user_picker_sheet.dart
rename lib/app/features/{chat/providers/users_data_source_provider.c.dart => user/providers/search_users_data_source_provider.c.dart} (80%)
create mode 100644 lib/app/features/wallet/views/pages/friends_modal/friends_modal.dart
diff --git a/lib/app/features/chat/components/following_user_list/following_user_list.dart b/lib/app/features/chat/components/following_user_list/following_user_list.dart
deleted file mode 100644
index 2be1228b6..000000000
--- a/lib/app/features/chat/components/following_user_list/following_user_list.dart
+++ /dev/null
@@ -1,47 +0,0 @@
-// SPDX-License-Identifier: ice License 1.0
-
-import 'package:flutter/material.dart';
-import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:ion/app/components/list_item/list_item.dart';
-import 'package:ion/app/features/chat/views/pages/new_chat_modal/components/new_chat_initial_view/new_chat_initial_view.dart';
-import 'package:ion/app/features/user/providers/follow_list_provider.c.dart';
-import 'package:ion/app/features/user/providers/user_metadata_provider.c.dart';
-import 'package:ion/app/utils/username.dart';
-
-class FollowingUsersList extends ConsumerWidget {
- const FollowingUsersList({super.key});
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final following = ref.watch(currentUserFollowListProvider);
- final followers = following.valueOrNull?.data.list;
- final pubkeys = followers?.map((e) => e.pubkey).toList() ?? [];
-
- if (pubkeys.isEmpty) {
- return const NewChatInitialView();
- }
-
- return ListView.builder(
- itemBuilder: (context, index) {
- return _FollowingUserListItem(pubkey: pubkeys[index]);
- },
- itemCount: pubkeys.length,
- );
- }
-}
-
-class _FollowingUserListItem extends ConsumerWidget {
- const _FollowingUserListItem({required this.pubkey});
-
- final String pubkey;
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final user = ref.watch(userMetadataProvider(pubkey)).valueOrNull;
- return ListItem.user(
- title: Text(user?.data.displayName ?? ''),
- subtitle: Text(prefixUsername(username: user?.data.name ?? '', context: context)),
- profilePicture: user?.data.picture,
- );
- }
-}
diff --git a/lib/app/features/chat/messages/views/components/messaging_bottom_bar/components/more_content_view.dart b/lib/app/features/chat/messages/views/components/messaging_bottom_bar/components/more_content_view.dart
index 52b2fb0d8..a58ab43f1 100644
--- a/lib/app/features/chat/messages/views/components/messaging_bottom_bar/components/more_content_view.dart
+++ b/lib/app/features/chat/messages/views/components/messaging_bottom_bar/components/more_content_view.dart
@@ -5,7 +5,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/chat/providers/messaging_bottom_bar_state_provider.c.dart';
import 'package:ion/app/router/app_routes.c.dart';
-import 'package:ion/app/services/logger/logger.dart';
import 'package:ion/generated/assets.gen.dart';
final double moreContentHeight = 206.0.s;
@@ -48,14 +47,8 @@ class MoreContentView extends ConsumerWidget {
_MoreContentItem(
iconPath: Assets.svg.walletChatPerson,
title: context.i18n.common_profile,
- onTap: () async {
- final contactId = await ShareProfileModalRoute(
- title: context.i18n.chat_profile_share_modal_title,
- ).push(context);
-
- //TODO: use contactId to share profile
- Logger.log(contactId ?? 'No contact selected');
-
+ onTap: () {
+ ShareProfileModalRoute().push(context);
ref.read(messagingBottomBarActiveStateProvider.notifier).setText();
},
),
diff --git a/lib/app/features/chat/views/pages/new_channel_modal/pages/add_admin_modal/add_admin_modal.dart b/lib/app/features/chat/views/pages/new_channel_modal/pages/add_admin_modal/add_admin_modal.dart
index cf7654e5e..86c2e6c53 100644
--- a/lib/app/features/chat/views/pages/new_channel_modal/pages/add_admin_modal/add_admin_modal.dart
+++ b/lib/app/features/chat/views/pages/new_channel_modal/pages/add_admin_modal/add_admin_modal.dart
@@ -7,12 +7,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/components/button/button.dart';
import 'package:ion/app/components/separated/separator.dart';
import 'package:ion/app/extensions/extensions.dart';
-import 'package:ion/app/features/auth/providers/content_creators_data_source_provider.c.dart';
import 'package:ion/app/features/chat/model/channel_admin_type.dart';
import 'package:ion/app/features/chat/providers/channel_admins_provider.c.dart';
-import 'package:ion/app/features/chat/views/components/selectable_user_list.dart';
-import 'package:ion/app/features/nostr/providers/entities_paged_data_provider.c.dart';
import 'package:ion/app/features/user/model/user_metadata.c.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/user_picker_sheet.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_close_button.dart';
import 'package:ion/generated/assets.gen.dart';
class AddAdminModal extends HookConsumerWidget {
@@ -22,73 +22,49 @@ class AddAdminModal extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
- final searchValue = useState('');
final selectedPubkey = useState(null);
- final dataSource = ref.watch(contentCreatorsDataSourceProvider);
- final entitiesPagedData = ref.watch(entitiesPagedDataProvider(dataSource));
- final contentCreators = entitiesPagedData?.data.items;
-
- final isLoading = contentCreators?.isEmpty ?? true;
- final userEntries = useMemoized(
- () => (contentCreators
- ?.whereType()
- .where(
- (entity) =>
- entity.data.displayName
- .toLowerCase()
- .contains(searchValue.value.toLowerCase()) ||
- entity.data.name.toLowerCase().contains(searchValue.value.toLowerCase()),
- )
- .toList() ??
- [])
- ..sort(
- (a, b) => a.data.displayName.toLowerCase().compareTo(b.data.displayName.toLowerCase()),
- ),
- [contentCreators, searchValue.value],
- );
-
return SizedBox(
height: MediaQuery.sizeOf(context).height * 0.8,
- child: Column(
- children: [
- Expanded(
- child: SelectableUserList(
- title: context.i18n.channel_create_admins_action,
- isLoading: isLoading,
- selected: [
- if (selectedPubkey.value != null) selectedPubkey.value!,
- ],
- userEntries: userEntries,
- onSelect: (String value) => selectedPubkey.value = value,
- onSearchValueChanged: (String value) => searchValue.value = value,
- ),
- ),
- const HorizontalSeparator(),
- Padding(
- padding: EdgeInsets.symmetric(
- vertical: 16.0.s,
- horizontal: 44.0.s,
- ),
- child: Button(
- type: selectedPubkey.value == null ? ButtonType.disabled : ButtonType.primary,
- mainAxisSize: MainAxisSize.max,
- minimumSize: Size(56.0.s, 56.0.s),
- leadingIcon: Assets.svg.iconProfileSave.icon(
- color: context.theme.appColors.onPrimaryAccent,
+ child: UserPickerSheet(
+ onUserSelected: (UserMetadataEntity user) => selectedPubkey.value = user.masterPubkey,
+ selectedPubkeys: selectedPubkey.value != null ? [selectedPubkey.value!] : null,
+ selectable: true,
+ navigationBar: NavigationAppBar.modal(
+ title: Text(context.i18n.channel_create_admins_action),
+ showBackButton: false,
+ actions: const [
+ NavigationCloseButton(),
+ ],
+ ),
+ bottomContent: Column(
+ children: [
+ const HorizontalSeparator(),
+ Padding(
+ padding: EdgeInsets.symmetric(
+ vertical: 16.0.s,
+ horizontal: 44.0.s,
),
- label: Text(
- context.i18n.button_confirm,
+ child: Button(
+ type: selectedPubkey.value == null ? ButtonType.disabled : ButtonType.primary,
+ mainAxisSize: MainAxisSize.max,
+ minimumSize: Size(56.0.s, 56.0.s),
+ leadingIcon: Assets.svg.iconProfileSave.icon(
+ color: context.theme.appColors.onPrimaryAccent,
+ ),
+ label: Text(
+ context.i18n.button_confirm,
+ ),
+ onPressed: () {
+ ref
+ .read(channelAdminsProvider().notifier)
+ .setAdmin(selectedPubkey.value!, ChannelAdminType.admin);
+ context.pop();
+ },
),
- onPressed: () {
- ref
- .read(channelAdminsProvider().notifier)
- .setAdmin(selectedPubkey.value!, ChannelAdminType.admin);
- context.pop();
- },
),
- ),
- ],
+ ],
+ ),
),
);
}
diff --git a/lib/app/features/chat/views/pages/new_chat_modal/new_chat_modal.dart b/lib/app/features/chat/views/pages/new_chat_modal/new_chat_modal.dart
index 16563d497..20befd4b8 100644
--- a/lib/app/features/chat/views/pages/new_chat_modal/new_chat_modal.dart
+++ b/lib/app/features/chat/views/pages/new_chat_modal/new_chat_modal.dart
@@ -1,78 +1,50 @@
// SPDX-License-Identifier: ice License 1.0
import 'package:flutter/material.dart';
-import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:go_router/go_router.dart';
import 'package:ion/app/components/button/button.dart';
-import 'package:ion/app/components/inputs/search_input/search_input.dart';
-import 'package:ion/app/components/screen_offset/screen_side_offset.dart';
import 'package:ion/app/extensions/extensions.dart';
-import 'package:ion/app/features/chat/components/following_user_list/following_user_list.dart';
-import 'package:ion/app/features/chat/components/searched_user_list/searched_user_list.dart';
-import 'package:ion/app/features/chat/providers/users_data_source_provider.c.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/user_picker_sheet.dart';
import 'package:ion/app/router/app_routes.c.dart';
import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart';
import 'package:ion/app/router/components/navigation_app_bar/navigation_close_button.dart';
import 'package:ion/app/router/components/sheet_content/sheet_content.dart';
import 'package:ion/generated/assets.gen.dart';
-class NewChatModal extends HookConsumerWidget {
+class NewChatModal extends StatelessWidget {
const NewChatModal({super.key});
@override
- Widget build(BuildContext context, WidgetRef ref) {
- final searchText = ref.watch(usersSearchTextProvider);
-
+ Widget build(BuildContext context) {
return SheetContent(
topPadding: 0,
- body: Column(
- children: [
- NavigationAppBar.modal(
- showBackButton: false,
- title: Text(context.i18n.new_chat_modal_title),
- actions: const [NavigationCloseButton()],
- ),
- SizedBox(height: 9.0.s),
- Expanded(
- child: ScreenSideOffset.small(
- child: Column(
- children: [
- SearchInput(
- textInputAction: TextInputAction.search,
- onTextChanged: (text) {
- ref.read(usersSearchTextProvider.notifier).text = text;
- },
- ),
- SizedBox(height: 12.0.s),
- Row(
- children: [
- _HeaderButton(
- icon: Assets.svg.iconSearchGroups,
- title: context.i18n.new_chat_modal_new_group_button,
- onTap: () {
- AddParticipantsToGroupModalRoute().push(context);
- },
- ),
- SizedBox(width: 20.0.s),
- _HeaderButton(
- icon: Assets.svg.iconSearchChannel,
- title: context.i18n.new_chat_modal_new_channel_button,
- onTap: () {
- NewChannelModalRoute().replace(context);
- },
- ),
- ],
- ),
- SizedBox(height: 12.0.s),
- //TODO: update user list when figma design is ready
- Expanded(
- child:
- searchText.isEmpty ? const FollowingUsersList() : const SearchedUsersList(),
- ),
- ],
- ),
+ body: UserPickerSheet(
+ navigationBar: NavigationAppBar.modal(
+ showBackButton: false,
+ title: Text(context.i18n.new_chat_modal_title),
+ actions: const [NavigationCloseButton()],
+ ),
+ initialUserListType: UserListType.follower,
+ onUserSelected: (_) => context.pop(),
+ header: Row(
+ children: [
+ _HeaderButton(
+ icon: Assets.svg.iconSearchGroups,
+ title: context.i18n.new_chat_modal_new_group_button,
+ onTap: () {
+ AddParticipantsToGroupModalRoute().push(context);
+ },
),
- ),
- ],
+ SizedBox(width: 20.0.s),
+ _HeaderButton(
+ icon: Assets.svg.iconSearchChannel,
+ title: context.i18n.new_chat_modal_new_channel_button,
+ onTap: () {
+ NewChannelModalRoute().replace(context);
+ },
+ ),
+ ],
+ ),
),
);
}
diff --git a/lib/app/features/chat/views/pages/new_group_modal/pages/add_group_participants_modal.dart b/lib/app/features/chat/views/pages/new_group_modal/pages/add_group_participants_modal.dart
index e6dbffbd1..37f926651 100644
--- a/lib/app/features/chat/views/pages/new_group_modal/pages/add_group_participants_modal.dart
+++ b/lib/app/features/chat/views/pages/new_group_modal/pages/add_group_participants_modal.dart
@@ -1,19 +1,17 @@
// SPDX-License-Identifier: ice License 1.0
import 'package:flutter/material.dart';
-import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/components/button/button.dart';
import 'package:ion/app/components/screen_offset/screen_bottom_offset.dart';
import 'package:ion/app/components/screen_offset/screen_side_offset.dart';
import 'package:ion/app/components/separated/separator.dart';
import 'package:ion/app/extensions/extensions.dart';
-import 'package:ion/app/features/auth/providers/content_creators_data_source_provider.c.dart';
import 'package:ion/app/features/chat/providers/create_group_form_controller_provider.c.dart';
-import 'package:ion/app/features/chat/views/components/selectable_user_list.dart';
-import 'package:ion/app/features/nostr/providers/entities_paged_data_provider.c.dart';
-import 'package:ion/app/features/user/model/user_metadata.c.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/user_picker_sheet.dart';
import 'package:ion/app/router/app_routes.c.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_close_button.dart';
import 'package:ion/app/router/components/sheet_content/sheet_content.dart';
import 'package:ion/generated/assets.gen.dart';
@@ -23,69 +21,48 @@ class AddGroupParticipantsModal extends HookConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final createGroupForm = ref.watch(createGroupFormControllerProvider);
- final createGroupFormNotifier = ref.read(createGroupFormControllerProvider.notifier);
-
- final searchValue = useState('');
-
- final dataSource = ref.watch(contentCreatorsDataSourceProvider);
- final entitiesPagedData = ref.watch(entitiesPagedDataProvider(dataSource));
- final contentCreators = entitiesPagedData?.data.items;
-
- final isLoading = contentCreators?.isEmpty ?? true;
-
- // TODO: Replace stub with implemented search
- final userEntries = useMemoized(
- () => (contentCreators
- ?.whereType()
- .where(
- (entity) =>
- entity.data.displayName
- .toLowerCase()
- .contains(searchValue.value.toLowerCase()) ||
- entity.data.name.toLowerCase().contains(searchValue.value.toLowerCase()),
- )
- .toList() ??
- [])
- ..sort(
- (a, b) => a.data.displayName.toLowerCase().compareTo(b.data.displayName.toLowerCase()),
- ),
- [contentCreators, searchValue.value],
- );
+ final createGroupFormNotifier = ref.watch(createGroupFormControllerProvider.notifier);
return SheetContent(
topPadding: 0,
- body: Column(
- children: [
- Expanded(
- child: SelectableUserList(
- title: context.i18n.group_create_title,
- isLoading: isLoading,
- userEntries: userEntries,
- selected: createGroupForm.members.toList(),
- onSelect: createGroupFormNotifier.toggleMember,
- onSearchValueChanged: (String value) => searchValue.value = value,
- ),
- ),
- const HorizontalSeparator(),
- ScreenBottomOffset(
- margin: 32.0.s,
- child: Padding(
- padding: EdgeInsets.only(top: 16.0.s),
- child: ScreenSideOffset.large(
- child: Button(
- onPressed: () {
- CreateGroupModalRoute().push(context);
- },
- label: Text(context.i18n.button_next),
- mainAxisSize: MainAxisSize.max,
- trailingIcon: Assets.svg.iconButtonNext.icon(
- color: context.theme.appColors.onPrimaryAccent,
+ body: UserPickerSheet(
+ key: const Key('add-group-participants-modal'),
+ selectedPubkeys: createGroupForm.members.toList(),
+ selectable: true,
+ initialUserListType: UserListType.follower,
+ onUserSelected: (user) {
+ createGroupFormNotifier.toggleMember(user.masterPubkey);
+ },
+ navigationBar: NavigationAppBar.modal(
+ title: Text(context.i18n.group_create_title),
+ showBackButton: false,
+ actions: const [
+ NavigationCloseButton(),
+ ],
+ ),
+ bottomContent: Column(
+ children: [
+ const HorizontalSeparator(),
+ ScreenBottomOffset(
+ margin: 32.0.s,
+ child: Padding(
+ padding: EdgeInsets.only(top: 16.0.s),
+ child: ScreenSideOffset.large(
+ child: Button(
+ onPressed: () {
+ CreateGroupModalRoute().push(context);
+ },
+ label: Text(context.i18n.button_next),
+ mainAxisSize: MainAxisSize.max,
+ trailingIcon: Assets.svg.iconButtonNext.icon(
+ color: context.theme.appColors.onPrimaryAccent,
+ ),
),
),
),
),
- ),
- ],
+ ],
+ ),
),
);
}
diff --git a/lib/app/features/chat/views/pages/share_profile_modal/share_profile_modal.dart b/lib/app/features/chat/views/pages/share_profile_modal/share_profile_modal.dart
new file mode 100644
index 000000000..1312ba145
--- /dev/null
+++ b/lib/app/features/chat/views/pages/share_profile_modal/share_profile_modal.dart
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: ice License 1.0
+
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:ion/app/extensions/extensions.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/user_picker_sheet.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_close_button.dart';
+import 'package:ion/app/router/components/sheet_content/sheet_content.dart';
+
+class ShareProfileModal extends StatelessWidget {
+ const ShareProfileModal({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return SheetContent(
+ topPadding: 0,
+ body: UserPickerSheet(
+ navigationBar: NavigationAppBar.modal(
+ showBackButton: false,
+ title: Text(context.i18n.chat_profile_share_modal_title),
+ actions: [NavigationCloseButton(onPressed: () => context.pop())],
+ ),
+ onUserSelected: (_) => context.pop(),
+ ),
+ );
+ }
+}
diff --git a/lib/app/features/contacts/pages/contacts_list_view.dart b/lib/app/features/contacts/pages/contacts_list_view.dart
deleted file mode 100644
index 99b019974..000000000
--- a/lib/app/features/contacts/pages/contacts_list_view.dart
+++ /dev/null
@@ -1,97 +0,0 @@
-// SPDX-License-Identifier: ice License 1.0
-
-import 'package:flutter/material.dart';
-import 'package:go_router/go_router.dart';
-import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:ion/app/components/inputs/search_input/search_input.dart';
-import 'package:ion/app/components/list_item/list_item.dart';
-import 'package:ion/app/components/screen_offset/screen_side_offset.dart';
-import 'package:ion/app/extensions/extensions.dart';
-import 'package:ion/app/features/contacts/providers/contacts_provider.c.dart';
-import 'package:ion/app/router/app_routes.c.dart';
-import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart';
-import 'package:ion/app/router/components/navigation_app_bar/navigation_close_button.dart';
-import 'package:ion/app/router/components/sheet_content/sheet_content.dart';
-
-enum ContactRouteAction {
- navigate,
- pop,
-}
-
-class ContactsListView extends ConsumerWidget {
- const ContactsListView({
- required this.appBarTitle,
- required this.action,
- this.showBackButton = false,
- super.key,
- });
-
- final String appBarTitle;
- final ContactRouteAction action;
- final bool showBackButton;
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final contacts = ref.watch(contactsProvider);
-
- return SheetContent(
- body: CustomScrollView(
- slivers: [
- SliverAppBar(
- primary: false,
- flexibleSpace: NavigationAppBar.modal(
- showBackButton: showBackButton,
- actions: [
- NavigationCloseButton(
- onPressed: () => context.pop(),
- ),
- ],
- title: Text(appBarTitle),
- ),
- automaticallyImplyLeading: false,
- toolbarHeight: NavigationAppBar.modalHeaderHeight,
- pinned: true,
- ),
- PinnedHeaderSliver(
- child: ColoredBox(
- color: context.theme.appColors.onPrimaryAccent,
- child: Column(
- children: [
- SizedBox(height: 16.0.s),
- ScreenSideOffset.small(
- child: SearchInput(
- onTextChanged: (String value) {},
- ),
- ),
- SizedBox(height: 16.0.s),
- ],
- ),
- ),
- ),
- SliverList(
- delegate: SliverChildBuilderDelegate(
- (BuildContext context, int index) {
- final contact = contacts[index];
- return ScreenSideOffset.small(
- child: Padding(
- padding: EdgeInsets.only(bottom: 12.0.s),
- child: ListItem.user(
- title: Text(contact.name),
- subtitle: Text(contact.nickname!),
- profilePicture: contact.icon,
- timeago: contact.lastSeen,
- onTap: () => action == ContactRouteAction.pop
- ? context.pop(contact.id)
- : ContactRoute(contactId: contact.id).push(context),
- ),
- ),
- );
- },
- childCount: contacts.length,
- ),
- ),
- ],
- ),
- );
- }
-}
diff --git a/lib/app/features/user/pages/user_picker_sheet/components/follower_users.dart b/lib/app/features/user/pages/user_picker_sheet/components/follower_users.dart
new file mode 100644
index 000000000..e2d75790c
--- /dev/null
+++ b/lib/app/features/user/pages/user_picker_sheet/components/follower_users.dart
@@ -0,0 +1,60 @@
+// SPDX-License-Identifier: ice License 1.0
+
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:ion/app/components/scroll_view/load_more_builder.dart';
+import 'package:ion/app/extensions/extensions.dart';
+import 'package:ion/app/features/auth/providers/auth_provider.c.dart';
+import 'package:ion/app/features/nostr/providers/entities_paged_data_provider.c.dart';
+import 'package:ion/app/features/user/model/user_metadata.c.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/components/selectable_user_list_item.dart';
+import 'package:ion/app/features/user/providers/followers_data_source_provider.c.dart';
+
+class FollowerUsers extends ConsumerWidget {
+ const FollowerUsers({
+ required this.onUserSelected,
+ this.selectable = false,
+ this.selectedPubkeys,
+ super.key,
+ });
+
+ final void Function(UserMetadataEntity user) onUserSelected;
+ final bool selectable;
+ final List? selectedPubkeys;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final pubkey = ref.watch(currentPubkeySelectorProvider).valueOrNull;
+ final dataSource = ref.watch(followersDataSourceProvider(pubkey!));
+ final entitiesPagedData = ref.watch(entitiesPagedDataProvider(dataSource));
+ final users = entitiesPagedData?.data.items?.whereType().toList();
+ final slivers = [
+ if (users == null || users.isEmpty)
+ const SliverToBoxAdapter(child: SizedBox.shrink())
+ else
+ SliverList.separated(
+ separatorBuilder: (BuildContext _, int __) => SizedBox(height: 8.0.s),
+ itemCount: users.length,
+ itemBuilder: (BuildContext context, int index) {
+ final user = users.elementAt(index);
+ return SelectableUserListItem(
+ pubkey: user.masterPubkey,
+ onUserSelected: onUserSelected,
+ selectedPubkeys: selectedPubkeys,
+ selectable: selectable,
+ );
+ },
+ ),
+ ];
+
+ return LoadMoreBuilder(
+ slivers: slivers,
+ builder: (context, slivers) => CustomScrollView(
+ key: const ValueKey('paged_followers_scroll_view'),
+ slivers: slivers,
+ ),
+ onLoadMore: ref.read(entitiesPagedDataProvider(dataSource).notifier).fetchEntities,
+ hasMore: entitiesPagedData?.hasMore ?? true,
+ );
+ }
+}
diff --git a/lib/app/features/user/pages/user_picker_sheet/components/following_users.dart b/lib/app/features/user/pages/user_picker_sheet/components/following_users.dart
new file mode 100644
index 000000000..9a68064db
--- /dev/null
+++ b/lib/app/features/user/pages/user_picker_sheet/components/following_users.dart
@@ -0,0 +1,46 @@
+// SPDX-License-Identifier: ice License 1.0
+
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:ion/app/extensions/extensions.dart';
+import 'package:ion/app/features/user/model/user_metadata.c.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/components/no_user_view.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/components/selectable_user_list_item.dart';
+import 'package:ion/app/features/user/providers/follow_list_provider.c.dart';
+
+class FollowingUsers extends ConsumerWidget {
+ const FollowingUsers({
+ required this.onUserSelected,
+ this.selectedPubkeys,
+ this.selectable = false,
+ super.key,
+ });
+
+ final void Function(UserMetadataEntity user) onUserSelected;
+ final List? selectedPubkeys;
+ final bool selectable;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final followList = ref.watch(currentUserFollowListProvider);
+
+ return followList.maybeWhen(
+ data: (data) {
+ final pubkeys = data?.data.list.map((e) => e.pubkey).toList() ?? [];
+ return ListView.separated(
+ itemBuilder: (context, index) {
+ return SelectableUserListItem(
+ pubkey: pubkeys[index],
+ onUserSelected: onUserSelected,
+ selectedPubkeys: selectedPubkeys,
+ selectable: selectable,
+ );
+ },
+ itemCount: pubkeys.length,
+ separatorBuilder: (context, index) => SizedBox(height: 14.0.s),
+ );
+ },
+ orElse: () => const NoUserView(),
+ );
+ }
+}
diff --git a/lib/app/features/user/pages/user_picker_sheet/components/no_user_view.dart b/lib/app/features/user/pages/user_picker_sheet/components/no_user_view.dart
new file mode 100644
index 000000000..96f78a5b6
--- /dev/null
+++ b/lib/app/features/user/pages/user_picker_sheet/components/no_user_view.dart
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: ice License 1.0
+
+import 'package:flutter/material.dart';
+import 'package:ion/app/components/screen_offset/screen_side_offset.dart';
+import 'package:ion/app/extensions/extensions.dart';
+import 'package:ion/generated/assets.gen.dart';
+
+class NoUserView extends StatelessWidget {
+ const NoUserView({
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Expanded(
+ child: ScreenSideOffset.small(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Spacer(),
+ Assets.svg.walletChatNewchat.icon(
+ size: 48.0.s,
+ ),
+ Padding(
+ padding: EdgeInsets.symmetric(vertical: 8.0.s, horizontal: 78.0.s),
+ child: Text(
+ context.i18n.new_chat_modal_description,
+ style: context.theme.appTextThemes.caption2.copyWith(
+ color: context.theme.appColors.onTertararyBackground,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ const Spacer(
+ flex: 2,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/app/features/chat/components/searched_user_list/searched_user_list.dart b/lib/app/features/user/pages/user_picker_sheet/components/searched_users.dart
similarity index 64%
rename from lib/app/features/chat/components/searched_user_list/searched_user_list.dart
rename to lib/app/features/user/pages/user_picker_sheet/components/searched_users.dart
index fcbcb0d2f..2788537fc 100644
--- a/lib/app/features/chat/components/searched_user_list/searched_user_list.dart
+++ b/lib/app/features/user/pages/user_picker_sheet/components/searched_users.dart
@@ -2,20 +2,28 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
-import 'package:ion/app/components/list_item/list_item.dart';
import 'package:ion/app/components/scroll_view/load_more_builder.dart';
import 'package:ion/app/extensions/extensions.dart';
-import 'package:ion/app/features/chat/providers/users_data_source_provider.c.dart';
import 'package:ion/app/features/nostr/providers/entities_paged_data_provider.c.dart';
import 'package:ion/app/features/user/model/user_metadata.c.dart';
-import 'package:ion/app/utils/username.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/components/selectable_user_list_item.dart';
+import 'package:ion/app/features/user/providers/search_users_data_source_provider.c.dart';
-class SearchedUsersList extends ConsumerWidget {
- const SearchedUsersList({super.key});
+class SearchedUsers extends ConsumerWidget {
+ const SearchedUsers({
+ required this.onUserSelected,
+ this.selectable = false,
+ this.selectedPubkeys,
+ super.key,
+ });
+
+ final void Function(UserMetadataEntity user) onUserSelected;
+ final bool selectable;
+ final List? selectedPubkeys;
@override
Widget build(BuildContext context, WidgetRef ref) {
- final dataSource = ref.watch(usersDataSourceProvider).valueOrNull;
+ final dataSource = ref.watch(searchUsersDataSourceProvider).valueOrNull;
final entitiesPagedData = ref.watch(entitiesPagedDataProvider(dataSource));
final users = entitiesPagedData?.data.items?.whereType().toList();
final slivers = [
@@ -27,10 +35,11 @@ class SearchedUsersList extends ConsumerWidget {
itemCount: users.length,
itemBuilder: (BuildContext context, int index) {
final user = users.elementAt(index);
- return ListItem.user(
- title: Text(user.data.displayName),
- subtitle: Text(prefixUsername(username: user.data.name, context: context)),
- profilePicture: user.data.picture,
+ return SelectableUserListItem(
+ pubkey: user.masterPubkey,
+ onUserSelected: onUserSelected,
+ selectedPubkeys: selectedPubkeys,
+ selectable: selectable,
);
},
),
diff --git a/lib/app/features/user/pages/user_picker_sheet/components/selectable_user_list_item.dart b/lib/app/features/user/pages/user_picker_sheet/components/selectable_user_list_item.dart
new file mode 100644
index 000000000..10a7d52ee
--- /dev/null
+++ b/lib/app/features/user/pages/user_picker_sheet/components/selectable_user_list_item.dart
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: ice License 1.0
+
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:ion/app/components/list_item/list_item.dart';
+import 'package:ion/app/extensions/extensions.dart';
+import 'package:ion/app/features/user/model/user_metadata.c.dart';
+import 'package:ion/app/features/user/providers/user_metadata_provider.c.dart';
+import 'package:ion/app/utils/username.dart';
+import 'package:ion/generated/assets.gen.dart';
+
+class SelectableUserListItem extends ConsumerWidget {
+ const SelectableUserListItem({
+ required this.pubkey,
+ required this.onUserSelected,
+ super.key,
+ this.selectedPubkeys,
+ this.selectable = false,
+ });
+
+ final String pubkey;
+ final void Function(UserMetadataEntity user) onUserSelected;
+ final List? selectedPubkeys;
+ final bool selectable;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final userMetadataResult = ref.watch(userMetadataProvider(pubkey));
+
+ return userMetadataResult.maybeWhen(
+ data: (user) {
+ if (user == null) return const SizedBox.shrink();
+
+ final isSelected = selectedPubkeys?.contains(pubkey) ?? false;
+ return ListItem.user(
+ onTap: () => onUserSelected(user),
+ title: Text(user.data.displayName),
+ subtitle: Text(prefixUsername(username: user.data.name, context: context)),
+ profilePicture: user.data.picture,
+ trailing: selectable
+ ? isSelected
+ ? Assets.svg.iconBlockCheckboxOnblue.icon(
+ color: context.theme.appColors.success,
+ )
+ : Assets.svg.iconBlockCheckboxOff.icon(
+ color: context.theme.appColors.tertararyText,
+ )
+ : null,
+ );
+ },
+ orElse: SizedBox.shrink,
+ );
+ }
+}
diff --git a/lib/app/features/user/pages/user_picker_sheet/user_picker_sheet.dart b/lib/app/features/user/pages/user_picker_sheet/user_picker_sheet.dart
new file mode 100644
index 000000000..460c40d1b
--- /dev/null
+++ b/lib/app/features/user/pages/user_picker_sheet/user_picker_sheet.dart
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: ice License 1.0
+
+import 'package:flutter/material.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:ion/app/components/inputs/search_input/search_input.dart';
+import 'package:ion/app/components/screen_offset/screen_side_offset.dart';
+import 'package:ion/app/extensions/extensions.dart';
+import 'package:ion/app/features/user/model/user_metadata.c.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/components/follower_users.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/components/following_users.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/components/searched_users.dart';
+import 'package:ion/app/features/user/providers/search_users_data_source_provider.c.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart';
+
+enum UserListType {
+ follower,
+ following,
+}
+
+class UserPickerSheet extends HookConsumerWidget {
+ const UserPickerSheet({
+ required this.navigationBar,
+ required this.onUserSelected,
+ this.header,
+ super.key,
+ this.selectedPubkeys,
+ this.selectable = false,
+ this.bottomContent,
+ this.initialUserListType = UserListType.following,
+ });
+
+ final NavigationAppBar navigationBar;
+ final Widget? header;
+ final List? selectedPubkeys;
+ final bool selectable;
+ final Widget? bottomContent;
+ final void Function(UserMetadataEntity user) onUserSelected;
+ final UserListType initialUserListType;
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final searchText = ref.watch(searchUsersQueryProvider);
+
+ return Column(
+ children: [
+ navigationBar,
+ Expanded(
+ child: ScreenSideOffset.small(
+ child: Column(
+ children: [
+ SearchInput(
+ textInputAction: TextInputAction.search,
+ onTextChanged: (text) {
+ ref.read(searchUsersQueryProvider.notifier).text = text;
+ },
+ ),
+ SizedBox(height: 12.0.s),
+ if (header != null)
+ Column(
+ children: [
+ header!,
+ SizedBox(height: 12.0.s),
+ ],
+ ),
+ Expanded(
+ child: searchText.isEmpty
+ ? initialUserListType == UserListType.follower
+ ? FollowerUsers(
+ onUserSelected: onUserSelected,
+ selectedPubkeys: selectedPubkeys,
+ selectable: selectable,
+ )
+ : FollowingUsers(
+ onUserSelected: onUserSelected,
+ selectedPubkeys: selectedPubkeys,
+ selectable: selectable,
+ )
+ : SearchedUsers(
+ onUserSelected: onUserSelected,
+ selectedPubkeys: selectedPubkeys,
+ selectable: selectable,
+ ),
+ ),
+ bottomContent ?? const SizedBox.shrink(),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/app/features/chat/providers/users_data_source_provider.c.dart b/lib/app/features/user/providers/search_users_data_source_provider.c.dart
similarity index 80%
rename from lib/app/features/chat/providers/users_data_source_provider.c.dart
rename to lib/app/features/user/providers/search_users_data_source_provider.c.dart
index e2d99862d..79e0ec868 100644
--- a/lib/app/features/chat/providers/users_data_source_provider.c.dart
+++ b/lib/app/features/user/providers/search_users_data_source_provider.c.dart
@@ -8,11 +8,11 @@ import 'package:ion/app/features/user/model/user_metadata.c.dart';
import 'package:nostr_dart/nostr_dart.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
-part 'users_data_source_provider.c.g.dart';
+part 'search_users_data_source_provider.c.g.dart';
@riverpod
-Future> usersDataSource(Ref ref) async {
- final searchText = ref.watch(usersSearchTextProvider);
+Future> searchUsersDataSource(Ref ref) async {
+ final searchText = ref.watch(searchUsersQueryProvider);
await ref.debounce();
return [
@@ -31,7 +31,7 @@ Future> usersDataSource(Ref ref) async {
}
@riverpod
-class UsersSearchText extends _$UsersSearchText {
+class SearchUsersQuery extends _$SearchUsersQuery {
@override
String build() {
return '';
diff --git a/lib/app/features/wallet/views/pages/coins_flow/send_coins/components/send_coins_form.dart b/lib/app/features/wallet/views/pages/coins_flow/send_coins/components/send_coins_form.dart
index eeb03cf80..cde0fbd5d 100644
--- a/lib/app/features/wallet/views/pages/coins_flow/send_coins/components/send_coins_form.dart
+++ b/lib/app/features/wallet/views/pages/coins_flow/send_coins/components/send_coins_form.dart
@@ -78,12 +78,10 @@ class SendCoinsForm extends HookConsumerWidget {
contactId: selectedContact?.id,
onClearTap: (contactId) => notifier.setContact(null),
onContactTap: () async {
- final contactId = await ContactsListRoute(
- title: context.i18n.contacts_select_title,
- ).push(context);
+ final pubkey = await CoinsSelectFriendRoute().push(context);
- if (contactId != null) {
- final contact = ref.read(contactByIdProvider(id: contactId));
+ if (pubkey != null) {
+ final contact = ref.read(contactByIdProvider(id: pubkey));
notifier.setContact(contact);
}
},
diff --git a/lib/app/features/wallet/views/pages/friends_modal/friends_modal.dart b/lib/app/features/wallet/views/pages/friends_modal/friends_modal.dart
new file mode 100644
index 000000000..1f150d817
--- /dev/null
+++ b/lib/app/features/wallet/views/pages/friends_modal/friends_modal.dart
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: ice License 1.0
+
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+import 'package:ion/app/extensions/extensions.dart';
+import 'package:ion/app/features/user/pages/user_picker_sheet/user_picker_sheet.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_app_bar.dart';
+import 'package:ion/app/router/components/navigation_app_bar/navigation_close_button.dart';
+import 'package:ion/app/router/components/sheet_content/sheet_content.dart';
+
+class FriendsModal extends StatelessWidget {
+ const FriendsModal({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return SheetContent(
+ topPadding: 0,
+ body: UserPickerSheet(
+ navigationBar: NavigationAppBar.modal(
+ title: Text(context.i18n.friends_modal_title),
+ actions: const [NavigationCloseButton()],
+ ),
+ onUserSelected: (user) => context.pop(user.masterPubkey),
+ ),
+ );
+ }
+}
diff --git a/lib/app/features/wallet/views/pages/send_nft_form/send_nft_form.dart b/lib/app/features/wallet/views/pages/send_nft_form/send_nft_form.dart
index 99bdd5afb..744f31aeb 100644
--- a/lib/app/features/wallet/views/pages/send_nft_form/send_nft_form.dart
+++ b/lib/app/features/wallet/views/pages/send_nft_form/send_nft_form.dart
@@ -68,12 +68,10 @@ class SendNftForm extends ConsumerWidget {
notifier.setContact(null),
},
onContactTap: () async {
- final contactId = await NftContactsListRoute(
- title: context.i18n.contacts_select_title,
- ).push(context);
+ final pubkey = await NftSelectFriendRoute().push(context);
- if (contactId != null) {
- final contact = ref.read(contactByIdProvider(id: contactId));
+ if (pubkey != null) {
+ final contact = ref.read(contactByIdProvider(id: pubkey));
notifier.setContact(contact);
}
},
diff --git a/lib/app/features/wallet/views/pages/wallet_page/components/contacts/contacts_list_header.dart b/lib/app/features/wallet/views/pages/wallet_page/components/contacts/contacts_list_header.dart
index 17745b781..5c6f74267 100644
--- a/lib/app/features/wallet/views/pages/wallet_page/components/contacts/contacts_list_header.dart
+++ b/lib/app/features/wallet/views/pages/wallet_page/components/contacts/contacts_list_header.dart
@@ -1,13 +1,13 @@
// SPDX-License-Identifier: ice License 1.0
+import 'dart:async';
+
import 'package:flutter/material.dart';
import 'package:ion/app/components/screen_offset/screen_side_offset.dart';
import 'package:ion/app/constants/ui.dart';
import 'package:ion/app/extensions/build_context.dart';
import 'package:ion/app/extensions/num.dart';
import 'package:ion/app/extensions/theme_data.dart';
-import 'package:ion/app/features/contacts/pages/contacts_list_view.dart';
-import 'package:ion/app/features/wallet/model/contact_data.c.dart';
import 'package:ion/app/router/app_routes.c.dart';
class ContactListHeader extends StatelessWidget {
@@ -32,10 +32,14 @@ class ContactListHeader extends StatelessWidget {
),
),
TextButton(
- onPressed: () => ContactsListRoute(
- title: context.i18n.contacts_title,
- action: ContactRouteAction.navigate,
- ).push(context),
+ onPressed: () async {
+ final pubkey = await NftSelectFriendRoute().push(context);
+ if (pubkey != null) {
+ if (context.mounted) {
+ unawaited(ContactRoute(contactId: pubkey).push(context));
+ }
+ }
+ },
child: Padding(
padding: EdgeInsets.all(UiConstants.hitSlop),
child: Text(
diff --git a/lib/app/router/app_routes.c.dart b/lib/app/router/app_routes.c.dart
index c8db88155..dec04fcda 100644
--- a/lib/app/router/app_routes.c.dart
+++ b/lib/app/router/app_routes.c.dart
@@ -28,7 +28,7 @@ import 'package:ion/app/features/chat/views/pages/new_channel_modal/new_channel_
import 'package:ion/app/features/chat/views/pages/new_chat_modal/new_chat_modal.dart';
import 'package:ion/app/features/chat/views/pages/new_group_modal/pages/add_group_participants_modal.dart';
import 'package:ion/app/features/chat/views/pages/new_group_modal/pages/create_group_modal.dart';
-import 'package:ion/app/features/contacts/pages/contacts_list_view.dart';
+import 'package:ion/app/features/chat/views/pages/share_profile_modal/share_profile_modal.dart';
import 'package:ion/app/features/core/model/language.dart';
import 'package:ion/app/features/core/views/pages/app_test_page/app_test_page.dart';
import 'package:ion/app/features/core/views/pages/error_page.dart';
@@ -123,6 +123,7 @@ import 'package:ion/app/features/wallet/views/pages/coins_flow/send_coins/compon
import 'package:ion/app/features/wallet/views/pages/coins_flow/send_coins/components/send_coins_form.dart';
import 'package:ion/app/features/wallet/views/pages/coins_flow/send_coins/send_coin_modal_page.dart';
import 'package:ion/app/features/wallet/views/pages/contact_modal_page/contact_modal_page.dart';
+import 'package:ion/app/features/wallet/views/pages/friends_modal/friends_modal.dart';
import 'package:ion/app/features/wallet/views/pages/manage_coins/manage_coins_page.dart';
import 'package:ion/app/features/wallet/views/pages/manage_nfts/manage_nfts_page.dart';
import 'package:ion/app/features/wallet/views/pages/nft_details/nft_details_page.dart';
diff --git a/lib/app/router/chat_routes.dart b/lib/app/router/chat_routes.dart
index d33c5614b..54cfdde65 100644
--- a/lib/app/router/chat_routes.dart
+++ b/lib/app/router/chat_routes.dart
@@ -82,17 +82,11 @@ class ChatLearnMoreModalRoute extends BaseRouteData {
}
class ShareProfileModalRoute extends BaseRouteData {
- ShareProfileModalRoute({required this.title, this.action = ContactRouteAction.pop})
+ ShareProfileModalRoute()
: super(
- child: ContactsListView(
- appBarTitle: title,
- action: action,
- ),
+ child: const ShareProfileModal(),
type: IceRouteType.bottomSheet,
);
-
- final String title;
- final ContactRouteAction action;
}
class ChatAddPollModalRoute extends BaseRouteData {
diff --git a/lib/app/router/wallet_routes.dart b/lib/app/router/wallet_routes.dart
index d1fa82601..2b9bc880e 100644
--- a/lib/app/router/wallet_routes.dart
+++ b/lib/app/router/wallet_routes.dart
@@ -46,7 +46,7 @@ class WalletRoutes {
TypedGoRoute(
path: 'nft-send',
routes: [
- TypedGoRoute(path: 'nft-contacts-list'),
+ TypedGoRoute(path: 'select-friend'),
],
),
TypedGoRoute(path: 'nft-confirm'),
@@ -65,7 +65,7 @@ class WalletRoutes {
TypedGoRoute(
path: 'coin-send-form',
routes: [
- TypedGoRoute(path: 'contacts-list'),
+ TypedGoRoute(path: 'select-friend'),
],
),
TypedGoRoute(path: 'scan-receiver-wallet'),
@@ -209,33 +209,20 @@ class CoinsSendFormRoute extends BaseRouteData {
);
}
-class ContactsListRoute extends BaseRouteData {
- ContactsListRoute({required this.title, this.action = ContactRouteAction.pop})
+class CoinsSelectFriendRoute extends BaseRouteData {
+ CoinsSelectFriendRoute()
: super(
- child: ContactsListView(
- appBarTitle: title,
- action: action,
- ),
+ child: const FriendsModal(),
type: IceRouteType.bottomSheet,
);
-
- final String title;
- final ContactRouteAction action;
}
-class NftContactsListRoute extends BaseRouteData {
- NftContactsListRoute({required this.title, this.action = ContactRouteAction.pop})
+class NftSelectFriendRoute extends BaseRouteData {
+ NftSelectFriendRoute()
: super(
- child: ContactsListView(
- action: action,
- appBarTitle: title,
- showBackButton: true,
- ),
+ child: const FriendsModal(),
type: IceRouteType.bottomSheet,
);
-
- final String title;
- final ContactRouteAction action;
}
class CoinsSendFormConfirmationRoute extends BaseRouteData {
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 24a3e5641..79a93230d 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -366,6 +366,7 @@
"sorting_price_desc": "Price: high to low",
"wallet_sorting_title": "Sorting the list",
"contacts_title": "Contacts",
+ "friends_modal_title": "Select friend",
"contacts_select_title": "Select contact",
"contacts_allow_pop_up_title": "Ice is better with friends",
"contacts_allow_pop_up_desc": "Sync your contacts, see who is already on Ice, and send and receive Ice payments from any of your contacts.",
@@ -578,7 +579,7 @@
"chat_modal_group_description": "Chat with multiple people together",
"chat_modal_channel_description": "Share updates with a wide audience",
"chat_profile_share_modal_title": "Share profile",
- "new_chat_modal_title": "New chat",
+ "new_chat_modal_title": "New Chat",
"new_chat_modal_description": "Search above for users, groups, and channels...",
"new_chat_modal_new_group_button": "New group",
"new_chat_modal_new_channel_button": "New channel",
@@ -602,7 +603,7 @@
"channel_create_type_public_desc": "Public channels are searchable and open to all users.",
"channel_create_type_private_desc": "Private channels can't be found in search and aren't end-to-end encrypted.",
"channel_create_admins_title": "Admin management",
- "channel_create_admins_action": "Add administrator",
+ "channel_create_admins_action": "Add Administrator",
"channel_create_admin_type_title": "Choose admin type",
"channel_create_admin_type_owner": "Owner",
"channel_create_admin_type_admin": "Admin",