all_sections_examples_state.dart 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import "dart:async";
  2. import "package:flutter/foundation.dart";
  3. import "package:flutter/material.dart";
  4. import "package:logging/logging.dart";
  5. import "package:photos/core/constants.dart";
  6. import "package:photos/core/event_bus.dart";
  7. import "package:photos/events/files_updated_event.dart";
  8. import "package:photos/events/people_changed_event.dart";
  9. import "package:photos/events/tab_changed_event.dart";
  10. import "package:photos/models/search/search_result.dart";
  11. import "package:photos/models/search/search_types.dart";
  12. import "package:photos/utils/debouncer.dart";
  13. class AllSectionsExamplesProvider extends StatefulWidget {
  14. final Widget child;
  15. const AllSectionsExamplesProvider({super.key, required this.child});
  16. @override
  17. State<AllSectionsExamplesProvider> createState() =>
  18. _AllSectionsExamplesProviderState();
  19. }
  20. class _AllSectionsExamplesProviderState
  21. extends State<AllSectionsExamplesProvider> {
  22. //Some section results in [allSectionsExamplesFuture] can be out of sync
  23. //with what is displayed on UI. This happens when some section is
  24. //independently listening to some set of events and is rebuilt. Sections
  25. //can listen to a list of events and rebuild (see sectionUpdateEvents()
  26. //in search_types.dart) and new results will not reflect in
  27. //[allSectionsExamplesFuture] unless reloadAllSections() is called.
  28. Future<List<List<SearchResult>>> allSectionsExamplesFuture = Future.value([]);
  29. late StreamSubscription<FilesUpdatedEvent> _filesUpdatedEvent;
  30. late StreamSubscription<PeopleChangedEvent> _onPeopleChangedEvent;
  31. late StreamSubscription<TabChangedEvent> _tabChangeEvent;
  32. bool hasPendingUpdate = false;
  33. bool isOnSearchTab = false;
  34. final _logger = Logger("AllSectionsExamplesProvider");
  35. final _debouncer = Debouncer(
  36. const Duration(seconds: 3),
  37. executionInterval: const Duration(seconds: 12),
  38. );
  39. @override
  40. void initState() {
  41. super.initState();
  42. //add all common events for all search sections to reload to here.
  43. _filesUpdatedEvent = Bus.instance.on<FilesUpdatedEvent>().listen((event) {
  44. onDataUpdate();
  45. });
  46. _onPeopleChangedEvent =
  47. Bus.instance.on<PeopleChangedEvent>().listen((event) {
  48. onDataUpdate();
  49. });
  50. _tabChangeEvent = Bus.instance.on<TabChangedEvent>().listen((event) {
  51. if (event.source == TabChangedEventSource.pageView &&
  52. event.selectedIndex == 3) {
  53. isOnSearchTab = true;
  54. if (hasPendingUpdate) {
  55. hasPendingUpdate = false;
  56. reloadAllSections();
  57. }
  58. } else {
  59. isOnSearchTab = false;
  60. }
  61. });
  62. reloadAllSections();
  63. }
  64. void onDataUpdate() {
  65. if (!isOnSearchTab) {
  66. if (kDebugMode) {
  67. _logger.finest('Skip reload till user clicks on search tab');
  68. }
  69. hasPendingUpdate = true;
  70. } else {
  71. hasPendingUpdate = false;
  72. reloadAllSections();
  73. }
  74. }
  75. void reloadAllSections() {
  76. _logger.info('queue reload all sections');
  77. _debouncer.run(() async {
  78. setState(() {
  79. _logger.info("'_debounceTimer: reloading all sections in search tab");
  80. final allSectionsExamples = <Future<List<SearchResult>>>[];
  81. for (SectionType sectionType in SectionType.values) {
  82. if (sectionType == SectionType.content) {
  83. continue;
  84. }
  85. allSectionsExamples.add(
  86. sectionType.getData(context, limit: kSearchSectionLimit),
  87. );
  88. }
  89. try {
  90. allSectionsExamplesFuture = Future.wait<List<SearchResult>>(
  91. allSectionsExamples,
  92. eagerError: false,
  93. );
  94. } catch (e) {
  95. _logger.severe("Error reloading all sections: $e");
  96. }
  97. });
  98. });
  99. }
  100. @override
  101. void dispose() {
  102. _onPeopleChangedEvent.cancel();
  103. _filesUpdatedEvent.cancel();
  104. _tabChangeEvent.cancel();
  105. _debouncer.cancelDebounce();
  106. super.dispose();
  107. }
  108. @override
  109. Widget build(BuildContext context) {
  110. return InheritedAllSectionsExamples(
  111. allSectionsExamplesFuture,
  112. _debouncer.debounceActiveNotifier,
  113. child: widget.child,
  114. );
  115. }
  116. }
  117. class InheritedAllSectionsExamples extends InheritedWidget {
  118. final Future<List<List<SearchResult>>> allSectionsExamplesFuture;
  119. final ValueNotifier<bool> isDebouncingNotifier;
  120. const InheritedAllSectionsExamples(
  121. this.allSectionsExamplesFuture,
  122. this.isDebouncingNotifier, {
  123. super.key,
  124. required super.child,
  125. });
  126. static InheritedAllSectionsExamples of(BuildContext context) {
  127. return context
  128. .dependOnInheritedWidgetOfExactType<InheritedAllSectionsExamples>()!;
  129. }
  130. @override
  131. bool updateShouldNotify(covariant InheritedAllSectionsExamples oldWidget) {
  132. return !identical(
  133. oldWidget.allSectionsExamplesFuture,
  134. allSectionsExamplesFuture,
  135. );
  136. }
  137. }