Add midnight fallback notice, overnight simulations, and keyboard log filter
This commit is contained in:
@@ -21,7 +21,7 @@ class JumatScreen extends ConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final clock = ref.watch(clockProvider).valueOrNull ?? DateTime.now();
|
||||
final schedule = ref.watch(todayScheduleProvider);
|
||||
final schedule = ref.watch(runtimeScheduleProvider).schedule;
|
||||
final settings = ref.watch(settingsProvider);
|
||||
final screenData = ref.watch(screenStateProvider);
|
||||
final size = MediaQuery.of(context).size;
|
||||
@@ -31,8 +31,8 @@ class JumatScreen extends ConsumerWidget {
|
||||
final timeStr = DateFormat('HH:mm').format(clock);
|
||||
final secStr = DateFormat(':ss').format(clock);
|
||||
final dateGregorian = DateFormat('EEEE, d MMMM yyyy', 'en').format(clock);
|
||||
final dateHijri =
|
||||
ref.watch(hijriDateProvider).valueOrNull ?? HijriDateFormatter.format(clock);
|
||||
final dateHijri = ref.watch(hijriDateProvider).valueOrNull ??
|
||||
HijriDateFormatter.format(clock);
|
||||
|
||||
final durToKhutbah = screenData.timeUntilNext ?? const Duration(minutes: 0);
|
||||
final minToKhutbah = durToKhutbah.inMinutes;
|
||||
@@ -42,7 +42,8 @@ class JumatScreen extends ConsumerWidget {
|
||||
child: Stack(
|
||||
children: [
|
||||
// ── Underlay: Branded local image or Unsplash ──
|
||||
if (settings.brandedBgImage != null && settings.brandedBgImage!.isNotEmpty)
|
||||
if (settings.brandedBgImage != null &&
|
||||
settings.brandedBgImage!.isNotEmpty)
|
||||
Positioned.fill(
|
||||
child: Image.file(
|
||||
File(settings.brandedBgImage!),
|
||||
@@ -99,16 +100,20 @@ class JumatScreen extends ConsumerWidget {
|
||||
children: [
|
||||
// Pill
|
||||
Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 8 * s),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 24 * s, vertical: 8 * s),
|
||||
decoration: BoxDecoration(
|
||||
color: SacredColors.secondary.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(SacredRadii.full),
|
||||
border: Border.all(color: SacredColors.secondary.withValues(alpha: 0.2)),
|
||||
border: Border.all(
|
||||
color: SacredColors.secondary
|
||||
.withValues(alpha: 0.2)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_PulsingDot(color: SacredColors.secondary, size: 12 * s),
|
||||
_PulsingDot(
|
||||
color: SacredColors.secondary, size: 12 * s),
|
||||
SizedBox(width: 12 * s),
|
||||
Text(
|
||||
'PERSIAPAN JUMAT',
|
||||
@@ -122,9 +127,9 @@ class JumatScreen extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
SizedBox(height: 16 * s),
|
||||
|
||||
|
||||
// Massive Clock
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -146,15 +151,16 @@ class JumatScreen extends ConsumerWidget {
|
||||
style: GoogleFonts.plusJakartaSans(
|
||||
fontSize: 48 * s,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: SacredColors.primary.withValues(alpha: 0.7),
|
||||
color:
|
||||
SacredColors.primary.withValues(alpha: 0.7),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
|
||||
SizedBox(height: 16 * s),
|
||||
|
||||
|
||||
// Dates
|
||||
Text(
|
||||
dateGregorian,
|
||||
@@ -176,16 +182,18 @@ class JumatScreen extends ConsumerWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
// ── Right Column: Khutbah Info Card ──
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(40 * s),
|
||||
decoration: BoxDecoration(
|
||||
color: SacredColors.surfaceContainerHigh.withValues(alpha: 0.6),
|
||||
color: SacredColors.surfaceContainerHigh
|
||||
.withValues(alpha: 0.6),
|
||||
borderRadius: BorderRadius.circular(SacredRadii.xl),
|
||||
border: Border.all(color: Colors.white.withValues(alpha: 0.05)),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.05)),
|
||||
),
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(SacredRadii.xl),
|
||||
@@ -207,7 +215,10 @@ class JumatScreen extends ConsumerWidget {
|
||||
letterSpacing: 3 * s,
|
||||
),
|
||||
),
|
||||
HugeIcon(icon: HugeIcons.strokeRoundedSparkles, color: SacredColors.secondary, size: 24 * s),
|
||||
HugeIcon(
|
||||
icon: HugeIcons.strokeRoundedSparkles,
|
||||
color: SacredColors.secondary,
|
||||
size: 24 * s),
|
||||
],
|
||||
),
|
||||
SizedBox(height: 16 * s),
|
||||
@@ -221,25 +232,25 @@ class JumatScreen extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
SizedBox(height: 32 * s),
|
||||
|
||||
|
||||
// Khatib Info
|
||||
_buildInfoTile(
|
||||
s,
|
||||
icon: Icons.person_pin,
|
||||
color: SacredColors.primary,
|
||||
label: 'KHATIB HARI INI',
|
||||
value: settings.khatibName.isEmpty ? 'Belum Diatur' : settings.khatibName
|
||||
),
|
||||
_buildInfoTile(s,
|
||||
icon: Icons.person_pin,
|
||||
color: SacredColors.primary,
|
||||
label: 'KHATIB HARI INI',
|
||||
value: settings.khatibName.isEmpty
|
||||
? 'Belum Diatur'
|
||||
: settings.khatibName),
|
||||
SizedBox(height: 24 * s),
|
||||
|
||||
|
||||
// Countdown Info
|
||||
_buildInfoTile(
|
||||
s,
|
||||
icon: Icons.timer,
|
||||
color: SacredColors.secondary,
|
||||
label: 'KHUTBAH DIMULAI DALAM',
|
||||
value: minToKhutbah > 0 ? '~ $minToKhutbah Menit' : 'Sebentar Lagi'
|
||||
),
|
||||
_buildInfoTile(s,
|
||||
icon: Icons.timer,
|
||||
color: SacredColors.secondary,
|
||||
label: 'KHUTBAH DIMULAI DALAM',
|
||||
value: minToKhutbah > 0
|
||||
? '~ $minToKhutbah Menit'
|
||||
: 'Sebentar Lagi'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -277,7 +288,8 @@ class JumatScreen extends ConsumerWidget {
|
||||
children: [
|
||||
// Mosque Name
|
||||
GestureDetector(
|
||||
onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const AdminScreen())),
|
||||
onTap: () => Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (_) => const AdminScreen())),
|
||||
child: Text(
|
||||
settings.masjidName,
|
||||
style: GoogleFonts.plusJakartaSans(
|
||||
@@ -290,7 +302,10 @@ class JumatScreen extends ConsumerWidget {
|
||||
// Mosque Address
|
||||
Row(
|
||||
children: [
|
||||
HugeIcon(icon: HugeIcons.strokeRoundedMosque01, color: SacredColors.primary, size: 24 * s),
|
||||
HugeIcon(
|
||||
icon: HugeIcons.strokeRoundedMosque01,
|
||||
color: SacredColors.primary,
|
||||
size: 24 * s),
|
||||
SizedBox(width: 8 * s),
|
||||
Text(
|
||||
settings.masjidAddress,
|
||||
@@ -306,7 +321,11 @@ class JumatScreen extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildInfoTile(double s, {required IconData icon, required Color color, required String label, required String value}) {
|
||||
Widget _buildInfoTile(double s,
|
||||
{required IconData icon,
|
||||
required Color color,
|
||||
required String label,
|
||||
required String value}) {
|
||||
return Container(
|
||||
padding: EdgeInsets.all(20 * s),
|
||||
decoration: BoxDecoration(
|
||||
@@ -373,32 +392,44 @@ class JumatScreen extends ConsumerWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
_buildTimeItem(s, 'Fajr', schedule.subuh, Icons.brightness_3, false),
|
||||
_buildTimeItem(s, 'Terbit', schedule.terbit, Icons.wb_twilight, false),
|
||||
_buildTimeItem(
|
||||
s, 'Terbit', schedule.terbit, Icons.wb_twilight, false),
|
||||
_buildTimeItem(s, 'JUMAT', schedule.dzuhur, Icons.wb_sunny, true),
|
||||
_buildTimeItem(s, 'Asr', schedule.ashar, Icons.sunny_snowing, false),
|
||||
_buildTimeItem(s, 'Maghrib', schedule.maghrib, Icons.wb_cloudy, false),
|
||||
_buildTimeItem(
|
||||
s, 'Maghrib', schedule.maghrib, Icons.wb_cloudy, false),
|
||||
_buildTimeItem(s, 'Isha', schedule.isya, Icons.bedtime, false),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTimeItem(double s, String name, String time, IconData icon, bool isJumat) {
|
||||
Widget _buildTimeItem(
|
||||
double s, String name, String time, IconData icon, bool isJumat) {
|
||||
if (isJumat) {
|
||||
return Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: 48 * s, vertical: 12 * s),
|
||||
decoration: BoxDecoration(
|
||||
color: SacredColors.primary.withValues(alpha: 0.15),
|
||||
border: Border.all(color: SacredColors.primary.withValues(alpha: 0.3)),
|
||||
border:
|
||||
Border.all(color: SacredColors.primary.withValues(alpha: 0.3)),
|
||||
borderRadius: BorderRadius.circular(SacredRadii.xl),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: SacredColors.primary, size: 28 * s),
|
||||
SizedBox(height: 8 * s),
|
||||
Text(name, style: GoogleFonts.manrope(fontSize: 18 * s, fontWeight: FontWeight.w800, color: SacredColors.primary)),
|
||||
Text(name,
|
||||
style: GoogleFonts.manrope(
|
||||
fontSize: 18 * s,
|
||||
fontWeight: FontWeight.w800,
|
||||
color: SacredColors.primary)),
|
||||
SizedBox(height: 4 * s),
|
||||
Text(time, style: GoogleFonts.plusJakartaSans(fontSize: 16 * s, fontWeight: FontWeight.w700, color: SacredColors.primary)),
|
||||
Text(time,
|
||||
style: GoogleFonts.plusJakartaSans(
|
||||
fontSize: 16 * s,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: SacredColors.primary)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -407,11 +438,21 @@ class JumatScreen extends ConsumerWidget {
|
||||
padding: EdgeInsets.symmetric(horizontal: 24 * s, vertical: 8 * s),
|
||||
child: Column(
|
||||
children: [
|
||||
Icon(icon, color: SacredColors.onSurface.withValues(alpha: 0.6), size: 24 * s),
|
||||
Icon(icon,
|
||||
color: SacredColors.onSurface.withValues(alpha: 0.6),
|
||||
size: 24 * s),
|
||||
SizedBox(height: 8 * s),
|
||||
Text(name, style: GoogleFonts.manrope(fontSize: 16 * s, fontWeight: FontWeight.w500, color: SacredColors.onSurface.withValues(alpha: 0.6))),
|
||||
Text(name,
|
||||
style: GoogleFonts.manrope(
|
||||
fontSize: 16 * s,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: SacredColors.onSurface.withValues(alpha: 0.6))),
|
||||
SizedBox(height: 4 * s),
|
||||
Text(time, style: GoogleFonts.plusJakartaSans(fontSize: 16 * s, fontWeight: FontWeight.w700, color: SacredColors.onSurface)),
|
||||
Text(time,
|
||||
style: GoogleFonts.plusJakartaSans(
|
||||
fontSize: 16 * s,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: SacredColors.onSurface)),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -419,8 +460,10 @@ class JumatScreen extends ConsumerWidget {
|
||||
|
||||
Widget _buildMarquee(double s, double fs, AppSettings settings) {
|
||||
// Quick custom simplified marquee or fallback to settings.runningTexts
|
||||
final texts = settings.runningTexts.isEmpty
|
||||
? ["JUMAT MUBARAK: Luruskan dan rapatkan shaf. Harap non-aktifkan alat komunikasi."]
|
||||
final texts = settings.runningTexts.isEmpty
|
||||
? [
|
||||
"JUMAT MUBARAK: Luruskan dan rapatkan shaf. Harap non-aktifkan alat komunikasi."
|
||||
]
|
||||
: settings.runningTexts;
|
||||
|
||||
return Container(
|
||||
@@ -448,29 +491,45 @@ class _PulsingDot extends StatefulWidget {
|
||||
State<_PulsingDot> createState() => _PulsingDotState();
|
||||
}
|
||||
|
||||
class _PulsingDotState extends State<_PulsingDot> with SingleTickerProviderStateMixin {
|
||||
class _PulsingDotState extends State<_PulsingDot>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late AnimationController _ctrl;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ctrl = AnimationController(vsync: this, duration: const Duration(milliseconds: 1200))..repeat();
|
||||
_ctrl = AnimationController(
|
||||
vsync: this, duration: const Duration(milliseconds: 1200))
|
||||
..repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() { _ctrl.dispose(); super.dispose(); }
|
||||
void dispose() {
|
||||
_ctrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: widget.size, height: widget.size,
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
child: Stack(
|
||||
children: [
|
||||
FadeTransition(
|
||||
opacity: Tween(begin: 0.75, end: 0.0).animate(_ctrl),
|
||||
child: ScaleTransition(
|
||||
scale: Tween(begin: 1.0, end: 2.0).animate(_ctrl),
|
||||
child: Container(decoration: BoxDecoration(shape: BoxShape.circle, color: widget.color)),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle, color: widget.color)),
|
||||
),
|
||||
),
|
||||
Center(child: Container(width: widget.size, height: widget.size, decoration: BoxDecoration(shape: BoxShape.circle, color: widget.color))),
|
||||
Center(
|
||||
child: Container(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle, color: widget.color))),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -485,15 +544,23 @@ class _JumatMarquee extends StatefulWidget {
|
||||
State<_JumatMarquee> createState() => _JumatMarqueeState();
|
||||
}
|
||||
|
||||
class _JumatMarqueeState extends State<_JumatMarquee> with TickerProviderStateMixin {
|
||||
class _JumatMarqueeState extends State<_JumatMarquee>
|
||||
with TickerProviderStateMixin {
|
||||
late AnimationController _ctrl;
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_ctrl = AnimationController(vsync: this, duration: const Duration(seconds: 30))..repeat();
|
||||
_ctrl =
|
||||
AnimationController(vsync: this, duration: const Duration(seconds: 30))
|
||||
..repeat();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() { _ctrl.dispose(); super.dispose(); }
|
||||
void dispose() {
|
||||
_ctrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final joined = widget.texts.join(" • ");
|
||||
@@ -503,30 +570,35 @@ class _JumatMarqueeState extends State<_JumatMarquee> with TickerProviderStateMi
|
||||
color: SacredColors.secondary,
|
||||
letterSpacing: 2 * widget.s,
|
||||
);
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return Stack(
|
||||
children: [
|
||||
AnimatedBuilder(
|
||||
animation: _ctrl,
|
||||
builder: (ctx, child) {
|
||||
// Ensure endless scroll mathematically
|
||||
return Positioned(
|
||||
left: -(_ctrl.value * constraints.maxWidth),
|
||||
top: 0, bottom: 0,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(alignment: Alignment.centerLeft, width: constraints.maxWidth, child: Text(joined, style: style, maxLines: 1)),
|
||||
Container(alignment: Alignment.centerLeft, width: constraints.maxWidth, child: Text(joined, style: style, maxLines: 1)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return LayoutBuilder(builder: (context, constraints) {
|
||||
return Stack(
|
||||
children: [
|
||||
AnimatedBuilder(
|
||||
animation: _ctrl,
|
||||
builder: (ctx, child) {
|
||||
// Ensure endless scroll mathematically
|
||||
return Positioned(
|
||||
left: -(_ctrl.value * constraints.maxWidth),
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
width: constraints.maxWidth,
|
||||
child: Text(joined, style: style, maxLines: 1)),
|
||||
Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
width: constraints.maxWidth,
|
||||
child: Text(joined, style: style, maxLines: 1)),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user