import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:miler/helpers/app_bootstrap.dart'; import 'package:miler/views/helpers/constants/Colorconstants.dart'; import 'package:miler/views/helpers/constants/design_constants.dart'; import 'package:miler/views/helpers/constants/miler_type.dart'; import 'package:miler/views/helpers/widgets/app_widgets.dart'; class OfflinePage extends StatefulWidget { const OfflinePage({super.key}); @override State createState() => _OfflinePageState(); } class _OfflinePageState extends State with SingleTickerProviderStateMixin { late final AnimationController _pulse; bool _checking = false; Timer? _autoRetry; @override void initState() { super.initState(); _pulse = AnimationController( vsync: this, duration: const Duration(milliseconds: 1800), )..repeat(reverse: true); // Automatically retry every 5 seconds. _autoRetry = Timer.periodic(const Duration(seconds: 5), (_) { if (mounted && !_checking) { _handleRetry(); } }); } @override void dispose() { _pulse.dispose(); _autoRetry?.cancel(); super.dispose(); } Future _handleRetry() async { if (_checking) return; setState(() => _checking = true); try { final results = await Connectivity().checkConnectivity(); final isOnline = results.isNotEmpty && results.any((r) => r != ConnectivityResult.none); if (isOnline) { Get.offAll(() => const AppBootstrap()); return; } if (mounted) { AppFeedback.info(context, "Still offline. We'll keep checking."); } } finally { if (mounted) { setState(() => _checking = false); } } } @override Widget build(BuildContext context) { final size = MediaQuery.of(context).size; return Scaffold( backgroundColor: const Color(0xffF7F8FC), body: SafeArea( child: Stack( children: [ Positioned( bottom: -size.height * .10, left: -40, right: -40, child: Container( height: size.height * .25, decoration: const BoxDecoration( color: ColorConstants.neutralLight, borderRadius: BorderRadius.only( topLeft: Radius.circular(250), topRight: Radius.circular(250), ), ), ), ), Center( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24), child: TweenAnimationBuilder( duration: const Duration(milliseconds: 700), tween: Tween(begin: 0, end: 1), curve: Curves.easeOut, builder: (context, value, child) { return Opacity( opacity: value, child: Transform.translate( offset: Offset(0, (1 - value) * 30), child: child, ), ); }, child: Container( padding: const EdgeInsets.all(28), decoration: BoxDecoration( color: ColorConstants.pureSurface, borderRadius: BorderRadius.circular(DesignConstants.radiusXl), boxShadow: [ BoxShadow( color: ColorConstants.slateText.withValues(alpha: .05), blurRadius: 25, offset: const Offset(0, 12), ), ], ), child: Column( mainAxisSize: MainAxisSize.min, children: [ AnimatedBuilder( animation: _pulse, builder: (_, child) { final t = Curves.easeInOut.transform(_pulse.value); return Container( width: 130, height: 130, decoration: BoxDecoration( shape: BoxShape.circle, color: ColorConstants.primary.withValues(alpha: .08), boxShadow: [ BoxShadow( color: ColorConstants.primary.withValues(alpha: .10 + (.15 * t), ), blurRadius: 25 + (18 * t), spreadRadius: 2 + (8 * t), ), ], ), child: child, ); }, child: Icon( Icons.wifi_off_rounded, color: ColorConstants.primary, size: 68, ), ), const SizedBox(height: 28), Text("You're offline", style: MilerType.hero), const SizedBox(height: 12), Text( "We can't reach Miler right now. Your shift will " "load as soon as you're back online.", textAlign: TextAlign.center, style: MilerType.caption, ), const SizedBox(height: 28), SizedBox( width: double.infinity, height: ButtonSizes.primary, child: ElevatedButton( onPressed: _checking ? null : _handleRetry, style: ElevatedButton.styleFrom( elevation: 0, backgroundColor: ColorConstants.primary, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( horizontal: 16, ), minimumSize: Size.zero, tapTargetSize: MaterialTapTargetSize.shrinkWrap, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular( ButtonSizes.radius, ), ), ), child: AnimatedSwitcher( duration: const Duration(milliseconds: 250), child: _checking ? const SizedBox( key: ValueKey("loading"), width: 22, height: 22, child: CircularProgressIndicator( strokeWidth: 2.5, color: Colors.white, ), ) : Text( "Try again", key: const ValueKey("retry"), style: MilerType.buttonLabel( ButtonSizes.primary, ), ), ), ), ), const SizedBox(height: 16), Text( "Checking every few seconds", style: MilerType.label, ), ], ), ), ), ), ), ], ), ), ); } }