diff --git a/lib/data/local/models.dart b/lib/data/local/models.dart index 9dc3020..5a392ef 100644 --- a/lib/data/local/models.dart +++ b/lib/data/local/models.dart @@ -8,6 +8,16 @@ class HiveBoxes { static const String hijriCache = 'hijri_cache'; } +class SlideshowPatternMode { + SlideshowPatternMode._(); + + static const String alternating = 'alternating'; + static const String burst = 'burst'; + + static bool isValid(String value) => + value == alternating || value == burst; +} + /// AppSettings stored in Hive. @HiveType(typeId: 0) class AppSettings extends HiveObject { @@ -94,6 +104,16 @@ class AppSettings extends HiveObject { @HiveField(34) int announcementSlideDurationSec; + // Slideshow pattern mode: + // - alternating: main-1-main-2-main... + // - burst: main-1-2-main-3-4... (N slides between main phases) + @HiveField(38) + String slideshowPatternMode; + + // Number of slideshow slides shown between main phases when mode=burst. + @HiveField(39) + int slideshowSlidesPerMain; + // Slideshow image paths (local) @HiveField(20) List slideshowImages; @@ -180,6 +200,8 @@ class AppSettings extends HiveObject { this.lastAutoSyncAttemptDate, this.mainCenterSlideDurationSec = 10, this.announcementSlideDurationSec = 7, + this.slideshowPatternMode = SlideshowPatternMode.alternating, + this.slideshowSlidesPerMain = 2, this.slideshowImages = const [], this.textScaleIndex = 1, this.useUnsplashBackground = false, @@ -221,6 +243,8 @@ class AppSettings extends HiveObject { String? lastAutoSyncAttemptDate, int? mainCenterSlideDurationSec, int? announcementSlideDurationSec, + String? slideshowPatternMode, + int? slideshowSlidesPerMain, List? slideshowImages, int? textScaleIndex, bool? useUnsplashBackground, @@ -265,6 +289,10 @@ class AppSettings extends HiveObject { mainCenterSlideDurationSec ?? this.mainCenterSlideDurationSec, announcementSlideDurationSec: announcementSlideDurationSec ?? this.announcementSlideDurationSec, + slideshowPatternMode: + slideshowPatternMode ?? this.slideshowPatternMode, + slideshowSlidesPerMain: + slideshowSlidesPerMain ?? this.slideshowSlidesPerMain, slideshowImages: slideshowImages ?? this.slideshowImages, textScaleIndex: textScaleIndex ?? this.textScaleIndex, useUnsplashBackground: @@ -299,6 +327,7 @@ class AppSettingsAdapter extends TypeAdapter { fields[reader.readByte()] = reader.read(); } final runningTexts = (fields[14] as List?)?.cast() ?? const []; + final storedPatternMode = (fields[38] as String?)?.trim() ?? ''; return AppSettings( masjidName: fields[0] as String? ?? 'Masjid Al-Ikhlas', masjidAddress: fields[1] as String? ?? 'Jl. Kebaikan No. 1', @@ -325,6 +354,11 @@ class AppSettingsAdapter extends TypeAdapter { lastAutoSyncAttemptDate: fields[32] as String?, mainCenterSlideDurationSec: fields[33] as int? ?? 10, announcementSlideDurationSec: fields[34] as int? ?? 7, + slideshowPatternMode: SlideshowPatternMode.isValid(storedPatternMode) + ? storedPatternMode + : SlideshowPatternMode.alternating, + slideshowSlidesPerMain: + ((fields[39] as int?) ?? 2).clamp(1, 20).toInt(), slideshowImages: (fields[20] as List?)?.cast() ?? const [], textScaleIndex: fields[21] as int? ?? 1, useUnsplashBackground: fields[22] as bool? ?? false, @@ -345,7 +379,7 @@ class AppSettingsAdapter extends TypeAdapter { @override void write(BinaryWriter writer, AppSettings obj) { writer - ..writeByte(38) + ..writeByte(40) ..writeByte(0) ..write(obj.masjidName) ..writeByte(1) @@ -394,6 +428,10 @@ class AppSettingsAdapter extends TypeAdapter { ..write(obj.mainCenterSlideDurationSec) ..writeByte(34) ..write(obj.announcementSlideDurationSec) + ..writeByte(38) + ..write(obj.slideshowPatternMode) + ..writeByte(39) + ..write(obj.slideshowSlidesPerMain) ..writeByte(20) ..write(obj.slideshowImages) ..writeByte(21) diff --git a/lib/features/admin/admin_screen.dart b/lib/features/admin/admin_screen.dart index 965b43e..890370d 100644 --- a/lib/features/admin/admin_screen.dart +++ b/lib/features/admin/admin_screen.dart @@ -9,6 +9,7 @@ import 'package:intl/intl.dart'; import 'package:path_provider/path_provider.dart'; import '../../core/sacred_tokens.dart'; +import '../../data/local/models.dart'; import '../../providers.dart'; import '../../data/services/sync_service.dart'; import '../../data/services/myquran_service.dart'; @@ -39,12 +40,14 @@ class _AdminScreenState extends ConsumerState { final _mainDurCtrl = TextEditingController(); final _slideDurCtrl = TextEditingController(); + final _slidesPerMainCtrl = TextEditingController(); final _mainHeroDurCtrl = TextEditingController(); final _textSlideDurCtrl = TextEditingController(); int _selectedTab = 0; bool _isSyncing = false; int _textScaleIndex = 1; + String _slideshowPatternMode = SlideshowPatternMode.alternating; List _slideshowImages = []; bool _useUnsplash = false; final _unsplashKeywordCtrl = TextEditingController(); @@ -162,9 +165,11 @@ class _AdminScreenState extends ConsumerState { _cityCtrl.text = '${settings.cityDisplayName} (${settings.cityIdApi})'; _mainDurCtrl.text = settings.mainScreenDurationSec.toString(); _slideDurCtrl.text = settings.slideDurationSec.toString(); + _slidesPerMainCtrl.text = settings.slideshowSlidesPerMain.toString(); _mainHeroDurCtrl.text = settings.mainCenterSlideDurationSec.toString(); _textSlideDurCtrl.text = settings.announcementSlideDurationSec.toString(); _textScaleIndex = settings.textScaleIndex; + _slideshowPatternMode = settings.slideshowPatternMode; _slideshowImages = List.from(settings.slideshowImages); _useUnsplash = settings.useUnsplashBackground; _unsplashKeywordCtrl.text = settings.unsplashKeyword; @@ -202,6 +207,7 @@ class _AdminScreenState extends ConsumerState { _mainDurCtrl.addListener(_queueTampilanAutoSave); _slideDurCtrl.addListener(_queueTampilanAutoSave); + _slidesPerMainCtrl.addListener(_queueTampilanAutoSave); _mainHeroDurCtrl.addListener(_queuePengumumanAutoSave); _textSlideDurCtrl.addListener(_queuePengumumanAutoSave); _unsplashKeywordCtrl.addListener(_queueTampilanAutoSave); @@ -236,6 +242,7 @@ class _AdminScreenState extends ConsumerState { _cityCtrl.dispose(); _mainDurCtrl.dispose(); _slideDurCtrl.dispose(); + _slidesPerMainCtrl.dispose(); _mainHeroDurCtrl.dispose(); _textSlideDurCtrl.dispose(); _unsplashKeywordCtrl.dispose(); @@ -322,6 +329,13 @@ class _AdminScreenState extends ConsumerState { s.slideshowImages = List.from(_slideshowImages); s.mainScreenDurationSec = int.tryParse(_mainDurCtrl.text.trim()) ?? 15; s.slideDurationSec = int.tryParse(_slideDurCtrl.text.trim()) ?? 10; + s.slideshowPatternMode = SlideshowPatternMode.isValid(_slideshowPatternMode) + ? _slideshowPatternMode + : SlideshowPatternMode.alternating; + s.slideshowSlidesPerMain = + (int.tryParse(_slidesPerMainCtrl.text.trim()) ?? 2) + .clamp(1, 20) + .toInt(); s.useUnsplashBackground = _useUnsplash; s.unsplashKeyword = _unsplashKeywordCtrl.text.trim().isEmpty ? 'mosque' : _unsplashKeywordCtrl.text.trim(); s.unsplashRotationHours = int.tryParse(_unsplashRotationCtrl.text.trim()) ?? 6; @@ -1390,15 +1404,16 @@ class _AdminScreenState extends ConsumerState { int _tampilanRowCount() { var count = 0; - count += 8; + count += 11; + if (_slideshowPatternMode == SlideshowPatternMode.burst) { + count += 1; + } if (_useUnsplash) { count += 2; } if (_brandedBgImage != null && _brandedBgImage!.isNotEmpty) { count += 1; } - count += 1; - count += 1; count += _slideshowImages.length; return count; } @@ -1844,7 +1859,15 @@ class _AdminScreenState extends ConsumerState { children: [ Text(title, style: GoogleFonts.manrope(fontSize: 15 * s, fontWeight: FontWeight.w700, color: SacredColors.onSurface)), SizedBox(height: 4 * s), - Text(desc, style: GoogleFonts.manrope(fontSize: 13 * s, color: SacredColors.onSurfaceVariant)), + Text( + desc, + style: GoogleFonts.manrope( + fontSize: 15 * s, + fontWeight: FontWeight.w500, + color: SacredColors.onSurfaceVariant, + height: 1.35, + ), + ), ], ), ), @@ -1857,6 +1880,11 @@ class _AdminScreenState extends ConsumerState { final textScaleRow = row++; final mainDurationRow = row++; final slideDurationRow = row++; + final slideshowPatternRow = row++; + int? slidesPerMainRow; + if (_slideshowPatternMode == SlideshowPatternMode.burst) { + slidesPerMainRow = row++; + } final scaleLabelRow = row++; final scaleBodyRow = row++; final scaleRunningRow = row++; @@ -1942,8 +1970,49 @@ class _AdminScreenState extends ConsumerState { suffix: 'detik', onMoveLeft: () => _focusNavTab(_selectedTab), onMoveUp: () => _focusTampilanRow(mainDurationRow), - onMoveDown: () => _focusTampilanRow(scaleLabelRow), + onMoveDown: () => _focusTampilanRow(slideshowPatternRow), ), + SizedBox(height: 24 * s), + _buildTvChoiceField( + s: s, + rowIndex: slideshowPatternRow, + label: 'Pola Rotasi Slideshow', + options: const ['Main-1-Main', 'Main-N-Main'], + selectedIndex: + _slideshowPatternMode == SlideshowPatternMode.burst + ? 1 + : 0, + onChanged: (index) { + setState(() { + _slideshowPatternMode = index == 1 + ? SlideshowPatternMode.burst + : SlideshowPatternMode.alternating; + if (_slideshowPatternMode == SlideshowPatternMode.burst && + _slidesPerMainCtrl.text.trim().isEmpty) { + _slidesPerMainCtrl.text = '2'; + } + }); + _queueTampilanAutoSave( + message: 'Pola slideshow otomatis tersimpan', + ); + }, + ), + if (_slideshowPatternMode == SlideshowPatternMode.burst) ...[ + SizedBox(height: 16 * s), + _buildTvIntStepperField( + s: s, + label: 'Jumlah Slide antar Main', + focusNode: _tampilanFocusNode(slidesPerMainRow!), + controller: _slidesPerMainCtrl, + fallback: 2, + min: 1, + max: 20, + suffix: 'slide', + onMoveLeft: () => _focusNavTab(_selectedTab), + onMoveUp: () => _focusTampilanRow(slideshowPatternRow), + onMoveDown: () => _focusTampilanRow(scaleLabelRow), + ), + ], SizedBox(height: 40 * s), _sectionLabel('Ukuran Teks Per Kelompok', s), SizedBox(height: 8 * s), @@ -1962,7 +2031,9 @@ class _AdminScreenState extends ConsumerState { _queueTampilanAutoSave(); }, onMoveLeft: () => _focusNavTab(_selectedTab), - onMoveUp: () => _focusTampilanRow(slideDurationRow), + onMoveUp: () => _focusTampilanRow( + slidesPerMainRow ?? slideshowPatternRow, + ), onMoveDown: () => _focusTampilanRow(scaleBodyRow), ), SizedBox(height: 16 * s), @@ -3375,7 +3446,8 @@ class _AdminScreenState extends ConsumerState { return Text( label, style: GoogleFonts.plusJakartaSans( - fontSize: 20 * s, + // Match sidebar menu text size for stronger hierarchy consistency. + fontSize: 18 * s, fontWeight: FontWeight.w700, color: SacredColors.primary, ), @@ -4249,8 +4321,22 @@ class _AdminScreenState extends ConsumerState { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(label, style: GoogleFonts.manrope(fontSize: 12 * s, color: SacredColors.onSurfaceVariant)), - Text(value, style: GoogleFonts.plusJakartaSans(fontSize: 18 * s, fontWeight: FontWeight.w600, color: SacredColors.onSurface)), + Text( + label, + style: GoogleFonts.manrope( + fontSize: 15 * s, + fontWeight: FontWeight.w600, + color: SacredColors.onSurfaceVariant, + ), + ), + Text( + value, + style: GoogleFonts.plusJakartaSans( + fontSize: 20 * s, + fontWeight: FontWeight.w700, + color: SacredColors.onSurface, + ), + ), ], ), ], @@ -4334,7 +4420,12 @@ class _AdminScreenState extends ConsumerState { SizedBox(height: 16 * s), Text( 'Gunakan tombol di bawah ini untuk melihat pratinjau bagaimana aplikasi bereaksi terhadap berbagai waktu dan status tanpa harus menunggu waktu sebenarnya.\nFitur ini bekerja dengan menggeser waktu aplikasi (Time Travel).', - style: GoogleFonts.manrope(fontSize: 18 * s, color: SacredColors.onSurfaceVariant), + style: GoogleFonts.manrope( + fontSize: 20 * s, + fontWeight: FontWeight.w500, + height: 1.35, + color: SacredColors.onSurfaceVariant, + ), ), SizedBox(height: 48 * s), Container( @@ -4524,7 +4615,9 @@ class _AdminScreenState extends ConsumerState { Text( 'Informasi aplikasi, kontak bantuan, dan pemeriksaan versi terbaru.', style: GoogleFonts.manrope( - fontSize: 18 * s, + fontSize: 20 * s, + fontWeight: FontWeight.w500, + height: 1.35, color: SacredColors.onSurfaceVariant, ), ), @@ -4834,7 +4927,9 @@ class _AdminScreenState extends ConsumerState { Text( desc, style: GoogleFonts.manrope( - fontSize: 14 * s, + fontSize: 16 * s, + fontWeight: FontWeight.w500, + height: 1.35, color: SacredColors.onSurfaceVariant, ), ), @@ -5175,10 +5270,11 @@ class _TvAdjustTileState extends State<_TvAdjustTile> { Expanded( child: Text( widget.label, - style: GoogleFonts.manrope( - fontSize: 16 * s, - fontWeight: FontWeight.w600, - color: SacredColors.onSurface, + style: GoogleFonts.plusJakartaSans( + fontSize: 18 * s, + fontWeight: FontWeight.w700, + color: SacredColors.onSurfaceVariant, + letterSpacing: 0.4 * s, ), ), ), @@ -5327,8 +5423,9 @@ class _TvAdjustTileState extends State<_TvAdjustTile> { ? 'Mode ubah aktif. Gunakan ← → lalu tekan OK untuk selesai.' : widget.helperText, style: GoogleFonts.manrope( - fontSize: 11 * s, - color: SacredColors.onSurfaceVariant.withValues(alpha: 0.75), + fontSize: 15 * s, + fontWeight: FontWeight.w500, + color: SacredColors.onSurfaceVariant.withValues(alpha: 0.88), ), ), ], @@ -5514,10 +5611,11 @@ class _TvEditableTextTileState extends State<_TvEditableTextTile> { children: [ Text( widget.label, - style: GoogleFonts.manrope( - fontSize: 16 * s, - fontWeight: FontWeight.w600, + style: GoogleFonts.plusJakartaSans( + fontSize: 18 * s, + fontWeight: FontWeight.w700, color: SacredColors.onSurfaceVariant, + letterSpacing: 0.4 * s, ), ), SizedBox(height: 12 * s), @@ -5565,8 +5663,9 @@ class _TvEditableTextTileState extends State<_TvEditableTextTile> { ? 'Mode edit aktif. Tekan ESC untuk selesai.' : 'Tekan OK untuk mulai edit.', style: GoogleFonts.manrope( - fontSize: 11 * s, - color: SacredColors.onSurfaceVariant.withValues(alpha: 0.75), + fontSize: 15 * s, + fontWeight: FontWeight.w500, + color: SacredColors.onSurfaceVariant.withValues(alpha: 0.88), ), ), ], diff --git a/lib/features/home/home_view.dart b/lib/features/home/home_view.dart index 64f3be3..b18ac9e 100644 --- a/lib/features/home/home_view.dart +++ b/lib/features/home/home_view.dart @@ -49,6 +49,10 @@ class _HomeViewState extends ConsumerState { bool _isAutoRefreshRunning = false; int _touchUnlockTapCount = 0; + LogicalKeyboardKey _normalizedComboKey(LogicalKeyboardKey key) { + return key == LogicalKeyboardKey.enter ? LogicalKeyboardKey.select : key; + } + @override void initState() { super.initState(); @@ -162,6 +166,13 @@ class _HomeViewState extends ConsumerState { _recentKeys.removeAt(0); } + final manualAction = _matchManualRotateSequence(); + if (manualAction != null) { + _dispatchManualBackgroundRotate(manualAction); + _resetCombo(); + return KeyEventResult.handled; + } + if (_matchesUnlockSequence()) { _resetCombo(); WidgetsBinding.instance.addPostFrameCallback((_) async { @@ -192,14 +203,50 @@ class _HomeViewState extends ConsumerState { if (_recentKeys.length != _adminUnlockSequence.length) return false; for (var i = 0; i < _adminUnlockSequence.length; i++) { - final current = _recentKeys[i] == LogicalKeyboardKey.enter - ? LogicalKeyboardKey.select - : _recentKeys[i]; + final current = _normalizedComboKey(_recentKeys[i]); if (current != _adminUnlockSequence[i]) return false; } return true; } + BackgroundRotateAction? _matchManualRotateSequence() { + if (_recentKeys.length < 3) return null; + final tail = _recentKeys.sublist(_recentKeys.length - 3).map(_normalizedComboKey).toList(); + if (tail[0] == LogicalKeyboardKey.arrowRight && + tail[1] == LogicalKeyboardKey.arrowRight && + tail[2] == LogicalKeyboardKey.select) { + return BackgroundRotateAction.next; + } + if (tail[0] == LogicalKeyboardKey.arrowLeft && + tail[1] == LogicalKeyboardKey.arrowLeft && + tail[2] == LogicalKeyboardKey.select) { + return BackgroundRotateAction.previous; + } + if (tail[0] == LogicalKeyboardKey.arrowDown && + tail[1] == LogicalKeyboardKey.arrowDown && + tail[2] == LogicalKeyboardKey.select) { + return BackgroundRotateAction.random; + } + return null; + } + + void _dispatchManualBackgroundRotate(BackgroundRotateAction action) { + final screenData = ref.read(screenStateProvider); + final isMainScreen = ref.read(isMainScreenProvider); + if (!isMainScreen || + !(screenData.state == ScreenState.normal || + screenData.state == ScreenState.menujuAdzan)) { + return; + } + + final notifier = ref.read(backgroundRotateCommandProvider.notifier); + final current = notifier.state; + notifier.state = BackgroundRotateCommand( + nonce: current.nonce + 1, + action: action, + ); + } + void _resetCombo() { _comboResetTimer?.cancel(); _recentKeys.clear(); @@ -262,6 +309,7 @@ class _HomeViewState extends ConsumerState { final screenData = ref.watch(screenStateProvider); final isMainScreen = ref.watch(isMainScreenProvider); + final rotationIndex = ref.watch(rotationIndexProvider); // Determine which screen to display Widget screen; @@ -273,7 +321,7 @@ class _HomeViewState extends ConsumerState { } else { screen = isMainScreen ? const MainScreen(key: ValueKey('main')) - : const SlideshowScreen(key: ValueKey('slideshow')); + : SlideshowScreen(key: ValueKey('slideshow-$rotationIndex')); } break; case ScreenState.kembaliNormal: diff --git a/lib/features/home/unsplash_background.dart b/lib/features/home/unsplash_background.dart index 3e9034e..9c24b69 100644 --- a/lib/features/home/unsplash_background.dart +++ b/lib/features/home/unsplash_background.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import '../../data/local/models.dart'; import '../../data/services/unsplash_cache_service.dart'; import '../../providers.dart'; @@ -25,6 +26,7 @@ class _UnsplashBackgroundState extends ConsumerState { String? _lastKeyword; int? _lastRotationHours; bool? _lastUseUnsplash; + int _lastHandledRotateNonce = 0; @override void initState() { @@ -106,6 +108,50 @@ class _UnsplashBackgroundState extends ConsumerState { } } + void _showNextImage() { + if (_imagePaths.length <= 1 || !mounted) return; + setState(() { + _currentIndex = (_currentIndex + 1) % _imagePaths.length; + }); + } + + void _showPreviousImage() { + if (_imagePaths.length <= 1 || !mounted) return; + setState(() { + _currentIndex = (_currentIndex - 1 + _imagePaths.length) % _imagePaths.length; + }); + } + + Future _handleManualRotate( + BackgroundRotateAction action, + AppSettings settings, + ) async { + if (!settings.useUnsplashBackground) return; + + if (_imagePaths.isEmpty) { + final cachedPaths = await UnsplashCacheService.instance.getCachedImagePaths( + settings.unsplashKeyword, + ); + if (!mounted) return; + if (cachedPaths.isNotEmpty) { + _applyImagePaths(cachedPaths); + } + } + + if (_imagePaths.isEmpty) return; + switch (action) { + case BackgroundRotateAction.next: + _showNextImage(); + break; + case BackgroundRotateAction.previous: + _showPreviousImage(); + break; + case BackgroundRotateAction.random: + _nextRandomImage(); + break; + } + } + void _startTimer(int hours) { _rotationTimer?.cancel(); if (hours <= 0) return; @@ -125,6 +171,7 @@ class _UnsplashBackgroundState extends ConsumerState { @override Widget build(BuildContext context) { final settings = ref.watch(settingsProvider); + final rotateCommand = ref.watch(backgroundRotateCommandProvider); // Watch for config changes if (settings.useUnsplashBackground != _lastUseUnsplash) { @@ -146,6 +193,14 @@ class _UnsplashBackgroundState extends ConsumerState { _startTimer(settings.unsplashRotationHours); } + if (rotateCommand.nonce != _lastHandledRotateNonce) { + _lastHandledRotateNonce = rotateCommand.nonce; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + _handleManualRotate(rotateCommand.action, settings); + }); + } + if (!settings.useUnsplashBackground || _imagePaths.isEmpty) { return const SizedBox.shrink(); // Fallback to flat background handled underneath } diff --git a/lib/providers.dart b/lib/providers.dart index 10abb40..6e879e7 100644 --- a/lib/providers.dart +++ b/lib/providers.dart @@ -13,6 +13,27 @@ import 'data/services/sync_service.dart'; // ────────────────────────────────────────────── final mockTimeOffsetProvider = StateProvider((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( + (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 { final Ref _ref; Timer? _timer; @@ -301,7 +338,11 @@ class RotationNotifier extends StateNotifier { 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((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); }); diff --git a/pubspec.yaml b/pubspec.yaml index 04f691b..ff4adb5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: jamshalat_masjid_screen description: Smart Digital Prayer Clock for Android TV Box publish_to: 'none' -version: 1.0.13+14 +version: 1.0.14+15 environment: sdk: '>=3.0.0 <4.0.0'