fixed stream id when locale uses non western arabic numerals, rtl prep
This commit is contained in:
parent
41a4577f36
commit
2f09ca8245
25 changed files with 1532 additions and 169 deletions
|
@ -102,8 +102,8 @@ object MediaMetadataRetrieverHelper {
|
|||
val symbol = "bit/s"
|
||||
|
||||
if (size < divider) return "$size $symbol"
|
||||
if (size < divider * divider) return "${String.format("%.2f", size.toDouble() / divider)} K$symbol"
|
||||
return "${String.format("%.2f", size.toDouble() / divider / divider)} M$symbol"
|
||||
if (size < divider * divider) return "${String.format(Locale.getDefault(), "%.2f", size.toDouble() / divider)} K$symbol"
|
||||
return "${String.format(Locale.getDefault(), "%.2f", size.toDouble() / divider / divider)} M$symbol"
|
||||
}
|
||||
|
||||
fun MediaMetadataRetriever.getSafeDescription(tag: Int, save: (value: String) -> Unit) {
|
||||
|
|
|
@ -22,6 +22,12 @@ class Constants {
|
|||
)
|
||||
];
|
||||
|
||||
// Bidi fun, cf https://www.unicode.org/reports/tr9/
|
||||
// First Strong Isolate
|
||||
static const fsi = '\u2068';
|
||||
// Pop Directional Isolate
|
||||
static const pdi = '\u2069';
|
||||
|
||||
static const overlayUnknown = '—'; // em dash
|
||||
|
||||
static final pointNemo = LatLng(-48.876667, -123.393333);
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:aves/widgets/collection/collection_page.dart';
|
|||
import 'package:aves/widgets/collection/entry_set_action_delegate.dart';
|
||||
import 'package:aves/widgets/collection/filter_bar.dart';
|
||||
import 'package:aves/widgets/collection/query_bar.dart';
|
||||
import 'package:aves/widgets/common/animated_icons_fix.dart';
|
||||
import 'package:aves/widgets/common/app_bar_subtitle.dart';
|
||||
import 'package:aves/widgets/common/app_bar_title.dart';
|
||||
import 'package:aves/widgets/common/basic/menu.dart';
|
||||
|
@ -164,8 +165,9 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
|
|||
return IconButton(
|
||||
// key is expected by test driver
|
||||
key: const Key('appbar-leading-button'),
|
||||
icon: AnimatedIcon(
|
||||
icon: AnimatedIcons.menu_arrow,
|
||||
// TODO TLAD [rtl] replace to regular `AnimatedIcon` when this is fixed: https://github.com/flutter/flutter/issues/60521
|
||||
icon: AnimatedIconFixIssue60521(
|
||||
icon: AnimatedIconsFixIssue60521.menu_arrow,
|
||||
progress: _browseToSelectAnimation,
|
||||
),
|
||||
onPressed: onPressed,
|
||||
|
|
|
@ -238,6 +238,7 @@ class _CollectionScaler extends StatelessWidget {
|
|||
borderWidth: DecoratedThumbnail.borderWidth,
|
||||
borderRadius: Radius.zero,
|
||||
color: DecoratedThumbnail.borderColor,
|
||||
textDirection: Directionality.of(context),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
|
|
|
@ -90,7 +90,7 @@ class _FilterBarState extends State<FilterBar> {
|
|||
initialItemCount: widget.filters.length,
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
itemBuilder: (context, index, animation) {
|
||||
if (index >= widget.filters.length) return const SizedBox();
|
||||
return _buildChip(widget.filters.toList()[index]);
|
||||
|
@ -102,7 +102,7 @@ class _FilterBarState extends State<FilterBar> {
|
|||
|
||||
Padding _buildChip(CollectionFilter filter) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Center(
|
||||
child: AvesFilterChip(
|
||||
key: ValueKey(filter),
|
||||
|
|
1315
lib/widgets/common/animated_icons_fix.dart
Normal file
1315
lib/widgets/common/animated_icons_fix.dart
Normal file
File diff suppressed because it is too large
Load diff
|
@ -116,7 +116,7 @@ class ScrollLabel extends StatelessWidget {
|
|||
return FadeTransition(
|
||||
opacity: animation,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.only(right: 12.0),
|
||||
margin: const EdgeInsetsDirectional.only(end: 12.0),
|
||||
child: Material(
|
||||
elevation: 4.0,
|
||||
color: backgroundColor,
|
||||
|
@ -350,8 +350,8 @@ class SlideFadeTransition extends StatelessWidget {
|
|||
builder: (context, child) => animation.value == 0.0 ? Container() : child!,
|
||||
child: SlideTransition(
|
||||
position: Tween(
|
||||
begin: const Offset(0.3, 0.0),
|
||||
end: const Offset(0.0, 0.0),
|
||||
begin: Offset((Directionality.of(context) == TextDirection.ltr ? 1 : -1) * .3, 0),
|
||||
end: Offset.zero,
|
||||
).animate(animation),
|
||||
child: FadeTransition(
|
||||
opacity: animation,
|
||||
|
|
|
@ -18,7 +18,7 @@ class MenuRow extends StatelessWidget {
|
|||
children: [
|
||||
if (icon != null)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
padding: const EdgeInsetsDirectional.only(end: 8),
|
||||
child: icon,
|
||||
),
|
||||
Expanded(child: Text(text)),
|
||||
|
|
|
@ -25,22 +25,12 @@ class SectionHeader<T> extends StatelessWidget {
|
|||
}) : super(key: key);
|
||||
|
||||
static const leadingDimension = 32.0;
|
||||
static const leadingPadding = EdgeInsets.only(right: 8, bottom: 4);
|
||||
static const trailingPadding = EdgeInsets.only(left: 8, bottom: 2);
|
||||
static const padding = EdgeInsets.all(16);
|
||||
static const widgetSpanAlignment = PlaceholderAlignment.middle;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
padding: padding,
|
||||
constraints: const BoxConstraints(minHeight: leadingDimension),
|
||||
child: GestureDetector(
|
||||
onTap: selectable ? () => _toggleSectionSelection(context) : null,
|
||||
child: Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
final spans = [
|
||||
WidgetSpan(
|
||||
alignment: widgetSpanAlignment,
|
||||
child: _SectionSelectableLeading<T>(
|
||||
|
@ -48,7 +38,7 @@ class SectionHeader<T> extends StatelessWidget {
|
|||
sectionKey: sectionKey,
|
||||
browsingBuilder: leading != null
|
||||
? (context) => Container(
|
||||
padding: leadingPadding,
|
||||
padding: const EdgeInsetsDirectional.only(end: 8, bottom: 4),
|
||||
width: leadingDimension,
|
||||
height: leadingDimension,
|
||||
child: leading,
|
||||
|
@ -65,12 +55,26 @@ class SectionHeader<T> extends StatelessWidget {
|
|||
WidgetSpan(
|
||||
alignment: widgetSpanAlignment,
|
||||
child: Container(
|
||||
padding: trailingPadding,
|
||||
padding: const EdgeInsetsDirectional.only(start: 8, bottom: 2),
|
||||
child: trailing,
|
||||
),
|
||||
),
|
||||
],
|
||||
];
|
||||
|
||||
return Container(
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
padding: padding,
|
||||
constraints: const BoxConstraints(minHeight: leadingDimension),
|
||||
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(),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -100,7 +104,7 @@ class SectionHeader<T> extends StatelessWidget {
|
|||
final para = RenderParagraph(
|
||||
TextSpan(
|
||||
children: [
|
||||
// as of Flutter v1.22.3, `RenderParagraph` fails to lay out `WidgetSpan` offscreen
|
||||
// as of Flutter v2.8.1, `RenderParagraph` fails to lay out `WidgetSpan` offscreen
|
||||
// so we use a hair space times a magic number to match width
|
||||
TextSpan(
|
||||
// 23 hair spaces match a width of 40.0
|
||||
|
|
|
@ -304,7 +304,7 @@ class _ScaleOverlayState extends State<_ScaleOverlay> {
|
|||
gradientCenter = center;
|
||||
break;
|
||||
case TileLayout.list:
|
||||
gradientCenter = Offset(0, center.dy);
|
||||
gradientCenter = Offset(Directionality.of(context) == TextDirection.rtl ? gridWidth : 0, center.dy);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -338,6 +338,7 @@ class GridPainter extends CustomPainter {
|
|||
final double spacing, borderWidth;
|
||||
final Radius borderRadius;
|
||||
final Color color;
|
||||
final TextDirection textDirection;
|
||||
|
||||
const GridPainter({
|
||||
required this.tileLayout,
|
||||
|
@ -347,6 +348,7 @@ class GridPainter extends CustomPainter {
|
|||
required this.borderWidth,
|
||||
required this.borderRadius,
|
||||
required this.color,
|
||||
required this.textDirection,
|
||||
});
|
||||
|
||||
@override
|
||||
|
@ -375,7 +377,8 @@ class GridPainter extends CustomPainter {
|
|||
break;
|
||||
case TileLayout.list:
|
||||
chipSize = Size.square(tileSize.shortestSide);
|
||||
chipCenter = Offset(chipSize.width / 2, tileCenter.dy);
|
||||
final chipCenterToEdge = chipSize.width / 2;
|
||||
chipCenter = Offset(textDirection == TextDirection.rtl ? size.width - chipCenterToEdge : chipCenterToEdge, tileCenter.dy);
|
||||
deltaColumn = 0;
|
||||
strokeShader = ui.Gradient.linear(
|
||||
tileCenter - Offset(0, chipSize.shortestSide * 3),
|
||||
|
|
|
@ -133,6 +133,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
|||
width: tileWidth,
|
||||
height: tileHeight,
|
||||
spacing: spacing,
|
||||
textDirection: Directionality.of(context),
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
@ -283,12 +284,14 @@ class SectionLayout extends Equatable {
|
|||
|
||||
class _GridRow extends MultiChildRenderObjectWidget {
|
||||
final double width, height, spacing;
|
||||
final TextDirection textDirection;
|
||||
|
||||
_GridRow({
|
||||
Key? key,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.spacing,
|
||||
required this.textDirection,
|
||||
required List<Widget> children,
|
||||
}) : super(key: key, children: children);
|
||||
|
||||
|
@ -298,6 +301,7 @@ class _GridRow extends MultiChildRenderObjectWidget {
|
|||
width: width,
|
||||
height: height,
|
||||
spacing: spacing,
|
||||
textDirection: textDirection,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -306,6 +310,7 @@ class _GridRow extends MultiChildRenderObjectWidget {
|
|||
renderObject.width = width;
|
||||
renderObject.height = height;
|
||||
renderObject.spacing = spacing;
|
||||
renderObject.textDirection = textDirection;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -314,6 +319,7 @@ class _GridRow extends MultiChildRenderObjectWidget {
|
|||
properties.add(DoubleProperty('width', width));
|
||||
properties.add(DoubleProperty('height', height));
|
||||
properties.add(DoubleProperty('spacing', spacing));
|
||||
properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,9 +331,11 @@ class _RenderGridRow extends RenderBox with ContainerRenderObjectMixin<RenderBox
|
|||
required double width,
|
||||
required double height,
|
||||
required double spacing,
|
||||
required TextDirection textDirection,
|
||||
}) : _width = width,
|
||||
_height = height,
|
||||
_spacing = spacing {
|
||||
_spacing = spacing,
|
||||
_textDirection = textDirection {
|
||||
addAll(children);
|
||||
}
|
||||
|
||||
|
@ -358,6 +366,15 @@ class _RenderGridRow extends RenderBox with ContainerRenderObjectMixin<RenderBox
|
|||
markNeedsLayout();
|
||||
}
|
||||
|
||||
TextDirection get textDirection => _textDirection;
|
||||
TextDirection _textDirection;
|
||||
|
||||
set textDirection(TextDirection value) {
|
||||
if (_textDirection == value) return;
|
||||
_textDirection = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
@override
|
||||
void setupParentData(RenderBox child) {
|
||||
if (child.parentData is! _GridRowParentData) {
|
||||
|
@ -388,12 +405,14 @@ class _RenderGridRow extends RenderBox with ContainerRenderObjectMixin<RenderBox
|
|||
}
|
||||
size = Size(constraints.maxWidth, height);
|
||||
final childConstraints = BoxConstraints.tight(Size(width, height));
|
||||
var offset = Offset.zero;
|
||||
final flipMainAxis = textDirection == TextDirection.rtl;
|
||||
var offset = Offset(flipMainAxis ? size.width - width : 0, 0);
|
||||
final dx = (flipMainAxis ? -1 : 1) * (width + spacing);
|
||||
while (child != null) {
|
||||
child.layout(childConstraints, parentUsesSize: false);
|
||||
final childParentData = child.parentData! as _GridRowParentData;
|
||||
childParentData.offset = offset;
|
||||
offset += Offset(width + spacing, 0);
|
||||
offset += Offset(dx, 0);
|
||||
child = childParentData.nextSibling;
|
||||
}
|
||||
}
|
||||
|
@ -419,5 +438,6 @@ class _RenderGridRow extends RenderBox with ContainerRenderObjectMixin<RenderBox
|
|||
properties.add(DoubleProperty('width', width));
|
||||
properties.add(DoubleProperty('height', height));
|
||||
properties.add(DoubleProperty('spacing', spacing));
|
||||
properties.add(EnumProperty<TextDirection>('textDirection', textDirection));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -215,7 +215,7 @@ class OverlayIcon extends StatelessWidget {
|
|||
|
||||
return Container(
|
||||
margin: margin,
|
||||
padding: text != null ? EdgeInsets.only(right: size / 4) : null,
|
||||
padding: text != null ? EdgeInsetsDirectional.only(end: size / 4) : null,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xBB000000),
|
||||
borderRadius: BorderRadius.all(Radius.circular(size)),
|
||||
|
|
|
@ -15,7 +15,7 @@ ScrollThumbBuilder avesScrollThumbBuilder({
|
|||
borderRadius: BorderRadius.all(Radius.circular(12)),
|
||||
),
|
||||
height: height,
|
||||
margin: const EdgeInsets.only(right: .5),
|
||||
margin: const EdgeInsetsDirectional.only(end: 1),
|
||||
padding: const EdgeInsets.all(2),
|
||||
child: ClipPath(
|
||||
clipper: ArrowClipper(),
|
||||
|
|
|
@ -4,6 +4,7 @@ import 'package:aves/model/filters/filters.dart';
|
|||
import 'package:aves/model/selection.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/widgets/common/animated_icons_fix.dart';
|
||||
import 'package:aves/widgets/common/app_bar_subtitle.dart';
|
||||
import 'package:aves/widgets/common/app_bar_title.dart';
|
||||
import 'package:aves/widgets/common/basic/menu.dart';
|
||||
|
@ -97,8 +98,9 @@ class _FilterGridAppBarState<T extends CollectionFilter> extends State<FilterGri
|
|||
return IconButton(
|
||||
// key is expected by test driver
|
||||
key: const Key('appbar-leading-button'),
|
||||
icon: AnimatedIcon(
|
||||
icon: AnimatedIcons.menu_arrow,
|
||||
// TODO TLAD [rtl] replace to regular `AnimatedIcon` when this is fixed: https://github.com/flutter/flutter/issues/60521
|
||||
icon: AnimatedIconFixIssue60521(
|
||||
icon: AnimatedIconsFixIssue60521.menu_arrow,
|
||||
progress: _browseToSelectAnimation,
|
||||
),
|
||||
onPressed: onPressed,
|
||||
|
|
|
@ -445,6 +445,7 @@ class _FilterScaler<T extends CollectionFilter> extends StatelessWidget {
|
|||
borderWidth: AvesFilterChip.outlineWidth,
|
||||
borderRadius: CoveredFilterChip.radius(tileSize.shortestSide),
|
||||
color: Colors.grey.shade700,
|
||||
textDirection: Directionality.of(context),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:aves/model/source/tag.dart';
|
|||
import 'package:aves/ref/mime_types.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/collection/collection_page.dart';
|
||||
import 'package:aves/widgets/common/animated_icons_fix.dart';
|
||||
import 'package:aves/widgets/common/expandable_filter_row.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
|
@ -60,8 +61,9 @@ class CollectionSearchDelegate {
|
|||
// so the leading may mistakenly switch to the close button
|
||||
return canPop
|
||||
? IconButton(
|
||||
icon: AnimatedIcon(
|
||||
icon: AnimatedIcons.menu_arrow,
|
||||
// TODO TLAD [rtl] replace to regular `AnimatedIcon` when this is fixed: https://github.com/flutter/flutter/issues/60521
|
||||
icon: AnimatedIconFixIssue60521(
|
||||
icon: AnimatedIconsFixIssue60521.menu_arrow,
|
||||
progress: transitionAnimation,
|
||||
),
|
||||
onPressed: () => _goBack(context),
|
||||
|
|
|
@ -3,7 +3,7 @@ import 'package:aves/utils/color_utils.dart';
|
|||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||
|
||||
class FilterTable<T extends Comparable> extends StatelessWidget {
|
||||
|
@ -40,6 +40,7 @@ class FilterTable<T extends Comparable> extends StatelessWidget {
|
|||
|
||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
||||
final lineHeight = 16 * textScaleFactor;
|
||||
final isRTL = Directionality.of(context) == TextDirection.rtl;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: AvesFilterChip.outlineWidth / 2 + 6, end: 8),
|
||||
|
@ -73,9 +74,10 @@ class FilterTable<T extends Comparable> extends StatelessWidget {
|
|||
backgroundColor: Colors.white24,
|
||||
progressColor: stringToColor(label),
|
||||
animation: true,
|
||||
isRTL: isRTL,
|
||||
padding: EdgeInsets.symmetric(horizontal: lineHeight),
|
||||
center: Text(
|
||||
NumberFormat.percentPattern().format(percent),
|
||||
intl.NumberFormat.percentPattern().format(percent),
|
||||
style: const TextStyle(shadows: Constants.embossShadows),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -24,7 +24,7 @@ import 'package:charts_flutter/flutter.dart' as charts;
|
|||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'package:percent_indicator/linear_percent_indicator.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
@ -100,20 +100,24 @@ class StatsPage extends StatelessWidget {
|
|||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
LinearPercentIndicator(
|
||||
Padding(
|
||||
// end padding to match leading, so that inside label is aligned with outside label below
|
||||
padding: const EdgeInsetsDirectional.only(end: 24),
|
||||
child: LinearPercentIndicator(
|
||||
percent: withGpsPercent,
|
||||
lineHeight: lineHeight,
|
||||
backgroundColor: Colors.white24,
|
||||
progressColor: Theme.of(context).colorScheme.secondary,
|
||||
animation: animate,
|
||||
isRTL: Directionality.of(context) == TextDirection.rtl,
|
||||
leading: const Icon(AIcons.location),
|
||||
// right padding to match leading, so that inside label is aligned with outside label below
|
||||
padding: EdgeInsets.symmetric(horizontal: lineHeight) + const EdgeInsets.only(right: 24),
|
||||
padding: EdgeInsets.symmetric(horizontal: lineHeight),
|
||||
center: Text(
|
||||
NumberFormat.percentPattern().format(withGpsPercent),
|
||||
intl.NumberFormat.percentPattern().format(withGpsPercent),
|
||||
style: const TextStyle(shadows: Constants.embossShadows),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
context.l10n.statsWithGps(withGpsCount),
|
||||
|
|
|
@ -28,7 +28,9 @@ class ViewerDebugPage extends StatelessWidget {
|
|||
Tuple2(const Tab(icon: Icon(AIcons.android)), MetadataTab(entry: entry)),
|
||||
Tuple2(const Tab(icon: Icon(AIcons.image)), _buildThumbnailsTabView()),
|
||||
];
|
||||
return DefaultTabController(
|
||||
return Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: DefaultTabController(
|
||||
length: tabs.length,
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
|
@ -43,6 +45,7 @@ class ViewerDebugPage extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -79,7 +80,6 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
|||
// compute the size of keys and space in order to align values
|
||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
||||
final keySizes = Map.fromEntries(keyValues.keys.map((key) => MapEntry(key, _getSpanWidth(TextSpan(text: key, style: InfoRowGroup.keyStyle), textScaleFactor))));
|
||||
final baseSpaceWidth = _getSpanWidth(TextSpan(text: '\u200A' * 100, style: InfoRowGroup.baseStyle), textScaleFactor);
|
||||
|
||||
final lastKey = keyValues.keys.last;
|
||||
return LayoutBuilder(
|
||||
|
@ -88,10 +88,7 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
|||
final maxBaseValueX = constraints.maxWidth / 3;
|
||||
final baseValueX = keySizes.values.where((size) => size < maxBaseValueX).fold(0.0, max);
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SelectableText.rich(
|
||||
return SelectableText.rich(
|
||||
TextSpan(
|
||||
children: keyValues.entries.expand(
|
||||
(kv) {
|
||||
|
@ -121,23 +118,21 @@ class _InfoRowGroupState extends State<InfoRowGroup> {
|
|||
value = '$value\n';
|
||||
}
|
||||
|
||||
// as of Flutter v2.5.3, `SelectableText` cannot contain `WidgetSpan`
|
||||
// so we add padding using multiple hair spaces instead
|
||||
// TODO TLAD 2021/10/26 other `InlineSpan` now possible thanks to https://github.com/flutter/flutter/pull/92295
|
||||
final thisSpaceSize = max(0.0, (baseValueX - keySizes[key]!)) + InfoRowGroup.keyValuePadding;
|
||||
final spaceCount = (100 * thisSpaceSize / baseSpaceWidth).round();
|
||||
|
||||
// each text span embeds and pops a Bidi isolate,
|
||||
// so that layout of the spans follows the directionality of the locale
|
||||
// (e.g. keys on the right for RTL locale, whatever the key intrinsic directionality)
|
||||
// and each span respects the directionality of its inner text only
|
||||
return [
|
||||
TextSpan(text: key, style: InfoRowGroup.keyStyle),
|
||||
TextSpan(text: '\u200A' * spaceCount),
|
||||
TextSpan(text: value, style: style, recognizer: recognizer),
|
||||
TextSpan(text: '${Constants.fsi}$key${Constants.pdi}', style: InfoRowGroup.keyStyle),
|
||||
WidgetSpan(child: SizedBox(width: thisSpaceSize)),
|
||||
TextSpan(text: '${Constants.fsi}$value${Constants.pdi}', style: style, recognizer: recognizer),
|
||||
];
|
||||
},
|
||||
).toList(),
|
||||
),
|
||||
style: InfoRowGroup.baseStyle,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:aves/model/entry.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/widgets/common/animated_icons_fix.dart';
|
||||
import 'package:aves/widgets/common/extensions/build_context.dart';
|
||||
import 'package:aves/widgets/common/identity/empty.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
|
@ -26,8 +27,9 @@ class InfoSearchDelegate extends SearchDelegate {
|
|||
@override
|
||||
Widget buildLeading(BuildContext context) {
|
||||
return IconButton(
|
||||
icon: AnimatedIcon(
|
||||
icon: AnimatedIcons.menu_arrow,
|
||||
// TODO TLAD [rtl] replace to regular `AnimatedIcon` when this is fixed: https://github.com/flutter/flutter/issues/60521
|
||||
icon: AnimatedIconFixIssue60521(
|
||||
icon: AnimatedIconsFixIssue60521.menu_arrow,
|
||||
progress: transitionAnimation,
|
||||
),
|
||||
onPressed: () => Navigator.pop(context),
|
||||
|
|
|
@ -58,10 +58,7 @@ class _OwnerPropState extends State<OwnerProp> {
|
|||
future: _appNameLoader,
|
||||
builder: (context, snapshot) {
|
||||
final appName = androidFileUtils.getCurrentAppName(ownerPackage) ?? ownerPackage;
|
||||
// as of Flutter v2.5.3, `SelectableText` cannot contain `WidgetSpan`
|
||||
// so we use a basic `Text` instead
|
||||
// TODO TLAD 2021/10/26 other `InlineSpan` now possible thanks to https://github.com/flutter/flutter/pull/92295
|
||||
return Text.rich(
|
||||
return SelectableText.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
|
|
|
@ -175,6 +175,9 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
|
|||
),
|
||||
ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
child: Directionality(
|
||||
// force directionality for `LinearProgressIndicator`
|
||||
textDirection: TextDirection.ltr,
|
||||
child: StreamBuilder<int>(
|
||||
stream: positionStream,
|
||||
builder: (context, snapshot) {
|
||||
|
@ -187,6 +190,7 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
|
|||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
const Text(
|
||||
// fake text below to match the height of the text above and center the whole thing
|
||||
'',
|
||||
|
|
|
@ -84,8 +84,8 @@ class SubtitleStyle extends Equatable with Diagnosticable {
|
|||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<TextAlign>('hAlign', hAlign));
|
||||
properties.add(DiagnosticsProperty<TextAlignVertical>('vAlign', vAlign));
|
||||
properties.add(EnumProperty<TextAlign>('hAlign', hAlign));
|
||||
properties.add(EnumProperty<TextAlignVertical>('vAlign', vAlign));
|
||||
properties.add(ColorProperty('borderColor', borderColor));
|
||||
properties.add(DoubleProperty('borderWidth', borderWidth));
|
||||
properties.add(DoubleProperty('edgeBlur', edgeBlur));
|
||||
|
|
46
pubspec.lock
46
pubspec.lock
|
@ -196,7 +196,7 @@ packages:
|
|||
name: dbus
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.6.6"
|
||||
version: "0.6.8"
|
||||
decorated_icon:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -305,7 +305,7 @@ packages:
|
|||
name: firebase_core
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.10.6"
|
||||
version: "1.11.0"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -326,14 +326,14 @@ packages:
|
|||
name: firebase_crashlytics
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.4"
|
||||
version: "2.4.5"
|
||||
firebase_crashlytics_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_crashlytics_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.11"
|
||||
version: "3.1.12"
|
||||
flex_color_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -447,7 +447,7 @@ packages:
|
|||
name: github
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.5.0"
|
||||
version: "9.0.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -475,7 +475,7 @@ packages:
|
|||
name: google_maps_flutter_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.5"
|
||||
highlight:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -510,7 +510,7 @@ packages:
|
|||
name: image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "3.1.1"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -727,21 +727,21 @@ packages:
|
|||
name: path_provider_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.5"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.0.3"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
version: "2.0.5"
|
||||
pdf:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -797,7 +797,7 @@ packages:
|
|||
name: plugin_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.2"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -895,28 +895,28 @@ packages:
|
|||
name: shared_preferences
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.11"
|
||||
version: "2.0.12"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.9"
|
||||
version: "2.0.10"
|
||||
shared_preferences_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.8"
|
||||
version: "2.0.9"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
version: "2.0.4"
|
||||
shared_preferences_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -937,14 +937,14 @@ packages:
|
|||
name: shared_preferences_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.0.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
version: "2.0.4"
|
||||
shelf:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1005,7 +1005,7 @@ packages:
|
|||
name: sqflite
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.0.2"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1039,7 +1039,7 @@ packages:
|
|||
description:
|
||||
path: "."
|
||||
ref: HEAD
|
||||
resolved-ref: fba50f0e380d8cbd6a5bbda32f97a9c5e4d033e2
|
||||
resolved-ref: cd5ccd925d0348218aaf156f0b9dc4f8caaec7cc
|
||||
url: "git://github.com/deckerst/aves_streams_channel.git"
|
||||
source: git
|
||||
version: "0.3.0"
|
||||
|
@ -1126,21 +1126,21 @@ packages:
|
|||
name: url_launcher
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.17"
|
||||
version: "6.0.18"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.13"
|
||||
version: "6.0.14"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "6.0.13"
|
||||
version: "6.0.14"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1168,7 +1168,7 @@ packages:
|
|||
name: url_launcher_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
version: "2.0.6"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
Loading…
Reference in a new issue