feat(tv-ui): split pengumuman tab and refine main text-slide behavior
This commit is contained in:
@@ -59,10 +59,13 @@ class SettingsNotifier extends StateNotifier<AppSettings> {
|
||||
final textScaleProvider = Provider<double>((ref) {
|
||||
final index = ref.watch(settingsProvider.select((s) => s.textScaleIndex));
|
||||
switch (index) {
|
||||
case 0: return 0.85; // Small
|
||||
case 2: return 1.15; // Large
|
||||
case 0:
|
||||
return 0.85; // Small
|
||||
case 2:
|
||||
return 1.15; // Large
|
||||
case 1:
|
||||
default: return 1.0; // Medium
|
||||
default:
|
||||
return 1.0; // Medium
|
||||
}
|
||||
});
|
||||
|
||||
@@ -103,11 +106,11 @@ final hijriDateProvider = FutureProvider<String>((ref) async {
|
||||
/// Computed state that tells the UI which screen to display.
|
||||
class ScreenStateData {
|
||||
final ScreenState state;
|
||||
final PrayerName? activePrayer; // Current or next prayer
|
||||
final PrayerName? activePrayer; // Current or next prayer
|
||||
final PrayerName? nextPrayer;
|
||||
final Duration? timeUntilNext; // Countdown to next prayer time
|
||||
final Duration? iqomahRemaining; // Countdown during iqomah state
|
||||
final Duration? blankRemaining; // Countdown during shalat/blank state
|
||||
final Duration? timeUntilNext; // Countdown to next prayer time
|
||||
final Duration? iqomahRemaining; // Countdown during iqomah state
|
||||
final Duration? blankRemaining; // Countdown during shalat/blank state
|
||||
final bool isFriday;
|
||||
final DateTime now;
|
||||
|
||||
@@ -151,12 +154,18 @@ final screenStateProvider = Provider<ScreenStateData>((ref) {
|
||||
|
||||
int iqomahMinutes(PrayerName p) {
|
||||
switch (p) {
|
||||
case PrayerName.subuh: return settings.iqomahSubuh;
|
||||
case PrayerName.dzuhur: return settings.iqomahDzuhur;
|
||||
case PrayerName.ashar: return settings.iqomahAshar;
|
||||
case PrayerName.maghrib: return settings.iqomahMaghrib;
|
||||
case PrayerName.isya: return settings.iqomahIsya;
|
||||
default: return 10;
|
||||
case PrayerName.subuh:
|
||||
return settings.iqomahSubuh;
|
||||
case PrayerName.dzuhur:
|
||||
return settings.iqomahDzuhur;
|
||||
case PrayerName.ashar:
|
||||
return settings.iqomahAshar;
|
||||
case PrayerName.maghrib:
|
||||
return settings.iqomahMaghrib;
|
||||
case PrayerName.isya:
|
||||
return settings.iqomahIsya;
|
||||
default:
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,8 +181,7 @@ final screenStateProvider = Provider<ScreenStateData>((ref) {
|
||||
adzanTime.subtract(Duration(minutes: settings.preAdzanLead));
|
||||
final iqomahDuration = Duration(minutes: iqomahMinutes(prayer.key));
|
||||
final iqomahEnd = adzanTime.add(iqomahDuration);
|
||||
final blankEnd =
|
||||
iqomahEnd.add(Duration(minutes: blankMinutes()));
|
||||
final blankEnd = iqomahEnd.add(Duration(minutes: blankMinutes()));
|
||||
|
||||
// STATE: SHALAT (Black Screen)
|
||||
if (clock.isAfter(iqomahEnd) && clock.isBefore(blankEnd)) {
|
||||
@@ -246,6 +254,9 @@ final screenStateProvider = Provider<ScreenStateData>((ref) {
|
||||
// ROTATION PROVIDER (for Normal state slideshow)
|
||||
// ──────────────────────────────────────────────
|
||||
|
||||
/// Elapsed seconds in the active rotation phase (main/slideshow).
|
||||
final rotationElapsedProvider = StateProvider<int>((ref) => 0);
|
||||
|
||||
/// Controls the rotation between main screen and slideshow views.
|
||||
final rotationIndexProvider =
|
||||
StateNotifierProvider<RotationNotifier, int>((ref) {
|
||||
@@ -264,36 +275,59 @@ class RotationNotifier extends StateNotifier<int> {
|
||||
void _startRotation() {
|
||||
_timer?.cancel();
|
||||
_elapsed = 0;
|
||||
_ref.read(rotationElapsedProvider.notifier).state = 0;
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (_) {
|
||||
final screenState = _ref.read(screenStateProvider);
|
||||
if (screenState.state != ScreenState.normal) {
|
||||
// Don't rotate during special states, reset elapsed
|
||||
_elapsed = 0;
|
||||
_ref.read(rotationElapsedProvider.notifier).state = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_elapsed++;
|
||||
final settings = _ref.read(settingsProvider);
|
||||
final validSlides = settings.slideshowImages.where((i) => i.trim().isNotEmpty).toList();
|
||||
final validSlides =
|
||||
settings.slideshowImages.where((i) => i.trim().isNotEmpty).toList();
|
||||
final hasContent = validSlides.isNotEmpty;
|
||||
|
||||
_elapsed++;
|
||||
if (!hasContent) {
|
||||
_elapsed = 0;
|
||||
final duration = _resolveMainPhaseDuration(settings);
|
||||
if (_elapsed >= duration) {
|
||||
_elapsed = 0;
|
||||
}
|
||||
_ref.read(rotationElapsedProvider.notifier).state = _elapsed;
|
||||
if (state != 0) state = 0; // force main screen state
|
||||
return;
|
||||
}
|
||||
|
||||
final isMainScreen = state % 2 == 0;
|
||||
final duration = isMainScreen
|
||||
? settings.mainScreenDurationSec
|
||||
: settings.slideDurationSec;
|
||||
? _resolveMainPhaseDuration(settings)
|
||||
: settings.slideDurationSec.clamp(1, 600);
|
||||
|
||||
if (_elapsed >= duration) {
|
||||
_elapsed = 0;
|
||||
state = state + 1;
|
||||
}
|
||||
_ref.read(rotationElapsedProvider.notifier).state = _elapsed;
|
||||
});
|
||||
}
|
||||
|
||||
int _resolveMainPhaseDuration(AppSettings settings) {
|
||||
final centerSlides = settings.textSlides
|
||||
.map((text) => text.trim())
|
||||
.where((text) => text.isNotEmpty)
|
||||
.toList();
|
||||
if (centerSlides.isEmpty) {
|
||||
return settings.mainScreenDurationSec.clamp(1, 600);
|
||||
}
|
||||
|
||||
final heroDuration = settings.mainCenterSlideDurationSec.clamp(1, 600);
|
||||
final perAnnouncement = settings.announcementSlideDurationSec.clamp(1, 600);
|
||||
return heroDuration + (perAnnouncement * centerSlides.length);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
@@ -306,11 +340,14 @@ class RotationNotifier extends StateNotifier<int> {
|
||||
/// Unsplash background is disabled — no point rotating to an empty slide.
|
||||
final isMainScreenProvider = Provider<bool>((ref) {
|
||||
final settings = ref.watch(settingsProvider);
|
||||
final validSlides = settings.slideshowImages.where((i) => i.trim().isNotEmpty).toList();
|
||||
// Keep rotation notifier alive even when slideshow media is empty,
|
||||
// because main-screen text slides depend on rotation elapsed time.
|
||||
final index = ref.watch(rotationIndexProvider);
|
||||
final validSlides =
|
||||
settings.slideshowImages.where((i) => i.trim().isNotEmpty).toList();
|
||||
final hasContent = validSlides.isNotEmpty;
|
||||
if (!hasContent) return true; // always stay on main screen
|
||||
|
||||
final index = ref.watch(rotationIndexProvider);
|
||||
// Even = main, Odd = slideshow
|
||||
return index % 2 == 0;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user