mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-28 20:32:15 +02:00
fix(album): show More only for truncated descriptions
This commit is contained in:
@@ -46,7 +46,25 @@ class AlbumDescription extends StatelessWidget {
|
||||
const TextSpan(text: '\n\n'),
|
||||
],
|
||||
];
|
||||
return TextSpan(children: spans(fragment.nodes));
|
||||
TextSpan trimEnd(TextSpan span) {
|
||||
final children = List<InlineSpan>.of(span.children ?? const []);
|
||||
while (children.isNotEmpty) {
|
||||
final last = trimEnd(children.last as TextSpan);
|
||||
if (last.toPlainText().isNotEmpty) {
|
||||
children[children.length - 1] = last;
|
||||
return TextSpan(
|
||||
text: span.text,
|
||||
style: span.style,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
children.removeLast();
|
||||
}
|
||||
return TextSpan(text: span.text?.trimRight(), style: span.style);
|
||||
}
|
||||
|
||||
// Paragraph separators at the end are not hidden editorial content.
|
||||
return trimEnd(TextSpan(children: spans(fragment.nodes)));
|
||||
}
|
||||
|
||||
void _showFullDescription(BuildContext context, TextSpan text) {
|
||||
@@ -121,43 +139,71 @@ class AlbumDescription extends StatelessWidget {
|
||||
if (text.toPlainText().trim().isEmpty) return const SizedBox.shrink();
|
||||
final theme = Theme.of(context);
|
||||
final more = MaterialLocalizations.of(context).moreButtonTooltip;
|
||||
final previewStyle = theme.textTheme.bodyLarge?.copyWith(
|
||||
fontSize: context.isMornye ? 17 : 16,
|
||||
height: 1.4,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||
child: Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => _showFullDescription(context, text),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
text,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontSize: context.isMornye ? 17 : 16,
|
||||
height: 1.4,
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
var effectiveStyle = DefaultTextStyle.of(
|
||||
context,
|
||||
).style.merge(previewStyle);
|
||||
if (MediaQuery.boldTextOf(context)) {
|
||||
effectiveStyle = effectiveStyle.merge(
|
||||
const TextStyle(fontWeight: FontWeight.bold),
|
||||
);
|
||||
}
|
||||
final painter = TextPainter(
|
||||
text: TextSpan(style: effectiveStyle, children: [text]),
|
||||
maxLines: 2,
|
||||
textDirection: Directionality.of(context),
|
||||
textScaler: MediaQuery.textScalerOf(context),
|
||||
locale: Localizations.maybeLocaleOf(context),
|
||||
)..layout(maxWidth: constraints.maxWidth);
|
||||
final hasMore = painter.didExceedMaxLines;
|
||||
painter.dispose();
|
||||
return Column(
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: hasMore
|
||||
? () => _showFullDescription(context, text)
|
||||
: null,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text.rich(
|
||||
text,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: previewStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (hasMore) ...[
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
context.isMornye ? more.toUpperCase() : more,
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
context.isMornye ? more.toUpperCase() : more,
|
||||
style: theme.textTheme.labelLarge?.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Divider(color: theme.colorScheme.onSurface.withValues(alpha: 0.15)),
|
||||
],
|
||||
Divider(
|
||||
color: theme.colorScheme.onSurface.withValues(alpha: 0.15),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,6 +44,88 @@ void main() {
|
||||
);
|
||||
|
||||
for (final style in ['mornye-light', 'mornye-dark', 'material']) {
|
||||
testWidgets('short album notes have no More action ($style)', (
|
||||
tester,
|
||||
) async {
|
||||
final mornye = style.startsWith('mornye');
|
||||
tester.view.physicalSize = const Size(393, 700);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
for (final description in [
|
||||
'A short description.',
|
||||
'<p>A <b>bold</b> sound.</p>\n\n',
|
||||
'<div><p>First line.<br>Second line.</p></div>',
|
||||
]) {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: mornye
|
||||
? MornyeTheme.build(
|
||||
style.endsWith('light')
|
||||
? Brightness.light
|
||||
: Brightness.dark,
|
||||
)
|
||||
: ThemeData(),
|
||||
home: Scaffold(
|
||||
body: AlbumDescription(title: 'Album', description: description),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text(mornye ? 'MORE' : 'More'), findsNothing);
|
||||
final tap = tester.widget<InkWell>(find.byType(InkWell));
|
||||
expect(tap.onTap, isNull);
|
||||
expect(tester.takeException(), isNull);
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('More responds to album preview width and text size ($style)', (
|
||||
tester,
|
||||
) async {
|
||||
final mornye = style.startsWith('mornye');
|
||||
final scale = ValueNotifier(1.0);
|
||||
addTearDown(scale.dispose);
|
||||
tester.view.physicalSize = const Size(393, 700);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: mornye
|
||||
? MornyeTheme.build(
|
||||
style.endsWith('light') ? Brightness.light : Brightness.dark,
|
||||
)
|
||||
: ThemeData(),
|
||||
home: ValueListenableBuilder<double>(
|
||||
valueListenable: scale,
|
||||
builder: (context, value, child) => MediaQuery(
|
||||
data: MediaQuery.of(
|
||||
context,
|
||||
).copyWith(textScaler: TextScaler.linear(value)),
|
||||
child: child!,
|
||||
),
|
||||
child: const Scaffold(
|
||||
body: AlbumDescription(
|
||||
title: 'Album',
|
||||
description: 'New sounds and new stories.',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
final more = find.text(mornye ? 'MORE' : 'More');
|
||||
expect(more, findsNothing);
|
||||
tester.view.physicalSize = const Size(180, 700);
|
||||
await tester.pumpAndSettle();
|
||||
expect(more, findsOneWidget);
|
||||
tester.view.physicalSize = const Size(393, 700);
|
||||
await tester.pumpAndSettle();
|
||||
expect(more, findsNothing);
|
||||
scale.value = 2.5;
|
||||
await tester.pumpAndSettle();
|
||||
expect(more, findsOneWidget);
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('album description opens a formatted reading sheet ($style)', (
|
||||
tester,
|
||||
) async {
|
||||
|
||||
Reference in New Issue
Block a user