- Add 10-second periodic auto-sync to CycleEntriesNotifier - Fix husband_devotional_screen: use partnerId for isConnected check, navigate to SharingSettingsScreen instead of legacy mock dialog - Remove obsolete _showConnectDialog method and mock data import - Update husband_settings_screen: show 'Partner Settings' with linked partner name when connected - Add SharingSettingsScreen: Pad Supplies toggle (disabled when pad tracking off), Intimacy always enabled - Add CORS OPTIONS handler to backend server - Add _ensureServerRegistration for reliable partner linking - Add copy button to Invite Partner dialog - Dynamic base URL for web (uses window.location.hostname)
162 lines
5.0 KiB
Dart
162 lines
5.0 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:hive_flutter/hive_flutter.dart';
|
|
import 'main.dart'; // Import ChristianPeriodTrackerApp
|
|
import 'models/cycle_entry.dart';
|
|
import 'models/teaching_plan.dart';
|
|
import 'models/prayer_request.dart';
|
|
import 'models/scripture.dart';
|
|
import 'models/user_profile.dart';
|
|
import 'screens/splash_screen.dart';
|
|
import 'services/notification_service.dart';
|
|
|
|
class AppStartupWidget extends StatefulWidget {
|
|
const AppStartupWidget({super.key});
|
|
|
|
@override
|
|
State<AppStartupWidget> createState() => _AppStartupWidgetState();
|
|
}
|
|
|
|
class _AppStartupWidgetState extends State<AppStartupWidget> {
|
|
bool _isInitialized = false;
|
|
String _status = 'Initializing...';
|
|
bool _showSkipButton = false;
|
|
final Duration _minSplashDuration = const Duration(milliseconds: 2000);
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_initializeApp();
|
|
|
|
// Show skip button if loading takes too long (e.g., 5 seconds)
|
|
Future.delayed(const Duration(seconds: 5), () {
|
|
if (mounted && !_isInitialized) {
|
|
setState(() => _showSkipButton = true);
|
|
}
|
|
});
|
|
}
|
|
|
|
Future<void> _initializeApp() async {
|
|
final stopwatch = Stopwatch()..start();
|
|
|
|
try {
|
|
setState(() => _status = 'Loading user profile...');
|
|
// Add timeout to prevent indefinite hanging
|
|
await Hive.openBox<UserProfile>('user_profile_v2')
|
|
.timeout(const Duration(seconds: 5));
|
|
|
|
setState(() => _status = 'Loading cycle data...');
|
|
await Hive.openBox<CycleEntry>('cycle_entries_v2')
|
|
.timeout(const Duration(seconds: 5));
|
|
|
|
setState(() => _status = 'Loading shared data...');
|
|
await Hive.openBox<TeachingPlan>('teaching_plans_v2')
|
|
.timeout(const Duration(seconds: 5));
|
|
await Hive.openBox<PrayerRequest>('prayer_requests_v2')
|
|
.timeout(const Duration(seconds: 5));
|
|
|
|
setState(() => _status = 'Loading scriptures...');
|
|
// Wrap scripture loading in a try-catch specifically to allow app to start even if assets fail
|
|
try {
|
|
await ScriptureDatabase()
|
|
.loadScriptures()
|
|
.timeout(const Duration(seconds: 5));
|
|
} catch (e) {
|
|
debugPrint('Scripture loading failed (non-critical): $e');
|
|
setState(() => _status = 'Scripture loading skipped...');
|
|
}
|
|
|
|
setState(() => _status = 'Setting up notifications...');
|
|
try {
|
|
await NotificationService()
|
|
.initialize()
|
|
.timeout(const Duration(seconds: 3));
|
|
} catch (e) {
|
|
debugPrint('Notification init failed: $e');
|
|
}
|
|
} catch (e, stack) {
|
|
debugPrint('Initialization error: $e');
|
|
debugPrint(stack.toString());
|
|
setState(() => _status = 'Error: $e');
|
|
// On critical error, allow user to proceed manually via skip button
|
|
setState(() => _showSkipButton = true);
|
|
return;
|
|
}
|
|
|
|
final elapsed = stopwatch.elapsed;
|
|
if (elapsed < _minSplashDuration) {
|
|
await Future.delayed(_minSplashDuration - elapsed);
|
|
}
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
_isInitialized = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
void _skipInitialization() {
|
|
setState(() => _isInitialized = true);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
if (!_isInitialized) {
|
|
return MaterialApp(
|
|
home: Scaffold(
|
|
body: Stack(
|
|
children: [
|
|
const SplashScreen(isStartup: true),
|
|
|
|
// Status Message (High Visibility)
|
|
Positioned(
|
|
bottom: 80,
|
|
left: 20,
|
|
right: 20,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withValues(alpha: 0.9),
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Text(
|
|
_status,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(
|
|
color: Colors.black87,
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
decoration: TextDecoration.none,
|
|
fontFamily: 'SansSerif'),
|
|
),
|
|
),
|
|
),
|
|
|
|
// Skip Button
|
|
if (_showSkipButton)
|
|
Positioned(
|
|
bottom: 30,
|
|
left: 0,
|
|
right: 0,
|
|
child: Center(
|
|
child: ElevatedButton(
|
|
onPressed: _skipInitialization,
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.redAccent,
|
|
foregroundColor: Colors.white,
|
|
),
|
|
child: const Text('Skip Loading (Force Start)'),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
debugShowCheckedModeBanner: false,
|
|
);
|
|
}
|
|
|
|
return const ChristianPeriodTrackerApp();
|
|
}
|
|
}
|