use 12/24 hour format system settings
This commit is contained in:
parent
07934bbf9e
commit
b94abc8be4
7 changed files with 137 additions and 116 deletions
|
@ -8,6 +8,7 @@ import android.database.Cursor
|
||||||
import android.database.MatrixCursor
|
import android.database.MatrixCursor
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.text.format.DateFormat
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import deckers.thibault.aves.model.FieldMap
|
import deckers.thibault.aves.model.FieldMap
|
||||||
import deckers.thibault.aves.utils.ContextUtils.resourceUri
|
import deckers.thibault.aves.utils.ContextUtils.resourceUri
|
||||||
|
@ -83,6 +84,7 @@ class SearchSuggestionsProvider : MethodChannel.MethodCallHandler, ContentProvid
|
||||||
backgroundChannel.invokeMethod("getSuggestions", hashMapOf(
|
backgroundChannel.invokeMethod("getSuggestions", hashMapOf(
|
||||||
"query" to query,
|
"query" to query,
|
||||||
"locale" to Locale.getDefault().toString(),
|
"locale" to Locale.getDefault().toString(),
|
||||||
|
"use24hour" to DateFormat.is24HourFormat(context),
|
||||||
), object : MethodChannel.Result {
|
), object : MethodChannel.Result {
|
||||||
override fun success(result: Any?) {
|
override fun success(result: Any?) {
|
||||||
@Suppress("unchecked_cast")
|
@Suppress("unchecked_cast")
|
||||||
|
|
|
@ -47,6 +47,9 @@ Future<List<Map<String, String?>>> _getSuggestions(dynamic args) async {
|
||||||
if (args is Map) {
|
if (args is Map) {
|
||||||
final query = args['query'];
|
final query = args['query'];
|
||||||
final locale = args['locale'];
|
final locale = args['locale'];
|
||||||
|
final use24hour = args['use24hour'];
|
||||||
|
debugPrint('getSuggestions query=$query, locale=$locale use24hour=$use24hour');
|
||||||
|
|
||||||
if (query is String && locale is String) {
|
if (query is String && locale is String) {
|
||||||
final entries = await metadataDb.searchEntries(query, limit: 9);
|
final entries = await metadataDb.searchEntries(query, limit: 9);
|
||||||
suggestions.addAll(entries.map((entry) {
|
suggestions.addAll(entries.map((entry) {
|
||||||
|
@ -55,7 +58,7 @@ Future<List<Map<String, String?>>> _getSuggestions(dynamic args) async {
|
||||||
'data': entry.uri,
|
'data': entry.uri,
|
||||||
'mimeType': entry.mimeType,
|
'mimeType': entry.mimeType,
|
||||||
'title': entry.bestTitle,
|
'title': entry.bestTitle,
|
||||||
'subtitle': date != null ? formatDateTime(date, locale) : null,
|
'subtitle': date != null ? formatDateTime(date, locale, use24hour) : null,
|
||||||
'iconUri': entry.uri,
|
'iconUri': entry.uri,
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -2,9 +2,9 @@ import 'package:intl/intl.dart';
|
||||||
|
|
||||||
String formatDay(DateTime date, String locale) => DateFormat.yMMMd(locale).format(date);
|
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) {
|
String formatFriendlyDuration(Duration d) {
|
||||||
final seconds = (d.inSeconds.remainder(Duration.secondsPerMinute)).toString().padLeft(2, '0');
|
final seconds = (d.inSeconds.remainder(Duration.secondsPerMinute)).toString().padLeft(2, '0');
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/theme/durations.dart';
|
||||||
import 'package:aves/theme/format.dart';
|
import 'package:aves/theme/format.dart';
|
||||||
import 'package:aves/theme/icons.dart';
|
import 'package:aves/theme/icons.dart';
|
||||||
import 'package:aves/widgets/common/extensions/build_context.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:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
@ -44,125 +45,134 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final l10n = context.l10n;
|
return MediaQueryDataProvider(
|
||||||
void _updateAction(DateEditAction? action) {
|
child: Builder(
|
||||||
if (action == null) return;
|
builder: (context) {
|
||||||
setState(() => _action = action);
|
final l10n = context.l10n;
|
||||||
}
|
final locale = l10n.localeName;
|
||||||
|
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
|
||||||
|
|
||||||
Widget _tileText(String text) => Text(
|
void _updateAction(DateEditAction? action) {
|
||||||
text,
|
if (action == null) return;
|
||||||
softWrap: false,
|
setState(() => _action = action);
|
||||||
overflow: TextOverflow.fade,
|
}
|
||||||
maxLines: 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
final setTile = Row(
|
Widget _tileText(String text) => Text(
|
||||||
children: [
|
text,
|
||||||
Expanded(
|
softWrap: false,
|
||||||
child: RadioListTile<DateEditAction>(
|
overflow: TextOverflow.fade,
|
||||||
value: DateEditAction.set,
|
maxLines: 1,
|
||||||
|
);
|
||||||
|
|
||||||
|
final setTile = Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: RadioListTile<DateEditAction>(
|
||||||
|
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<DateEditAction>(
|
||||||
|
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<DateEditAction>(
|
||||||
|
value: DateEditAction.clear,
|
||||||
groupValue: _action,
|
groupValue: _action,
|
||||||
onChanged: _updateAction,
|
onChanged: _updateAction,
|
||||||
title: _tileText(l10n.editEntryDateDialogSet),
|
title: _tileText(l10n.editEntryDateDialogClear),
|
||||||
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<DateEditAction>(
|
|
||||||
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<DateEditAction>(
|
|
||||||
value: DateEditAction.clear,
|
|
||||||
groupValue: _action,
|
|
||||||
onChanged: _updateAction,
|
|
||||||
title: _tileText(l10n.editEntryDateDialogClear),
|
|
||||||
);
|
|
||||||
|
|
||||||
final animationDuration = context.select<DurationsData, Duration>((v) => v.expansionTileAnimation);
|
final animationDuration = context.select<DurationsData, Duration>((v) => v.expansionTileAnimation);
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return Theme(
|
return Theme(
|
||||||
data: theme.copyWith(
|
data: theme.copyWith(
|
||||||
textTheme: theme.textTheme.copyWith(
|
textTheme: theme.textTheme.copyWith(
|
||||||
// dense style font for tile subtitles, without modifying title font
|
// dense style font for tile subtitles, without modifying title font
|
||||||
bodyText2: const TextStyle(fontSize: 12),
|
bodyText2: const TextStyle(fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: AvesDialog(
|
child: AvesDialog(
|
||||||
context: context,
|
context: context,
|
||||||
title: l10n.editEntryDateDialogTitle,
|
title: l10n.editEntryDateDialogTitle,
|
||||||
scrollableContent: [
|
scrollableContent: [
|
||||||
setTile,
|
setTile,
|
||||||
shiftTile,
|
shiftTile,
|
||||||
clearTile,
|
clearTile,
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 1),
|
padding: const EdgeInsets.only(bottom: 1),
|
||||||
child: ExpansionPanelList(
|
child: ExpansionPanelList(
|
||||||
expansionCallback: (index, isExpanded) {
|
expansionCallback: (index, isExpanded) {
|
||||||
setState(() => _showOptions = !isExpanded);
|
setState(() => _showOptions = !isExpanded);
|
||||||
},
|
},
|
||||||
animationDuration: animationDuration,
|
animationDuration: animationDuration,
|
||||||
expandedHeaderPadding: EdgeInsets.zero,
|
expandedHeaderPadding: EdgeInsets.zero,
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
children: [
|
children: [
|
||||||
ExpansionPanel(
|
ExpansionPanel(
|
||||||
headerBuilder: (context, isExpanded) => ListTile(
|
headerBuilder: (context, isExpanded) => ListTile(
|
||||||
title: Text(l10n.editEntryDateDialogFieldSelection),
|
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(
|
actions: [
|
||||||
value: _fields.contains(field),
|
TextButton(
|
||||||
onChanged: (selected) => setState(() => selected ? _fields.add(field) : _fields.remove(field)),
|
onPressed: () => Navigator.pop(context),
|
||||||
title: Text(_fieldTitle(field)),
|
child: Text(MaterialLocalizations.of(context).cancelButtonLabel),
|
||||||
))
|
),
|
||||||
.toList(),
|
TextButton(
|
||||||
),
|
onPressed: () => _submit(context),
|
||||||
isExpanded: _showOptions,
|
child: Text(l10n.applyButtonLabel),
|
||||||
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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,8 +168,10 @@ class _DateRow extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final locale = context.l10n.localeName;
|
final locale = context.l10n.localeName;
|
||||||
|
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
|
||||||
|
|
||||||
final date = entry?.bestDate;
|
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(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(width: MapInfoRow.iconPadding),
|
const SizedBox(width: MapInfoRow.iconPadding),
|
||||||
|
|
|
@ -17,6 +17,7 @@ import 'package:aves/widgets/viewer/info/common.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class BasicSection extends StatelessWidget {
|
class BasicSection extends StatelessWidget {
|
||||||
final AvesEntry entry;
|
final AvesEntry entry;
|
||||||
|
@ -41,6 +42,7 @@ class BasicSection extends StatelessWidget {
|
||||||
final l10n = context.l10n;
|
final l10n = context.l10n;
|
||||||
final infoUnknown = l10n.viewerInfoUnknown;
|
final infoUnknown = l10n.viewerInfoUnknown;
|
||||||
final locale = l10n.localeName;
|
final locale = l10n.localeName;
|
||||||
|
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
|
||||||
|
|
||||||
return AnimatedBuilder(
|
return AnimatedBuilder(
|
||||||
animation: entry.metadataChangeNotifier,
|
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)
|
// inserting ZWSP (\u200B) between characters does help, but it messes with width and height computation (another Flutter issue)
|
||||||
final title = entry.bestTitle ?? infoUnknown;
|
final title = entry.bestTitle ?? infoUnknown;
|
||||||
final date = entry.bestDate;
|
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 showResolution = !entry.isSvg && entry.isSized;
|
||||||
final sizeText = entry.sizeBytes != null ? formatFilesize(entry.sizeBytes!) : infoUnknown;
|
final sizeText = entry.sizeBytes != null ? formatFilesize(entry.sizeBytes!) : infoUnknown;
|
||||||
final path = entry.path;
|
final path = entry.path;
|
||||||
|
|
|
@ -395,8 +395,10 @@ class _DateRow extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final locale = context.l10n.localeName;
|
final locale = context.l10n.localeName;
|
||||||
|
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
|
||||||
|
|
||||||
final date = entry.bestDate;
|
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
|
final resolutionText = entry.isSvg
|
||||||
? entry.aspectRatioText
|
? entry.aspectRatioText
|
||||||
: entry.isSized
|
: entry.isSized
|
||||||
|
|
Loading…
Reference in a new issue