diff --git a/mobile/lib/models/search/search_types.dart b/mobile/lib/models/search/search_types.dart index 90d1a320f5..a56ecbf791 100644 --- a/mobile/lib/models/search/search_types.dart +++ b/mobile/lib/models/search/search_types.dart @@ -21,7 +21,6 @@ import "package:photos/ui/viewer/location/add_location_sheet.dart"; import "package:photos/ui/viewer/location/pick_center_point_widget.dart"; import "package:photos/utils/dialog_util.dart"; import "package:photos/utils/navigation_util.dart"; -import "package:photos/utils/share_util.dart"; enum ResultType { collection, @@ -46,8 +45,6 @@ enum SectionType { // includes year, month , day, event ResultType moment, album, - // People section shows the files shared by other persons - contacts, fileTypesAndExtension, } @@ -63,8 +60,6 @@ extension SectionTypeExtensions on SectionType { return S.of(context).moments; case SectionType.location: return S.of(context).locations; - case SectionType.contacts: - return S.of(context).contacts; case SectionType.album: return S.of(context).albums; case SectionType.fileTypesAndExtension: @@ -82,8 +77,6 @@ extension SectionTypeExtensions on SectionType { return S.of(context).searchDatesEmptySection; case SectionType.location: return S.of(context).searchLocationEmptySection; - case SectionType.contacts: - return S.of(context).searchPeopleEmptySection; case SectionType.album: return S.of(context).searchAlbumsEmptySection; case SectionType.fileTypesAndExtension: @@ -103,8 +96,6 @@ extension SectionTypeExtensions on SectionType { return false; case SectionType.location: return true; - case SectionType.contacts: - return true; case SectionType.album: return true; case SectionType.fileTypesAndExtension: @@ -124,8 +115,6 @@ extension SectionTypeExtensions on SectionType { return false; case SectionType.location: return true; - case SectionType.contacts: - return true; case SectionType.album: return true; case SectionType.fileTypesAndExtension: @@ -145,8 +134,6 @@ extension SectionTypeExtensions on SectionType { return S.of(context).addNew; case SectionType.location: return S.of(context).addNew; - case SectionType.contacts: - return S.of(context).invite; case SectionType.album: return S.of(context).addNew; case SectionType.fileTypesAndExtension: @@ -164,8 +151,6 @@ extension SectionTypeExtensions on SectionType { return null; case SectionType.location: return Icons.add_location_alt_outlined; - case SectionType.contacts: - return Icons.adaptive.share; case SectionType.album: return Icons.add; case SectionType.fileTypesAndExtension: @@ -175,12 +160,6 @@ extension SectionTypeExtensions on SectionType { FutureVoidCallback ctaOnTap(BuildContext context) { switch (this) { - case SectionType.contacts: - return () async { - await shareText( - S.of(context).shareTextRecommendUsingEnte, - ); - }; case SectionType.location: return () async { final centerPoint = await showPickCenterPointSheet(context); @@ -249,9 +228,6 @@ extension SectionTypeExtensions on SectionType { case SectionType.location: return SearchService.instance.getAllLocationTags(limit); - case SectionType.contacts: - return SearchService.instance.getAllContactsSearchResults(limit); - case SectionType.album: return SearchService.instance.getAllCollectionSearchResults(limit); diff --git a/mobile/lib/ui/tabs/section_title.dart b/mobile/lib/ui/tabs/section_title.dart index 5229082014..1f1be877f2 100644 --- a/mobile/lib/ui/tabs/section_title.dart +++ b/mobile/lib/ui/tabs/section_title.dart @@ -14,9 +14,9 @@ class SectionTitle extends StatelessWidget { this.title, this.titleWithBrand, this.mutedTitle = false, - Key? key, + super.key, this.padding, - }) : super(key: key); + }); @override Widget build(BuildContext context) { diff --git a/mobile/lib/ui/tabs/shared/contacts_all_page.dart b/mobile/lib/ui/tabs/shared/contacts_all_page.dart new file mode 100644 index 0000000000..4b1834e44c --- /dev/null +++ b/mobile/lib/ui/tabs/shared/contacts_all_page.dart @@ -0,0 +1,198 @@ +import "dart:async"; + +import "package:collection/collection.dart"; +import "package:dotted_border/dotted_border.dart"; +import "package:flutter/material.dart"; +import "package:flutter_animate/flutter_animate.dart"; +import "package:photos/generated/l10n.dart"; +import "package:photos/models/search/generic_search_result.dart"; +import "package:photos/models/search/search_result.dart"; +import "package:photos/services/search_service.dart"; +import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/common/loading_widget.dart"; +import "package:photos/ui/components/title_bar_title_widget.dart"; +import "package:photos/ui/viewer/search/result/searchable_item.dart"; +import "package:photos/utils/share_util.dart"; + +class ContactsSectionAllPage extends StatefulWidget { + const ContactsSectionAllPage({super.key}); + + @override + State createState() => _ContactsSectionAllPageState(); +} + +class _ContactsSectionAllPageState extends State { + late Future> resutls; + final streamSubscriptions = []; + + @override + void initState() { + super.initState(); + resutls = SearchService.instance.getAllContactsSearchResults(null); + } + + @override + void dispose() { + for (var subscriptions in streamSubscriptions) { + subscriptions.cancel(); + } + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + toolbarHeight: 48, + leadingWidth: 48, + leading: GestureDetector( + onTap: () { + Navigator.pop(context); + }, + child: const Icon( + Icons.arrow_back_outlined, + ), + ), + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TitleBarTitleWidget( + title: S.of(context).contacts, + ), + FutureBuilder( + future: resutls, + builder: (context, snapshot) { + if (snapshot.hasData) { + final sectionResults = snapshot.data!; + return Text(sectionResults.length.toString()) + .animate() + .fadeIn( + duration: const Duration(milliseconds: 150), + curve: Curves.easeIn, + ); + } else { + return const SizedBox.shrink(); + } + }, + ), + ], + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 20, + horizontal: 16, + ), + child: FutureBuilder( + future: resutls, + builder: (context, snapshot) { + if (snapshot.hasData) { + final sectionResults = snapshot.data!; + + sectionResults.sort( + (a, b) => + compareAsciiLowerCaseNatural(b.name(), a.name()), + ); + + return ListView.separated( + itemBuilder: (context, index) { + if (sectionResults.length == index) { + return const _SearchableItemPlaceholder(); + } + + final result = + sectionResults[index] as GenericSearchResult; + return SearchableItemWidget( + sectionResults[index], + onResultTap: result.onResultTap != null + ? () => result.onResultTap!(context) + : null, + ); + }, + separatorBuilder: (context, index) { + return const SizedBox(height: 10); + }, + itemCount: sectionResults.length + 1, + physics: const BouncingScrollPhysics(), + ) + .animate() + .fadeIn( + duration: const Duration(milliseconds: 225), + curve: Curves.easeIn, + ) + .slide( + begin: const Offset(0, -0.01), + curve: Curves.easeIn, + duration: const Duration( + milliseconds: 225, + ), + ); + } else { + return const EnteLoadingWidget(); + } + }, + ), + ), + ), + ], + ), + ); + } +} + +class _SearchableItemPlaceholder extends StatelessWidget { + const _SearchableItemPlaceholder(); + + @override + Widget build(BuildContext context) { + final colorScheme = getEnteColorScheme(context); + final textTheme = getEnteTextTheme(context); + return Padding( + padding: const EdgeInsets.only(right: 1), + child: GestureDetector( + onTap: () async { + await shareText( + S.of(context).shareTextRecommendUsingEnte, + ); + }, + child: DottedBorder( + strokeWidth: 2, + borderType: BorderType.RRect, + radius: const Radius.circular(4), + padding: EdgeInsets.zero, + dashPattern: const [4, 4], + color: colorScheme.strokeFainter, + child: Row( + children: [ + Container( + width: 60, + height: 60, + decoration: BoxDecoration( + borderRadius: + const BorderRadius.horizontal(left: Radius.circular(4)), + color: colorScheme.fillFaint, + ), + child: Icon( + Icons.adaptive.share, + color: colorScheme.strokeMuted, + ), + ), + const SizedBox(width: 12), + Text( + S.of(context).invite, + style: textTheme.body, + ), + ], + ), + ), + ), + ); + } +} diff --git a/mobile/lib/ui/viewer/search_tab/contacts_section.dart b/mobile/lib/ui/tabs/shared/contacts_section.dart similarity index 68% rename from mobile/lib/ui/viewer/search_tab/contacts_section.dart rename to mobile/lib/ui/tabs/shared/contacts_section.dart index 25662e2c1b..93e6437505 100644 --- a/mobile/lib/ui/viewer/search_tab/contacts_section.dart +++ b/mobile/lib/ui/tabs/shared/contacts_section.dart @@ -1,69 +1,24 @@ -import "dart:async"; - import "package:dotted_border/dotted_border.dart"; import "package:flutter/material.dart"; import "package:photos/core/constants.dart"; -import "package:photos/events/event.dart"; import "package:photos/generated/l10n.dart"; import "package:photos/models/search/generic_search_result.dart"; import "package:photos/models/search/recent_searches.dart"; -import "package:photos/models/search/search_types.dart"; import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/tabs/shared/contacts_all_page.dart"; import "package:photos/ui/viewer/file/no_thumbnail_widget.dart"; import "package:photos/ui/viewer/file/thumbnail_widget.dart"; import "package:photos/ui/viewer/search/result/contact_result_page.dart"; -import "package:photos/ui/viewer/search/search_section_cta.dart"; -import "package:photos/ui/viewer/search_tab/section_header.dart"; import "package:photos/utils/navigation_util.dart"; +import "package:photos/utils/share_util.dart"; -class ContactsSection extends StatefulWidget { +class ContactsSection extends StatelessWidget { final List contactSearchResults; const ContactsSection(this.contactSearchResults, {super.key}); - @override - State createState() => _ContactsSectionState(); -} - -class _ContactsSectionState extends State { - late List _contactSearchResults; - final streamSubscriptions = []; - - @override - void initState() { - super.initState(); - _contactSearchResults = widget.contactSearchResults; - - final streamsToListenTo = SectionType.contacts.sectionUpdateEvents(); - for (Stream stream in streamsToListenTo) { - streamSubscriptions.add( - stream.listen((event) async { - _contactSearchResults = (await SectionType.contacts.getData( - context, - limit: kSearchSectionLimit, - )) as List; - setState(() {}); - }), - ); - } - } - - @override - void dispose() { - for (var subscriptions in streamSubscriptions) { - subscriptions.cancel(); - } - super.dispose(); - } - - @override - void didUpdateWidget(covariant ContactsSection oldWidget) { - super.didUpdateWidget(oldWidget); - _contactSearchResults = widget.contactSearchResults; - } - @override Widget build(BuildContext context) { - if (_contactSearchResults.isEmpty) { + if (contactSearchResults.isEmpty) { final textTheme = getEnteTextTheme(context); return Padding( padding: const EdgeInsets.only(left: 12, right: 8), @@ -74,14 +29,14 @@ class _ContactsSectionState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - SectionType.contacts.sectionTitle(context), + S.of(context).contacts, style: textTheme.largeBold, ), const SizedBox(height: 24), Padding( padding: const EdgeInsets.only(left: 4), child: Text( - SectionType.contacts.getEmptyStateText(context), + S.of(context).searchPeopleEmptySection, style: textTheme.smallMuted, ), ), @@ -89,13 +44,13 @@ class _ContactsSectionState extends State { ), ), const SizedBox(width: 8), - const SearchSectionEmptyCTAIcon(SectionType.contacts), + const _ContactsSectionEmptyCTAIcon(), ], ), ); } else { final recommendations = [ - ..._contactSearchResults.map( + ...contactSearchResults.map( (contactSearchResult) => ContactRecommendation(contactSearchResult), ), const ContactCTA(), @@ -103,9 +58,8 @@ class _ContactsSectionState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SectionHeader( - SectionType.contacts, - hasMore: (_contactSearchResults.length >= kSearchSectionLimit - 1), + _SectionHeader( + hasMore: (contactSearchResults.length >= kSearchSectionLimit - 1), ), SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 4.5), @@ -209,7 +163,11 @@ class ContactCTA extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric(horizontal: 2.5), child: GestureDetector( - onTap: SectionType.contacts.ctaOnTap(context), + onTap: () async { + await shareText( + S.of(context).shareTextRecommendUsingEnte, + ); + }, child: ConstrainedBox( constraints: const BoxConstraints( maxHeight: double.infinity, @@ -253,3 +211,96 @@ class ContactCTA extends StatelessWidget { ); } } + +class _ContactsSectionEmptyCTAIcon extends StatelessWidget { + const _ContactsSectionEmptyCTAIcon(); + + @override + Widget build(BuildContext context) { + final textTheme = getEnteTextTheme(context); + final colorScheme = getEnteColorScheme(context); + return GestureDetector( + onTap: () async { + await shareText( + S.of(context).shareTextRecommendUsingEnte, + ); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 14, 8, 0), + child: Column( + children: [ + DottedBorder( + color: colorScheme.strokeFaint, + dashPattern: const [3.875, 3.875], + borderType: BorderType.Circle, + strokeWidth: 1.5, + radius: const Radius.circular(33.25), + child: SizedBox( + width: 62.5, + height: 62.5, + child: Icon( + Icons.adaptive.share, + color: colorScheme.strokeFaint, + size: 20, + ), + ), + ), + const SizedBox( + height: 10, + ), + Text( + S.of(context).invite, + maxLines: 2, + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + style: textTheme.miniFaint, + ), + ], + ), + ), + ); + } +} + +class _SectionHeader extends StatelessWidget { + final bool hasMore; + const _SectionHeader({required this.hasMore}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + if (hasMore) { + routeToPage( + context, + const ContactsSectionAllPage(), + ); + } + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(12), + child: Text( + S.of(context).contacts, + style: getEnteTextTheme(context).largeBold, + ), + ), + hasMore + ? Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.fromLTRB(24, 12, 12, 12), + child: Icon( + Icons.chevron_right_outlined, + color: getEnteColorScheme(context).strokeMuted, + ), + ), + ) + : const SizedBox.shrink(), + ], + ), + ); + } +} diff --git a/mobile/lib/ui/tabs/shared_collections_tab.dart b/mobile/lib/ui/tabs/shared_collections_tab.dart index 247783161c..680c75ccb8 100644 --- a/mobile/lib/ui/tabs/shared_collections_tab.dart +++ b/mobile/lib/ui/tabs/shared_collections_tab.dart @@ -19,11 +19,11 @@ import 'package:photos/ui/common/loading_widget.dart'; import "package:photos/ui/components/buttons/icon_button_widget.dart"; import 'package:photos/ui/tabs/section_title.dart'; import "package:photos/ui/tabs/shared/all_quick_links_page.dart"; +import "package:photos/ui/tabs/shared/contacts_section.dart"; import "package:photos/ui/tabs/shared/empty_state.dart"; import "package:photos/ui/tabs/shared/quick_link_album_item.dart"; import "package:photos/ui/viewer/gallery/collect_photos_card_widget.dart"; import "package:photos/ui/viewer/gallery/collection_page.dart"; -import "package:photos/ui/viewer/search_tab/contacts_section.dart"; import "package:photos/utils/debouncer.dart"; import "package:photos/utils/navigation_util.dart"; diff --git a/mobile/lib/ui/viewer/search_tab/search_tab.dart b/mobile/lib/ui/viewer/search_tab/search_tab.dart index ae5d8ed7ad..d21becdcbf 100644 --- a/mobile/lib/ui/viewer/search_tab/search_tab.dart +++ b/mobile/lib/ui/viewer/search_tab/search_tab.dart @@ -143,12 +143,6 @@ class _AllSearchSectionsState extends State { snapshot.data!.elementAt(index) as List, ); - case SectionType.contacts: - // return ContactsSection( - // snapshot.data!.elementAt(index) - // as List, - // ); - return const SizedBox.shrink(); case SectionType.fileTypesAndExtension: return FileTypeSection( snapshot.data!.elementAt(index) diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 0cba96166c..28219c70d9 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "76.0.0" + version: "72.0.0" _flutterfire_internals: dependency: transitive description: @@ -21,7 +21,7 @@ packages: dependency: transitive description: dart source: sdk - version: "0.3.3" + version: "0.3.2" adaptive_theme: dependency: "direct main" description: @@ -34,10 +34,10 @@ packages: dependency: transitive description: name: analyzer - sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.11.0" + version: "6.7.0" android_intent_plus: dependency: "direct main" description: @@ -284,10 +284,10 @@ packages: dependency: "direct main" description: name: collection - sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.0" + version: "1.18.0" computer: dependency: "direct main" description: @@ -585,12 +585,11 @@ packages: figma_squircle: dependency: "direct main" description: - path: "." - ref: HEAD - resolved-ref: "5f1ad5aaccdf31fc398fc141979ea845a0f45383" - url: "https://github.com/Ax0elz/figma_squircle.git" - source: git - version: "0.5.5" + name: figma_squircle + sha256: "790b91a9505e90d246f6efe2fa065ff7fffe658c7b44fe9b5b20c7b0ad3818c0" + url: "https://pub.dev" + source: hosted + version: "0.5.3" file: dependency: transitive description: @@ -1359,18 +1358,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.7" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.8" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -1487,10 +1486,10 @@ packages: dependency: transitive description: name: macros - sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" url: "https://pub.dev" source: hosted - version: "0.1.3-main.0" + version: "0.1.2-main.4" maps_launcher: dependency: "direct main" description: @@ -2293,7 +2292,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" source_gen: dependency: transitive description: @@ -2418,10 +2417,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.11.1" step_progress_indicator: dependency: "direct main" description: @@ -2450,10 +2449,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.2.0" styled_text: dependency: "direct main" description: @@ -2514,26 +2513,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" + sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" url: "https://pub.dev" source: hosted - version: "1.25.8" + version: "1.25.7" test_api: dependency: transitive description: name: test_api - sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.3" + version: "0.7.2" test_core: dependency: transitive description: name: test_core - sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" + sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.4" timezone: dependency: transitive description: @@ -2820,10 +2819,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.3.0" + version: "14.2.5" volume_controller: dependency: transitive description: @@ -2892,10 +2891,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.3" webkit_inspection_protocol: dependency: transitive description: