Use debouncer for home, owned and shared albums tabs and gallery. (#1554)

This commit is contained in:
Ashil 2023-12-01 16:04:09 +05:30 committed by GitHub
commit bdac20a711
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 99 additions and 33 deletions

View file

@ -37,8 +37,8 @@ class _AllSectionsExamplesProviderState
final _logger = Logger("AllSectionsExamplesProvider"); final _logger = Logger("AllSectionsExamplesProvider");
final _debouncer = Debouncer( final _debouncer = Debouncer(
const Duration(seconds: 4), const Duration(seconds: 3),
executionIntervalInMilliSeconds: 15000, executionInterval: const Duration(seconds: 12),
); );
@override @override

View file

@ -12,6 +12,7 @@ import 'package:photos/models/device_collection.dart';
import "package:photos/ui/collections/device/device_folder_item.dart"; import "package:photos/ui/collections/device/device_folder_item.dart";
import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/ui/common/loading_widget.dart';
import 'package:photos/ui/viewer/gallery/empty_state.dart'; import 'package:photos/ui/viewer/gallery/empty_state.dart';
import "package:photos/utils/debouncer.dart";
class DeviceFoldersGridView extends StatefulWidget { class DeviceFoldersGridView extends StatefulWidget {
const DeviceFoldersGridView({ const DeviceFoldersGridView({
@ -27,6 +28,10 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
StreamSubscription<LocalPhotosUpdatedEvent>? _localFilesSubscription; StreamSubscription<LocalPhotosUpdatedEvent>? _localFilesSubscription;
String _loadReason = "init"; String _loadReason = "init";
final _logger = Logger((_DeviceFoldersGridViewState).toString()); final _logger = Logger((_DeviceFoldersGridViewState).toString());
final _debouncer = Debouncer(
const Duration(seconds: 2),
executionInterval: const Duration(seconds: 5),
);
@override @override
void initState() { void initState() {
@ -39,8 +44,12 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
}); });
_localFilesSubscription = _localFilesSubscription =
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) { Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
_loadReason = event.reason; _debouncer.run(() async {
setState(() {}); if (mounted) {
_loadReason = event.reason;
setState(() {});
}
});
}); });
super.initState(); super.initState();
@ -93,6 +102,7 @@ class _DeviceFoldersGridViewState extends State<DeviceFoldersGridView> {
void dispose() { void dispose() {
_backupFoldersUpdatedEvent?.cancel(); _backupFoldersUpdatedEvent?.cancel();
_localFilesSubscription?.cancel(); _localFilesSubscription?.cancel();
_debouncer.cancelDebounce();
super.dispose(); super.dispose();
} }
} }

View file

@ -13,6 +13,7 @@ import 'package:photos/models/device_collection.dart';
import "package:photos/ui/collections/device/device_folder_item.dart"; import "package:photos/ui/collections/device/device_folder_item.dart";
import 'package:photos/ui/common/loading_widget.dart'; import 'package:photos/ui/common/loading_widget.dart';
import 'package:photos/ui/viewer/gallery/empty_state.dart'; import 'package:photos/ui/viewer/gallery/empty_state.dart';
import "package:photos/utils/debouncer.dart";
class DeviceFolderVerticalGridView extends StatelessWidget { class DeviceFolderVerticalGridView extends StatelessWidget {
final Widget? appTitle; final Widget? appTitle;
@ -58,6 +59,10 @@ class _DeviceFolderVerticalGridViewBodyState
StreamSubscription<LocalPhotosUpdatedEvent>? _localFilesSubscription; StreamSubscription<LocalPhotosUpdatedEvent>? _localFilesSubscription;
String _loadReason = "init"; String _loadReason = "init";
final logger = Logger((_DeviceFolderVerticalGridViewBodyState).toString()); final logger = Logger((_DeviceFolderVerticalGridViewBodyState).toString());
final _debouncer = Debouncer(
const Duration(milliseconds: 1500),
executionInterval: const Duration(seconds: 4),
);
/* /*
Aspect ratio 1:1 Max width 224 Fixed gap 8 Aspect ratio 1:1 Max width 224 Fixed gap 8
Width changes dynamically with screen width such that we can fit 2 in one row. Width changes dynamically with screen width such that we can fit 2 in one row.
@ -78,8 +83,12 @@ class _DeviceFolderVerticalGridViewBodyState
}); });
_localFilesSubscription = _localFilesSubscription =
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) { Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
_loadReason = event.reason; _debouncer.run(() async {
setState(() {}); if (mounted) {
_loadReason = event.reason;
setState(() {});
}
});
}); });
super.initState(); super.initState();
} }
@ -149,6 +158,7 @@ class _DeviceFolderVerticalGridViewBodyState
void dispose() { void dispose() {
_backupFoldersUpdatedEvent?.cancel(); _backupFoldersUpdatedEvent?.cancel();
_localFilesSubscription?.cancel(); _localFilesSubscription?.cancel();
_debouncer.cancelDebounce();
super.dispose(); super.dispose();
} }
} }

View file

@ -81,6 +81,8 @@ class HomeGalleryWidget extends StatelessWidget {
footer: footer, footer: footer,
// scrollSafe area -> SafeArea + Preserver more + Nav Bar buttons // scrollSafe area -> SafeArea + Preserver more + Nav Bar buttons
scrollBottomSafeArea: bottomSafeArea + 180, scrollBottomSafeArea: bottomSafeArea + 180,
reloadDebounceTime: const Duration(seconds: 2),
reloadDebounceExecutionInterval: const Duration(seconds: 5),
); );
return Stack( return Stack(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,

View file

@ -42,7 +42,7 @@ class _MapViewState extends State<MapView> {
late List<Marker> _markers; late List<Marker> _markers;
final _debouncer = Debouncer( final _debouncer = Debouncer(
const Duration(milliseconds: 300), const Duration(milliseconds: 300),
executionIntervalInMilliSeconds: 750, executionInterval: const Duration(milliseconds: 750),
); );
@override @override

View file

@ -19,6 +19,7 @@ import "package:photos/ui/components/models/button_type.dart";
import 'package:photos/ui/tabs/section_title.dart'; import 'package:photos/ui/tabs/section_title.dart';
import "package:photos/ui/tabs/shared/empty_state.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/tabs/shared/quick_link_album_item.dart";
import "package:photos/utils/debouncer.dart";
import "package:photos/utils/navigation_util.dart"; import "package:photos/utils/navigation_util.dart";
import "package:photos/utils/share_util.dart"; import "package:photos/utils/share_util.dart";
@ -36,18 +37,31 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
late StreamSubscription<CollectionUpdatedEvent> late StreamSubscription<CollectionUpdatedEvent>
_collectionUpdatesSubscription; _collectionUpdatesSubscription;
late StreamSubscription<UserLoggedOutEvent> _loggedOutEvent; late StreamSubscription<UserLoggedOutEvent> _loggedOutEvent;
final _debouncer = Debouncer(
const Duration(seconds: 2),
executionInterval: const Duration(seconds: 5),
);
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_localFilesSubscription = _localFilesSubscription =
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) { Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
debugPrint("SetState Shared Collections on ${event.reason}"); _debouncer.run(() async {
setState(() {}); if (mounted) {
debugPrint("SetState Shared Collections on ${event.reason}");
setState(() {});
}
});
}); });
_collectionUpdatesSubscription = _collectionUpdatesSubscription =
Bus.instance.on<CollectionUpdatedEvent>().listen((event) { Bus.instance.on<CollectionUpdatedEvent>().listen((event) {
setState(() {}); _debouncer.run(() async {
if (mounted) {
debugPrint("SetState Shared Collections on ${event.reason}");
setState(() {});
}
});
}); });
_loggedOutEvent = Bus.instance.on<UserLoggedOutEvent>().listen((event) { _loggedOutEvent = Bus.instance.on<UserLoggedOutEvent>().listen((event) {
setState(() {}); setState(() {});
@ -262,6 +276,7 @@ class _SharedCollectionsTabState extends State<SharedCollectionsTab>
_localFilesSubscription.cancel(); _localFilesSubscription.cancel();
_collectionUpdatesSubscription.cancel(); _collectionUpdatesSubscription.cancel();
_loggedOutEvent.cancel(); _loggedOutEvent.cancel();
_debouncer.cancelDebounce();
super.dispose(); super.dispose();
} }

View file

@ -24,6 +24,7 @@ import 'package:photos/ui/components/buttons/icon_button_widget.dart';
import "package:photos/ui/tabs/section_title.dart"; import "package:photos/ui/tabs/section_title.dart";
import "package:photos/ui/viewer/actions/delete_empty_albums.dart"; import "package:photos/ui/viewer/actions/delete_empty_albums.dart";
import "package:photos/ui/viewer/gallery/empty_state.dart"; import "package:photos/ui/viewer/gallery/empty_state.dart";
import "package:photos/utils/debouncer.dart";
import 'package:photos/utils/local_settings.dart'; import 'package:photos/utils/local_settings.dart';
import "package:photos/utils/navigation_util.dart"; import "package:photos/utils/navigation_util.dart";
@ -44,19 +45,31 @@ class _UserCollectionsTabState extends State<UserCollectionsTab>
AlbumSortKey? sortKey; AlbumSortKey? sortKey;
String _loadReason = "init"; String _loadReason = "init";
final _scrollController = ScrollController(); final _scrollController = ScrollController();
final _debouncer = Debouncer(
const Duration(seconds: 2),
executionInterval: const Duration(seconds: 5),
);
static const int _kOnEnteItemLimitCount = 10; static const int _kOnEnteItemLimitCount = 10;
@override @override
void initState() { void initState() {
_localFilesSubscription = _localFilesSubscription =
Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) { Bus.instance.on<LocalPhotosUpdatedEvent>().listen((event) {
_loadReason = event.reason; _debouncer.run(() async {
setState(() {}); if (mounted) {
_loadReason = event.reason;
setState(() {});
}
});
}); });
_collectionUpdatesSubscription = _collectionUpdatesSubscription =
Bus.instance.on<CollectionUpdatedEvent>().listen((event) { Bus.instance.on<CollectionUpdatedEvent>().listen((event) {
_loadReason = event.reason; _debouncer.run(() async {
setState(() {}); if (mounted) {
_loadReason = event.reason;
setState(() {});
}
});
}); });
_loggedOutEvent = Bus.instance.on<UserLoggedOutEvent>().listen((event) { _loggedOutEvent = Bus.instance.on<UserLoggedOutEvent>().listen((event) {
_loadReason = event.reason; _loadReason = event.reason;
@ -268,6 +281,7 @@ class _UserCollectionsTabState extends State<UserCollectionsTab>
_collectionUpdatesSubscription.cancel(); _collectionUpdatesSubscription.cancel();
_loggedOutEvent.cancel(); _loggedOutEvent.cancel();
_scrollController.dispose(); _scrollController.dispose();
_debouncer.cancelDebounce();
super.dispose(); super.dispose();
} }

View file

@ -16,6 +16,7 @@ import "package:photos/ui/viewer/gallery/component/multiple_groups_gallery_view.
import 'package:photos/ui/viewer/gallery/empty_state.dart'; import 'package:photos/ui/viewer/gallery/empty_state.dart';
import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart"; import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart";
import 'package:photos/utils/date_time_util.dart'; import 'package:photos/utils/date_time_util.dart';
import "package:photos/utils/debouncer.dart";
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
typedef GalleryLoader = Future<FileLoadResult> Function( typedef GalleryLoader = Future<FileLoadResult> Function(
@ -43,6 +44,8 @@ class Gallery extends StatefulWidget {
final bool enableFileGrouping; final bool enableFileGrouping;
final Widget loadingWidget; final Widget loadingWidget;
final bool disableScroll; final bool disableScroll;
final Duration reloadDebounceTime;
final Duration reloadDebounceExecutionInterval;
/// When true, selection will be limited to one item. Tapping on any item /// When true, selection will be limited to one item. Tapping on any item
/// will select even when no other item is selected. /// will select even when no other item is selected.
@ -78,6 +81,8 @@ class Gallery extends StatefulWidget {
this.sortAsyncFn, this.sortAsyncFn,
this.showSelectAllByDefault = true, this.showSelectAllByDefault = true,
this.isScrollablePositionedList = true, this.isScrollablePositionedList = true,
this.reloadDebounceTime = const Duration(milliseconds: 500),
this.reloadDebounceExecutionInterval = const Duration(seconds: 2),
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -89,6 +94,7 @@ class Gallery extends StatefulWidget {
class GalleryState extends State<Gallery> { class GalleryState extends State<Gallery> {
static const int kInitialLoadLimit = 100; static const int kInitialLoadLimit = 100;
late final Debouncer _debouncer;
late Logger _logger; late Logger _logger;
List<List<EnteFile>> currentGroupedFiles = []; List<List<EnteFile>> currentGroupedFiles = [];
@ -106,20 +112,26 @@ class GalleryState extends State<Gallery> {
"Gallery_${widget.tagPrefix}${kDebugMode ? "_" + widget.albumName! : ""}"; "Gallery_${widget.tagPrefix}${kDebugMode ? "_" + widget.albumName! : ""}";
_logger = Logger(_logTag); _logger = Logger(_logTag);
_logger.finest("init Gallery"); _logger.finest("init Gallery");
_debouncer = Debouncer(
widget.reloadDebounceTime,
executionInterval: widget.reloadDebounceExecutionInterval,
);
_sortOrderAsc = widget.sortAsyncFn != null ? widget.sortAsyncFn!() : false; _sortOrderAsc = widget.sortAsyncFn != null ? widget.sortAsyncFn!() : false;
_itemScroller = ItemScrollController(); _itemScroller = ItemScrollController();
if (widget.reloadEvent != null) { if (widget.reloadEvent != null) {
_reloadEventSubscription = widget.reloadEvent!.listen((event) async { _reloadEventSubscription = widget.reloadEvent!.listen((event) async {
// In soft refresh, setState is called for entire gallery only when _debouncer.run(() async {
// number of child change // In soft refresh, setState is called for entire gallery only when
_logger.finest("Soft refresh all files on ${event.reason} "); // number of child change
final result = await _loadFiles(); _logger.finest("Soft refresh all files on ${event.reason} ");
final bool hasReloaded = _onFilesLoaded(result.files); final result = await _loadFiles();
if (hasReloaded && kDebugMode) { final bool hasReloaded = _onFilesLoaded(result.files);
_logger.finest( if (hasReloaded && kDebugMode) {
"Reloaded gallery on soft refresh all files on ${event.reason}", _logger.finest(
); "Reloaded gallery on soft refresh all files on ${event.reason}",
} );
}
});
}); });
} }
_tabDoubleTapEvent = _tabDoubleTapEvent =
@ -137,11 +149,13 @@ class GalleryState extends State<Gallery> {
for (final event in widget.forceReloadEvents!) { for (final event in widget.forceReloadEvents!) {
_forceReloadEventSubscriptions.add( _forceReloadEventSubscriptions.add(
event.listen((event) async { event.listen((event) async {
_logger.finest("Force refresh all files on ${event.reason}"); _debouncer.run(() async {
_sortOrderAsc = _logger.finest("Force refresh all files on ${event.reason}");
widget.sortAsyncFn != null ? widget.sortAsyncFn!() : false; _sortOrderAsc =
final result = await _loadFiles(); widget.sortAsyncFn != null ? widget.sortAsyncFn!() : false;
_setFilesAndReload(result.files); final result = await _loadFiles();
_setFilesAndReload(result.files);
});
}), }),
); );
} }
@ -219,6 +233,7 @@ class GalleryState extends State<Gallery> {
for (final subscription in _forceReloadEventSubscriptions) { for (final subscription in _forceReloadEventSubscriptions) {
subscription.cancel(); subscription.cancel();
} }
_debouncer.cancelDebounce();
super.dispose(); super.dispose();
} }

View file

@ -12,19 +12,19 @@ class Debouncer {
/// If executionIntervalInSeconds is not null, then the debouncer will execute the /// If executionIntervalInSeconds is not null, then the debouncer will execute the
/// current callback it has in run() method repeatedly in the given interval. /// current callback it has in run() method repeatedly in the given interval.
/// This is useful for example when you want to execute a callback every 5 seconds /// This is useful for example when you want to execute a callback every 5 seconds
final int? executionIntervalInMilliSeconds; final Duration? executionInterval;
Timer? _debounceTimer; Timer? _debounceTimer;
Debouncer(this._duration, {this.executionIntervalInMilliSeconds}); Debouncer(this._duration, {this.executionInterval});
final Stopwatch _stopwatch = Stopwatch(); final Stopwatch _stopwatch = Stopwatch();
void run(FutureVoidCallback fn) { void run(FutureVoidCallback fn) {
bool shouldRunImmediately = false; bool shouldRunImmediately = false;
if (executionIntervalInMilliSeconds != null) { if (executionInterval != null) {
// ensure the stop watch is running // ensure the stop watch is running
_stopwatch.start(); _stopwatch.start();
if (_stopwatch.elapsedMilliseconds > executionIntervalInMilliSeconds!) { if (_stopwatch.elapsedMilliseconds > executionInterval!.inMilliseconds) {
shouldRunImmediately = true; shouldRunImmediately = true;
_stopwatch.stop(); _stopwatch.stop();
_stopwatch.reset(); _stopwatch.reset();