fixed stream id when locale uses non western arabic numerals, rtl prep

This commit is contained in:
Thibault Deckers 2022-01-15 14:51:02 +09:00
parent 41a4577f36
commit 2f09ca8245
25 changed files with 1532 additions and 169 deletions

View file

@ -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) {

View file

@ -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);

View file

@ -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,

View file

@ -238,6 +238,7 @@ class _CollectionScaler extends StatelessWidget {
borderWidth: DecoratedThumbnail.borderWidth,
borderRadius: Radius.zero,
color: DecoratedThumbnail.borderColor,
textDirection: Directionality.of(context),
),
child: child,
),

View file

@ -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),

File diff suppressed because it is too large Load diff

View file

@ -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,

View file

@ -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)),

View file

@ -25,13 +25,42 @@ 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) {
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,
@ -39,38 +68,13 @@ 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: [
WidgetSpan(
alignment: widgetSpanAlignment,
child: _SectionSelectableLeading<T>(
selectable: selectable,
sectionKey: sectionKey,
browsingBuilder: leading != null
? (context) => Container(
padding: leadingPadding,
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: trailingPadding,
child: trailing,
),
),
],
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

View file

@ -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),

View file

@ -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));
}
}

View file

@ -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)),

View file

@ -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(),

View file

@ -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,

View file

@ -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,
),

View file

@ -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),

View file

@ -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),
),
),

View file

@ -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,18 +100,22 @@ class StatsPage extends StatelessWidget {
padding: const EdgeInsets.all(16),
child: Column(
children: [
LinearPercentIndicator(
percent: withGpsPercent,
lineHeight: lineHeight,
backgroundColor: Colors.white24,
progressColor: Theme.of(context).colorScheme.secondary,
animation: animate,
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),
center: Text(
NumberFormat.percentPattern().format(withGpsPercent),
style: const TextStyle(shadows: Constants.embossShadows),
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),
padding: EdgeInsets.symmetric(horizontal: lineHeight),
center: Text(
intl.NumberFormat.percentPattern().format(withGpsPercent),
style: const TextStyle(shadows: Constants.embossShadows),
),
),
),
const SizedBox(height: 8),

View file

@ -28,18 +28,21 @@ class ViewerDebugPage extends StatelessWidget {
Tuple2(const Tab(icon: Icon(AIcons.android)), MetadataTab(entry: entry)),
Tuple2(const Tab(icon: Icon(AIcons.image)), _buildThumbnailsTabView()),
];
return DefaultTabController(
length: tabs.length,
child: Scaffold(
appBar: AppBar(
title: const Text('Debug'),
bottom: TabBar(
tabs: tabs.map((t) => t.item1).toList(),
return Directionality(
textDirection: TextDirection.ltr,
child: DefaultTabController(
length: tabs.length,
child: Scaffold(
appBar: AppBar(
title: const Text('Debug'),
bottom: TabBar(
tabs: tabs.map((t) => t.item1).toList(),
),
),
),
body: SafeArea(
child: TabBarView(
children: tabs.map((t) => t.item2).toList(),
body: SafeArea(
child: TabBarView(
children: tabs.map((t) => t.item2).toList(),
),
),
),
),

View file

@ -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,56 +88,51 @@ 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(
TextSpan(
children: keyValues.entries.expand(
(kv) {
final key = kv.key;
String value;
TextStyle? style;
GestureRecognizer? recognizer;
return SelectableText.rich(
TextSpan(
children: keyValues.entries.expand(
(kv) {
final key = kv.key;
String value;
TextStyle? style;
GestureRecognizer? recognizer;
if (linkHandlers?.containsKey(key) == true) {
final handler = linkHandlers![key]!;
value = handler.linkText(context);
// open link on tap
recognizer = TapGestureRecognizer()..onTap = () => handler.onTap(context);
style = InfoRowGroup.linkStyle;
} else {
value = kv.value;
// long values are clipped, and made expandable by tapping them
final showPreviewOnly = maxValueLength > 0 && value.length > maxValueLength && !_expandedKeys.contains(key);
if (showPreviewOnly) {
value = '${value.substring(0, maxValueLength)}';
// show full value on tap
recognizer = TapGestureRecognizer()..onTap = () => setState(() => _expandedKeys.add(key));
}
}
if (linkHandlers?.containsKey(key) == true) {
final handler = linkHandlers![key]!;
value = handler.linkText(context);
// open link on tap
recognizer = TapGestureRecognizer()..onTap = () => handler.onTap(context);
style = InfoRowGroup.linkStyle;
} else {
value = kv.value;
// long values are clipped, and made expandable by tapping them
final showPreviewOnly = maxValueLength > 0 && value.length > maxValueLength && !_expandedKeys.contains(key);
if (showPreviewOnly) {
value = '${value.substring(0, maxValueLength)}';
// show full value on tap
recognizer = TapGestureRecognizer()..onTap = () => setState(() => _expandedKeys.add(key));
}
}
if (key != lastKey) {
value = '$value\n';
}
if (key != lastKey) {
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();
final thisSpaceSize = max(0.0, (baseValueX - keySizes[key]!)) + InfoRowGroup.keyValuePadding;
return [
TextSpan(text: key, style: InfoRowGroup.keyStyle),
TextSpan(text: '\u200A' * spaceCount),
TextSpan(text: value, style: style, recognizer: recognizer),
];
},
).toList(),
),
style: InfoRowGroup.baseStyle,
),
],
// 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: '${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,
);
},
);

View file

@ -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),

View file

@ -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(

View file

@ -175,17 +175,21 @@ class _VideoControlOverlayState extends State<VideoControlOverlay> with SingleTi
),
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(4)),
child: StreamBuilder<int>(
stream: positionStream,
builder: (context, snapshot) {
// do not use stream snapshot because it is obsolete when switching between videos
var progress = controller?.progress ?? 0.0;
if (!progress.isFinite) progress = 0.0;
return LinearProgressIndicator(
value: progress,
backgroundColor: Colors.grey.shade700,
);
}),
child: Directionality(
// force directionality for `LinearProgressIndicator`
textDirection: TextDirection.ltr,
child: StreamBuilder<int>(
stream: positionStream,
builder: (context, snapshot) {
// do not use stream snapshot because it is obsolete when switching between videos
var progress = controller?.progress ?? 0.0;
if (!progress.isFinite) progress = 0.0;
return LinearProgressIndicator(
value: progress,
backgroundColor: Colors.grey.shade700,
);
}),
),
),
const Text(
// fake text below to match the height of the text above and center the whole thing

View file

@ -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));

View file

@ -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: