From 11d080f7215cd201f1f316a1f45e06010f0a9a76 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Fri, 24 Jan 2025 19:13:09 +0530 Subject: [PATCH] [mob][photos] Reload Contacts and AllContacts section on necessary update events --- .../lib/ui/tabs/shared/contacts_all_page.dart | 62 +++++- .../lib/ui/tabs/shared/contacts_section.dart | 183 +++++++++++++----- .../lib/ui/tabs/shared_collections_tab.dart | 24 +-- 3 files changed, 185 insertions(+), 84 deletions(-) diff --git a/mobile/lib/ui/tabs/shared/contacts_all_page.dart b/mobile/lib/ui/tabs/shared/contacts_all_page.dart index 1e978a57ec..75761fd0c6 100644 --- a/mobile/lib/ui/tabs/shared/contacts_all_page.dart +++ b/mobile/lib/ui/tabs/shared/contacts_all_page.dart @@ -4,14 +4,18 @@ 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:logging/logging.dart"; +import "package:photos/core/event_bus.dart"; +import "package:photos/events/files_updated_event.dart"; +import "package:photos/events/people_changed_event.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/debouncer.dart"; import "package:photos/utils/share_util.dart"; class ContactsSectionAllPage extends StatefulWidget { @@ -22,12 +26,37 @@ class ContactsSectionAllPage extends StatefulWidget { } class _ContactsSectionAllPageState extends State { - late Future> resutls; + late Future> results; + late final StreamSubscription + _peopleChangedEventSubscription; + final _logger = Logger("ContactsSectionAllPage"); + late StreamSubscription _filesUpdatedEvent; + final _debouncer = Debouncer( + const Duration(seconds: 2), + executionInterval: const Duration(seconds: 10), + ); @override void initState() { super.initState(); - resutls = SearchService.instance.getAllContactsSearchResults(null); + results = SearchService.instance.getAllContactsSearchResults(null); + _filesUpdatedEvent = Bus.instance.on().listen((event) { + _reloadContacts(); + }); + _peopleChangedEventSubscription = + Bus.instance.on().listen((event) { + if (event.type == PeopleEventType.saveOrEditPerson) { + _reloadContacts(); + } + }); + } + + @override + void dispose() { + _peopleChangedEventSubscription.cancel(); + _filesUpdatedEvent.cancel(); + _debouncer.cancelDebounceTimer(); + super.dispose(); } @override @@ -57,7 +86,7 @@ class _ContactsSectionAllPageState extends State { title: S.of(context).contacts, ), FutureBuilder( - future: resutls, + future: results, builder: (context, snapshot) { if (snapshot.hasData) { final sectionResults = snapshot.data!; @@ -67,9 +96,13 @@ class _ContactsSectionAllPageState extends State { duration: const Duration(milliseconds: 150), curve: Curves.easeIn, ); - } else { - return const SizedBox.shrink(); + } else if (snapshot.hasError) { + _logger + .severe("Error loading contacts: ${snapshot.error}"); } + return const RepaintBoundary( + child: EnteLoadingWidget(), + ); }, ), ], @@ -82,7 +115,7 @@ class _ContactsSectionAllPageState extends State { horizontal: 16, ), child: FutureBuilder( - future: resutls, + future: results, builder: (context, snapshot) { if (snapshot.hasData) { final sectionResults = snapshot.data!; @@ -98,8 +131,7 @@ class _ContactsSectionAllPageState extends State { return const _SearchableItemPlaceholder(); } - final result = - sectionResults[index] as GenericSearchResult; + final result = sectionResults[index]; return SearchableItemWidget( sectionResults[index], onResultTap: result.onResultTap != null @@ -136,6 +168,18 @@ class _ContactsSectionAllPageState extends State { ), ); } + + void _reloadContacts() { + _debouncer.run( + () async { + if (mounted) { + setState(() { + results = SearchService.instance.getAllContactsSearchResults(null); + }); + } + }, + ); + } } class _SearchableItemPlaceholder extends StatelessWidget { diff --git a/mobile/lib/ui/tabs/shared/contacts_section.dart b/mobile/lib/ui/tabs/shared/contacts_section.dart index 93e6437505..957436148f 100644 --- a/mobile/lib/ui/tabs/shared/contacts_section.dart +++ b/mobile/lib/ui/tabs/shared/contacts_section.dart @@ -1,78 +1,157 @@ +import "dart:async"; + import "package:dotted_border/dotted_border.dart"; import "package:flutter/material.dart"; +import "package:logging/logging.dart"; import "package:photos/core/constants.dart"; +import "package:photos/core/event_bus.dart"; +import "package:photos/events/files_updated_event.dart"; +import "package:photos/events/people_changed_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/services/search_service.dart"; import "package:photos/theme/ente_theme.dart"; +import "package:photos/ui/common/loading_widget.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/utils/debouncer.dart"; import "package:photos/utils/navigation_util.dart"; import "package:photos/utils/share_util.dart"; -class ContactsSection extends StatelessWidget { - final List contactSearchResults; - const ContactsSection(this.contactSearchResults, {super.key}); +class ContactsSection extends StatefulWidget { + const ContactsSection({super.key}); + + @override + State createState() => _ContactsSectionState(); +} + +class _ContactsSectionState extends State { + late Future> _contactSearchResults; + late final StreamSubscription + _peopleChangedEventSubscription; + final _logger = Logger("ContactsSection"); + late StreamSubscription _filesUpdatedEvent; + final _debouncer = Debouncer( + const Duration(seconds: 2), + executionInterval: const Duration(seconds: 10), + ); + + @override + void initState() { + super.initState(); + //Adding delay to avoid operation on app start + _contactSearchResults = + Future.delayed(const Duration(seconds: 2)).then((_) { + return SearchService.instance + .getAllContactsSearchResults(kSearchSectionLimit); + }); + + _filesUpdatedEvent = Bus.instance.on().listen((event) { + _reloadContacts(); + }); + _peopleChangedEventSubscription = + Bus.instance.on().listen((event) { + if (event.type == PeopleEventType.saveOrEditPerson) { + _reloadContacts(); + } + }); + } + + @override + void dispose() { + _peopleChangedEventSubscription.cancel(); + _filesUpdatedEvent.cancel(); + _debouncer.cancelDebounceTimer(); + super.dispose(); + } @override Widget build(BuildContext context) { - if (contactSearchResults.isEmpty) { - final textTheme = getEnteTextTheme(context); - return Padding( - padding: const EdgeInsets.only(left: 12, right: 8), - child: Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + return FutureBuilder>( + future: _contactSearchResults, + builder: (context, snapshot) { + if (snapshot.hasData) { + final results = snapshot.data!; + if (results.isEmpty) { + final textTheme = getEnteTextTheme(context); + return Padding( + padding: const EdgeInsets.only(left: 12, right: 8), + child: Row( children: [ - Text( - S.of(context).contacts, - style: textTheme.largeBold, - ), - const SizedBox(height: 24), - Padding( - padding: const EdgeInsets.only(left: 4), - child: Text( - S.of(context).searchPeopleEmptySection, - style: textTheme.smallMuted, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).contacts, + style: textTheme.largeBold, + ), + const SizedBox(height: 24), + Padding( + padding: const EdgeInsets.only(left: 4), + child: Text( + S.of(context).searchPeopleEmptySection, + style: textTheme.smallMuted, + ), + ), + ], ), ), + const SizedBox(width: 8), + const _ContactsSectionEmptyCTAIcon(), ], ), - ), - const SizedBox(width: 8), - const _ContactsSectionEmptyCTAIcon(), - ], - ), - ); - } else { - final recommendations = [ - ...contactSearchResults.map( - (contactSearchResult) => ContactRecommendation(contactSearchResult), - ), - const ContactCTA(), - ]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _SectionHeader( - hasMore: (contactSearchResults.length >= kSearchSectionLimit - 1), - ), - SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 4.5), - physics: const BouncingScrollPhysics(), - scrollDirection: Axis.horizontal, - child: Row( + ); + } else { + final recommendations = [ + ...results.map( + (contactSearchResult) => + ContactRecommendation(contactSearchResult), + ), + const ContactCTA(), + ]; + return Column( crossAxisAlignment: CrossAxisAlignment.start, - children: recommendations, - ), - ), - ], - ); - } + children: [ + _SectionHeader( + hasMore: (results.length >= kSearchSectionLimit - 1), + ), + SingleChildScrollView( + padding: const EdgeInsets.symmetric(horizontal: 4.5), + physics: const BouncingScrollPhysics(), + scrollDirection: Axis.horizontal, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: recommendations, + ), + ), + ], + ); + } + } else if (snapshot.hasError) { + _logger.severe("Error loading contacts: ${snapshot.error}"); + } + return const RepaintBoundary( + child: EnteLoadingWidget(), + ); + }, + ); + } + + void _reloadContacts() { + _debouncer.run( + () async { + if (mounted) { + setState(() { + _contactSearchResults = SearchService.instance + .getAllContactsSearchResults(kSearchSectionLimit); + }); + } + }, + ); } } diff --git a/mobile/lib/ui/tabs/shared_collections_tab.dart b/mobile/lib/ui/tabs/shared_collections_tab.dart index 680c75ccb8..9a01a6a48f 100644 --- a/mobile/lib/ui/tabs/shared_collections_tab.dart +++ b/mobile/lib/ui/tabs/shared_collections_tab.dart @@ -3,16 +3,13 @@ import "dart:math"; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; -import "package:photos/core/constants.dart"; import 'package:photos/core/event_bus.dart'; import 'package:photos/events/collection_updated_event.dart'; import 'package:photos/events/local_photos_updated_event.dart'; import 'package:photos/events/user_logged_out_event.dart'; import "package:photos/generated/l10n.dart"; import 'package:photos/models/collection/collection_items.dart'; -import "package:photos/models/search/generic_search_result.dart"; import 'package:photos/services/collections_service.dart'; -import "package:photos/services/search_service.dart"; import "package:photos/ui/collections/album/row_item.dart"; import "package:photos/ui/collections/collection_list_page.dart"; import 'package:photos/ui/common/loading_widget.dart'; @@ -295,26 +292,7 @@ class _SharedCollectionsTabState extends State ) : const SizedBox.shrink(), const SizedBox(height: 2), - FutureBuilder( - future: SearchService.instance - .getAllContactsSearchResults(kSearchSectionLimit), - builder: (context, snapshot) { - if (snapshot.hasData) { - return ContactsSection( - snapshot.data as List, - ); - } else if (snapshot.hasError) { - _logger.severe( - "failed to load contacts section", - snapshot.error, - snapshot.stackTrace, - ); - return const EnteLoadingWidget(); - } else { - return const EnteLoadingWidget(); - } - }, - ), + const ContactsSection(), const SizedBox(height: 4), const CollectPhotosCardWidget(), const SizedBox(height: 32),