diff --git a/mobile/lib/models/search/search_types.dart b/mobile/lib/models/search/search_types.dart index a56ecbf791..90d1a320f5 100644 --- a/mobile/lib/models/search/search_types.dart +++ b/mobile/lib/models/search/search_types.dart @@ -21,6 +21,7 @@ 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, @@ -45,6 +46,8 @@ enum SectionType { // includes year, month , day, event ResultType moment, album, + // People section shows the files shared by other persons + contacts, fileTypesAndExtension, } @@ -60,6 +63,8 @@ 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: @@ -77,6 +82,8 @@ 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: @@ -96,6 +103,8 @@ extension SectionTypeExtensions on SectionType { return false; case SectionType.location: return true; + case SectionType.contacts: + return true; case SectionType.album: return true; case SectionType.fileTypesAndExtension: @@ -115,6 +124,8 @@ extension SectionTypeExtensions on SectionType { return false; case SectionType.location: return true; + case SectionType.contacts: + return true; case SectionType.album: return true; case SectionType.fileTypesAndExtension: @@ -134,6 +145,8 @@ 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: @@ -151,6 +164,8 @@ 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: @@ -160,6 +175,12 @@ 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); @@ -228,6 +249,9 @@ 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 1f1be877f2..5229082014 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, - super.key, + Key? 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 deleted file mode 100644 index 1e978a57ec..0000000000 --- a/mobile/lib/ui/tabs/shared/contacts_all_page.dart +++ /dev/null @@ -1,189 +0,0 @@ -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; - - @override - void initState() { - super.initState(); - resutls = SearchService.instance.getAllContactsSearchResults(null); - } - - @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/tabs/shared_collections_tab.dart b/mobile/lib/ui/tabs/shared_collections_tab.dart index 680c75ccb8..247783161c 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/tabs/shared/contacts_section.dart b/mobile/lib/ui/viewer/search_tab/contacts_section.dart similarity index 68% rename from mobile/lib/ui/tabs/shared/contacts_section.dart rename to mobile/lib/ui/viewer/search_tab/contacts_section.dart index 93e6437505..25662e2c1b 100644 --- a/mobile/lib/ui/tabs/shared/contacts_section.dart +++ b/mobile/lib/ui/viewer/search_tab/contacts_section.dart @@ -1,24 +1,69 @@ +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 StatelessWidget { +class ContactsSection extends StatefulWidget { 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), @@ -29,14 +74,14 @@ class ContactsSection extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - S.of(context).contacts, + SectionType.contacts.sectionTitle(context), style: textTheme.largeBold, ), const SizedBox(height: 24), Padding( padding: const EdgeInsets.only(left: 4), child: Text( - S.of(context).searchPeopleEmptySection, + SectionType.contacts.getEmptyStateText(context), style: textTheme.smallMuted, ), ), @@ -44,13 +89,13 @@ class ContactsSection extends StatelessWidget { ), ), const SizedBox(width: 8), - const _ContactsSectionEmptyCTAIcon(), + const SearchSectionEmptyCTAIcon(SectionType.contacts), ], ), ); } else { final recommendations = [ - ...contactSearchResults.map( + ..._contactSearchResults.map( (contactSearchResult) => ContactRecommendation(contactSearchResult), ), const ContactCTA(), @@ -58,8 +103,9 @@ class ContactsSection extends StatelessWidget { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - _SectionHeader( - hasMore: (contactSearchResults.length >= kSearchSectionLimit - 1), + SectionHeader( + SectionType.contacts, + hasMore: (_contactSearchResults.length >= kSearchSectionLimit - 1), ), SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 4.5), @@ -163,11 +209,7 @@ class ContactCTA extends StatelessWidget { return Padding( padding: const EdgeInsets.symmetric(horizontal: 2.5), child: GestureDetector( - onTap: () async { - await shareText( - S.of(context).shareTextRecommendUsingEnte, - ); - }, + onTap: SectionType.contacts.ctaOnTap(context), child: ConstrainedBox( constraints: const BoxConstraints( maxHeight: double.infinity, @@ -211,96 +253,3 @@ 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/viewer/search_tab/search_tab.dart b/mobile/lib/ui/viewer/search_tab/search_tab.dart index d21becdcbf..ae5d8ed7ad 100644 --- a/mobile/lib/ui/viewer/search_tab/search_tab.dart +++ b/mobile/lib/ui/viewer/search_tab/search_tab.dart @@ -143,6 +143,12 @@ 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 28219c70d9..0cba96166c 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.dev" source: hosted - version: "72.0.0" + version: "76.0.0" _flutterfire_internals: dependency: transitive description: @@ -21,7 +21,7 @@ packages: dependency: transitive description: dart source: sdk - version: "0.3.2" + version: "0.3.3" adaptive_theme: dependency: "direct main" description: @@ -34,10 +34,10 @@ packages: dependency: transitive description: name: analyzer - sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.dev" source: hosted - version: "6.7.0" + version: "6.11.0" android_intent_plus: dependency: "direct main" description: @@ -284,10 +284,10 @@ packages: dependency: "direct main" description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" computer: dependency: "direct main" description: @@ -585,11 +585,12 @@ packages: figma_squircle: dependency: "direct main" description: - name: figma_squircle - sha256: "790b91a9505e90d246f6efe2fa065ff7fffe658c7b44fe9b5b20c7b0ad3818c0" - url: "https://pub.dev" - source: hosted - version: "0.5.3" + path: "." + ref: HEAD + resolved-ref: "5f1ad5aaccdf31fc398fc141979ea845a0f45383" + url: "https://github.com/Ax0elz/figma_squircle.git" + source: git + version: "0.5.5" file: dependency: transitive description: @@ -1358,18 +1359,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.5" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -1486,10 +1487,10 @@ packages: dependency: transitive description: name: macros - sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" url: "https://pub.dev" source: hosted - version: "0.1.2-main.4" + version: "0.1.3-main.0" maps_launcher: dependency: "direct main" description: @@ -2292,7 +2293,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_gen: dependency: transitive description: @@ -2417,10 +2418,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" step_progress_indicator: dependency: "direct main" description: @@ -2449,10 +2450,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" styled_text: dependency: "direct main" description: @@ -2513,26 +2514,26 @@ packages: dependency: "direct dev" description: name: test - sha256: "7ee44229615f8f642b68120165ae4c2a75fe77ae2065b1e55ae4711f6cf0899e" + sha256: "713a8789d62f3233c46b4a90b174737b2c04cb6ae4500f2aa8b1be8f03f5e67f" url: "https://pub.dev" source: hosted - version: "1.25.7" + version: "1.25.8" test_api: dependency: transitive description: name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.7.3" test_core: dependency: transitive description: name: test_core - sha256: "55ea5a652e38a1dfb32943a7973f3681a60f872f8c3a05a14664ad54ef9c6696" + sha256: "12391302411737c176b0b5d6491f466b0dd56d4763e347b6714efbaa74d7953d" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" timezone: dependency: transitive description: @@ -2819,10 +2820,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.5" + version: "14.3.0" volume_controller: dependency: transitive description: @@ -2891,10 +2892,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" + sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.4" webkit_inspection_protocol: dependency: transitive description: