diff --git a/assets/svg/ic_delete_group.svg b/assets/svg/ic_delete_group.svg new file mode 100644 index 00000000..4187677c --- /dev/null +++ b/assets/svg/ic_delete_group.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/desktop_routes/desktop_route_names.dart b/lib/desktop_routes/desktop_route_names.dart index 4d0fd7b2..deb0294a 100644 --- a/lib/desktop_routes/desktop_route_names.dart +++ b/lib/desktop_routes/desktop_route_names.dart @@ -27,4 +27,6 @@ class DesktopRoutes { 'desktop_group_right_initial'; static const String DESKTOP_NEW_GROUP = 'desktop_new_group'; static const String DESKTOP_GROUP_DETAIL = 'desktop_group_detail'; + static const String DESKTOP_ADD_OR_REMOVE_CONTACTS = + 'desktop_add_or_remove_contacts'; } diff --git a/lib/desktop_routes/desktop_routes.dart b/lib/desktop_routes/desktop_routes.dart index c7327718..8f736176 100644 --- a/lib/desktop_routes/desktop_routes.dart +++ b/lib/desktop_routes/desktop_routes.dart @@ -5,6 +5,7 @@ import 'package:atsign_atmosphere_pro/desktop_screens/desktop_download_all_files import 'package:atsign_atmosphere_pro/desktop_screens/desktop_home/desktop_home.dart'; import 'package:atsign_atmosphere_pro/desktop_screens/trusted_sender/desktop_empty_trusted_sender.dart'; import 'package:atsign_atmosphere_pro/desktop_screens_new/contacts_screen/desktop_contact_screen.dart'; +import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/desktop_add_or_remove_contacts_screen.dart'; import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/desktop_groups_screen.dart'; import 'package:atsign_atmosphere_pro/desktop_screens_new/settings_screen/blocked_contacts.dart'; import 'package:atsign_atmosphere_pro/desktop_screens_new/settings_screen/settings_desktop.dart'; @@ -71,6 +72,9 @@ class DesktopSetupRoutes { DesktopRoutes.DEKSTOP_CONTACTS_SCREEN: (context) { return DesktopContactScreen(); }, + DesktopRoutes.DESKTOP_ADD_OR_REMOVE_CONTACTS: (context) { + return DesktopAddOrRemoveContactsScreen(); + }, DesktopRoutes.DESKTOP_DOWNLOAD_ALL: (context) { return DesktopDownloadAllFiles(); }, diff --git a/lib/desktop_screens_new/groups_screen/desktop_add_or_remove_contacts_screen.dart b/lib/desktop_screens_new/groups_screen/desktop_add_or_remove_contacts_screen.dart new file mode 100644 index 00000000..2b6e34ba --- /dev/null +++ b/lib/desktop_screens_new/groups_screen/desktop_add_or_remove_contacts_screen.dart @@ -0,0 +1,309 @@ +import 'dart:typed_data'; + +import 'package:at_contact/at_contact.dart'; +import 'package:at_contacts_group_flutter/models/group_contacts_model.dart'; +import 'package:at_contacts_group_flutter/services/group_service.dart'; +import 'package:at_contacts_group_flutter/utils/text_constants.dart'; +import 'package:at_contacts_group_flutter/widgets/confirmation_dialog.dart'; +import 'package:at_onboarding_flutter/at_onboarding_flutter.dart'; +import 'package:atsign_atmosphere_pro/data_models/enums/group_card_state.dart'; +import 'package:atsign_atmosphere_pro/desktop_routes/desktop_route_names.dart'; +import 'package:atsign_atmosphere_pro/desktop_routes/desktop_routes.dart'; +import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/widgets/desktop_cover_image_picker.dart'; +import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/widgets/desktop_group_contacts_list.dart'; +import 'package:atsign_atmosphere_pro/screens/common_widgets/custom_toast.dart'; +import 'package:atsign_atmosphere_pro/utils/colors.dart'; +import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; +import 'package:atsign_atmosphere_pro/utils/vectors.dart'; +import 'package:atsign_atmosphere_pro/view_models/desktop_groups_screen_provider.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:provider/provider.dart'; + +class DesktopAddOrRemoveContactsScreen extends StatefulWidget { + const DesktopAddOrRemoveContactsScreen(); + + @override + State createState() => + _DesktopAddOrRemoveContactsScreenState(); +} + +class _DesktopAddOrRemoveContactsScreenState + extends State { + final _groupService = GroupService(); + late DesktopGroupsScreenProvider _groupsProvider = + context.read(); + bool isLoading = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) async { + await _groupService.fetchGroupsAndContacts(isDesktop: true); + + _groupService.selectedGroupContacts = + _groupsProvider.selectedAtGroup?.members + ?.map( + (e) => GroupContactsModel( + contact: e, + contactType: ContactsType.CONTACT, + ), + ) + .toList() ?? + []; + _groupService.selectedContactsSink + .add(_groupService.selectedGroupContacts); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Row( + children: [ + Expanded( + flex: 572, + child: buildHalfLeftWidget(), + ), + Expanded( + flex: 441, + child: buildHalfRightWidget(), + ), + ], + ), + ); + } + + Widget buildHalfLeftWidget() { + return Container( + decoration: BoxDecoration( + color: ColorConstants.background, + borderRadius: BorderRadius.horizontal( + right: Radius.circular(47), + ), + ), + child: ListView( + children: [ + SizedBox(height: 40), + Padding( + padding: const EdgeInsets.only(left: 60), + child: buildAppbar(), + ), + SizedBox(height: 20), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 72), + child: DesktopGroupContactsList( + asSelectionScreen: true, + initialData: _groupsProvider.selectedAtGroup?.members + ?.map( + (e) => GroupContactsModel( + contact: e, + contactType: ContactsType.CONTACT, + ), + ) + .toList(), + ), + ), + ], + ), + ); + } + + Widget buildAppbar() { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () async { + await DesktopSetupRoutes.nested_push( + DesktopRoutes.DESKTOP_GROUP, + ); + }, + child: SizedBox( + width: 24, + height: 24, + child: Center( + child: SvgPicture.asset( + AppVectors.icBack, + width: 8, + height: 16, + fit: BoxFit.cover, + ), + ), + ), + ), + SizedBox(width: 28), + Text( + 'Add or Remove Contacts', + style: CustomTextStyles.blackW50020, + ) + ], + ); + } + + Widget buildHalfRightWidget() { + return Stack( + children: [ + ListView( + padding: const EdgeInsets.fromLTRB(56, 40, 24, 0), + children: [ + Text( + _groupsProvider.selectedGroupName ?? '', + style: CustomTextStyles.blackW50020, + ), + SizedBox(height: 28), + DesktopCoverImagePicker( + selectedImage: _groupsProvider.selectedGroupImage, + isEdit: false, + ), + SizedBox(height: 52), + StreamBuilder>( + stream: _groupService.selectedContactsStream, + initialData: _groupsProvider.selectedAtGroup?.members + ?.map( + (e) => GroupContactsModel( + contact: e, + contactType: ContactsType.CONTACT, + ), + ) + .toList(), + builder: (context, snapshot) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Text( + '${snapshot.data?.length} ${(snapshot.data?.length ?? 0) > 1 ? 'Members' : 'Member'}', + style: CustomTextStyles.raisinBlackW50015, + ), + ), + const SizedBox(height: 28), + DesktopGroupContactsList( + showMembersOnly: true, + asSelectionScreen: false, + initialData: snapshot.data, + showSelectedBorder: false, + ), + ], + ); + }, + ), + ], + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + color: Colors.white, + padding: const EdgeInsets.fromLTRB(56, 16, 24, 52), + child: InkWell( + onTap: () async { + await updateMembers(context); + }, + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + color: ColorConstants.orange, + borderRadius: BorderRadius.circular(135), + ), + child: isLoading + ? SizedBox( + width: 20, + height: 20, + child: CircularProgressIndicator( + color: Colors.white, + ), + ) + : Text( + 'Update Group', + style: CustomTextStyles.whiteBold16, + ), + ), + ), + ), + ), + ], + ); + } + + Future updateMembers(BuildContext context) async { + if (isLoading) return; + setState(() { + isLoading = true; + }); + + if (_groupService.selectedGroupContacts.isEmpty) { + await showMyDialog(context, _groupsProvider.selectedAtGroup!); + } else { + AtGroup group = _groupsProvider.selectedAtGroup!; + group.members = _groupService.selectedGroupContacts + .where((e) => + e!.contactType == ContactsType.CONTACT && e.contact != null) + .map((e) => e!.contact!) + .toSet(); + final result = await GroupService().updateGroup(group); + if (result is AtGroup) { + _groupService.selectedGroupContacts = []; + await _groupsProvider.setSelectedAtGroup(result); + await _groupService.fetchGroupsAndContacts(isDesktop: true); + await DesktopSetupRoutes.nested_push( + DesktopRoutes.DESKTOP_GROUP, + ); + } else if (result != null) { + if (result.runtimeType == AlreadyExistsException) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(TextConstants().GROUP_ALREADY_EXISTS))); + } else if (result.runtimeType == InvalidAtSignException) { + ScaffoldMessenger.of(context) + .showSnackBar(SnackBar(content: Text(result.content))); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(TextConstants().SERVICE_ERROR))); + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(TextConstants().SERVICE_ERROR))); + } + } + setState(() { + isLoading = false; + }); + } + + Future showMyDialog(BuildContext context, AtGroup group) async { + return showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + Uint8List? groupPicture; + if (group.groupPicture != null) { + List intList = group.groupPicture.cast(); + groupPicture = Uint8List.fromList(intList); + } + return ConfirmationDialog( + title: '${group.displayName}', + heading: 'Are you sure you want to delete this group?', + onYesPressed: () async { + var result = await GroupService().deleteGroup(group); + + if (!mounted) return; + if (result != null && result) { + Navigator.pop(context); + await _groupsProvider.setSelectedAtGroup(null); + _groupsProvider.setGroupCardState(GroupCardState.disable); + await DesktopSetupRoutes.nested_push( + DesktopRoutes.DESKTOP_GROUP, + ); + } else { + CustomToast().show(TextConstants().SERVICE_ERROR, context); + } + }, + image: groupPicture, + ); + }, + ); + } +} diff --git a/lib/desktop_screens_new/groups_screen/widgets/desktop_cover_image_picker.dart b/lib/desktop_screens_new/groups_screen/widgets/desktop_cover_image_picker.dart index 3e275f7c..963c80f5 100644 --- a/lib/desktop_screens_new/groups_screen/widgets/desktop_cover_image_picker.dart +++ b/lib/desktop_screens_new/groups_screen/widgets/desktop_cover_image_picker.dart @@ -7,16 +7,16 @@ import 'package:flutter_svg/flutter_svg.dart'; class DesktopCoverImagePicker extends StatelessWidget { final Uint8List? selectedImage; - final Function() onPickImage; + final Function()? onPickImage; final bool isEdit; - final Function() onCancel; + final Function()? onCancel; const DesktopCoverImagePicker({ Key? key, this.selectedImage, - required this.onPickImage, + this.onPickImage, required this.isEdit, - required this.onCancel, + this.onCancel, }) : super(key: key); @override @@ -24,14 +24,13 @@ class DesktopCoverImagePicker extends StatelessWidget { return InkWell( onTap: () async { if (isEdit) { - onPickImage.call(); + onPickImage?.call(); } }, - child: selectedImage != null && selectedImage!.isNotEmpty - ? SizedBox( - width: 360, - height: 88, - child: Stack( + child: SizedBox( + height: 120, + child: selectedImage != null && selectedImage!.isNotEmpty + ? Stack( alignment: Alignment.center, fit: StackFit.expand, children: [ @@ -84,30 +83,30 @@ class DesktopCoverImagePicker extends StatelessWidget { ), ), ], + ) + : Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: ColorConstants.pickerBackgroundColor, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Insert Cover Image', + style: CustomTextStyles.orangeW50014, + ), + const SizedBox(height: 8), + SvgPicture.asset( + AppVectors.icDesktopImage, + width: 48, + height: 32, + ), + ], + ), ), - ) - : Container( - padding: const EdgeInsets.only(top: 12, bottom: 16), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - color: ColorConstants.pickerBackgroundColor, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - 'Insert Cover Image', - style: CustomTextStyles.orangeW50014, - ), - const SizedBox(height: 8), - SvgPicture.asset( - AppVectors.icDesktopImage, - width: 48, - height: 32, - ), - ], - ), - ), + ), ); } } diff --git a/lib/desktop_screens_new/groups_screen/widgets/desktop_custom_list_tile.dart b/lib/desktop_screens_new/groups_screen/widgets/desktop_custom_list_tile.dart index 9ca9f6fe..351cd38a 100644 --- a/lib/desktop_screens_new/groups_screen/widgets/desktop_custom_list_tile.dart +++ b/lib/desktop_screens_new/groups_screen/widgets/desktop_custom_list_tile.dart @@ -32,6 +32,8 @@ class DesktopCustomListTile extends StatefulWidget { final bool selectSingle; final ValueChanged>? selectedList; final bool isTrusted; + final List selectedContact; + final bool showSelectedBorder; const DesktopCustomListTile({ Key? key, @@ -42,6 +44,8 @@ class DesktopCustomListTile extends StatefulWidget { this.selectSingle = false, this.selectedList, required this.isTrusted, + this.selectedContact = const [], + required this.showSelectedBorder, }) : super(key: key); @override @@ -63,7 +67,7 @@ class _DesktopCustomListTileState extends State { _groupService = GroupService(); // ignore: omit_local_variable_types - getIsSelectedValue(_groupService.selectedGroupContacts); + getIsSelectedValue(widget.selectedContact); super.initState(); } @@ -113,110 +117,104 @@ class _DesktopCustomListTileState extends State { Widget build(BuildContext context) { getNameAndImage(); - return StreamBuilder>( - initialData: _groupService.selectedGroupContacts, - stream: _groupService.selectedContactsStream, - builder: (context, snapshot) { - getIsSelectedValue(_groupService.selectedGroupContacts); - - return InkWell( - onTap: () { - if (widget.asSelectionTile) { - if (widget.selectSingle) { - _groupService.selectedGroupContacts = []; + return InkWell( + onTap: () { + if (widget.asSelectionTile) { + if (widget.selectSingle) { + _groupService.selectedGroupContacts = []; + _groupService.addGroupContact(widget.item); + widget.selectedList!([widget.item]); + Navigator.pop(context); + } else if (!widget.selectSingle) { + if (mounted) { + setState(() { + if (isSelected) { + _groupService.removeGroupContact(widget.item); + } else { _groupService.addGroupContact(widget.item); - widget.selectedList!([widget.item]); - Navigator.pop(context); - } else if (!widget.selectSingle) { - if (mounted) { - setState(() { - if (isSelected) { - _groupService.removeGroupContact(widget.item); - } else { - _groupService.addGroupContact(widget.item); - } - isSelected = !isSelected; - }); - } } - } else { - widget.onTap!(); - } - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: - isSelected ? ColorConstants.orange : Colors.transparent, - width: 2, - ), - ), - child: Row( - children: [ - (isLoading) - ? const CircularProgressIndicator() - : ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(10), - bottomLeft: Radius.circular(10), - ), - child: (image != null) - ? Image.memory( - image!, - width: 72, - height: 72, - fit: BoxFit.cover, - ) - : ContactInitial( - size: 72, - borderRadius: 0, - initials: (initials ?? 'UG'), - ), - ), - const SizedBox(width: 16), - Expanded( - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.item?.contact?.atSign ?? - '${widget.item?.group?.members?.length} Members', - style: TextStyle( - color: Colors.black, - fontSize: 13.toFont, - fontWeight: FontWeight.w600, - ), - ), - if ((widget.item?.contact?.tags?["nickname"] ?? '') - .isNotEmpty) - Text( - widget.item?.contact?.tags?["nickname"], - style: TextStyle( - color: Colors.black, - fontSize: 10.toFont, - fontWeight: FontWeight.w400, - ), - ), - ], - ), + isSelected = !isSelected; + }); + } + } + } else { + widget.onTap!(); + } + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: isSelected && widget.showSelectedBorder + ? ColorConstants.orange + : Colors.transparent, + width: 2, + ), + ), + child: Row( + children: [ + (isLoading) + ? const CircularProgressIndicator() + : ClipRRect( + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(10), + bottomLeft: Radius.circular(10), ), + child: (image != null) + ? Image.memory( + image!, + width: 72, + height: 72, + fit: BoxFit.cover, + ) + : ContactInitial( + size: 72, + borderRadius: 0, + initials: (initials ?? 'UG'), + ), ), - if (widget.isTrusted) - SvgPicture.asset( - AppVectors.icTrust, - width: 24, - height: 20, - color: ColorConstants.orange, + const SizedBox(width: 16), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.item?.contact?.atSign ?? + '${widget.item?.group?.members?.length} Members', + style: TextStyle( + color: Colors.black, + fontSize: 13.toFont, + fontWeight: FontWeight.w600, + ), ), - const SizedBox(width: 24), - ], + if ((widget.item?.contact?.tags?["nickname"] ?? '') + .isNotEmpty) + Text( + widget.item?.contact?.tags?["nickname"], + style: TextStyle( + color: Colors.black, + fontSize: 10.toFont, + fontWeight: FontWeight.w400, + ), + ), + ], + ), ), ), - ); - }); + if (widget.isTrusted) + SvgPicture.asset( + AppVectors.icTrust, + width: 24, + height: 20, + color: ColorConstants.orange, + ), + const SizedBox(width: 24), + ], + ), + ), + ); } } diff --git a/lib/desktop_screens_new/groups_screen/widgets/desktop_group_contacts_list.dart b/lib/desktop_screens_new/groups_screen/widgets/desktop_group_contacts_list.dart index e5c97509..efbfabe3 100644 --- a/lib/desktop_screens_new/groups_screen/widgets/desktop_group_contacts_list.dart +++ b/lib/desktop_screens_new/groups_screen/widgets/desktop_group_contacts_list.dart @@ -5,7 +5,6 @@ import 'package:at_contacts_flutter/utils/text_strings.dart'; import 'package:at_contacts_group_flutter/models/group_contacts_model.dart'; import 'package:at_contacts_group_flutter/services/group_service.dart'; import 'package:at_contacts_group_flutter/widgets/add_contacts_group_dialog.dart'; -import 'package:at_contacts_group_flutter/widgets/horizontal_circular_list.dart'; import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/widgets/desktop_custom_list_tile.dart'; import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/widgets/icon_button_widget.dart'; import 'package:atsign_atmosphere_pro/utils/colors.dart'; @@ -13,6 +12,7 @@ import 'package:atsign_atmosphere_pro/utils/text_styles.dart'; import 'package:atsign_atmosphere_pro/utils/vectors.dart'; import 'package:atsign_atmosphere_pro/view_models/desktop_groups_screen_provider.dart'; import 'package:atsign_atmosphere_pro/view_models/trusted_sender_view_model.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -20,16 +20,16 @@ import 'package:provider/provider.dart'; class DesktopGroupContactsList extends StatefulWidget { final bool asSelectionScreen; - final bool singleSelection; - final bool showContacts; final List? initialData; + final bool showMembersOnly; + final bool showSelectedBorder; const DesktopGroupContactsList({ Key? key, this.asSelectionScreen = false, - this.singleSelection = false, - this.showContacts = true, this.initialData, + this.showMembersOnly = false, + this.showSelectedBorder = true, }) : super(key: key); @override @@ -51,11 +51,12 @@ class _DesktopGroupContactsListState extends State { groupProvider = context.read(); trustedProvider = context.read(); searchController = TextEditingController(); - WidgetsBinding.instance.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) async { groupProvider.setSearchContactText(''); if (groupProvider.showTrustedContacts) { groupProvider.setShowTrustedContacts(); } + _groupService.selectedContactsSink.add(widget.initialData ?? []); }); super.initState(); } @@ -66,8 +67,6 @@ class _DesktopGroupContactsListState extends State { @override void dispose() { - _groupService.selectedGroupContacts = []; - _groupService.selectedContactsSink.add(_groupService.selectedGroupContacts); super.dispose(); } @@ -77,33 +76,29 @@ class _DesktopGroupContactsListState extends State { builder: (context, provider, child) { return Column( children: [ - Row( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: buildSearchField(), - ), - const SizedBox(width: 12), - IconButtonWidget( - icon: AppVectors.icTrust, - backgroundColor: ColorConstants.iconButtonColor, - isSelected: provider.showTrustedContacts, - onTap: () { - provider.setShowTrustedContacts(); - }, - ) - ], - ), - const SizedBox(height: 20), - widget.asSelectionScreen ? HorizontalCircularList() : Container(), - (widget.initialData ?? []).isNotEmpty - ? buildContactsList(provider.showTrustedContacts - ? widget.initialData - ?.where((e) => trustedProvider.trustedContacts.any( - (element) => element.atSign == e?.contact?.atSign)) - .toList() - : widget.initialData) + if (!widget.showMembersOnly) ...[ + Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: buildSearchField(), + ), + const SizedBox(width: 12), + IconButtonWidget( + icon: AppVectors.icTrust, + backgroundColor: ColorConstants.iconButtonColor, + isSelected: provider.showTrustedContacts, + onTap: () { + provider.setShowTrustedContacts(); + }, + ) + ], + ), + const SizedBox(height: 20), + ], + !widget.asSelectionScreen + ? buildContactsList(widget.initialData) : StreamBuilder>( stream: _groupService.allContactsStream, initialData: _groupService.allContacts, @@ -140,14 +135,16 @@ class _DesktopGroupContactsListState extends State { ], ); } else { - return buildContactsList(provider.showTrustedContacts - ? trustedProvider.trustedContacts - .map((e) => GroupContactsModel( - contact: e, - contactType: ContactsType.CONTACT, - )) - .toList() - : snapshot.data); + return buildContactsList( + provider.showTrustedContacts + ? trustedProvider.trustedContacts + .map((e) => GroupContactsModel( + contact: e, + contactType: ContactsType.CONTACT, + )) + .toList() + : snapshot.data, + ); } } }) @@ -160,6 +157,7 @@ class _DesktopGroupContactsListState extends State { // filtering contacts and groups var _filteredList = []; _filteredList = getAllContactList(data ?? []); + bool isFirst = true; if (_filteredList.isEmpty) { return Center( @@ -175,7 +173,6 @@ class _DesktopGroupContactsListState extends State { // renders contacts according to the initial alphabet return ListView.builder( - padding: EdgeInsets.only(bottom: 80.toHeight), itemCount: 27, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -200,31 +197,91 @@ class _DesktopGroupContactsListState extends State { } if (contactsForAlphabet.isEmpty) { - return Container(); + Widget separator = SizedBox(); + if (widget.showMembersOnly && + getContactsForAlphabets( + _filteredList, + String.fromCharCode(alphabetIndex + 1 + 65).toUpperCase(), + alphabetIndex + 1, + ).isNotEmpty) { + if (isFirst && + getContactsForAlphabets( + _filteredList, + String.fromCharCode(alphabetIndex -1 + 65).toUpperCase(), + alphabetIndex - 1, + ).isNotEmpty) { + isFirst = false; + } else { + separator = Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Divider( + color: ColorConstants.boxGrey, + thickness: 1.toHeight, + height: 0, + ), + ); + } + } else if (!widget.showMembersOnly && + getContactsForAlphabets( + _filteredList, + String.fromCharCode(alphabetIndex + 1 + 65).toUpperCase(), + alphabetIndex + 1, + ).isNotEmpty) { + if (isFirst && + getContactsForAlphabets( + _filteredList, + String.fromCharCode(alphabetIndex -1 + 65).toUpperCase(), + alphabetIndex - 1, + ).isNotEmpty) { + isFirst = false; + } else { + separator = SizedBox(height: 16); + } + } + return separator; } return Column( + mainAxisSize: MainAxisSize.min, children: [ - Row( - children: [ - Text( - currentChar, - style: TextStyle( - color: ColorConstants.darkSliver, - fontSize: 20.toFont, - fontWeight: FontWeight.bold, + if (!widget.showMembersOnly) ...[ + Row( + children: [ + Text( + currentChar, + style: TextStyle( + color: ColorConstants.darkSliver, + fontSize: 20.toFont, + fontWeight: FontWeight.bold, + ), ), - ), - SizedBox(width: 16.toWidth), - Expanded( - child: Divider( - color: ColorConstants.boxGrey, - height: 1.toHeight, + SizedBox(width: 16.toWidth), + Expanded( + child: Divider( + color: ColorConstants.boxGrey, + thickness: 1.toHeight, + height: 0, + ), ), + ], + ), + SizedBox(height: 16), + ], + contactListBuilder(contactsForAlphabet), + if (widget.showMembersOnly && + getContactsForAlphabets( + _filteredList, + String.fromCharCode(alphabetIndex + 1 + 65).toUpperCase(), + alphabetIndex + 1, + ).isNotEmpty) + Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Divider( + color: ColorConstants.boxGrey, + thickness: 1.toHeight, + height: 0, ), - ], - ), - contactListBuilder(contactsForAlphabet) + ), ], ); }, @@ -324,47 +381,83 @@ class _DesktopGroupContactsListState extends State { List contactsForAlphabet, ) { return ListView.separated( - padding: const EdgeInsets.symmetric(horizontal: 28), + padding: EdgeInsets.symmetric( + horizontal: widget.showMembersOnly ? 0 : 28, + ), itemCount: contactsForAlphabet.length, physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, - separatorBuilder: (context, _) => Divider( - color: ColorConstants.dividerColor, - height: 1.toHeight, + separatorBuilder: (context, _) => Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: Divider( + color: ColorConstants.boxGrey, + thickness: 1.toHeight, + height: 0, + ), ), itemBuilder: (context, index) { - return Padding( - padding: const EdgeInsets.only( - right: 24, - bottom: 12, - top: 12, - ), - child: (contactsForAlphabet[index]!.contact != null) - ? Slidable( - endActionPane: ActionPane( - motion: const ScrollMotion(), - extentRatio: 0.25, - children: [ - SlidableAction( - label: TextStrings().block, - backgroundColor: ColorConstants.inputFieldColor, - icon: Icons.block, - onPressed: (context) async { - blockUnblockContact( - contactsForAlphabet[index]!.contact!); + return (contactsForAlphabet[index]!.contact != null) + ? Slidable( + endActionPane: ActionPane( + motion: const ScrollMotion(), + extentRatio: 0.25, + children: [ + SlidableAction( + label: TextStrings().block, + backgroundColor: ColorConstants.inputFieldColor, + icon: Icons.block, + onPressed: (context) async { + blockUnblockContact( + contactsForAlphabet[index]!.contact!); + }, + ), + SlidableAction( + label: TextStrings().delete, + backgroundColor: Colors.red, + icon: Icons.delete, + onPressed: (context) async { + deleteAtSign(contactsForAlphabet[index]!.contact!); + }, + ), + ], + ), + child: StreamBuilder>( + initialData: widget.initialData, + stream: _groupService.selectedContactsStream, + builder: (context, snapshot) { + return DesktopCustomListTile( + key: UniqueKey(), + onTap: () {}, + asSelectionTile: widget.asSelectionScreen, + selectSingle: false, + item: contactsForAlphabet[index], + selectedList: (s) { + setSelectedContactsList(s); }, - ), - SlidableAction( - label: TextStrings().delete, - backgroundColor: Colors.red, - icon: Icons.delete, - onPressed: (context) async { - deleteAtSign(contactsForAlphabet[index]!.contact!); + selectedContact: snapshot.data ?? [], + onTrailingPressed: () { + if (contactsForAlphabet[index]!.contact != null) { + Navigator.pop(context); + + _groupService + .addGroupContact(contactsForAlphabet[index]); + setSelectedContactsList( + _groupService.selectedGroupContacts); + } }, - ), - ], - ), - child: DesktopCustomListTile( + isTrusted: trustedProvider.trustedContacts.any( + (element) => + element.atSign == + contactsForAlphabet[index]?.contact?.atSign), + showSelectedBorder: widget.showSelectedBorder, + ); + }), + ) + : StreamBuilder>( + initialData: widget.initialData, + stream: _groupService.selectedContactsStream, + builder: (context, snapshot) { + return DesktopCustomListTile( key: UniqueKey(), onTap: () {}, asSelectionTile: widget.asSelectionScreen, @@ -373,8 +466,9 @@ class _DesktopGroupContactsListState extends State { selectedList: (s) { setSelectedContactsList(s); }, + selectedContact: snapshot.data ?? [], onTrailingPressed: () { - if (contactsForAlphabet[index]!.contact != null) { + if (contactsForAlphabet[index]!.group != null) { Navigator.pop(context); _groupService @@ -386,31 +480,9 @@ class _DesktopGroupContactsListState extends State { isTrusted: trustedProvider.trustedContacts.any((element) => element.atSign == contactsForAlphabet[index]?.contact?.atSign), - ), - ) - : DesktopCustomListTile( - key: UniqueKey(), - onTap: () {}, - asSelectionTile: widget.asSelectionScreen, - selectSingle: false, - item: contactsForAlphabet[index], - selectedList: (s) { - setSelectedContactsList(s); - }, - onTrailingPressed: () { - if (contactsForAlphabet[index]!.group != null) { - Navigator.pop(context); - - _groupService.addGroupContact(contactsForAlphabet[index]); - setSelectedContactsList( - _groupService.selectedGroupContacts); - } - }, - isTrusted: trustedProvider.trustedContacts.any((element) => - element.atSign == - contactsForAlphabet[index]?.contact?.atSign), - ), - ); + showSelectedBorder: widget.showSelectedBorder, + ); + }); }, ); } diff --git a/lib/desktop_screens_new/groups_screen/widgets/desktop_groups_detail.dart b/lib/desktop_screens_new/groups_screen/widgets/desktop_groups_detail.dart index 8cbb8b56..f82d0d6c 100644 --- a/lib/desktop_screens_new/groups_screen/widgets/desktop_groups_detail.dart +++ b/lib/desktop_screens_new/groups_screen/widgets/desktop_groups_detail.dart @@ -10,13 +10,12 @@ import 'package:at_contacts_group_flutter/utils/text_constants.dart'; import 'package:at_contacts_group_flutter/widgets/confirmation_dialog.dart'; import 'package:at_contacts_group_flutter/widgets/custom_toast.dart'; import 'package:atsign_atmosphere_pro/data_models/enums/group_card_state.dart'; +import 'package:atsign_atmosphere_pro/desktop_routes/desktop_route_names.dart'; import 'package:atsign_atmosphere_pro/desktop_routes/desktop_routes.dart'; import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/widgets/desktop_cover_image_picker.dart'; import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/widgets/desktop_custom_app_bar.dart'; -import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/widgets/desktop_floating_add_contact_button.dart'; import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/widgets/desktop_group_contacts_list.dart'; import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/widgets/desktop_group_name_text_field.dart'; -import 'package:atsign_atmosphere_pro/desktop_screens_new/groups_screen/widgets/icon_button_widget.dart'; import 'package:atsign_atmosphere_pro/services/picker_service.dart'; import 'package:atsign_atmosphere_pro/services/snackbar_service.dart'; import 'package:atsign_atmosphere_pro/utils/colors.dart'; @@ -100,41 +99,55 @@ class _DesktopGroupsDetailState extends State { resetGroupName(); } : () => widget.onBackArrowTap(false), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 20), - child: SvgPicture.asset( - AppVectors.icBack, - width: 8, - height: 20, + child: SizedBox( + height: 24, + width: 24, + child: Center( + child: SvgPicture.asset( + AppVectors.icBack, + width: 8, + height: 20, + fit: BoxFit.cover, + ), ), ), ), showLeadingIcon: true, - showTrailingIcon: provider.isEditing, - trailingIcon: InkWell( - onTap: () async { - await updateGroup(); - }, - child: Container( - padding: - const EdgeInsets.symmetric(horizontal: 52, vertical: 8), - margin: const EdgeInsets.only(right: 28), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(46), - color: ColorConstants.orange, - ), - child: Text( - 'Save', - style: CustomTextStyles.whiteW50015, - ), - ), - ), + showTrailingIcon: true, + onTrailingIconPressed: provider.isEditing + ? () async { + await updateGroup(); + } + : () { + groupProvider.setShowEditOptionsStatus(); + }, + trailingIcon: provider.isEditing + ? Container( + padding: + const EdgeInsets.symmetric(horizontal: 52, vertical: 8), + margin: const EdgeInsets.only(right: 28), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(46), + color: ColorConstants.orange, + ), + child: Text( + 'Save', + style: CustomTextStyles.whiteW50015, + ), + ) + : SvgPicture.asset( + AppVectors.icOptions, + width: 16, + fit: BoxFit.fitWidth, + color: provider.showEditOptions + ? ColorConstants.orange + : Colors.black, + ), ), body: Stack( children: [ ListView( - padding: - const EdgeInsets.symmetric(horizontal: 24, vertical: 16), + padding: const EdgeInsets.fromLTRB(20, 16, 20, 132), children: [ if (provider.isEditing) ...[ DesktopGroupNameTextField( @@ -178,13 +191,21 @@ class _DesktopGroupsDetailState extends State { ), ), SizedBox(height: provider.isEditing ? 16 : 20), - buildDetailOptions( - isAddingContacts: provider.isAddingContacts, - isEditing: provider.isEditing, - ), - const SizedBox(height: 12), + if (!provider.isEditing) buildTransferFileButton(), + const SizedBox(height: 36), + provider.isEditing + ? SizedBox(height: 8) + : Center( + child: Text( + '${provider.selectedAtGroup?.members?.length} ${(provider.selectedAtGroup?.members?.length ?? 0) > 1 ? 'Members' : 'Member'}', + style: CustomTextStyles.raisinBlackW50015, + ), + ), + const SizedBox(height: 28), DesktopGroupContactsList( - asSelectionScreen: provider.isAddingContacts, + asSelectionScreen: false, + showMembersOnly: !provider.isEditing, + showSelectedBorder: provider.isEditing, initialData: provider.isAddingContacts ? [] : provider.selectedAtGroup?.members @@ -195,8 +216,32 @@ class _DesktopGroupsDetailState extends State { ), ], ), - if (provider.isAddingContacts) - const DesktopFloatingAddContactButton(), + if (!provider.isEditing) + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Container( + color: Colors.white, + padding: const EdgeInsets.fromLTRB(28, 16, 28, 52), + child: buildEditMembers(), + ), + ), + if (provider.showEditOptions) ...[ + Positioned.fill( + child: InkWell( + onTap: () { + groupProvider.setShowEditOptionsStatus(); + }, + ), + ), + Positioned( + top: 8, + left: 20, + right: 20, + child: buildEditOptionsDialog(), + ), + ], ], ), ), @@ -204,111 +249,152 @@ class _DesktopGroupsDetailState extends State { }); } - Widget buildDetailOptions({ - required bool isEditing, - required bool isAddingContacts, - }) { - return isEditing - ? Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - IconButtonWidget( - icon: AppVectors.icDesktopAdd, - isSelected: isAddingContacts, - onTap: () { - groupProvider.setIsAddingContact(); - }, - backgroundColor: ColorConstants.iconButtonColor, - ), - const SizedBox(width: 20), - IconButtonWidget( - icon: AppVectors.icShare, - padding: - const EdgeInsets.symmetric(vertical: 12, horizontal: 8), - onTap: () {}, - backgroundColor: ColorConstants.iconButtonColor, - ), - const SizedBox(width: 20), - IconButtonWidget( - icon: AppVectors.icDelete, - onTap: () async { - await showMyDialog( - context, - groupProvider.selectedAtGroup!, - ); - }, - backgroundColor: ColorConstants.iconButtonColor, - ), - ], - ) - : Column( - mainAxisSize: MainAxisSize.min, - children: [ - InkWell( - onTap: () async { - Provider.of(context, listen: false) - .selectedContacts = [ - GroupContactsModel( - group: groupProvider.selectedAtGroup, - contactType: ContactsType.GROUP, - ), - ]; - Provider.of(context, listen: false) - .notify(); - await DesktopSetupRoutes.nested_pop(); - }, - child: Container( - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(5), - color: Colors.black, - ), - child: Center( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "Transfer File", - style: CustomTextStyles.whiteBold12, - ), - const SizedBox(width: 12), - SvgPicture.asset( - AppVectors.icTransfer, - width: 16, - height: 12, - fit: BoxFit.cover, - ), - ], - ), + Widget buildEditMembers() { + return InkWell( + onTap: () async { + await DesktopSetupRoutes.nested_push( + DesktopRoutes.DESKTOP_ADD_OR_REMOVE_CONTACTS, + ); + }, + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.symmetric(vertical: 20), + decoration: BoxDecoration( + color: ColorConstants.editMembersButtonColor, + borderRadius: BorderRadius.circular(10), + ), + child: Text( + 'Edit Members', + style: CustomTextStyles.dimGrayW50015, + ), + ), + ); + } + + Widget buildEditOptionsDialog() { + return Container( + clipBehavior: Clip.antiAlias, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(7), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.25), + offset: Offset(0, 4), + blurRadius: 18, + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + InkWell( + onTap: () async { + await groupProvider.setIsEditing(true); + groupProvider.setShowEditOptionsStatus(); + }, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 12), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'Edit Group', + style: CustomTextStyles.blackW40015, ), - ), + SizedBox(width: 12), + SvgPicture.asset( + AppVectors.icPencil, + width: 16, + height: 16, + fit: BoxFit.cover, + ) + ], ), - const SizedBox(height: 20), - Row( - mainAxisSize: MainAxisSize.min, + ), + ), + Divider( + height: 0, + color: Colors.black, + thickness: 1, + ), + InkWell( + onTap: () async { + groupProvider.setShowEditOptionsStatus(); + await showMyDialog( + context, + groupProvider.selectedAtGroup!, + ); + }, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 12), + child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - IconButtonWidget( - icon: AppVectors.icEdit, - backgroundColor: ColorConstants.iconButtonColor, - onTap: () async => await groupProvider.setIsEditing(true), - ), - const SizedBox(width: 24), - IconButtonWidget( - icon: AppVectors.icDelete, - backgroundColor: ColorConstants.iconButtonColor, - onTap: () async { - await showMyDialog( - context, - groupProvider.selectedAtGroup!, - ); - }, + Text( + 'Delete Group', + style: CustomTextStyles.blackW40015, ), + SizedBox(width: 12), + SvgPicture.asset( + AppVectors.icDeleteGroup, + width: 16, + height: 16, + fit: BoxFit.cover, + ) ], ), + ), + ) + ], + ), + ); + } + + Widget buildTransferFileButton() { + return InkWell( + onTap: () async { + Provider.of(context, listen: false) + .selectedContacts = [ + GroupContactsModel( + group: groupProvider.selectedAtGroup, + contactType: ContactsType.GROUP, + ), + ]; + Provider.of(context, listen: false).notify(); + await DesktopSetupRoutes.nested_pop(); + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 16), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: Colors.black, + ), + child: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Transfer File", + style: CustomTextStyles.whiteBold12, + ), + const SizedBox(width: 12), + SizedBox( + width: 20, + height: 20, + child: SvgPicture.asset( + AppVectors.icSendGroup, + width: 16, + height: 16, + fit: BoxFit.cover, + color: Colors.white, + ), + ), ], - ); + ), + ), + ), + ); } Future showMyDialog(BuildContext context, AtGroup group) async { diff --git a/lib/utils/colors.dart b/lib/utils/colors.dart index fd3c1fc8..496bab8a 100644 --- a/lib/utils/colors.dart +++ b/lib/utils/colors.dart @@ -61,6 +61,7 @@ class ColorConstants { static const Color gray2 = Color(0xFFB9B9B9); static const Color lightGray2 = Color(0xFFE3E3E3); static const Color darkGray2 = Color(0xFF474747); + static const Color dimGray = Color(0xFF696969); static const Color sidebarTextUnselected = Color(0xFFA4A4A5); static const Color sidebarTextSelected = Color(0xFF000000); @@ -106,6 +107,7 @@ class ColorConstants { static const Color disableTooltipColor = Color(0xFFC4C4C4); static const Color inactiveIconColor = Color(0xFFC5C5C5); static const Color closeButtonColor = Color(0xFFE6E6E6); + static const Color editMembersButtonColor = Color(0xFFF0F0F0); } class ContactInitialsColors { diff --git a/lib/utils/text_styles.dart b/lib/utils/text_styles.dart index 9aac0c8f..c38f7208 100644 --- a/lib/utils/text_styles.dart +++ b/lib/utils/text_styles.dart @@ -509,6 +509,12 @@ class CustomTextStyles { fontSize: 12, ); + static TextStyle blackW40015 = TextStyle( + color: Colors.black, + fontWeight: FontWeight.w400, + fontSize: 15, + ); + static TextStyle blackW40017 = TextStyle( color: Colors.black, fontWeight: FontWeight.w400, @@ -527,6 +533,12 @@ class CustomTextStyles { fontWeight: FontWeight.w400, ); + static TextStyle raisinBlackW50015 = TextStyle( + color: ColorConstants.raisinBlack, + fontSize: 15, + fontWeight: FontWeight.w500, + ); + static TextStyle blackW60010 = TextStyle( color: Colors.black, fontWeight: FontWeight.w600, @@ -568,4 +580,10 @@ class CustomTextStyles { fontWeight: FontWeight.w500, color: ColorConstants.grey, ); + + static TextStyle dimGrayW50015 = TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: ColorConstants.dimGray, + ); } diff --git a/lib/utils/vectors.dart b/lib/utils/vectors.dart index 36e7dc6d..7c2224c4 100644 --- a/lib/utils/vectors.dart +++ b/lib/utils/vectors.dart @@ -101,4 +101,5 @@ class AppVectors { static String icPencil = '$_basePath/ic_pencil.svg'; static String icMobileImage = '$_basePath/ic_mobile_image.svg'; static String icTrustDeactivated = '$_basePath/ic_trust_deactivated.svg'; + static String icDeleteGroup = '$_basePath/ic_delete_group.svg'; } diff --git a/lib/view_models/desktop_groups_screen_provider.dart b/lib/view_models/desktop_groups_screen_provider.dart index 11da5bad..7a929123 100644 --- a/lib/view_models/desktop_groups_screen_provider.dart +++ b/lib/view_models/desktop_groups_screen_provider.dart @@ -15,6 +15,7 @@ class DesktopGroupsScreenProvider extends ChangeNotifier { bool isAddingContacts = false; Uint8List? selectedGroupImage; String? selectedGroupName; + bool showEditOptions = false; void reset() { searchGroupText = ''; @@ -23,6 +24,7 @@ class DesktopGroupsScreenProvider extends ChangeNotifier { isEditing = false; isAddingContacts = false; groupCardState = GroupCardState.disable; + showEditOptions = false; notifyListeners(); } @@ -100,4 +102,9 @@ class DesktopGroupsScreenProvider extends ChangeNotifier { notifyListeners(); } } + + void setShowEditOptionsStatus() { + showEditOptions = !showEditOptions; + notifyListeners(); + } } diff --git a/pubspec.lock b/pubspec.lock index 87839d46..47a82969 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -132,10 +132,11 @@ packages: at_onboarding_flutter: dependency: "direct main" description: - name: at_onboarding_flutter - sha256: "3d7021a3789a4fb87a04e79f7f849e22875ea13f9ff3fdef7ad246679dce0e6c" - url: "https://pub.dev" - source: hosted + path: "packages/at_onboarding_flutter" + ref: onboard_with_qr + resolved-ref: "437c720fd6fb92330da2c194536ddd83a1b7416d" + url: "https://github.com/atsign-foundation/at_widgets/" + source: git version: "6.1.7" at_persistence_secondary_server: dependency: transitive diff --git a/pubspec.yaml b/pubspec.yaml index 34993c70..d0b6829c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -102,6 +102,11 @@ dependency_overrides: # url: https://github.com/atsign-foundation/at_widgets/ # path: packages/at_contacts_group_flutter # ref: feat/group_service_restore + at_onboarding_flutter: + git: + url: https://github.com/atsign-foundation/at_widgets/ + path: packages/at_onboarding_flutter + ref: onboard_with_qr package_info_plus: ^5.0.1 file_picker: ^5.0.0 biometric_storage: 5.0.0+1