From 47f6ee2fe86dbceeedc1a2e160ca617961d2e466 Mon Sep 17 00:00:00 2001 From: timonrieger Date: Sun, 14 Dec 2025 01:43:57 +0100 Subject: [PATCH 1/3] fix with logging --- .../lib/domain/services/timeline.service.dart | 5 + mobile/lib/main.dart | 9 + .../lib/pages/common/gallery_viewer.page.dart | 229 +++++++++++++++++- mobile/lib/pages/search/map/map.page.dart | 18 ++ .../asset_viewer/asset_viewer.page.dart | 35 +++ .../widgets/images/local_image_provider.dart | 15 ++ .../widgets/images/remote_image_provider.dart | 13 + .../presentation/widgets/map/map.widget.dart | 13 + .../widgets/timeline/fixed/segment.model.dart | 5 + .../lib/routing/app_navigation_observer.dart | 11 + 10 files changed, 352 insertions(+), 1 deletion(-) diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart index 96630f1eba..0f8894ec62 100644 --- a/mobile/lib/domain/services/timeline.service.dart +++ b/mobile/lib/domain/services/timeline.service.dart @@ -226,6 +226,11 @@ class TimelineService { } Future dispose() async { + // #region agent log (H6 device) + print( + '[AGENT_LOG] TimelineService.dispose origin=$origin totalAssets=$_totalAssets bufferLen=${_buffer.length} bufferOffset=$_bufferOffset', + ); + // #endregion await _bucketSubscription?.cancel(); _bucketSubscription = null; _buffer = []; diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index c3804d97f6..e823cfaeb7 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -47,6 +47,11 @@ import 'package:logging/logging.dart'; import 'package:timezone/data/latest.dart'; void main() async { + // #region agent log (VERIFY device) + // This MUST show in `flutter run -d Phone` output immediately after hot restart. + // If you don't see it, you're not running the updated build. + print('[AGENT_LOG] main.dart:main ENTER ts=${DateTime.now().toIso8601String()}'); + // #endregion ImmichWidgetsBinding(); unawaited(BackgroundWorkerLockService(BackgroundWorkerLockApi()).lock()); final (isar, drift, logDb) = await Bootstrap.initDB(); @@ -67,6 +72,10 @@ void main() async { child: const MainWidget(), ), ); + + // #region agent log (VERIFY device) + print('[AGENT_LOG] main.dart:main AFTER runApp ts=${DateTime.now().toIso8601String()}'); + // #endregion } Future initApp() async { diff --git a/mobile/lib/pages/common/gallery_viewer.page.dart b/mobile/lib/pages/common/gallery_viewer.page.dart index 9a7e78ddb8..2f7144b22a 100644 --- a/mobile/lib/pages/common/gallery_viewer.page.dart +++ b/mobile/lib/pages/common/gallery_viewer.page.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'dart:ui' as ui; @@ -57,6 +58,72 @@ class GalleryViewerPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + // #region agent log helper + void _agentLog({ + required String hypothesisId, + required String location, + required String message, + Map data = const {}, + }) { + // Print to console as immediate verification + print('[AGENT_LOG] $location: $message'); + // Best-effort logging only; never throw from UI code. + () async { + try { + final logEntry = jsonEncode({ + 'sessionId': 'debug-session', + 'runId': 'run1', + 'hypothesisId': hypothesisId, + 'location': location, + 'message': message, + 'data': data, + 'timestamp': DateTime.now().millisecondsSinceEpoch, + }); + // Write directly to file (Dart/Flutter approach) + try { + final file = File('/Users/timonthegoat/code/oss/immich/.cursor/debug.log'); + await file.writeAsString('$logEntry\n', mode: FileMode.append, flush: true); + print('[AGENT_LOG] File write SUCCESS: $location'); + } catch (e) { + print('[AGENT_LOG] File write FAILED: $location - $e'); + } + // Also try HTTP as backup + try { + final client = HttpClient(); + final req = await client.postUrl( + Uri.parse('http://127.0.0.1:7242/ingest/9f2496b1-d42f-425c-9b63-abfeb2cdbe71'), + ); + req.headers.contentType = ContentType.json; + req.write(logEntry); + await req.close(); + client.close(force: true); + print('[AGENT_LOG] HTTP write SUCCESS: $location'); + } catch (e) { + print('[AGENT_LOG] HTTP write FAILED: $location - $e'); + } + } catch (e) { + print('[AGENT_LOG] Logging error: $e'); + } + }(); + } + // #endregion + + // #region agent log (VERIFY) - immediate entry point + _agentLog( + hypothesisId: 'VERIFY', + location: 'gallery_viewer.page.dart:build:ENTRY', + message: 'GalleryViewer build ENTRY - logging system test', + data: {'test': true, 'timestamp': DateTime.now().toIso8601String()}, + ); + // #endregion + + // #region agent log (VERIFY device) - plain print (no async/file/http) + // This MUST appear in `flutter run` output as soon as the viewer route builds. + print( + '[AGENT_LOG] gallery_viewer.page.dart:ENTRY_PRINT isIOS=${Platform.isIOS} orientation=${MediaQuery.orientationOf(context).name} size=${MediaQuery.sizeOf(context).width}x${MediaQuery.sizeOf(context).height}', + ); + // #endregion + final totalAssets = useState(renderList.totalAssets); final isZoomed = useState(false); final stackIndex = useState(0); @@ -65,6 +132,25 @@ class GalleryViewerPage extends HookConsumerWidget { final loadAsset = renderList.loadAsset; final isPlayingMotionVideo = ref.watch(isPlayingMotionVideoProvider); final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); + final showControls = ref.watch(showControlsProvider); + + // #region agent log (H1) build snapshot + _agentLog( + hypothesisId: 'H1', + location: 'gallery_viewer.page.dart:build', + message: 'GalleryViewer build', + data: { + 'isIOS': Platform.isIOS, + 'orientation': MediaQuery.orientationOf(context).name, + 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, + 'devicePixelRatio': MediaQuery.devicePixelRatioOf(context), + 'showControls': showControls, + 'isZoomed': isZoomed.value, + 'currentIndex': currentIndex.value, + 'totalAssets': totalAssets.value, + }, + ); + // #endregion final videoPlayerKeys = useRef>({}); @@ -100,12 +186,28 @@ class GalleryViewerPage extends HookConsumerWidget { } useEffect(() { - if (ref.read(showControlsProvider)) { + final initialShowControls = ref.read(showControlsProvider); + if (initialShowControls) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); } else { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); } + // #region agent log (H1) init ui mode + _agentLog( + hypothesisId: 'H1', + location: 'gallery_viewer.page.dart:useEffect(init)', + message: 'Initial SystemChrome.setEnabledSystemUIMode', + data: { + 'initialShowControls': initialShowControls, + 'mode': (initialShowControls ? SystemUiMode.edgeToEdge : SystemUiMode.immersive).name, + 'isIOS': Platform.isIOS, + 'orientation': MediaQuery.orientationOf(context).name, + 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, + }, + ); + // #endregion + // Delay this a bit so we can finish loading the page Timer(const Duration(milliseconds: 400), () { precacheNextImage(currentIndex.value + 1); @@ -202,6 +304,22 @@ class GalleryViewerPage extends HookConsumerWidget { } ref.listen(showControlsProvider, (_, show) { + // #region agent log (H1) controls -> system ui mode + _agentLog( + hypothesisId: 'H1', + location: 'gallery_viewer.page.dart:ref.listen(showControlsProvider)', + message: 'showControls changed; applying SystemChrome mode', + data: { + 'show': show, + 'isIOS': Platform.isIOS, + 'willSetMode': (show || Platform.isIOS ? SystemUiMode.edgeToEdge : SystemUiMode.immersive).name, + 'orientation': MediaQuery.orientationOf(context).name, + 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, + 'currentIndex': currentIndex.value, + }, + ); + // #endregion + if (show || Platform.isIOS) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); return; @@ -222,7 +340,32 @@ class GalleryViewerPage extends HookConsumerWidget { handleSwipeUpDown(details); }, onTapDown: (_, __, ___) { + final prev = ref.read(showControlsProvider); ref.read(showControlsProvider.notifier).toggle(); + final next = ref.read(showControlsProvider); + // #region agent log (H1) user tapped to toggle controls + _agentLog( + hypothesisId: 'H1', + location: 'gallery_viewer.page.dart:buildImage.onTapDown', + message: 'Tap to toggle viewer controls', + data: { + 'prev': prev, + 'next': next, + 'isIOS': Platform.isIOS, + 'orientation': MediaQuery.orientationOf(context).name, + 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, + 'asset': { + 'id': asset.id, + 'type': asset.type.toString(), + 'isRemote': asset.isRemote, + 'isLocal': asset.isLocal, + 'remoteIdPresent': asset.remoteId != null, + 'localIdPresent': asset.localId != null, + }, + 'currentIndex': currentIndex.value, + }, + ); + // #endregion }, onLongPressStart: asset.isMotionPhoto ? (_, __, ___) { @@ -280,8 +423,56 @@ class GalleryViewerPage extends HookConsumerWidget { } if (newAsset.isImage && !isPlayingMotionVideo) { + // #region agent log (H5) asset builder for current page + if (index == currentIndex.value) { + _agentLog( + hypothesisId: 'H5', + location: 'gallery_viewer.page.dart:buildAsset', + message: 'Built page options for current index (image path)', + data: { + 'index': index, + 'isIOS': Platform.isIOS, + 'orientation': MediaQuery.orientationOf(context).name, + 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, + 'showControls': ref.read(showControlsProvider), + 'isZoomed': isZoomed.value, + 'asset': { + 'id': newAsset.id, + 'type': newAsset.type.toString(), + 'isRemote': newAsset.isRemote, + 'isLocal': newAsset.isLocal, + 'stackIdPresent': newAsset.stackId != null, + }, + }, + ); + } + // #endregion return buildImage(newAsset); } + // #region agent log (H5) asset builder for current page (video path) + if (index == currentIndex.value) { + _agentLog( + hypothesisId: 'H5', + location: 'gallery_viewer.page.dart:buildAsset', + message: 'Built page options for current index (video path)', + data: { + 'index': index, + 'isIOS': Platform.isIOS, + 'orientation': MediaQuery.orientationOf(context).name, + 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, + 'showControls': ref.read(showControlsProvider), + 'isZoomed': isZoomed.value, + 'asset': { + 'id': newAsset.id, + 'type': newAsset.type.toString(), + 'isRemote': newAsset.isRemote, + 'isLocal': newAsset.isLocal, + 'stackIdPresent': newAsset.stackId != null, + }, + }, + ); + } + // #endregion return buildVideo(context, newAsset); } @@ -308,6 +499,29 @@ class GalleryViewerPage extends HookConsumerWidget { gaplessPlayback: true, loadingBuilder: (context, event, index) { final asset = loadAsset(index); + // #region agent log (H2) stuck on loading builder + if (index == currentIndex.value) { + _agentLog( + hypothesisId: 'H2', + location: 'gallery_viewer.page.dart:PhotoViewGallery.loadingBuilder', + message: 'PhotoViewGallery loadingBuilder for current index', + data: { + 'index': index, + 'isIOS': Platform.isIOS, + 'orientation': MediaQuery.orientationOf(context).name, + 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, + 'showControls': ref.read(showControlsProvider), + 'isZoomed': isZoomed.value, + 'asset': { + 'id': asset.id, + 'type': asset.type.toString(), + 'isRemote': asset.isRemote, + 'isLocal': asset.isLocal, + }, + }, + ); + } + // #endregion return ClipRect( child: Stack( fit: StackFit.expand, @@ -328,6 +542,19 @@ class GalleryViewerPage extends HookConsumerWidget { itemCount: totalAssets.value, scrollDirection: Axis.horizontal, onPageChanged: (value, _) { + // #region agent log (VERIFY) page changed + _agentLog( + hypothesisId: 'VERIFY', + location: 'gallery_viewer.page.dart:onPageChanged', + message: 'PhotoViewGallery page changed', + data: { + 'from': currentIndex.value, + 'to': value, + 'isIOS': Platform.isIOS, + 'orientation': MediaQuery.orientationOf(context).name, + }, + ); + // #endregion final next = currentIndex.value < value ? value + 1 : value - 1; ref.read(hapticFeedbackProvider.notifier).selectionClick(); diff --git a/mobile/lib/pages/search/map/map.page.dart b/mobile/lib/pages/search/map/map.page.dart index a93b826f03..ce751d75b2 100644 --- a/mobile/lib/pages/search/map/map.page.dart +++ b/mobile/lib/pages/search/map/map.page.dart @@ -151,11 +151,24 @@ class MapPage extends HookConsumerWidget { Future onMarkerTapped() async { final assetId = selectedMarker.value?.marker.assetRemoteId; if (assetId == null) { + // #region agent log (VERIFY device) + // Device-friendly runtime evidence: prints show up in `flutter run` output. + print('[AGENT_LOG] map.page.dart:onMarkerTapped assetId=null'); + // #endregion return; } + // #region agent log (VERIFY device) + print( + '[AGENT_LOG] map.page.dart:onMarkerTapped start assetId=$assetId orientation=${MediaQuery.orientationOf(context).name} size=${MediaQuery.sizeOf(context).width}x${MediaQuery.sizeOf(context).height}', + ); + // #endregion + final asset = await ref.read(dbProvider).assets.getByRemoteId(assetId); if (asset == null) { + // #region agent log (VERIFY device) + print('[AGENT_LOG] map.page.dart:onMarkerTapped asset not found in db remoteId=$assetId'); + // #endregion return; } @@ -166,6 +179,11 @@ class MapPage extends HookConsumerWidget { if (asset.isVideo) { ref.read(showControlsProvider.notifier).show = false; } + // #region agent log (VERIFY device) + print( + '[AGENT_LOG] map.page.dart:onMarkerTapped push GalleryViewerRoute assetType=${asset.type} isVideo=${asset.isVideo} isRemote=${asset.isRemote} isLocal=${asset.isLocal}', + ); + // #endregion unawaited(context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList))); } diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart index 70eb6699aa..2716861d48 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart @@ -376,7 +376,17 @@ class _AssetViewerState extends ConsumerState { void _onTapDown(_, __, ___) { if (!showingBottomSheet) { + // #region agent log (H1 device) + final before = ref.read(assetViewerProvider.select((s) => s.showingControls)); + print( + '[AGENT_LOG] asset_viewer.page.dart:_onTapDown toggleControls before=$before orientation=${MediaQuery.orientationOf(context).name} size=${MediaQuery.sizeOf(context).width}x${MediaQuery.sizeOf(context).height}', + ); + // #endregion ref.read(assetViewerProvider.notifier).toggleControls(); + // #region agent log (H1 device) + final after = ref.read(assetViewerProvider.select((s) => s.showingControls)); + print('[AGENT_LOG] asset_viewer.page.dart:_onTapDown toggleControls after=$after'); + // #endregion } } @@ -516,6 +526,12 @@ class _AssetViewerState extends ConsumerState { } Widget _placeholderBuilder(BuildContext ctx, ImageChunkEvent? progress, int index) { + // #region agent log (H2 device) + final show = ref.read(assetViewerProvider.select((s) => s.showingControls)); + print( + '[AGENT_LOG] asset_viewer.page.dart:PhotoViewGallery.loadingBuilder index=$index showControls=$show orientation=${MediaQuery.orientationOf(ctx).name} size=${MediaQuery.sizeOf(ctx).width}x${MediaQuery.sizeOf(ctx).height} progress=${progress == null ? "null" : "${progress.cumulativeBytesLoaded}/${progress.expectedTotalBytes}"}', + ); + // #endregion return const Center(child: ImmichLoadingIndicator()); } @@ -536,6 +552,12 @@ class _AssetViewerState extends ConsumerState { // If asset is not available in buffer, return a placeholder if (asset == null) { + // #region agent log (H6 device) + final show = ref.read(assetViewerProvider.select((s) => s.showingControls)); + print( + '[AGENT_LOG] asset_viewer.page.dart:_assetBuilder asset=null index=$index totalAssets=${timelineService.totalAssets} showControls=$show orientation=${MediaQuery.orientationOf(ctx).name} size=${MediaQuery.sizeOf(ctx).width}x${MediaQuery.sizeOf(ctx).height}', + ); + // #endregion return PhotoViewGalleryPageOptions.customChild( heroAttributes: PhotoViewHeroAttributes(tag: 'loading_$index'), child: Container( @@ -547,6 +569,14 @@ class _AssetViewerState extends ConsumerState { ); } + // #region agent log (H6 device) + if (index == (pageController.hasClients ? pageController.page?.round() : null)) { + print( + '[AGENT_LOG] asset_viewer.page.dart:_assetBuilder asset!=null index=$index heroTag=${asset.heroTag} type=${asset.type} hasLocal=${asset.hasLocal} hasRemote=${asset.hasRemote}', + ); + } + // #endregion + BaseAsset displayAsset = asset; final stackChildren = ref.read(stackChildrenNotifier(asset)).valueOrNull; if (stackChildren != null && stackChildren.isNotEmpty) { @@ -646,6 +676,11 @@ class _AssetViewerState extends ConsumerState { // Listen for control visibility changes and change system UI mode accordingly ref.listen(assetViewerProvider.select((value) => value.showingControls), (_, showingControls) async { + // #region agent log (H1 device) + print( + '[AGENT_LOG] asset_viewer.page.dart:showingControlsChanged=$showingControls -> SystemUiMode=${(showingControls ? SystemUiMode.edgeToEdge : SystemUiMode.immersiveSticky).name} orientation=${MediaQuery.orientationOf(context).name} size=${MediaQuery.sizeOf(context).width}x${MediaQuery.sizeOf(context).height}', + ); + // #endregion if (showingControls) { unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)); } else { diff --git a/mobile/lib/presentation/widgets/images/local_image_provider.dart b/mobile/lib/presentation/widgets/images/local_image_provider.dart index c5dca57f9c..0ea026e91b 100644 --- a/mobile/lib/presentation/widgets/images/local_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/local_image_provider.dart @@ -62,6 +62,16 @@ class LocalFullImageProvider extends CancellableImageProvider obtainKey(ImageConfiguration configuration) { return SynchronousFuture(this); @@ -69,6 +79,11 @@ class LocalFullImageProvider extends CancellableImageProvider obtainKey(ImageConfiguration configuration) { return SynchronousFuture(this); @@ -72,6 +80,11 @@ class RemoteFullImageProvider extends CancellableImageProvider { return; } + // #region agent log (H6 device) + // When the AssetViewer is open, the DriftMap route stays alive in the background. + // If we continue to update bounds, the map-scoped timeline service gets recreated and the previous one disposed, + // which can invalidate the TimelineService instance that was passed into AssetViewerRoute (causing "loading forever"). + final currentRoute = ref.read(currentRouteNameProvider); + if (currentRoute == AssetViewerRoute.name || currentRoute == GalleryViewerRoute.name) { + print('[AGENT_LOG] DriftMap.setBounds SKIP currentRoute=$currentRoute'); + return; + } + // #endregion + final bounds = await controller.getVisibleRegion(); unawaited( _reloadMutex.run(() async { diff --git a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart index b879b33f68..42a00f9170 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart @@ -155,6 +155,11 @@ class _AssetTileWidget extends ConsumerWidget { if (multiSelectState.forceEnable || multiSelectState.isEnabled) { ref.read(multiSelectProvider.notifier).toggleAssetSelection(asset); } else { + // #region agent log (VERIFY device) + print( + '[AGENT_LOG] timeline.assetTile:onTap open AssetViewerRoute index=$assetIndex assetType=${asset.type} isRemote=${asset is RemoteAsset} heroOffset=$heroOffset', + ); + // #endregion await ref.read(timelineServiceProvider).loadAssets(assetIndex, 1); ref.read(isPlayingMotionVideoProvider.notifier).playing = false; AssetViewer.setAsset(ref, asset); diff --git a/mobile/lib/routing/app_navigation_observer.dart b/mobile/lib/routing/app_navigation_observer.dart index b05a28172d..e957b9f4e2 100644 --- a/mobile/lib/routing/app_navigation_observer.dart +++ b/mobile/lib/routing/app_navigation_observer.dart @@ -19,6 +19,17 @@ class AppNavigationObserver extends AutoRouterObserver { @override void didPush(Route route, Route? previousRoute) { + // #region agent log (VERIFY device) + // Keep this targeted to reduce noise; we care about the Places -> (Drift)Map -> AssetViewer flow. + final name = route.settings.name; + final prev = previousRoute?.settings.name; + if (name == MapRoute.name || + name == DriftMapRoute.name || + name == GalleryViewerRoute.name || + name == AssetViewerRoute.name) { + print('[AGENT_LOG] nav.didPush route=$name prev=$prev'); + } + // #endregion _handleLockedViewState(route, previousRoute); _handleDriftLockedFolderState(route, previousRoute); Future(() { From ec28044fa9dbd2b97b8160f88c8fe7db9a902a12 Mon Sep 17 00:00:00 2001 From: timonrieger Date: Sun, 14 Dec 2025 01:49:06 +0100 Subject: [PATCH 2/3] remove logging --- .../lib/domain/services/timeline.service.dart | 5 - mobile/lib/main.dart | 9 - .../lib/pages/common/gallery_viewer.page.dart | 226 ------------------ mobile/lib/pages/search/map/map.page.dart | 18 -- .../asset_viewer/asset_viewer.page.dart | 35 --- .../widgets/images/local_image_provider.dart | 10 - .../widgets/images/remote_image_provider.dart | 8 - .../presentation/widgets/map/map.widget.dart | 3 - .../widgets/timeline/fixed/segment.model.dart | 5 - .../lib/routing/app_navigation_observer.dart | 11 - 10 files changed, 330 deletions(-) diff --git a/mobile/lib/domain/services/timeline.service.dart b/mobile/lib/domain/services/timeline.service.dart index 0f8894ec62..96630f1eba 100644 --- a/mobile/lib/domain/services/timeline.service.dart +++ b/mobile/lib/domain/services/timeline.service.dart @@ -226,11 +226,6 @@ class TimelineService { } Future dispose() async { - // #region agent log (H6 device) - print( - '[AGENT_LOG] TimelineService.dispose origin=$origin totalAssets=$_totalAssets bufferLen=${_buffer.length} bufferOffset=$_bufferOffset', - ); - // #endregion await _bucketSubscription?.cancel(); _bucketSubscription = null; _buffer = []; diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index e823cfaeb7..c3804d97f6 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -47,11 +47,6 @@ import 'package:logging/logging.dart'; import 'package:timezone/data/latest.dart'; void main() async { - // #region agent log (VERIFY device) - // This MUST show in `flutter run -d Phone` output immediately after hot restart. - // If you don't see it, you're not running the updated build. - print('[AGENT_LOG] main.dart:main ENTER ts=${DateTime.now().toIso8601String()}'); - // #endregion ImmichWidgetsBinding(); unawaited(BackgroundWorkerLockService(BackgroundWorkerLockApi()).lock()); final (isar, drift, logDb) = await Bootstrap.initDB(); @@ -72,10 +67,6 @@ void main() async { child: const MainWidget(), ), ); - - // #region agent log (VERIFY device) - print('[AGENT_LOG] main.dart:main AFTER runApp ts=${DateTime.now().toIso8601String()}'); - // #endregion } Future initApp() async { diff --git a/mobile/lib/pages/common/gallery_viewer.page.dart b/mobile/lib/pages/common/gallery_viewer.page.dart index 2f7144b22a..d1822ba9ef 100644 --- a/mobile/lib/pages/common/gallery_viewer.page.dart +++ b/mobile/lib/pages/common/gallery_viewer.page.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'dart:math'; import 'dart:ui' as ui; @@ -58,72 +57,6 @@ class GalleryViewerPage extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - // #region agent log helper - void _agentLog({ - required String hypothesisId, - required String location, - required String message, - Map data = const {}, - }) { - // Print to console as immediate verification - print('[AGENT_LOG] $location: $message'); - // Best-effort logging only; never throw from UI code. - () async { - try { - final logEntry = jsonEncode({ - 'sessionId': 'debug-session', - 'runId': 'run1', - 'hypothesisId': hypothesisId, - 'location': location, - 'message': message, - 'data': data, - 'timestamp': DateTime.now().millisecondsSinceEpoch, - }); - // Write directly to file (Dart/Flutter approach) - try { - final file = File('/Users/timonthegoat/code/oss/immich/.cursor/debug.log'); - await file.writeAsString('$logEntry\n', mode: FileMode.append, flush: true); - print('[AGENT_LOG] File write SUCCESS: $location'); - } catch (e) { - print('[AGENT_LOG] File write FAILED: $location - $e'); - } - // Also try HTTP as backup - try { - final client = HttpClient(); - final req = await client.postUrl( - Uri.parse('http://127.0.0.1:7242/ingest/9f2496b1-d42f-425c-9b63-abfeb2cdbe71'), - ); - req.headers.contentType = ContentType.json; - req.write(logEntry); - await req.close(); - client.close(force: true); - print('[AGENT_LOG] HTTP write SUCCESS: $location'); - } catch (e) { - print('[AGENT_LOG] HTTP write FAILED: $location - $e'); - } - } catch (e) { - print('[AGENT_LOG] Logging error: $e'); - } - }(); - } - // #endregion - - // #region agent log (VERIFY) - immediate entry point - _agentLog( - hypothesisId: 'VERIFY', - location: 'gallery_viewer.page.dart:build:ENTRY', - message: 'GalleryViewer build ENTRY - logging system test', - data: {'test': true, 'timestamp': DateTime.now().toIso8601String()}, - ); - // #endregion - - // #region agent log (VERIFY device) - plain print (no async/file/http) - // This MUST appear in `flutter run` output as soon as the viewer route builds. - print( - '[AGENT_LOG] gallery_viewer.page.dart:ENTRY_PRINT isIOS=${Platform.isIOS} orientation=${MediaQuery.orientationOf(context).name} size=${MediaQuery.sizeOf(context).width}x${MediaQuery.sizeOf(context).height}', - ); - // #endregion - final totalAssets = useState(renderList.totalAssets); final isZoomed = useState(false); final stackIndex = useState(0); @@ -132,25 +65,6 @@ class GalleryViewerPage extends HookConsumerWidget { final loadAsset = renderList.loadAsset; final isPlayingMotionVideo = ref.watch(isPlayingMotionVideoProvider); final isCasting = ref.watch(castProvider.select((c) => c.isCasting)); - final showControls = ref.watch(showControlsProvider); - - // #region agent log (H1) build snapshot - _agentLog( - hypothesisId: 'H1', - location: 'gallery_viewer.page.dart:build', - message: 'GalleryViewer build', - data: { - 'isIOS': Platform.isIOS, - 'orientation': MediaQuery.orientationOf(context).name, - 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, - 'devicePixelRatio': MediaQuery.devicePixelRatioOf(context), - 'showControls': showControls, - 'isZoomed': isZoomed.value, - 'currentIndex': currentIndex.value, - 'totalAssets': totalAssets.value, - }, - ); - // #endregion final videoPlayerKeys = useRef>({}); @@ -193,21 +107,6 @@ class GalleryViewerPage extends HookConsumerWidget { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); } - // #region agent log (H1) init ui mode - _agentLog( - hypothesisId: 'H1', - location: 'gallery_viewer.page.dart:useEffect(init)', - message: 'Initial SystemChrome.setEnabledSystemUIMode', - data: { - 'initialShowControls': initialShowControls, - 'mode': (initialShowControls ? SystemUiMode.edgeToEdge : SystemUiMode.immersive).name, - 'isIOS': Platform.isIOS, - 'orientation': MediaQuery.orientationOf(context).name, - 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, - }, - ); - // #endregion - // Delay this a bit so we can finish loading the page Timer(const Duration(milliseconds: 400), () { precacheNextImage(currentIndex.value + 1); @@ -304,22 +203,6 @@ class GalleryViewerPage extends HookConsumerWidget { } ref.listen(showControlsProvider, (_, show) { - // #region agent log (H1) controls -> system ui mode - _agentLog( - hypothesisId: 'H1', - location: 'gallery_viewer.page.dart:ref.listen(showControlsProvider)', - message: 'showControls changed; applying SystemChrome mode', - data: { - 'show': show, - 'isIOS': Platform.isIOS, - 'willSetMode': (show || Platform.isIOS ? SystemUiMode.edgeToEdge : SystemUiMode.immersive).name, - 'orientation': MediaQuery.orientationOf(context).name, - 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, - 'currentIndex': currentIndex.value, - }, - ); - // #endregion - if (show || Platform.isIOS) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); return; @@ -340,32 +223,7 @@ class GalleryViewerPage extends HookConsumerWidget { handleSwipeUpDown(details); }, onTapDown: (_, __, ___) { - final prev = ref.read(showControlsProvider); ref.read(showControlsProvider.notifier).toggle(); - final next = ref.read(showControlsProvider); - // #region agent log (H1) user tapped to toggle controls - _agentLog( - hypothesisId: 'H1', - location: 'gallery_viewer.page.dart:buildImage.onTapDown', - message: 'Tap to toggle viewer controls', - data: { - 'prev': prev, - 'next': next, - 'isIOS': Platform.isIOS, - 'orientation': MediaQuery.orientationOf(context).name, - 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, - 'asset': { - 'id': asset.id, - 'type': asset.type.toString(), - 'isRemote': asset.isRemote, - 'isLocal': asset.isLocal, - 'remoteIdPresent': asset.remoteId != null, - 'localIdPresent': asset.localId != null, - }, - 'currentIndex': currentIndex.value, - }, - ); - // #endregion }, onLongPressStart: asset.isMotionPhoto ? (_, __, ___) { @@ -423,56 +281,8 @@ class GalleryViewerPage extends HookConsumerWidget { } if (newAsset.isImage && !isPlayingMotionVideo) { - // #region agent log (H5) asset builder for current page - if (index == currentIndex.value) { - _agentLog( - hypothesisId: 'H5', - location: 'gallery_viewer.page.dart:buildAsset', - message: 'Built page options for current index (image path)', - data: { - 'index': index, - 'isIOS': Platform.isIOS, - 'orientation': MediaQuery.orientationOf(context).name, - 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, - 'showControls': ref.read(showControlsProvider), - 'isZoomed': isZoomed.value, - 'asset': { - 'id': newAsset.id, - 'type': newAsset.type.toString(), - 'isRemote': newAsset.isRemote, - 'isLocal': newAsset.isLocal, - 'stackIdPresent': newAsset.stackId != null, - }, - }, - ); - } - // #endregion return buildImage(newAsset); } - // #region agent log (H5) asset builder for current page (video path) - if (index == currentIndex.value) { - _agentLog( - hypothesisId: 'H5', - location: 'gallery_viewer.page.dart:buildAsset', - message: 'Built page options for current index (video path)', - data: { - 'index': index, - 'isIOS': Platform.isIOS, - 'orientation': MediaQuery.orientationOf(context).name, - 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, - 'showControls': ref.read(showControlsProvider), - 'isZoomed': isZoomed.value, - 'asset': { - 'id': newAsset.id, - 'type': newAsset.type.toString(), - 'isRemote': newAsset.isRemote, - 'isLocal': newAsset.isLocal, - 'stackIdPresent': newAsset.stackId != null, - }, - }, - ); - } - // #endregion return buildVideo(context, newAsset); } @@ -499,29 +309,6 @@ class GalleryViewerPage extends HookConsumerWidget { gaplessPlayback: true, loadingBuilder: (context, event, index) { final asset = loadAsset(index); - // #region agent log (H2) stuck on loading builder - if (index == currentIndex.value) { - _agentLog( - hypothesisId: 'H2', - location: 'gallery_viewer.page.dart:PhotoViewGallery.loadingBuilder', - message: 'PhotoViewGallery loadingBuilder for current index', - data: { - 'index': index, - 'isIOS': Platform.isIOS, - 'orientation': MediaQuery.orientationOf(context).name, - 'size': {'w': MediaQuery.sizeOf(context).width, 'h': MediaQuery.sizeOf(context).height}, - 'showControls': ref.read(showControlsProvider), - 'isZoomed': isZoomed.value, - 'asset': { - 'id': asset.id, - 'type': asset.type.toString(), - 'isRemote': asset.isRemote, - 'isLocal': asset.isLocal, - }, - }, - ); - } - // #endregion return ClipRect( child: Stack( fit: StackFit.expand, @@ -542,19 +329,6 @@ class GalleryViewerPage extends HookConsumerWidget { itemCount: totalAssets.value, scrollDirection: Axis.horizontal, onPageChanged: (value, _) { - // #region agent log (VERIFY) page changed - _agentLog( - hypothesisId: 'VERIFY', - location: 'gallery_viewer.page.dart:onPageChanged', - message: 'PhotoViewGallery page changed', - data: { - 'from': currentIndex.value, - 'to': value, - 'isIOS': Platform.isIOS, - 'orientation': MediaQuery.orientationOf(context).name, - }, - ); - // #endregion final next = currentIndex.value < value ? value + 1 : value - 1; ref.read(hapticFeedbackProvider.notifier).selectionClick(); diff --git a/mobile/lib/pages/search/map/map.page.dart b/mobile/lib/pages/search/map/map.page.dart index ce751d75b2..a93b826f03 100644 --- a/mobile/lib/pages/search/map/map.page.dart +++ b/mobile/lib/pages/search/map/map.page.dart @@ -151,24 +151,11 @@ class MapPage extends HookConsumerWidget { Future onMarkerTapped() async { final assetId = selectedMarker.value?.marker.assetRemoteId; if (assetId == null) { - // #region agent log (VERIFY device) - // Device-friendly runtime evidence: prints show up in `flutter run` output. - print('[AGENT_LOG] map.page.dart:onMarkerTapped assetId=null'); - // #endregion return; } - // #region agent log (VERIFY device) - print( - '[AGENT_LOG] map.page.dart:onMarkerTapped start assetId=$assetId orientation=${MediaQuery.orientationOf(context).name} size=${MediaQuery.sizeOf(context).width}x${MediaQuery.sizeOf(context).height}', - ); - // #endregion - final asset = await ref.read(dbProvider).assets.getByRemoteId(assetId); if (asset == null) { - // #region agent log (VERIFY device) - print('[AGENT_LOG] map.page.dart:onMarkerTapped asset not found in db remoteId=$assetId'); - // #endregion return; } @@ -179,11 +166,6 @@ class MapPage extends HookConsumerWidget { if (asset.isVideo) { ref.read(showControlsProvider.notifier).show = false; } - // #region agent log (VERIFY device) - print( - '[AGENT_LOG] map.page.dart:onMarkerTapped push GalleryViewerRoute assetType=${asset.type} isVideo=${asset.isVideo} isRemote=${asset.isRemote} isLocal=${asset.isLocal}', - ); - // #endregion unawaited(context.pushRoute(GalleryViewerRoute(initialIndex: 0, heroOffset: 0, renderList: renderList))); } diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart index 2716861d48..70eb6699aa 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart @@ -376,17 +376,7 @@ class _AssetViewerState extends ConsumerState { void _onTapDown(_, __, ___) { if (!showingBottomSheet) { - // #region agent log (H1 device) - final before = ref.read(assetViewerProvider.select((s) => s.showingControls)); - print( - '[AGENT_LOG] asset_viewer.page.dart:_onTapDown toggleControls before=$before orientation=${MediaQuery.orientationOf(context).name} size=${MediaQuery.sizeOf(context).width}x${MediaQuery.sizeOf(context).height}', - ); - // #endregion ref.read(assetViewerProvider.notifier).toggleControls(); - // #region agent log (H1 device) - final after = ref.read(assetViewerProvider.select((s) => s.showingControls)); - print('[AGENT_LOG] asset_viewer.page.dart:_onTapDown toggleControls after=$after'); - // #endregion } } @@ -526,12 +516,6 @@ class _AssetViewerState extends ConsumerState { } Widget _placeholderBuilder(BuildContext ctx, ImageChunkEvent? progress, int index) { - // #region agent log (H2 device) - final show = ref.read(assetViewerProvider.select((s) => s.showingControls)); - print( - '[AGENT_LOG] asset_viewer.page.dart:PhotoViewGallery.loadingBuilder index=$index showControls=$show orientation=${MediaQuery.orientationOf(ctx).name} size=${MediaQuery.sizeOf(ctx).width}x${MediaQuery.sizeOf(ctx).height} progress=${progress == null ? "null" : "${progress.cumulativeBytesLoaded}/${progress.expectedTotalBytes}"}', - ); - // #endregion return const Center(child: ImmichLoadingIndicator()); } @@ -552,12 +536,6 @@ class _AssetViewerState extends ConsumerState { // If asset is not available in buffer, return a placeholder if (asset == null) { - // #region agent log (H6 device) - final show = ref.read(assetViewerProvider.select((s) => s.showingControls)); - print( - '[AGENT_LOG] asset_viewer.page.dart:_assetBuilder asset=null index=$index totalAssets=${timelineService.totalAssets} showControls=$show orientation=${MediaQuery.orientationOf(ctx).name} size=${MediaQuery.sizeOf(ctx).width}x${MediaQuery.sizeOf(ctx).height}', - ); - // #endregion return PhotoViewGalleryPageOptions.customChild( heroAttributes: PhotoViewHeroAttributes(tag: 'loading_$index'), child: Container( @@ -569,14 +547,6 @@ class _AssetViewerState extends ConsumerState { ); } - // #region agent log (H6 device) - if (index == (pageController.hasClients ? pageController.page?.round() : null)) { - print( - '[AGENT_LOG] asset_viewer.page.dart:_assetBuilder asset!=null index=$index heroTag=${asset.heroTag} type=${asset.type} hasLocal=${asset.hasLocal} hasRemote=${asset.hasRemote}', - ); - } - // #endregion - BaseAsset displayAsset = asset; final stackChildren = ref.read(stackChildrenNotifier(asset)).valueOrNull; if (stackChildren != null && stackChildren.isNotEmpty) { @@ -676,11 +646,6 @@ class _AssetViewerState extends ConsumerState { // Listen for control visibility changes and change system UI mode accordingly ref.listen(assetViewerProvider.select((value) => value.showingControls), (_, showingControls) async { - // #region agent log (H1 device) - print( - '[AGENT_LOG] asset_viewer.page.dart:showingControlsChanged=$showingControls -> SystemUiMode=${(showingControls ? SystemUiMode.edgeToEdge : SystemUiMode.immersiveSticky).name} orientation=${MediaQuery.orientationOf(context).name} size=${MediaQuery.sizeOf(context).width}x${MediaQuery.sizeOf(context).height}', - ); - // #endregion if (showingControls) { unawaited(SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge)); } else { diff --git a/mobile/lib/presentation/widgets/images/local_image_provider.dart b/mobile/lib/presentation/widgets/images/local_image_provider.dart index 0ea026e91b..aa3190831c 100644 --- a/mobile/lib/presentation/widgets/images/local_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/local_image_provider.dart @@ -64,11 +64,6 @@ class LocalFullImageProvider extends CancellableImageProvider { return; } - // #region agent log (H6 device) // When the AssetViewer is open, the DriftMap route stays alive in the background. // If we continue to update bounds, the map-scoped timeline service gets recreated and the previous one disposed, // which can invalidate the TimelineService instance that was passed into AssetViewerRoute (causing "loading forever"). final currentRoute = ref.read(currentRouteNameProvider); if (currentRoute == AssetViewerRoute.name || currentRoute == GalleryViewerRoute.name) { - print('[AGENT_LOG] DriftMap.setBounds SKIP currentRoute=$currentRoute'); return; } - // #endregion final bounds = await controller.getVisibleRegion(); unawaited( diff --git a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart index 42a00f9170..b879b33f68 100644 --- a/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart +++ b/mobile/lib/presentation/widgets/timeline/fixed/segment.model.dart @@ -155,11 +155,6 @@ class _AssetTileWidget extends ConsumerWidget { if (multiSelectState.forceEnable || multiSelectState.isEnabled) { ref.read(multiSelectProvider.notifier).toggleAssetSelection(asset); } else { - // #region agent log (VERIFY device) - print( - '[AGENT_LOG] timeline.assetTile:onTap open AssetViewerRoute index=$assetIndex assetType=${asset.type} isRemote=${asset is RemoteAsset} heroOffset=$heroOffset', - ); - // #endregion await ref.read(timelineServiceProvider).loadAssets(assetIndex, 1); ref.read(isPlayingMotionVideoProvider.notifier).playing = false; AssetViewer.setAsset(ref, asset); diff --git a/mobile/lib/routing/app_navigation_observer.dart b/mobile/lib/routing/app_navigation_observer.dart index e957b9f4e2..b05a28172d 100644 --- a/mobile/lib/routing/app_navigation_observer.dart +++ b/mobile/lib/routing/app_navigation_observer.dart @@ -19,17 +19,6 @@ class AppNavigationObserver extends AutoRouterObserver { @override void didPush(Route route, Route? previousRoute) { - // #region agent log (VERIFY device) - // Keep this targeted to reduce noise; we care about the Places -> (Drift)Map -> AssetViewer flow. - final name = route.settings.name; - final prev = previousRoute?.settings.name; - if (name == MapRoute.name || - name == DriftMapRoute.name || - name == GalleryViewerRoute.name || - name == AssetViewerRoute.name) { - print('[AGENT_LOG] nav.didPush route=$name prev=$prev'); - } - // #endregion _handleLockedViewState(route, previousRoute); _handleDriftLockedFolderState(route, previousRoute); Future(() { From 408d23090ec44d0a977744dd7aedb0b196fc5812 Mon Sep 17 00:00:00 2001 From: timonrieger Date: Sun, 14 Dec 2025 01:52:23 +0100 Subject: [PATCH 3/3] analyze --- mobile/lib/pages/common/gallery_viewer.page.dart | 3 +-- .../presentation/widgets/images/local_image_provider.dart | 5 ----- .../presentation/widgets/images/remote_image_provider.dart | 5 ----- 3 files changed, 1 insertion(+), 12 deletions(-) diff --git a/mobile/lib/pages/common/gallery_viewer.page.dart b/mobile/lib/pages/common/gallery_viewer.page.dart index d1822ba9ef..9a7e78ddb8 100644 --- a/mobile/lib/pages/common/gallery_viewer.page.dart +++ b/mobile/lib/pages/common/gallery_viewer.page.dart @@ -100,8 +100,7 @@ class GalleryViewerPage extends HookConsumerWidget { } useEffect(() { - final initialShowControls = ref.read(showControlsProvider); - if (initialShowControls) { + if (ref.read(showControlsProvider)) { SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); } else { SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersive); diff --git a/mobile/lib/presentation/widgets/images/local_image_provider.dart b/mobile/lib/presentation/widgets/images/local_image_provider.dart index aa3190831c..c5dca57f9c 100644 --- a/mobile/lib/presentation/widgets/images/local_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/local_image_provider.dart @@ -62,11 +62,6 @@ class LocalFullImageProvider extends CancellableImageProvider obtainKey(ImageConfiguration configuration) { return SynchronousFuture(this); diff --git a/mobile/lib/presentation/widgets/images/remote_image_provider.dart b/mobile/lib/presentation/widgets/images/remote_image_provider.dart index 2646dc9fd7..7a063a8672 100644 --- a/mobile/lib/presentation/widgets/images/remote_image_provider.dart +++ b/mobile/lib/presentation/widgets/images/remote_image_provider.dart @@ -65,11 +65,6 @@ class RemoteFullImageProvider extends CancellableImageProvider obtainKey(ImageConfiguration configuration) { return SynchronousFuture(this);