rtl prep
This commit is contained in:
parent
2a4c07a657
commit
d44b001bb7
33 changed files with 268 additions and 177 deletions
|
@ -3,10 +3,11 @@
|
|||
"welcomeMessage": "Willkommen bei Aves",
|
||||
"welcomeOptional": "Optional",
|
||||
"welcomeTermsToggle": "Ich stimme den Bedingungen und Konditionen zu",
|
||||
"itemCount": " {count, plural, =1{1 Element} other{{count} Elemente}}",
|
||||
"itemCount": "{count, plural, =1{1 Element} other{{count} Elemente}}",
|
||||
|
||||
"timeSeconds": " {seconds, plural, =1{1 Sekunde} other{{seconds} Sekunde}}",
|
||||
"timeMinutes": " {minutes, plural, =1{1 Minute} other{{minutes} Minuten}}",
|
||||
"timeSeconds": "{seconds, plural, =1{1 Sekunde} other{{seconds} Sekunde}}",
|
||||
"timeMinutes": "{minutes, plural, =1{1 Minute} other{{minutes} Minuten}}",
|
||||
"focalLength": "{length} mm",
|
||||
|
||||
"applyButtonLabel": "ANWENDEN",
|
||||
"deleteButtonLabel": "LÖSCHEN",
|
||||
|
@ -93,7 +94,7 @@
|
|||
|
||||
"coordinateFormatDms": "GMS",
|
||||
"coordinateFormatDecimal": "Dezimalgrad",
|
||||
"coordinateDms": " {coordinate} {direction}",
|
||||
"coordinateDms": "{coordinate} {direction}",
|
||||
"coordinateDmsNorth": "N",
|
||||
"coordinateDmsSouth": "s",
|
||||
"coordinateDmsEast": "O",
|
||||
|
@ -144,7 +145,7 @@
|
|||
"missingSystemFilePickerDialogMessage": "Der System-Dateiauswahldialog fehlt oder ist deaktiviert. Bitte aktivieren Sie ihn und versuchen Sie es erneut.",
|
||||
|
||||
"unsupportedTypeDialogTitle": "Nicht unterstützte Typen",
|
||||
"unsupportedTypeDialogMessage": " {count, plural, =1{Dieser Vorgang wird für Elemente des folgenden Typs nicht unterstützt: {types}.} other{Dieser Vorgang wird für Elemente der folgenden Typen nicht unterstützt: {types}.}}",
|
||||
"unsupportedTypeDialogMessage": "{count, plural, =1{Dieser Vorgang wird für Elemente des folgenden Typs nicht unterstützt: {types}.} other{Dieser Vorgang wird für Elemente der folgenden Typen nicht unterstützt: {types}.}}",
|
||||
|
||||
"nameConflictDialogSingleSourceMessage": "Einige Dateien im Zielordner haben den gleichen Namen.",
|
||||
"nameConflictDialogMultipleSourceMessage": "Einige Dateien haben denselben Namen.",
|
||||
|
@ -155,7 +156,7 @@
|
|||
"noMatchingAppDialogTitle": "Keine passende App",
|
||||
"noMatchingAppDialogMessage": "Es gibt keine Anwendungen, die dies bewältigen können.",
|
||||
|
||||
"deleteEntriesConfirmationDialogMessage": " {count, plural, =1{Sind Sie sicher, dass Sie dieses Element löschen möchten?} other{Sind Sie sicher, dass Sie diese {count} Elemente löschen möchten?}}",
|
||||
"deleteEntriesConfirmationDialogMessage": "{count, plural, =1{Sind Sie sicher, dass Sie dieses Element löschen möchten?} other{Sind Sie sicher, dass Sie diese {count} Elemente löschen möchten?}}",
|
||||
|
||||
"videoResumeDialogMessage": "Möchten Sie bei {time} weiter abspielen?",
|
||||
"videoStartOverButtonLabel": "NEU BEGINNEN",
|
||||
|
@ -175,8 +176,8 @@
|
|||
"renameAlbumDialogLabel": "Neuer Name",
|
||||
"renameAlbumDialogLabelAlreadyExistsHelper": "Verzeichnis existiert bereits",
|
||||
|
||||
"deleteSingleAlbumConfirmationDialogMessage": " {count, plural, =1{Sind Sie sicher, dass Sie dieses Album und ihren Inhalt löschen möchten?} other{Sind Sie sicher, dass Sie dieses Album und deren {count} Elemente löschen möchten?}}",
|
||||
"deleteMultiAlbumConfirmationDialogMessage": " {count, plural, =1{Sind Sie sicher, dass Sie diese Alben und ihren Inhalt löschen möchten?} other{Sind Sie sicher, dass Sie diese Alben und deren {count} Elemente löschen möchten?}}",
|
||||
"deleteSingleAlbumConfirmationDialogMessage": "{count, plural, =1{Sind Sie sicher, dass Sie dieses Album und ihren Inhalt löschen möchten?} other{Sind Sie sicher, dass Sie dieses Album und deren {count} Elemente löschen möchten?}}",
|
||||
"deleteMultiAlbumConfirmationDialogMessage": "{count, plural, =1{Sind Sie sicher, dass Sie diese Alben und ihren Inhalt löschen möchten?} other{Sind Sie sicher, dass Sie diese Alben und deren {count} Elemente löschen möchten?}}",
|
||||
|
||||
"exportEntryDialogFormat": "Format:",
|
||||
|
||||
|
@ -256,7 +257,7 @@
|
|||
|
||||
"collectionPageTitle": "Sammlung",
|
||||
"collectionPickPageTitle": "Wähle",
|
||||
"collectionSelectionPageTitle": " {count, plural, =0{Elemente auswählen} =1{1 Element} other{{count} Elemente}}",
|
||||
"collectionSelectionPageTitle": "{count, plural, =0{Elemente auswählen} =1{1 Element} other{{count} Elemente}}",
|
||||
|
||||
"collectionActionShowTitleSearch": "Titelfilter anzeigen",
|
||||
"collectionActionHideTitleSearch": "Titelfilter ausblenden",
|
||||
|
@ -282,14 +283,14 @@
|
|||
"dateToday": "Heute",
|
||||
"dateYesterday": "Gestern",
|
||||
"dateThisMonth": "Diesen Monat",
|
||||
"collectionDeleteFailureFeedback": " {count, plural, =1{1 Element konnte nicht gelöscht werden} other{{count} Elemente konnten nicht gelöscht werden}}",
|
||||
"collectionCopyFailureFeedback": " {count, plural, =1{1 Element konnte nicht kopiert werden} other{{count} Element konnten nicht kopiert werden}}",
|
||||
"collectionMoveFailureFeedback": " {count, plural, =1{1 Element konnte nicht verschoben werden} other{{count} Elemente konnten nicht verschoben werden}}",
|
||||
"collectionEditFailureFeedback": " {count, plural, =1{1 Element konnte nicht bearbeitet werden} other{{count} 1 Elemente konnten nicht bearbeitet werden}}",
|
||||
"collectionExportFailureFeedback": " {count, plural, =1{1 Seite konnte nicht exportiert werden} other{{count} Seiten konnten nicht exportiert werden}}",
|
||||
"collectionCopySuccessFeedback": " {count, plural, =1{1 Element kopier} other{ {count} Elemente kopiert}}",
|
||||
"collectionMoveSuccessFeedback": " {count, plural, =1{1 Element verschoben} other{{count} Elemente verschoben}}",
|
||||
"collectionEditSuccessFeedback": " {count, plural, =1{1 Element bearbeitet} other{ {count} Elemente bearbeitet}}",
|
||||
"collectionDeleteFailureFeedback": "{count, plural, =1{1 Element konnte nicht gelöscht werden} other{{count} Elemente konnten nicht gelöscht werden}}",
|
||||
"collectionCopyFailureFeedback": "{count, plural, =1{1 Element konnte nicht kopiert werden} other{{count} Element konnten nicht kopiert werden}}",
|
||||
"collectionMoveFailureFeedback": "{count, plural, =1{1 Element konnte nicht verschoben werden} other{{count} Elemente konnten nicht verschoben werden}}",
|
||||
"collectionEditFailureFeedback": "{count, plural, =1{1 Element konnte nicht bearbeitet werden} other{{count} 1 Elemente konnten nicht bearbeitet werden}}",
|
||||
"collectionExportFailureFeedback": "{count, plural, =1{1 Seite konnte nicht exportiert werden} other{{count} Seiten konnten nicht exportiert werden}}",
|
||||
"collectionCopySuccessFeedback": "{count, plural, =1{1 Element kopier} other{ {count} Elemente kopiert}}",
|
||||
"collectionMoveSuccessFeedback": "{count, plural, =1{1 Element verschoben} other{{count} Elemente verschoben}}",
|
||||
"collectionEditSuccessFeedback": "{count, plural, =1{1 Element bearbeitet} other{ {count} Elemente bearbeitet}}",
|
||||
|
||||
"collectionEmptyFavourites": "Keine Favoriten",
|
||||
"collectionEmptyVideos": "Keine Videos",
|
||||
|
@ -471,7 +472,7 @@
|
|||
"settingsUnitSystemTitle": "Einheiten",
|
||||
|
||||
"statsPageTitle": "Statistiken",
|
||||
"statsWithGps": " {count, plural, =1{1 Element mit Standort} other{{count} Elemente mit Standort}}",
|
||||
"statsWithGps": "{count, plural, =1{1 Element mit Standort} other{{count} Elemente mit Standort}}",
|
||||
"statsTopCountries": "Top-Länder",
|
||||
"statsTopPlaces": "Top-Plätze",
|
||||
"statsTopTags": "Top-Tags",
|
||||
|
|
|
@ -22,6 +22,15 @@
|
|||
"minutes": {}
|
||||
}
|
||||
},
|
||||
"focalLength": "{length} mm",
|
||||
"@focalLength": {
|
||||
"placeholders": {
|
||||
"length": {
|
||||
"type": "String",
|
||||
"example": "5.4"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"applyButtonLabel": "APPLY",
|
||||
"deleteButtonLabel": "DELETE",
|
||||
|
|
|
@ -4,10 +4,11 @@
|
|||
"welcomeOptional": "Opcional",
|
||||
"welcomeTermsToggle": "Acepto los términos y condiciones",
|
||||
"itemCount": "{count, plural, =1{1 elemento} other{{count} elementos}}",
|
||||
|
||||
|
||||
"timeSeconds": "{seconds, plural, =1{1 segundo} other{{seconds} segundos}}",
|
||||
"timeMinutes": "{minutes, plural, =1{1 minuto} other{{minutes} minutos}}",
|
||||
|
||||
"focalLength": "{length} mm",
|
||||
|
||||
"applyButtonLabel": "APLICAR",
|
||||
"deleteButtonLabel": "BORRAR",
|
||||
"nextButtonLabel": "SIGUIENTE",
|
||||
|
@ -140,13 +141,13 @@
|
|||
"restrictedAccessDialogMessage": "Esta aplicación no tiene permiso para modificar archivos de {directory} en «{volume}».\n\nPor favor use un gestor de archivos o la aplicación de galería preinstalada para mover los elementos a otro directorio.",
|
||||
"notEnoughSpaceDialogTitle": "Espacio insuficiente",
|
||||
"notEnoughSpaceDialogMessage": "Esta operación necesita {neededSize} de espacio libre en «{volume}» para completarse, pero sólo hay {freeSize} disponible.",
|
||||
|
||||
|
||||
"missingSystemFilePickerDialogTitle": "Selector de archivos del sistema no disponible",
|
||||
"missingSystemFilePickerDialogMessage": "El selector de archivos del sistema no se encuentra disponible o fue deshabilitado. Por favor habilítelo e intente nuevamente.",
|
||||
|
||||
"unsupportedTypeDialogTitle": "Tipos de archivo incompatibles",
|
||||
"unsupportedTypeDialogMessage": "{count, plural, =1{Esta operación no está disponible para un elemento del siguiente tipo: {types}.} other{Esta operación no está disponible para elementos de los siguientes tipos: {types}.}}",
|
||||
|
||||
|
||||
"nameConflictDialogSingleSourceMessage": "Algunos archivos en el directorio de destino tienen el mismo nombre.",
|
||||
"nameConflictDialogMultipleSourceMessage": "Algunos archivos tienen el mismo nombre.",
|
||||
|
||||
|
@ -244,7 +245,7 @@
|
|||
"aboutCreditsWorldAtlas2": "bajo licencia ISC.",
|
||||
"aboutCreditsTranslators": "Traductores:",
|
||||
"aboutCreditsTranslatorLine": "{language}: {names}",
|
||||
|
||||
|
||||
"aboutLicenses": "Licencias de código abierto",
|
||||
"aboutLicensesBanner": "Esta aplicación usa los siguientes paquetes y librerías de código abierto.",
|
||||
"aboutLicensesAndroidLibraries": "Librerías de Android",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
"timeSeconds": "{seconds, plural, =1{1 seconde} other{{seconds} secondes}}",
|
||||
"timeMinutes": "{minutes, plural, =1{1 minute} other{{minutes} minutes}}",
|
||||
"focalLength": "{length} mm",
|
||||
|
||||
"applyButtonLabel": "ENREGISTRER",
|
||||
"deleteButtonLabel": "SUPPRIMER",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
"timeSeconds": "{seconds, plural, other{{seconds}초}}",
|
||||
"timeMinutes": "{minutes, plural, other{{minutes}분}}",
|
||||
"focalLength": "{length} mm",
|
||||
|
||||
"applyButtonLabel": "확인",
|
||||
"deleteButtonLabel": "삭제",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
"timeSeconds": "{seconds, plural, =1{1 segundo} other{{seconds} segundos}}",
|
||||
"timeMinutes": "{minutes, plural, =1{1 minuto} other{{minutes} minutos}}",
|
||||
"focalLength": "{length} mm",
|
||||
|
||||
"applyButtonLabel": "APLIQUE",
|
||||
"deleteButtonLabel": "EXCLUIR",
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
"timeSeconds": "{seconds, plural, =1{1 секунда} few{{seconds} секунды} other{{seconds} секунд}}",
|
||||
"timeMinutes": "{minutes, plural, =1{1 минута} few{{minutes} минуты} other{{minutes} минут}}",
|
||||
"focalLength": "{length} mm",
|
||||
|
||||
"applyButtonLabel": "ПРИМЕНИТЬ",
|
||||
"deleteButtonLabel": "УДАЛИТЬ",
|
||||
|
|
|
@ -17,10 +17,13 @@ class PolicyPage extends StatefulWidget {
|
|||
class _PolicyPageState extends State<PolicyPage> {
|
||||
late Future<String> _termsLoader;
|
||||
|
||||
static const termsPath = 'assets/terms.md';
|
||||
static const termsDirection = TextDirection.ltr;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_termsLoader = rootBundle.loadString('assets/terms.md');
|
||||
_termsLoader = rootBundle.loadString(termsPath);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -38,7 +41,10 @@ class _PolicyPageState extends State<PolicyPage> {
|
|||
final terms = snapshot.data!;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: MarkdownContainer(data: terms),
|
||||
child: MarkdownContainer(
|
||||
data: terms,
|
||||
textDirection: termsDirection,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
|
|
@ -192,6 +192,7 @@ class _CollectionSectionedContentState extends State<_CollectionSectionedContent
|
|||
|
||||
final isMainMode = context.select<ValueNotifier<AppMode>, bool>((vn) => vn.value == AppMode.main);
|
||||
final selector = GridSelectionGestureDetector(
|
||||
scrollableKey: scrollableKey,
|
||||
selectable: isMainMode,
|
||||
items: collection.sortedEntries,
|
||||
scrollController: scrollController,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/*
|
||||
|
@ -9,6 +10,8 @@ import 'package:flutter/material.dart';
|
|||
- allow any `Widget` as label content
|
||||
- moved out constraints responsibility
|
||||
- various extent & thumb positioning fixes
|
||||
- null safety
|
||||
- directionality aware
|
||||
*/
|
||||
|
||||
/// Build the Scroll Thumb and label using the current configuration
|
||||
|
@ -350,7 +353,7 @@ class SlideFadeTransition extends StatelessWidget {
|
|||
builder: (context, child) => animation.value == 0.0 ? Container() : child!,
|
||||
child: SlideTransition(
|
||||
position: Tween(
|
||||
begin: Offset((Directionality.of(context) == TextDirection.ltr ? 1 : -1) * .3, 0),
|
||||
begin: Offset((context.isRtl ? -1 : 1) * .3, 0),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: FadeTransition(
|
||||
|
|
|
@ -33,6 +33,8 @@ class SideGestureAreaProtector extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return Positioned.fill(
|
||||
child: Row(
|
||||
// `systemGestureInsets` are not directional
|
||||
textDirection: TextDirection.ltr,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: context.select<MediaQueryData, double>((mq) => mq.systemGestureInsets.left),
|
||||
|
|
|
@ -4,10 +4,12 @@ import 'package:url_launcher/url_launcher.dart';
|
|||
|
||||
class MarkdownContainer extends StatelessWidget {
|
||||
final String data;
|
||||
final TextDirection? textDirection;
|
||||
|
||||
const MarkdownContainer({
|
||||
Key? key,
|
||||
required this.data,
|
||||
this.textDirection,
|
||||
}) : super(key: key);
|
||||
|
||||
static const double maxWidth = 460;
|
||||
|
@ -34,15 +36,18 @@ class MarkdownContainer extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
child: Scrollbar(
|
||||
child: Markdown(
|
||||
data: data,
|
||||
selectable: true,
|
||||
onTapLink: (text, href, title) async {
|
||||
if (href != null && await canLaunch(href)) {
|
||||
await launch(href);
|
||||
}
|
||||
},
|
||||
shrinkWrap: true,
|
||||
child: Directionality(
|
||||
textDirection: textDirection ?? Directionality.of(context),
|
||||
child: Markdown(
|
||||
data: data,
|
||||
selectable: true,
|
||||
onTapLink: (text, href, title) async {
|
||||
if (href != null && await canLaunch(href)) {
|
||||
await launch(href);
|
||||
}
|
||||
},
|
||||
shrinkWrap: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -5,4 +5,6 @@ extension ExtraContext on BuildContext {
|
|||
String? get currentRouteName => ModalRoute.of(this)?.settings.name;
|
||||
|
||||
AppLocalizations get l10n => AppLocalizations.of(this)!;
|
||||
|
||||
bool get isRtl => Directionality.of(this) == TextDirection.rtl;
|
||||
}
|
||||
|
|
|
@ -30,37 +30,6 @@ class SectionHeader<T> extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final spans = [
|
||||
WidgetSpan(
|
||||
alignment: widgetSpanAlignment,
|
||||
child: _SectionSelectableLeading<T>(
|
||||
selectable: selectable,
|
||||
sectionKey: sectionKey,
|
||||
browsingBuilder: leading != null
|
||||
? (context) => Container(
|
||||
padding: const EdgeInsetsDirectional.only(end: 8, bottom: 4),
|
||||
width: leadingDimension,
|
||||
height: leadingDimension,
|
||||
child: leading,
|
||||
)
|
||||
: null,
|
||||
onPressed: selectable ? () => _toggleSectionSelection(context) : null,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: title,
|
||||
style: Constants.titleTextStyle,
|
||||
),
|
||||
if (trailing != null)
|
||||
WidgetSpan(
|
||||
alignment: widgetSpanAlignment,
|
||||
child: Container(
|
||||
padding: const EdgeInsetsDirectional.only(start: 8, bottom: 2),
|
||||
child: trailing,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
padding: padding,
|
||||
|
@ -68,13 +37,38 @@ class SectionHeader<T> extends StatelessWidget {
|
|||
child: GestureDetector(
|
||||
onTap: selectable ? () => _toggleSectionSelection(context) : null,
|
||||
child: Text.rich(
|
||||
// bidi with optional surrounding widget spans is tricky
|
||||
// so we use LTR direction for the rich text itself and reverse the spans if necessary
|
||||
// TODO TLAD [rtl] revisit this solution as it is failing for multiline headers
|
||||
TextSpan(
|
||||
children: Directionality.of(context) == TextDirection.ltr ? spans : spans.reversed.toList(),
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: widgetSpanAlignment,
|
||||
child: _SectionSelectableLeading<T>(
|
||||
selectable: selectable,
|
||||
sectionKey: sectionKey,
|
||||
browsingBuilder: leading != null
|
||||
? (context) => Container(
|
||||
padding: const EdgeInsetsDirectional.only(end: 8, bottom: 4),
|
||||
width: leadingDimension,
|
||||
height: leadingDimension,
|
||||
child: leading,
|
||||
)
|
||||
: null,
|
||||
onPressed: selectable ? () => _toggleSectionSelection(context) : null,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: title,
|
||||
style: Constants.titleTextStyle,
|
||||
),
|
||||
if (trailing != null)
|
||||
WidgetSpan(
|
||||
alignment: widgetSpanAlignment,
|
||||
child: Container(
|
||||
padding: const EdgeInsetsDirectional.only(start: 8, bottom: 2),
|
||||
child: trailing,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:aves/model/highlight.dart';
|
|||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/common/behaviour/eager_scale_gesture_recognizer.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/grid/theme.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/common/tile_extent_controller.dart';
|
||||
|
@ -304,7 +305,7 @@ class _ScaleOverlayState extends State<_ScaleOverlay> {
|
|||
gradientCenter = center;
|
||||
break;
|
||||
case TileLayout.list:
|
||||
gradientCenter = Offset(Directionality.of(context) == TextDirection.rtl ? gridWidth : 0, center.dy);
|
||||
gradientCenter = Offset(context.isRtl ? gridWidth : 0, center.dy);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -191,6 +191,7 @@ class SectionedListLayout<T> {
|
|||
required this.sectionLayouts,
|
||||
});
|
||||
|
||||
// return tile rectangle in layout space, i.e. x=0 is start
|
||||
Rect? getTileRect(T item) {
|
||||
final MapEntry<SectionKey?, List<T>>? section = sections.entries.firstWhereOrNull((kv) => kv.value.contains(item));
|
||||
if (section == null) return null;
|
||||
|
@ -211,6 +212,7 @@ class SectionedListLayout<T> {
|
|||
|
||||
SectionLayout? getSectionAt(double offsetY) => sectionLayouts.firstWhereOrNull((sl) => offsetY < sl.maxOffset);
|
||||
|
||||
// `position` in layout space, i.e. x=0 is start
|
||||
T? getItemAt(Offset position) {
|
||||
var dy = position.dy;
|
||||
final sectionLayout = getSectionAt(dy);
|
||||
|
|
|
@ -3,12 +3,14 @@ import 'dart:math';
|
|||
|
||||
import 'package:aves/model/selection.dart';
|
||||
import 'package:aves/utils/math_utils.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/extensions/media_query.dart';
|
||||
import 'package:aves/widgets/common/grid/section_layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class GridSelectionGestureDetector<T> extends StatefulWidget {
|
||||
final GlobalKey scrollableKey;
|
||||
final bool selectable;
|
||||
final List<T> items;
|
||||
final ScrollController scrollController;
|
||||
|
@ -17,6 +19,7 @@ class GridSelectionGestureDetector<T> extends StatefulWidget {
|
|||
|
||||
const GridSelectionGestureDetector({
|
||||
Key? key,
|
||||
required this.scrollableKey,
|
||||
this.selectable = true,
|
||||
required this.items,
|
||||
required this.scrollController,
|
||||
|
@ -42,6 +45,13 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
|
|||
|
||||
double get appBarHeight => widget.appBarHeightNotifier.value;
|
||||
|
||||
double get scrollableWidth {
|
||||
final scrollableContext = widget.scrollableKey.currentContext!;
|
||||
final scrollableBox = scrollableContext.findRenderObject() as RenderBox;
|
||||
// not the same as `MediaQuery.size.width`, because of screen insets/padding
|
||||
return scrollableBox.size.width;
|
||||
}
|
||||
|
||||
static const double scrollEdgeRatio = .15;
|
||||
static const double scrollMaxPixelPerSecond = 600.0;
|
||||
static const Duration scrollUpdateInterval = Duration(milliseconds: 100);
|
||||
|
@ -147,7 +157,7 @@ class _GridSelectionGestureDetectorState<T> extends State<GridSelectionGestureDe
|
|||
// so we use custom layout computation instead to find the item.
|
||||
final offset = Offset(0, scrollController.offset - appBarHeight) + localPosition;
|
||||
final sectionedListLayout = context.read<SectionedListLayout<T>>();
|
||||
return sectionedListLayout.getItemAt(offset);
|
||||
return sectionedListLayout.getItemAt(context.isRtl ? Offset(scrollableWidth - offset.dx, offset.dy) : offset);
|
||||
}
|
||||
|
||||
void _toggleSelectionToIndex(int toIndex) {
|
||||
|
|
|
@ -283,20 +283,26 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
),
|
||||
),
|
||||
if (banner != null)
|
||||
LayoutBuilder(builder: (context, constraints) {
|
||||
return ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
child: Transform(
|
||||
transform: Matrix4.identity().scaled((constraints.maxHeight / 90 - .4).clamp(.45, 1.0)),
|
||||
child: Banner(
|
||||
message: banner.toUpperCase(),
|
||||
location: BannerLocation.topStart,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
child: const SizedBox(),
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
child: Align(
|
||||
// align to corner the scaled down banner in RTL
|
||||
alignment: AlignmentDirectional.topStart,
|
||||
child: Transform(
|
||||
transform: Matrix4.identity().scaled((constraints.maxHeight / 90 - .4).clamp(.45, 1.0)),
|
||||
child: Banner(
|
||||
message: banner.toUpperCase(),
|
||||
location: BannerLocation.topStart,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
child: const SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/image.dart';
|
||||
import 'package:custom_rounded_rectangle_border/custom_rounded_rectangle_border.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -82,18 +83,31 @@ class ImageMarker extends StatelessWidget {
|
|||
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 2),
|
||||
decoration: ShapeDecoration(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
shape: const CustomRoundedRectangleBorder(
|
||||
leftSide: borderSide,
|
||||
rightSide: borderSide,
|
||||
topSide: borderSide,
|
||||
bottomSide: borderSide,
|
||||
topLeftCornerSide: borderSide,
|
||||
bottomRightCornerSide: borderSide,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: innerRadius,
|
||||
bottomRight: innerRadius,
|
||||
),
|
||||
),
|
||||
shape: context.isRtl
|
||||
? const CustomRoundedRectangleBorder(
|
||||
leftSide: borderSide,
|
||||
rightSide: borderSide,
|
||||
topSide: borderSide,
|
||||
bottomSide: borderSide,
|
||||
topRightCornerSide: borderSide,
|
||||
bottomLeftCornerSide: borderSide,
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: innerRadius,
|
||||
bottomLeft: innerRadius,
|
||||
),
|
||||
)
|
||||
: const CustomRoundedRectangleBorder(
|
||||
leftSide: borderSide,
|
||||
rightSide: borderSide,
|
||||
topSide: borderSide,
|
||||
bottomSide: borderSide,
|
||||
topLeftCornerSide: borderSide,
|
||||
bottomRightCornerSide: borderSide,
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: innerRadius,
|
||||
bottomRight: innerRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
'$count',
|
||||
|
|
|
@ -46,46 +46,49 @@ class _AppDebugPageState extends State<AppDebugPage> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MediaQueryDataProvider(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Debug'),
|
||||
actions: [
|
||||
MenuIconTheme(
|
||||
child: PopupMenuButton<AppDebugAction>(
|
||||
// key is expected by test driver
|
||||
key: const Key('appbar-menu-button'),
|
||||
itemBuilder: (context) => AppDebugAction.values
|
||||
.map((v) => PopupMenuItem(
|
||||
// key is expected by test driver
|
||||
key: Key('menu-${v.name}'),
|
||||
value: v,
|
||||
child: MenuRow(text: v.name),
|
||||
))
|
||||
.toList(),
|
||||
onSelected: (action) async {
|
||||
// wait for the popup menu to hide before proceeding with the action
|
||||
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
|
||||
unawaited(_onActionSelected(action));
|
||||
},
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Debug'),
|
||||
actions: [
|
||||
MenuIconTheme(
|
||||
child: PopupMenuButton<AppDebugAction>(
|
||||
// key is expected by test driver
|
||||
key: const Key('appbar-menu-button'),
|
||||
itemBuilder: (context) => AppDebugAction.values
|
||||
.map((v) => PopupMenuItem(
|
||||
// key is expected by test driver
|
||||
key: Key('menu-${v.name}'),
|
||||
value: v,
|
||||
child: MenuRow(text: v.name),
|
||||
))
|
||||
.toList(),
|
||||
onSelected: (action) async {
|
||||
// wait for the popup menu to hide before proceeding with the action
|
||||
await Future.delayed(Durations.popupMenuAnimation * timeDilation);
|
||||
unawaited(_onActionSelected(action));
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
children: [
|
||||
_buildGeneralTabView(),
|
||||
const DebugAndroidAppSection(),
|
||||
const DebugAndroidCodecSection(),
|
||||
const DebugAndroidDirSection(),
|
||||
const DebugCacheSection(),
|
||||
const DebugAppDatabaseSection(),
|
||||
const DebugErrorReportingSection(),
|
||||
const DebugSettingsSection(),
|
||||
const DebugStorageSection(),
|
||||
],
|
||||
),
|
||||
body: SafeArea(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
children: [
|
||||
_buildGeneralTabView(),
|
||||
const DebugAndroidAppSection(),
|
||||
const DebugAndroidCodecSection(),
|
||||
const DebugAndroidDirSection(),
|
||||
const DebugCacheSection(),
|
||||
const DebugAppDatabaseSection(),
|
||||
const DebugErrorReportingSection(),
|
||||
const DebugSettingsSection(),
|
||||
const DebugStorageSection(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -131,7 +131,7 @@ class _EditEntryDateDialogState extends State<EditEntryDateDialog> {
|
|||
final use24hour = context.select<MediaQueryData, bool>((v) => v.alwaysUse24HourFormat);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 16, right: 8),
|
||||
padding: const EdgeInsetsDirectional.only(start: 16, end: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(child: Text(formatDateTime(_setDateTime, locale, use24hour))),
|
||||
|
@ -177,6 +177,8 @@ 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,
|
||||
children: [
|
||||
TableRow(
|
||||
children: [
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
|||
|
||||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/services/common/services.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
|
@ -40,12 +41,17 @@ class _RenameEntryDialogState extends State<RenameEntryDialog> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isRtl = context.isRtl;
|
||||
final extensionSuffixText = '${Constants.fsi}${entry.extension}${Constants.pdi}';
|
||||
return AvesDialog(
|
||||
content: TextField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(
|
||||
labelText: context.l10n.renameEntryDialogLabel,
|
||||
suffixText: entry.extension,
|
||||
// decoration prefix and suffix follow directionality
|
||||
// but the file extension should always be on the right
|
||||
prefixText: isRtl ? extensionSuffixText : null,
|
||||
suffixText: isRtl ? null : extensionSuffixText,
|
||||
),
|
||||
autofocus: true,
|
||||
onChanged: (_) => _validate(),
|
||||
|
|
|
@ -131,9 +131,8 @@ class _TileViewDialogState<S, G, L> extends State<TileViewDialog<S, G, L>> with
|
|||
// crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Material(
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: AvesDialog.cornerRadius,
|
||||
topRight: AvesDialog.cornerRadius,
|
||||
borderRadius: const BorderRadius.vertical(
|
||||
top: AvesDialog.cornerRadius,
|
||||
),
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: TabBar(
|
||||
|
|
|
@ -168,7 +168,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
|||
children: [
|
||||
if (pinned)
|
||||
AnimatedPadding(
|
||||
padding: EdgeInsets.only(right: padding),
|
||||
padding: EdgeInsetsDirectional.only(end: padding),
|
||||
duration: Durations.chipDecorationAnimation,
|
||||
child: Icon(
|
||||
AIcons.pin,
|
||||
|
@ -178,7 +178,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
|||
),
|
||||
if (filter is AlbumFilter && androidFileUtils.isOnRemovableStorage(filter.album))
|
||||
AnimatedPadding(
|
||||
padding: EdgeInsets.only(right: padding),
|
||||
padding: EdgeInsetsDirectional.only(end: padding),
|
||||
duration: Durations.chipDecorationAnimation,
|
||||
child: Icon(
|
||||
AIcons.removableStorage,
|
||||
|
|
|
@ -384,6 +384,7 @@ class _FilterSectionedContentState<T extends CollectionFilter> extends State<_Fi
|
|||
|
||||
final isMainMode = context.select<ValueNotifier<AppMode>, bool>((vn) => vn.value == AppMode.main);
|
||||
final selector = GridSelectionGestureDetector<FilterGridItem<T>>(
|
||||
scrollableKey: scrollableKey,
|
||||
selectable: isMainMode && widget.selectable,
|
||||
items: visibleSections.values.expand((v) => v).toList(),
|
||||
scrollController: scrollController,
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:aves/model/source/collection_lens.dart';
|
|||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/model/source/enums.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/grid/scaling.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/covered_filter_chip.dart';
|
||||
|
@ -178,8 +179,11 @@ class FilterTile<T extends CollectionFilter> extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
if (onTap != null) {
|
||||
// larger than the chip corner radius, so ink effects will be effectively clipped from the leading chip corners
|
||||
const radius = Radius.circular(123);
|
||||
child = InkWell(
|
||||
borderRadius: const BorderRadius.only(topLeft: Radius.circular(123), bottomLeft: Radius.circular(123)),
|
||||
// as of Flutter v2.8.1, `InkWell` does not use `BorderRadiusGeometry`
|
||||
borderRadius: context.isRtl ? const BorderRadius.only(topRight: radius, bottomRight: radius) : const BorderRadius.only(topLeft: radius, bottomLeft: radius),
|
||||
onTap: onTap,
|
||||
child: child,
|
||||
);
|
||||
|
|
|
@ -51,7 +51,7 @@ class FilterListDetails<T extends CollectionFilter> extends StatelessWidget {
|
|||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: FilterListDetailsTheme.titleIconPadding),
|
||||
padding: const EdgeInsetsDirectional.only(end: FilterListDetailsTheme.titleIconPadding),
|
||||
child: IconTheme(
|
||||
data: IconThemeData(color: detailsTheme.titleStyle.color),
|
||||
child: leading,
|
||||
|
@ -127,7 +127,7 @@ class FilterListDetails<T extends CollectionFilter> extends StatelessWidget {
|
|||
children: leadingIcons
|
||||
.mapIndexed((i, child) => i > 0
|
||||
? Padding(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
padding: const EdgeInsetsDirectional.only(start: 8),
|
||||
child: child,
|
||||
)
|
||||
: child)
|
||||
|
|
|
@ -174,14 +174,14 @@ class _QuickActionEditorBodyState<T extends Object> extends State<QuickActionEdi
|
|||
children: [
|
||||
Positioned.fill(
|
||||
child: FractionallySizedBox(
|
||||
alignment: Alignment.centerLeft,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
widthFactor: .5,
|
||||
child: header,
|
||||
),
|
||||
),
|
||||
Positioned.fill(
|
||||
child: FractionallySizedBox(
|
||||
alignment: Alignment.centerRight,
|
||||
alignment: AlignmentDirectional.centerEnd,
|
||||
widthFactor: .5,
|
||||
child: footer,
|
||||
),
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:aves/services/common/services.dart';
|
|||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/basic/menu.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/buttons.dart';
|
||||
|
@ -179,7 +180,7 @@ class _FilePickerState extends State<FilePicker> {
|
|||
Widget _buildContentLine(BuildContext context, FileSystemEntity content) {
|
||||
return ListTile(
|
||||
leading: const Icon(AIcons.folder),
|
||||
title: Text(pContext.split(content.path).last),
|
||||
title: Text('${Constants.fsi}${pContext.split(content.path).last}${Constants.pdi}'),
|
||||
onTap: () {
|
||||
_goTo(content.path);
|
||||
setState(() {});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:aves/model/filters/filters.dart';
|
||||
import 'package:aves/utils/color_utils.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
@ -40,7 +41,7 @@ class FilterTable<T extends Comparable> extends StatelessWidget {
|
|||
|
||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
||||
final lineHeight = 16 * textScaleFactor;
|
||||
final isRTL = Directionality.of(context) == TextDirection.rtl;
|
||||
final isRtl = context.isRtl;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: AvesFilterChip.outlineWidth / 2 + 6, end: 8),
|
||||
|
@ -74,7 +75,7 @@ class FilterTable<T extends Comparable> extends StatelessWidget {
|
|||
backgroundColor: Colors.white24,
|
||||
progressColor: stringToColor(label),
|
||||
animation: true,
|
||||
isRTL: isRTL,
|
||||
isRTL: isRtl,
|
||||
padding: EdgeInsets.symmetric(horizontal: lineHeight),
|
||||
center: Text(
|
||||
intl.NumberFormat.percentPattern().format(percent),
|
||||
|
|
|
@ -109,7 +109,7 @@ class StatsPage extends StatelessWidget {
|
|||
backgroundColor: Colors.white24,
|
||||
progressColor: Theme.of(context).colorScheme.secondary,
|
||||
animation: animate,
|
||||
isRTL: Directionality.of(context) == TextDirection.rtl,
|
||||
isRTL: context.isRtl,
|
||||
leading: const Icon(AIcons.location),
|
||||
padding: EdgeInsets.symmetric(horizontal: lineHeight),
|
||||
center: Text(
|
||||
|
@ -221,23 +221,25 @@ class StatsPage extends StatelessWidget {
|
|||
children: seriesData
|
||||
.map((d) => GestureDetector(
|
||||
onTap: () => _onFilterSelection(context, MimeFilter(d.mimeType)),
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.only(end: 8),
|
||||
child: Icon(AIcons.disc, color: d.color),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(AIcons.disc, color: d.color),
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
d.displayText,
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
),
|
||||
TextSpan(text: '${d.displayText} '),
|
||||
TextSpan(text: '${d.entryCount}', style: const TextStyle(color: Colors.white70)),
|
||||
],
|
||||
),
|
||||
overflow: TextOverflow.fade,
|
||||
softWrap: false,
|
||||
maxLines: 1,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'${d.entryCount}',
|
||||
style: const TextStyle(color: Colors.white70),
|
||||
),
|
||||
],
|
||||
),
|
||||
))
|
||||
.toList(),
|
||||
|
|
|
@ -358,7 +358,7 @@ class _PositionTitleRow extends StatelessWidget {
|
|||
[
|
||||
if (collectionPosition != null) collectionPosition,
|
||||
if (pagePosition != null) pagePosition,
|
||||
if (title != null) title,
|
||||
if (title != null) '${Constants.fsi}$title${Constants.pdi}',
|
||||
].join(separator),
|
||||
strutStyle: Constants.overflowStrutStyle);
|
||||
|
||||
|
@ -430,7 +430,7 @@ class _ShootingRow extends StatelessWidget {
|
|||
final apertureText = aperture != null ? 'ƒ/${NumberFormat('0.0', locale).format(aperture)}' : Constants.overlayUnknown;
|
||||
|
||||
final focalLength = details.focalLength;
|
||||
final focalLengthText = focalLength != null ? '${NumberFormat('0.#', locale).format(focalLength)} mm' : Constants.overlayUnknown;
|
||||
final focalLengthText = focalLength != null ? context.l10n.focalLength(NumberFormat('0.#', locale).format(focalLength)) : Constants.overlayUnknown;
|
||||
|
||||
final iso = details.iso;
|
||||
final isoText = iso != null ? 'ISO$iso' : Constants.overlayUnknown;
|
||||
|
|
|
@ -25,11 +25,14 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
bool _hasAcceptedTerms = false;
|
||||
late Future<String> _termsLoader;
|
||||
|
||||
static const termsPath = 'assets/terms.md';
|
||||
static const termsDirection = TextDirection.ltr;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
settings.setContextualDefaults();
|
||||
_termsLoader = rootBundle.loadString('assets/terms.md');
|
||||
_termsLoader = rootBundle.loadString(termsPath);
|
||||
WidgetsBinding.instance!.addPostFrameCallback((_) => _initWelcomeSettings());
|
||||
}
|
||||
|
||||
|
@ -68,7 +71,12 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
children: [
|
||||
..._buildHeader(context, isPortrait: isPortrait),
|
||||
if (isPortrait) ...[
|
||||
Flexible(child: MarkdownContainer(data: terms)),
|
||||
Flexible(
|
||||
child: MarkdownContainer(
|
||||
data: terms,
|
||||
textDirection: termsDirection,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
..._buildControls(context),
|
||||
] else
|
||||
|
@ -76,16 +84,19 @@ class _WelcomePageState extends State<WelcomePage> {
|
|||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: MarkdownContainer(data: terms),
|
||||
)),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: MarkdownContainer(
|
||||
data: terms,
|
||||
textDirection: termsDirection,
|
||||
),
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ListView(
|
||||
// shrinkWrap: true,
|
||||
children: _buildControls(context),
|
||||
),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue