feat(tv-ui): add slideshow pattern mode and improve admin readability

This commit is contained in:
dwindown
2026-04-06 09:23:42 +07:00
parent 185c55a143
commit 414450125d
6 changed files with 312 additions and 34 deletions

View File

@@ -13,6 +13,27 @@ import 'data/services/sync_service.dart';
// ──────────────────────────────────────────────
final mockTimeOffsetProvider = StateProvider<Duration>((ref) => Duration.zero);
enum BackgroundRotateAction { next, previous, random }
class BackgroundRotateCommand {
final int nonce;
final BackgroundRotateAction action;
const BackgroundRotateCommand({
required this.nonce,
required this.action,
});
const BackgroundRotateCommand.initial()
: nonce = 0,
action = BackgroundRotateAction.random;
}
final backgroundRotateCommandProvider =
StateProvider<BackgroundRotateCommand>(
(ref) => const BackgroundRotateCommand.initial(),
);
// ──────────────────────────────────────────────
// CLOCK PROVIDER — fires every second
// ──────────────────────────────────────────────
@@ -263,6 +284,22 @@ final rotationIndexProvider =
return RotationNotifier(ref);
});
bool _isMainPhaseForSettings(
int phaseIndex,
AppSettings settings, {
required bool hasContent,
}) {
if (!hasContent) return true;
if (settings.slideshowPatternMode == SlideshowPatternMode.burst) {
final slidesBetweenMain = settings.slideshowSlidesPerMain.clamp(1, 20);
final cycleLength = slidesBetweenMain + 1; // main + N slides
return phaseIndex % cycleLength == 0;
}
// Default alternating pattern.
return phaseIndex % 2 == 0;
}
class RotationNotifier extends StateNotifier<int> {
final Ref _ref;
Timer? _timer;
@@ -301,7 +338,11 @@ class RotationNotifier extends StateNotifier<int> {
return;
}
final isMainScreen = state % 2 == 0;
final isMainScreen = _isMainPhaseForSettings(
state,
settings,
hasContent: hasContent,
);
final duration = isMainScreen
? _resolveMainPhaseDuration(settings)
: settings.slideDurationSec.clamp(1, 600);
@@ -346,8 +387,5 @@ final isMainScreenProvider = Provider<bool>((ref) {
final validSlides =
settings.slideshowImages.where((i) => i.trim().isNotEmpty).toList();
final hasContent = validSlides.isNotEmpty;
if (!hasContent) return true; // always stay on main screen
// Even = main, Odd = slideshow
return index % 2 == 0;
return _isMainPhaseForSettings(index, settings, hasContent: hasContent);
});