diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt b/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt index e045954f9..bb967a4a8 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/SearchSuggestionsProvider.kt @@ -8,6 +8,7 @@ import android.database.Cursor import android.database.MatrixCursor import android.net.Uri import android.os.Build +import android.text.format.DateFormat import android.util.Log import deckers.thibault.aves.model.FieldMap import deckers.thibault.aves.utils.ContextUtils.resourceUri @@ -83,6 +84,7 @@ class SearchSuggestionsProvider : MethodChannel.MethodCallHandler, ContentProvid backgroundChannel.invokeMethod("getSuggestions", hashMapOf( "query" to query, "locale" to Locale.getDefault().toString(), + "use24hour" to DateFormat.is24HourFormat(context), ), object : MethodChannel.Result { override fun success(result: Any?) { @Suppress("unchecked_cast") diff --git a/lib/services/global_search.dart b/lib/services/global_search.dart index d66ba2c17..2edc86528 100644 --- a/lib/services/global_search.dart +++ b/lib/services/global_search.dart @@ -47,6 +47,9 @@ Future>> _getSuggestions(dynamic args) async { if (args is Map) { final query = args['query']; final locale = args['locale']; + final use24hour = args['use24hour']; + debugPrint('getSuggestions query=$query, locale=$locale use24hour=$use24hour'); + if (query is String && locale is String) { final entries = await metadataDb.searchEntries(query, limit: 9); suggestions.addAll(entries.map((entry) { @@ -55,7 +58,7 @@ Future>> _getSuggestions(dynamic args) async { 'data': entry.uri, 'mimeType': entry.mimeType, 'title': entry.bestTitle, - 'subtitle': date != null ? formatDateTime(date, locale) : null, + 'subtitle': date != null ? formatDateTime(date, locale, use24hour) : null, 'iconUri': entry.uri, }; })); diff --git a/lib/theme/format.dart b/lib/theme/format.dart index 48de7d153..e0ce13501 100644 --- a/lib/theme/format.dart +++ b/lib/theme/format.dart @@ -2,9 +2,9 @@ import 'package:intl/intl.dart'; String formatDay(DateTime date, String locale) => DateFormat.yMMMd(locale).format(date); -String formatTime(DateTime date, String locale) => DateFormat.Hm(locale).format(date); +String formatTime(DateTime date, String locale, bool use24hour) => (use24hour ? DateFormat.Hm(locale) : DateFormat.jm(locale)).format(date); -String formatDateTime(DateTime date, String locale) => '${formatDay(date, locale)} • ${formatTime(date, locale)}'; +String formatDateTime(DateTime date, String locale, bool use24hour) => '${formatDay(date, locale)} • ${formatTime(date, locale, use24hour)}'; String formatFriendlyDuration(Duration d) { final seconds = (d.inSeconds.remainder(Duration.secondsPerMinute)).toString().padLeft(2, '0'); diff --git a/lib/widgets/dialogs/edit_entry_date_dialog.dart b/lib/widgets/dialogs/edit_entry_date_dialog.dart index 064e2181a..87aa06ff1 100644 --- a/lib/widgets/dialogs/edit_entry_date_dialog.dart +++ b/lib/widgets/dialogs/edit_entry_date_dialog.dart @@ -5,6 +5,7 @@ import 'package:aves/theme/durations.dart'; import 'package:aves/theme/format.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; +import 'package:aves/widgets/common/providers/media_query_data_provider.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -44,125 +45,134 @@ class _EditEntryDateDialogState extends State { @override Widget build(BuildContext context) { - final l10n = context.l10n; - void _updateAction(DateEditAction? action) { - if (action == null) return; - setState(() => _action = action); - } + return MediaQueryDataProvider( + child: Builder( + builder: (context) { + final l10n = context.l10n; + final locale = l10n.localeName; + final use24hour = context.select((v) => v.alwaysUse24HourFormat); - Widget _tileText(String text) => Text( - text, - softWrap: false, - overflow: TextOverflow.fade, - maxLines: 1, - ); + void _updateAction(DateEditAction? action) { + if (action == null) return; + setState(() => _action = action); + } - final setTile = Row( - children: [ - Expanded( - child: RadioListTile( - value: DateEditAction.set, + Widget _tileText(String text) => Text( + text, + softWrap: false, + overflow: TextOverflow.fade, + maxLines: 1, + ); + + final setTile = Row( + children: [ + Expanded( + child: RadioListTile( + value: DateEditAction.set, + groupValue: _action, + onChanged: _updateAction, + title: _tileText(l10n.editEntryDateDialogSet), + subtitle: Text(formatDateTime(_dateTime, locale, use24hour)), + ), + ), + Padding( + padding: const EdgeInsetsDirectional.only(end: 12), + child: IconButton( + icon: const Icon(AIcons.edit), + onPressed: _action == DateEditAction.set ? _editDate : null, + tooltip: l10n.changeTooltip, + ), + ), + ], + ); + final shiftTile = Row( + children: [ + Expanded( + child: RadioListTile( + value: DateEditAction.shift, + groupValue: _action, + onChanged: _updateAction, + title: _tileText(l10n.editEntryDateDialogShift), + subtitle: Text(_formatShiftDuration()), + ), + ), + Padding( + padding: const EdgeInsetsDirectional.only(end: 12), + child: IconButton( + icon: const Icon(AIcons.edit), + onPressed: _action == DateEditAction.shift ? _editShift : null, + tooltip: l10n.changeTooltip, + ), + ), + ], + ); + final clearTile = RadioListTile( + value: DateEditAction.clear, groupValue: _action, onChanged: _updateAction, - title: _tileText(l10n.editEntryDateDialogSet), - subtitle: Text(formatDateTime(_dateTime, l10n.localeName)), - ), - ), - Padding( - padding: const EdgeInsetsDirectional.only(end: 12), - child: IconButton( - icon: const Icon(AIcons.edit), - onPressed: _action == DateEditAction.set ? _editDate : null, - tooltip: l10n.changeTooltip, - ), - ), - ], - ); - final shiftTile = Row( - children: [ - Expanded( - child: RadioListTile( - value: DateEditAction.shift, - groupValue: _action, - onChanged: _updateAction, - title: _tileText(l10n.editEntryDateDialogShift), - subtitle: Text(_formatShiftDuration()), - ), - ), - Padding( - padding: const EdgeInsetsDirectional.only(end: 12), - child: IconButton( - icon: const Icon(AIcons.edit), - onPressed: _action == DateEditAction.shift ? _editShift : null, - tooltip: l10n.changeTooltip, - ), - ), - ], - ); - final clearTile = RadioListTile( - value: DateEditAction.clear, - groupValue: _action, - onChanged: _updateAction, - title: _tileText(l10n.editEntryDateDialogClear), - ); + title: _tileText(l10n.editEntryDateDialogClear), + ); - final animationDuration = context.select((v) => v.expansionTileAnimation); - final theme = Theme.of(context); - return Theme( - data: theme.copyWith( - textTheme: theme.textTheme.copyWith( - // dense style font for tile subtitles, without modifying title font - bodyText2: const TextStyle(fontSize: 12), - ), - ), - child: AvesDialog( - context: context, - title: l10n.editEntryDateDialogTitle, - scrollableContent: [ - setTile, - shiftTile, - clearTile, - Padding( - padding: const EdgeInsets.only(bottom: 1), - child: ExpansionPanelList( - expansionCallback: (index, isExpanded) { - setState(() => _showOptions = !isExpanded); - }, - animationDuration: animationDuration, - expandedHeaderPadding: EdgeInsets.zero, - elevation: 0, - children: [ - ExpansionPanel( - headerBuilder: (context, isExpanded) => ListTile( - title: Text(l10n.editEntryDateDialogFieldSelection), + final animationDuration = context.select((v) => v.expansionTileAnimation); + final theme = Theme.of(context); + return Theme( + data: theme.copyWith( + textTheme: theme.textTheme.copyWith( + // dense style font for tile subtitles, without modifying title font + bodyText2: const TextStyle(fontSize: 12), + ), + ), + child: AvesDialog( + context: context, + title: l10n.editEntryDateDialogTitle, + scrollableContent: [ + setTile, + shiftTile, + clearTile, + Padding( + padding: const EdgeInsets.only(bottom: 1), + child: ExpansionPanelList( + expansionCallback: (index, isExpanded) { + setState(() => _showOptions = !isExpanded); + }, + animationDuration: animationDuration, + expandedHeaderPadding: EdgeInsets.zero, + elevation: 0, + children: [ + ExpansionPanel( + headerBuilder: (context, isExpanded) => ListTile( + title: Text(l10n.editEntryDateDialogFieldSelection), + ), + body: Column( + children: DateModifier.allDateFields + .map((field) => SwitchListTile( + value: _fields.contains(field), + onChanged: (selected) => setState(() => selected ? _fields.add(field) : _fields.remove(field)), + title: Text(_fieldTitle(field)), + )) + .toList(), + ), + isExpanded: _showOptions, + canTapOnHeader: true, + backgroundColor: Theme.of(context).dialogBackgroundColor, + ), + ], ), - body: Column( - children: DateModifier.allDateFields - .map((field) => SwitchListTile( - value: _fields.contains(field), - onChanged: (selected) => setState(() => selected ? _fields.add(field) : _fields.remove(field)), - title: Text(_fieldTitle(field)), - )) - .toList(), - ), - isExpanded: _showOptions, - canTapOnHeader: true, - backgroundColor: Theme.of(context).dialogBackgroundColor, + ), + ], + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text(MaterialLocalizations.of(context).cancelButtonLabel), + ), + TextButton( + onPressed: () => _submit(context), + child: Text(l10n.applyButtonLabel), ), ], ), - ), - ], - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: Text(MaterialLocalizations.of(context).cancelButtonLabel), - ), - TextButton( - onPressed: () => _submit(context), - child: Text(l10n.applyButtonLabel), - ), - ], + ); + }, ), ); } diff --git a/lib/widgets/map/map_info_row.dart b/lib/widgets/map/map_info_row.dart index ee267c138..aa589a510 100644 --- a/lib/widgets/map/map_info_row.dart +++ b/lib/widgets/map/map_info_row.dart @@ -168,8 +168,10 @@ class _DateRow extends StatelessWidget { @override Widget build(BuildContext context) { final locale = context.l10n.localeName; + final use24hour = context.select((v) => v.alwaysUse24HourFormat); + final date = entry?.bestDate; - final dateText = date != null ? formatDateTime(date, locale) : Constants.overlayUnknown; + final dateText = date != null ? formatDateTime(date, locale, use24hour) : Constants.overlayUnknown; return Row( children: [ const SizedBox(width: MapInfoRow.iconPadding), diff --git a/lib/widgets/viewer/info/basic_section.dart b/lib/widgets/viewer/info/basic_section.dart index 8fa125ab9..b8d062669 100644 --- a/lib/widgets/viewer/info/basic_section.dart +++ b/lib/widgets/viewer/info/basic_section.dart @@ -17,6 +17,7 @@ import 'package:aves/widgets/viewer/info/common.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class BasicSection extends StatelessWidget { final AvesEntry entry; @@ -41,6 +42,7 @@ class BasicSection extends StatelessWidget { final l10n = context.l10n; final infoUnknown = l10n.viewerInfoUnknown; final locale = l10n.localeName; + final use24hour = context.select((v) => v.alwaysUse24HourFormat); return AnimatedBuilder( animation: entry.metadataChangeNotifier, @@ -49,7 +51,7 @@ class BasicSection extends StatelessWidget { // inserting ZWSP (\u200B) between characters does help, but it messes with width and height computation (another Flutter issue) final title = entry.bestTitle ?? infoUnknown; final date = entry.bestDate; - final dateText = date != null ? formatDateTime(date, locale) : infoUnknown; + final dateText = date != null ? formatDateTime(date, locale, use24hour) : infoUnknown; final showResolution = !entry.isSvg && entry.isSized; final sizeText = entry.sizeBytes != null ? formatFilesize(entry.sizeBytes!) : infoUnknown; final path = entry.path; diff --git a/lib/widgets/viewer/overlay/bottom/common.dart b/lib/widgets/viewer/overlay/bottom/common.dart index 82c264bf0..7762d43c2 100644 --- a/lib/widgets/viewer/overlay/bottom/common.dart +++ b/lib/widgets/viewer/overlay/bottom/common.dart @@ -395,8 +395,10 @@ class _DateRow extends StatelessWidget { @override Widget build(BuildContext context) { final locale = context.l10n.localeName; + final use24hour = context.select((v) => v.alwaysUse24HourFormat); + final date = entry.bestDate; - final dateText = date != null ? formatDateTime(date, locale) : Constants.overlayUnknown; + final dateText = date != null ? formatDateTime(date, locale, use24hour) : Constants.overlayUnknown; final resolutionText = entry.isSvg ? entry.aspectRatioText : entry.isSized