Merge branch 'feature/collapse-long-descriptions'

This commit is contained in:
edde746
2026-02-04 11:59:00 +01:00
22 changed files with 184 additions and 30 deletions
+3 -1
View File
@@ -34,7 +34,9 @@
"no": "Nein",
"delete": "Löschen",
"shuffle": "Zufall",
"addTo": "Hinzufügen zu..."
"addTo": "Hinzufügen zu...",
"showMore": "Mehr anzeigen",
"showLess": "Weniger anzeigen"
},
"screens": {
"licenses": "Lizenzen",
+3 -1
View File
@@ -34,7 +34,9 @@
"no": "No",
"delete": "Delete",
"shuffle": "Shuffle",
"addTo": "Add to..."
"addTo": "Add to...",
"showMore": "Show more",
"showLess": "Show less"
},
"screens": {
"licenses": "Licenses",
+3 -1
View File
@@ -34,7 +34,9 @@
"no": "No",
"delete": "Eliminar",
"shuffle": "Aleatorio",
"addTo": "Añadir a..."
"addTo": "Añadir a...",
"showMore": "Ver más",
"showLess": "Ver menos"
},
"screens": {
"licenses": "Licencias",
+3 -1
View File
@@ -34,7 +34,9 @@
"no": "Non",
"delete": "Supprimer",
"shuffle": "Mélanger",
"addTo": "Ajouter à..."
"addTo": "Ajouter à...",
"showMore": "Voir plus",
"showLess": "Voir moins"
},
"screens": {
"licenses": "Licenses",
+3 -1
View File
@@ -34,7 +34,9 @@
"no": "No",
"delete": "Elimina",
"shuffle": "Casuale",
"addTo": "Aggiungi a..."
"addTo": "Aggiungi a...",
"showMore": "Mostra di più",
"showLess": "Mostra meno"
},
"screens": {
"licenses": "Licenze",
+3 -1
View File
@@ -34,7 +34,9 @@
"no": "아니오",
"delete": "삭제",
"shuffle": "무작위 재생",
"addTo": "추가하기..."
"addTo": "추가하기...",
"showMore": "더 보기",
"showLess": "접기"
},
"screens": {
"licenses": "라이선스",
+3 -1
View File
@@ -34,7 +34,9 @@
"no": "Nee",
"delete": "Verwijderen",
"shuffle": "Willekeurig",
"addTo": "Toevoegen aan..."
"addTo": "Toevoegen aan...",
"showMore": "Meer tonen",
"showLess": "Minder tonen"
},
"screens": {
"licenses": "Licenties",
+2 -2
View File
@@ -4,9 +4,9 @@
/// To regenerate, run: `dart run slang`
///
/// Locales: 9
/// Strings: 5169 (574 per locale)
/// Strings: 5187 (576 per locale)
///
/// Built on 2026-02-03 at 01:03 UTC
/// Built on 2026-02-03 at 19:20 UTC
// coverage:ignore-file
// ignore_for_file: type=lint, unused_import
+6 -2
View File
@@ -128,6 +128,8 @@ class _TranslationsCommonDe implements TranslationsCommonEn {
@override String get delete => 'Löschen';
@override String get shuffle => 'Zufall';
@override String get addTo => 'Hinzufügen zu...';
@override String get showMore => 'Mehr anzeigen';
@override String get showLess => 'Weniger anzeigen';
}
// Path: screens
@@ -1023,6 +1025,8 @@ extension on TranslationsDe {
'common.delete' => 'Löschen',
'common.shuffle' => 'Zufall',
'common.addTo' => 'Hinzufügen zu...',
'common.showMore' => 'Mehr anzeigen',
'common.showLess' => 'Weniger anzeigen',
'screens.licenses' => 'Lizenzen',
'screens.selectServer' => 'Server auswählen',
'screens.switchProfile' => 'Profil wechseln',
@@ -1502,10 +1506,10 @@ extension on TranslationsDe {
'collections.deleted' => 'Sammlung gelöscht',
'collections.deleteFailed' => 'Sammlung konnte nicht gelöscht werden',
'collections.deleteFailedWithError' => ({required Object error}) => 'Sammlung konnte nicht gelöscht werden: ${error}',
'collections.failedToLoadItems' => ({required Object error}) => 'Sammlungselemente konnten nicht geladen werden: ${error}',
'collections.selectCollection' => 'Sammlung auswählen',
_ => null,
} ?? switch (path) {
'collections.failedToLoadItems' => ({required Object error}) => 'Sammlungselemente konnten nicht geladen werden: ${error}',
'collections.selectCollection' => 'Sammlung auswählen',
'collections.createNewCollection' => 'Neue Sammlung erstellen',
'collections.collectionName' => 'Sammlungsname',
'collections.enterCollectionName' => 'Sammlungsnamen eingeben',
+10 -2
View File
@@ -193,6 +193,12 @@ class TranslationsCommonEn {
/// en: 'Add to...'
String get addTo => 'Add to...';
/// en: 'Show more'
String get showMore => 'Show more';
/// en: 'Show less'
String get showLess => 'Show less';
}
// Path: screens
@@ -2173,6 +2179,8 @@ extension on Translations {
'common.delete' => 'Delete',
'common.shuffle' => 'Shuffle',
'common.addTo' => 'Add to...',
'common.showMore' => 'Show more',
'common.showLess' => 'Show less',
'screens.licenses' => 'Licenses',
'screens.switchProfile' => 'Switch Profile',
'screens.subtitleStyling' => 'Subtitle Styling',
@@ -2652,10 +2660,10 @@ extension on Translations {
'watchTogether.title' => 'Watch Together',
'watchTogether.description' => 'Watch content in sync with friends and family',
'watchTogether.createSession' => 'Create Session',
'watchTogether.creating' => 'Creating...',
'watchTogether.joinSession' => 'Join Session',
_ => null,
} ?? switch (path) {
'watchTogether.creating' => 'Creating...',
'watchTogether.joinSession' => 'Join Session',
'watchTogether.joining' => 'Joining...',
'watchTogether.controlMode' => 'Control Mode',
'watchTogether.controlModeQuestion' => 'Who can control playback?',
+6 -2
View File
@@ -128,6 +128,8 @@ class _TranslationsCommonEs implements TranslationsCommonEn {
@override String get delete => 'Eliminar';
@override String get shuffle => 'Aleatorio';
@override String get addTo => 'Añadir a...';
@override String get showMore => 'Ver más';
@override String get showLess => 'Ver menos';
}
// Path: screens
@@ -1021,6 +1023,8 @@ extension on TranslationsEs {
'common.delete' => 'Eliminar',
'common.shuffle' => 'Aleatorio',
'common.addTo' => 'Añadir a...',
'common.showMore' => 'Ver más',
'common.showLess' => 'Ver menos',
'screens.licenses' => 'Licencias',
'screens.switchProfile' => 'Cambiar Perfil',
'screens.subtitleStyling' => 'Estilo de Subtítulos',
@@ -1500,10 +1504,10 @@ extension on TranslationsEs {
'watchTogether.title' => 'Ver Juntos',
'watchTogether.description' => 'Mira contenido en sincronía con amigos y familiares',
'watchTogether.createSession' => 'Crear Sesión',
'watchTogether.creating' => 'Creando...',
'watchTogether.joinSession' => 'Unirse a Sesión',
_ => null,
} ?? switch (path) {
'watchTogether.creating' => 'Creando...',
'watchTogether.joinSession' => 'Unirse a Sesión',
'watchTogether.joining' => 'Uniendo...',
'watchTogether.controlMode' => 'Modo de Control',
'watchTogether.controlModeQuestion' => '¿Quién puede controlar la reproducción?',
+6 -2
View File
@@ -128,6 +128,8 @@ class _TranslationsCommonFr implements TranslationsCommonEn {
@override String get delete => 'Supprimer';
@override String get shuffle => 'Mélanger';
@override String get addTo => 'Ajouter à...';
@override String get showMore => 'Voir plus';
@override String get showLess => 'Voir moins';
}
// Path: screens
@@ -1021,6 +1023,8 @@ extension on TranslationsFr {
'common.delete' => 'Supprimer',
'common.shuffle' => 'Mélanger',
'common.addTo' => 'Ajouter à...',
'common.showMore' => 'Voir plus',
'common.showLess' => 'Voir moins',
'screens.licenses' => 'Licenses',
'screens.switchProfile' => 'Changer de profil',
'screens.subtitleStyling' => 'Configuration des sous-titres',
@@ -1500,10 +1504,10 @@ extension on TranslationsFr {
'watchTogether.title' => 'Regarder ensemble',
'watchTogether.description' => 'Regardez du contenu en synchronisation avec vos amis et votre famille',
'watchTogether.createSession' => 'Créer une session',
'watchTogether.creating' => 'Création...',
'watchTogether.joinSession' => 'Rejoindre la session',
_ => null,
} ?? switch (path) {
'watchTogether.creating' => 'Création...',
'watchTogether.joinSession' => 'Rejoindre la session',
'watchTogether.joining' => 'Rejoindre...',
'watchTogether.controlMode' => 'Mode de contrôle',
'watchTogether.controlModeQuestion' => 'Qui peut contrôler la lecture ?',
+6 -2
View File
@@ -128,6 +128,8 @@ class _TranslationsCommonIt implements TranslationsCommonEn {
@override String get delete => 'Elimina';
@override String get shuffle => 'Casuale';
@override String get addTo => 'Aggiungi a...';
@override String get showMore => 'Mostra di più';
@override String get showLess => 'Mostra meno';
}
// Path: screens
@@ -1023,6 +1025,8 @@ extension on TranslationsIt {
'common.delete' => 'Elimina',
'common.shuffle' => 'Casuale',
'common.addTo' => 'Aggiungi a...',
'common.showMore' => 'Mostra di più',
'common.showLess' => 'Mostra meno',
'screens.licenses' => 'Licenze',
'screens.selectServer' => 'Seleziona server',
'screens.switchProfile' => 'Cambia profilo',
@@ -1502,10 +1506,10 @@ extension on TranslationsIt {
'collections.deleted' => 'Raccolta eliminata',
'collections.deleteFailed' => 'Impossibile eliminare la raccolta',
'collections.deleteFailedWithError' => ({required Object error}) => 'Impossibile eliminare la raccolta: ${error}',
'collections.failedToLoadItems' => ({required Object error}) => 'Impossibile caricare gli elementi della raccolta: ${error}',
'collections.selectCollection' => 'Seleziona raccolta',
_ => null,
} ?? switch (path) {
'collections.failedToLoadItems' => ({required Object error}) => 'Impossibile caricare gli elementi della raccolta: ${error}',
'collections.selectCollection' => 'Seleziona raccolta',
'collections.createNewCollection' => 'Crea nuova raccolta',
'collections.collectionName' => 'Nome raccolta',
'collections.enterCollectionName' => 'Inserisci nome raccolta',
+6 -2
View File
@@ -128,6 +128,8 @@ class _TranslationsCommonKo implements TranslationsCommonEn {
@override String get delete => '삭제';
@override String get shuffle => '무작위 재생';
@override String get addTo => '추가하기...';
@override String get showMore => '더 보기';
@override String get showLess => '접기';
}
// Path: screens
@@ -1023,6 +1025,8 @@ extension on TranslationsKo {
'common.delete' => '삭제',
'common.shuffle' => '무작위 재생',
'common.addTo' => '추가하기...',
'common.showMore' => '더 보기',
'common.showLess' => '접기',
'screens.licenses' => '라이선스',
'screens.selectServer' => '서버 선택',
'screens.switchProfile' => '프로필 전환',
@@ -1502,10 +1506,10 @@ extension on TranslationsKo {
'playlists.errorReordering' => '재생 목록 항목 재정렬 실패',
'playlists.errorRemoving' => '재생 목록에서 제거 실패',
'watchTogether.title' => '함께 보기',
'watchTogether.description' => '친구 및 가족과 콘텐츠를 동시에 시청하세요',
'watchTogether.createSession' => '세션 생성',
_ => null,
} ?? switch (path) {
'watchTogether.description' => '친구 및 가족과 콘텐츠를 동시에 시청하세요',
'watchTogether.createSession' => '세션 생성',
'watchTogether.creating' => '생성 중...',
'watchTogether.joinSession' => '세션 참여',
'watchTogether.joining' => '참가 중...',
+6 -2
View File
@@ -128,6 +128,8 @@ class _TranslationsCommonNl implements TranslationsCommonEn {
@override String get delete => 'Verwijderen';
@override String get shuffle => 'Willekeurig';
@override String get addTo => 'Toevoegen aan...';
@override String get showMore => 'Meer tonen';
@override String get showLess => 'Minder tonen';
}
// Path: screens
@@ -1023,6 +1025,8 @@ extension on TranslationsNl {
'common.delete' => 'Verwijderen',
'common.shuffle' => 'Willekeurig',
'common.addTo' => 'Toevoegen aan...',
'common.showMore' => 'Meer tonen',
'common.showLess' => 'Minder tonen',
'screens.licenses' => 'Licenties',
'screens.selectServer' => 'Selecteer server',
'screens.switchProfile' => 'Wissel van profiel',
@@ -1502,10 +1506,10 @@ extension on TranslationsNl {
'collections.deleted' => 'Collectie verwijderd',
'collections.deleteFailed' => 'Collectie verwijderen mislukt',
'collections.deleteFailedWithError' => ({required Object error}) => 'Collectie verwijderen mislukt: ${error}',
'collections.failedToLoadItems' => ({required Object error}) => 'Collectie-items laden mislukt: ${error}',
'collections.selectCollection' => 'Selecteer collectie',
_ => null,
} ?? switch (path) {
'collections.failedToLoadItems' => ({required Object error}) => 'Collectie-items laden mislukt: ${error}',
'collections.selectCollection' => 'Selecteer collectie',
'collections.createNewCollection' => 'Nieuwe collectie maken',
'collections.collectionName' => 'Collectienaam',
'collections.enterCollectionName' => 'Voer collectienaam in',
+6 -2
View File
@@ -128,6 +128,8 @@ class _TranslationsCommonSv implements TranslationsCommonEn {
@override String get delete => 'Ta bort';
@override String get shuffle => 'Blanda';
@override String get addTo => 'Lägg till i...';
@override String get showMore => 'Visa mer';
@override String get showLess => 'Visa mindre';
}
// Path: screens
@@ -1023,6 +1025,8 @@ extension on TranslationsSv {
'common.delete' => 'Ta bort',
'common.shuffle' => 'Blanda',
'common.addTo' => 'Lägg till i...',
'common.showMore' => 'Visa mer',
'common.showLess' => 'Visa mindre',
'screens.licenses' => 'Licenser',
'screens.selectServer' => 'Välj server',
'screens.switchProfile' => 'Byt profil',
@@ -1502,10 +1506,10 @@ extension on TranslationsSv {
'collections.deleted' => 'Samling borttagen',
'collections.deleteFailed' => 'Det gick inte att ta bort samlingen',
'collections.deleteFailedWithError' => ({required Object error}) => 'Det gick inte att ta bort samlingen: ${error}',
'collections.failedToLoadItems' => ({required Object error}) => 'Det gick inte att läsa in samlingsobjekt: ${error}',
'collections.selectCollection' => 'Välj samling',
_ => null,
} ?? switch (path) {
'collections.failedToLoadItems' => ({required Object error}) => 'Det gick inte att läsa in samlingsobjekt: ${error}',
'collections.selectCollection' => 'Välj samling',
'collections.createNewCollection' => 'Skapa ny samling',
'collections.collectionName' => 'Samlingsnamn',
'collections.enterCollectionName' => 'Ange samlingsnamn',
+6 -2
View File
@@ -128,6 +128,8 @@ class _TranslationsCommonZh implements TranslationsCommonEn {
@override String get delete => '删除';
@override String get shuffle => '随机播放';
@override String get addTo => '添加到...';
@override String get showMore => '展开';
@override String get showLess => '收起';
}
// Path: screens
@@ -1023,6 +1025,8 @@ extension on TranslationsZh {
'common.delete' => '删除',
'common.shuffle' => '随机播放',
'common.addTo' => '添加到...',
'common.showMore' => '展开',
'common.showLess' => '收起',
'screens.licenses' => '许可证',
'screens.selectServer' => '选择服务器',
'screens.switchProfile' => '切换用户',
@@ -1502,10 +1506,10 @@ extension on TranslationsZh {
'collections.deleted' => '已删除合集',
'collections.deleteFailed' => '删除合集失败',
'collections.deleteFailedWithError' => ({required Object error}) => '删除合集失败:${error}',
'collections.failedToLoadItems' => ({required Object error}) => '加载合集项目失败:${error}',
'collections.selectCollection' => '选择合集',
_ => null,
} ?? switch (path) {
'collections.failedToLoadItems' => ({required Object error}) => '加载合集项目失败:${error}',
'collections.selectCollection' => '选择合集',
'collections.createNewCollection' => '创建新合集',
'collections.collectionName' => '合集名称',
'collections.enterCollectionName' => '输入合集名称',
+3 -1
View File
@@ -34,7 +34,9 @@
"no": "Nej",
"delete": "Ta bort",
"shuffle": "Blanda",
"addTo": "Lägg till i..."
"addTo": "Lägg till i...",
"showMore": "Visa mer",
"showLess": "Visa mindre"
},
"screens": {
"licenses": "Licenser",
+3 -1
View File
@@ -34,7 +34,9 @@
"no": "否",
"delete": "删除",
"shuffle": "随机播放",
"addTo": "添加到..."
"addTo": "添加到...",
"showMore": "展开",
"showLess": "收起"
},
"screens": {
"licenses": "许可证",
+12 -1
View File
@@ -4,9 +4,11 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
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 '../widgets/collapsible_text.dart';
import '../focus/dpad_navigator.dart';
import '../focus/focusable_wrapper.dart';
@@ -1216,6 +1218,8 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
// Use full metadata if loaded, otherwise use passed metadata
final metadata = _fullMetadata ?? widget.metadata;
final isShow = metadata.isShow;
final isMobile = PlatformDetector.isMobile(context);
final isTv = PlatformDetector.isTV();
KeyEventResult handleBack(FocusNode _, KeyEvent event) =>
handleBackKeyNavigation(context, event, result: _watchStateChanged);
@@ -1466,7 +1470,14 @@ class _MediaDetailScreenState extends State<MediaDetailScreen> with WatchStateAw
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Text(metadata.summary!, style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.6)),
if (isTv)
Text(metadata.summary!, style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.6))
else
CollapsibleText(
text: metadata.summary!,
maxLines: isMobile ? 6 : 4,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(height: 1.6),
),
const SizedBox(height: 24),
],
+84
View File
@@ -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<CollapsibleText> createState() => _CollapsibleTextState();
}
class _CollapsibleTextState extends State<CollapsibleText> {
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,
),
),
);
}
}
+1
View File
@@ -48,6 +48,7 @@ dependencies:
in_app_review: ^2.0.11
dart_discord_presence: ^1.1.0
dev_dependencies:
flutter_test:
sdk: flutter