diff --git a/lib/screens/media_detail_screen.dart b/lib/screens/media_detail_screen.dart index c9beb606..f32dc7d9 100644 --- a/lib/screens/media_detail_screen.dart +++ b/lib/screens/media_detail_screen.dart @@ -8,7 +8,7 @@ import 'package:plezy/utils/platform_detector.dart'; import 'package:plezy/widgets/app_icon.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:provider/provider.dart'; -import 'package:readmore/readmore.dart'; +import '../widgets/collapsible_text.dart'; import '../focus/dpad_navigator.dart'; import '../focus/focusable_wrapper.dart'; @@ -1471,26 +1471,12 @@ class _MediaDetailScreenState extends State with WatchStateAw ), const SizedBox(height: 12), if (isTv) - Text( - metadata.summary!, - style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.6), - ) + Text(metadata.summary!, style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.6)) else - ReadMoreText( - metadata.summary!, - trimMode: TrimMode.Line, - trimLines: isMobile ? 6 : 4, - trimCollapsedText: t.common.showMore, - trimExpandedText: '${metadata.summary!.endsWith('\n') ? '' : '\n'}${t.common.showLess}', + CollapsibleText( + text: metadata.summary!, + maxLines: isMobile ? 6 : 4, style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.6), - moreStyle: TextStyle( - color: Theme.of(context).textTheme.bodyLarge?.color?.withValues(alpha: 0.4), - fontWeight: FontWeight.w600, - ), - lessStyle: TextStyle( - color: Theme.of(context).textTheme.bodyLarge?.color?.withValues(alpha: 0.4), - fontWeight: FontWeight.w600, - ), ), const SizedBox(height: 24), ], diff --git a/lib/widgets/collapsible_text.dart b/lib/widgets/collapsible_text.dart new file mode 100644 index 00000000..8b6974e7 --- /dev/null +++ b/lib/widgets/collapsible_text.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; + +class CollapsibleText extends StatefulWidget { + final String text; + final int maxLines; + final TextStyle? style; + + const CollapsibleText({super.key, required this.text, this.maxLines = 4, this.style}); + + @override + State createState() => _CollapsibleTextState(); +} + +class _CollapsibleTextState extends State { + bool _expanded = false; + + @override + Widget build(BuildContext context) { + final style = widget.style ?? DefaultTextStyle.of(context).style; + + return LayoutBuilder( + builder: (context, constraints) { + final textPainter = TextPainter( + text: TextSpan(text: widget.text, style: style), + maxLines: widget.maxLines, + textDirection: TextDirection.ltr, + )..layout(maxWidth: constraints.maxWidth); + + final overflows = textPainter.didExceedMaxLines; + + if (!overflows) { + textPainter.dispose(); + return Text(widget.text, style: style); + } + + String displayText = widget.text; + if (!_expanded) { + // Find where to truncate to leave room for the badge on the last line + final cutPoint = textPainter.getPositionForOffset( + Offset(constraints.maxWidth - 54, textPainter.height - 1), + ); + displayText = widget.text.substring(0, cutPoint.offset).trimRight(); + } + textPainter.dispose(); + + return GestureDetector( + onTap: () => setState(() => _expanded = !_expanded), + child: Text.rich( + TextSpan( + children: [ + TextSpan(text: displayText, style: style), + if (!_expanded) + WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: _buildBadge(context), + ), + ], + ), + ), + ); + }, + ); + } + + Widget _buildBadge(BuildContext context) { + return Container( + margin: const EdgeInsets.only(left: 6), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onSurface.withValues(alpha: 0.12), + borderRadius: BorderRadius.circular(10), + ), + child: Text( + '\u00B7\u00B7\u00B7', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurfaceVariant, + letterSpacing: 2, + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 2c4943fc..f5c95c5a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -926,14 +926,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" - readmore: - dependency: "direct main" - description: - name: readmore - sha256: e8fca2bd397b86342483b409e2ec26f06560a5963aceaa39b27f30722b506187 - url: "https://pub.dev" - source: hosted - version: "3.0.0" recase: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 193b78b0..8519ce10 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,7 +47,7 @@ dependencies: peerdart: ^0.5.6 in_app_review: ^2.0.11 dart_discord_presence: ^1.1.0 - readmore: ^3.0.0 + dev_dependencies: flutter_test: