From 2223667dbe43b544b467173a9e3b6779c9b252cb Mon Sep 17 00:00:00 2001 From: "$@#il" Date: Mon, 19 May 2025 19:57:14 +0530 Subject: [PATCH 1/3] made evacuatioin updates --- android/app/build.gradle | 29 +- android/gradle.properties | 5 +- bash.exe.stackdump | 29 + .../common/animated_background.dart | 760 +++++++++++++++++ .../models/navigation_item.dart | 30 + .../pages/emergency_contacts_screen.dart | 2 +- .../pages/evacuation_screen.dart | 256 ++++-- .../disaster_alerts/pages/home_screen.dart | 20 +- .../widgets/SideNavigation/nav_footer.dart | 185 +++++ .../widgets/SideNavigation/nav_header.dart | 364 +++++++++ .../widgets/SideNavigation/nav_item.dart | 360 ++++++++ .../SideNavigation/side_navigation.dart | 314 +++++++ .../widgets/evacuation_map.dart | 766 +++++++++++++++--- .../widgets/side_navigation.dart | 415 ---------- 14 files changed, 2943 insertions(+), 592 deletions(-) create mode 100644 bash.exe.stackdump create mode 100644 lib/features/disaster_alerts/common/animated_background.dart create mode 100644 lib/features/disaster_alerts/models/navigation_item.dart create mode 100644 lib/features/disaster_alerts/widgets/SideNavigation/nav_footer.dart create mode 100644 lib/features/disaster_alerts/widgets/SideNavigation/nav_header.dart create mode 100644 lib/features/disaster_alerts/widgets/SideNavigation/nav_item.dart create mode 100644 lib/features/disaster_alerts/widgets/SideNavigation/side_navigation.dart delete mode 100644 lib/features/disaster_alerts/widgets/side_navigation.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 25e6a0f..62b2530 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -8,6 +8,13 @@ plugins { id "dev.flutter.flutter-gradle-plugin" } +// Add this at the top of the file, before the android { ... } block +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + android { namespace = "com.example.disaster_management" compileSdk = flutter.compileSdkVersion @@ -23,10 +30,7 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId = "com.example.disaster_management" - // You can update the following values to match your application needs. - // For more information, see: https://flutter.dev/to/review-gradle-config. minSdk = flutter.minSdkVersion targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode @@ -36,6 +40,25 @@ android { 'com.google.firebase.auth.RecaptchaEnabled': 'true' ] } + + signingConfigs { + release { + keyAlias keystoreProperties['keyAlias'] + keyPassword keystoreProperties['keyPassword'] + storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null + storePassword keystoreProperties['storePassword'] + } + } + + buildTypes { + release { + // Removed duplicate applicationId and other properties that should only be in defaultConfig + signingConfig signingConfigs.release + minifyEnabled false // Change this to false + shrinkResources false // Add this line + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } } flutter { diff --git a/android/gradle.properties b/android/gradle.properties index 2597170..a3c9e29 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,3 +1,6 @@ -org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +org.gradle.daemon=true +org.gradle.parallel=true +org.gradle.configureondemand=true android.useAndroidX=true android.enableJetifier=true diff --git a/bash.exe.stackdump b/bash.exe.stackdump new file mode 100644 index 0000000..146949e --- /dev/null +++ b/bash.exe.stackdump @@ -0,0 +1,29 @@ +Stack trace: +Frame Function Args +0007FFFFBBD0 00021005FE8E (000210285F68, 00021026AB6E, 0007FFFFBBD0, 0007FFFFAAD0) msys-2.0.dll+0x1FE8E +0007FFFFBBD0 0002100467F9 (000000000000, 000000000000, 000000000000, 0007FFFFBEA8) msys-2.0.dll+0x67F9 +0007FFFFBBD0 000210046832 (000210286019, 0007FFFFBA88, 0007FFFFBBD0, 000000000000) msys-2.0.dll+0x6832 +0007FFFFBBD0 000210068CF6 (000000000000, 000000000000, 000000000000, 000000000000) msys-2.0.dll+0x28CF6 +0007FFFFBBD0 000210068E24 (0007FFFFBBE0, 000000000000, 000000000000, 000000000000) msys-2.0.dll+0x28E24 +0007FFFFBEB0 00021006A225 (0007FFFFBBE0, 000000000000, 000000000000, 000000000000) msys-2.0.dll+0x2A225 +End of stack trace +Loaded modules: +000100400000 bash.exe +7FFF4D5F0000 ntdll.dll +7FFF4CD40000 KERNEL32.DLL +7FFF4B080000 KERNELBASE.dll +7FFF4B6B0000 USER32.dll +7FFF4B4E0000 win32u.dll +7FFF4C690000 GDI32.dll +7FFF4AF30000 gdi32full.dll +7FFF4AE90000 msvcp_win.dll +7FFF4B510000 ucrtbase.dll +000210040000 msys-2.0.dll +7FFF4CC90000 advapi32.dll +7FFF4B610000 msvcrt.dll +7FFF4B850000 sechost.dll +7FFF4BF90000 RPCRT4.dll +7FFF4B050000 bcrypt.dll +7FFF49F20000 CRYPTBASE.DLL +7FFF4AD30000 bcryptPrimitives.dll +7FFF4CE10000 IMM32.DLL diff --git a/lib/features/disaster_alerts/common/animated_background.dart b/lib/features/disaster_alerts/common/animated_background.dart new file mode 100644 index 0000000..e9c3be6 --- /dev/null +++ b/lib/features/disaster_alerts/common/animated_background.dart @@ -0,0 +1,760 @@ +import 'package:flutter/material.dart'; +import 'dart:math' as math; + +class AnimatedBackground extends StatefulWidget { + final List colors; + final Duration duration; + final bool isEmergencyMode; + final AnimationStyle style; + + const AnimatedBackground({ + Key? key, + required this.colors, + this.duration = const Duration(seconds: 6), // Faster animation (was 10) + this.isEmergencyMode = false, + this.style = AnimationStyle.topographicMap, + }) : super(key: key); + + @override + State createState() => _AnimatedBackgroundState(); +} + +enum AnimationStyle { + topographicMap, + satelliteView, + navigationLines, + gridMap, +} + +class _AnimatedBackgroundState extends State + with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _pulseAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: widget.duration, + )..repeat(); + + _pulseAnimation = TweenSequence([ + TweenSequenceItem( + tween: Tween(begin: 1.0, end: 1.2), + weight: 1.0, + ), + TweenSequenceItem( + tween: Tween(begin: 1.2, end: 1.0), + weight: 1.0, + ), + ]).animate(CurvedAnimation( + parent: _controller, + curve: Curves.easeInOut, + )); + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RepaintBoundary( + child: AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: widget.colors, + stops: const [0.0, 0.5, 1.0], + begin: Alignment( + math.cos(_controller.value * 2 * math.pi) * 0.5 + 0.5, + math.sin(_controller.value * 2 * math.pi) * 0.5 + 0.5, + ), + end: Alignment( + math.cos((_controller.value + 0.5) * 2 * math.pi) * 0.5 + 0.5, + math.sin((_controller.value + 0.5) * 2 * math.pi) * 0.5 + 0.5, + ), + tileMode: TileMode.mirror, + ), + ), + child: Stack( + children: [ + // Advanced map background - INCREASED OPACITY for more visibility + Positioned.fill( + child: Opacity( + opacity: widget.isEmergencyMode ? 0.4 : 0.3, // Increased from 0.25/0.15 + child: CustomPaint( + painter: _getBackgroundPainter(), + ), + ), + ), + + // Emergency pulse effect + if (widget.isEmergencyMode) + Positioned.fill( + child: ScaleTransition( + scale: _pulseAnimation, + child: Container( + decoration: BoxDecoration( + gradient: RadialGradient( + colors: [ + Colors.red.withOpacity(0.3), + Colors.transparent, + ], + stops: const [0.0, 1.0], + center: Alignment.center, + radius: 1.5, + ), + ), + ), + ), + ), + + // Overlay gradient + Positioned.fill( + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black.withOpacity(0.1), + Colors.black.withOpacity(0.2), + ], + ), + ), + ), + ), + ], + ), + ); + }, + ), + ); + } + + CustomPainter _getBackgroundPainter() { + switch (widget.style) { + case AnimationStyle.topographicMap: + return TopographicMapPainter( + progress: _controller.value, + isEmergencyMode: widget.isEmergencyMode, + ); + case AnimationStyle.satelliteView: + return SatelliteViewPainter( + progress: _controller.value, + isEmergencyMode: widget.isEmergencyMode, + ); + case AnimationStyle.navigationLines: + return NavigationLinesPainter( + progress: _controller.value, + isEmergencyMode: widget.isEmergencyMode, + ); + case AnimationStyle.gridMap: + return GridMapPainter( + progress: _controller.value, + isEmergencyMode: widget.isEmergencyMode, + ); + default: + return TopographicMapPainter( + progress: _controller.value, + isEmergencyMode: widget.isEmergencyMode, + ); + } + } +} + +class TopographicMapPainter extends CustomPainter { + final double progress; + final bool isEmergencyMode; + + TopographicMapPainter({ + required this.progress, + this.isEmergencyMode = false, + }); + + @override + void paint(Canvas canvas, Size size) { + final baseColor = isEmergencyMode ? Colors.red : Colors.cyan; + + // Draw topographic contour lines + for (int i = 0; i < 8; i++) { + final contourPaint = Paint() + ..color = baseColor.withOpacity(0.1 + (i * 0.02)) + ..style = PaintingStyle.stroke + ..strokeWidth = 1.0 + ..strokeCap = StrokeCap.round; + + final scale = 1.0 + (i * 0.15); + final offset = progress * 20.0; + + final path = Path(); + + // Create organic, flowing contour lines + for (int j = 0; j < 5; j++) { + final startX = -size.width * 0.2 + (j * size.width * 0.3) + offset; + final startY = size.height * (0.3 + (i * 0.1)) + + (math.sin(progress * math.pi * 2 + (j * 0.5)) * 20.0); + + if (j == 0) { + path.moveTo(startX, startY); + } else { + final controlPoint1X = startX - (size.width * 0.1) + + (math.sin(progress * math.pi + j) * 10.0); + final controlPoint1Y = startY - (size.height * 0.05) + + (math.cos(progress * math.pi + j) * 10.0); + + final controlPoint2X = startX - (size.width * 0.05) + + (math.cos(progress * math.pi + j) * 10.0); + final controlPoint2Y = startY + (size.height * 0.05) + + (math.sin(progress * math.pi + j) * 10.0); + + path.cubicTo( + controlPoint1X, controlPoint1Y, + controlPoint2X, controlPoint2Y, + startX, startY + ); + } + } + + // Complete the path with a smooth curve back to start + path.quadraticBezierTo( + size.width * 1.2, + size.height * (0.3 + (i * 0.1)) + (math.sin(progress * math.pi) * 30.0), + size.width * 1.5, + size.height * (0.5 + (i * 0.05)) + ); + + canvas.drawPath(path, contourPaint); + } + + // Draw location markers + final markerPositions = [ + Offset(size.width * 0.2, size.height * 0.3), + Offset(size.width * 0.5, size.height * 0.6), + Offset(size.width * 0.8, size.height * 0.4), + Offset(size.width * 0.3, size.height * 0.7), + ]; + + for (var position in markerPositions) { + _drawLocationMarker(canvas, position, baseColor); + } + } + + void _drawLocationMarker(Canvas canvas, Offset position, Color color) { + // Pulsing effect based on progress + final scale = 1.0 + math.sin(progress * math.pi * 2) * 0.3; + + // Draw outer glow + for (int i = 5; i > 0; i--) { + final glowPaint = Paint() + ..color = color.withOpacity(0.03 * i) + ..style = PaintingStyle.fill; + + canvas.drawCircle( + position, + (6.0 + (i * 1.5)) * scale, + glowPaint + ); + } + + // Draw marker + final markerPaint = Paint() + ..color = color.withOpacity(0.8) + ..style = PaintingStyle.fill; + + canvas.drawCircle( + position, + 4.0 * scale, + markerPaint + ); + + // Draw inner highlight + final highlightPaint = Paint() + ..color = Colors.white.withOpacity(0.8) + ..style = PaintingStyle.fill; + + canvas.drawCircle( + Offset(position.dx - 1.0, position.dy - 1.0), + 1.5 * scale, + highlightPaint + ); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} + +class SatelliteViewPainter extends CustomPainter { + final double progress; + final bool isEmergencyMode; + + SatelliteViewPainter({ + required this.progress, + this.isEmergencyMode = false, + }); + + @override + void paint(Canvas canvas, Size size) { + final baseColor = isEmergencyMode ? Colors.red : Colors.cyan; + + // Draw satellite grid pattern + final gridPaint = Paint() + ..color = Colors.white.withOpacity(0.1) + ..style = PaintingStyle.stroke + ..strokeWidth = 0.5; + + final gridSize = 30.0; + final offset = progress * gridSize * 0.5; + + // Draw horizontal grid lines with wave effect + for (double i = -gridSize; i <= size.height + gridSize; i += gridSize) { + final path = Path(); + path.moveTo(0, i + offset); + + for (double x = 0; x <= size.width; x += 10) { + final waveHeight = math.sin((x / size.width) * math.pi * 4 + (progress * math.pi * 2)) * 5.0; + path.lineTo(x, i + offset + waveHeight); + } + + canvas.drawPath(path, gridPaint); + } + + // Draw vertical grid lines with wave effect + for (double i = -gridSize; i <= size.width + gridSize; i += gridSize) { + final path = Path(); + path.moveTo(i + offset, 0); + + for (double y = 0; y <= size.height; y += 10) { + final waveWidth = math.sin((y / size.height) * math.pi * 4 + (progress * math.pi * 2)) * 5.0; + path.lineTo(i + offset + waveWidth, y); + } + + canvas.drawPath(path, gridPaint); + } + + // Draw satellite scan line + final scanPaint = Paint() + ..color = baseColor.withOpacity(0.3) + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0; + + final scanY = size.height * progress; + canvas.drawLine( + Offset(0, scanY), + Offset(size.width, scanY), + scanPaint + ); + + // Draw scan glow + final scanGlowPaint = Paint() + ..shader = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + baseColor.withOpacity(0.2), + baseColor.withOpacity(0.0), + ], + ).createShader(Rect.fromLTWH(0, scanY - 20, size.width, 40)); + + canvas.drawRect( + Rect.fromLTWH(0, scanY - 20, size.width, 40), + scanGlowPaint + ); + + // Draw regions + final regions = [ + Rect.fromLTWH(size.width * 0.1, size.height * 0.2, size.width * 0.3, size.height * 0.2), + Rect.fromLTWH(size.width * 0.5, size.height * 0.3, size.width * 0.3, size.height * 0.3), + Rect.fromLTWH(size.width * 0.2, size.height * 0.6, size.width * 0.2, size.height * 0.2), + ]; + + for (var region in regions) { + final regionPaint = Paint() + ..color = baseColor.withOpacity(0.1) + ..style = PaintingStyle.fill; + + final borderPaint = Paint() + ..color = baseColor.withOpacity(0.3) + ..style = PaintingStyle.stroke + ..strokeWidth = 1.0; + + canvas.drawRect(region, regionPaint); + canvas.drawRect(region, borderPaint); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} + +class NavigationLinesPainter extends CustomPainter { + final double progress; + final bool isEmergencyMode; + + NavigationLinesPainter({ + required this.progress, + this.isEmergencyMode = false, + }); + + @override + void paint(Canvas canvas, Size size) { + final baseColor = isEmergencyMode ? Colors.red : Colors.cyan; + + // Draw animated navigation lines + final linePaint = Paint() + ..color = baseColor.withOpacity(0.4) + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..strokeCap = StrokeCap.round; + + // Create multiple paths + final paths = [ + _createNavigationPath(size, 0.0), + _createNavigationPath(size, 0.33), + _createNavigationPath(size, 0.66), + ]; + + for (var path in paths) { + // Draw glowing effect with INCREASED OPACITY + for (int i = 5; i > 0; i--) { + final glowPaint = Paint() + ..color = baseColor.withOpacity(0.05 * i) // Increased from 0.02 + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + (i * 2.0) // Slightly wider glow + ..strokeCap = StrokeCap.round; + + canvas.drawPath(path, glowPaint); + } + + // Draw the main path with INCREASED OPACITY + final linePaint = Paint() + ..color = baseColor.withOpacity(0.7) // Increased from 0.4 + ..style = PaintingStyle.stroke + ..strokeWidth = 2.5 // Slightly thicker + ..strokeCap = StrokeCap.round; + + canvas.drawPath(path, linePaint); + + // Draw animated dots along the path + _drawAnimatedDotsAlongPath(canvas, path, baseColor); + } + + // Draw intersection points + final intersections = [ + Offset(size.width * 0.3, size.height * 0.3), + Offset(size.width * 0.6, size.height * 0.5), + Offset(size.width * 0.4, size.height * 0.7), + ]; + + for (var point in intersections) { + _drawIntersectionPoint(canvas, point, baseColor); + } + } + + Path _createNavigationPath(Size size, double offset) { + final adjustedProgress = (progress + offset) % 1.0; + + final path = Path(); + path.moveTo(0, size.height * (0.3 + offset * 0.4)); + + path.cubicTo( + size.width * 0.3, + size.height * (0.2 + math.sin(adjustedProgress * math.pi * 2) * 0.1), + size.width * 0.6, + size.height * (0.7 + math.cos(adjustedProgress * math.pi * 2) * 0.1), + size.width, + size.height * (0.5 + offset * 0.2) + ); + + return path; + } + + void _drawAnimatedDotsAlongPath(Canvas canvas, Path path, Color color) { + final pathMetrics = path.computeMetrics().first; + final pathLength = pathMetrics.length; + + // Draw 5 dots along the path + for (int i = 0; i < 5; i++) { + final dotProgress = (progress + (i / 5)) % 1.0; + final dotPosition = pathMetrics.getTangentForOffset(pathLength * dotProgress); + + if (dotPosition != null) { + // Draw glow around dot + final glowPaint = Paint() + ..color = color.withOpacity(0.4) // Increased opacity + ..style = PaintingStyle.fill; + + canvas.drawCircle( + dotPosition.position, + 6.0, // Larger glow + glowPaint + ); + + // Draw dot + final dotPaint = Paint() + ..color = color.withOpacity(0.9) // More visible + ..style = PaintingStyle.fill; + + canvas.drawCircle( + dotPosition.position, + 4.0, // Larger dot (was 3.0) + dotPaint + ); + + // Draw trail + final trailPaint = Paint() + ..color = color.withOpacity(0.5) + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0; + + final trailPath = Path(); + trailPath.moveTo(dotPosition.position.dx, dotPosition.position.dy); + trailPath.lineTo( + dotPosition.position.dx - (dotPosition.vector.dx * 15), + dotPosition.position.dy - (dotPosition.vector.dy * 15) + ); + + canvas.drawPath(trailPath, trailPaint); + } + } + } + + void _drawIntersectionPoint(Canvas canvas, Offset position, Color color) { + // Pulsing effect + final scale = 1.0 + math.sin(progress * math.pi * 2) * 0.3; + + // Draw outer circle + final outerPaint = Paint() + ..color = color.withOpacity(0.3) + ..style = PaintingStyle.stroke + ..strokeWidth = 1.5; + + canvas.drawCircle( + position, + 10.0 * scale, + outerPaint + ); + + // Draw inner circle + final innerPaint = Paint() + ..color = color.withOpacity(0.7) + ..style = PaintingStyle.fill; + + canvas.drawCircle( + position, + 4.0 * scale, + innerPaint + ); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} + +class GridMapPainter extends CustomPainter { + final double progress; + final bool isEmergencyMode; + + GridMapPainter({ + required this.progress, + this.isEmergencyMode = false, + }); + + @override + void paint(Canvas canvas, Size size) { + final baseColor = isEmergencyMode ? Colors.red : Colors.cyan; + + // Draw base grid + final gridPaint = Paint() + ..color = Colors.white.withOpacity(0.1) + ..style = PaintingStyle.stroke + ..strokeWidth = 0.5; + + final gridSize = 40.0; + + // Draw horizontal grid lines + for (double i = 0; i <= size.height; i += gridSize) { + canvas.drawLine( + Offset(0, i), + Offset(size.width, i), + gridPaint + ); + } + + // Draw vertical grid lines + for (double i = 0; i <= size.width; i += gridSize) { + canvas.drawLine( + Offset(i, 0), + Offset(i, size.height), + gridPaint + ); + } + + // Draw animated "roads" or paths + final roadPaint = Paint() + ..color = baseColor.withOpacity(0.4) + ..style = PaintingStyle.stroke + ..strokeWidth = 3.0 + ..strokeCap = StrokeCap.round; + + // Main diagonal path + final path = Path(); + path.moveTo(0, size.height * 0.8); + path.quadraticBezierTo( + size.width * 0.3, + size.height * (0.2 + progress * 0.1), + size.width * 0.7, + size.height * (0.5 - progress * 0.1) + ); + path.quadraticBezierTo( + size.width * 0.9, + size.height * (0.7 + progress * 0.1), + size.width, + size.height * 0.2 + ); + + // Secondary path + final path2 = Path(); + path2.moveTo(0, size.height * 0.3); + path2.quadraticBezierTo( + size.width * 0.4, + size.height * (0.5 - progress * 0.1), + size.width * 0.6, + size.height * (0.2 + progress * 0.1) + ); + path2.quadraticBezierTo( + size.width * 0.8, + size.height * (0.4 - progress * 0.1), + size.width, + size.height * 0.7 + ); + + // Draw glowing effect for both paths + for (int i = 5; i > 0; i--) { + final glowPaint = Paint() + ..color = baseColor.withOpacity(0.03 * i) + ..style = PaintingStyle.stroke + ..strokeWidth = 3.0 + (6 - i) * 2.0; + + canvas.drawPath(path, glowPaint); + canvas.drawPath(path2, glowPaint); + } + + // Draw the main paths + canvas.drawPath(path, roadPaint); + canvas.drawPath(path2, roadPaint); + + // Draw animated dots along the paths + _drawAnimatedDotsAlongPath(canvas, path, baseColor); + _drawAnimatedDotsAlongPath(canvas, path2, baseColor); + + // Draw location markers + final markerPositions = [ + Offset(size.width * 0.2, size.height * 0.3), + Offset(size.width * 0.5, size.height * 0.6), + Offset(size.width * 0.8, size.height * 0.4), + ]; + + for (var position in markerPositions) { + _drawLocationMarker(canvas, position, baseColor); + } + + // Draw city/location blocks + final locations = [ + Rect.fromLTWH(size.width * 0.1, size.height * 0.2, gridSize, gridSize), + Rect.fromLTWH(size.width * 0.6, size.height * 0.3, gridSize * 1.5, gridSize), + Rect.fromLTWH(size.width * 0.3, size.height * 0.6, gridSize, gridSize * 1.5), + ]; + + for (var location in locations) { + final locationPaint = Paint() + ..color = baseColor.withOpacity(0.2) + ..style = PaintingStyle.fill; + + canvas.drawRect(location, locationPaint); + + final borderPaint = Paint() + ..color = baseColor.withOpacity(0.4) + ..style = PaintingStyle.stroke + ..strokeWidth = 1.0; + + canvas.drawRect(location, borderPaint); + } + } + + void _drawAnimatedDotsAlongPath(Canvas canvas, Path path, Color color) { + try { + final pathMetrics = path.computeMetrics().first; + final pathLength = pathMetrics.length; + + // Draw 3 dots along the path + for (int i = 0; i < 3; i++) { + final dotProgress = (progress + (i / 3)) % 1.0; + final dotPosition = pathMetrics.getTangentForOffset(pathLength * dotProgress); + + if (dotPosition != null) { + final dotPaint = Paint() + ..color = color + ..style = PaintingStyle.fill; + + canvas.drawCircle( + dotPosition.position, + 4.0, + dotPaint + ); + + // Draw glow + final glowPaint = Paint() + ..color = color.withOpacity(0.3) + ..style = PaintingStyle.fill; + + canvas.drawCircle( + dotPosition.position, + 8.0, + glowPaint + ); + } + } + } catch (e) { + // Handle empty path metrics + } + } + + void _drawLocationMarker(Canvas canvas, Offset position, Color color) { + // Pulsing effect based on progress + final scale = 1.0 + math.sin(progress * math.pi * 2) * 0.3; + + // Draw marker + final markerPaint = Paint() + ..color = color + ..style = PaintingStyle.fill; + + canvas.drawCircle( + position, + 4.0 * scale, + markerPaint + ); + + // Draw ripple effect + final ripplePaint = Paint() + ..color = color.withOpacity(0.2 - (0.2 * scale)) + ..style = PaintingStyle.stroke + ..strokeWidth = 1.0; + + canvas.drawCircle( + position, + 10.0 * scale, + ripplePaint + ); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} \ No newline at end of file diff --git a/lib/features/disaster_alerts/models/navigation_item.dart b/lib/features/disaster_alerts/models/navigation_item.dart new file mode 100644 index 0000000..78a595f --- /dev/null +++ b/lib/features/disaster_alerts/models/navigation_item.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +class NavigationItem { + final IconData icon; + final String title; + final String? badge; + final String? route; + final Color? iconColor; + final Color? badgeColor; + final bool hasAlert; + final AlertLevel alertLevel; + + NavigationItem({ + required this.icon, + required this.title, + this.badge, + this.route, + this.iconColor, + this.badgeColor, + this.hasAlert = false, + this.alertLevel = AlertLevel.none, + }); +} + +enum AlertLevel { + none, + low, + medium, + high, +} \ No newline at end of file diff --git a/lib/features/disaster_alerts/pages/emergency_contacts_screen.dart b/lib/features/disaster_alerts/pages/emergency_contacts_screen.dart index 79a61b5..0bae9ca 100644 --- a/lib/features/disaster_alerts/pages/emergency_contacts_screen.dart +++ b/lib/features/disaster_alerts/pages/emergency_contacts_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:convert'; -import 'package:disaster_management/features/disaster_alerts/widgets/side_navigation.dart'; +import 'package:disaster_management/features/disaster_alerts/widgets/SideNavigation/side_navigation.dart'; import 'package:disaster_management/features/disaster_alerts/constants/colors.dart'; import 'package:disaster_management/features/disaster_alerts/widgets/add_contact_form.dart'; import 'package:disaster_management/features/disaster_alerts/widgets/emergency_services_list.dart'; diff --git a/lib/features/disaster_alerts/pages/evacuation_screen.dart b/lib/features/disaster_alerts/pages/evacuation_screen.dart index 8179fe5..0dd2afe 100644 --- a/lib/features/disaster_alerts/pages/evacuation_screen.dart +++ b/lib/features/disaster_alerts/pages/evacuation_screen.dart @@ -7,7 +7,7 @@ import 'package:disaster_management/features/disaster_alerts/widgets/expanded_ma import 'package:disaster_management/features/disaster_alerts/widgets/headerComponent.dart'; import 'package:disaster_management/features/disaster_alerts/widgets/permission_denied_message.dart'; import 'package:disaster_management/features/disaster_alerts/widgets/place_type_selector.dart'; -import 'package:disaster_management/features/disaster_alerts/widgets/side_navigation.dart'; +import 'package:disaster_management/features/disaster_alerts/widgets/SideNavigation/side_navigation.dart'; import 'package:disaster_management/shared/widgets/app_scaffold.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -41,6 +41,26 @@ class _EvacuationScreenState extends State { String? _nextPageToken; final ScrollController _placesScrollController = ScrollController(); List _allPlaces = []; + bool _isMapExpanded = false; + + // Helper methods for route information + String _calculateRouteDistance() { + // In a real app, this would calculate the actual distance + // For now, return a placeholder value + return '3.2'; + } + + String _calculateRouteDuration() { + // In a real app, this would calculate the actual duration + // For now, return a placeholder value + return '12'; + } + + void _clearRoute() { + setState(() { + _polylines.clear(); + }); + } @override void initState() { @@ -278,9 +298,6 @@ Future _fetchNearbyPlaces() async { case PlaceType.fire: hue = BitmapDescriptor.hueOrange; break; - case PlaceType.hospital: - hue = BitmapDescriptor.hueGreen; - break; case PlaceType.shelter: hue = BitmapDescriptor.hueViolet; break; @@ -291,11 +308,167 @@ Future _fetchNearbyPlaces() async { return BitmapDescriptor.defaultMarkerWithHue(hue); } + void _toggleMapExpansion() { + setState(() { + _isMapExpanded = !_isMapExpanded; + }); + } + @override Widget build(BuildContext context) { final screenHeight = MediaQuery.of(context).size.height; final screenWidth = MediaQuery.of(context).size.width; + // If map is expanded, show full-screen map + if (_isMapExpanded) { + return Scaffold( + body: Stack( + children: [ + // Full-screen map - use entire screen + SizedBox( + width: MediaQuery.of(context).size.width, + height: MediaQuery.of(context).size.height, + child: _currentPosition == null + ? _buildLoadingIndicator() + : EvacuationMap( + currentPosition: _currentPosition!, + places: _places, + polylines: _polylines, + markers: _markers, + onMapCreated: (controller) { + mapController = controller; + }, + ), + ), + + // Back button + Positioned( + top: MediaQuery.of(context).padding.top + 16, + left: 16, + child: Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: _toggleMapExpansion, + ), + ), + ), + + // Add a floating route info panel at the bottom + if (_polylines.isNotEmpty) + Positioned( + left: 16, + right: 16, + bottom: 24, + child: Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: EvacuationColors.primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.directions, + color: EvacuationColors.primaryColor, + size: 24, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Route to Destination', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: EvacuationColors.textColor, + ), + ), + const SizedBox(height: 4), + Text( + '${_calculateRouteDistance()} km • ${_calculateRouteDuration()} min', + style: TextStyle( + fontSize: 14, + color: EvacuationColors.subtitleColor, + ), + ), + ], + ), + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: _clearRoute, + color: Colors.red[400], + ), + ], + ), + const SizedBox(height: 16), + Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () { + // Start navigation logic + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Navigation started'), + behavior: SnackBarBehavior.floating, + ), + ); + }, + icon: const Icon(Icons.navigation), + label: const Text('Start Navigation'), + style: ElevatedButton.styleFrom( + backgroundColor: EvacuationColors.primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ); + } + + // Regular view with small map return Stack( children: [ AppScaffold( @@ -367,16 +540,19 @@ Future _fetchNearbyPlaces() async { ? _buildPermissionDeniedMessage() : _currentPosition == null ? _buildLoadingIndicator() - : ClipRRect( - borderRadius: BorderRadius.circular(24), - child: EvacuationMap( - currentPosition: _currentPosition!, - places: _places, - polylines: _polylines, - markers: _markers, // Pass the markers - onMapCreated: (controller) { - mapController = controller; - }, + : GestureDetector( + onTap: _toggleMapExpansion, + child: ClipRRect( + borderRadius: BorderRadius.circular(24), + child: EvacuationMap( + currentPosition: _currentPosition!, + places: _places, + polylines: _polylines, + markers: _markers, // Pass the markers + onMapCreated: (controller) { + mapController = controller; + }, + ), ), ), // Map controls remain the same @@ -546,47 +722,7 @@ Future _fetchNearbyPlaces() async { } void _showExpandedMap() { - showModalBottomSheet( - context: context, - isScrollControlled: true, - backgroundColor: Colors.transparent, - builder: (context) => DraggableScrollableSheet( - initialChildSize: 1.0, - minChildSize: 0.5, - builder: (_, controller) => Container( - decoration: BoxDecoration( - color: EvacuationColors.cardBackground, - borderRadius: const BorderRadius.vertical(top: Radius.circular(28)), - ), - child: Column( - children: [ - Container( - width: 40, - height: 4, - margin: const EdgeInsets.symmetric(vertical: 12), - decoration: BoxDecoration( - color: EvacuationColors.textColor.withOpacity(0.1), - borderRadius: BorderRadius.circular(2), - ), - ), - Expanded( - child: ClipRRect( - borderRadius: - const BorderRadius.vertical(top: Radius.circular(28)), - child: EvacuationMap( - currentPosition: _currentPosition!, - places: _places, - polylines: _polylines, - markers: _markers, // Pass the markers - onMapCreated: (controller) => mapController = controller, - ), - ), - ), - ], - ), - ), - ), - ); + _toggleMapExpansion(); } Widget _buildMapButton({ @@ -1135,7 +1271,15 @@ Future _fetchNearbyPlaces() async { _polylines.clear(); _polylines.add(routePolyline); }); -// Remove the _getBoundsForRoute method and update where it's used + + // If map is expanded, show the route info panel + if (_isMapExpanded) { + // Already showing the route info panel via the if condition in the UI + } else { + // Consider expanding the map to show the route + _toggleMapExpansion(); + } + mapController.animateCamera( CameraUpdate.newLatLngBounds( MapBoundsCalculator.getRouteLatLngBounds(polylinePoints), diff --git a/lib/features/disaster_alerts/pages/home_screen.dart b/lib/features/disaster_alerts/pages/home_screen.dart index 5200c92..d4e0156 100644 --- a/lib/features/disaster_alerts/pages/home_screen.dart +++ b/lib/features/disaster_alerts/pages/home_screen.dart @@ -1,5 +1,5 @@ import 'package:disaster_management/core/constants/app_colors.dart'; -import 'package:disaster_management/features/disaster_alerts/widgets/side_navigation.dart'; +import 'package:disaster_management/features/disaster_alerts/widgets/SideNavigation/side_navigation.dart'; import 'package:disaster_management/features/disaster_alerts/widgets/alert_card.dart'; import 'package:disaster_management/features/disaster_alerts/widgets/current_weather_card.dart'; import 'package:disaster_management/features/disaster_alerts/widgets/disaster_declaration_card.dart'; @@ -58,24 +58,6 @@ class _CombinedHomeWeatherComponentState WidgetsBinding.instance.addObserver(this); } - // Add method to connect to socket and register user - // void _connectAndRegisterWithSocket() { - // debugPrint('Connecting to socket server from home screen...'); - - // // First connect to the socket - // _socketService.connectSocket(); - - // // Then register the user after a short delay to ensure connection is established - // Future.delayed(const Duration(seconds: 2), () { - // debugPrint('Registering user with socket server...'); - // _socketService.registerUser(); - - // // Request active disasters after registration - // Future.delayed(const Duration(seconds: 1), () { - // _socketService.requestActiveDisasters(); - // }); - // }); - // } @override void dispose() { diff --git a/lib/features/disaster_alerts/widgets/SideNavigation/nav_footer.dart b/lib/features/disaster_alerts/widgets/SideNavigation/nav_footer.dart new file mode 100644 index 0000000..7580831 --- /dev/null +++ b/lib/features/disaster_alerts/widgets/SideNavigation/nav_footer.dart @@ -0,0 +1,185 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../../constants/colors.dart'; + +class NavFooter extends StatefulWidget { + final VoidCallback? onLogoutTap; + + const NavFooter({ + Key? key, + this.onLogoutTap, + }) : super(key: key); + + @override + State createState() => _NavFooterState(); +} + +class _NavFooterState extends State with SingleTickerProviderStateMixin { + late AnimationController _hoverController; + bool _isHovering = false; + + @override + void initState() { + super.initState(); + _hoverController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 200), + ); + } + + @override + void dispose() { + _hoverController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + padding: EdgeInsets.only( + left: 20, + right: 20, + bottom: MediaQuery.of(context).padding.bottom + 16, + top: 16, + ), + decoration: BoxDecoration( + color: EvacuationColors.cardBackground, + boxShadow: [ + BoxShadow( + color: EvacuationColors.shadowColor.withOpacity(0.1), + blurRadius: 10, + offset: const Offset(0, -5), + ), + ], + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(24), + topRight: Radius.circular(24), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildAppVersion(), + const SizedBox(height: 16), + _buildLogoutButton(), + ], + ), + ); + } + + Widget _buildLogoutButton() { + return MouseRegion( + onEnter: (_) { + setState(() { + _isHovering = true; + }); + _hoverController.forward(); + }, + onExit: (_) { + setState(() { + _isHovering = false; + }); + _hoverController.reverse(); + }, + child: Material( + color: Colors.transparent, + borderRadius: BorderRadius.circular(16), + child: InkWell( + onTap: () { + if (widget.onLogoutTap != null) { + HapticFeedback.mediumImpact(); + widget.onLogoutTap!(); + } + }, + borderRadius: BorderRadius.circular(16), + splashColor: Colors.red.withOpacity(0.1), + highlightColor: Colors.red.withOpacity(0.05), + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + decoration: BoxDecoration( + color: _isHovering ? Colors.red.withOpacity(0.05) : Colors.transparent, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: Colors.red.withOpacity(_isHovering ? 0.3 : 0.1), + width: 1.5, + ), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: Colors.red.withOpacity(_isHovering ? 0.2 : 0.1), + borderRadius: BorderRadius.circular(12), + boxShadow: _isHovering ? [ + BoxShadow( + color: Colors.red.withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ] : null, + ), + child: const Icon( + Icons.logout_rounded, + color: Colors.red, + size: 22, + ), + ), + const SizedBox(width: 16), + Text( + 'Logout', + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.red, + ), + ), + const Spacer(), + AnimatedContainer( + duration: const Duration(milliseconds: 300), + transform: Matrix4.translationValues( + _isHovering ? 5.0 : 0.0, 0.0, 0.0), + child: const Icon( + Icons.arrow_forward_ios_rounded, + color: Colors.red, + size: 16, + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildAppVersion() { + return Align( + alignment: Alignment.center, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: EvacuationColors.borderColor.withOpacity(0.5), + borderRadius: BorderRadius.circular(20), + boxShadow: [ + BoxShadow( + color: EvacuationColors.shadowColor.withOpacity(0.05), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + 'App Version 1.0.0', + style: GoogleFonts.poppins( + color: EvacuationColors.subtitleColor, + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/disaster_alerts/widgets/SideNavigation/nav_header.dart b/lib/features/disaster_alerts/widgets/SideNavigation/nav_header.dart new file mode 100644 index 0000000..5f4cd6b --- /dev/null +++ b/lib/features/disaster_alerts/widgets/SideNavigation/nav_header.dart @@ -0,0 +1,364 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../../constants/colors.dart'; +import '../../common/animated_background.dart'; + +class NavHeader extends StatefulWidget { + final String userName; + final String? userEmail; + final String? userAvatar; + final VoidCallback? onProfileTap; + final bool isEmergencyMode; + + const NavHeader({ + Key? key, + required this.userName, + this.userEmail, + this.userAvatar, + this.onProfileTap, + this.isEmergencyMode = false, + }) : super(key: key); + + @override + State createState() => _NavHeaderState(); +} + +class _NavHeaderState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _pulseAnimation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 2000), + )..repeat(reverse: true); + + _pulseAnimation = Tween(begin: 1.0, end: 1.15).animate( + CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + ), + ); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + height: 200, + width: double.infinity, + child: Stack( + children: [ + // Map-themed animated background + AnimatedBackground( + colors: widget.isEmergencyMode + ? [ + Color(0xFF7D0000), + Color(0xFFAA0000), + Color(0xFF7D0000), + ] + : [ + EvacuationColors.primaryColor, + EvacuationColors.primaryColor.withBlue(EvacuationColors.primaryColor.blue + 40), + EvacuationColors.accentColor, + ], + isEmergencyMode: widget.isEmergencyMode, + ), + + // Custom map elements overlay + Positioned.fill( + child: CustomPaint( + painter: NavigationLinesPainter( + progress: _animationController.value, + isEmergencyMode: widget.isEmergencyMode, + ), + ), + ), + + // Content + Positioned.fill( + child: Padding( + padding: EdgeInsets.only( + top: MediaQuery.of(context).padding.top + 20, + bottom: 20, + left: 20, + right: 20, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Profile section with GPS beacon effect + GestureDetector( + onTap: widget.onProfileTap, + onTapDown: (_) { + HapticFeedback.selectionClick(); + }, + child: Row( + children: [ + // Avatar with pulsing effect + AnimatedBuilder( + animation: _animationController, + builder: (context, child) { + return Stack( + alignment: Alignment.center, + children: [ + // Outer pulse + Transform.scale( + scale: _pulseAnimation.value, + child: Container( + width: 70, + height: 70, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: widget.isEmergencyMode + ? Colors.red.withOpacity(0.2) + : Colors.cyan.withOpacity(0.2), + ), + ), + ), + // Middle pulse + Transform.scale( + scale: _pulseAnimation.value * 0.9, + child: Container( + width: 60, + height: 60, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: widget.isEmergencyMode + ? Colors.red.withOpacity(0.3) + : Colors.cyan.withOpacity(0.3), + ), + ), + ), + // Avatar + Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: widget.isEmergencyMode + ? Colors.red.withOpacity(0.8) + : Colors.white, + width: 2, + ), + boxShadow: [ + BoxShadow( + color: widget.isEmergencyMode + ? Colors.red.withOpacity(0.5) + : Colors.cyan.withOpacity(0.5), + blurRadius: 10, + spreadRadius: 2, + ), + ], + ), + child: CircleAvatar( + radius: 25, + backgroundColor: Colors.white, + backgroundImage: widget.userAvatar != null + ? NetworkImage(widget.userAvatar!) + : null, + child: widget.userAvatar == null + ? Text( + widget.userName[0].toUpperCase(), + style: GoogleFonts.poppins( + fontSize: 20, + fontWeight: FontWeight.bold, + color: EvacuationColors.primaryColor, + ), + ) + : null, + ), + ), + ], + ); + }, + ), + + const SizedBox(width: 16), + + // User info + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.userName, + style: GoogleFonts.poppins( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (widget.userEmail != null) + Text( + widget.userEmail!, + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.white.withOpacity(0.8), + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + + // Location indicator + Container( + margin: const EdgeInsets.only(top: 4), + padding: const EdgeInsets.symmetric( + horizontal: 8, + vertical: 4, + ), + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.3), + borderRadius: BorderRadius.circular(12), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon( + Icons.location_on, + color: widget.isEmergencyMode + ? Colors.red + : Colors.cyan, + size: 12, + ), + const SizedBox(width: 4), + Text( + 'Current Location', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.white.withOpacity(0.9), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + + const Spacer(), + + // View Profile button + InkWell( + onTap: widget.onProfileTap, + borderRadius: BorderRadius.circular(20), + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(20), + border: Border.all( + color: Colors.white.withOpacity(0.3), + width: 1, + ), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'View Profile', + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + const SizedBox(width: 4), + const Icon( + Icons.arrow_forward_ios_rounded, + size: 12, + color: Colors.white, + ), + ], + ), + ), + ), + ], + ), + ), + ), + + // Bottom wave decoration + Positioned( + bottom: 0, + left: 0, + right: 0, + child: SizedBox( + height: 20, + child: ClipPath( + clipper: WaveClipper(), + child: Container( + color: Colors.white, + ), + ), + ), + ), + ], + ), + ); + } +} + +// Wave clipper for the bottom decoration +class WaveClipper extends CustomClipper { + @override + Path getClip(Size size) { + final path = Path(); + path.lineTo(0, 0); + + // First wave + final firstControlPoint = Offset(size.width / 4, size.height); + final firstEndPoint = Offset(size.width / 2.25, size.height / 2); + path.quadraticBezierTo( + firstControlPoint.dx, + firstControlPoint.dy, + firstEndPoint.dx, + firstEndPoint.dy, + ); + + // Second wave + final secondControlPoint = Offset(size.width / 1.8, 0); + final secondEndPoint = Offset(size.width / 1.25, size.height / 2); + path.quadraticBezierTo( + secondControlPoint.dx, + secondControlPoint.dy, + secondEndPoint.dx, + secondEndPoint.dy, + ); + + // Third wave + final thirdControlPoint = Offset(size.width / 1.1, size.height); + final thirdEndPoint = Offset(size.width, 0); + path.quadraticBezierTo( + thirdControlPoint.dx, + thirdControlPoint.dy, + thirdEndPoint.dx, + thirdEndPoint.dy, + ); + + path.lineTo(size.width, size.height); + path.lineTo(0, size.height); + path.close(); + + return path; + } + + @override + bool shouldReclip(CustomClipper oldClipper) => false; +} \ No newline at end of file diff --git a/lib/features/disaster_alerts/widgets/SideNavigation/nav_item.dart b/lib/features/disaster_alerts/widgets/SideNavigation/nav_item.dart new file mode 100644 index 0000000..43f905b --- /dev/null +++ b/lib/features/disaster_alerts/widgets/SideNavigation/nav_item.dart @@ -0,0 +1,360 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import '../../models/navigation_item.dart'; +import '../../constants/colors.dart'; + +class NavItem extends StatefulWidget { + final NavigationItem item; + final bool isSelected; + final int index; + final VoidCallback onTap; + + const NavItem({ + Key? key, + required this.item, + required this.isSelected, + required this.index, + required this.onTap, + }) : super(key: key); + + @override + State createState() => _NavItemState(); +} + +class _NavItemState extends State with SingleTickerProviderStateMixin { + late AnimationController _controller; + late Animation _scaleAnimation; + late Animation _iconScaleAnimation; + late Animation _alertPulseAnimation; + + @override + void initState() { + super.initState(); + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 300), + ); + + _scaleAnimation = Tween(begin: 1.0, end: 0.97).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.easeOutCubic, + ), + ); + + _iconScaleAnimation = Tween(begin: 1.0, end: 1.1).animate( + CurvedAnimation( + parent: _controller, + curve: Curves.elasticOut, + ), + ); + + _alertPulseAnimation = TweenSequence([ + TweenSequenceItem( + tween: Tween(begin: 1.0, end: 1.2), + weight: 1.0, + ), + TweenSequenceItem( + tween: Tween(begin: 1.2, end: 1.0), + weight: 1.0, + ), + ]).animate(CurvedAnimation( + parent: _controller, + curve: Curves.easeInOut, + )); + + if (widget.item.hasAlert) { + _controller.repeat(reverse: true); + } + } + + @override + void didUpdateWidget(NavItem oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.isSelected != oldWidget.isSelected) { + if (widget.isSelected) { + _controller.forward(from: 0).then((_) => _controller.reverse()); + } + } + + if (widget.item.hasAlert != oldWidget.item.hasAlert) { + if (widget.item.hasAlert) { + _controller.repeat(reverse: true); + } else { + _controller.stop(); + } + } + } + + Color _getAlertColor() { + switch (widget.item.alertLevel) { + case AlertLevel.high: + return Colors.red; + case AlertLevel.medium: + return Colors.orange; + case AlertLevel.low: + return Colors.yellow; + default: + return Colors.transparent; + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return TweenAnimationBuilder( + tween: Tween(begin: 0.0, end: 1.0), + duration: Duration(milliseconds: 400 + (widget.index * 100)), + curve: Curves.easeOutQuint, + builder: (context, value, child) { + return Transform.translate( + offset: Offset(30 * (1 - value), 0), + child: Opacity( + opacity: value, + child: child, + ), + ); + }, + child: Padding( + padding: const EdgeInsets.only(bottom: 8), + child: GestureDetector( + onTap: () { + HapticFeedback.selectionClick(); + widget.onTap(); + }, + onTapDown: (_) => _controller.forward(), + onTapUp: (_) => _controller.reverse(), + onTapCancel: () => _controller.reverse(), + child: AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Transform.scale( + scale: _scaleAnimation.value, + child: child, + ); + }, + child: Stack( + children: [ + _buildNavItemContent(), + if (widget.item.hasAlert) + Positioned( + right: 0, + top: 0, + child: AnimatedBuilder( + animation: _alertPulseAnimation, + builder: (context, child) { + return Transform.scale( + scale: _alertPulseAnimation.value, + child: Container( + width: 12, + height: 12, + decoration: BoxDecoration( + color: _getAlertColor(), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: _getAlertColor().withOpacity(0.5), + blurRadius: 6, + spreadRadius: 2, + ), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ), + ), + ), + ); + } + + Widget _buildNavItemContent() { + final alertColor = _getAlertColor(); + final hasAlert = widget.item.hasAlert; + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), + decoration: BoxDecoration( + color: widget.isSelected + ? (hasAlert ? alertColor.withOpacity(0.1) : EvacuationColors.primaryColor.withOpacity(0.1)) + : Colors.grey.withOpacity(0.07), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: widget.isSelected + ? (hasAlert ? alertColor : EvacuationColors.primaryColor) + : Colors.transparent, + width: 1.5, + ), + boxShadow: widget.isSelected + ? [ + BoxShadow( + color: (hasAlert ? alertColor : EvacuationColors.primaryColor).withOpacity(0.2), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ] + : null, + ), + child: Row( + children: [ + // Icon container with map-themed animation + AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Stack( + alignment: Alignment.center, + children: [ + // Animated background for selected items + if (widget.isSelected) + Transform.scale( + scale: _iconScaleAnimation.value * 0.9, + child: Container( + width: 44, + height: 44, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: (hasAlert ? alertColor : EvacuationColors.primaryColor).withOpacity(0.2), + ), + ), + ), + + // Icon container + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: widget.isSelected + ? (hasAlert ? alertColor : EvacuationColors.primaryColor) + : Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: widget.isSelected + ? (hasAlert ? alertColor : EvacuationColors.primaryColor).withOpacity(0.3) + : Colors.black.withOpacity(0.05), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Icon( + widget.item.icon, + color: widget.isSelected + ? Colors.white + : (hasAlert ? alertColor : widget.item.iconColor ?? EvacuationColors.primaryColor), + size: 24, + ), + ), + + // Ripple effect for selected items + if (widget.isSelected) + ...List.generate(2, (index) { + final delay = index * 0.4; + final progress = (_controller.value - delay) % 1.0; + + // Only show if within the visible range + if (progress < 0) return const SizedBox(); + + return Transform.scale( + scale: 0.5 + (progress * 0.8), + child: Opacity( + opacity: (1.0 - progress) * 0.4, + child: Container( + width: 44, + height: 44, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: hasAlert ? alertColor : EvacuationColors.primaryColor, + width: 2, + ), + ), + ), + ), + ); + }), + ], + ); + }, + ), + + const SizedBox(width: 16), + + // Title with animated underline for selected items + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.item.title, + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: widget.isSelected + ? FontWeight.w600 + : FontWeight.w500, + color: widget.isSelected + ? (hasAlert ? alertColor : EvacuationColors.primaryColor) + : EvacuationColors.textColor, + ), + ), + + // Animated underline for selected items + if (widget.isSelected) + AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return Container( + margin: const EdgeInsets.only(top: 4), + height: 2, + width: 20 + (30 * _controller.value), + decoration: BoxDecoration( + color: hasAlert ? alertColor : EvacuationColors.primaryColor, + borderRadius: BorderRadius.circular(1), + ), + ); + }, + ), + ], + ), + ), + + // Badge or distance indicator + if (widget.item.badge != null) + AnimatedContainer( + duration: const Duration(milliseconds: 300), + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: widget.item.badgeColor ?? EvacuationColors.primaryColor, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: (widget.item.badgeColor ?? EvacuationColors.primaryColor) + .withOpacity(0.3), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], + ), + child: Text( + widget.item.badge!, + style: GoogleFonts.poppins( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/features/disaster_alerts/widgets/SideNavigation/side_navigation.dart b/lib/features/disaster_alerts/widgets/SideNavigation/side_navigation.dart new file mode 100644 index 0000000..22cfa8b --- /dev/null +++ b/lib/features/disaster_alerts/widgets/SideNavigation/side_navigation.dart @@ -0,0 +1,314 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:lottie/lottie.dart'; + +import 'nav_header.dart'; +import 'nav_item.dart'; +import 'nav_footer.dart'; +import '../../common/animated_background.dart'; +import '../../models/navigation_item.dart'; +import '../../constants/colors.dart'; + +class SideNavigation extends StatefulWidget { + final String userName; + final String? userEmail; + final String? userAvatar; + final VoidCallback? onClose; + final int initialSelectedIndex; + final Function(int index)? onItemSelected; + + const SideNavigation({ + Key? key, + required this.userName, + this.userEmail, + this.userAvatar, + this.onClose, + this.initialSelectedIndex = 0, + this.onItemSelected, + }) : super(key: key); + + @override + State createState() => _SideNavigationState(); +} + +class _SideNavigationState extends State + with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late int _selectedIndex; + final ScrollController _scrollController = ScrollController(); + final List _navItems = []; + + @override + void initState() { + super.initState(); + _selectedIndex = widget.initialSelectedIndex; + + // Animation controller for entrance and exit animations + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 400), + ); + _animationController.forward(); + + // Initialize navigation items + _initNavigationItems(); + } + + void _initNavigationItems() { + _navItems.addAll([ + NavigationItem( + icon: Icons.home_rounded, + title: 'Home', + route: '/home', + ), + NavigationItem( + icon: Icons.notifications_rounded, + title: 'Notifications', + badge: '3', + route: '/notifications', + ), + NavigationItem( + icon: Icons.shield_rounded, + title: 'Emergency Contacts', + route: '/emergency_contacts', + ), + NavigationItem( + icon: Icons.directions_run, + title: 'Evacuation', + route: '/evacuation', + ), + NavigationItem( + icon: Icons.history_rounded, + title: 'History', + route: '/history', + ), + NavigationItem( + icon: Icons.settings_rounded, + title: 'Settings', + route: '/settings', + ), + ]); + } + + @override + void dispose() { + _animationController.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + void _handleItemTap(int index) { + if (_selectedIndex == index) return; + + HapticFeedback.selectionClick(); + setState(() { + _selectedIndex = index; + }); + + if (widget.onItemSelected != null) { + widget.onItemSelected!(index); + } + + // Navigate to the selected route + final route = _navItems[index].route; + if (route != null) { + Navigator.pop(context); + Navigator.pushReplacementNamed(context, route); + } + } + + @override + Widget build(BuildContext context) { + return Drawer( + backgroundColor: EvacuationColors.backgroundColor, + elevation: 0, + child: AnimatedBuilder( + animation: _animationController, + builder: (context, child) { + return SlideTransition( + position: Tween( + begin: const Offset(-0.2, 0), + end: Offset.zero, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeOutCubic, + )), + child: FadeTransition( + opacity: _animationController, + child: child, + ), + ); + }, + child: Column( + children: [ + // Header with user profile + NavHeader( + userName: widget.userName, + userEmail: widget.userEmail, + userAvatar: widget.userAvatar, + onProfileTap: () { + Navigator.pop(context); + Navigator.pushNamed(context, '/profile'); + }, + ), + + // Navigation items + Expanded( + child: Theme( + data: Theme.of(context).copyWith( + scrollbarTheme: ScrollbarThemeData( + thumbColor: MaterialStateProperty.all( + EvacuationColors.primaryColor.withOpacity(0.5), + ), + radius: const Radius.circular(10), + thickness: MaterialStateProperty.all(5), + ), + ), + child: Scrollbar( + controller: _scrollController, + thumbVisibility: true, + child: SingleChildScrollView( + controller: _scrollController, + physics: const BouncingScrollPhysics(), + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 12, + ), + child: Column( + children: List.generate( + _navItems.length, + (index) => NavItem( + item: _navItems[index], + isSelected: _selectedIndex == index, + index: index, + onTap: () => _handleItemTap(index), + ), + ), + ), + ), + ), + ), + ), + + // Footer with logout button + NavFooter( + onLogoutTap: () { + HapticFeedback.mediumImpact(); + // Show confirmation dialog + showModalBottomSheet( + context: context, + backgroundColor: Colors.transparent, + isScrollControlled: true, + builder: (context) => _buildLogoutConfirmation(context), + ); + }, + ), + ], + ), + ), + ); + } + + Widget _buildLogoutConfirmation(BuildContext context) { + return Container( + height: MediaQuery.of(context).size.height * 0.25, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + spreadRadius: 1, + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: const EdgeInsets.only(top: 12), + width: 50, + height: 5, + decoration: BoxDecoration( + color: Colors.grey.withOpacity(0.3), + borderRadius: BorderRadius.circular(10), + ), + ), + const SizedBox(height: 20), + Text( + 'Logout', + style: GoogleFonts.poppins( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + const SizedBox(height: 12), + Text( + 'Are you sure you want to logout?', + style: GoogleFonts.poppins( + fontSize: 16, + color: Colors.black87, + ), + ), + const SizedBox(height: 24), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () => Navigator.pop(context), + style: OutlinedButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16), + side: BorderSide(color: EvacuationColors.primaryColor), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: Text( + 'Cancel', + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.w600, + color: EvacuationColors.primaryColor, + ), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: ElevatedButton( + onPressed: () { + // Handle logout + Navigator.pop(context); + Navigator.pushReplacementNamed(context, '/login'); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + padding: const EdgeInsets.symmetric(vertical: 16), + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + ), + child: Text( + 'Logout', + style: GoogleFonts.poppins( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/disaster_alerts/widgets/evacuation_map.dart b/lib/features/disaster_alerts/widgets/evacuation_map.dart index 73d742b..b5b085e 100644 --- a/lib/features/disaster_alerts/widgets/evacuation_map.dart +++ b/lib/features/disaster_alerts/widgets/evacuation_map.dart @@ -3,21 +3,25 @@ import 'package:flutter/services.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import '../models/evacuation_place.dart'; import 'dart:math' show cos, sqrt, asin; +import '../constants/colors.dart'; +import 'dart:async'; class EvacuationMap extends StatefulWidget { final LatLng currentPosition; final List places; final Function(GoogleMapController) onMapCreated; final Set polylines; - final Set markers; // Add this parameter + final Set markers; + final bool isExpanded; // Add this parameter const EvacuationMap({ Key? key, required this.currentPosition, required this.places, required this.polylines, - this.markers = const {}, // Add this parameter with default value + this.markers = const {}, required this.onMapCreated, + this.isExpanded = false, // Default to false }) : super(key: key); @override @@ -25,57 +29,21 @@ class EvacuationMap extends StatefulWidget { } class _EvacuationMapState extends State with SingleTickerProviderStateMixin { - @override - Widget build(BuildContext context) { - return Stack( - children: [ - GoogleMap( - initialCameraPosition: CameraPosition( - target: widget.currentPosition, - zoom: 15, - ), - onMapCreated: (GoogleMapController controller) { - _mapController = controller; - widget.onMapCreated(controller); - if (_isDarkMode) { - _setMapStyle(controller); - } - }, - markers: _createMarkers(), - polylines: widget.polylines, - myLocationEnabled: true, - myLocationButtonEnabled: true, - mapType: _currentMapType, - trafficEnabled: _trafficEnabled, - ), - if (widget.polylines.isNotEmpty) - Positioned( - left: 0, - right: 0, - bottom: 0, - child: _buildRouteInfoPanel(), - ), - ], - ); - } - // Add these properties at the top of the class + // Properties String _routeDuration = '0'; String _routeDistance = '0.0'; double _averageSpeed = 0.0; bool _isDarkMode = false; + bool _isMapControlsVisible = false; + bool _isAnimatingRoute = false; late AnimationController _controller; late GoogleMapController _mapController; MapType _currentMapType = MapType.normal; bool _trafficEnabled = false; - // Add this method to the _EvacuationMapState class - void _clearRoute() { - setState(() { - widget.polylines.clear(); - _routeDuration = '0'; - _routeDistance = '0.0'; - _averageSpeed = 0.0; - }); - } + Timer? _routeAnimationTimer; + List _animatedPoints = []; + Set _animatedPolylines = {}; + @override void initState() { super.initState(); @@ -83,31 +51,468 @@ class _EvacuationMapState extends State with SingleTickerProvider duration: const Duration(milliseconds: 200), vsync: this, ); + + // Start route animation if polylines exist + if (widget.polylines.isNotEmpty) { + _startRouteAnimation(); + } } @override void dispose() { _controller.dispose(); + _routeAnimationTimer?.cancel(); super.dispose(); } + + @override + void didUpdateWidget(EvacuationMap oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.polylines != oldWidget.polylines) { + _updateRouteDetails(); + if (widget.polylines.isNotEmpty) { + _startRouteAnimation(); + } + } + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + // Map + SizedBox( + width: MediaQuery.of(context).size.width, + height: widget.isExpanded + ? MediaQuery.of(context).size.height + : MediaQuery.of(context).size.height * 0.4, + child: GoogleMap( + initialCameraPosition: CameraPosition( + target: widget.currentPosition, + zoom: 15, + ), + onMapCreated: (GoogleMapController controller) { + _mapController = controller; + + // Add a slight delay to ensure the map is properly initialized + Future.delayed(const Duration(milliseconds: 100), () { + controller.animateCamera( + CameraUpdate.newLatLngZoom(widget.currentPosition, 15), + ); + widget.onMapCreated(controller); + if (_isDarkMode) { + _setMapStyle(controller); + } + }); + }, + markers: widget.markers.isEmpty ? _createMarkers() : widget.markers, + polylines: _isAnimatingRoute ? _animatedPolylines : widget.polylines, + myLocationEnabled: true, + myLocationButtonEnabled: false, // We'll add our own button + mapType: _currentMapType, + trafficEnabled: _trafficEnabled, + compassEnabled: true, + zoomControlsEnabled: false, // We'll add our own controls + ), + ), + + // Map controls - adjust position based on expanded state + Positioned( + right: 16, + top: widget.isExpanded ? MediaQuery.of(context).padding.top + 16 : 16, + child: _buildMapControls(), + ), + + // Zoom controls - adjust position based on expanded state + Positioned( + right: 16, + bottom: widget.polylines.isNotEmpty + ? (widget.isExpanded ? 120 : 100) + : 16, + child: _buildZoomControls(), + ), + + // Route info panel - modify this part + if (widget.polylines.isNotEmpty) + Positioned( + left: 16, + right: 16, + bottom: 16, + child: Container( + height: 100, // Set a fixed height for the panel + child: _buildFloatingRouteInfoPanel(), + ), + ), + ], + ); + } + + // Replace your existing _buildRouteInfoPanel with this floating version + Widget _buildFloatingRouteInfoPanel() { + return Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.9), // Semi-transparent background + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Row( + children: [ + // Route icon + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: EvacuationColors.primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.directions, + color: EvacuationColors.primaryColor, + size: 20, + ), + ), + + // Route details + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Route to Destination', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: EvacuationColors.textColor, + ), + ), + const SizedBox(height: 4), + Text( + '$_routeDistance km • $_routeDuration min', + style: TextStyle( + fontSize: 12, + color: EvacuationColors.subtitleColor, + ), + ), + ], + ), + ), + ), + + // Navigation button + ElevatedButton.icon( + onPressed: () { + // Start navigation logic + }, + icon: const Icon(Icons.navigation, size: 16), + label: const Text('Navigate'), + style: ElevatedButton.styleFrom( + backgroundColor: EvacuationColors.primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), + ), + ], + ), + ); + } + + Widget _buildMapControls() { + return AnimatedContainer( + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Map type button + IconButton( + icon: const Icon(Icons.layers_outlined), + color: EvacuationColors.primaryColor, + onPressed: () { + HapticFeedback.selectionClick(); + _showMapTypeSelector(); + }, + ), + + // Traffic toggle + IconButton( + icon: Icon( + Icons.traffic_outlined, + color: _trafficEnabled + ? EvacuationColors.primaryColor + : Colors.grey[600], + ), + onPressed: () { + HapticFeedback.selectionClick(); + setState(() { + _trafficEnabled = !_trafficEnabled; + }); + }, + ), + + // Dark mode toggle + IconButton( + icon: Icon( + _isDarkMode ? Icons.wb_sunny_outlined : Icons.nights_stay_outlined, + color: _isDarkMode + ? EvacuationColors.primaryColor + : Colors.grey[600], + ), + onPressed: () { + HapticFeedback.selectionClick(); + setState(() { + _isDarkMode = !_isDarkMode; + _setMapStyle(_mapController); + }); + }, + ), + + // My location button + IconButton( + icon: const Icon(Icons.my_location), + color: EvacuationColors.primaryColor, + onPressed: () { + HapticFeedback.selectionClick(); + _mapController.animateCamera( + CameraUpdate.newLatLng(widget.currentPosition), + ); + }, + ), + ], + ), + ); + } + + Widget _buildZoomControls() { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + children: [ + // Zoom in + IconButton( + icon: const Icon(Icons.add), + color: EvacuationColors.primaryColor, + onPressed: () { + HapticFeedback.selectionClick(); + _mapController.animateCamera(CameraUpdate.zoomIn()); + }, + ), + + // Zoom out + IconButton( + icon: const Icon(Icons.remove), + color: EvacuationColors.primaryColor, + onPressed: () { + HapticFeedback.selectionClick(); + _mapController.animateCamera(CameraUpdate.zoomOut()); + }, + ), + ], + ), + ); + } + + void _showMapTypeSelector() { + showModalBottomSheet( + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(20)), + ), + builder: (context) => Container( + padding: const EdgeInsets.symmetric(vertical: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Padding( + padding: EdgeInsets.only(bottom: 16), + child: Text( + 'Select Map Type', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + _buildMapTypeOption( + 'Standard', + Icons.map_outlined, + MapType.normal, + ), + _buildMapTypeOption( + 'Satellite', + Icons.satellite_outlined, + MapType.satellite, + ), + _buildMapTypeOption( + 'Terrain', + Icons.terrain_outlined, + MapType.terrain, + ), + _buildMapTypeOption( + 'Hybrid', + Icons.layers_outlined, + MapType.hybrid, + ), + ], + ), + ), + ); + } + + Widget _buildMapTypeOption(String title, IconData icon, MapType mapType) { + final isSelected = _currentMapType == mapType; + + return ListTile( + leading: Icon( + icon, + color: isSelected ? EvacuationColors.primaryColor : Colors.grey[600], + ), + title: Text( + title, + style: TextStyle( + color: isSelected ? EvacuationColors.primaryColor : Colors.black, + fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, + ), + ), + trailing: isSelected + ? const Icon(Icons.check, color: EvacuationColors.primaryColor) + : null, + onTap: () { + setState(() { + _currentMapType = mapType; + }); + Navigator.pop(context); + }, + ); + } Future _setMapStyle(GoogleMapController controller) async { - await controller.setMapStyle(''' - [ - { - "elementType": "geometry", - "stylers": [{"color": "#242f3e"}] - }, - { - "elementType": "labels.text.fill", - "stylers": [{"color": "#746855"}] - }, - { - "elementType": "labels.text.stroke", - "stylers": [{"color": "#242f3e"}] - } - ] - '''); + if (_isDarkMode) { + await controller.setMapStyle(''' + [ + { + "elementType": "geometry", + "stylers": [{"color": "#242f3e"}] + }, + { + "elementType": "labels.text.fill", + "stylers": [{"color": "#746855"}] + }, + { + "elementType": "labels.text.stroke", + "stylers": [{"color": "#242f3e"}] + }, + { + "featureType": "administrative.locality", + "elementType": "labels.text.fill", + "stylers": [{"color": "#d59563"}] + }, + { + "featureType": "poi", + "elementType": "labels.text.fill", + "stylers": [{"color": "#d59563"}] + }, + { + "featureType": "poi.park", + "elementType": "geometry", + "stylers": [{"color": "#263c3f"}] + }, + { + "featureType": "poi.park", + "elementType": "labels.text.fill", + "stylers": [{"color": "#6b9a76"}] + }, + { + "featureType": "road", + "elementType": "geometry", + "stylers": [{"color": "#38414e"}] + }, + { + "featureType": "road", + "elementType": "geometry.stroke", + "stylers": [{"color": "#212a37"}] + }, + { + "featureType": "road", + "elementType": "labels.text.fill", + "stylers": [{"color": "#9ca5b3"}] + }, + { + "featureType": "road.highway", + "elementType": "geometry", + "stylers": [{"color": "#746855"}] + }, + { + "featureType": "road.highway", + "elementType": "geometry.stroke", + "stylers": [{"color": "#1f2835"}] + }, + { + "featureType": "road.highway", + "elementType": "labels.text.fill", + "stylers": [{"color": "#f3d19c"}] + }, + { + "featureType": "transit", + "elementType": "geometry", + "stylers": [{"color": "#2f3948"}] + }, + { + "featureType": "transit.station", + "elementType": "labels.text.fill", + "stylers": [{"color": "#d59563"}] + }, + { + "featureType": "water", + "elementType": "geometry", + "stylers": [{"color": "#17263c"}] + }, + { + "featureType": "water", + "elementType": "labels.text.fill", + "stylers": [{"color": "#515c6d"}] + }, + { + "featureType": "water", + "elementType": "labels.text.stroke", + "stylers": [{"color": "#17263c"}] + } + ] + '''); + } else { + await controller.setMapStyle(null); // Reset to default style + } } Set _createMarkers() { @@ -138,6 +543,7 @@ class _EvacuationMapState extends State with SingleTickerProvider return markers; } + void _updateRouteDetails() { if (widget.polylines.isEmpty) return; @@ -156,6 +562,7 @@ class _EvacuationMapState extends State with SingleTickerProvider _averageSpeed = totalDistance / (durationInMinutes / 60); }); } + double _calculateDistance(LatLng start, LatLng end) { var p = 0.017453292519943295; // Math.PI / 180 var c = cos; @@ -164,21 +571,83 @@ class _EvacuationMapState extends State with SingleTickerProvider (1 - c((end.longitude - start.longitude) * p))/2; return 12742 * asin(sqrt(a)); // 2 * R; R = 6371 km } - @override - void didUpdateWidget(EvacuationMap oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.polylines != oldWidget.polylines) { - _updateRouteDetails(); - } + + void _clearRoute() { + setState(() { + widget.polylines.clear(); + _animatedPolylines.clear(); + _animatedPoints.clear(); + _routeDuration = '0'; + _routeDistance = '0.0'; + _averageSpeed = 0.0; + _isAnimatingRoute = false; + }); + _routeAnimationTimer?.cancel(); } - // Update the route info panel build method + + void _startRouteAnimation() { + // Cancel any existing animation + _routeAnimationTimer?.cancel(); + + if (widget.polylines.isEmpty) return; + + setState(() { + _isAnimatingRoute = true; + _animatedPoints = []; + _animatedPolylines = {}; + }); + + final allPoints = widget.polylines.first.points; + final totalPoints = allPoints.length; + int currentPointIndex = 0; + + // Create a timer that adds points to the animated polyline + _routeAnimationTimer = Timer.periodic(const Duration(milliseconds: 50), (timer) { + if (currentPointIndex >= totalPoints) { + timer.cancel(); + return; + } + + setState(() { + // Add the next point to our animated points list + _animatedPoints.add(allPoints[currentPointIndex]); + + // Create a new polyline with the current points + _animatedPolylines = { + Polyline( + polylineId: const PolylineId('animated_route'), + points: _animatedPoints, + color: EvacuationColors.primaryColor, + width: 5, + patterns: [ + PatternItem.dash(20), + PatternItem.gap(5), + ], + startCap: Cap.roundCap, + endCap: Cap.roundCap, + ), + }; + }); + + currentPointIndex++; + + // If we've added all points, stop the animation + if (currentPointIndex >= totalPoints) { + setState(() { + _isAnimatingRoute = false; + }); + timer.cancel(); + } + }); + } + Widget _buildRouteInfoPanel() { return AnimatedContainer( duration: const Duration(milliseconds: 300), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, - borderRadius: BorderRadius.circular(12), + borderRadius: const BorderRadius.vertical(top: Radius.circular(20)), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), @@ -190,15 +659,39 @@ class _EvacuationMapState extends State with SingleTickerProvider child: Column( mainAxisSize: MainAxisSize.min, children: [ + // Handle for dragging + Container( + width: 40, + height: 4, + margin: const EdgeInsets.only(bottom: 16), + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(2), + ), + ), + + // Header Row( children: [ - const Icon(Icons.directions, color: Colors.blue), - const SizedBox(width: 8), - const Text( + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: EvacuationColors.primaryColor.withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.directions, + color: EvacuationColors.primaryColor, + size: 20, + ), + ), + const SizedBox(width: 12), + Text( 'Route Details', style: TextStyle( - fontSize: 16, + fontSize: 18, fontWeight: FontWeight.bold, + color: EvacuationColors.textColor, ), ), const Spacer(), @@ -209,36 +702,115 @@ class _EvacuationMapState extends State with SingleTickerProvider }, icon: const Icon(Icons.clear, size: 18), label: const Text('Clear'), + style: TextButton.styleFrom( + foregroundColor: Colors.red[400], + ), ), ], ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildRouteDetail(Icons.timer, '$_routeDuration min'), - _buildRouteDetail(Icons.directions_car, '$_routeDistance km'), - _buildRouteDetail(Icons.speed, '${_averageSpeed.round()} km/h'), - ], + + const SizedBox(height: 16), + + // Route details + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: EvacuationColors.backgroundColor, + borderRadius: BorderRadius.circular(16), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildRouteDetailCard( + Icons.timer, + '$_routeDuration min', + 'Duration', + ), + _buildRouteDetailCard( + Icons.directions_car, + '$_routeDistance km', + 'Distance', + ), + _buildRouteDetailCard( + Icons.speed, + '${_averageSpeed.round()} km/h', + 'Avg. Speed', + ), + ], + ), + ), + + const SizedBox(height: 16), + + // Navigation button + ElevatedButton.icon( + onPressed: () { + // Start navigation logic here + HapticFeedback.mediumImpact(); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Navigation started'), + behavior: SnackBarBehavior.floating, + ), + ); + }, + icon: const Icon(Icons.navigation), + label: const Text('Start Navigation'), + style: ElevatedButton.styleFrom( + backgroundColor: EvacuationColors.primaryColor, + foregroundColor: Colors.white, + padding: const EdgeInsets.symmetric(vertical: 12), + minimumSize: const Size(double.infinity, 0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + ), ), ], ), ); } - Widget _buildRouteDetail(IconData icon, String text) { - return Row( - children: [ - Icon(icon, size: 18, color: Colors.grey[600]), - const SizedBox(width: 6), - Text( - text, - style: TextStyle( - color: Colors.grey[800], - fontWeight: FontWeight.w500, - fontSize: 14, - ), + + Widget _buildRouteDetailCard(IconData icon, String value, String label) { + return Column( + children: [ + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 4, + offset: const Offset(0, 2), + ), + ], ), - ], - ); - } -} + child: Icon( + icon, + color: EvacuationColors.primaryColor, + size: 24, + ), + ), + const SizedBox(height: 8), + Text( + value, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: EvacuationColors.textColor, + ), + ), + const SizedBox(height: 4), + Text( + label, + style: TextStyle( + fontSize: 12, + color: EvacuationColors.subtitleColor, + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/features/disaster_alerts/widgets/side_navigation.dart b/lib/features/disaster_alerts/widgets/side_navigation.dart deleted file mode 100644 index bfeef79..0000000 --- a/lib/features/disaster_alerts/widgets/side_navigation.dart +++ /dev/null @@ -1,415 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:google_fonts/google_fonts.dart'; -import 'package:lottie/lottie.dart'; - -class SideNavigation extends StatefulWidget { - final String userName; - final VoidCallback? onClose; - - const SideNavigation({ - Key? key, - required this.userName, - this.onClose, - }) : super(key: key); - - @override - State createState() => _SideNavigationState(); -} - -class _SideNavigationState extends State with SingleTickerProviderStateMixin { - late AnimationController _animationController; - int _selectedIndex = 0; - final ScrollController _scrollController = ScrollController(); - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - vsync: this, - duration: const Duration(milliseconds: 300), - ); - _animationController.forward(); - } - - @override - void dispose() { - _animationController.dispose(); - _scrollController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Drawer( - backgroundColor: Colors.white, - elevation: 0, - child: AnimatedBuilder( - animation: _animationController, - builder: (context, child) { - return SlideTransition( - position: Tween( - begin: const Offset(-0.3, 0), - end: Offset.zero, - ).animate(CurvedAnimation( - parent: _animationController, - curve: Curves.easeOutCubic, - )), - child: FadeTransition( - opacity: _animationController, - child: child, - ), - ); - }, - child: Column( - children: [ - _buildHeader(context), - Expanded( - // Removing the ShaderMask that might be causing visibility issues - child: SingleChildScrollView( - controller: _scrollController, - physics: const BouncingScrollPhysics(), - padding: const EdgeInsets.symmetric(vertical: 16), - child: _buildNavigationItems(context), - ), - ), - _buildFooter(context), - ], - ), - ), - ); - } - - Widget _buildHeader(BuildContext context) { - return Container( - padding: EdgeInsets.only( - top: MediaQuery.of(context).padding.top + 20, - bottom: 20, - left: 20, - right: 20, - ), - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - const Color(0xFF2196F3), - const Color(0xFF1976D2), - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - borderRadius: const BorderRadius.only( - bottomRight: Radius.circular(30), - ), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 10, - offset: const Offset(0, 5), - ), - ], - ), - child: Column( - children: [ - Row( - children: [ - Hero( - tag: 'profile_avatar', - child: Container( - padding: const EdgeInsets.all(3), - decoration: BoxDecoration( - shape: BoxShape.circle, - border: Border.all(color: Colors.white, width: 2), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.1), - blurRadius: 8, - spreadRadius: 1, - ), - ], - ), - child: CircleAvatar( - radius: 30, - backgroundColor: Colors.white, - child: Text( - widget.userName[0].toUpperCase(), - style: GoogleFonts.poppins( - fontSize: 24, - fontWeight: FontWeight.bold, - color: const Color(0xFF1976D2), - ), - ), - ), - ), - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.userName, - style: GoogleFonts.poppins( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - const SizedBox(height: 4), - InkWell( - onTap: () { - HapticFeedback.lightImpact(); - // Navigate to profile - }, - child: Row( - children: [ - Text( - 'View Profile', - style: GoogleFonts.poppins( - color: Colors.white.withOpacity(0.9), - fontSize: 14, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(width: 4), - Icon( - Icons.arrow_forward_ios_rounded, - size: 12, - color: Colors.white.withOpacity(0.9), - ), - ], - ), - ), - ], - ), - ), - ], - ), - ], - ), - ); - } - - Widget _buildNavigationItems(BuildContext context) { - return Column( - children: [ - _buildNavItem( - icon: Icons.home_rounded, - title: 'Home', - index: 0, - onTap: () { - Navigator.pop(context); - Navigator.pushReplacementNamed(context, '/home'); - }, - ), - _buildNavItem( - icon: Icons.notifications_rounded, - title: 'Notifications', - badge: '3', - index: 1, - onTap: () { - // Navigate to notifications - Navigator.pop(context); - }, - ), - _buildNavItem( - icon: Icons.shield_rounded, - title: 'Emergency Contacts', - index: 2, - onTap: () { - Navigator.pop(context); - Navigator.pushReplacementNamed(context, '/emergency_contacts'); - }, - ), - _buildNavItem( - icon: Icons.directions_run, - title: 'Evacuation', - index: 3, - onTap: () { - Navigator.pop(context); - Navigator.pushReplacementNamed(context, '/evacuation'); - }, - ), - _buildNavItem( - icon: Icons.history_rounded, - title: 'History', - index: 4, - onTap: () { - // Navigate to history - Navigator.pop(context); - }, - ), - ], - ); - } - - Widget _buildNavItem({ - required IconData icon, - required String title, - String? badge, - required int index, - VoidCallback? onTap, - }) { - final isSelected = _selectedIndex == index; - - return TweenAnimationBuilder( - tween: Tween(begin: 0.0, end: 1.0), - duration: Duration(milliseconds: 300 + (index * 100)), - curve: Curves.easeOutQuint, - builder: (context, value, child) { - return Transform.translate( - offset: Offset(30 * (1 - value), 0), - child: Opacity( - opacity: value, - child: child, - ), - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: InkWell( - onTap: () { - HapticFeedback.selectionClick(); - setState(() { - _selectedIndex = index; - }); - - // Execute the provided onTap callback - if (onTap != null) { - onTap(); - } - }, - borderRadius: BorderRadius.circular(16), - child: AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeOutCubic, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), - decoration: BoxDecoration( - color: isSelected ? const Color(0xFF1976D2).withOpacity(0.2) : Colors.grey.withOpacity(0.1), - borderRadius: BorderRadius.circular(16), - border: Border.all( - color: isSelected ? const Color(0xFF1976D2) : Colors.grey.withOpacity(0.2), - width: 1.5, - ), - ), - child: Row( - children: [ - AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeOutCubic, - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: isSelected ? const Color(0xFF1976D2) : Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: isSelected - ? const Color(0xFF1976D2).withOpacity(0.3) - : Colors.black.withOpacity(0.05), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Icon( - icon, - color: isSelected ? Colors.white : const Color(0xFF1976D2), - size: 24, - ), - ), - const SizedBox(width: 16), - Expanded( - child: Text( - title, - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500, - color: isSelected ? const Color(0xFF1976D2) : Colors.black, - ), - ), - ), - if (badge != null) - Container( - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), - decoration: BoxDecoration( - color: const Color(0xFF1976D2), - borderRadius: BorderRadius.circular(12), - ), - child: Text( - badge, - style: GoogleFonts.poppins( - color: Colors.white, - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - ), - ), - ); - } - - Widget _buildFooter(BuildContext context) { - return Container( - padding: EdgeInsets.only( - left: 20, - right: 20, - bottom: MediaQuery.of(context).padding.bottom + 20, - top: 20, - ), - decoration: BoxDecoration( - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.05), - blurRadius: 10, - offset: const Offset(0, -5), - ), - ], - ), - child: Column( - children: [ - const Divider(height: 1), - const SizedBox(height: 20), - InkWell( - onTap: () { - HapticFeedback.mediumImpact(); - // Handle logout - }, - borderRadius: BorderRadius.circular(16), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Colors.red.withOpacity(0.1), - borderRadius: BorderRadius.circular(12), - ), - child: const Icon( - Icons.logout_rounded, - color: Colors.red, - size: 24, - ), - ), - const SizedBox(width: 16), - Text( - 'Logout', - style: GoogleFonts.poppins( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.red, - ), - ), - ], - ), - ), - ), - ], - ), - ); - } -} \ No newline at end of file From 23937e8fc735f70f62fd3fcbbc75b26d81c8c2e4 Mon Sep 17 00:00:00 2001 From: "$@#il" Date: Mon, 19 May 2025 20:25:01 +0530 Subject: [PATCH 2/3] added app sign in configurations --- .github/workflows/android-build.yml | 11 ++++++- android/app/build.gradle | 16 ++++----- base64 | 0 clear | 3 ++ disaster_management.keystore | Bin 0 -> 2786 bytes keystore_base64.txt | 49 ++++++++++++++++++++++++++++ 6 files changed, 68 insertions(+), 11 deletions(-) create mode 100644 base64 create mode 100644 clear create mode 100644 disaster_management.keystore create mode 100644 keystore_base64.txt diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml index 8792b2f..7caf3e0 100644 --- a/.github/workflows/android-build.yml +++ b/.github/workflows/android-build.yml @@ -34,7 +34,16 @@ jobs: - name: Get Dependencies run: flutter pub get - # Removed the Flutter Tests step + # Set up signing configuration + - name: Setup Keystore + run: | + echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/keystore.jks + + # Create key.properties file + echo "storeFile=keystore.jks" > android/key.properties + echo "storePassword=${{ secrets.KEYSTORE_PASSWORD }}" >> android/key.properties + echo "keyAlias=${{ secrets.KEY_ALIAS }}" >> android/key.properties + echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> android/key.properties - name: Build APK run: flutter build apk --release diff --git a/android/app/build.gradle b/android/app/build.gradle index 62b2530..50c0022 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -8,13 +8,6 @@ plugins { id "dev.flutter.flutter-gradle-plugin" } -// Add this at the top of the file, before the android { ... } block -def keystoreProperties = new Properties() -def keystorePropertiesFile = rootProject.file('key.properties') -if (keystorePropertiesFile.exists()) { - keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -} - android { namespace = "com.example.disaster_management" compileSdk = flutter.compileSdkVersion @@ -54,9 +47,6 @@ android { release { // Removed duplicate applicationId and other properties that should only be in defaultConfig signingConfig signingConfigs.release - minifyEnabled false // Change this to false - shrinkResources false // Add this line - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } @@ -64,3 +54,9 @@ android { flutter { source = "../.." } + +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} diff --git a/base64 b/base64 new file mode 100644 index 0000000..e69de29 diff --git a/clear b/clear new file mode 100644 index 0000000..6561f77 --- /dev/null +++ b/clear @@ -0,0 +1,3 @@ + PID PPID PGID WINPID TTY UID STIME COMMAND + 1993 1711 1993 25544 cons0 197609 20:16:31 /usr/bin/PS + 1711 1 1711 23612 cons0 197609 20:16:20 /usr/bin/bash diff --git a/disaster_management.keystore b/disaster_management.keystore new file mode 100644 index 0000000000000000000000000000000000000000..47023362ac5dccac030e208acb6cdc7953800121 GIT binary patch literal 2786 zcma)8c{CJ`8l4$qFpYhSL9&$H*p(KtC&QpnWRx|HJ&b)OOIbtNmtlqsvSlP>DElbN z5}L`DvHVAZ@2LzNvz##1Zz6j!A1t7#B9OwiypgRi~ zr~u|!@Uu7E74knd{M8D8EPAv7#)C&XnuT_zr+7|V4*sMmw!Z7Q_hX9uKAQmzOK6e7 zQup+xR+mb+bO-#5$vU1ry7+dZtcaod{5)J(1eERi!>UNHEzidJlk~iwTL-0esQIQq z^!DxeC_7N4e`BkGb)zH+m1&TyXCqhEVqh;b85S*%R={fcult|Bz2eZ&-pb*lT>kRy z8H+;k<5*N*4;sbtiDhw3OPj);W`lV!ReFXfqfyfaki9aDAsAP8R6x}^T6y}g`{_6O92x;>nDqd^f5fu-3UWQo z+QAKRk@6kZx0Gd}|P65 zmy;)t_>lp5Vc1CxpA~#=(=PMf03KxLVo_0?DfST5 zuOV1IPG^9QE$dFB+glpLpO0a^&L1w^&t%@ME>}s{e@t#&@&#+-Cjk@m;;Qy$)D@b$ z?S>01jHW>LoWIpXv)xlQ1q21GL!bVZk4S8Lo-ef0RGPoBkfMJkqceMO#UFr{CBLl) zi`?#c&+GHKv5I00vr7+ZX}v@Fbof(qiLp8}C9U~}ZTGI}GTdp$OUZTgGIHJ2pu})C z#Y}l&#;8L*^zo=S82TyU{8ya*V|u}J4i&wtW@$A;cosF0oH>&`lmmP|q_;e%5m%<& z$?Rq`=WU@|+X5;_4sa})=e}mDjb>tO!KbR731V)i5aVU%*yG?x@D^Y zpli=igVgrN%wlM<@;TI#H!X|nwE)RT%wmHehI^Q8T<$b%^Rlp`huP5JuTk9UwU#v6 zB4g&owFmg0!FuO0z zB=A|jYS@|4xG{ll2!FZJkUgGsU{=VR7aRzHL-zJ0bKq`<&4c|Kn{u*T#(obR%O&3Q z37d|pHvKj9%4L(Qe|K^$#ZvQx&%|ZSC{enasOC#Oxj{7bnE{#$Hgo z8#k@Kk>2cO`6c&SyL&<8^{e@9qNfGycNx7`HxHYYm7j$yc4W5WyjGUt)r+$Bu5`CD zS%5n+%4*jUT7T-2LqJVV;uOFQ;0*`@xE>)G;0_1`xBxIm%=ZYMM;vnmUqCS8rZZ4SUC({wv)l;&9|Uh|%iYnKtZ4s(@zEoG8O{1&f_ub0*O@qL`J zEY1yEMCm7z^7sZyy9P48RYd9wMoGlK=Ca9z?XZga5qalJbo-QcY=|cA zf{&w^*OR3zzht_gF0QL(GaM)_2GxZZXK(+c6P9de>U8v6*ubAzM|^u?BFfI(Pqp^` zRU4XH#FjN&Zm>$y)4#;z;3I#oeMDrvshD~KqLgHEH=yX6fjcxul1x?D4>c`iLtE!8 zur}X!*n57zH<^}A8o!c9-*=5dpJEAUaKNfl)6j57B`NgPnZ~%+8h%c7W{m*?-im}E zXXS(Rz#I~hKN!IfaWiLF1&RgPyxUGbo4MYuYaU_v<5iSt0ee6Lz@W<0%U9zpoLR+E zltBkAR$SG1Q3O`95{bE)BQZ+SQEGWkgLa1=_?wkVtDsDYyGFub8DXY{XoM)3Tk5#_ zz`53G=AvCD&XRgv*?PWL0Smu7Yb|NY+sMi6!J$uU}QLECzTgLdbRE!5z)hJkd^i*@7 z@XelX>4Jq9!#9j~Yt-KwPDYgPFkNQ%NcYIiiMejPu-sX)w>jNYxb9BAglgL_c?G4Z z7^9eilI%+PPS4F!_=<+!IW^hBT&^Y$7zMIBV^a8C<`znzST~}nx~;~~m)pGC!H&rR zI`?igVP-BQJS^>#6CzhpmQUT+(loXE9UY|EzNOO(ER%FNyLFWr^!=yn;)UAXiq#9I zEF+Ai?prOyS3>S7YDv?FLe1_MF8AzhNL#*1OL~RErj#upED>r5&Od)22*?TmOB#jR z*M3~ou9}*h-S_Tsz@TcU*u literal 0 HcmV?d00001 diff --git a/keystore_base64.txt b/keystore_base64.txt new file mode 100644 index 0000000..9199726 --- /dev/null +++ b/keystore_base64.txt @@ -0,0 +1,49 @@ +MIIK3gIBAzCCCogGCSqGSIb3DQEHAaCCCnkEggp1MIIKcTCCBcgGCSqGSIb3DQEHAaCCBbkEggW1 +MIIFsTCCBa0GCyqGSIb3DQEMCgECoIIFQDCCBTwwZgYJKoZIhvcNAQUNMFkwOAYJKoZIhvcNAQUM +MCsEFKfye+sd4ciTfIFMxPNkTkWRrOmWAgInEAIBIDAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQB +KgQQ429XQ/UWcCbP8U8ATp2Q7gSCBNC+thb02tkQJ03K/Msrus6+dfvZqdGW3Qp4hrgkeb/3O9nq +5p8OOMlvRak3Z8U4grxEk3vMsJqZgXh7Cgdaiap8aPJnsuIh3Jn88vqhr1HitHJg82OFfVQCqHCy +ukFQsiCkUZJBiztSKKK4QVck2Hh+KWAqeTVw7XA9Y+hYsby6CWstpbnNOUucHRh5UcfFYFED2wPl +7DU2tQiOUm2I2aE5oyQzrLsAJUzPbaBFk0Zo418exRaE6IttHWPvvwS+/I5++hK+vrsjSTh5c2WB +p1Tn0Mss/88GtiNIuWYUV10EDx7So0V5wAakg40HjixTVwhH3pjn1I0AkYpmuTeUOJrwOwDb2aQ1 +W+67Gbh9Iczn2ezbKp8UhRUbrdnrQP4Zzw/9n0nsQvn0Z8FPsT8KOhyvoaS8cfkw1QO+y5tLo37n +CSwZpKDuqsmOsUh+ZNbM7ALAZoWcUncoimSfkaoQgOfz3Ex1BwkWIrV9Rh41ymhcDJm6yF10G2fE +QZfZAZw2zYbwornN/LXq7K8PAGFRHMeP4dComIKmUzbomB4E3cRZRyWRaFBh7LxuNxoTJJrI50HB +p4y+qSofm7ko1WBOaqvU5PrljPCcFRNrJHsrqnKrIzO8TlPJG34vMDsrnqM2xQ7hcUOzt1DBKKp7 +Ny1yD2bCqGxFyyNrfisHCuH71GsDx115HPu2C9+Sg5NRcao8y6WPz2Unw0AJM3GObe1xb++ABW4V +DqeT9IHMDqSaWO3yLSUK1S/ujWRSk05AogXlcO4A9DPkK+Mw4YeWdj8Sgjag/uvEu/ITHHW4qTgN +EJmj190Gt0PAv3rgFbNSvCT5QRt8RI34UrJOvcJmjyNw2LSEyB4EdJ6LqS5Pog6Nd+02cPGiZOpr +7uDm4dyitYvWMS++DQ4WR9ANJ7DYxq32dI9ehJXHRsrU3xyQeaOdnYRHef0CyDMVpdXc3QIL5zja +YLy4snqz1HloPf3jg5LrbKulLo9AlKm65m4ENoLYANbcnqq8tlFMy2VT7l8MeMsqqWfiYrTLU/cv +RwBzce0Kp/8pe4e7s5oW6LShmu7jjEA5kcCWyuhwAGAlqc2wBBljxdERa96yqrVFDFSPc7i6arXb +/fw05t3q39kjNMMH9WxH5xtc9GgsYdNKTu1HQZ9D4IxILePfRL4yd5TTaQQK24Y98YFAlNybswku +O1VIjqzMggMxGZfhRplRWBvidsalc7KBojbA68QHSRG43FBTAKjQ+oEDkjzxkjHTkm1TBCarXaAc +ZCYov8h89IKrECPumdqSqNqor+dPyQFhxcyxIvPXkp4Kni08UZXDuOXtrgAgfG3lsRVtD88H1Sgn +DPBKF1lmSMzK/tOB6la4jsvl3d+3VoiCfunteHnSh0eErWkXNnzzjOc/j41z3VvaJfCxqbYRV8W1 +04Io9/HyRWrrxD7fhhJNvbH03zNb1mCxernusZbVivpInBGZdHIAGwb39yCXG2RDtsrIM/AoJQ5F +b4hZpR7DxxdH0y+06cy9X/AOyPXY1IxNNB9r1kpt06Mjq6Mxbr8h7qSON+yoZMTQKbdsgRbd9gib +nvWE2jLuj7ZoTeSYVrxlm3xaVZq7GicUV/XdxlXw/bYtLa125b6SvIFo6OaFMp2T90zEuusDAfR/ +LTFaMDUGCSqGSIb3DQEJFDEoHiYAZABpAHMAYQBzAHQAZQByAF8AbQBhAG4AYQBnAGUAbQBlAG4A +dDAhBgkqhkiG9w0BCRUxFAQSVGltZSAxNzQ3NjY1NzU5NTQ4MIIEoQYJKoZIhvcNAQcGoIIEkjCC +BI4CAQAwggSHBgkqhkiG9w0BBwEwZgYJKoZIhvcNAQUNMFkwOAYJKoZIhvcNAQUMMCsEFAxUbaIJ +3Vpiv/rYtZhcm0G3Yl1hAgInEAIBIDAMBggqhkiG9w0CCQUAMB0GCWCGSAFlAwQBKgQQiDijGhkl +RoB89M+fgR+K1oCCBBDyDHR13UK1x66ihoBxV9Tgr6tjKVzkBHps7njAlC4qGg0enXS1XwPPAbDJ +AkpdnlBsNcG+KLVNC2Da8s8PLb9EDGvWwGQVn0Q6QvAlLj2XXL9RQ+pXDk31S4DL+4KRTFqGa8yM +9k6TTt4TT+hRHcBLrGCf46PjroFC6ZtJDVi/dhFhEA0IQ2jrDppvE2/OiRlZ3tzz4qg5f9iOY+x0 +hllUxy6ftW4PYBtOk/gX4k5LZNGr8zSFagfhAA4ktGOT08tnnB2s0aCYVJF87QxhKMIiRoej9oUt +grAlNfv51d3nHQ8M5VFAh6SZEsmhwcmR9qZ8QBV9HoXCDlKRKZ0O456g8l1rsGUKbXCg+boGLJdD +QcqmIaPoBczF0z3mbVF3hS+CxbrxnEIGzG+hic2oXhDjdNANwa0TtlYw4BqdM/1OXH9ttGb0XEui +HT6YFGes637S4k5R5RjSoC5OGwpd/qUxkijPit+/maDdD2lijvgY0MrahTF9jSqU4L/LpnjyeiCQ +gM+spjI4gVud27YSHAX4y3QSf9bmF8AwEzCKYV6XVv2qv9dOauR+vNk2gHY0gT5rWRxoleaTb4vc +FTiBwbjwegEjHAo4G56W8/zcoOZT3683O18HBN4F0G7Ff3uiOYONUGn+rneYnQeTz6VB6qQ7QELr +CRKp3q6QowVeK40fBiyKRmxxnVZBZQqXIKm/Kvh3R6EHYFCX4wW2g1j3s4PGi8uWpNVMmdz4YX1g +JgNxsVh5Mr+OYBtZLCJgVd+yhMIzb1yvSLJxFGkroHNdLfqPcpekoxN0MN0ZZEndT3Icc1LO84s6 +ku28OEl7Q/u9fUebCHGxAEGqZ2huMzob66oDnZHcAuWmVTO3nQQsTxl/Xpce06Q3LLizywrEd/pw +SKEjLlFHo/VEFwQkF+tLGnsaBA8iGDLJPLra6+VUkoFNv1otUOLGLgu1sH+8pHZgX5D9RtCwEXk/ +zovq3E8Gy/7xDUMiGVOh4qmudvgapljlisJylWl3H//wMHP/6mkxefYGcABPpKsa1IgzKTdFoJLG +KBWJ9hrByNXkzNoLtU8MQHp/QK4+N3lvso8CqSqHomVKrtZ9hHPGWATz4GJGhzWoS2kkRScif2a/ +L0R0NtMmtscXXsXEI5vjt89bRfWsMs1D2Hul9Otf4Pekdam2h5hkEQyY4KlpSdxnF69qmh4vu/4p +KizZf0R/m5+WqCnAZ/ChG+gzOzwxJZyYDrRmDLUG9d9GnPkkYlEUnjh7tKu7rG9uD0nEWFTrCQI8 +ztOG3+F2eniO21wwmCslEjJuTUeNxM7cAjS88jfGAaIgWDryVetz+fxh5T6u9abqPkcD0N3mxGK4 +o70WZYwxitr9FrZlPkrF9+4jTcOOir1ReYyi4zBNMDEwDQYJYIZIAWUDBAIBBQAEIER3V67X5Taq +2djg+GnFWG1RrtoIXw8snSKWa4TXcpc1BBQGCAjHG2UV854RW4vamrjmQUg9kAICJxA= From 5cc3b44e037f28c67363ef412ffd45e5de40e7fc Mon Sep 17 00:00:00 2001 From: "$@#il" Date: Mon, 19 May 2025 20:32:29 +0530 Subject: [PATCH 3/3] added app sign in configurations --- android/app/build.gradle | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 50c0022..c498fc6 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -8,6 +8,13 @@ plugins { id "dev.flutter.flutter-gradle-plugin" } +// Move keystoreProperties definition here, before it's used +def keystoreProperties = new Properties() +def keystorePropertiesFile = rootProject.file('key.properties') +if (keystorePropertiesFile.exists()) { + keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +} + android { namespace = "com.example.disaster_management" compileSdk = flutter.compileSdkVersion @@ -55,8 +62,4 @@ flutter { source = "../.." } -def keystoreProperties = new Properties() -def keystorePropertiesFile = rootProject.file('key.properties') -if (keystorePropertiesFile.exists()) { - keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -} +// Remove the duplicate keystoreProperties definition that was here