diff --git a/app/lib/widget/builder/photo_list_item_builder.dart b/app/lib/widget/builder/photo_list_item_builder.dart index 0dcf9ebd..e283866b 100644 --- a/app/lib/widget/builder/photo_list_item_builder.dart +++ b/app/lib/widget/builder/photo_list_item_builder.dart @@ -1,4 +1,3 @@ -import 'package:clock/clock.dart'; import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; @@ -14,6 +13,7 @@ import 'package:nc_photos/widget/photo_list_item.dart'; import 'package:nc_photos/widget/photo_list_util.dart'; import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_datetime/np_datetime.dart'; part 'photo_list_item_builder.g.dart'; @@ -135,7 +135,7 @@ class _PhotoListItemBuilder { PhotoListItemBuilderResult _fromSortedItems( Account account, List files) { - final today = clock.now(); + final today = Date.today(); final memoryAlbumHelper = smartAlbumConfig != null ? MemoryCollectionHelper(account, today: today, dayRange: smartAlbumConfig!.memoriesDayRange) diff --git a/app/lib/widget/collection_browser.dart b/app/lib/widget/collection_browser.dart index 2636dcdd..db9d4cc3 100644 --- a/app/lib/widget/collection_browser.dart +++ b/app/lib/widget/collection_browser.dart @@ -62,6 +62,7 @@ import 'package:nc_photos/widget/sliver_visualized_scale.dart'; import 'package:nc_photos/widget/viewer.dart'; import 'package:np_codegen/np_codegen.dart'; import 'package:np_common/or_null.dart'; +import 'package:np_datetime/np_datetime.dart'; import 'package:np_ui/np_ui.dart'; import 'package:to_string/to_string.dart'; diff --git a/app/lib/widget/collection_browser/type.dart b/app/lib/widget/collection_browser/type.dart index 0a29452a..7e7d70a0 100644 --- a/app/lib/widget/collection_browser/type.dart +++ b/app/lib/widget/collection_browser/type.dart @@ -172,7 +172,7 @@ class _DateItem extends _Item { ); } - final DateTime date; + final Date date; } @toString diff --git a/app/lib/widget/home_photos/bloc.dart b/app/lib/widget/home_photos/bloc.dart index 7492d59e..7577d164 100644 --- a/app/lib/widget/home_photos/bloc.dart +++ b/app/lib/widget/home_photos/bloc.dart @@ -376,7 +376,7 @@ _ItemTransformerResult _buildItem(_ItemTransformerArgument arg) { final dateHelper = arg.sort == _ItemSort.dateTime ? photo_list_util.DateGroupHelper(isMonthOnly: !arg.isGroupByDay) : null; - final today = clock.now(); + final today = Date.today(); final memoryCollectionHelper = arg.sort == _ItemSort.dateTime ? photo_list_util.MemoryCollectionHelper( arg.account, @@ -393,7 +393,7 @@ _ItemTransformerResult _buildItem(_ItemTransformerArgument arg) { if (item == null) { continue; } - final localDate = file.fdDateTime.add(tzOffset); + final localDate = file.fdDateTime.add(tzOffset).toDate(); final date = dateHelper?.onFile(file, localDate: localDate); if (date != null) { transformed.add(_DateItem(date: date, isMonthOnly: !arg.isGroupByDay)); diff --git a/app/lib/widget/home_photos/type.dart b/app/lib/widget/home_photos/type.dart index b7ea8521..eb955e9c 100644 --- a/app/lib/widget/home_photos/type.dart +++ b/app/lib/widget/home_photos/type.dart @@ -93,7 +93,7 @@ class _DateItem extends _Item { ); } - final DateTime date; + final Date date; final bool isMonthOnly; } diff --git a/app/lib/widget/home_photos2.dart b/app/lib/widget/home_photos2.dart index b29ce93c..fa61b368 100644 --- a/app/lib/widget/home_photos2.dart +++ b/app/lib/widget/home_photos2.dart @@ -55,6 +55,7 @@ import 'package:np_codegen/np_codegen.dart'; import 'package:np_collection/np_collection.dart'; import 'package:np_common/object_util.dart'; import 'package:np_common/or_null.dart'; +import 'package:np_datetime/np_datetime.dart'; import 'package:to_string/to_string.dart'; import 'package:visibility_detector/visibility_detector.dart'; diff --git a/app/lib/widget/photo_list_item.dart b/app/lib/widget/photo_list_item.dart index a1287887..1f5a7f05 100644 --- a/app/lib/widget/photo_list_item.dart +++ b/app/lib/widget/photo_list_item.dart @@ -12,6 +12,7 @@ import 'package:nc_photos/mobile/android/content_uri_image_provider.dart'; import 'package:nc_photos/theme.dart'; import 'package:nc_photos/widget/network_thumbnail.dart'; import 'package:nc_photos/widget/selectable_item_stream_list_mixin.dart'; +import 'package:np_datetime/np_datetime.dart'; import 'package:to_string/to_string.dart'; part 'photo_list_item.g.dart'; @@ -116,7 +117,7 @@ class PhotoListDateItem extends SelectableItem { isMonthOnly: isMonthOnly, ); - final DateTime date; + final Date date; final bool isMonthOnly; } @@ -392,18 +393,18 @@ class PhotoListLabelEdit extends PhotoListLabel { class PhotoListDate extends StatelessWidget { const PhotoListDate({ - Key? key, + super.key, required this.date, this.isMonthOnly = false, - }) : super(key: key); + }); @override - build(BuildContext context) { + Widget build(BuildContext context) { final pattern = isMonthOnly ? DateFormat.YEAR_MONTH : DateFormat.YEAR_MONTH_DAY; final subtitle = DateFormat(pattern, Localizations.localeOf(context).languageCode) - .format(date.toLocal()); + .format(date.toUtcDateTime()); return Align( alignment: AlignmentDirectional.centerStart, child: Padding( @@ -416,6 +417,6 @@ class PhotoListDate extends StatelessWidget { ); } - final DateTime date; + final Date date; final bool isMonthOnly; } diff --git a/app/lib/widget/photo_list_util.dart b/app/lib/widget/photo_list_util.dart index f8b9335b..883c8d3a 100644 --- a/app/lib/widget/photo_list_util.dart +++ b/app/lib/widget/photo_list_util.dart @@ -9,6 +9,7 @@ import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/collection/content_provider/memory.dart'; import 'package:nc_photos/entity/file_descriptor.dart'; import 'package:np_codegen/np_codegen.dart'; +import 'package:np_datetime/np_datetime.dart'; part 'photo_list_util.g.dart'; @@ -17,13 +18,13 @@ class DateGroupHelper { required this.isMonthOnly, }) : _tzOffset = clock.now().timeZoneOffset; - DateTime? onFile( + Date? onFile( FileDescriptor file, { - DateTime? localDate, + Date? localDate, }) { // toLocal is way too slow // final localDate = file.fdDateTime.toLocal(); - localDate ??= file.fdDateTime.add(_tzOffset); + localDate ??= file.fdDateTime.add(_tzOffset).toDate(); if (localDate.year != _currentDate?.year || localDate.month != _currentDate?.month || (!isMonthOnly && localDate.day != _currentDate?.day)) { @@ -35,7 +36,7 @@ class DateGroupHelper { } final bool isMonthOnly; - DateTime? _currentDate; + Date? _currentDate; final Duration _tzOffset; } @@ -46,21 +47,21 @@ class DateGroupHelper { class MemoryCollectionHelper { MemoryCollectionHelper( this.account, { - DateTime? today, + Date? today, required int dayRange, }) : _tzOffset = clock.now().timeZoneOffset, // today = (today?.toLocal() ?? clock.now()).toMidnight(), dayRange = math.max(dayRange, 0) { - this.today = (today ?? clock.now()).toUtc().add(_tzOffset).toMidnight(); + this.today = today ?? Date.today(); } void addFile( FileDescriptor f, { - DateTime? localDate, + Date? localDate, }) { // too slow // final localDate = f.fdDateTime.toLocal().toMidnight(); - localDate = (localDate ?? f.fdDateTime.add(_tzOffset)).toMidnight(); + localDate ??= f.fdDateTime.add(_tzOffset).toDate(); final diff = today.difference(localDate).inDays; if (diff < 300) { return; @@ -115,7 +116,7 @@ class MemoryCollectionHelper { } final Account account; - late final DateTime today; + late final Date today; final int dayRange; final Duration _tzOffset; final _data = {}; @@ -142,10 +143,14 @@ class _MemoryCollectionHelperItem { _MemoryCollectionHelperItem(this.date, this.coverFile) : coverDiff = getCoverDiff(date, coverFile); - static Duration getCoverDiff(DateTime date, FileDescriptor f) => - f.fdDateTime.difference(date.copyWith(hour: 12)).abs(); + static Duration getCoverDiff(Date date, FileDescriptor f) => f.fdDateTime + .add(_tzOffset) + .difference(date.toLocalDateTime().copyWith(hour: 12)) + .abs(); - final DateTime date; + final Date date; FileDescriptor coverFile; Duration coverDiff; + + static final Duration _tzOffset = clock.now().timeZoneOffset; } diff --git a/app/test/widget/photo_list_util_test.dart b/app/test/widget/photo_list_util_test.dart index f339e593..ba939329 100644 --- a/app/test/widget/photo_list_util_test.dart +++ b/app/test/widget/photo_list_util_test.dart @@ -1,6 +1,7 @@ import 'package:nc_photos/entity/collection.dart'; import 'package:nc_photos/entity/collection/content_provider/memory.dart'; import 'package:nc_photos/widget/photo_list_util.dart'; +import 'package:np_datetime/np_datetime.dart'; import 'package:test/test.dart'; import '../test_util.dart' as util; @@ -52,7 +53,7 @@ void main() { /// Expect: empty void _sameYear() { final account = util.buildAccount(); - final today = DateTime(2021, 2, 3); + final today = Date(2021, 2, 3); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2021, 2, 3)); @@ -68,7 +69,7 @@ void _sameYear() { /// Expect: empty void _nextYear() { final account = util.buildAccount(); - final today = DateTime(2021, 2, 3); + final today = Date(2021, 2, 3); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2022, 2, 3)); @@ -83,7 +84,7 @@ void _nextYear() { /// Expect: [2020] void _prevYear() { final account = util.buildAccount(); - final today = DateTime(2021, 2, 3); + final today = Date(2021, 2, 3); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 2, 3)); @@ -107,7 +108,7 @@ void _prevYear() { /// Expect: empty void _prevYear3DaysBefore() { final account = util.buildAccount(); - final today = DateTime(2021, 2, 3); + final today = Date(2021, 2, 3); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 1, 31)); @@ -122,7 +123,7 @@ void _prevYear3DaysBefore() { /// Expect: [2020] void _prevYear2DaysBefore() { final account = util.buildAccount(); - final today = DateTime(2021, 2, 3); + final today = Date(2021, 2, 3); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 2, 1)); @@ -146,7 +147,7 @@ void _prevYear2DaysBefore() { /// Expect: empty void _prevYear3DaysAfter() { final account = util.buildAccount(); - final today = DateTime(2021, 2, 3); + final today = Date(2021, 2, 3); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 2, 6)); @@ -161,7 +162,7 @@ void _prevYear3DaysAfter() { /// Expect: [2020] void _prevYear2DaysAfter() { final account = util.buildAccount(); - final today = DateTime(2021, 2, 3); + final today = Date(2021, 2, 3); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 2, 5)); @@ -185,7 +186,7 @@ void _prevYear2DaysAfter() { /// Expect: empty void _onFeb29AddFeb26() { final account = util.buildAccount(); - final today = DateTime(2020, 2, 29); + final today = Date(2020, 2, 29); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2019, 2, 26)); @@ -200,7 +201,7 @@ void _onFeb29AddFeb26() { /// Expect: [2019] void _onFeb29AddFeb27() { final account = util.buildAccount(); - final today = DateTime(2020, 2, 29); + final today = Date(2020, 2, 29); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2019, 2, 27)); @@ -224,7 +225,7 @@ void _onFeb29AddFeb27() { /// Expect: empty void _onFeb29AddMar4() { final account = util.buildAccount(); - final today = DateTime(2020, 2, 29); + final today = Date(2020, 2, 29); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2019, 3, 4)); @@ -239,7 +240,7 @@ void _onFeb29AddMar4() { /// Expect: [2019] void _onFeb29AddMar3() { final account = util.buildAccount(); - final today = DateTime(2020, 2, 29); + final today = Date(2020, 2, 29); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2019, 3, 3)); @@ -263,7 +264,7 @@ void _onFeb29AddMar3() { /// Expect: empty void _onFeb29AddMar3LeapYear() { final account = util.buildAccount(); - final today = DateTime(2020, 2, 29); + final today = Date(2020, 2, 29); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2016, 3, 3)); @@ -278,7 +279,7 @@ void _onFeb29AddMar3LeapYear() { /// Expect: [2016] void _onFeb29AddMar2LeapYear() { final account = util.buildAccount(); - final today = DateTime(2020, 2, 29); + final today = Date(2020, 2, 29); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2016, 3, 2)); @@ -302,7 +303,7 @@ void _onFeb29AddMar2LeapYear() { /// Expect: empty void _onJan1AddDec31() { final account = util.buildAccount(); - final today = DateTime(2020, 1, 1); + final today = Date(2020, 1, 1); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2019, 12, 31)); @@ -317,7 +318,7 @@ void _onJan1AddDec31() { /// Expect: [2019] void _onJan1AddDec31PrevYear() { final account = util.buildAccount(); - final today = DateTime(2020, 1, 1); + final today = Date(2020, 1, 1); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2018, 12, 31)); @@ -341,7 +342,7 @@ void _onJan1AddDec31PrevYear() { /// Expect: [2019] void _onDec31AddJan1() { final account = util.buildAccount(); - final today = DateTime(2020, 12, 31); + final today = Date(2020, 12, 31); final obj = MemoryCollectionHelper(account, today: today, dayRange: 2); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2020, 1, 1)); @@ -365,7 +366,7 @@ void _onDec31AddJan1() { /// Expect: [2022] void _onMay15AddMay15Range0() { final account = util.buildAccount(); - final today = DateTime(2022, 5, 15); + final today = Date(2022, 5, 15); final obj = MemoryCollectionHelper(account, today: today, dayRange: 0); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2021, 5, 15)); @@ -389,7 +390,7 @@ void _onMay15AddMay15Range0() { /// Expect: [] void _onMay15AddMay16Range0() { final account = util.buildAccount(); - final today = DateTime(2022, 5, 15); + final today = Date(2022, 5, 15); final obj = MemoryCollectionHelper(account, today: today, dayRange: 0); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2021, 5, 16)); @@ -404,7 +405,7 @@ void _onMay15AddMay16Range0() { /// Expect: [] void _onMay15AddMay16RangeNegative() { final account = util.buildAccount(); - final today = DateTime(2022, 5, 15); + final today = Date(2022, 5, 15); final obj = MemoryCollectionHelper(account, today: today, dayRange: -1); final file = util.buildJpegFile( path: "", fileId: 0, lastModified: DateTime.utc(2021, 5, 16)); diff --git a/np_datetime/lib/np_datetime.dart b/np_datetime/lib/np_datetime.dart index cb765214..d21d97a7 100644 --- a/np_datetime/lib/np_datetime.dart +++ b/np_datetime/lib/np_datetime.dart @@ -1,3 +1,4 @@ library np_datetime; +export 'src/date.dart'; export 'src/time_range.dart'; diff --git a/np_datetime/lib/src/date.dart b/np_datetime/lib/src/date.dart new file mode 100644 index 00000000..9b0f7ec6 --- /dev/null +++ b/np_datetime/lib/src/date.dart @@ -0,0 +1,100 @@ +import 'package:clock/clock.dart'; + +/// A calendar date with no timezone information +class Date implements Comparable { + factory Date(int year, [int month = 1, int day = 1]) { + final d = DateTime.utc(year, month, day); + return Date._unchecked(d.year, d.month, d.day); + } + + const Date._unchecked(this.year, [this.month = 1, this.day = 1]); + + /// Convert a [DateTime] object to [Date]. The data is taken from [dateTime] + /// as-is, the timezone will not be considered + static Date fromDateTime(DateTime dateTime) => + Date._unchecked(dateTime.year, dateTime.month, dateTime.day); + + static Date today() => fromDateTime(clock.now()); + + Date copyWith({ + int? year, + int? month, + int? day, + }) { + return Date(year ?? this.year, month ?? this.month, day ?? this.day); + } + + DateTime toUtcDateTime() => DateTime.utc(year, month, day); + DateTime toLocalDateTime() => DateTime(year, month, day); + + @override + int compareTo(Date other) => toUtcDateTime().compareTo(other.toUtcDateTime()); + + @override + String toString() => "$day/$month/$year"; + + @override + bool operator ==(Object other) => + other is Date && + year == other.year && + month == other.month && + day == other.day; + + @override + int get hashCode => Object.hash(year, month, day); + + final int year; + final int month; + final int day; +} + +extension DateExtension on Date { + Date add({ + int? year, + int? month, + int? day, + }) { + final d = DateTime.utc(this.year + (year ?? 0), this.month + (month ?? 0), + this.day + (day ?? 0)); + return Date(d.year, d.month, d.day); + } + + Duration difference(Date other) => + toUtcDateTime().difference(other.toUtcDateTime()); + + bool isBefore(Date other) { + if (year > other.year) { + return false; + } else if (year < other.year) { + return true; + } + if (month > other.month) { + return false; + } else if (month < other.month) { + return true; + } + return day < other.day; + } + + bool isBeforeOrAt(Date other) => !isAfter(other); + + bool isAfter(Date other) { + if (year < other.year) { + return false; + } else if (year > other.year) { + return true; + } + if (month < other.month) { + return false; + } else if (month > other.month) { + return true; + } + return day > other.day; + } + + bool isAfterOrAt(Date other) => !isBefore(other); +} + +extension DateTimeDateExtension on DateTime { + Date toDate() => Date.fromDateTime(this); +} diff --git a/np_datetime/pubspec.yaml b/np_datetime/pubspec.yaml index 2daff614..b3e6f58b 100644 --- a/np_datetime/pubspec.yaml +++ b/np_datetime/pubspec.yaml @@ -7,6 +7,9 @@ publish_to: none environment: sdk: '>=2.19.6 <3.0.0' +dependencies: + clock: ^1.1.1 + dev_dependencies: np_lints: path: ../np_lints diff --git a/np_db/lib/src/api.dart b/np_db/lib/src/api.dart index 2c773666..493aca4e 100644 --- a/np_db/lib/src/api.dart +++ b/np_db/lib/src/api.dart @@ -148,7 +148,7 @@ class DbFilesSummary { String toString() => _$toString(); @Format(r"{length: ${$?.length}}") - final Map items; + final Map items; } @npLog diff --git a/np_db_sqlite/lib/src/database/file_extension.dart b/np_db_sqlite/lib/src/database/file_extension.dart index 190e6c4f..074f5049 100644 --- a/np_db_sqlite/lib/src/database/file_extension.dart +++ b/np_db_sqlite/lib/src/database/file_extension.dart @@ -45,7 +45,7 @@ class CountFileGroupsByDateResult { required this.dateCount, }); - final Map dateCount; + final Map dateCount; } extension SqliteDbFileExtension on SqliteDb { @@ -657,8 +657,8 @@ extension SqliteDbFileExtension on SqliteDb { ..orderBy([OrderingTerm.desc(accountFiles.bestDateTime)]) ..groupBy([localDate]); final results = await query - .map((r) => MapEntry( - DateTime.parse(r.read(localDate)!), + .map((r) => MapEntry( + DateTime.parse(r.read(localDate)!).toDate(), r.read(count)!, )) .get();