582 lines
17 KiB
Dart
582 lines
17 KiB
Dart
import 'package:hive_flutter/hive_flutter.dart';
|
|
|
|
/// Hive type adapter IDs and box names.
|
|
class HiveBoxes {
|
|
HiveBoxes._();
|
|
static const String settings = 'app_settings';
|
|
static const String prayerSchedule = 'prayer_schedule';
|
|
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 {
|
|
@HiveField(0)
|
|
String masjidName;
|
|
|
|
@HiveField(1)
|
|
String masjidAddress;
|
|
|
|
@HiveField(2)
|
|
String cityIdApi; // myQuran city hash ID
|
|
|
|
@HiveField(3)
|
|
String cityDisplayName;
|
|
|
|
@HiveField(4)
|
|
bool showImsak;
|
|
|
|
@HiveField(5)
|
|
bool showTerbit;
|
|
|
|
// Iqomah durations in minutes
|
|
@HiveField(6)
|
|
int iqomahSubuh;
|
|
|
|
@HiveField(7)
|
|
int iqomahDzuhur;
|
|
|
|
@HiveField(8)
|
|
int iqomahAshar;
|
|
|
|
@HiveField(9)
|
|
int iqomahMaghrib;
|
|
|
|
@HiveField(10)
|
|
int iqomahIsya;
|
|
|
|
// Pre-Adzan lead time (minutes before adzan to lock main screen)
|
|
@HiveField(11)
|
|
int preAdzanLead;
|
|
|
|
// Blank screen durations
|
|
@HiveField(12)
|
|
int blankScreenNormal; // minutes
|
|
|
|
@HiveField(13)
|
|
int blankScreenJumat; // minutes
|
|
|
|
// Running text items
|
|
@HiveField(14)
|
|
List<String> runningTexts;
|
|
|
|
// Center text-slide items (separate from running ticker at bottom).
|
|
@HiveField(35)
|
|
List<String> textSlides;
|
|
|
|
// Friday officers
|
|
@HiveField(15)
|
|
String khatibName;
|
|
|
|
@HiveField(16)
|
|
String imamName;
|
|
|
|
// Rotation settings
|
|
@HiveField(17)
|
|
int mainScreenDurationSec;
|
|
|
|
@HiveField(18)
|
|
int slideDurationSec;
|
|
|
|
// Last sync timestamp
|
|
@HiveField(19)
|
|
String? lastSyncDate;
|
|
|
|
// Last automatic sync attempt timestamp (ISO8601).
|
|
@HiveField(32)
|
|
String? lastAutoSyncAttemptDate;
|
|
|
|
// Center hero block duration on main screen (seconds).
|
|
@HiveField(33)
|
|
int mainCenterSlideDurationSec;
|
|
|
|
// Per-text announcement slide duration on main screen (seconds).
|
|
@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<String> slideshowImages;
|
|
|
|
// Text scaling (0=Small, 1=Medium, 2=Large)
|
|
@HiveField(21)
|
|
int textScaleIndex;
|
|
|
|
// Unsplash Background configs
|
|
@HiveField(22)
|
|
bool useUnsplashBackground;
|
|
|
|
@HiveField(23)
|
|
String unsplashKeyword;
|
|
|
|
@HiveField(24)
|
|
int unsplashRotationHours;
|
|
|
|
// Branded background image (local file path set by admin)
|
|
@HiveField(25)
|
|
String? brandedBgImage;
|
|
|
|
// Per-item duration for running texts (seconds each)
|
|
@HiveField(26)
|
|
List<int> runningTextDurations;
|
|
|
|
// Running text animation type: 'marquee' or 'fade'
|
|
@HiveField(27)
|
|
String marqueeAnimType;
|
|
|
|
// Granular text group scales (independent of textScaleIndex)
|
|
// Group: Prayer card label (e.g. "SUBUH", "DZUHUR")
|
|
@HiveField(28)
|
|
double scaleCardLabel;
|
|
|
|
// Group: Prayer card body (time + iqomah text)
|
|
@HiveField(29)
|
|
double scaleCardBody;
|
|
|
|
// Group: Running text ticker at bottom
|
|
@HiveField(30)
|
|
double scaleRunningText;
|
|
|
|
// Manual day adjustment applied to displayed Hijri date.
|
|
@HiveField(31)
|
|
int hijriOffsetDays;
|
|
|
|
// Group: Top header (identity + date on upper area)
|
|
@HiveField(36)
|
|
double scaleTopHeader;
|
|
|
|
// Group: Center text slides (pengumuman in main area)
|
|
@HiveField(37)
|
|
double scaleTextSlideCenter;
|
|
|
|
AppSettings({
|
|
this.masjidName = 'Masjid Al-Ikhlas',
|
|
this.masjidAddress = 'Jl. Kebaikan No. 1',
|
|
this.cityIdApi = '1218', // Default: Yogyakarta
|
|
this.cityDisplayName = 'Kota Yogyakarta',
|
|
this.showImsak = true,
|
|
this.showTerbit = true,
|
|
this.iqomahSubuh = 15,
|
|
this.iqomahDzuhur = 10,
|
|
this.iqomahAshar = 10,
|
|
this.iqomahMaghrib = 7,
|
|
this.iqomahIsya = 10,
|
|
this.preAdzanLead = 10,
|
|
this.blankScreenNormal = 15,
|
|
this.blankScreenJumat = 45,
|
|
this.runningTexts = const [
|
|
'Mohon luruskan dan rapatkan shaf',
|
|
'Kajian rutin setiap Ahad pagi',
|
|
],
|
|
this.textSlides = const [
|
|
'Mohon luruskan dan rapatkan shaf',
|
|
'Kajian rutin setiap Ahad pagi',
|
|
],
|
|
this.khatibName = 'Ust. Fulan, S.Ag',
|
|
this.imamName = 'Ust. Alan, Lc',
|
|
this.mainScreenDurationSec = 15,
|
|
this.slideDurationSec = 10,
|
|
this.lastSyncDate,
|
|
this.lastAutoSyncAttemptDate,
|
|
this.mainCenterSlideDurationSec = 10,
|
|
this.announcementSlideDurationSec = 7,
|
|
this.slideshowPatternMode = SlideshowPatternMode.alternating,
|
|
this.slideshowSlidesPerMain = 2,
|
|
this.slideshowImages = const [],
|
|
this.textScaleIndex = 1,
|
|
this.useUnsplashBackground = false,
|
|
this.unsplashKeyword = 'mosque',
|
|
this.unsplashRotationHours = 6,
|
|
this.brandedBgImage,
|
|
this.runningTextDurations = const [],
|
|
this.marqueeAnimType = 'marquee',
|
|
this.scaleCardLabel = 1.0,
|
|
this.scaleCardBody = 1.0,
|
|
this.scaleRunningText = 1.0,
|
|
this.hijriOffsetDays = 0,
|
|
this.scaleTopHeader = 1.0,
|
|
this.scaleTextSlideCenter = 1.0,
|
|
});
|
|
|
|
AppSettings copyWith({
|
|
String? masjidName,
|
|
String? masjidAddress,
|
|
String? cityIdApi,
|
|
String? cityDisplayName,
|
|
bool? showImsak,
|
|
bool? showTerbit,
|
|
int? iqomahSubuh,
|
|
int? iqomahDzuhur,
|
|
int? iqomahAshar,
|
|
int? iqomahMaghrib,
|
|
int? iqomahIsya,
|
|
int? preAdzanLead,
|
|
int? blankScreenNormal,
|
|
int? blankScreenJumat,
|
|
List<String>? runningTexts,
|
|
List<String>? textSlides,
|
|
String? khatibName,
|
|
String? imamName,
|
|
int? mainScreenDurationSec,
|
|
int? slideDurationSec,
|
|
String? lastSyncDate,
|
|
String? lastAutoSyncAttemptDate,
|
|
int? mainCenterSlideDurationSec,
|
|
int? announcementSlideDurationSec,
|
|
String? slideshowPatternMode,
|
|
int? slideshowSlidesPerMain,
|
|
List<String>? slideshowImages,
|
|
int? textScaleIndex,
|
|
bool? useUnsplashBackground,
|
|
String? unsplashKeyword,
|
|
int? unsplashRotationHours,
|
|
String? brandedBgImage,
|
|
List<int>? runningTextDurations,
|
|
String? marqueeAnimType,
|
|
double? scaleCardLabel,
|
|
double? scaleCardBody,
|
|
double? scaleRunningText,
|
|
int? hijriOffsetDays,
|
|
double? scaleTopHeader,
|
|
double? scaleTextSlideCenter,
|
|
}) {
|
|
return AppSettings(
|
|
masjidName: masjidName ?? this.masjidName,
|
|
masjidAddress: masjidAddress ?? this.masjidAddress,
|
|
cityIdApi: cityIdApi ?? this.cityIdApi,
|
|
cityDisplayName: cityDisplayName ?? this.cityDisplayName,
|
|
showImsak: showImsak ?? this.showImsak,
|
|
showTerbit: showTerbit ?? this.showTerbit,
|
|
iqomahSubuh: iqomahSubuh ?? this.iqomahSubuh,
|
|
iqomahDzuhur: iqomahDzuhur ?? this.iqomahDzuhur,
|
|
iqomahAshar: iqomahAshar ?? this.iqomahAshar,
|
|
iqomahMaghrib: iqomahMaghrib ?? this.iqomahMaghrib,
|
|
iqomahIsya: iqomahIsya ?? this.iqomahIsya,
|
|
preAdzanLead: preAdzanLead ?? this.preAdzanLead,
|
|
blankScreenNormal: blankScreenNormal ?? this.blankScreenNormal,
|
|
blankScreenJumat: blankScreenJumat ?? this.blankScreenJumat,
|
|
runningTexts: runningTexts ?? this.runningTexts,
|
|
textSlides: textSlides ?? this.textSlides,
|
|
khatibName: khatibName ?? this.khatibName,
|
|
imamName: imamName ?? this.imamName,
|
|
mainScreenDurationSec:
|
|
mainScreenDurationSec ?? this.mainScreenDurationSec,
|
|
slideDurationSec: slideDurationSec ?? this.slideDurationSec,
|
|
lastSyncDate: lastSyncDate ?? this.lastSyncDate,
|
|
lastAutoSyncAttemptDate:
|
|
lastAutoSyncAttemptDate ?? this.lastAutoSyncAttemptDate,
|
|
mainCenterSlideDurationSec:
|
|
mainCenterSlideDurationSec ?? this.mainCenterSlideDurationSec,
|
|
announcementSlideDurationSec:
|
|
announcementSlideDurationSec ?? this.announcementSlideDurationSec,
|
|
slideshowPatternMode:
|
|
slideshowPatternMode ?? this.slideshowPatternMode,
|
|
slideshowSlidesPerMain:
|
|
slideshowSlidesPerMain ?? this.slideshowSlidesPerMain,
|
|
slideshowImages: slideshowImages ?? this.slideshowImages,
|
|
textScaleIndex: textScaleIndex ?? this.textScaleIndex,
|
|
useUnsplashBackground:
|
|
useUnsplashBackground ?? this.useUnsplashBackground,
|
|
unsplashKeyword: unsplashKeyword ?? this.unsplashKeyword,
|
|
unsplashRotationHours:
|
|
unsplashRotationHours ?? this.unsplashRotationHours,
|
|
brandedBgImage: brandedBgImage ?? this.brandedBgImage,
|
|
runningTextDurations: runningTextDurations ?? this.runningTextDurations,
|
|
marqueeAnimType: marqueeAnimType ?? this.marqueeAnimType,
|
|
scaleCardLabel: scaleCardLabel ?? this.scaleCardLabel,
|
|
scaleCardBody: scaleCardBody ?? this.scaleCardBody,
|
|
scaleRunningText: scaleRunningText ?? this.scaleRunningText,
|
|
hijriOffsetDays: hijriOffsetDays ?? this.hijriOffsetDays,
|
|
scaleTopHeader: scaleTopHeader ?? this.scaleTopHeader,
|
|
scaleTextSlideCenter:
|
|
scaleTextSlideCenter ?? this.scaleTextSlideCenter,
|
|
);
|
|
}
|
|
}
|
|
|
|
/// Adapter for AppSettings — hand-written to avoid code generation.
|
|
class AppSettingsAdapter extends TypeAdapter<AppSettings> {
|
|
@override
|
|
final int typeId = 0;
|
|
|
|
@override
|
|
AppSettings read(BinaryReader reader) {
|
|
final numOfFields = reader.readByte();
|
|
final fields = <int, dynamic>{};
|
|
for (int i = 0; i < numOfFields; i++) {
|
|
fields[reader.readByte()] = reader.read();
|
|
}
|
|
final runningTexts = (fields[14] as List?)?.cast<String>() ?? 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',
|
|
cityIdApi: fields[2] as String? ?? '1218',
|
|
cityDisplayName: fields[3] as String? ?? 'Kota Yogyakarta',
|
|
showImsak: fields[4] as bool? ?? true,
|
|
showTerbit: fields[5] as bool? ?? true,
|
|
iqomahSubuh: fields[6] as int? ?? 15,
|
|
iqomahDzuhur: fields[7] as int? ?? 10,
|
|
iqomahAshar: fields[8] as int? ?? 10,
|
|
iqomahMaghrib: fields[9] as int? ?? 7,
|
|
iqomahIsya: fields[10] as int? ?? 10,
|
|
preAdzanLead: fields[11] as int? ?? 10,
|
|
blankScreenNormal: fields[12] as int? ?? 15,
|
|
blankScreenJumat: fields[13] as int? ?? 45,
|
|
runningTexts: runningTexts,
|
|
textSlides:
|
|
(fields[35] as List?)?.cast<String>() ?? List<String>.from(runningTexts),
|
|
khatibName: fields[15] as String? ?? '',
|
|
imamName: fields[16] as String? ?? '',
|
|
mainScreenDurationSec: fields[17] as int? ?? 15,
|
|
slideDurationSec: fields[18] as int? ?? 10,
|
|
lastSyncDate: fields[19] as String?,
|
|
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<String>() ?? const [],
|
|
textScaleIndex: fields[21] as int? ?? 1,
|
|
useUnsplashBackground: fields[22] as bool? ?? false,
|
|
unsplashKeyword: fields[23] as String? ?? 'mosque',
|
|
unsplashRotationHours: fields[24] as int? ?? 6,
|
|
brandedBgImage: fields[25] as String?,
|
|
runningTextDurations: (fields[26] as List?)?.cast<int>() ?? const [],
|
|
marqueeAnimType: fields[27] as String? ?? 'marquee',
|
|
scaleCardLabel: (fields[28] as num?)?.toDouble() ?? 1.0,
|
|
scaleCardBody: (fields[29] as num?)?.toDouble() ?? 1.0,
|
|
scaleRunningText: (fields[30] as num?)?.toDouble() ?? 1.0,
|
|
hijriOffsetDays: fields[31] as int? ?? 0,
|
|
scaleTopHeader: (fields[36] as num?)?.toDouble() ?? 1.0,
|
|
scaleTextSlideCenter: (fields[37] as num?)?.toDouble() ?? 1.0,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void write(BinaryWriter writer, AppSettings obj) {
|
|
writer
|
|
..writeByte(40)
|
|
..writeByte(0)
|
|
..write(obj.masjidName)
|
|
..writeByte(1)
|
|
..write(obj.masjidAddress)
|
|
..writeByte(2)
|
|
..write(obj.cityIdApi)
|
|
..writeByte(3)
|
|
..write(obj.cityDisplayName)
|
|
..writeByte(4)
|
|
..write(obj.showImsak)
|
|
..writeByte(5)
|
|
..write(obj.showTerbit)
|
|
..writeByte(6)
|
|
..write(obj.iqomahSubuh)
|
|
..writeByte(7)
|
|
..write(obj.iqomahDzuhur)
|
|
..writeByte(8)
|
|
..write(obj.iqomahAshar)
|
|
..writeByte(9)
|
|
..write(obj.iqomahMaghrib)
|
|
..writeByte(10)
|
|
..write(obj.iqomahIsya)
|
|
..writeByte(11)
|
|
..write(obj.preAdzanLead)
|
|
..writeByte(12)
|
|
..write(obj.blankScreenNormal)
|
|
..writeByte(13)
|
|
..write(obj.blankScreenJumat)
|
|
..writeByte(14)
|
|
..write(obj.runningTexts)
|
|
..writeByte(35)
|
|
..write(obj.textSlides)
|
|
..writeByte(15)
|
|
..write(obj.khatibName)
|
|
..writeByte(16)
|
|
..write(obj.imamName)
|
|
..writeByte(17)
|
|
..write(obj.mainScreenDurationSec)
|
|
..writeByte(18)
|
|
..write(obj.slideDurationSec)
|
|
..writeByte(19)
|
|
..write(obj.lastSyncDate)
|
|
..writeByte(32)
|
|
..write(obj.lastAutoSyncAttemptDate)
|
|
..writeByte(33)
|
|
..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)
|
|
..write(obj.textScaleIndex)
|
|
..writeByte(22)
|
|
..write(obj.useUnsplashBackground)
|
|
..writeByte(23)
|
|
..write(obj.unsplashKeyword)
|
|
..writeByte(24)
|
|
..write(obj.unsplashRotationHours)
|
|
..writeByte(25)
|
|
..write(obj.brandedBgImage)
|
|
..writeByte(26)
|
|
..write(obj.runningTextDurations)
|
|
..writeByte(27)
|
|
..write(obj.marqueeAnimType)
|
|
..writeByte(28)
|
|
..write(obj.scaleCardLabel)
|
|
..writeByte(29)
|
|
..write(obj.scaleCardBody)
|
|
..writeByte(30)
|
|
..write(obj.scaleRunningText)
|
|
..writeByte(31)
|
|
..write(obj.hijriOffsetDays)
|
|
..writeByte(36)
|
|
..write(obj.scaleTopHeader)
|
|
..writeByte(37)
|
|
..write(obj.scaleTextSlideCenter);
|
|
}
|
|
}
|
|
|
|
/// Daily prayer schedule row cached from the MyQuran API.
|
|
@HiveType(typeId: 1)
|
|
class DailyPrayerSchedule extends HiveObject {
|
|
@HiveField(0)
|
|
String date; // yyyy-MM-dd
|
|
|
|
@HiveField(1)
|
|
String imsak;
|
|
|
|
@HiveField(2)
|
|
String subuh;
|
|
|
|
@HiveField(3)
|
|
String terbit;
|
|
|
|
@HiveField(4)
|
|
String dhuha;
|
|
|
|
@HiveField(5)
|
|
String dzuhur;
|
|
|
|
@HiveField(6)
|
|
String ashar;
|
|
|
|
@HiveField(7)
|
|
String maghrib;
|
|
|
|
@HiveField(8)
|
|
String isya;
|
|
|
|
DailyPrayerSchedule({
|
|
required this.date,
|
|
required this.imsak,
|
|
required this.subuh,
|
|
required this.terbit,
|
|
required this.dhuha,
|
|
required this.dzuhur,
|
|
required this.ashar,
|
|
required this.maghrib,
|
|
required this.isya,
|
|
});
|
|
|
|
/// Parse time string "HH:mm" to a DateTime on the given date.
|
|
DateTime timeToDateTime(String time, DateTime refDate) {
|
|
final parts = time.split(':');
|
|
return DateTime(
|
|
refDate.year,
|
|
refDate.month,
|
|
refDate.day,
|
|
int.parse(parts[0]),
|
|
int.parse(parts[1]),
|
|
);
|
|
}
|
|
|
|
/// Get all prayer times as DateTime map for a given reference date.
|
|
Map<String, DateTime> toDateTimeMap(DateTime refDate) => {
|
|
'imsak': timeToDateTime(imsak, refDate),
|
|
'subuh': timeToDateTime(subuh, refDate),
|
|
'terbit': timeToDateTime(terbit, refDate),
|
|
'dhuha': timeToDateTime(dhuha, refDate),
|
|
'dzuhur': timeToDateTime(dzuhur, refDate),
|
|
'ashar': timeToDateTime(ashar, refDate),
|
|
'maghrib': timeToDateTime(maghrib, refDate),
|
|
'isya': timeToDateTime(isya, refDate),
|
|
};
|
|
}
|
|
|
|
/// Adapter for DailyPrayerSchedule.
|
|
class DailyPrayerScheduleAdapter extends TypeAdapter<DailyPrayerSchedule> {
|
|
@override
|
|
final int typeId = 1;
|
|
|
|
@override
|
|
DailyPrayerSchedule read(BinaryReader reader) {
|
|
final numOfFields = reader.readByte();
|
|
final fields = <int, dynamic>{};
|
|
for (int i = 0; i < numOfFields; i++) {
|
|
fields[reader.readByte()] = reader.read();
|
|
}
|
|
return DailyPrayerSchedule(
|
|
date: fields[0] as String? ?? '',
|
|
imsak: fields[1] as String? ?? '00:00',
|
|
subuh: fields[2] as String? ?? '00:00',
|
|
terbit: fields[3] as String? ?? '00:00',
|
|
dhuha: fields[4] as String? ?? '00:00',
|
|
dzuhur: fields[5] as String? ?? '00:00',
|
|
ashar: fields[6] as String? ?? '00:00',
|
|
maghrib: fields[7] as String? ?? '00:00',
|
|
isya: fields[8] as String? ?? '00:00',
|
|
);
|
|
}
|
|
|
|
@override
|
|
void write(BinaryWriter writer, DailyPrayerSchedule obj) {
|
|
writer
|
|
..writeByte(9)
|
|
..writeByte(0)
|
|
..write(obj.date)
|
|
..writeByte(1)
|
|
..write(obj.imsak)
|
|
..writeByte(2)
|
|
..write(obj.subuh)
|
|
..writeByte(3)
|
|
..write(obj.terbit)
|
|
..writeByte(4)
|
|
..write(obj.dhuha)
|
|
..writeByte(5)
|
|
..write(obj.dzuhur)
|
|
..writeByte(6)
|
|
..write(obj.ashar)
|
|
..writeByte(7)
|
|
..write(obj.maghrib)
|
|
..writeByte(8)
|
|
..write(obj.isya);
|
|
}
|
|
}
|