diff --git a/lib/model/entry.dart b/lib/model/entry.dart index be81490c3..0b6f72511 100644 --- a/lib/model/entry.dart +++ b/lib/model/entry.dart @@ -16,6 +16,7 @@ import 'package:aves/services/geocoding_service.dart'; import 'package:aves/services/metadata/svg_metadata_service.dart'; import 'package:aves/theme/format.dart'; import 'package:aves/utils/change_notifier.dart'; +import 'package:aves/utils/time_utils.dart'; import 'package:collection/collection.dart'; import 'package:country_code/country_code.dart'; import 'package:flutter/foundation.dart'; @@ -349,12 +350,17 @@ class AvesEntry { DateTime? get bestDate { if (_bestDate == null) { - if ((_catalogDateMillis ?? 0) > 0) { - _bestDate = DateTime.fromMillisecondsSinceEpoch(_catalogDateMillis!); - } else if ((sourceDateTakenMillis ?? 0) > 0) { - _bestDate = DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis!); - } else if ((dateModifiedSecs ?? 0) > 0) { - _bestDate = DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs! * 1000); + try { + if ((_catalogDateMillis ?? 0) > 0) { + _bestDate = DateTime.fromMillisecondsSinceEpoch(_catalogDateMillis!); + } else if ((sourceDateTakenMillis ?? 0) > 0) { + _bestDate = DateTime.fromMillisecondsSinceEpoch(sourceDateTakenMillis!); + } else if ((dateModifiedSecs ?? 0) > 0) { + _bestDate = DateTime.fromMillisecondsSinceEpoch(dateModifiedSecs! * 1000); + } + } catch (e, stack) { + // date millis may be out of range + reportService.recordError(e, stack); } } return _bestDate; @@ -748,13 +754,11 @@ class AvesEntry { return c != 0 ? c : compareAsciiUpperCase(a.extension ?? '', b.extension ?? ''); } - static final _epoch = DateTime.fromMillisecondsSinceEpoch(0); - // compare by: // 1) date descending // 2) name descending static int compareByDate(AvesEntry a, AvesEntry b) { - var c = (b.bestDate ?? _epoch).compareTo(a.bestDate ?? _epoch); + var c = (b.bestDate ?? epoch).compareTo(a.bestDate ?? epoch); if (c != 0) return c; return compareByName(b, a); } diff --git a/lib/model/video/metadata.dart b/lib/model/video/metadata.dart index 924d45918..04146985d 100644 --- a/lib/model/video/metadata.dart +++ b/lib/model/video/metadata.dart @@ -15,13 +15,13 @@ import 'package:aves/theme/format.dart'; import 'package:aves/utils/file_utils.dart'; import 'package:aves/utils/math_utils.dart'; import 'package:aves/utils/string_utils.dart'; +import 'package:aves/utils/time_utils.dart'; import 'package:aves/widgets/viewer/video/fijkplayer.dart'; import 'package:collection/collection.dart'; import 'package:fijkplayer/fijkplayer.dart'; import 'package:flutter/foundation.dart'; class VideoMetadataFormatter { - static final _epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); static final _anotherDatePattern = RegExp(r'(\d{4})[-/](\d{2})[-/](\d{2}) (\d{2}):(\d{2}):(\d{2})'); static final _durationPattern = RegExp(r'(\d+):(\d+):(\d+)(.\d+)'); static final _locationPattern = RegExp(r'([+-][.0-9]+)'); @@ -349,7 +349,7 @@ class VideoMetadataFormatter { static String? _formatDate(String value) { final date = DateTime.tryParse(value); if (date == null) return value; - if (date == _epoch) return null; + if (date == epoch) return null; return date.toIso8601String(); } diff --git a/lib/services/metadata/metadata_fetch_service.dart b/lib/services/metadata/metadata_fetch_service.dart index 5a665af3d..b3e93e5fa 100644 --- a/lib/services/metadata/metadata_fetch_service.dart +++ b/lib/services/metadata/metadata_fetch_service.dart @@ -237,7 +237,14 @@ class PlatformMetadataFetchService implements MetadataFetchService { 'sizeBytes': entry.sizeBytes, 'field': field.toExifInterfaceTag(), }); - if (result is int) return DateTime.fromMillisecondsSinceEpoch(result); + if (result is int) { + try { + return DateTime.fromMillisecondsSinceEpoch(result); + } catch (e, stack) { + // date millis may be out of range + await reportService.recordError(e, stack); + } + } } on PlatformException catch (e, stack) { if (!entry.isMissingAtPath) { await reportService.recordError(e, stack); diff --git a/lib/utils/time_utils.dart b/lib/utils/time_utils.dart index 6980430ba..c49e427b4 100644 --- a/lib/utils/time_utils.dart +++ b/lib/utils/time_utils.dart @@ -1,3 +1,5 @@ +import 'package:aves/services/common/services.dart'; + extension ExtraDateTime on DateTime { bool isAtSameYearAs(DateTime? other) => year == other?.year; @@ -14,6 +16,8 @@ extension ExtraDateTime on DateTime { bool get isThisYear => isAtSameYearAs(DateTime.now()); } +final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); + final _unixStampMillisPattern = RegExp(r'\d{13}'); final _unixStampSecPattern = RegExp(r'\d{10}'); final _plainPattern = RegExp(r'(\d{8})([_-\s](\d{6})([_-\s](\d{3}))?)?'); @@ -23,22 +27,32 @@ DateTime? parseUnknownDateFormat(String? s) { var match = _unixStampMillisPattern.firstMatch(s); if (match != null) { - final stampString = match.group(0); - if (stampString != null) { - final stampMillis = int.tryParse(stampString); + final stampMillisString = match.group(0); + if (stampMillisString != null) { + final stampMillis = int.tryParse(stampMillisString); if (stampMillis != null) { - return DateTime.fromMillisecondsSinceEpoch(stampMillis, isUtc: false); + try { + return DateTime.fromMillisecondsSinceEpoch(stampMillis, isUtc: false); + } catch (e, stack) { + // date millis may be out of range + reportService.recordError(e, stack); + } } } } match = _unixStampSecPattern.firstMatch(s); if (match != null) { - final stampString = match.group(0); - if (stampString != null) { - final stampMillis = int.tryParse(stampString); - if (stampMillis != null) { - return DateTime.fromMillisecondsSinceEpoch(stampMillis * 1000, isUtc: false); + final stampSecString = match.group(0); + if (stampSecString != null) { + final stampSec = int.tryParse(stampSecString); + if (stampSec != null) { + try { + return DateTime.fromMillisecondsSinceEpoch(stampSec * 1000, isUtc: false); + } catch (e, stack) { + // date millis may be out of range + reportService.recordError(e, stack); + } } } } diff --git a/lib/widgets/common/grid/sliver.dart b/lib/widgets/common/grid/sliver.dart index b8f8299ef..d322a5a4f 100644 --- a/lib/widgets/common/grid/sliver.dart +++ b/lib/widgets/common/grid/sliver.dart @@ -76,18 +76,18 @@ class _RenderSliverKnownExtentBoxAdaptor extends RenderSliverMultiBoxAdaptor { SectionLayout? sectionAtIndex(int index) => sectionLayouts.firstWhereOrNull((section) => section.hasChild(index)); - SectionLayout sectionAtOffset(double scrollOffset) => sectionLayouts.firstWhere((section) => section.hasChildAtOffset(scrollOffset), orElse: () => sectionLayouts.last); + SectionLayout? sectionAtOffset(double scrollOffset) => sectionLayouts.firstWhereOrNull((section) => section.hasChildAtOffset(scrollOffset)) ?? sectionLayouts.lastOrNull; double indexToLayoutOffset(int index) { return (sectionAtIndex(index) ?? sectionLayouts.last).indexToLayoutOffset(index); } int getMinChildIndexForScrollOffset(double scrollOffset) { - return sectionAtOffset(scrollOffset).getMinChildIndexForScrollOffset(scrollOffset); + return sectionAtOffset(scrollOffset)?.getMinChildIndexForScrollOffset(scrollOffset) ?? 0; } int getMaxChildIndexForScrollOffset(double scrollOffset) { - return sectionAtOffset(scrollOffset).getMaxChildIndexForScrollOffset(scrollOffset); + return sectionAtOffset(scrollOffset)?.getMaxChildIndexForScrollOffset(scrollOffset) ?? 0; } double estimateMaxScrollOffset( diff --git a/lib/widgets/filter_grids/common/filter_nav_page.dart b/lib/widgets/filter_grids/common/filter_nav_page.dart index cdfff8149..0ed81b797 100644 --- a/lib/widgets/filter_grids/common/filter_nav_page.dart +++ b/lib/widgets/filter_grids/common/filter_nav_page.dart @@ -1,6 +1,8 @@ import 'package:aves/model/filters/filters.dart'; import 'package:aves/model/source/collection_source.dart'; import 'package:aves/model/source/enums.dart'; +import 'package:aves/utils/constants.dart'; +import 'package:aves/utils/time_utils.dart'; import 'package:aves/widgets/common/identity/aves_filter_chip.dart'; import 'package:aves/widgets/common/providers/selection_provider.dart'; import 'package:aves/widgets/filter_grids/common/action_delegates/chip_set.dart'; @@ -63,7 +65,7 @@ class FilterNavigationPage extends StatelessWidget { } static int compareFiltersByDate(FilterGridItem a, FilterGridItem b) { - final c = (b.entry?.bestDate ?? DateTime.fromMillisecondsSinceEpoch(0)).compareTo(a.entry?.bestDate ?? DateTime.fromMillisecondsSinceEpoch(0)); + final c = (b.entry?.bestDate ?? epoch).compareTo(a.entry?.bestDate ?? epoch); return c != 0 ? c : a.filter.compareTo(b.filter); } diff --git a/lib/widgets/viewer/debug/debug_page.dart b/lib/widgets/viewer/debug/debug_page.dart index 607fec7ca..f916b96d5 100644 --- a/lib/widgets/viewer/debug/debug_page.dart +++ b/lib/widgets/viewer/debug/debug_page.dart @@ -53,7 +53,11 @@ class ViewerDebugPage extends StatelessWidget { String toDateValue(int? time, {int factor = 1}) { var value = '$time'; if (time != null && time > 0) { - value += ' (${DateTime.fromMillisecondsSinceEpoch(time * factor)})'; + try { + value += ' (${DateTime.fromMillisecondsSinceEpoch(time * factor)})'; + } catch (e) { + value += ' (invalid DateTime})'; + } } return value; } diff --git a/lib/widgets/viewer/debug/metadata.dart b/lib/widgets/viewer/debug/metadata.dart index abe90ed89..780f7c1a8 100644 --- a/lib/widgets/viewer/debug/metadata.dart +++ b/lib/widgets/viewer/debug/metadata.dart @@ -57,7 +57,11 @@ class _MetadataTabState extends State { if (secondTimestampKeys.contains(key)) { v *= 1000; } - value += ' (${DateTime.fromMillisecondsSinceEpoch(v)})'; + try { + value += ' (${DateTime.fromMillisecondsSinceEpoch(v)})'; + } catch (e) { + value += ' (invalid DateTime})'; + } } if (key == 'xmp' && v != null && v is Uint8List) { value = String.fromCharCodes(v);