This commit is contained in:
Phuoc Nguyen
2025-10-17 17:22:28 +07:00
parent 2125e85d40
commit 628c81ce13
86 changed files with 31339 additions and 1710 deletions

View File

@@ -1,122 +1,267 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(const MyApp());
import 'package:worker/app.dart';
import 'package:worker/core/database/hive_initializer.dart';
/// Main entry point of the Worker Mobile App
///
/// Initializes core dependencies:
/// - Hive database with adapters and boxes
/// - SharedPreferences for simple key-value storage
/// - Riverpod ProviderScope for state management
/// - Error handling boundaries
/// - System UI customization
void main() async {
// Ensure Flutter is initialized before async operations
WidgetsFlutterBinding.ensureInitialized();
// Set preferred device orientations
await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
// Initialize app with error handling
await _initializeApp();
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
/// Initialize all app dependencies with comprehensive error handling
Future<void> _initializeApp() async {
// Set up error handlers before anything else
_setupErrorHandlers();
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a purple toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
try {
// Initialize core dependencies in parallel for faster startup
await Future.wait([
_initializeHive(),
_initializeSharedPreferences(),
]);
// Run the app with Riverpod ProviderScope
runApp(
const ProviderScope(
child: WorkerApp(),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
} catch (error, stackTrace) {
// Critical initialization error - show error screen
debugPrint('Failed to initialize app: $error');
debugPrint('StackTrace: $stackTrace');
// Run minimal error app
runApp(_buildErrorApp(error, stackTrace));
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
/// Initialize Hive database
///
/// Sets up local database with:
/// - Type adapters for all models
/// - All required boxes (user, cart, products, etc.)
/// - Cache cleanup for expired data
/// - Encryption for sensitive data (in production)
Future<void> _initializeHive() async {
try {
debugPrint('Initializing Hive database...');
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
await HiveInitializer.initialize(
enableEncryption: kReleaseMode, // Enable encryption in release builds
verbose: kDebugMode, // Verbose logging in debug mode
);
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
debugPrint('Hive database initialized successfully');
} catch (error, stackTrace) {
debugPrint('Failed to initialize Hive: $error');
debugPrint('StackTrace: $stackTrace');
rethrow;
}
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
/// Initialize SharedPreferences
///
/// Used for simple key-value storage like:
/// - Last sync timestamp
/// - User preferences (language, theme)
/// - App settings
/// - Feature flags
Future<void> _initializeSharedPreferences() async {
try {
debugPrint('Initializing SharedPreferences...');
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
// Pre-initialize SharedPreferences instance
await SharedPreferences.getInstance();
debugPrint('SharedPreferences initialized successfully');
} catch (error, stackTrace) {
debugPrint('Failed to initialize SharedPreferences: $error');
debugPrint('StackTrace: $stackTrace');
rethrow;
}
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
/// Set up global error handlers
///
/// Captures and logs all Flutter framework errors and uncaught exceptions
void _setupErrorHandlers() {
// Handle Flutter framework errors
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
// Log to console in debug mode
if (kDebugMode) {
debugPrint('Flutter Error: ${details.exceptionAsString()}');
debugPrint('StackTrace: ${details.stack}');
}
// In production, you would send to crash analytics service
// Example: FirebaseCrashlytics.instance.recordFlutterError(details);
};
// Handle errors outside of Flutter framework
PlatformDispatcher.instance.onError = (error, stackTrace) {
if (kDebugMode) {
debugPrint('Platform Error: $error');
debugPrint('StackTrace: $stackTrace');
}
// In production, you would send to crash analytics service
// Example: FirebaseCrashlytics.instance.recordError(error, stackTrace);
return true; // Return true to indicate error was handled
};
// Handle zone errors (async errors not caught by Flutter)
runZonedGuarded(
() {
// App will run in this zone
},
(error, stackTrace) {
if (kDebugMode) {
debugPrint('Zone Error: $error');
debugPrint('StackTrace: $stackTrace');
}
// In production, you would send to crash analytics service
// Example: FirebaseCrashlytics.instance.recordError(error, stackTrace);
},
);
}
/// Build minimal error app when initialization fails
///
/// Shows a user-friendly error screen instead of crashing
Widget _buildErrorApp(Object error, StackTrace stackTrace) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: const Color(0xFFF5F5F5),
body: SafeArea(
child: Center(
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Error icon
const Icon(
Icons.error_outline,
size: 80,
color: Color(0xFFDC3545),
),
const SizedBox(height: 24),
// Error title
const Text(
'Không thể khởi động ứng dụng',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Color(0xFF212529),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 16),
// Error message
const Text(
'Đã xảy ra lỗi khi khởi động ứng dụng. '
'Vui lòng thử lại sau hoặc liên hệ hỗ trợ.',
style: TextStyle(
fontSize: 16,
color: Color(0xFF6C757D),
),
textAlign: TextAlign.center,
),
const SizedBox(height: 32),
// Error details (debug mode only)
if (kDebugMode) ...[
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFFFFF3CD),
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color(0xFFFFECB5),
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
'Debug Information:',
style: TextStyle(
fontWeight: FontWeight.bold,
color: Color(0xFF856404),
),
),
const SizedBox(height: 8),
Text(
error.toString(),
style: const TextStyle(
fontSize: 12,
color: Color(0xFF856404),
fontFamily: 'monospace',
),
),
],
),
),
const SizedBox(height: 16),
],
// Restart button
ElevatedButton.icon(
onPressed: () {
// Restart app
_initializeApp();
},
icon: const Icon(Icons.refresh),
label: const Text('Thử lại'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF005B9A),
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(
horizontal: 32,
vertical: 16,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
],
),
],
),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
),
);
}