feat: Add connectivity handling and offline screen, update MPIN to 4-digit support
This commit is contained in:
parent
9e4c0378c6
commit
982c81f849
@ -6,5 +6,6 @@
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
|
||||
</manifest>
|
||||
|
@ -6,6 +6,7 @@
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
|
||||
|
||||
|
||||
|
@ -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<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@ -18,11 +21,12 @@ Future<void> main() async {
|
||||
runApp(
|
||||
ChangeNotifierProvider<AppNotifier>(
|
||||
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<void> 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<MainWrapper> createState() => _MainWrapperState();
|
||||
}
|
||||
|
||||
class _MainWrapperState extends State<MainWrapper> {
|
||||
// Use a List to store connectivity status as the API now returns a list
|
||||
List<ConnectivityResult> _connectivityStatus = [ConnectivityResult.none];
|
||||
final Connectivity _connectivity = Connectivity();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_initializeConnectivity();
|
||||
// Listen for changes, the callback now provides a List<ConnectivityResult>
|
||||
_connectivity.onConnectivityChanged.listen((List<ConnectivityResult> results) {
|
||||
setState(() {
|
||||
_connectivityStatus = results;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _initializeConnectivity() async {
|
||||
// checkConnectivity() now returns a List<ConnectivityResult>
|
||||
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();
|
||||
}
|
||||
}
|
@ -168,10 +168,10 @@ class _MPINAuthScreenState extends State<MPINAuthScreen>
|
||||
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,
|
||||
|
@ -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<DashboardScreen> 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<ProjectController>(
|
||||
@ -80,64 +78,6 @@ class _DashboardScreenState extends State<DashboardScreen> 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,
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
156
lib/view/layouts/offline_screen.dart
Normal file
156
lib/view/layouts/offline_screen.dart
Normal file
@ -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<OfflineScreen> createState() => _OfflineScreenState();
|
||||
}
|
||||
|
||||
class _OfflineScreenState extends State<OfflineScreen>
|
||||
with SingleTickerProviderStateMixin, UIMixin {
|
||||
late AnimationController _controller;
|
||||
late Animation<double> _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;
|
||||
}
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user