@@ -2,6 +2,7 @@ import "dart:async";
22import 'dart:convert' ;
33import "dart:math" ;
44
5+ import 'package:async/async.dart' ;
56import "package:collection/collection.dart" ;
67import 'package:crypto/crypto.dart' ;
78import 'package:flutter/material.dart' ;
@@ -23,14 +24,21 @@ import 'package:shared_preferences/shared_preferences.dart';
2324
2425class AlbumHomeWidgetService {
2526 // Constants
27+ static const String WIDGET_TYPE = "album" ; // Identifier for this widget type
2628 static const String SELECTED_ALBUMS_KEY = "selectedAlbumsHW" ;
2729 static const String ALBUMS_LAST_HASH_KEY = "albumsLastHash" ;
30+ static const String ALBUMS_LAST_REFRESH_KEY = "albumsLastRefresh" ;
2831 static const String ANDROID_CLASS_NAME = "EnteAlbumsWidgetProvider" ;
2932 static const String IOS_CLASS_NAME = "EnteAlbumWidget" ;
3033 static const String ALBUMS_CHANGED_KEY = "albumsChanged.widget" ;
3134 static const String ALBUMS_STATUS_KEY = "albumsStatusKey.widget" ;
3235 static const String TOTAL_ALBUMS_KEY = "totalAlbums" ;
33- static const int MAX_ALBUMS_LIMIT = 50 ;
36+ // Widget optimization constants (internal users only)
37+ static const int MAX_ALBUMS_LIMIT_INTERNAL =
38+ 10 ; // Optimized for 6-hour refresh
39+ static const int MAX_ALBUMS_LIMIT_DEFAULT = 50 ; // Original limit
40+ static const Duration REFRESH_INTERVAL =
41+ Duration (hours: 6 ); // Refresh every 6 hours
3442
3543 // Singleton pattern
3644 static final AlbumHomeWidgetService instance =
@@ -41,6 +49,13 @@ class AlbumHomeWidgetService {
4149 final Logger _logger = Logger ((AlbumHomeWidgetService ).toString ());
4250 SharedPreferences get _prefs => ServiceLocator .instance.prefs;
4351
52+ // Track the latest request generation to skip outdated operations
53+ int _requestGeneration = 0 ;
54+
55+ // Debounce timer to prevent rapid consecutive widget sync calls
56+ Timer ? _debounceTimer;
57+ static const Duration _debounceDuration = Duration (seconds: 2 );
58+
4459 // Public methods
4560 List <int >? getSelectedAlbumIds () {
4661 final selectedAlbums = _prefs.getStringList (SELECTED_ALBUMS_KEY );
@@ -61,7 +76,32 @@ class AlbumHomeWidgetService {
6176 }
6277
6378 Future <void > initAlbumHomeWidget (bool isBg) async {
79+ // Cancel any pending debounced calls
80+ _debounceTimer? .cancel ();
81+
82+ // Debounce rapid consecutive calls (except for background calls)
83+ if (! isBg) {
84+ _debounceTimer = Timer (_debounceDuration, () async {
85+ await _initAlbumHomeWidgetInternal (isBg);
86+ });
87+ } else {
88+ // Background calls are executed immediately
89+ await _initAlbumHomeWidgetInternal (isBg);
90+ }
91+ }
92+
93+ Future <void > _initAlbumHomeWidgetInternal (bool isBg) async {
94+ // Increment generation for this request
95+ final currentGeneration = ++ _requestGeneration;
96+
6497 await HomeWidgetService .instance.computeLock.synchronized (() async {
98+ // Skip if a newer request has already been made
99+ if (currentGeneration != _requestGeneration) {
100+ _logger.info (
101+ "Skipping outdated album widget request (gen $currentGeneration , latest $_requestGeneration )" ,
102+ );
103+ return ;
104+ }
65105 if (await _hasAnyBlockers (isBg)) {
66106 await clearWidget ();
67107 return ;
@@ -72,9 +112,23 @@ class AlbumHomeWidgetService {
72112 final bool forceFetchNewAlbums = await _shouldUpdateWidgetCache ();
73113
74114 if (forceFetchNewAlbums) {
75- await _updateAlbumsWidgetCache ();
76- await setSelectionChange (false );
77- _logger.info ("Force fetch new albums complete" );
115+ // Only cancel album operations, not other widget types
116+ await HomeWidgetService .instance.cancelWidgetOperation (WIDGET_TYPE );
117+
118+ // Create a cancellable operation for this album widget update
119+ final completer = CancelableCompleter <void >();
120+ HomeWidgetService .instance
121+ .setWidgetOperation (WIDGET_TYPE , completer.operation);
122+
123+ await _updateAlbumsWidgetCacheWithCancellation (completer);
124+ if (! completer.isCanceled) {
125+ await setSelectionChange (false );
126+ _logger.info ("Force fetch new albums complete" );
127+ }
128+
129+ if (! completer.isCompleted && ! completer.isCanceled) {
130+ completer.complete ();
131+ }
78132 } else {
79133 await _refreshAlbumsWidget ();
80134 _logger.info ("Refresh albums widget complete" );
@@ -128,7 +182,8 @@ class AlbumHomeWidgetService {
128182
129183 _logger.info ("Checking pending albums sync" );
130184 if (await _shouldUpdateWidgetCache ()) {
131- await initAlbumHomeWidget (false );
185+ // Use internal method to bypass debouncing for scheduled checks
186+ await _initAlbumHomeWidgetInternal (false );
132187 }
133188 }
134189
@@ -208,11 +263,14 @@ class AlbumHomeWidgetService {
208263
209264 // Private methods
210265 String _calculateHash (List <int > albumIds) {
266+ if (albumIds.isEmpty) return "" ;
267+
268+ // Get all collections in one shot instead of individual queries
269+ final collections = CollectionsService .instance.getActiveCollections ();
211270 String updationTimestamps = "" ;
212271
213- // TODO: This can be done in one shot by querying the database directly
214272 for (final albumId in albumIds) {
215- final collection = CollectionsService .instance. getCollectionByID ( albumId);
273+ final collection = collections. firstWhereOrNull ((c) => c.id == albumId);
216274 if (collection != null ) {
217275 updationTimestamps += "$albumId :${collection .updationTime .toString ()}_" ;
218276 }
@@ -265,6 +323,39 @@ class AlbumHomeWidgetService {
265323 return false ;
266324 }
267325
326+ // Widget optimization for enhanced widget feature
327+ if (flagService.enhancedWidgetImage) {
328+ // Check if we already have all available images (less than limit)
329+ // If the last sync was successful and we had less than the limit, no need to refresh
330+ final lastStatus = getAlbumsStatus ();
331+ final totalAlbums = await _getTotalAlbums ();
332+ const maxLimit = MAX_ALBUMS_LIMIT_INTERNAL ;
333+
334+ if (lastStatus == WidgetStatus .syncedAll &&
335+ totalAlbums != null &&
336+ totalAlbums < maxLimit) {
337+ _logger.info (
338+ "[Enhanced] Skipping refresh: already have all available images ($totalAlbums < $maxLimit )" ,
339+ );
340+ return false ;
341+ }
342+
343+ // Check if enough time has passed for a refresh (even if content hasn't changed)
344+ final lastRefreshStr = _prefs.getString (ALBUMS_LAST_REFRESH_KEY );
345+ if (lastRefreshStr != null ) {
346+ final lastRefresh = DateTime .tryParse (lastRefreshStr);
347+ if (lastRefresh != null ) {
348+ final timeSinceRefresh = DateTime .now ().difference (lastRefresh);
349+ if (timeSinceRefresh >= REFRESH_INTERVAL ) {
350+ _logger.info (
351+ "[Enhanced] Time-based refresh triggered (last refresh: ${timeSinceRefresh .inHours } hours ago)" ,
352+ );
353+ return true ;
354+ }
355+ }
356+ }
357+ }
358+
268359 // Check if hash has changed
269360 final currentHash = _calculateHash (selectedAlbumIds);
270361 final lastHash = getAlbumsLastHash ();
@@ -343,11 +434,23 @@ class AlbumHomeWidgetService {
343434 return albumsWithFiles;
344435 }
345436
437+ Future <int ?> _getTotalAlbums () async {
438+ return await HomeWidgetService .instance.getData <int >(TOTAL_ALBUMS_KEY );
439+ }
440+
346441 Future <void > _setTotalAlbums (int ? total) async {
347442 await HomeWidgetService .instance.setData (TOTAL_ALBUMS_KEY , total);
348443 }
349444
350- Future <void > _updateAlbumsWidgetCache () async {
445+ Future <void > _updateAlbumsWidgetCacheWithCancellation (
446+ CancelableCompleter completer,
447+ ) async {
448+ return _updateAlbumsWidgetCache (completer);
449+ }
450+
451+ Future <void > _updateAlbumsWidgetCache ([
452+ CancelableCompleter ? completer,
453+ ]) async {
351454 final selectedAlbumIds = await _getEffectiveSelectedAlbumIds ();
352455 final albumsWithFiles = await _getAlbumsWithFiles ();
353456
@@ -358,11 +461,26 @@ class AlbumHomeWidgetService {
358461
359462 final bool isWidgetPresent = await countHomeWidgets () > 0 ;
360463
361- final limit = isWidgetPresent ? MAX_ALBUMS_LIMIT : 5 ;
362- final maxAttempts = limit * 10 ;
464+ // Use optimized limits for enhanced widget feature
465+ final maxLimit = flagService.enhancedWidgetImage
466+ ? MAX_ALBUMS_LIMIT_INTERNAL
467+ : MAX_ALBUMS_LIMIT_DEFAULT ;
468+ final limit = isWidgetPresent ? maxLimit : 5 ;
469+
470+ // Record the refresh time for enhanced widget feature
471+ if (flagService.enhancedWidgetImage) {
472+ await _prefs.setString (
473+ ALBUMS_LAST_REFRESH_KEY ,
474+ DateTime .now ().toIso8601String (),
475+ );
476+ }
477+ final maxAttempts =
478+ limit * 3 ; // Reduce max attempts to avoid excessive retries
363479
364480 int renderedCount = 0 ;
365481 int attemptsCount = 0 ;
482+ // Track files that have already failed to avoid retrying them
483+ final Set <String > failedFiles = {};
366484
367485 await updateAlbumsStatus (WidgetStatus .notSynced);
368486
@@ -371,6 +489,12 @@ class AlbumHomeWidgetService {
371489 final random = Random ();
372490
373491 while (renderedCount < limit && attemptsCount < maxAttempts) {
492+ // Check if operation was cancelled
493+ if (completer != null && completer.isCanceled) {
494+ _logger.info ("Albums widget update cancelled during rendering" );
495+ return ;
496+ }
497+
374498 final randomEntry =
375499 albumsWithFilesEntries[random.nextInt (albumsWithFilesLength)];
376500
@@ -379,6 +503,15 @@ class AlbumHomeWidgetService {
379503 final randomAlbumFile = randomEntry.value.$2.elementAt (
380504 random.nextInt (randomEntry.value.$2.length),
381505 );
506+
507+ // Skip files that have already failed
508+ final fileKey =
509+ '${randomAlbumFile .uploadedFileID ?? randomAlbumFile .localID }_${randomAlbumFile .displayName }' ;
510+ if (failedFiles.contains (fileKey)) {
511+ attemptsCount++ ;
512+ continue ;
513+ }
514+
382515 final albumId = randomEntry.key;
383516 final albumName = randomEntry.value.$1;
384517
@@ -395,6 +528,12 @@ class AlbumHomeWidgetService {
395528 });
396529
397530 if (renderResult != null ) {
531+ // Check if cancelled before continuing
532+ if (completer != null && completer.isCanceled) {
533+ _logger.info ("Albums widget update cancelled after rendering" );
534+ return ;
535+ }
536+
398537 // Check for blockers again before continuing
399538 if (await _hasAnyBlockers ()) {
400539 await clearWidget ();
@@ -412,6 +551,9 @@ class AlbumHomeWidgetService {
412551 }
413552
414553 renderedCount++ ;
554+ } else {
555+ // Mark this file as failed to avoid retrying it
556+ failedFiles.add (fileKey);
415557 }
416558
417559 attemptsCount++ ;
0 commit comments