diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml index db3ff6b..6d51c54 100644 --- a/android/app/src/debug/AndroidManifest.xml +++ b/android/app/src/debug/AndroidManifest.xml @@ -6,5 +6,6 @@ + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 531d4a4..8ae7375 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + diff --git a/lib/main.dart b/lib/main.dart index 6529d15..e624408 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; + import 'package:marco/helpers/services/app_initializer.dart'; import 'package:marco/view/my_app.dart'; -import 'package:provider/provider.dart'; import 'package:marco/helpers/theme/app_notifier.dart'; import 'package:marco/helpers/services/app_logger.dart'; +import 'package:marco/view/layouts/offline_screen.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -18,11 +21,12 @@ Future main() async { runApp( ChangeNotifierProvider( create: (_) => AppNotifier(), - child: const MyApp(), + child: const MainWrapper(), ), ); } catch (e, stacktrace) { - logSafe('App failed to initialize.', + logSafe( + 'App failed to initialize.', level: LogLevel.error, error: e, stackTrace: stacktrace, @@ -42,3 +46,55 @@ Future main() async { ); } } + +/// This widget listens to connectivity changes and switches between +/// `MyApp` and `OfflineScreen` automatically. +class MainWrapper extends StatefulWidget { + const MainWrapper({super.key}); + + @override + State createState() => _MainWrapperState(); +} + +class _MainWrapperState extends State { + // Use a List to store connectivity status as the API now returns a list + List _connectivityStatus = [ConnectivityResult.none]; + final Connectivity _connectivity = Connectivity(); + + @override + void initState() { + super.initState(); + _initializeConnectivity(); + // Listen for changes, the callback now provides a List + _connectivity.onConnectivityChanged.listen((List results) { + setState(() { + _connectivityStatus = results; + }); + }); + } + + Future _initializeConnectivity() async { + // checkConnectivity() now returns a List + final result = await _connectivity.checkConnectivity(); + setState(() { + _connectivityStatus = result; + }); + } + + @override + Widget build(BuildContext context) { + // Check if any of the connectivity results indicate no internet + final bool isOffline = _connectivityStatus.contains(ConnectivityResult.none); + + // Show OfflineScreen if no internet + if (isOffline) { + return const MaterialApp( + debugShowCheckedModeBanner: false, + home: OfflineScreen(), + ); + } + + // Show main app if online + return const MyApp(); + } +} \ No newline at end of file diff --git a/lib/view/auth/mpin_auth_screen.dart b/lib/view/auth/mpin_auth_screen.dart index d1d332c..32343f7 100644 --- a/lib/view/auth/mpin_auth_screen.dart +++ b/lib/view/auth/mpin_auth_screen.dart @@ -168,10 +168,10 @@ class _MPINAuthScreenState extends State const SizedBox(height: 10), MyText( isChangeMpin - ? 'Set a new 6-digit MPIN for your account.' + ? 'Set a new 4-digit MPIN for your account.' : (isNewUser - ? 'Set your 6-digit MPIN for quick login.' - : 'Enter your 6-digit MPIN to continue.'), + ? 'Set your 4-digit MPIN for quick login.' + : 'Enter your 4-digit MPIN to continue.'), fontSize: 14, color: Colors.black54, textAlign: TextAlign.center, diff --git a/lib/view/dashboard/dashboard_screen.dart b/lib/view/dashboard/dashboard_screen.dart index ef0b8d6..99274eb 100644 --- a/lib/view/dashboard/dashboard_screen.dart +++ b/lib/view/dashboard/dashboard_screen.dart @@ -6,7 +6,6 @@ import 'package:marco/controller/project_controller.dart'; import 'package:marco/helpers/services/storage/local_storage.dart'; import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; import 'package:marco/helpers/utils/my_shadow.dart'; -// import 'package:marco/helpers/widgets/my_button.dart'; import 'package:marco/helpers/widgets/my_card.dart'; import 'package:marco/helpers/widgets/my_container.dart'; import 'package:marco/helpers/widgets/my_spacing.dart'; @@ -53,11 +52,10 @@ class _DashboardScreenState extends State with UIMixin { Widget build(BuildContext context) { return Layout( child: SingleChildScrollView( - padding: const EdgeInsets.all(12), + padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - MySpacing.height(12), _buildDashboardStats(), MySpacing.height(24), GetBuilder( @@ -80,64 +78,6 @@ class _DashboardScreenState extends State with UIMixin { ); }, ), - // if (!hasMpin) ...[ - // MyCard( - // borderRadiusAll: 12, - // paddingAll: 16, - // shadow: MyShadow(elevation: 2), - // color: Colors.red.withOpacity(0.05), - // child: Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // const Icon(Icons.warning_amber_rounded, - // color: Colors.redAccent, size: 28), - // MySpacing.width(12), - // Expanded( - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // MyText.bodyMedium( - // "MPIN Not Generated", - // color: Colors.redAccent, - // fontWeight: 700, - // ), - // MySpacing.height(4), - // MyText.bodySmall( - // "To secure your account, please generate your MPIN now.", - // color: contentTheme.onBackground.withOpacity(0.8), - // ), - // MySpacing.height(10), - // Align( - // alignment: Alignment.center, - // child: MyButton.rounded( - // onPressed: () { - // Get.toNamed("/auth/mpin-auth"); - // }, - // backgroundColor: contentTheme.brandRed, - // padding: const EdgeInsets.symmetric( - // horizontal: 20, vertical: 10), - // child: Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // const Icon(Icons.lock_outline, - // size: 18, color: Colors.white), - // MySpacing.width(8), - // MyText.bodyMedium( - // "Generate MPIN", - // color: Colors.white, - // fontWeight: 600, - // ), - // ], - // ), - // ), - // ), - // ], - // ), - // ), - // ], - // ), - // ), - // ], ], ), ), diff --git a/lib/view/layouts/offline_screen.dart b/lib/view/layouts/offline_screen.dart new file mode 100644 index 0000000..c5d4a77 --- /dev/null +++ b/lib/view/layouts/offline_screen.dart @@ -0,0 +1,156 @@ +import 'package:flutter/material.dart'; +import 'package:marco/helpers/utils/mixins/ui_mixin.dart'; +import 'package:marco/images.dart'; + +class OfflineScreen extends StatefulWidget { + const OfflineScreen({super.key}); + + @override + State createState() => _OfflineScreenState(); +} + +class _OfflineScreenState extends State + with SingleTickerProviderStateMixin, UIMixin { + late AnimationController _controller; + late Animation _logoAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 800), + ); + _logoAnimation = CurvedAnimation( + parent: _controller, + curve: Curves.easeOutBack, + ); + _controller.forward(); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + _RedWaveBackground(brandRed: contentTheme.brandRed), + SafeArea( + child: Center( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ScaleTransition( + scale: _logoAnimation, + child: Container( + width: 100, + height: 100, + decoration: BoxDecoration( + color: Colors.white, + shape: BoxShape.circle, + boxShadow: const [ + BoxShadow( + color: Colors.black12, + blurRadius: 10, + offset: Offset(0, 10), + ), + ], + ), + padding: const EdgeInsets.all(20), + child: Image.asset(Images.logoDark), + ), + ), + // Increased spacing here + const SizedBox(height: 120), + const Icon(Icons.wifi_off, + size: 100, color: Colors.redAccent), + const SizedBox(height: 20), + const Text( + "No Internet Connection", + style: TextStyle( + fontSize: 26, + fontWeight: FontWeight.bold, + color: Colors.black87, + ), + textAlign: TextAlign.center, + ), + const SizedBox(height: 15), + const Text( + "It seems you're currently offline. Please check your network settings or Wi-Fi connection.", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + + ], + ), + ), + ), + ), + ], + ), + ); + } +} + +class _RedWaveBackground extends StatelessWidget { + final Color brandRed; + const _RedWaveBackground({required this.brandRed}); + + @override + Widget build(BuildContext context) { + return CustomPaint( + painter: _WavePainter(brandRed), + size: Size.infinite, + ); + } +} + +class _WavePainter extends CustomPainter { + final Color brandRed; + + _WavePainter(this.brandRed); + + @override + void paint(Canvas canvas, Size size) { + final paint1 = Paint() + ..shader = LinearGradient( + colors: [brandRed, const Color.fromARGB(255, 97, 22, 22)], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ).createShader(Rect.fromLTWH(0, 0, size.width, size.height)); + + final path1 = Path() + ..moveTo(0, size.height * 0.2) + ..quadraticBezierTo(size.width * 0.25, size.height * 0.05, + size.width * 0.5, size.height * 0.15) + ..quadraticBezierTo( + size.width * 0.75, size.height * 0.25, size.width, size.height * 0.1) + ..lineTo(size.width, 0) + ..lineTo(0, 0) + ..close(); + + canvas.drawPath(path1, paint1); + + final paint2 = Paint()..color = Colors.redAccent.withOpacity(0.15); + final path2 = Path() + ..moveTo(0, size.height * 0.25) + ..quadraticBezierTo( + size.width * 0.4, size.height * 0.1, size.width, size.height * 0.2) + ..lineTo(size.width, 0) + ..lineTo(0, 0) + ..close(); + + canvas.drawPath(path2, paint2); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index fef9493..fd193f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -77,6 +77,7 @@ dependencies: html_editor_enhanced: ^2.7.0 flutter_quill_delta_from_html: ^1.5.2 quill_delta: ^3.0.0-nullsafety.2 + connectivity_plus: ^6.1.4 dev_dependencies: flutter_test: sdk: flutter