@@ -21,6 +21,7 @@ import '../../widgets/media_context_menu.dart';
|
||||
import '../../widgets/media_progress_bar.dart';
|
||||
import '../../widgets/optimized_media_image.dart';
|
||||
import '../../widgets/overlay_sheet.dart';
|
||||
import '../../widgets/unwatched_count_badge.dart';
|
||||
import '../../theme/mono_tokens.dart';
|
||||
import '../../i18n/strings.g.dart';
|
||||
import '../../widgets/loading_indicator_box.dart';
|
||||
@@ -406,24 +407,7 @@ class _FolderTreeItemState extends State<FolderTreeItem> with ContextMenuTapMixi
|
||||
(item.kind == MediaKind.show || item.kind == MediaKind.season) &&
|
||||
unwatchedCount != null &&
|
||||
unwatchedCount > 0)
|
||||
Positioned(
|
||||
top: 3,
|
||||
right: 3,
|
||||
child: Container(
|
||||
width: 20,
|
||||
height: 20,
|
||||
decoration: BoxDecoration(
|
||||
color: tokens(context).text,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.3), blurRadius: 4)],
|
||||
),
|
||||
alignment: .center,
|
||||
child: Text(
|
||||
'$unwatchedCount',
|
||||
style: TextStyle(color: tokens(context).bg, fontSize: 10, fontWeight: .bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
Positioned(top: 3, right: 3, child: UnwatchedCountBadge(count: unwatchedCount, size: 20, fontSize: 10)),
|
||||
// Progress bar
|
||||
if (hasActiveProgress)
|
||||
Positioned(
|
||||
|
||||
@@ -28,6 +28,7 @@ import 'media_context_menu.dart';
|
||||
import 'media_progress_bar.dart';
|
||||
import 'media_card_list_layout.dart';
|
||||
import 'backend_badge.dart';
|
||||
import 'unwatched_count_badge.dart';
|
||||
import 'optimized_media_image.dart';
|
||||
|
||||
const _failedPosterUrlCacheLimit = 512;
|
||||
@@ -904,6 +905,7 @@ class _MediaCardHelpers {
|
||||
/// Builds watch progress overlay (checkmark for watched, progress bar for in-progress)
|
||||
static Widget buildWatchProgress(BuildContext context, MediaItem mi) {
|
||||
final showUnwatchedCount = SettingsService.instance.read(SettingsService.showUnwatchedCount);
|
||||
final unwatched = mi.unwatchedCount;
|
||||
|
||||
final hasActiveProgress =
|
||||
mi.viewOffsetMs != null && mi.durationMs != null && mi.viewOffsetMs! > 0 && mi.viewOffsetMs! < mi.durationMs!;
|
||||
@@ -928,25 +930,9 @@ class _MediaCardHelpers {
|
||||
if (showUnwatchedCount &&
|
||||
!mi.isWatched &&
|
||||
(mi.kind == MediaKind.show || mi.kind == MediaKind.season) &&
|
||||
(mi.leafCount != null && mi.leafCount! > 0 && mi.viewedLeafCount != null))
|
||||
Positioned(
|
||||
top: 4,
|
||||
right: 4,
|
||||
child: Container(
|
||||
width: 24,
|
||||
height: 24,
|
||||
decoration: BoxDecoration(
|
||||
color: tokens(context).text,
|
||||
shape: BoxShape.circle,
|
||||
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.3), blurRadius: 4)],
|
||||
),
|
||||
alignment: .center,
|
||||
child: Text(
|
||||
'${mi.leafCount! - mi.viewedLeafCount!}',
|
||||
style: TextStyle(color: tokens(context).bg, fontSize: 12, fontWeight: .bold),
|
||||
),
|
||||
),
|
||||
),
|
||||
unwatched != null &&
|
||||
unwatched > 0)
|
||||
Positioned(top: 4, right: 4, child: UnwatchedCountBadge(count: unwatched)),
|
||||
// Progress bar for partially watched content (episodes/movies)
|
||||
if (hasActiveProgress)
|
||||
Positioned(
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../theme/mono_tokens.dart';
|
||||
|
||||
/// Unwatched-episode count chip shown in the top-right corner of poster
|
||||
/// cards. Keeps a [size]-diameter circular footprint for 1–2 digit counts
|
||||
/// and widens into a stadium pill beyond that; counts above 999 render as
|
||||
/// "999+" so the label stays on one line (#1310).
|
||||
class UnwatchedCountBadge extends StatelessWidget {
|
||||
final int count;
|
||||
final double size;
|
||||
final double fontSize;
|
||||
|
||||
const UnwatchedCountBadge({super.key, required this.count, this.size = 24, this.fontSize = 12});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
height: size,
|
||||
constraints: BoxConstraints(minWidth: size),
|
||||
padding: EdgeInsets.symmetric(horizontal: size * 0.2),
|
||||
decoration: BoxDecoration(
|
||||
color: tokens(context).text,
|
||||
borderRadius: BorderRadius.circular(size / 2),
|
||||
boxShadow: [BoxShadow(color: Colors.black.withValues(alpha: 0.3), blurRadius: 4)],
|
||||
),
|
||||
// Center with widthFactor shrink-wraps the pill to the label (Container
|
||||
// alignment would expand into the Stack's loose width instead).
|
||||
child: Center(
|
||||
widthFactor: 1,
|
||||
child: Text(
|
||||
count > 999 ? '999+' : '$count',
|
||||
maxLines: 1,
|
||||
softWrap: false,
|
||||
style: TextStyle(color: tokens(context).bg, fontSize: fontSize, fontWeight: .bold),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:plezy/theme/mono_theme.dart';
|
||||
import 'package:plezy/widgets/unwatched_count_badge.dart';
|
||||
|
||||
void main() {
|
||||
Future<void> pumpBadge(WidgetTester tester, int count) {
|
||||
return tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: monoTheme(dark: true),
|
||||
home: Scaffold(
|
||||
body: Center(child: UnwatchedCountBadge(count: count)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('single digit keeps circular footprint', (tester) async {
|
||||
await pumpBadge(tester, 5);
|
||||
expect(find.text('5'), findsOneWidget);
|
||||
expect(tester.getSize(find.byType(UnwatchedCountBadge)), const Size(24, 24));
|
||||
});
|
||||
|
||||
testWidgets('counts above 999 cap at 999+ on a single line', (tester) async {
|
||||
await pumpBadge(tester, 1200);
|
||||
expect(find.text('999+'), findsOneWidget);
|
||||
|
||||
final badge = tester.getSize(find.byType(UnwatchedCountBadge));
|
||||
expect(badge.height, 24);
|
||||
expect(badge.width, greaterThan(24));
|
||||
// Wide labels widen the pill instead of wrapping (#1310): the text stays
|
||||
// one line tall and inside the badge.
|
||||
expect(tester.getSize(find.text('999+')).height, lessThanOrEqualTo(24));
|
||||
expect(tester.takeException(), isNull);
|
||||
});
|
||||
|
||||
testWidgets('999 renders uncapped', (tester) async {
|
||||
await pumpBadge(tester, 999);
|
||||
expect(find.text('999'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user