Harden app for 24-7 offline-first operation
This commit is contained in:
@@ -1628,6 +1628,17 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
||||
height: 180 * s,
|
||||
width: double.infinity,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
height: 180 * s,
|
||||
width: double.infinity,
|
||||
color: SacredColors.surfaceContainerLowest,
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
Icons.broken_image,
|
||||
size: 36 * s,
|
||||
color: SacredColors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 12 * s),
|
||||
@@ -1673,8 +1684,9 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
||||
s: s,
|
||||
onActivate: () async {
|
||||
final res = await FilePicker.platform.pickFiles(type: FileType.image);
|
||||
if (res != null && res.files.single.path != null) {
|
||||
setState(() => _brandedBgImage = res.files.single.path);
|
||||
final selectedPath = res?.files.single.path;
|
||||
if (selectedPath != null && File(selectedPath).existsSync()) {
|
||||
setState(() => _brandedBgImage = selectedPath);
|
||||
_queueTampilanAutoSave(
|
||||
message: 'Foto latar otomatis tersimpan',
|
||||
);
|
||||
@@ -1683,8 +1695,10 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () async {
|
||||
final res = await FilePicker.platform.pickFiles(type: FileType.image);
|
||||
if (res != null && res.files.single.path != null) {
|
||||
setState(() => _brandedBgImage = res.files.single.path);
|
||||
final selectedPath = res?.files.single.path;
|
||||
if (selectedPath != null &&
|
||||
File(selectedPath).existsSync()) {
|
||||
setState(() => _brandedBgImage = selectedPath);
|
||||
_queueTampilanAutoSave(
|
||||
message: 'Foto latar otomatis tersimpan',
|
||||
);
|
||||
@@ -1720,7 +1734,9 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
||||
if (res != null) {
|
||||
setState(() {
|
||||
for (var path in res.paths) {
|
||||
if (path != null && !_slideshowImages.contains(path)) {
|
||||
if (path != null &&
|
||||
File(path).existsSync() &&
|
||||
!_slideshowImages.contains(path)) {
|
||||
_slideshowImages.add(path);
|
||||
}
|
||||
}
|
||||
@@ -1736,7 +1752,9 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
||||
if (res != null) {
|
||||
setState(() {
|
||||
for (var path in res.paths) {
|
||||
if (path != null && !_slideshowImages.contains(path)) {
|
||||
if (path != null &&
|
||||
File(path).existsSync() &&
|
||||
!_slideshowImages.contains(path)) {
|
||||
_slideshowImages.add(path);
|
||||
}
|
||||
}
|
||||
@@ -1785,6 +1803,17 @@ class _AdminScreenState extends ConsumerState<AdminScreen> {
|
||||
width: double.infinity,
|
||||
height: 120 * s,
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (_, __, ___) => Container(
|
||||
width: double.infinity,
|
||||
height: 120 * s,
|
||||
color: SacredColors.surfaceContainerHigh,
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
Icons.broken_image,
|
||||
size: 32 * s,
|
||||
color: SacredColors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(height: 10 * s),
|
||||
|
||||
@@ -44,12 +44,15 @@ class _HomeViewState extends ConsumerState<HomeView> {
|
||||
final List<LogicalKeyboardKey> _simulationShortcutKeys = [];
|
||||
Timer? _comboResetTimer;
|
||||
Timer? _simulationShortcutTimer;
|
||||
Timer? _autoRefreshTimer;
|
||||
bool _isAutoRefreshRunning = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_checkAutoSync();
|
||||
_startAutoRefreshMonitor();
|
||||
if (mounted) {
|
||||
_homeFocusNode.requestFocus();
|
||||
}
|
||||
@@ -60,21 +63,38 @@ class _HomeViewState extends ConsumerState<HomeView> {
|
||||
void dispose() {
|
||||
_comboResetTimer?.cancel();
|
||||
_simulationShortcutTimer?.cancel();
|
||||
_autoRefreshTimer?.cancel();
|
||||
_homeFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _startAutoRefreshMonitor() {
|
||||
_autoRefreshTimer?.cancel();
|
||||
_autoRefreshTimer = Timer.periodic(
|
||||
const Duration(hours: 6),
|
||||
(_) => _checkAutoSync(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _checkAutoSync() async {
|
||||
final schedule = ref.read(todayScheduleProvider);
|
||||
if (schedule == null) {
|
||||
debugPrint('[AutoSync] No schedule found for today! Starting auto-sync...');
|
||||
final success = await SyncService.instance.syncMonthlyData();
|
||||
if (success && mounted) {
|
||||
debugPrint('[AutoSync] Success! Invalidating todayScheduleProvider.');
|
||||
if (_isAutoRefreshRunning || !mounted) return;
|
||||
_isAutoRefreshRunning = true;
|
||||
try {
|
||||
final result = await SyncService.instance.autoRefreshIfNeeded();
|
||||
if (!mounted) return;
|
||||
|
||||
if (result.synced) {
|
||||
debugPrint('[AutoSync] Cache refreshed successfully.');
|
||||
ref.invalidate(todayScheduleProvider);
|
||||
} else {
|
||||
debugPrint('[AutoSync] Failed or data remained empty.');
|
||||
ref.invalidate(scheduleCacheStatusProvider);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.attempted) {
|
||||
debugPrint('[AutoSync] Refresh attempt failed. Staying on local cache.');
|
||||
}
|
||||
} finally {
|
||||
_isAutoRefreshRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ class JumatScreen extends ConsumerWidget {
|
||||
fit: BoxFit.cover,
|
||||
color: Colors.black.withValues(alpha: 0.55),
|
||||
colorBlendMode: BlendMode.darken,
|
||||
errorBuilder: (_, __, ___) => const UnsplashBackground(),
|
||||
),
|
||||
)
|
||||
else
|
||||
|
||||
@@ -53,6 +53,7 @@ class MainScreen extends ConsumerWidget {
|
||||
fit: BoxFit.cover,
|
||||
color: Colors.black.withValues(alpha: 0.55),
|
||||
colorBlendMode: BlendMode.darken,
|
||||
errorBuilder: (_, __, ___) => const UnsplashBackground(),
|
||||
),
|
||||
)
|
||||
else
|
||||
|
||||
@@ -96,6 +96,7 @@ class _UnsplashBackgroundState extends ConsumerState<UnsplashBackground> {
|
||||
// Soft opacity behind the MainScreen's dark glass vignette
|
||||
color: Colors.black.withValues(alpha: 0.5),
|
||||
colorBlendMode: BlendMode.darken,
|
||||
errorBuilder: (_, __, ___) => const SizedBox.shrink(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user