l10n: fixes for RTL

This commit is contained in:
Thibault Deckers 2024-05-20 01:41:00 +02:00
parent dd6258d8ac
commit ff16651ce1
15 changed files with 181 additions and 151 deletions

View file

@ -2,6 +2,12 @@ import 'dart:ui';
const String asciiLocale = 'en_US';
// time components hours/minutes/seconds are always displayed in that order
const TextDirection timeComponentsDirection = TextDirection.ltr;
// represents direction of tape being played, not direction of time
const TextDirection videoPlaybackDirection = TextDirection.ltr;
// cf https://en.wikipedia.org/wiki/Eastern_Arabic_numerals
bool shouldUseNativeDigits(Locale? countrifiedLocale) {
switch (countrifiedLocale?.toString()) {

View file

@ -1,3 +1,4 @@
import 'package:aves/ref/locales.dart';
import 'package:aves/utils/time_utils.dart';
import 'package:aves/widgets/common/basic/wheel.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
@ -48,8 +49,7 @@ class _DurationDialogState extends State<DurationDialog> {
padding: const EdgeInsets.only(top: 16),
child: Center(
child: Table(
// even when ambient direction is RTL, time is displayed in LTR
textDirection: TextDirection.ltr,
textDirection: timeComponentsDirection,
children: [
TableRow(
children: [

View file

@ -1,6 +1,7 @@
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/metadata/date_modifier.dart';
import 'package:aves/model/source/collection_lens.dart';
import 'package:aves/ref/locales.dart';
import 'package:aves/theme/durations.dart';
import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart';
@ -204,8 +205,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
const textStyle = TextStyle(fontSize: 34);
return Center(
child: Table(
// even when ambient direction is RTL, time is displayed in LTR
textDirection: TextDirection.ltr,
textDirection: timeComponentsDirection,
children: [
TableRow(
children: [

View file

@ -19,6 +19,7 @@ import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class QuickActionEditorPage<T extends Object> extends StatelessWidget {
final String title, bannerText;
final TextDirection? displayedButtonsDirection;
final List<List<T>> allAvailableActions;
final Widget Function(T action) actionIcon;
final String Function(BuildContext context, T action) actionText;
@ -29,6 +30,7 @@ class QuickActionEditorPage<T extends Object> extends StatelessWidget {
super.key,
required this.title,
required this.bannerText,
this.displayedButtonsDirection,
required this.allAvailableActions,
required this.actionIcon,
required this.actionText,
@ -45,6 +47,7 @@ class QuickActionEditorPage<T extends Object> extends StatelessWidget {
body: SafeArea(
child: QuickActionEditorBody(
bannerText: bannerText,
displayedButtonsDirection: displayedButtonsDirection,
allAvailableActions: allAvailableActions,
actionIcon: actionIcon,
actionText: actionText,
@ -58,6 +61,7 @@ class QuickActionEditorPage<T extends Object> extends StatelessWidget {
class QuickActionEditorBody<T extends Object> extends StatefulWidget {
final String bannerText;
final TextDirection? displayedButtonsDirection;
final List<List<T>> allAvailableActions;
final Widget Function(T action) actionIcon;
final String Function(BuildContext context, T action) actionText;
@ -67,6 +71,7 @@ class QuickActionEditorBody<T extends Object> extends StatefulWidget {
const QuickActionEditorBody({
super.key,
required this.bannerText,
this.displayedButtonsDirection,
required this.allAvailableActions,
required this.actionIcon,
required this.actionText,
@ -147,6 +152,7 @@ class _QuickActionEditorBodyState<T extends Object> extends State<QuickActionEdi
removeAction: _removeQuickAction,
onTargetLeave: _onQuickActionTargetLeave,
);
final originalDirection = Directionality.of(context);
return PopScope(
canPop: true,
onPopInvoked: (didPop) => widget.save(_quickActions),
@ -176,6 +182,8 @@ class _QuickActionEditorBodyState<T extends Object> extends State<QuickActionEdi
highlight: highlight,
child: child!,
),
child: Directionality(
textDirection: widget.displayedButtonsDirection ?? originalDirection,
child: SizedBox(
height: OverlayButton.getSize(context) + quickActionVerticalPadding * 2,
child: Stack(
@ -205,7 +213,9 @@ class _QuickActionEditorBodyState<T extends Object> extends State<QuickActionEdi
itemBuilder: (context, index, animation) {
if (index >= _quickActions.length) return const SizedBox();
final action = _quickActions[index];
return QuickActionButton<T>(
return Directionality(
textDirection: originalDirection,
child: QuickActionButton<T>(
placement: QuickActionPlacement.action,
action: action,
panelHighlight: _quickActionHighlight,
@ -221,6 +231,7 @@ class _QuickActionEditorBodyState<T extends Object> extends State<QuickActionEdi
onPressed: () {},
),
child: _buildQuickActionButton(action, animation),
),
);
},
),
@ -240,6 +251,7 @@ class _QuickActionEditorBodyState<T extends Object> extends State<QuickActionEdi
),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(

View file

@ -2,6 +2,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/view/view.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/settings/common/quick_actions/editor_page.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart';
import 'package:aves_model/aves_model.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
@ -40,6 +41,7 @@ class ViewerActionEditorPage extends StatelessWidget {
return QuickActionEditorPage<EntryAction>(
title: context.l10n.settingsViewerQuickActionEditorPageTitle,
bannerText: context.l10n.settingsViewerQuickActionEditorBanner,
displayedButtonsDirection: ViewerBottomOverlay.actionsDirection,
allAvailableActions: allAvailableActions,
actionIcon: (action) => action.getIcon(),
actionText: (context, action) => action.getText(context),

View file

@ -30,6 +30,9 @@ class ViewerBottomOverlay extends StatefulWidget {
final EdgeInsets? viewInsets, viewPadding;
final MultiPageController? multiPageController;
// always keep action buttons in the lower right corner, even with RTL locales
static const actionsDirection = TextDirection.ltr;
const ViewerBottomOverlay({
super.key,
required this.entries,
@ -185,10 +188,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
builder: (context, child) {
final viewInsetsPadding = (widget.viewInsets ?? EdgeInsets.zero) + (widget.viewPadding ?? EdgeInsets.zero);
final selection = context.read<Selection<AvesEntry>?>();
final viewerButtonRow = Directionality(
// always keep action buttons in the lower right corner, even with RTL locales
textDirection: TextDirection.ltr,
child: (selection?.isSelecting ?? false)
final viewerButtonRow = (selection?.isSelecting ?? false)
? SelectionButton(
mainEntry: mainEntry,
scale: _buttonScale,
@ -222,7 +222,6 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
scale: _buttonScale,
),
),
),
);
final showMultiPageOverlay = mainEntry.isMultiPage && multiPageController != null;
@ -250,8 +249,7 @@ class _BottomOverlayContentState extends State<_BottomOverlayContent> {
(showMultiPageOverlay && collapsedPageScroller)
? Row(
crossAxisAlignment: CrossAxisAlignment.center,
// always keep action buttons in the lower right corner, even with RTL locales
textDirection: TextDirection.ltr,
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
SafeArea(
top: false,

View file

@ -65,7 +65,7 @@ class _ViewerLockedOverlayState extends State<ViewerLockedOverlay> {
builder: (context, mqPaddingBottom, child) {
final viewInsetsPadding = (widget.viewInsets ?? EdgeInsets.zero) + (widget.viewPadding ?? EdgeInsets.zero);
return Container(
alignment: AlignmentDirectional.bottomEnd,
alignment: Alignment.bottomRight,
padding: EdgeInsets.only(bottom: mqPaddingBottom) + const EdgeInsets.all(ViewerButtonRowContent.padding),
child: SafeArea(
top: false,

View file

@ -4,6 +4,7 @@ import 'package:aves/model/entry/entry.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart';
import 'package:aves/widgets/viewer/panorama_page.dart';
import 'package:flutter/material.dart';
@ -20,6 +21,7 @@ class PanoramaOverlay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Row(
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
const Spacer(),
ScalingOverlayTextButton(

View file

@ -6,6 +6,7 @@ import 'package:aves/theme/text.dart';
import 'package:aves/widgets/common/basic/text/animated_diff.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -33,6 +34,7 @@ class SelectionButton extends StatelessWidget {
padding: const EdgeInsets.only(left: padding, right: padding, bottom: padding),
child: Row(
mainAxisSize: MainAxisSize.min,
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
const Spacer(),
ScalingOverlayTextButton(
@ -43,6 +45,7 @@ class SelectionButton extends StatelessWidget {
builder: (context, count, child) {
return Row(
mainAxisSize: MainAxisSize.min,
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
AnimatedDiffText(
count == 0 ? l10n.collectionSelectPageTitle : l10n.itemCount(count),

View file

@ -1,6 +1,7 @@
import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart';
import 'package:aves_video/aves_video.dart';
import 'package:flutter/material.dart';
@ -55,6 +56,7 @@ class _VideoABRepeatOverlayState extends State<VideoABRepeatOverlay> {
);
}
return Row(
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
const Spacer(),
OverlayButton(

View file

@ -3,6 +3,7 @@ import 'package:aves/model/settings/settings.dart';
import 'package:aves/view/view.dart';
import 'package:aves/widgets/common/action_controls/togglers/play.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart';
import 'package:aves_model/aves_model.dart';
import 'package:aves_video/aves_video.dart';
import 'package:flutter/material.dart';
@ -33,7 +34,7 @@ class VideoControlRow extends StatelessWidget {
switch (videoControls) {
case VideoControls.play:
return Padding(
padding: const EdgeInsetsDirectional.only(start: padding),
padding: const EdgeInsets.only(left: padding),
child: _buildOverlayButton(
child: PlayToggler(
controller: controller,
@ -44,6 +45,7 @@ class VideoControlRow extends StatelessWidget {
case VideoControls.playSeek:
return Row(
mainAxisSize: MainAxisSize.min,
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
const SizedBox(width: padding),
_buildIconButton(
@ -67,7 +69,7 @@ class VideoControlRow extends StatelessWidget {
);
case VideoControls.playOutside:
return Padding(
padding: const EdgeInsetsDirectional.only(start: padding),
padding: const EdgeInsets.only(left: padding),
child: _buildIconButton(context, EntryAction.openVideo, enabled: !entry.trashed),
);
case VideoControls.none:

View file

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:aves/model/settings/settings.dart';
import 'package:aves/ref/locales.dart';
import 'package:aves/theme/format.dart';
import 'package:aves/theme/icons.dart';
import 'package:aves/theme/styles.dart';
@ -122,8 +123,7 @@ class _VideoProgressBarState extends State<VideoProgressBar> {
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: Directionality(
// force directionality for `LinearProgressIndicator`
textDirection: TextDirection.ltr,
textDirection: videoPlaybackDirection,
child: StreamBuilder<int>(
stream: positionStream,
builder: (context, snapshot) {

View file

@ -3,6 +3,7 @@ import 'dart:async';
import 'package:aves/model/entry/entry.dart';
import 'package:aves/view/view.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart';
import 'package:aves/widgets/viewer/overlay/video/ab_repeat.dart';
import 'package:aves/widgets/viewer/overlay/video/controls.dart';
import 'package:aves/widgets/viewer/overlay/video/progress_bar.dart';
@ -41,10 +42,7 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
@override
Widget build(BuildContext context) {
return Directionality(
// always keep action buttons in the lower right corner, even with RTL locales
textDirection: TextDirection.ltr,
child: StreamBuilder<VideoStatus>(
return StreamBuilder<VideoStatus>(
stream: statusStream,
builder: (context, snapshot) {
// do not use stream snapshot because it is obsolete when switching between videos
@ -53,7 +51,7 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
if (status == VideoStatus.error) {
const action = EntryAction.openVideo;
return Align(
alignment: AlignmentDirectional.centerEnd,
alignment: Alignment.centerRight,
child: OverlayButton(
scale: scale,
child: IconButton(
@ -73,6 +71,7 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
),
const SizedBox(height: 8),
Row(
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
Expanded(
child: VideoProgressBar(
@ -91,7 +90,6 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
],
);
},
),
);
}
}

View file

@ -26,6 +26,7 @@ import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/viewer/action/entry_action_delegate.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart';
import 'package:aves/widgets/viewer/video/conductor.dart';
import 'package:aves_model/aves_model.dart';
import 'package:aves_utils/aves_utils.dart';
@ -130,6 +131,7 @@ class _TvButtonRowContent extends StatelessWidget {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
...EntryActions.topLevel,
...EntryActions.export,
@ -257,6 +259,7 @@ class _ViewerButtonRowContentState extends State<ViewerButtonRowContent> {
return Padding(
padding: const EdgeInsets.only(left: padding / 2, right: padding / 2, bottom: padding),
child: Row(
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
const Spacer(),
...widget.quickActions.map((action) => _buildOverlayButton(context, action, videoController)),

View file

@ -10,6 +10,7 @@ import 'package:aves/widgets/common/action_mixins/feedback.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/overlay_button.dart';
import 'package:aves/widgets/dialogs/wallpaper_settings_dialog.dart';
import 'package:aves/widgets/viewer/overlay/bottom.dart';
import 'package:aves/widgets/viewer/overlay/viewer_buttons.dart';
import 'package:aves/widgets/viewer/video/conductor.dart';
import 'package:aves/widgets/viewer/view/conductor.dart';
@ -38,6 +39,7 @@ class WallpaperButtons extends StatelessWidget with FeedbackMixin {
child: Padding(
padding: const EdgeInsets.only(left: padding / 2, right: padding / 2, bottom: padding),
child: Row(
textDirection: ViewerBottomOverlay.actionsDirection,
children: [
const Spacer(),
Padding(