MIF_E31222656/lib/main.dart

492 lines
16 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/foundation.dart';
import 'dart:async';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:tugas_akhir_supabase/core/constants/app_constants.dart';
import 'package:tugas_akhir_supabase/di/service_locator.dart';
import 'package:tugas_akhir_supabase/core/routes/app_routes.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:tugas_akhir_supabase/services/session_manager.dart';
import 'package:tugas_akhir_supabase/services/user_presence_service.dart';
import 'package:tugas_akhir_supabase/widgets/session_expired_dialog.dart';
import 'package:get_it/get_it.dart';
import 'package:tugas_akhir_supabase/widgets/session_guard_wrapper.dart';
import 'package:tugas_akhir_supabase/screens/intro/intro_page_screen.dart'
as intro;
// Tambahkan listener untuk hot reload
bool _hasDoneHotReloadSetup = false;
// Global navigator key for accessing navigation from anywhere
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// Fungsi isolate terpisah untuk inisialisasi Supabase
Future<void> _initializeSupabase() async {
try {
await Supabase.initialize(
url: AppConstants.supabaseUrl,
anonKey: AppConstants.supabaseAnonKey,
debug: false,
);
debugPrint('Supabase initialized successfully');
} catch (e) {
debugPrint('Error initializing Supabase: $e');
rethrow;
}
}
void main() async {
// Langsung memulai aplikasi utama
try {
// Initialize Flutter binding
WidgetsFlutterBinding.ensureInitialized();
// Tambahkan penanganan error global
PlatformDispatcher.instance.onError = (error, stack) {
debugPrint('Global error handler: $error');
debugPrint('Stack trace: $stack');
// Return true to prevent the error from being reported to the framework
return true;
};
// Tambahkan dukungan untuk hot reload
if (!_hasDoneHotReloadSetup) {
_hasDoneHotReloadSetup = true;
// Set debug flags
debugPrint('======= Setting up hot reload support =======');
// Pastikan semua yang memblokir hot reload dibersihkan
final binding = WidgetsFlutterBinding.ensureInitialized();
binding.addPostFrameCallback((_) {
// Execute after first frame is rendered
debugPrint(
'======= First frame rendered, hot reload should work =======',
);
});
}
// Debug log untuk pelacakan splash screen
debugPrint('======= App Start: Loading TaniSMART application =======');
// Set orientation to portrait
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
// Set up error handlers
FlutterError.onError = (FlutterErrorDetails details) {
debugPrint('Flutter error: ${details.exception}');
};
// Initialize date formatting
await initializeDateFormatting('id_ID');
await initializeDateFormatting('en_US');
// Initialize Supabase dengan timeout
try {
// Gunakan timeout untuk mencegah blocking terlalu lama
await _initializeSupabase().timeout(
const Duration(seconds: 5),
onTimeout: () {
debugPrint('Supabase initialization timed out, continuing startup');
throw TimeoutException('Supabase initialization timed out');
},
);
} catch (e) {
// Lanjutkan meskipun ada error, akan ditangani nanti
debugPrint('Continuing after Supabase initialization issue: $e');
}
// Initialize service locator dengan timeout
try {
await initServiceLocator().timeout(
const Duration(seconds: 3),
onTimeout: () {
debugPrint('Service locator initialization timed out, continuing');
throw TimeoutException('Service locator initialization timed out');
},
);
} catch (e) {
debugPrint('Continuing after service locator issue: $e');
}
// Initialize session management dengan timeout
try {
await SessionManager.initializeSession().timeout(
const Duration(seconds: 3),
onTimeout: () {
debugPrint('Session initialization timed out, continuing');
throw TimeoutException('Session initialization timed out');
},
);
// Initialize user presence service if user is logged in
if (Supabase.instance.client.auth.currentUser != null) {
try {
if (GetIt.instance.isRegistered<UserPresenceService>()) {
await GetIt.instance<UserPresenceService>().initialize();
debugPrint('User presence service initialized');
}
} catch (e) {
debugPrint('Error initializing user presence service: $e');
}
}
} catch (e) {
debugPrint('Continuing after session initialization issue: $e');
}
// Debug log sebelum menjalankan aplikasi
debugPrint(
'======= App initialized: Running TaniSMART application =======',
);
// Run the app
runApp(const RealApp());
} catch (e, stack) {
debugPrint('Error starting full app: $e\n$stack');
// Show error screen
runApp(
MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('TaniSMART Error'),
backgroundColor: Colors.red,
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.error_outline, color: Colors.red, size: 48),
const SizedBox(height: 16),
const Text(
'Aplikasi tidak dapat dimulai',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
'Error: ${e.toString()}',
style: const TextStyle(color: Colors.red),
textAlign: TextAlign.center,
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () {
main();
},
child: const Text('Coba Lagi'),
),
],
),
),
),
),
),
);
}
}
class RealApp extends StatefulWidget {
const RealApp({super.key});
@override
State<RealApp> createState() => _RealAppState();
}
class _RealAppState extends State<RealApp> with WidgetsBindingObserver {
bool _showingSessionExpiredDialog = false;
bool _isInitialLaunch = true; // Flag untuk menandai initial launch
Timer? _initialLaunchTimer;
StreamSubscription? _sessionSubscription;
Timer? _sessionCheckTimer; // Timer untuk memeriksa sesi secara berkala
bool _hasSetupSessionMonitoring =
false; // Flag baru untuk menandai setup monitoring
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
// Penundaan yang lebih lama untuk pemasangan listener agar aplikasi bisa dimuat sepenuhnya
Future.delayed(Duration(seconds: 7), () {
if (!mounted) return;
// Hanya setup jika aplikasi masih berjalan dan belum setup
if (!_hasSetupSessionMonitoring) {
_setupSessionMonitoring();
}
});
// Delay pemeriksaan session sampai aplikasi benar-benar siap
// Beri waktu splash screen menyelesaikan animasinya dan navigasi selesai
_initialLaunchTimer = Timer(Duration(seconds: 10), () {
if (mounted) {
setState(() {
_isInitialLaunch =
false; // Reset flag setelah initial launch benar-benar selesai
debugPrint('App: Initial launch phase completed');
});
// Hanya setup jika aplikasi masih berjalan dan belum setup
if (!_hasSetupSessionMonitoring) {
_setupSessionMonitoring();
}
}
});
}
// Metode terpisah untuk setup monitoring sesi
void _setupSessionMonitoring() {
if (_hasSetupSessionMonitoring) return; // Hindari setup duplikat
_hasSetupSessionMonitoring = true;
debugPrint('App: Setting up session expiration listener');
_sessionSubscription = SessionManager.sessionExpiredStream.listen((
expired,
) {
debugPrint('App: Session expired event received: $expired');
final currentUser = Supabase.instance.client.auth.currentUser;
if (expired && !_showingSessionExpiredDialog && currentUser != null) {
debugPrint('App: Showing session expired dialog from stream');
_showSessionExpiredDialog();
}
});
// Mulai pemeriksaan sesi yang lebih agresif (DISABLED)
void startAggressiveSessionChecking() {
debugPrint('App: Session monitoring DISABLED for permissive mode');
// Cancel any existing timer
_sessionCheckTimer?.cancel();
// No timer setup - completely disabled
debugPrint('App: No periodic session checks will run');
}
}
// Check session validity on startup and periodically (DISABLED)
Future<void> _checkSessionValidity() async {
// Session checking DISABLED for permissive mode
debugPrint('App: Session checking DISABLED for permissive mode');
return;
}
@override
void dispose() {
_initialLaunchTimer?.cancel();
_sessionSubscription?.cancel();
_sessionCheckTimer?.cancel();
WidgetsBinding.instance.removeObserver(this);
SessionManager.dispose();
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
debugPrint('App lifecycle state changed to: $state');
switch (state) {
case AppLifecycleState.paused:
case AppLifecycleState.inactive:
case AppLifecycleState.detached:
// App went to background
debugPrint('App: Going to background, calling onAppBackground');
SessionManager.onAppBackground();
break;
case AppLifecycleState.resumed:
// App came to foreground
debugPrint('App: Coming to foreground, calling onAppForeground');
SessionManager.onAppForeground().then((_) {
debugPrint(
'App: After foreground transition, expired = ${SessionManager.isExpired}',
);
// Periksa apakah pengguna sudah login terlebih dahulu
final currentUser = Supabase.instance.client.auth.currentUser;
if (currentUser == null) {
debugPrint(
'App: No authenticated user after foreground, skipping session check',
);
return;
}
if (SessionManager.isExpired && !_showingSessionExpiredDialog) {
debugPrint('App: Session expired after coming to foreground');
_showSessionExpiredDialog();
} else {
// Periksa sesi secara manual untuk memastikan
_checkSessionValidity();
}
});
break;
default:
break;
}
}
void _showSessionExpiredDialog() {
// Session expired dialog DISABLED for permissive mode
debugPrint('App: Session expired dialog DISABLED for permissive mode');
return;
}
// Reset expired dialog state saat login/logout
void _resetExpiredDialogState() {
_showingSessionExpiredDialog = false;
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'TaniSMART',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.green,
primaryColor: const Color(0xFF056839),
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF056839),
brightness: Brightness.light,
),
useMaterial3: true,
fontFamily: 'Poppins',
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF056839),
foregroundColor: Colors.white,
elevation: 0,
centerTitle: true,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF056839),
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
),
),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF056839), width: 2),
),
),
),
home: const intro.AnimatedIntroScreen(),
routes: Map.from(AppRoutes.routes)..remove('/'),
// Add navigation observer to clear SnackBars when navigating
navigatorObservers: [
_SnackBarClearingNavigatorObserver(),
_UserInteractionObserver(),
],
// Add router to intercept navigation when session is expired
builder: (context, child) {
// Force login screen if session is expired
if (SessionManager.isExpired &&
child != null &&
!_isInitialLaunch &&
Supabase.instance.client.auth.currentUser != null) {
debugPrint('App: Session expired, forcing login screen');
// Show session expired dialog if not already showing
if (!_showingSessionExpiredDialog) {
// Use a post-frame callback to avoid build phase issues
WidgetsBinding.instance.addPostFrameCallback((_) {
_showSessionExpiredDialog();
});
}
// Return a restricted UI that prevents interaction
return Material(
child: Stack(
children: [
// Blur the background content
Opacity(opacity: 0.3, child: child),
// Show a loading indicator or message
if (!_showingSessionExpiredDialog)
const Center(child: CircularProgressIndicator()),
],
),
);
}
// Normal app flow
return GestureDetector(
onTap: () => _updateUserInteraction(),
onPanDown: (_) => _updateUserInteraction(),
onScaleStart: (_) => _updateUserInteraction(),
behavior: HitTestBehavior.translucent,
child: child!,
);
},
);
}
// Method untuk memperbarui timestamp interaksi pengguna
void _updateUserInteraction() {
debugPrint('App: User interaction detected');
SessionManager.updateLastUserInteraction();
}
}
// Custom navigator observer to clear SnackBars when navigating to new screens
class _SnackBarClearingNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPush(route, previousRoute);
_clearSnackBars();
}
@override
void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
super.didReplace(newRoute: newRoute, oldRoute: oldRoute);
_clearSnackBars();
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPop(route, previousRoute);
_clearSnackBars();
}
void _clearSnackBars() {
if (navigatorKey.currentContext != null) {
ScaffoldMessenger.of(navigatorKey.currentContext!).hideCurrentSnackBar();
ScaffoldMessenger.of(navigatorKey.currentContext!).clearSnackBars();
}
}
}
// Observer baru untuk mendeteksi navigasi pengguna sebagai bentuk interaksi
class _UserInteractionObserver extends NavigatorObserver {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPush(route, previousRoute);
debugPrint('App: Navigation interaction detected (push)');
SessionManager.updateLastUserInteraction();
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
super.didPop(route, previousRoute);
debugPrint('App: Navigation interaction detected (pop)');
SessionManager.updateLastUserInteraction();
}
}
// Add a utility function to show the debug FAB from anywhere
void showDebugFAB(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
// Use the navigator key to navigate safely
navigatorKey.currentState?.pushNamed('/image-test');
});
}