From 233d79f03b63b581a278e2f7c2a7a8beefaab818 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sun, 15 Jun 2025 09:39:09 +0530 Subject: [PATCH 01/19] search landmark --- .../hike/services/geoapify_service.dart | 28 ++++++ .../hike/widgets/search_places.dart | 89 +++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 lib/presentation/hike/services/geoapify_service.dart create mode 100644 lib/presentation/hike/widgets/search_places.dart diff --git a/lib/presentation/hike/services/geoapify_service.dart b/lib/presentation/hike/services/geoapify_service.dart new file mode 100644 index 0000000..b0b5470 --- /dev/null +++ b/lib/presentation/hike/services/geoapify_service.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; +import 'package:http/http.dart' as http; + +class GeoapifyService { + static const String _apiKey = '1e810532f9934cd3a0d964b4caabd5d0'; + static const String _baseUrl = + 'https://api.geoapify.com/v1/geocode/autocomplete'; + + Future> getLocationSuggestions(String query) async { + final url = Uri.parse("$_baseUrl?text=$query&limit=5&apiKey=$_apiKey"); + + if (query.isEmpty) { + return []; + } + + final response = await http.get(url); + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + List suggestions = data['features']; + + return suggestions + .map((item) => item['properties']['formatted'] as String) + .toList(); + } else { + throw Exception("Failed to fetch location suggestions"); + } + } +} diff --git a/lib/presentation/hike/widgets/search_places.dart b/lib/presentation/hike/widgets/search_places.dart new file mode 100644 index 0000000..40bed5e --- /dev/null +++ b/lib/presentation/hike/widgets/search_places.dart @@ -0,0 +1,89 @@ +import 'package:beacon/presentation/hike/services/geoapify_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; + +class LocationSearchWidget extends StatelessWidget { + final GeoapifyService geoapifyService = GeoapifyService(); + + @override + Widget build(BuildContext context) { + return Container( + margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + child: TypeAheadField( + suggestionsCallback: (pattern) async { + return await geoapifyService.getLocationSuggestions(pattern); + }, + builder: (context, controller, focusNode) { + return Container( + decoration: BoxDecoration( + color: Colors.white, borderRadius: BorderRadius.circular(20)), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + child: TextFormField( + controller: controller, + focusNode: focusNode, + cursorColor: Colors.deepPurpleAccent.withAlpha(120), + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.symmetric(vertical: 12), + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + errorBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(14), + borderSide: BorderSide(color: Colors.red), + ), + hintText: 'Search for a place', + hintStyle: TextStyle(fontSize: 15, color: Colors.grey[600]), + prefixIcon: Icon( + Icons.search, + size: 20, + color: Colors.deepPurpleAccent.withAlpha(120), + ), + suffixIcon: IconButton( + icon: Icon(Icons.clear, color: Colors.grey), + onPressed: () { + controller.clear(); + focusNode.unfocus(); + // remove keyboard + }, + )), + style: TextStyle( + color: Colors.black, + fontSize: 14, + ), + ), + ); + }, + itemBuilder: (context, suggestion) { + return ListTile( + title: Text(suggestion), + leading: Icon(Icons.location_on, color: Colors.blue), + ); + }, + onSelected: (suggestion) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('You selected: $suggestion')), + ); + }, + hideOnEmpty: true, + decorationBuilder: (context, child) { + return Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8.0), + boxShadow: [ + BoxShadow( + color: Colors.grey.withOpacity(0.2), + spreadRadius: 2, + blurRadius: 5, + offset: Offset(0, 3), // changes position of shadow + ), + ], + ), + child: child, + ); + }, + ), + ); + } +} From 0b57d23329be03c0b99f25d1c5cfeb05159c6a05 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Mon, 23 Jun 2025 14:06:49 +0530 Subject: [PATCH 02/19] add new maps interface --- .../cubit/location_cubit/location_cubit.dart | 119 +++++- lib/presentation/hike/hike_screen.dart | 381 ++++++++---------- .../hike/widgets/hike_screen_widget.dart | 257 +++--------- .../hike/widgets/search_places.dart | 6 +- lib/presentation/widgets/hike_button.dart | 4 +- pubspec.lock | 168 ++++++++ pubspec.yaml | 4 + 7 files changed, 508 insertions(+), 431 deletions(-) diff --git a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart index 82dbc6c..06464b5 100644 --- a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart +++ b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart @@ -1,7 +1,10 @@ import 'dart:async'; import 'dart:convert'; import 'dart:developer'; -import 'dart:ui'; +import 'dart:typed_data'; +import 'dart:ui' as ui; +import 'package:http/http.dart' as http; +import 'package:flutter/services.dart'; import 'package:beacon/core/resources/data_state.dart'; import 'package:beacon/core/utils/constants.dart'; import 'package:beacon/domain/entities/beacon/beacon_entity.dart'; @@ -13,7 +16,6 @@ import 'package:beacon/domain/usecase/hike_usecase.dart'; import 'package:beacon/locator.dart'; import 'package:beacon/presentation/hike/cubit/location_cubit/location_state.dart'; import 'package:beacon/presentation/hike/cubit/panel_cubit/panel_cubit.dart'; -import 'package:beacon/presentation/widgets/custom_label_marker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animarker/core/ripple_marker.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -21,7 +23,6 @@ import 'package:flutter_polyline_points/flutter_polyline_points.dart'; import 'package:gap/gap.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:location/location.dart'; -import 'package:http/http.dart' as http; import 'package:responsive_sizer/responsive_sizer.dart'; import 'package:vibration/vibration.dart'; @@ -63,6 +64,29 @@ class LocationCubit extends Cubit { mapController = controller; } + void centerMap() { + Location location = new Location(); + location.changeSettings( + interval: 5000, accuracy: LocationAccuracy.high, distanceFilter: 0); + + _streamLocaitonData = + location.onLocationChanged.listen((LocationData newPosition) async { + var latLng = locationDataToLatLng(newPosition); + + mapController?.animateCamera(CameraUpdate.newCameraPosition( + CameraPosition(target: latLng, zoom: 15), + )); + }); + } + + void zoomIn() { + mapController?.animateCamera(CameraUpdate.zoomIn()); + } + + void zoomOut() { + mapController?.animateCamera(CameraUpdate.zoomOut()); + } + Future loadBeaconData( BeaconEntity beacon, TickerProvider vsync, BuildContext context) async { this.vsync = vsync; @@ -675,7 +699,7 @@ class LocationCubit extends Cubit { _hikeMarkers.where((element) => element.markerId == markerId); if (existingMarkers.isEmpty) { - var newMarker = await createMarker(landMark); + var newMarker = await createMarkerWithCircularNetworkImage(landMark); _hikeMarkers.add(newMarker); } else { // If the marker exists, update its position @@ -717,25 +741,94 @@ class LocationCubit extends Cubit { } } - Future createMarker(LandMarkEntity landmark) async { - final pictureRecorder = PictureRecorder(); - final canvas = Canvas(pictureRecorder); - final customMarker = CustomMarker(text: landmark.title!); - customMarker.paint(canvas, Size(100, 100)); - final picture = pictureRecorder.endRecording(); - final image = await picture.toImage(100, 100); - final bytes = await image.toByteData(format: ImageByteFormat.png); + Future createMarkerWithCircularNetworkImage( + LandMarkEntity landmark) async { + final Uint8List markerIcon = await getCircularImageWithBorderAndPointer( + "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRuNhTZJTtkR6b-ADMhmzPvVwaLuLdz273wvQ&s", + size: 80, + borderColor: Colors.deepPurple, + borderWidth: 4, + ); return Marker( markerId: MarkerId(landmark.id!.toString()), position: locationToLatLng(landmark.location!), - icon: BitmapDescriptor.bytes(bytes!.buffer.asUint8List()), + icon: BitmapDescriptor.fromBytes(markerIcon), infoWindow: InfoWindow( title: 'Created by: ${landmark.createdBy?.name ?? 'Anonymous'}', ), ); } + Future getCircularImageWithBorderAndPointer( + String imageUrl, { + int size = 150, + Color borderColor = Colors.red, + double borderWidth = 6, + }) async { + final http.Response response = await http.get(Uri.parse(imageUrl)); + final Uint8List bytes = response.bodyBytes; + + final ui.Codec codec = await ui.instantiateImageCodec( + bytes, + targetWidth: size, + targetHeight: size, + ); + final ui.FrameInfo frame = await codec.getNextFrame(); + final ui.Image image = frame.image; + + final double radius = size / 2; + final double triangleHeight = size * 0.35; // Increase triangle height here + final double totalHeight = size + triangleHeight; + + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final Canvas canvas = Canvas(recorder); + final Paint paint = Paint()..isAntiAlias = true; + + final Offset circleCenter = Offset(radius, radius); + + // 1. Draw triangle FIRST (so it goes behind the circle) + paint + ..style = PaintingStyle.fill + ..color = borderColor; + + final Path trianglePath = Path() + ..moveTo(radius - 25, size.toDouble() - 10) // narrower base + ..lineTo(radius + 25, size.toDouble() - 10) + ..lineTo(radius, size + triangleHeight - 10) + ..close(); + + canvas.drawPath(trianglePath, paint); + + // 2. Draw white background circle + paint.color = Colors.white; + canvas.drawCircle(circleCenter, radius, paint); + + // 3. Clip and draw circular image + Path clipPath = Path() + ..addOval( + Rect.fromCircle(center: circleCenter, radius: radius - borderWidth)); + canvas.save(); + canvas.clipPath(clipPath); + canvas.drawImage(image, Offset.zero, Paint()); + canvas.restore(); + + // 4. Draw circular border + paint + ..color = borderColor + ..style = PaintingStyle.stroke + ..strokeWidth = borderWidth; + canvas.drawCircle(circleCenter, radius - borderWidth / 2, paint); + + // 5. Convert to PNG bytes + final ui.Image finalImage = + await recorder.endRecording().toImage(size, totalHeight.toInt()); + final ByteData? byteData = + await finalImage.toByteData(format: ui.ImageByteFormat.png); + + return byteData!.buffer.asUint8List(); + } + void changeGeofenceRadius(double radius, LatLng center) { var index = _geofence .toList() diff --git a/lib/presentation/hike/hike_screen.dart b/lib/presentation/hike/hike_screen.dart index fd5ebe6..8ef8a28 100644 --- a/lib/presentation/hike/hike_screen.dart +++ b/lib/presentation/hike/hike_screen.dart @@ -2,20 +2,21 @@ import 'package:auto_route/auto_route.dart'; import 'package:beacon/config/pip_manager.dart'; import 'package:beacon/core/utils/constants.dart'; import 'package:beacon/domain/entities/beacon/beacon_entity.dart'; -import 'package:beacon/domain/entities/user/user_entity.dart'; import 'package:beacon/locator.dart'; +import 'package:beacon/presentation/auth/auth_cubit/auth_cubit.dart'; import 'package:beacon/presentation/hike/cubit/hike_cubit/hike_cubit.dart'; import 'package:beacon/presentation/hike/cubit/hike_cubit/hike_state.dart'; import 'package:beacon/presentation/hike/cubit/location_cubit/location_cubit.dart'; import 'package:beacon/presentation/hike/cubit/location_cubit/location_state.dart'; -import 'package:beacon/presentation/hike/cubit/panel_cubit/panel_cubit.dart'; -import 'package:beacon/presentation/hike/cubit/panel_cubit/panel_state.dart'; + import 'package:beacon/presentation/hike/widgets/hike_screen_widget.dart'; +import 'package:beacon/presentation/hike/widgets/search_places.dart'; +import 'package:beacon/presentation/widgets/hike_button.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; -import 'package:gap/gap.dart'; + import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; import 'package:simple_pip_mode/pip_widget.dart'; @@ -85,49 +86,154 @@ class _HikeScreenState extends State ); } else { return Scaffold( - body: Stack( - children: [ - SlidingUpPanel( - onPanelOpened: () { - setState(() {}); - }, - borderRadius: BorderRadius.only( - topRight: Radius.circular(15), - topLeft: Radius.circular(15), - ), - controller: _panelController, - maxHeight: 60.h, - minHeight: isSmallsized ? 22.h : 20.h, - panel: _SlidingPanelWidget(), - collapsed: _collapsedWidget(), - body: _mapScreen()), - Align( - alignment: Alignment(-0.9, -0.9), - child: FloatingActionButton( - heroTag: 'BackButton', - backgroundColor: kYellow, + appBar: AppBar( + backgroundColor: Colors.grey[50], + leading: IconButton( + icon: Icon(Icons.arrow_back_ios_new, + size: 20, color: Colors.grey), onPressed: () { - SimplePip().enterPipMode(); + _panelController.close(); + appRouter.maybePop(); }, - child: Icon( - CupertinoIcons.back, - color: kBlue, - ), ), + centerTitle: true, + title: Image.asset( + 'images/beacon_logo.png', + height: 28, + ), + actions: [ + IconButton( + icon: const Icon(Icons.power_settings_new, + color: Colors.grey), + onPressed: () => showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Color(0xffFAFAFA), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(12.0), + ), + title: Text('Logout', + style: Style.heading), + content: Text( + 'Are you sure you want to logout?', + style: TextStyle( + fontSize: 16, color: kBlack), + ), + actions: [ + HikeButton( + buttonWidth: 80, + buttonHeight: 40, + isDotted: true, + onTap: () => AutoRouter.of(context) + .maybePop(false), + text: 'No', + textSize: 18.0, + ), + SizedBox( + height: 5, + ), + HikeButton( + buttonWidth: 80, + buttonHeight: 40, + onTap: () async { + appRouter.replaceNamed('/auth'); + localApi.deleteUser(); + context + .read() + .googleSignOut(); + }, + text: 'Yes', + textSize: 18.0, + ), + ], + ))), + ], ), - Align( - alignment: Alignment(0.85, -0.9), - child: HikeScreenWidget.shareButton( - context, widget.beacon.shortcode, widget.beacon)), - Align( - alignment: Alignment(1, -0.7), - child: HikeScreenWidget.showMapViewSelector(context)), - Align( - alignment: Alignment(0.85, -0.5), - child: HikeScreenWidget.sosButton( - widget.beacon.id!, context)), - ], - )); + body: Stack( + children: [ + _mapScreen(), + LocationSearchWidget(), + Positioned( + bottom: 200, + right: 10, + child: Column( + children: [ + FloatingActionButton( + backgroundColor: Colors.white, + mini: true, + onPressed: () => _locationCubit.zoomIn(), + child: Icon(Icons.add), + ), + //SizedBox(height: 2), + FloatingActionButton( + backgroundColor: Colors.white, + mini: true, + onPressed: () => _locationCubit.zoomOut(), + child: Icon(Icons.remove), + ), + SizedBox(height: 2), + FloatingActionButton( + backgroundColor: Colors.white, + mini: true, + onPressed: () => _locationCubit.centerMap(), + child: Icon(Icons.map), + ), + ], + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + height: 100, + child: Container( + height: 100, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Left Circular Icon Button + Container( + width: 50, + height: 50, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + ), + ], + image: DecorationImage( + image: NetworkImage( + "https://media.istockphoto.com/id/1253926432/vector/flashlight-warning-alarm-light-and-siren-light-flat-design-vector-design.jpg?s=612x612&w=0&k=20&c=yOj6Jpu7XDrPJCTfUIpQm-LWI9q9RWQB91s-N7CgQDQ="))), + ), + + const SizedBox(width: 10), + + // Right Red SOS Button + HikeButton( + buttonWidth: 70.w, + buttonHeight: 50, + text: 'Send SOS', + textSize: 18.0, + buttonColor: Colors.red, + textColor: Colors.white, + ) + ], + ), + )), + ], + )); } }, ); @@ -152,20 +258,19 @@ class _HikeScreenState extends State ); } else if (state is LoadedLocationState) { return GoogleMap( - circles: state.geofence, - polylines: state.polyline, - mapType: state.mapType, - compassEnabled: true, - onTap: (latlng) { - _panelController.close(); - HikeScreenWidget.showCreateLandMarkDialogueDialog( - context, widget.beacon.id!, latlng); - }, - zoomControlsEnabled: true, - onMapCreated: _locationCubit.onMapCreated, - markers: state.locationMarkers, - initialCameraPosition: CameraPosition( - zoom: 15, target: state.locationMarkers.first.position)); + circles: state.geofence, + polylines: state.polyline, + onLongPress: (latlng) { + HikeScreenWidget.showCreateLandMarkDialogueDialog( + context, widget.beacon.id!, latlng); + }, + onMapCreated: _locationCubit.onMapCreated, + markers: state.locationMarkers, + initialCameraPosition: CameraPosition( + zoom: 15, + target: state.locationMarkers.first.position, + ), + ); } return Center( child: GestureDetector( @@ -178,166 +283,4 @@ class _HikeScreenState extends State }, ); } - - Widget _collapsedWidget() { - var beacon = widget.beacon; - return Container( - padding: EdgeInsets.symmetric(horizontal: 20, vertical: 10), - decoration: BoxDecoration( - color: kBlue, - borderRadius: BorderRadius.only( - topRight: Radius.circular(10), - topLeft: Radius.circular(10), - ), - ), - child: BlocBuilder( - builder: (context, state) { - return state.when( - initial: () { - return SpinKitCircle(color: kYellow); - }, - loaded: ( - isActive, - expiringTime, - leaderAddress, - leader, - followers, - message, - ) { - followers = followers ?? []; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - alignment: Alignment.center, - child: Container( - alignment: Alignment.center, - height: 0.8.h, - width: 18.w, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: - BorderRadius.all(Radius.circular(10))), - ), - ), - Gap(10), - Text( - isActive == true - ? 'Beacon expiring at ${expiringTime ?? '<>'}' - : 'Beacon is expired', - style: TextStyle( - fontSize: 18, - color: Colors.white, - fontFamily: '', - fontWeight: FontWeight.w700)), - Gap(2), - Text('Beacon leader at: ${leaderAddress ?? '<>'}', - maxLines: 2, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: 16, - color: Colors.white, - fontFamily: '', - fontWeight: FontWeight.w600)), - Gap(1.5), - Text('Total followers: ${followers.length} ', - style: TextStyle( - fontSize: 16, - color: Colors.white, - fontFamily: '', - fontWeight: FontWeight.w500)), - Gap(1), - Text('Share the pass key to join user: ${beacon.shortcode}', - style: TextStyle( - fontSize: 15, - color: Colors.white, - fontFamily: '', - fontWeight: FontWeight.w500)) - ], - ); - }, - error: (message) { - return Text(message.toString()); - }, - ); - }, - )); - } - - Widget _SlidingPanelWidget() { - // return Container(); - return BlocBuilder( - builder: (context, state) { - return state.when( - initial: () { - return CircularProgressIndicator(); - }, - loaded: ( - isActive, - expiringTime, - leaderAddress, - leader, - followers, - message, - ) { - List members = []; - members.add(leader!); - if (followers != null) { - followers.forEach((element) { - members.add(element!); - }); - } - - return ListView.builder( - physics: NeverScrollableScrollPhysics(), - itemCount: members.length, - itemBuilder: (context, index) { - var member = members[index]; - return Container( - padding: EdgeInsets.symmetric(vertical: 5), - child: Row( - children: [ - Gap(10), - CircleAvatar( - radius: 25, - backgroundColor: kYellow, - child: Icon( - Icons.person_2_rounded, - color: Colors.white, - size: 40, - ), - ), - Gap(10), - Text( - member.name ?? 'Anonymous', - style: TextStyle(fontSize: 19), - ), - Spacer(), - Container( - height: 40, - width: 40, - child: FloatingActionButton( - backgroundColor: kYellow, - onPressed: () async { - _locationCubit - .changeCameraPosition(member.id ?? ''); - _panelController.close(); - }, - child: Icon(Icons.location_city), - ), - ), - Gap(10), - ], - ), - ); - }, - ); - }, - error: (message) { - return Text(message.toString()); - }, - ); - }, - ); - } } diff --git a/lib/presentation/hike/widgets/hike_screen_widget.dart b/lib/presentation/hike/widgets/hike_screen_widget.dart index 5afd9e7..40842b5 100644 --- a/lib/presentation/hike/widgets/hike_screen_widget.dart +++ b/lib/presentation/hike/widgets/hike_screen_widget.dart @@ -163,40 +163,6 @@ class HikeScreenWidget { ); } - static final Map mapTypeNames = { - MapType.hybrid: 'Hybrid', - MapType.normal: 'Normal', - MapType.satellite: 'Satellite', - MapType.terrain: 'Terrain', - }; - - static MapType _selectedMapType = MapType.normal; - - static Widget showMapViewSelector(BuildContext context) { - return DropdownButton( - icon: null, - value: _selectedMapType, - items: mapTypeNames.entries.map((entry) { - return DropdownMenuItem( - onTap: () { - locator().changeMap(entry.key); - }, - value: entry.key, - child: Text(entry.value), - ); - }).toList(), - onChanged: (newValue) {}, - selectedItemBuilder: (BuildContext context) { - return mapTypeNames.entries.map((entry) { - return FloatingActionButton( - backgroundColor: kYellow, - onPressed: null, - child: Icon(CupertinoIcons.map), - ); - }).toList(); - }, - ); - } static TextEditingController _landMarkeController = TextEditingController(); static GlobalKey _landmarkFormKey = GlobalKey(); @@ -233,179 +199,82 @@ class HikeScreenWidget { ); } - // static GlobalKey _geofenceKey = GlobalKey(); - // static double _value = 0.1; - - // static void showCreateGeofenceDialogueDialog( - // BuildContext context, - // String beaconId, - // LatLng loc, - // ) { - // bool isSmallSized = 100.h < 800; - // bool isGeofenceCreated = false; - - // showModalBottomSheet( - // context: context, - // isDismissible: false, - // builder: (context) { - // return Stack( - // children: [ - // Container( - // height: isSmallSized ? 30.h : 25.h, - // decoration: BoxDecoration( - // color: Colors.white, - // borderRadius: BorderRadius.only( - // topLeft: Radius.circular(20), - // topRight: Radius.circular(20), - // ), - // ), - // child: Padding( - // padding: - // const EdgeInsets.symmetric(horizontal: 32, vertical: 16), - // child: Form( - // key: _geofenceKey, - // child: Column( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Container( - // alignment: Alignment.topRight, - // child: IconButton( - // onPressed: () { - // appRouter.maybePop().then((value) { - // locator() - // .removeUncreatedGeofence(); - // }); - // }, - // icon: Icon( - // Icons.cancel, - // color: kBlue, - // )), - // ), - // StatefulBuilder( - // builder: (context, setState) { - // return Stack( - // children: [ - // Slider( - // activeColor: kBlue, - // inactiveColor: kYellow, - // value: _value, - // onChanged: (value) { - // setState(() { - // _value = value; - // }); - // locator() - // .changeGeofenceRadius(_value * 1000, loc); - // }, - // ), - // Align( - // alignment: Alignment(1, -1), - // child: Text( - // '${(_value * 1000).toStringAsFixed(0)} meters', - // ), - // ), - // ], - // ); - // }, - // ), - // Gap(10), - // Flexible( - // child: HikeButton( - // buttonHeight: 15, - // onTap: () async { - // if (!_geofenceKey.currentState!.validate()) return; - // locator() - // .createGeofence(beaconId, loc, _value) - // .then((onvalue) { - // isGeofenceCreated = true; - // }); - // appRouter.maybePop().then((onValue) { - // if (isGeofenceCreated) { - // locator() - // .removeUncreatedGeofence(); - // } - // }); - // }, - // text: 'Create Geofence', - // ), - // ), - // ], - // ), - // ), - // ), - // ), - // ], - // ); - // }, - // ); - // } - static void showCreateLandMarkDialogueDialog( BuildContext context, String beaconId, LatLng loc, ) { + final size = MediaQuery.of(context).size; + final isSmallScreen = size.height < 800; + showDialog( context: context, builder: (context) => Dialog( + backgroundColor: Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ), child: Container( - height: MediaQuery.of(context).size.height < 800 ? 33.h : 25.h, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 16), - child: Form( - key: _landmarkFormKey, - child: Column( - children: [ - Container( - height: - MediaQuery.of(context).size.height < 800 ? 14.h : 12.h, - child: Padding( - padding: const EdgeInsets.all(4.0), - child: TextFormField( - controller: _landMarkeController, - style: TextStyle(fontSize: 20.0), - onChanged: (key) {}, - validator: (value) { - if (value == null || value.isEmpty) { - return "Please enter title for landmark"; - } else { - return null; - } - }, - decoration: InputDecoration( - border: InputBorder.none, - alignLabelWithHint: true, - floatingLabelBehavior: FloatingLabelBehavior.always, - hintText: 'Add title for the landmark', - hintStyle: - TextStyle(fontSize: hintsize, color: hintColor), - labelText: 'Title', - labelStyle: - TextStyle(fontSize: labelsize, color: kYellow), - ), - ), - ), + width: size.width * 0.85, // Set a reasonable width + height: isSmallScreen ? size.height * 0.25 : size.height * 0.2, + padding: const EdgeInsets.all(24), + child: Form( + key: _landmarkFormKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + height: 60, + decoration: BoxDecoration( color: kLightBlue, + borderRadius: BorderRadius.circular(8), ), - SizedBox( - height: 2.h, - ), - Flexible( - child: HikeButton( - text: 'Create Landmark', - textSize: 14.0, - textColor: Colors.white, - buttonColor: kYellow, - onTap: () { - if (!_landmarkFormKey.currentState!.validate()) return; - appRouter.maybePop(); - locator().createLandmark( - beaconId, _landMarkeController.text, loc); - _landMarkeController.clear(); - }, + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: TextFormField( + controller: _landMarkeController, + style: TextStyle(fontSize: 18.0), + validator: (value) { + if (value == null || value.isEmpty) { + return "Please enter title for landmark"; + } + return null; + }, + decoration: InputDecoration( + hintText: 'Enter Landmark Title', + labelText: 'Title', + labelStyle: TextStyle( + fontSize: 14, + color: Theme.of(context).primaryColor, + ), + hintStyle: + TextStyle(fontSize: 16, color: Colors.grey[400]), + alignLabelWithHint: true, + floatingLabelBehavior: FloatingLabelBehavior.always, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + contentPadding: EdgeInsets.zero, ), ), - ], - ), + ), + SizedBox(height: 16), + HikeButton( + buttonHeight: 5.5.h, + buttonWidth: 50.w, + textSize: 14.0, + textColor: Colors.white, + onTap: () { + if (!_landmarkFormKey.currentState!.validate()) return; + appRouter.maybePop(); + locator().createLandmark( + beaconId, _landMarkeController.text.trim(), loc); + _landMarkeController.clear(); + }, + text: 'Create Landmark', + ) + ], ), ), ), diff --git a/lib/presentation/hike/widgets/search_places.dart b/lib/presentation/hike/widgets/search_places.dart index 40bed5e..40d24bf 100644 --- a/lib/presentation/hike/widgets/search_places.dart +++ b/lib/presentation/hike/widgets/search_places.dart @@ -8,7 +8,7 @@ class LocationSearchWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10), + margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: TypeAheadField( suggestionsCallback: (pattern) async { return await geoapifyService.getLocationSuggestions(pattern); @@ -16,8 +16,8 @@ class LocationSearchWidget extends StatelessWidget { builder: (context, controller, focusNode) { return Container( decoration: BoxDecoration( - color: Colors.white, borderRadius: BorderRadius.circular(20)), - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + color: Colors.white, borderRadius: BorderRadius.circular(50)), + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), child: TextFormField( controller: controller, focusNode: focusNode, diff --git a/lib/presentation/widgets/hike_button.dart b/lib/presentation/widgets/hike_button.dart index a978765..538922b 100644 --- a/lib/presentation/widgets/hike_button.dart +++ b/lib/presentation/widgets/hike_button.dart @@ -50,7 +50,7 @@ class HikeButton extends StatelessWidget { ? Colors.transparent : isDisabled! ? Colors.grey - : Colors.teal, + : buttonColor, borderRadius: BorderRadius.circular(12), ), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ @@ -65,7 +65,7 @@ class HikeButton extends StatelessWidget { ? Colors.teal : isDisabled! ? Colors.white - : Colors.black, + : textColor, ), ), ]), diff --git a/pubspec.lock b/pubspec.lock index 8c17ced..caa5c7e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,6 +22,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.11.0" + animate_do: + dependency: "direct main" + description: + name: animate_do + sha256: e5c8b92e8495cba5adfff17c0b017d50f46b2766226e9faaf68bc08c91aef034 + url: "https://pub.dev" + source: hosted + version: "4.2.0" archive: dependency: transitive description: @@ -427,6 +435,54 @@ packages: url: "https://pub.dev" source: hosted version: "0.18.6" + flutter_keyboard_visibility: + dependency: transitive + description: + name: flutter_keyboard_visibility + sha256: "98664be7be0e3ffca00de50f7f6a287ab62c763fc8c762e0a21584584a3ff4f8" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_keyboard_visibility_linux: + dependency: transitive + description: + name: flutter_keyboard_visibility_linux + sha256: "6fba7cd9bb033b6ddd8c2beb4c99ad02d728f1e6e6d9b9446667398b2ac39f08" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_macos: + dependency: transitive + description: + name: flutter_keyboard_visibility_macos + sha256: c5c49b16fff453dfdafdc16f26bdd8fb8d55812a1d50b0ce25fc8d9f2e53d086 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + flutter_keyboard_visibility_platform_interface: + dependency: transitive + description: + name: flutter_keyboard_visibility_platform_interface + sha256: e43a89845873f7be10cb3884345ceb9aebf00a659f479d1c8f4293fcb37022a4 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_web: + dependency: transitive + description: + name: flutter_keyboard_visibility_web + sha256: d3771a2e752880c79203f8d80658401d0c998e4183edca05a149f5098ce6e3d1 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + flutter_keyboard_visibility_windows: + dependency: transitive + description: + name: flutter_keyboard_visibility_windows + sha256: fc4b0f0b6be9b93ae527f3d527fb56ee2d918cd88bbca438c478af7bcfd0ef73 + url: "https://pub.dev" + source: hosted + version: "1.0.0" flutter_launcher_icons: dependency: "direct dev" description: @@ -459,6 +515,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.2.0" + flutter_map: + dependency: "direct main" + description: + name: flutter_map + sha256: "52c65a977daae42f9aae6748418dd1535eaf27186e9bac9bf431843082bc75a3" + url: "https://pub.dev" + source: hosted + version: "4.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -497,6 +561,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_typeahead: + dependency: "direct main" + description: + name: flutter_typeahead + sha256: d64712c65db240b1057559b952398ebb6e498077baeebf9b0731dade62438a6d + url: "https://pub.dev" + source: hosted + version: "5.2.0" flutter_web_plugins: dependency: transitive description: flutter @@ -878,6 +950,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.9.0" + latlong2: + dependency: "direct main" + description: + name: latlong2 + sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0" + url: "https://pub.dev" + source: hosted + version: "0.8.2" leak_tracker: dependency: transitive description: @@ -902,6 +982,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + lists: + dependency: transitive + description: + name: lists + sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27" + url: "https://pub.dev" + source: hosted + version: "1.0.1" location: dependency: "direct main" description: @@ -974,6 +1062,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + mgrs_dart: + dependency: transitive + description: + name: mgrs_dart + sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7 + url: "https://pub.dev" + source: hosted + version: "2.0.0" mime: dependency: transitive description: @@ -1150,6 +1246,46 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.8" + pointer_interceptor: + dependency: transitive + description: + name: pointer_interceptor + sha256: "57210410680379aea8b1b7ed6ae0c3ad349bfd56fe845b8ea934a53344b9d523" + url: "https://pub.dev" + source: hosted + version: "0.10.1+2" + pointer_interceptor_ios: + dependency: transitive + description: + name: pointer_interceptor_ios + sha256: a6906772b3205b42c44614fcea28f818b1e5fdad73a4ca742a7bd49818d9c917 + url: "https://pub.dev" + source: hosted + version: "0.10.1" + pointer_interceptor_platform_interface: + dependency: transitive + description: + name: pointer_interceptor_platform_interface + sha256: "0597b0560e14354baeb23f8375cd612e8bd4841bf8306ecb71fcd0bb78552506" + url: "https://pub.dev" + source: hosted + version: "0.10.0+1" + pointer_interceptor_web: + dependency: transitive + description: + name: pointer_interceptor_web + sha256: "460b600e71de6fcea2b3d5f662c92293c049c4319e27f0829310e5a953b3ee2a" + url: "https://pub.dev" + source: hosted + version: "0.10.3" + polylabel: + dependency: transitive + description: + name: polylabel + sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b" + url: "https://pub.dev" + source: hosted + version: "1.0.1" pool: dependency: transitive description: @@ -1158,6 +1294,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + proj4dart: + dependency: transitive + description: + name: proj4dart + sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e + url: "https://pub.dev" + source: hosted + version: "2.1.0" provider: dependency: "direct main" description: @@ -1467,6 +1611,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" typed_data: dependency: transitive description: @@ -1499,6 +1651,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.0" + unicode: + dependency: transitive + description: + name: unicode + sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1" + url: "https://pub.dev" + source: hosted + version: "0.3.1" universal_platform: dependency: transitive description: @@ -1627,6 +1787,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.5" + wkt_parser: + dependency: transitive + description: + name: wkt_parser + sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13" + url: "https://pub.dev" + source: hosted + version: "2.0.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7aa8812..c89fcf4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,6 +9,10 @@ environment: sdk: ">=3.3.0 <4.0.0" dependencies: + animate_do: + flutter_map: + latlong2: + flutter_typeahead: ^5.2.0 connectivity_plus: any cupertino_icons: ^1.0.5 date_time_picker: ^2.1.0 From cfb03c18975c0d9a5b3cd9fc1687a1bba59d91de Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Mon, 23 Jun 2025 14:12:20 +0530 Subject: [PATCH 03/19] formatted hike screen widget --- lib/presentation/hike/widgets/hike_screen_widget.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/hike/widgets/hike_screen_widget.dart b/lib/presentation/hike/widgets/hike_screen_widget.dart index 40842b5..fdfc4d6 100644 --- a/lib/presentation/hike/widgets/hike_screen_widget.dart +++ b/lib/presentation/hike/widgets/hike_screen_widget.dart @@ -163,7 +163,6 @@ class HikeScreenWidget { ); } - static TextEditingController _landMarkeController = TextEditingController(); static GlobalKey _landmarkFormKey = GlobalKey(); From 2752e0ca078b94a1246a63695647429702db322e Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Thu, 3 Jul 2025 15:31:26 +0530 Subject: [PATCH 04/19] image selection and bottom navigation bar --- .env.example | 4 - lib/config/router/router.dart | 1 + lib/core/queries/auth.dart | 2 + lib/core/queries/beacon.dart | 18 + lib/core/queries/group.dart | 26 + .../datasource/remote/remote_hike_api.dart | 4 + .../datasource/remote/remote_home_api.dart | 16 + lib/data/models/user/user_model.dart | 46 +- lib/data/models/user/user_model.g.dart | 9 +- .../home_repository_implementation.dart | 5 + .../beacon/beacon_entity.freezed.dart | 23 +- .../entities/group/group_entity.freezed.dart | 19 +- .../landmark/landmark_entity.freezed.dart | 21 +- .../location/location_entity.freezed.dart | 17 +- .../beacon_locations_entity.freezed.dart | 23 +- .../join_leave_beacon_entity.freezed.dart | 21 +- .../updated_group_entity.freezed.dart | 25 +- lib/domain/entities/user/user_entity.dart | 43 +- .../entities/user/user_entity.freezed.dart | 55 +- lib/domain/repositories/home_repository.dart | 1 + lib/domain/usecase/home_usecase.dart | 4 + lib/firebase_options.dart | 28 +- .../auth/auth_cubit/auth_cubit.dart | 2 +- .../auth/auth_cubit/auth_state.freezed.dart | 34 +- .../verification_state.freezed.dart | 29 +- .../group_cubit/group_state.freezed.dart | 56 +- .../members_cubit/members_state.freezed.dart | 20 +- lib/presentation/group/group_screen.dart | 15 +- .../cubit/hike_cubit/hike_state.freezed.dart | 30 +- .../cubit/location_cubit/location_cubit.dart | 8 +- .../location_state.freezed.dart | 28 +- .../panel_cubit/panel_state.freezed.dart | 30 +- lib/presentation/hike/hike_screen.dart | 6 +- lib/presentation/hike/stack.dart | 32 + .../home/home_cubit/home_cubit.dart | 12 + .../home/home_cubit/home_state.freezed.dart | 23 +- lib/presentation/home/home_screen.dart | 649 ++++++++++-------- lib/presentation/home/profile_screen.dart | 199 ++++++ lib/presentation/home/widgets/group_card.dart | 2 + lib/presentation/widgets/hike_button.dart | 2 +- 40 files changed, 1190 insertions(+), 398 deletions(-) delete mode 100644 .env.example create mode 100644 lib/presentation/hike/stack.dart create mode 100644 lib/presentation/home/profile_screen.dart diff --git a/.env.example b/.env.example deleted file mode 100644 index b4df0d9..0000000 --- a/.env.example +++ /dev/null @@ -1,4 +0,0 @@ -ANDROID_MAP_API_KEY= -IOS_MAP_API_KEY= -HTTP_ENDPOINT= -WEBSOCKET_ENDPOINT= \ No newline at end of file diff --git a/lib/config/router/router.dart b/lib/config/router/router.dart index 7ff1777..2f197b4 100644 --- a/lib/config/router/router.dart +++ b/lib/config/router/router.dart @@ -25,5 +25,6 @@ class AppRouter extends _$AppRouter { AutoRoute( page: VerificationScreenRoute.page, ), + ]; } diff --git a/lib/core/queries/auth.dart b/lib/core/queries/auth.dart index b665027..ca74e6d 100644 --- a/lib/core/queries/auth.dart +++ b/lib/core/queries/auth.dart @@ -7,6 +7,7 @@ class AuthQueries { _id name email + imageUrl } } '''; @@ -85,6 +86,7 @@ class AuthQueries { email name isVerified + imageUrl groups{ _id } diff --git a/lib/core/queries/beacon.dart b/lib/core/queries/beacon.dart index ea2735f..ef19ef5 100644 --- a/lib/core/queries/beacon.dart +++ b/lib/core/queries/beacon.dart @@ -247,6 +247,7 @@ deleteBeacon(id: "$id") lat lon } + imageUrl } landmarks{ _id @@ -258,6 +259,7 @@ deleteBeacon(id: "$id") createdBy{ _id name + imageUrl } } location{ @@ -414,6 +416,7 @@ deleteBeacon(id: "$id") createdBy{ _id name + imageUrl } } } @@ -448,6 +451,7 @@ deleteBeacon(id: "$id") updatedUser{ _id name + imageUrl location{ lat lon @@ -465,10 +469,24 @@ deleteBeacon(id: "$id") _id name email + imageUrl } } } } '''); + + String updateUserImage(String? imageUrl) { + return ''' + mutation{ + updateUserImage(imageUrl: "$imageUrl"){ + _id + name + email + imageUrl + } + } + '''; + } } diff --git a/lib/core/queries/group.dart b/lib/core/queries/group.dart index 06cea68..bae132b 100644 --- a/lib/core/queries/group.dart +++ b/lib/core/queries/group.dart @@ -17,6 +17,7 @@ class GroupQueries { members{ _id name + imageUrl } shortcode __typename @@ -122,10 +123,12 @@ class GroupQueries { leader { _id name + imageUrl } members { _id name + imageUrl } beacons { @@ -146,6 +149,7 @@ query{ leader { _id name + imageUrl } location{ lat @@ -154,6 +158,7 @@ query{ followers { _id name + imageUrl } group{ _id @@ -178,10 +183,12 @@ query{ leader{ _id name + imageUrl } members{ _id name + imageUrl } shortcode __typename @@ -200,6 +207,7 @@ query{ _id name email + imageUrl } newBeacon{ @@ -209,10 +217,12 @@ query{ _id name email + imageUrl } followers { _id name + imageUrl } group{ _id @@ -257,10 +267,12 @@ query{ _id name email + imageUrl } followers { _id name + imageUrl } group{ _id @@ -300,4 +312,18 @@ query{ } } '''); + + String updateUserImage(String userId, String? imageUrl) { + return ''' + mutation{ + updateUserImage(userId: "${userId}", imageUrl: "$imageUrl") + { + _id + name + email + imageUrl + } + } + '''; + } } diff --git a/lib/data/datasource/remote/remote_hike_api.dart b/lib/data/datasource/remote/remote_hike_api.dart index 1317f01..d520542 100644 --- a/lib/data/datasource/remote/remote_hike_api.dart +++ b/lib/data/datasource/remote/remote_hike_api.dart @@ -33,6 +33,8 @@ class RemoteHikeApi { final result = await _authClient.mutate(MutationOptions( document: gql(beaconQueries.fetchBeaconDetail(beaconId)))); + print("Result fetch beacon queries: ${result.data}"); + if (result.isConcrete && result.data != null) { final beaconJson = result.data!['beacon']; @@ -92,6 +94,8 @@ class RemoteHikeApi { final result = await _authClient.mutate(MutationOptions( document: gql(beaconQueries.createLandmark(id, lat, lon, title)))); + print("Result: ${result.data}"); + if (result.isConcrete && result.data != null && result.data!['createLandmark'] != null) { diff --git a/lib/data/datasource/remote/remote_home_api.dart b/lib/data/datasource/remote/remote_home_api.dart index e183ece..3d3969b 100644 --- a/lib/data/datasource/remote/remote_home_api.dart +++ b/lib/data/datasource/remote/remote_home_api.dart @@ -204,4 +204,20 @@ class RemoteHomeApi { return exception.graphqlErrors[0].message.toString(); } } + + Future> updateUserImage( + String userId, String? imageUrl) async { + bool isConnected = await utils.checkInternetConnectivity(); + if (!isConnected) { + return DataFailed('No internet connection'); + } + + final result = await _authClient.mutate(MutationOptions( + document: gql(_groupQueries.updateUserImage(userId, imageUrl)))); + + if (result.isConcrete && result.data != null) { + return DataSuccess(true); + } + return DataFailed(encounteredExceptionOrError(result.exception!)); + } } diff --git a/lib/data/models/user/user_model.dart b/lib/data/models/user/user_model.dart index 1c2b61d..3f8ebe8 100644 --- a/lib/data/models/user/user_model.dart +++ b/lib/data/models/user/user_model.dart @@ -38,16 +38,21 @@ class UserModel implements UserEntity { @HiveField(8) bool? isVerified; - UserModel( - {this.authToken, - this.beacons, - this.email, - this.groups, - this.id, - this.isGuest, - this.location, - this.name, - this.isVerified}); + @HiveField(9) // New field number (must be unique) + String? imageUrl; // Add this line + + UserModel({ + this.authToken, + this.beacons, + this.email, + this.groups, + this.id, + this.isGuest, + this.location, + this.name, + this.isVerified, + this.imageUrl, // Add this line + }); @override $UserEntityCopyWith get copyWith => throw UnimplementedError(); @@ -67,16 +72,19 @@ class UserModel implements UserEntity { List? groups, List? beacons, LocationModel? location, + String? imageUrl, // Add this line }) { return UserModel( - id: id ?? this.id, - name: name ?? this.name, - authToken: authToken ?? this.authToken, - email: email ?? this.email, - isGuest: isGuest ?? this.isGuest, - groups: groups ?? this.groups, - beacons: beacons ?? this.beacons, - location: location ?? this.location, - isVerified: isVerified ?? this.isVerified); + id: id ?? this.id, + name: name ?? this.name, + authToken: authToken ?? this.authToken, + email: email ?? this.email, + isGuest: isGuest ?? this.isGuest, + groups: groups ?? this.groups, + beacons: beacons ?? this.beacons, + location: location ?? this.location, + isVerified: isVerified ?? this.isVerified, + imageUrl: imageUrl ?? this.imageUrl, // Add this line + ); } } diff --git a/lib/data/models/user/user_model.g.dart b/lib/data/models/user/user_model.g.dart index 45bb58a..3102166 100644 --- a/lib/data/models/user/user_model.g.dart +++ b/lib/data/models/user/user_model.g.dart @@ -26,13 +26,14 @@ class UserModelAdapter extends TypeAdapter { location: fields[7] as LocationModel?, name: fields[1] as String?, isVerified: fields[8] as bool?, + imageUrl: fields[9] as String?, ); } @override void write(BinaryWriter writer, UserModel obj) { writer - ..writeByte(9) + ..writeByte(10) ..writeByte(0) ..write(obj.id) ..writeByte(1) @@ -50,7 +51,9 @@ class UserModelAdapter extends TypeAdapter { ..writeByte(7) ..write(obj.location) ..writeByte(8) - ..write(obj.isVerified); + ..write(obj.isVerified) + ..writeByte(9) + ..write(obj.imageUrl); } @override @@ -87,6 +90,7 @@ UserModel _$UserModelFromJson(Map json) => UserModel( : LocationModel.fromJson(json['location'] as Map), name: json['name'] as String?, isVerified: json['isVerified'] as bool?, + imageUrl: json['imageUrl'] as String?, ); Map _$UserModelToJson(UserModel instance) => { @@ -99,4 +103,5 @@ Map _$UserModelToJson(UserModel instance) => { 'beacons': instance.beacons, 'location': instance.location, 'isVerified': instance.isVerified, + 'imageUrl': instance.imageUrl, }; diff --git a/lib/data/repositories/home_repository_implementation.dart b/lib/data/repositories/home_repository_implementation.dart index 3804d64..164962e 100644 --- a/lib/data/repositories/home_repository_implementation.dart +++ b/lib/data/repositories/home_repository_implementation.dart @@ -40,4 +40,9 @@ class HomeRepostitoryImplementation implements HomeRepository { Future> changeShortcode(String groupId) { return remoteHomeApi.changeShortCode(groupId); } + + @override + Future> updateUserImage(String userId, String? imageUrl) { + return remoteHomeApi.updateUserImage(userId, imageUrl); + } } diff --git a/lib/domain/entities/beacon/beacon_entity.freezed.dart b/lib/domain/entities/beacon/beacon_entity.freezed.dart index a623a54..ce37706 100644 --- a/lib/domain/entities/beacon/beacon_entity.freezed.dart +++ b/lib/domain/entities/beacon/beacon_entity.freezed.dart @@ -28,7 +28,9 @@ mixin _$BeaconEntity { LocationEntity? get location => throw _privateConstructorUsedError; GroupEntity? get group => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of BeaconEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $BeaconEntityCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -67,6 +69,8 @@ class _$BeaconEntityCopyWithImpl<$Res, $Val extends BeaconEntity> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of BeaconEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -130,6 +134,8 @@ class _$BeaconEntityCopyWithImpl<$Res, $Val extends BeaconEntity> ) as $Val); } + /// Create a copy of BeaconEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $UserEntityCopyWith<$Res>? get leader { @@ -142,6 +148,8 @@ class _$BeaconEntityCopyWithImpl<$Res, $Val extends BeaconEntity> }); } + /// Create a copy of BeaconEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $LocationEntityCopyWith<$Res>? get location { @@ -154,6 +162,8 @@ class _$BeaconEntityCopyWithImpl<$Res, $Val extends BeaconEntity> }); } + /// Create a copy of BeaconEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $GroupEntityCopyWith<$Res>? get group { @@ -204,6 +214,8 @@ class __$$BeaconEntityImplCopyWithImpl<$Res> _$BeaconEntityImpl _value, $Res Function(_$BeaconEntityImpl) _then) : super(_value, _then); + /// Create a copy of BeaconEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -378,7 +390,9 @@ class _$BeaconEntityImpl implements _BeaconEntity { location, group); - @JsonKey(ignore: true) + /// Create a copy of BeaconEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$BeaconEntityImplCopyWith<_$BeaconEntityImpl> get copyWith => @@ -421,8 +435,11 @@ abstract class _BeaconEntity implements BeaconEntity { LocationEntity? get location; @override GroupEntity? get group; + + /// Create a copy of BeaconEntity + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$BeaconEntityImplCopyWith<_$BeaconEntityImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/domain/entities/group/group_entity.freezed.dart b/lib/domain/entities/group/group_entity.freezed.dart index 74507d0..fbd2290 100644 --- a/lib/domain/entities/group/group_entity.freezed.dart +++ b/lib/domain/entities/group/group_entity.freezed.dart @@ -25,7 +25,9 @@ mixin _$GroupEntity { bool get hasBeaconActivity => throw _privateConstructorUsedError; bool get hasMemberActivity => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of GroupEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $GroupEntityCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -59,6 +61,8 @@ class _$GroupEntityCopyWithImpl<$Res, $Val extends GroupEntity> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of GroupEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -107,6 +111,8 @@ class _$GroupEntityCopyWithImpl<$Res, $Val extends GroupEntity> ) as $Val); } + /// Create a copy of GroupEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $UserEntityCopyWith<$Res>? get leader { @@ -150,6 +156,8 @@ class __$$GroupEntityImplCopyWithImpl<$Res> _$GroupEntityImpl _value, $Res Function(_$GroupEntityImpl) _then) : super(_value, _then); + /// Create a copy of GroupEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -284,7 +292,9 @@ class _$GroupEntityImpl implements _GroupEntity { hasBeaconActivity, hasMemberActivity); - @JsonKey(ignore: true) + /// Create a copy of GroupEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$GroupEntityImplCopyWith<_$GroupEntityImpl> get copyWith => @@ -318,8 +328,11 @@ abstract class _GroupEntity implements GroupEntity { bool get hasBeaconActivity; @override bool get hasMemberActivity; + + /// Create a copy of GroupEntity + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$GroupEntityImplCopyWith<_$GroupEntityImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/domain/entities/landmark/landmark_entity.freezed.dart b/lib/domain/entities/landmark/landmark_entity.freezed.dart index e0aa695..0fe6e87 100644 --- a/lib/domain/entities/landmark/landmark_entity.freezed.dart +++ b/lib/domain/entities/landmark/landmark_entity.freezed.dart @@ -21,7 +21,9 @@ mixin _$LandMarkEntity { LocationEntity? get location => throw _privateConstructorUsedError; UserEntity? get createdBy => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of LandMarkEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $LandMarkEntityCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -52,6 +54,8 @@ class _$LandMarkEntityCopyWithImpl<$Res, $Val extends LandMarkEntity> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of LandMarkEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -80,6 +84,8 @@ class _$LandMarkEntityCopyWithImpl<$Res, $Val extends LandMarkEntity> ) as $Val); } + /// Create a copy of LandMarkEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $LocationEntityCopyWith<$Res>? get location { @@ -92,6 +98,8 @@ class _$LandMarkEntityCopyWithImpl<$Res, $Val extends LandMarkEntity> }); } + /// Create a copy of LandMarkEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $UserEntityCopyWith<$Res>? get createdBy { @@ -133,6 +141,8 @@ class __$$LandMarkEntityImplCopyWithImpl<$Res> _$LandMarkEntityImpl _value, $Res Function(_$LandMarkEntityImpl) _then) : super(_value, _then); + /// Create a copy of LandMarkEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -198,7 +208,9 @@ class _$LandMarkEntityImpl implements _LandMarkEntity { @override int get hashCode => Object.hash(runtimeType, id, title, location, createdBy); - @JsonKey(ignore: true) + /// Create a copy of LandMarkEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LandMarkEntityImplCopyWith<_$LandMarkEntityImpl> get copyWith => @@ -221,8 +233,11 @@ abstract class _LandMarkEntity implements LandMarkEntity { LocationEntity? get location; @override UserEntity? get createdBy; + + /// Create a copy of LandMarkEntity + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$LandMarkEntityImplCopyWith<_$LandMarkEntityImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/domain/entities/location/location_entity.freezed.dart b/lib/domain/entities/location/location_entity.freezed.dart index 833de3e..52427de 100644 --- a/lib/domain/entities/location/location_entity.freezed.dart +++ b/lib/domain/entities/location/location_entity.freezed.dart @@ -20,7 +20,9 @@ mixin _$LocationEntity { String? get lat => throw _privateConstructorUsedError; String? get lon => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of LocationEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $LocationEntityCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -44,6 +46,8 @@ class _$LocationEntityCopyWithImpl<$Res, $Val extends LocationEntity> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of LocationEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -87,6 +91,8 @@ class __$$LocationEntityImplCopyWithImpl<$Res> _$LocationEntityImpl _value, $Res Function(_$LocationEntityImpl) _then) : super(_value, _then); + /// Create a copy of LocationEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -141,7 +147,9 @@ class _$LocationEntityImpl implements _LocationEntity { @override int get hashCode => Object.hash(runtimeType, id, lat, lon); - @JsonKey(ignore: true) + /// Create a copy of LocationEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LocationEntityImplCopyWith<_$LocationEntityImpl> get copyWith => @@ -161,8 +169,11 @@ abstract class _LocationEntity implements LocationEntity { String? get lat; @override String? get lon; + + /// Create a copy of LocationEntity + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$LocationEntityImplCopyWith<_$LocationEntityImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/domain/entities/subscriptions/beacon_locations_entity/beacon_locations_entity.freezed.dart b/lib/domain/entities/subscriptions/beacon_locations_entity/beacon_locations_entity.freezed.dart index 592eece..bffe4a5 100644 --- a/lib/domain/entities/subscriptions/beacon_locations_entity/beacon_locations_entity.freezed.dart +++ b/lib/domain/entities/subscriptions/beacon_locations_entity/beacon_locations_entity.freezed.dart @@ -21,7 +21,9 @@ mixin _$BeaconLocationsEntity { LandMarkEntity? get landmark => throw _privateConstructorUsedError; UserEntity? get user => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of BeaconLocationsEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $BeaconLocationsEntityCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -54,6 +56,8 @@ class _$BeaconLocationsEntityCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of BeaconLocationsEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -82,6 +86,8 @@ class _$BeaconLocationsEntityCopyWithImpl<$Res, ) as $Val); } + /// Create a copy of BeaconLocationsEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $UserEntityCopyWith<$Res>? get userSOS { @@ -94,6 +100,8 @@ class _$BeaconLocationsEntityCopyWithImpl<$Res, }); } + /// Create a copy of BeaconLocationsEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $LandMarkEntityCopyWith<$Res>? get landmark { @@ -106,6 +114,8 @@ class _$BeaconLocationsEntityCopyWithImpl<$Res, }); } + /// Create a copy of BeaconLocationsEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $UserEntityCopyWith<$Res>? get user { @@ -151,6 +161,8 @@ class __$$BeaconLocationsEntityImplCopyWithImpl<$Res> $Res Function(_$BeaconLocationsEntityImpl) _then) : super(_value, _then); + /// Create a copy of BeaconLocationsEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -228,7 +240,9 @@ class _$BeaconLocationsEntityImpl implements _BeaconLocationsEntity { int get hashCode => Object.hash(runtimeType, userSOS, const DeepCollectionEquality().hash(_route), landmark, user); - @JsonKey(ignore: true) + /// Create a copy of BeaconLocationsEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$BeaconLocationsEntityImplCopyWith<_$BeaconLocationsEntityImpl> @@ -251,8 +265,11 @@ abstract class _BeaconLocationsEntity implements BeaconLocationsEntity { LandMarkEntity? get landmark; @override UserEntity? get user; + + /// Create a copy of BeaconLocationsEntity + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$BeaconLocationsEntityImplCopyWith<_$BeaconLocationsEntityImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/domain/entities/subscriptions/join_leave_beacon_entity/join_leave_beacon_entity.freezed.dart b/lib/domain/entities/subscriptions/join_leave_beacon_entity/join_leave_beacon_entity.freezed.dart index 51bd776..78a01a7 100644 --- a/lib/domain/entities/subscriptions/join_leave_beacon_entity/join_leave_beacon_entity.freezed.dart +++ b/lib/domain/entities/subscriptions/join_leave_beacon_entity/join_leave_beacon_entity.freezed.dart @@ -19,7 +19,9 @@ mixin _$JoinLeaveBeaconEntity { UserEntity? get newfollower => throw _privateConstructorUsedError; UserEntity? get inactiveuser => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of JoinLeaveBeaconEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $JoinLeaveBeaconEntityCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -47,6 +49,8 @@ class _$JoinLeaveBeaconEntityCopyWithImpl<$Res, // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of JoinLeaveBeaconEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -65,6 +69,8 @@ class _$JoinLeaveBeaconEntityCopyWithImpl<$Res, ) as $Val); } + /// Create a copy of JoinLeaveBeaconEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $UserEntityCopyWith<$Res>? get newfollower { @@ -77,6 +83,8 @@ class _$JoinLeaveBeaconEntityCopyWithImpl<$Res, }); } + /// Create a copy of JoinLeaveBeaconEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $UserEntityCopyWith<$Res>? get inactiveuser { @@ -116,6 +124,8 @@ class __$$JoinLeaveBeaconEntityImplCopyWithImpl<$Res> $Res Function(_$JoinLeaveBeaconEntityImpl) _then) : super(_value, _then); + /// Create a copy of JoinLeaveBeaconEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -164,7 +174,9 @@ class _$JoinLeaveBeaconEntityImpl implements _JoinLeaveBeaconEntity { @override int get hashCode => Object.hash(runtimeType, newfollower, inactiveuser); - @JsonKey(ignore: true) + /// Create a copy of JoinLeaveBeaconEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$JoinLeaveBeaconEntityImplCopyWith<_$JoinLeaveBeaconEntityImpl> @@ -181,8 +193,11 @@ abstract class _JoinLeaveBeaconEntity implements JoinLeaveBeaconEntity { UserEntity? get newfollower; @override UserEntity? get inactiveuser; + + /// Create a copy of JoinLeaveBeaconEntity + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$JoinLeaveBeaconEntityImplCopyWith<_$JoinLeaveBeaconEntityImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/domain/entities/subscriptions/updated_group_entity/updated_group_entity.freezed.dart b/lib/domain/entities/subscriptions/updated_group_entity/updated_group_entity.freezed.dart index 2e18afd..0774f29 100644 --- a/lib/domain/entities/subscriptions/updated_group_entity/updated_group_entity.freezed.dart +++ b/lib/domain/entities/subscriptions/updated_group_entity/updated_group_entity.freezed.dart @@ -22,7 +22,9 @@ mixin _$UpdatedGroupEntity { BeaconEntity? get newBeacon => throw _privateConstructorUsedError; UserEntity? get newUser => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of UpdatedGroupEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $UpdatedGroupEntityCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -56,6 +58,8 @@ class _$UpdatedGroupEntityCopyWithImpl<$Res, $Val extends UpdatedGroupEntity> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of UpdatedGroupEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -89,6 +93,8 @@ class _$UpdatedGroupEntityCopyWithImpl<$Res, $Val extends UpdatedGroupEntity> ) as $Val); } + /// Create a copy of UpdatedGroupEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $BeaconEntityCopyWith<$Res>? get deletedBeacon { @@ -101,6 +107,8 @@ class _$UpdatedGroupEntityCopyWithImpl<$Res, $Val extends UpdatedGroupEntity> }); } + /// Create a copy of UpdatedGroupEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $BeaconEntityCopyWith<$Res>? get updatedBeacon { @@ -113,6 +121,8 @@ class _$UpdatedGroupEntityCopyWithImpl<$Res, $Val extends UpdatedGroupEntity> }); } + /// Create a copy of UpdatedGroupEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $BeaconEntityCopyWith<$Res>? get newBeacon { @@ -125,6 +135,8 @@ class _$UpdatedGroupEntityCopyWithImpl<$Res, $Val extends UpdatedGroupEntity> }); } + /// Create a copy of UpdatedGroupEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $UserEntityCopyWith<$Res>? get newUser { @@ -171,6 +183,8 @@ class __$$UpdatedGroupEntityImplCopyWithImpl<$Res> $Res Function(_$UpdatedGroupEntityImpl) _then) : super(_value, _then); + /// Create a copy of UpdatedGroupEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -250,7 +264,9 @@ class _$UpdatedGroupEntityImpl implements _UpdatedGroupEntity { int get hashCode => Object.hash( runtimeType, id, deletedBeacon, updatedBeacon, newBeacon, newUser); - @JsonKey(ignore: true) + /// Create a copy of UpdatedGroupEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$UpdatedGroupEntityImplCopyWith<_$UpdatedGroupEntityImpl> get copyWith => @@ -276,8 +292,11 @@ abstract class _UpdatedGroupEntity implements UpdatedGroupEntity { BeaconEntity? get newBeacon; @override UserEntity? get newUser; + + /// Create a copy of UpdatedGroupEntity + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$UpdatedGroupEntityImplCopyWith<_$UpdatedGroupEntityImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/domain/entities/user/user_entity.dart b/lib/domain/entities/user/user_entity.dart index 572c903..f3ee0fc 100644 --- a/lib/domain/entities/user/user_entity.dart +++ b/lib/domain/entities/user/user_entity.dart @@ -7,16 +7,18 @@ part 'user_entity.freezed.dart'; @freezed class UserEntity with _$UserEntity { - const factory UserEntity( - {String? id, - List? groups, - List? beacons, - String? authToken, - String? email, - bool? isGuest, - String? name, - bool? isVerified, - LocationEntity? location}) = _UserEntity; + const factory UserEntity({ + String? id, + List? groups, + List? beacons, + String? authToken, + String? email, + bool? isGuest, + String? name, + bool? isVerified, + LocationEntity? location, + String? imageUrl, // Add this line + }) = _UserEntity; } extension UserEntityCopyWithExtension on UserEntity { @@ -30,16 +32,19 @@ extension UserEntityCopyWithExtension on UserEntity { String? name, bool? isVerified, LocationEntity? location, + String? imageUrl, // Add this line }) { return UserEntity( - id: id ?? this.id, - groups: groups ?? this.groups, - beacons: beacons ?? this.beacons, - authToken: authToken ?? this.authToken, - email: email ?? this.email, - isGuest: isGuest ?? this.isGuest, - name: name ?? this.name, - location: location ?? this.location, - isVerified: isVerified ?? this.isVerified); + id: id ?? this.id, + groups: groups ?? this.groups, + beacons: beacons ?? this.beacons, + authToken: authToken ?? this.authToken, + email: email ?? this.email, + isGuest: isGuest ?? this.isGuest, + name: name ?? this.name, + location: location ?? this.location, + isVerified: isVerified ?? this.isVerified, + imageUrl: imageUrl ?? this.imageUrl, // Add this line + ); } } diff --git a/lib/domain/entities/user/user_entity.freezed.dart b/lib/domain/entities/user/user_entity.freezed.dart index ee0b007..bc7271e 100644 --- a/lib/domain/entities/user/user_entity.freezed.dart +++ b/lib/domain/entities/user/user_entity.freezed.dart @@ -25,8 +25,11 @@ mixin _$UserEntity { String? get name => throw _privateConstructorUsedError; bool? get isVerified => throw _privateConstructorUsedError; LocationEntity? get location => throw _privateConstructorUsedError; + String? get imageUrl => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + /// Create a copy of UserEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $UserEntityCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -46,7 +49,8 @@ abstract class $UserEntityCopyWith<$Res> { bool? isGuest, String? name, bool? isVerified, - LocationEntity? location}); + LocationEntity? location, + String? imageUrl}); $LocationEntityCopyWith<$Res>? get location; } @@ -61,6 +65,8 @@ class _$UserEntityCopyWithImpl<$Res, $Val extends UserEntity> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of UserEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -73,6 +79,7 @@ class _$UserEntityCopyWithImpl<$Res, $Val extends UserEntity> Object? name = freezed, Object? isVerified = freezed, Object? location = freezed, + Object? imageUrl = freezed, }) { return _then(_value.copyWith( id: freezed == id @@ -111,9 +118,15 @@ class _$UserEntityCopyWithImpl<$Res, $Val extends UserEntity> ? _value.location : location // ignore: cast_nullable_to_non_nullable as LocationEntity?, + imageUrl: freezed == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } + /// Create a copy of UserEntity + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $LocationEntityCopyWith<$Res>? get location { @@ -144,7 +157,8 @@ abstract class _$$UserEntityImplCopyWith<$Res> bool? isGuest, String? name, bool? isVerified, - LocationEntity? location}); + LocationEntity? location, + String? imageUrl}); @override $LocationEntityCopyWith<$Res>? get location; @@ -158,6 +172,8 @@ class __$$UserEntityImplCopyWithImpl<$Res> _$UserEntityImpl _value, $Res Function(_$UserEntityImpl) _then) : super(_value, _then); + /// Create a copy of UserEntity + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -170,6 +186,7 @@ class __$$UserEntityImplCopyWithImpl<$Res> Object? name = freezed, Object? isVerified = freezed, Object? location = freezed, + Object? imageUrl = freezed, }) { return _then(_$UserEntityImpl( id: freezed == id @@ -208,6 +225,10 @@ class __$$UserEntityImplCopyWithImpl<$Res> ? _value.location : location // ignore: cast_nullable_to_non_nullable as LocationEntity?, + imageUrl: freezed == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -224,7 +245,8 @@ class _$UserEntityImpl implements _UserEntity { this.isGuest, this.name, this.isVerified, - this.location}) + this.location, + this.imageUrl}) : _groups = groups, _beacons = beacons; @@ -262,10 +284,12 @@ class _$UserEntityImpl implements _UserEntity { final bool? isVerified; @override final LocationEntity? location; + @override + final String? imageUrl; @override String toString() { - return 'UserEntity(id: $id, groups: $groups, beacons: $beacons, authToken: $authToken, email: $email, isGuest: $isGuest, name: $name, isVerified: $isVerified, location: $location)'; + return 'UserEntity(id: $id, groups: $groups, beacons: $beacons, authToken: $authToken, email: $email, isGuest: $isGuest, name: $name, isVerified: $isVerified, location: $location, imageUrl: $imageUrl)'; } @override @@ -284,7 +308,9 @@ class _$UserEntityImpl implements _UserEntity { (identical(other.isVerified, isVerified) || other.isVerified == isVerified) && (identical(other.location, location) || - other.location == location)); + other.location == location) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl)); } @override @@ -298,9 +324,12 @@ class _$UserEntityImpl implements _UserEntity { isGuest, name, isVerified, - location); + location, + imageUrl); - @JsonKey(ignore: true) + /// Create a copy of UserEntity + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$UserEntityImplCopyWith<_$UserEntityImpl> get copyWith => @@ -317,7 +346,8 @@ abstract class _UserEntity implements UserEntity { final bool? isGuest, final String? name, final bool? isVerified, - final LocationEntity? location}) = _$UserEntityImpl; + final LocationEntity? location, + final String? imageUrl}) = _$UserEntityImpl; @override String? get id; @@ -338,7 +368,12 @@ abstract class _UserEntity implements UserEntity { @override LocationEntity? get location; @override - @JsonKey(ignore: true) + String? get imageUrl; + + /// Create a copy of UserEntity + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) _$$UserEntityImplCopyWith<_$UserEntityImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/domain/repositories/home_repository.dart b/lib/domain/repositories/home_repository.dart index 237ba5c..f7513d4 100644 --- a/lib/domain/repositories/home_repository.dart +++ b/lib/domain/repositories/home_repository.dart @@ -10,4 +10,5 @@ abstract class HomeRepository { Stream> groupUpdateSubscription( List groupIds); Future> changeShortcode(String groupId); + Future> updateUserImage(String userId, String? imageUrl); } diff --git a/lib/domain/usecase/home_usecase.dart b/lib/domain/usecase/home_usecase.dart index 359b972..b60db59 100644 --- a/lib/domain/usecase/home_usecase.dart +++ b/lib/domain/usecase/home_usecase.dart @@ -32,4 +32,8 @@ class HomeUseCase { Future> changeShortcode(String groupId) { return homeRepository.changeShortcode(groupId); } + + Future> updateUserImage(String userId, String? imageUrl) { + return homeRepository.updateUserImage(userId, imageUrl); + } } diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart index af84ee9..1120542 100644 --- a/lib/firebase_options.dart +++ b/lib/firebase_options.dart @@ -50,21 +50,23 @@ class DefaultFirebaseOptions { } static const FirebaseOptions android = FirebaseOptions( - apiKey: '', - appId: '', - messagingSenderId: '', - projectId: '', - storageBucket: '', + apiKey: 'AIzaSyDhR4FOBzIkmS0Pz0gfqQISkcdFzomsxF4', + appId: '1:724090909650:android:33a605e98b134abe8f8e0b', + messagingSenderId: '724090909650', + projectId: 'beacon-b75bf', + storageBucket: 'beacon-b75bf.firebasestorage.app', ); static const FirebaseOptions ios = FirebaseOptions( - apiKey: '', - appId: '', - messagingSenderId: '', - projectId: '', - storageBucket: '', - androidClientId: '', - iosClientId: '', - iosBundleId: '', + apiKey: 'AIzaSyCRbwD5ECjgrwYHok_naB2OKGAy3whA6SY', + appId: '1:724090909650:ios:7dfc01703caf03d48f8e0b', + messagingSenderId: '724090909650', + projectId: 'beacon-b75bf', + storageBucket: 'beacon-b75bf.firebasestorage.app', + androidClientId: + '724090909650-24e79l5pait1nhksbsh9260duvqps6ri.apps.googleusercontent.com', + iosClientId: + '724090909650-441i6sm3rleavnrcdibdgjn3n1j9lqot.apps.googleusercontent.com', + iosBundleId: 'com.ccextractor.beaconmobile', ); } diff --git a/lib/presentation/auth/auth_cubit/auth_cubit.dart b/lib/presentation/auth/auth_cubit/auth_cubit.dart index 04fb0d7..b168f49 100644 --- a/lib/presentation/auth/auth_cubit/auth_cubit.dart +++ b/lib/presentation/auth/auth_cubit/auth_cubit.dart @@ -46,7 +46,7 @@ class AuthCubit extends Cubit { Future login(String email, String password) async { emit(AuthLoadingState()); final dataState = await authUseCase.loginUserCase(email, password); - + print("Data State from login: ${dataState.data?.imageUrl}"); if (dataState is DataSuccess && dataState.data != null) { if (dataState.data!.isVerified == false) { // show verification screen diff --git a/lib/presentation/auth/auth_cubit/auth_state.freezed.dart b/lib/presentation/auth/auth_cubit/auth_state.freezed.dart index 8bda403..5431a82 100644 --- a/lib/presentation/auth/auth_cubit/auth_state.freezed.dart +++ b/lib/presentation/auth/auth_cubit/auth_state.freezed.dart @@ -89,6 +89,9 @@ class _$AuthStateCopyWithImpl<$Res, $Val extends AuthState> final $Val _value; // ignore: unused_field final $Res Function($Val) _then; + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -105,6 +108,9 @@ class __$$InitialAuthStateImplCopyWithImpl<$Res> __$$InitialAuthStateImplCopyWithImpl(_$InitialAuthStateImpl _value, $Res Function(_$InitialAuthStateImpl) _then) : super(_value, _then); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -225,6 +231,9 @@ class __$$AuthLoadingStateImplCopyWithImpl<$Res> __$$AuthLoadingStateImplCopyWithImpl(_$AuthLoadingStateImpl _value, $Res Function(_$AuthLoadingStateImpl) _then) : super(_value, _then); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -348,6 +357,8 @@ class __$$AuthErrorStateImplCopyWithImpl<$Res> _$AuthErrorStateImpl _value, $Res Function(_$AuthErrorStateImpl) _then) : super(_value, _then); + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -386,7 +397,9 @@ class _$AuthErrorStateImpl implements AuthErrorState { @override int get hashCode => Object.hash(runtimeType, error); - @JsonKey(ignore: true) + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$AuthErrorStateImplCopyWith<_$AuthErrorStateImpl> get copyWith => @@ -478,7 +491,10 @@ abstract class AuthErrorState implements AuthState { const factory AuthErrorState({final String? error}) = _$AuthErrorStateImpl; String? get error; - @JsonKey(ignore: true) + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$AuthErrorStateImplCopyWith<_$AuthErrorStateImpl> get copyWith => throw _privateConstructorUsedError; } @@ -500,6 +516,8 @@ class __$$SuccessStateImplCopyWithImpl<$Res> _$SuccessStateImpl _value, $Res Function(_$SuccessStateImpl) _then) : super(_value, _then); + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -538,7 +556,9 @@ class _$SuccessStateImpl implements SuccessState { @override int get hashCode => Object.hash(runtimeType, message); - @JsonKey(ignore: true) + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SuccessStateImplCopyWith<_$SuccessStateImpl> get copyWith => @@ -629,7 +649,10 @@ abstract class SuccessState implements AuthState { const factory SuccessState({final String? message}) = _$SuccessStateImpl; String? get message; - @JsonKey(ignore: true) + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$SuccessStateImplCopyWith<_$SuccessStateImpl> get copyWith => throw _privateConstructorUsedError; } @@ -649,6 +672,9 @@ class __$$AuthVerificationStateImplCopyWithImpl<$Res> __$$AuthVerificationStateImplCopyWithImpl(_$AuthVerificationStateImpl _value, $Res Function(_$AuthVerificationStateImpl) _then) : super(_value, _then); + + /// Create a copy of AuthState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc diff --git a/lib/presentation/auth/verification_cubit/verification_state.freezed.dart b/lib/presentation/auth/verification_cubit/verification_state.freezed.dart index 688d823..7b54378 100644 --- a/lib/presentation/auth/verification_cubit/verification_state.freezed.dart +++ b/lib/presentation/auth/verification_cubit/verification_state.freezed.dart @@ -97,6 +97,9 @@ class _$OTPVerificationStateCopyWithImpl<$Res, final $Val _value; // ignore: unused_field final $Res Function($Val) _then; + + /// Create a copy of OTPVerificationState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -113,6 +116,9 @@ class __$$InitialOTPStateImplCopyWithImpl<$Res> __$$InitialOTPStateImplCopyWithImpl( _$InitialOTPStateImpl _value, $Res Function(_$InitialOTPStateImpl) _then) : super(_value, _then); + + /// Create a copy of OTPVerificationState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -239,6 +245,9 @@ class __$$OTPSendingStateImplCopyWithImpl<$Res> __$$OTPSendingStateImplCopyWithImpl( _$OTPSendingStateImpl _value, $Res Function(_$OTPSendingStateImpl) _then) : super(_value, _then); + + /// Create a copy of OTPVerificationState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -368,6 +377,8 @@ class __$$OTPSentStateImplCopyWithImpl<$Res> _$OTPSentStateImpl _value, $Res Function(_$OTPSentStateImpl) _then) : super(_value, _then); + /// Create a copy of OTPVerificationState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -406,7 +417,9 @@ class _$OTPSentStateImpl implements OTPSentState { @override int get hashCode => Object.hash(runtimeType, otp); - @JsonKey(ignore: true) + /// Create a copy of OTPVerificationState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$OTPSentStateImplCopyWith<_$OTPSentStateImpl> get copyWith => @@ -503,7 +516,10 @@ abstract class OTPSentState implements OTPVerificationState { factory OTPSentState({final String? otp}) = _$OTPSentStateImpl; String? get otp; - @JsonKey(ignore: true) + + /// Create a copy of OTPVerificationState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$OTPSentStateImplCopyWith<_$OTPSentStateImpl> get copyWith => throw _privateConstructorUsedError; } @@ -522,6 +538,9 @@ class __$$OTPVerifyingStateImplCopyWithImpl<$Res> __$$OTPVerifyingStateImplCopyWithImpl(_$OTPVerifyingStateImpl _value, $Res Function(_$OTPVerifyingStateImpl) _then) : super(_value, _then); + + /// Create a copy of OTPVerificationState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -648,6 +667,9 @@ class __$$OTPVerifiedStateImplCopyWithImpl<$Res> __$$OTPVerifiedStateImplCopyWithImpl(_$OTPVerifiedStateImpl _value, $Res Function(_$OTPVerifiedStateImpl) _then) : super(_value, _then); + + /// Create a copy of OTPVerificationState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -774,6 +796,9 @@ class __$$OTPFailureStateImplCopyWithImpl<$Res> __$$OTPFailureStateImplCopyWithImpl( _$OTPFailureStateImpl _value, $Res Function(_$OTPFailureStateImpl) _then) : super(_value, _then); + + /// Create a copy of OTPVerificationState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc diff --git a/lib/presentation/group/cubit/group_cubit/group_state.freezed.dart b/lib/presentation/group/cubit/group_cubit/group_state.freezed.dart index 0ee4c3f..c743b6c 100644 --- a/lib/presentation/group/cubit/group_cubit/group_state.freezed.dart +++ b/lib/presentation/group/cubit/group_cubit/group_state.freezed.dart @@ -151,6 +151,9 @@ class _$GroupStateCopyWithImpl<$Res, $Val extends GroupState> final $Val _value; // ignore: unused_field final $Res Function($Val) _then; + + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -167,6 +170,9 @@ class __$$InitialGroupStateImplCopyWithImpl<$Res> __$$InitialGroupStateImplCopyWithImpl(_$InitialGroupStateImpl _value, $Res Function(_$InitialGroupStateImpl) _then) : super(_value, _then); + + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -348,6 +354,9 @@ class __$$LoadingGroupStateImplCopyWithImpl<$Res> __$$LoadingGroupStateImplCopyWithImpl(_$LoadingGroupStateImpl _value, $Res Function(_$LoadingGroupStateImpl) _then) : super(_value, _then); + + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -529,6 +538,9 @@ class __$$ShrimmerGroupStateImplCopyWithImpl<$Res> __$$ShrimmerGroupStateImplCopyWithImpl(_$ShrimmerGroupStateImpl _value, $Res Function(_$ShrimmerGroupStateImpl) _then) : super(_value, _then); + + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -719,6 +731,8 @@ class __$$AllBeaconGroupStateImplCopyWithImpl<$Res> $Res Function(_$AllBeaconGroupStateImpl) _then) : super(_value, _then); + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -823,7 +837,9 @@ class _$AllBeaconGroupStateImpl implements AllBeaconGroupState { const DeepCollectionEquality().hash(_beacons), version); - @JsonKey(ignore: true) + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$AllBeaconGroupStateImplCopyWith<_$AllBeaconGroupStateImpl> get copyWith => @@ -990,7 +1006,10 @@ abstract class AllBeaconGroupState implements GroupState { filters get type; List get beacons; int get version; - @JsonKey(ignore: true) + + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$AllBeaconGroupStateImplCopyWith<_$AllBeaconGroupStateImpl> get copyWith => throw _privateConstructorUsedError; } @@ -1019,6 +1038,8 @@ class __$$NearbyBeaconGroupStateImplCopyWithImpl<$Res> $Res Function(_$NearbyBeaconGroupStateImpl) _then) : super(_value, _then); + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1105,7 +1126,9 @@ class _$NearbyBeaconGroupStateImpl implements NearbyBeaconGroupState { int get hashCode => Object.hash(runtimeType, message, type, const DeepCollectionEquality().hash(_beacons), radius, version); - @JsonKey(ignore: true) + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$NearbyBeaconGroupStateImplCopyWith<_$NearbyBeaconGroupStateImpl> @@ -1267,7 +1290,10 @@ abstract class NearbyBeaconGroupState implements GroupState { List get beacons; double get radius; int get version; - @JsonKey(ignore: true) + + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$NearbyBeaconGroupStateImplCopyWith<_$NearbyBeaconGroupStateImpl> get copyWith => throw _privateConstructorUsedError; } @@ -1297,6 +1323,8 @@ class __$$StatusFilterBeaconGroupStateImplCopyWithImpl<$Res> $Res Function(_$StatusFilterBeaconGroupStateImpl) _then) : super(_value, _then); + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1401,7 +1429,9 @@ class _$StatusFilterBeaconGroupStateImpl const DeepCollectionEquality().hash(_beacons), version); - @JsonKey(ignore: true) + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$StatusFilterBeaconGroupStateImplCopyWith< @@ -1569,7 +1599,10 @@ abstract class StatusFilterBeaconGroupState implements GroupState { filters? get type; List get beacons; int get version; - @JsonKey(ignore: true) + + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$StatusFilterBeaconGroupStateImplCopyWith< _$StatusFilterBeaconGroupStateImpl> get copyWith => throw _privateConstructorUsedError; @@ -1592,6 +1625,8 @@ class __$$ErrorGroupStateImplCopyWithImpl<$Res> _$ErrorGroupStateImpl _value, $Res Function(_$ErrorGroupStateImpl) _then) : super(_value, _then); + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -1630,7 +1665,9 @@ class _$ErrorGroupStateImpl implements ErrorGroupState { @override int get hashCode => Object.hash(runtimeType, message); - @JsonKey(ignore: true) + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ErrorGroupStateImplCopyWith<_$ErrorGroupStateImpl> get copyWith => @@ -1784,7 +1821,10 @@ abstract class ErrorGroupState implements GroupState { _$ErrorGroupStateImpl; String get message; - @JsonKey(ignore: true) + + /// Create a copy of GroupState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$ErrorGroupStateImplCopyWith<_$ErrorGroupStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/presentation/group/cubit/members_cubit/members_state.freezed.dart b/lib/presentation/group/cubit/members_cubit/members_state.freezed.dart index 8f64935..4bdca17 100644 --- a/lib/presentation/group/cubit/members_cubit/members_state.freezed.dart +++ b/lib/presentation/group/cubit/members_cubit/members_state.freezed.dart @@ -79,6 +79,9 @@ class _$MembersStateCopyWithImpl<$Res, $Val extends MembersState> final $Val _value; // ignore: unused_field final $Res Function($Val) _then; + + /// Create a copy of MembersState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -95,6 +98,9 @@ class __$$InitialMemberStateImplCopyWithImpl<$Res> __$$InitialMemberStateImplCopyWithImpl(_$InitialMemberStateImpl _value, $Res Function(_$InitialMemberStateImpl) _then) : super(_value, _then); + + /// Create a copy of MembersState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -204,6 +210,9 @@ class __$$LoadingMemberStateImplCopyWithImpl<$Res> __$$LoadingMemberStateImplCopyWithImpl(_$LoadingMemberStateImpl _value, $Res Function(_$LoadingMemberStateImpl) _then) : super(_value, _then); + + /// Create a copy of MembersState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -316,6 +325,8 @@ class __$$LoadedMemberStateImplCopyWithImpl<$Res> $Res Function(_$LoadedMemberStateImpl) _then) : super(_value, _then); + /// Create a copy of MembersState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -372,7 +383,9 @@ class _$LoadedMemberStateImpl implements LoadedMemberState { int get hashCode => Object.hash( runtimeType, const DeepCollectionEquality().hash(_members), message); - @JsonKey(ignore: true) + /// Create a copy of MembersState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LoadedMemberStateImplCopyWith<_$LoadedMemberStateImpl> get copyWith => @@ -456,7 +469,10 @@ abstract class LoadedMemberState implements MembersState { List? get members; String? get message; - @JsonKey(ignore: true) + + /// Create a copy of MembersState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$LoadedMemberStateImplCopyWith<_$LoadedMemberStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/presentation/group/group_screen.dart b/lib/presentation/group/group_screen.dart index 61a21b0..6351315 100644 --- a/lib/presentation/group/group_screen.dart +++ b/lib/presentation/group/group_screen.dart @@ -175,15 +175,14 @@ class _GroupScreenState extends State : widget.group.members ?? []) .map((member) { if (member != null) { + print( + 'Member Image URL: ${member.imageUrl}'); return Positioned( left: widget.group.members! .indexOf(member) * 20.0, child: _buildProfileCircle( - member.id == localApi.userModel.id - ? Colors.teal - : shimmerSkeletonColor, - ), + member.imageUrl!), ); } else { return const SizedBox.shrink(); @@ -501,14 +500,18 @@ class _GroupScreenState extends State } } -Widget _buildProfileCircle(Color color) { +Widget _buildProfileCircle(String url) { return Container( width: 40, height: 40, decoration: BoxDecoration( shape: BoxShape.circle, - color: color, + color: Colors.grey[300], border: Border.all(color: Colors.white, width: 2), + image: DecorationImage( + image: NetworkImage(url), + fit: BoxFit.cover, + ), ), ); } diff --git a/lib/presentation/hike/cubit/hike_cubit/hike_state.freezed.dart b/lib/presentation/hike/cubit/hike_cubit/hike_state.freezed.dart index 94b72b1..1aa159b 100644 --- a/lib/presentation/hike/cubit/hike_cubit/hike_state.freezed.dart +++ b/lib/presentation/hike/cubit/hike_cubit/hike_state.freezed.dart @@ -77,6 +77,9 @@ class _$HikeStateCopyWithImpl<$Res, $Val extends HikeState> final $Val _value; // ignore: unused_field final $Res Function($Val) _then; + + /// Create a copy of HikeState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -93,6 +96,9 @@ class __$$InitialHikeStateImplCopyWithImpl<$Res> __$$InitialHikeStateImplCopyWithImpl(_$InitialHikeStateImpl _value, $Res Function(_$InitialHikeStateImpl) _then) : super(_value, _then); + + /// Create a copy of HikeState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -206,6 +212,8 @@ class __$$LoadedHikeStateImplCopyWithImpl<$Res> _$LoadedHikeStateImpl _value, $Res Function(_$LoadedHikeStateImpl) _then) : super(_value, _then); + /// Create a copy of HikeState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -224,6 +232,8 @@ class __$$LoadedHikeStateImplCopyWithImpl<$Res> )); } + /// Create a copy of HikeState + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $BeaconEntityCopyWith<$Res>? get beacon { @@ -264,7 +274,9 @@ class _$LoadedHikeStateImpl implements LoadedHikeState { @override int get hashCode => Object.hash(runtimeType, beacon, message); - @JsonKey(ignore: true) + /// Create a copy of HikeState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LoadedHikeStateImplCopyWith<_$LoadedHikeStateImpl> get copyWith => @@ -346,7 +358,10 @@ abstract class LoadedHikeState implements HikeState { BeaconEntity? get beacon; String? get message; - @JsonKey(ignore: true) + + /// Create a copy of HikeState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$LoadedHikeStateImplCopyWith<_$LoadedHikeStateImpl> get copyWith => throw _privateConstructorUsedError; } @@ -368,6 +383,8 @@ class __$$ErrorHikeStateImplCopyWithImpl<$Res> _$ErrorHikeStateImpl _value, $Res Function(_$ErrorHikeStateImpl) _then) : super(_value, _then); + /// Create a copy of HikeState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -407,7 +424,9 @@ class _$ErrorHikeStateImpl implements ErrorHikeState { @override int get hashCode => Object.hash(runtimeType, errmessage); - @JsonKey(ignore: true) + /// Create a copy of HikeState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ErrorHikeStateImplCopyWith<_$ErrorHikeStateImpl> get copyWith => @@ -487,7 +506,10 @@ abstract class ErrorHikeState implements HikeState { factory ErrorHikeState({final String? errmessage}) = _$ErrorHikeStateImpl; String? get errmessage; - @JsonKey(ignore: true) + + /// Create a copy of HikeState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$ErrorHikeStateImplCopyWith<_$ErrorHikeStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart index 06464b5..dda3a19 100644 --- a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart +++ b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart @@ -638,12 +638,15 @@ class LocationCubit extends Cubit { return _address.isNotEmpty ? _address : null; } + // update here Future createLandmark( String beaconId, String title, LatLng latlng) async { var dataState = await _hikeUseCase.createLandMark(beaconId, title, latlng.latitude.toString(), latlng.longitude.toString()); if (dataState is DataSuccess && dataState.data != null) { + print( + 'Creating marker for landmark: 1: ${dataState.data!.createdBy?.imageUrl}'); await _createLandMarkMarker(dataState.data!); emit(LoadedLocationState( polyline: _polyline, @@ -717,6 +720,7 @@ class LocationCubit extends Cubit { final markerPosition = locationToLatLng(user.location!); // final bitmap = await _createCustomMarkerBitmap(); + print("logging user marker creation: ${user.name}"); final existingMarkers = _hikeMarkers.where((element) => element.markerId == markerId); @@ -743,8 +747,10 @@ class LocationCubit extends Cubit { Future createMarkerWithCircularNetworkImage( LandMarkEntity landmark) async { + print("Creating marker for landmark: ${landmark.createdBy?.imageUrl}"); final Uint8List markerIcon = await getCircularImageWithBorderAndPointer( - "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRuNhTZJTtkR6b-ADMhmzPvVwaLuLdz273wvQ&s", + landmark.createdBy?.imageUrl ?? + 'https://cdn.jsdelivr.net/gh/alohe/avatars/png/toon_5.png', size: 80, borderColor: Colors.deepPurple, borderWidth: 4, diff --git a/lib/presentation/hike/cubit/location_cubit/location_state.freezed.dart b/lib/presentation/hike/cubit/location_cubit/location_state.freezed.dart index f9cdabe..9c749f5 100644 --- a/lib/presentation/hike/cubit/location_cubit/location_state.freezed.dart +++ b/lib/presentation/hike/cubit/location_cubit/location_state.freezed.dart @@ -99,6 +99,9 @@ class _$LocationStateCopyWithImpl<$Res, $Val extends LocationState> final $Val _value; // ignore: unused_field final $Res Function($Val) _then; + + /// Create a copy of LocationState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -115,6 +118,9 @@ class __$$InitialLocationStateImplCopyWithImpl<$Res> __$$InitialLocationStateImplCopyWithImpl(_$InitialLocationStateImpl _value, $Res Function(_$InitialLocationStateImpl) _then) : super(_value, _then); + + /// Create a copy of LocationState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -254,6 +260,8 @@ class __$$LoadedLocationStateImplCopyWithImpl<$Res> $Res Function(_$LoadedLocationStateImpl) _then) : super(_value, _then); + /// Create a copy of LocationState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -372,7 +380,9 @@ class _$LoadedLocationStateImpl implements LoadedLocationState { message, version); - @JsonKey(ignore: true) + /// Create a copy of LocationState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LoadedLocationStateImplCopyWith<_$LoadedLocationStateImpl> get copyWith => @@ -487,7 +497,10 @@ abstract class LoadedLocationState implements LocationState { Set get polyline; String? get message; int get version; - @JsonKey(ignore: true) + + /// Create a copy of LocationState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$LoadedLocationStateImplCopyWith<_$LoadedLocationStateImpl> get copyWith => throw _privateConstructorUsedError; } @@ -509,6 +522,8 @@ class __$$LocationErrorStateImplCopyWithImpl<$Res> $Res Function(_$LocationErrorStateImpl) _then) : super(_value, _then); + /// Create a copy of LocationState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -547,7 +562,9 @@ class _$LocationErrorStateImpl implements LocationErrorState { @override int get hashCode => Object.hash(runtimeType, message); - @JsonKey(ignore: true) + /// Create a copy of LocationState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LocationErrorStateImplCopyWith<_$LocationErrorStateImpl> get copyWith => @@ -649,7 +666,10 @@ abstract class LocationErrorState implements LocationState { _$LocationErrorStateImpl; String? get message; - @JsonKey(ignore: true) + + /// Create a copy of LocationState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$LocationErrorStateImplCopyWith<_$LocationErrorStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/presentation/hike/cubit/panel_cubit/panel_state.freezed.dart b/lib/presentation/hike/cubit/panel_cubit/panel_state.freezed.dart index ff5fb37..89decf7 100644 --- a/lib/presentation/hike/cubit/panel_cubit/panel_state.freezed.dart +++ b/lib/presentation/hike/cubit/panel_cubit/panel_state.freezed.dart @@ -99,6 +99,9 @@ class _$SlidingPanelStateCopyWithImpl<$Res, $Val extends SlidingPanelState> final $Val _value; // ignore: unused_field final $Res Function($Val) _then; + + /// Create a copy of SlidingPanelState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -115,6 +118,9 @@ class __$$InitialPanelStateImplCopyWithImpl<$Res> __$$InitialPanelStateImplCopyWithImpl(_$InitialPanelStateImpl _value, $Res Function(_$InitialPanelStateImpl) _then) : super(_value, _then); + + /// Create a copy of SlidingPanelState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -255,6 +261,8 @@ class __$$LoadedPanelStateImplCopyWithImpl<$Res> $Res Function(_$LoadedPanelStateImpl) _then) : super(_value, _then); + /// Create a copy of SlidingPanelState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -293,6 +301,8 @@ class __$$LoadedPanelStateImplCopyWithImpl<$Res> )); } + /// Create a copy of SlidingPanelState + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $UserEntityCopyWith<$Res>? get leader { @@ -371,7 +381,9 @@ class _$LoadedPanelStateImpl implements LoadedPanelState { const DeepCollectionEquality().hash(_followers), message); - @JsonKey(ignore: true) + /// Create a copy of SlidingPanelState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LoadedPanelStateImplCopyWith<_$LoadedPanelStateImpl> get copyWith => @@ -486,7 +498,10 @@ abstract class LoadedPanelState implements SlidingPanelState { UserEntity? get leader; List? get followers; String? get message; - @JsonKey(ignore: true) + + /// Create a copy of SlidingPanelState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$LoadedPanelStateImplCopyWith<_$LoadedPanelStateImpl> get copyWith => throw _privateConstructorUsedError; } @@ -508,6 +523,8 @@ class __$$ErrorPanelStateImplCopyWithImpl<$Res> _$ErrorPanelStateImpl _value, $Res Function(_$ErrorPanelStateImpl) _then) : super(_value, _then); + /// Create a copy of SlidingPanelState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -546,7 +563,9 @@ class _$ErrorPanelStateImpl implements ErrorPanelState { @override int get hashCode => Object.hash(runtimeType, message); - @JsonKey(ignore: true) + /// Create a copy of SlidingPanelState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ErrorPanelStateImplCopyWith<_$ErrorPanelStateImpl> get copyWith => @@ -647,7 +666,10 @@ abstract class ErrorPanelState implements SlidingPanelState { factory ErrorPanelState({final String? message}) = _$ErrorPanelStateImpl; String? get message; - @JsonKey(ignore: true) + + /// Create a copy of SlidingPanelState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$ErrorPanelStateImplCopyWith<_$ErrorPanelStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/presentation/hike/hike_screen.dart b/lib/presentation/hike/hike_screen.dart index 8ef8a28..9ac0000 100644 --- a/lib/presentation/hike/hike_screen.dart +++ b/lib/presentation/hike/hike_screen.dart @@ -261,8 +261,12 @@ class _HikeScreenState extends State circles: state.geofence, polylines: state.polyline, onLongPress: (latlng) { + // share user info HikeScreenWidget.showCreateLandMarkDialogueDialog( - context, widget.beacon.id!, latlng); + context, + widget.beacon.id!, + latlng, + ); }, onMapCreated: _locationCubit.onMapCreated, markers: state.locationMarkers, diff --git a/lib/presentation/hike/stack.dart b/lib/presentation/hike/stack.dart new file mode 100644 index 0000000..acb398a --- /dev/null +++ b/lib/presentation/hike/stack.dart @@ -0,0 +1,32 @@ +class Stack { + final List _stack = []; + + void push(T element) { + _stack.add(element); + } + + T pop() { + if (_stack.isEmpty) { + throw StateError("No elements in the Stack"); + } else { + T lastElement = _stack.last; + _stack.removeLast(); + return lastElement; + } + } + + T top() { + if (_stack.isEmpty) { + throw StateError("No elements in the Stack"); + } else { + return _stack.last; + } + } + + bool isEmpty() { + return _stack.isEmpty; + } + + @override + String toString() => _stack.toString(); +} diff --git a/lib/presentation/home/home_cubit/home_cubit.dart b/lib/presentation/home/home_cubit/home_cubit.dart index 9ec802a..987652c 100644 --- a/lib/presentation/home/home_cubit/home_cubit.dart +++ b/lib/presentation/home/home_cubit/home_cubit.dart @@ -368,6 +368,18 @@ class HomeCubit extends Cubit { _currentGroupId = groupId; } + void updateUserImage(String userId, String? imageUrl) async { + if (imageUrl != null && imageUrl.isNotEmpty) { + var dataState = await homeUseCase.updateUserImage(userId, imageUrl); + + if (dataState is DataSuccess && dataState.data == true) { + emit(_loadedhomeState.copyWith(message: 'Profile image updated!')); + } else { + emit(_loadedhomeState.copyWith(message: dataState.error)); + } + } + } + void init() async { var groups = localApi.userModel.groups ?? []; _groupIds = List.generate(groups.length, (index) => groups[index]!.id!); diff --git a/lib/presentation/home/home_cubit/home_state.freezed.dart b/lib/presentation/home/home_cubit/home_state.freezed.dart index 506b8d7..04750ff 100644 --- a/lib/presentation/home/home_cubit/home_state.freezed.dart +++ b/lib/presentation/home/home_cubit/home_state.freezed.dart @@ -89,6 +89,9 @@ class _$HomeStateCopyWithImpl<$Res, $Val extends HomeState> final $Val _value; // ignore: unused_field final $Res Function($Val) _then; + + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -105,6 +108,9 @@ class __$$InitialHomeStateImplCopyWithImpl<$Res> __$$InitialHomeStateImplCopyWithImpl(_$InitialHomeStateImpl _value, $Res Function(_$InitialHomeStateImpl) _then) : super(_value, _then); + + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -225,6 +231,9 @@ class __$$ShimmerHomeStateImplCopyWithImpl<$Res> __$$ShimmerHomeStateImplCopyWithImpl(_$ShimmerHomeStateImpl _value, $Res Function(_$ShimmerHomeStateImpl) _then) : super(_value, _then); + + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -345,6 +354,9 @@ class __$$LoadingHomeStateImplCopyWithImpl<$Res> __$$LoadingHomeStateImplCopyWithImpl(_$LoadingHomeStateImpl _value, $Res Function(_$LoadingHomeStateImpl) _then) : super(_value, _then); + + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. } /// @nodoc @@ -472,6 +484,8 @@ class __$$LoadedHomeStateImplCopyWithImpl<$Res> _$LoadedHomeStateImpl _value, $Res Function(_$LoadedHomeStateImpl) _then) : super(_value, _then); + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -554,7 +568,9 @@ class _$LoadedHomeStateImpl implements LoadedHomeState { isLoadingmore, hasReachedEnd); - @JsonKey(ignore: true) + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$LoadedHomeStateImplCopyWith<_$LoadedHomeStateImpl> get copyWith => @@ -653,7 +669,10 @@ abstract class LoadedHomeState implements HomeState { String? get message; bool get isLoadingmore; bool get hasReachedEnd; - @JsonKey(ignore: true) + + /// Create a copy of HomeState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) _$$LoadedHomeStateImplCopyWith<_$LoadedHomeStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/presentation/home/home_screen.dart b/lib/presentation/home/home_screen.dart index 96e609b..16f2c83 100644 --- a/lib/presentation/home/home_screen.dart +++ b/lib/presentation/home/home_screen.dart @@ -1,9 +1,12 @@ +import 'dart:ui'; + import 'package:auto_route/auto_route.dart'; import 'package:beacon/domain/entities/group/group_entity.dart'; import 'package:beacon/presentation/auth/auth_cubit/auth_cubit.dart'; import 'package:beacon/presentation/home/home_cubit/home_cubit.dart'; import 'package:beacon/presentation/home/home_cubit/home_state.dart'; import 'package:beacon/presentation/group/widgets/create_join_dialog.dart'; +import 'package:beacon/presentation/home/profile_screen.dart'; import 'package:beacon/presentation/widgets/shimmer.dart'; import 'package:beacon/presentation/home/widgets/group_card.dart'; import 'package:beacon/presentation/widgets/hike_button.dart'; @@ -26,38 +29,51 @@ class HomeScreen extends StatefulWidget { } class _HomeScreenState extends State { + int _currentIndex = 0; Future _onPopHome(BuildContext context) async { return showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(12.0), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 5.w, + vertical: 3.h, ), - // actionsAlignment: MainAxisAlignment.spaceEvenly, - contentPadding: EdgeInsets.all(25.0), title: Text( 'Confirm Exit', - style: TextStyle(fontSize: 25, color: kYellow), + style: TextStyle( + fontSize: 18.sp, + color: kYellow, + fontWeight: FontWeight.w600, + ), ), content: Text( 'Do you really want to exit?', - style: TextStyle(fontSize: 18, color: kBlack), + style: TextStyle( + fontSize: 16.sp, + color: kBlack, + ), ), actions: [ - HikeButton( - buttonHeight: 2.5.h, - buttonWidth: 8.w, - onTap: () => AutoRouter.of(context).maybePop(false), - text: 'No', - ), - SizedBox( - height: 5, - ), - HikeButton( - buttonHeight: 2.5.h, - buttonWidth: 8.w, - onTap: () => AutoRouter.of(context).maybePop(true), - text: 'Yes', + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + HikeButton( + buttonHeight: 5.h, + buttonWidth: 25.w, + onTap: () => AutoRouter.of(context).maybePop(false), + text: 'No', + isDotted: true, + ), + HikeButton( + buttonHeight: 5.h, + buttonWidth: 25.w, + onTap: () => AutoRouter.of(context).maybePop(true), + text: 'Yes', + ), + ], ), ], ), @@ -69,15 +85,13 @@ class _HomeScreenState extends State { @override void initState() { - _scrollController = ScrollController(); - if (localApi.userModel.isGuest == false) { - locationService.getCurrentLocation(); - _homeCubit = BlocProvider.of(context); - _homeCubit.init(); - _homeCubit.fetchUserGroups(); - _scrollController.addListener(_onScroll); - } super.initState(); + _scrollController = ScrollController(); + locationService.getCurrentLocation(); + _homeCubit = BlocProvider.of(context); + _homeCubit.init(); + _homeCubit.fetchUserGroups(); + _scrollController.addListener(_onScroll); } void _onScroll() { @@ -90,218 +104,231 @@ class _HomeScreenState extends State { @override void dispose() { _scrollController.dispose(); - if (localApi.userModel.isGuest == false) { - _homeCubit.clear(); - } + _homeCubit.clear(); super.dispose(); } @override Widget build(BuildContext context) { - final screensize = MediaQuery.of(context).size; - return PopScope( - canPop: false, - onPopInvokedWithResult: (bool didPop, Object? result) async { - if (didPop) { - return; - } + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) async { + if (didPop) { + return; + } - bool? popped = await _onPopHome(context); - if (popped == true) { - await SystemNavigator.pop(); + bool? popped = await _onPopHome(context); + if (popped == true) { + await SystemNavigator.pop(); + } + }, + child: BlocConsumer( + listener: (context, state) { + if (state is LoadedHomeState) { + state.message != null + ? utils.showSnackBar(state.message!, context) + : null; } }, - child: BlocConsumer( - listener: (context, state) { - if (state is LoadedHomeState) { - state.message != null - ? utils.showSnackBar(state.message!, context) - : null; - } - }, - builder: (context, state) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: ModalProgressHUD( + builder: (context, state) { + return Scaffold( + resizeToAvoidBottomInset: false, + body: SafeArea( + child: ModalProgressHUD( inAsyncCall: state is LoadingHomeState ? true : false, progressIndicator: LoadingScreen(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.only( - left: screensize.width * 0.04, - right: screensize.width * 0.04, - top: 12), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: 4.w, + vertical: 2.h, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // App bar + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Image.asset( - 'images/beacon_logo.png', - height: 28, - ), - IconButton( - icon: const Icon(Icons.power_settings_new, - color: Colors.grey), - onPressed: () => showDialog( - context: context, - builder: (context) => AlertDialog( - backgroundColor: Color(0xffFAFAFA), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12.0), - ), - title: Text('Logout', - style: Style.heading), - content: Text( - 'Are you sure you want to logout?', - style: TextStyle( - fontSize: 16, color: kBlack), - ), - actions: [ - HikeButton( - buttonWidth: 80, - buttonHeight: 40, - isDotted: true, - onTap: () => - AutoRouter.of(context) - .maybePop(false), - text: 'No', - textSize: 18.0, - ), - SizedBox( - height: 5, - ), - HikeButton( - buttonWidth: 80, - buttonHeight: 40, - onTap: () async { - appRouter - .replaceNamed('/auth'); - localApi.deleteUser(); - context - .read() - .googleSignOut(); - }, - text: 'Yes', - textSize: 18.0, - ), - ], - ))), - ], + Image.asset( + 'images/beacon_logo.png', + height: 4.h, + ), + IconButton( + icon: Icon( + Icons.power_settings_new, + color: Colors.grey, + size: 6.w, + ), + onPressed: () => _showLogoutDialog(), ), + ], + ), - // welcome message - const SizedBox(height: 20), + SizedBox(height: 3.h), - // Welcome message - Row( - children: [ - Text( - 'Welcome back, ', - style: Style.subHeading - .copyWith(fontWeight: FontWeight.w600), - ), - Text( - localApi.userModel.name - .toString() - .toUpperCase()[0] + - localApi.userModel.name - .toString() - .substring(1), - style: Style.heading - .copyWith(color: Colors.teal)), - ], - ), + SizedBox(height: 2.h), - // Ready to explore - Text( - 'Ready to explore?', - style: TextStyle( - fontSize: 28, - fontWeight: FontWeight.bold, - color: Color(0xFF673AB7), - ), - textAlign: TextAlign.start, - ), + Expanded( + child: _currentIndex == 0 + ? HomePage() + : _currentIndex == 1 + ? ProfileScreen(homeCubit: _homeCubit) + : SettingsPage()) + ], + ), + ), + ), + ), + bottomNavigationBar: BottomNavigationBar( + currentIndex: 0, + onTap: (index) { + setState(() { + _currentIndex = index; + }); + }, + items: [ + BottomNavigationBarItem( + icon: Icon(Icons.home, size: 6.w), + label: 'Home', + ), + BottomNavigationBarItem( + icon: Icon(Icons.person_4, size: 6.w), + label: 'Profile', + ), + BottomNavigationBarItem( + icon: Icon(Icons.hiking, size: 6.w), + label: 'Hike', + ), + ], + ), + ); + }, + ), + ); + } - SizedBox(height: 2.h), + Widget HomePage() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(), + _buildList(), + ], + ); + } - // Create and Join Group + Widget SettingsPage() { + return Center( + child: Text( + 'Settings Page', + style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.bold), + ), + ); + } - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - HikeButton( - widget: Icon( - Icons.add, - color: Colors.black, - size: 18, - ), - buttonWidth: screensize.width * 0.44, - buttonHeight: 45, - text: 'Create Group', - onTap: () async { - CreateJoinGroupDialog.createGroupDialog( - context); - }, - ), - SizedBox( - width: 1.w, - ), - HikeButton( - widget: Icon( - Icons.add, - color: Colors.teal, - size: 18, - ), - isDotted: true, - buttonWidth: screensize.width * 0.44, - buttonHeight: 45, - text: 'Join a Group', - onTap: () async { - CreateJoinGroupDialog.joinGroupDialog( - context); - }, - ), - ], - ), - SizedBox(height: 4.h), - Text( - 'Your Groups', - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w600, - ), - textAlign: TextAlign.start, - ), - ], - ), - ), + Widget _buildHeader() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Welcome message + Row( + children: [ + Flexible( + child: Text( + 'Welcome back, ', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ), + Flexible( + child: Text( + _getCapitalizedName(), + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + color: Colors.teal, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + + SizedBox(height: 1.h), + + // Ready to explore + Text( + 'Ready to explore?', + style: TextStyle( + fontSize: 20.sp, + fontWeight: FontWeight.bold, + color: Color(0xFF673AB7), + ), + ), + + SizedBox(height: 3.h), - // Your Groups - _buildList() - ], + // Create and Join Group buttons + Row( + children: [ + Expanded( + child: HikeButton( + widget: Icon( + Icons.add, + color: Colors.white, + size: 5.w, ), - )), - ); - }, - )); + buttonWidth: double.infinity, + buttonHeight: 6.h, + text: 'Create Group', + textSize: 14.sp, + onTap: () async { + CreateJoinGroupDialog.createGroupDialog(context); + }, + ), + ), + SizedBox(width: 3.w), + Expanded( + child: HikeButton( + widget: Icon( + Icons.add, + color: Colors.teal, + size: 5.w, + ), + isDotted: true, + buttonWidth: double.infinity, + buttonHeight: 6.h, + text: 'Join a Group', + textSize: 14.sp, + onTap: () async { + CreateJoinGroupDialog.joinGroupDialog(context); + }, + ), + ), + ], + ), + + SizedBox(height: 4.h), + + Text( + 'Your Groups', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + ], + ); } Widget _buildList() { return Expanded( child: BlocBuilder( - buildWhen: (previous, current) { - return true; - }, + buildWhen: (previous, current) => true, builder: (context, state) { if (state is ShimmerHomeState) { return Center( @@ -310,64 +337,9 @@ class _HomeScreenState extends State { } else if (state is LoadedHomeState) { List groups = state.groups; if (groups.isEmpty) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: Padding( - padding: const EdgeInsets.only(top: 30), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Lottie.asset('animations/empty.json', - width: 200, height: 200), - const SizedBox(height: 20), - Text( - 'You haven\'t joined or created any group yet', - textAlign: TextAlign.center, - style: TextStyle(color: Colors.black, fontSize: 14), - ), - SizedBox( - height: 20, - ), - RichText( - text: TextSpan( - style: TextStyle(color: Colors.black, fontSize: 20), - children: [ - TextSpan( - text: 'Join', - style: TextStyle(fontWeight: FontWeight.bold)), - TextSpan(text: ' a Group or '), - TextSpan( - text: 'Create', - style: TextStyle(fontWeight: FontWeight.bold)), - TextSpan(text: ' a new one!'), - ], - ), - ), - ], - ), - ), - ); + return _buildEmptyState(); } else { - return ListView.builder( - shrinkWrap: true, - controller: _scrollController, - physics: AlwaysScrollableScrollPhysics(), - scrollDirection: Axis.vertical, - itemCount: groups.length + - (state.isLoadingmore && !state.hasReachedEnd ? 1 : 0), - padding: EdgeInsets.all(8), - itemBuilder: (context, index) { - if (index == groups.length) { - return Center(child: LinearProgressIndicator()); - } else { - return GroupCard( - group: groups[index], - ); - // return GroupCustomWidgets.getGroupCard( - // context, groups[index]); - } - }, - ); + return _buildGroupsList(groups, state); } } @@ -378,4 +350,141 @@ class _HomeScreenState extends State { ), ); } + + Widget _buildEmptyState() { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 4.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Lottie.asset( + 'animations/empty.json', + width: 50.w, + height: 25.h, + ), + SizedBox(height: 3.h), + Text( + 'You haven\'t joined or created any group yet', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.black87, + fontSize: 14.sp, + ), + ), + SizedBox(height: 3.h), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: TextStyle( + color: Colors.black, + fontSize: 16.sp, + ), + children: [ + TextSpan( + text: 'Join', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: ' a Group or '), + TextSpan( + text: 'Create', + style: TextStyle(fontWeight: FontWeight.bold), + ), + TextSpan(text: ' a new one!'), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildGroupsList(List groups, LoadedHomeState state) { + return ListView.builder( + shrinkWrap: true, + controller: _scrollController, + physics: AlwaysScrollableScrollPhysics(), + scrollDirection: Axis.vertical, + itemCount: + groups.length + (state.isLoadingmore && !state.hasReachedEnd ? 1 : 0), + padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 1.h), + itemBuilder: (context, index) { + if (index == groups.length) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 2.h), + child: Center(child: LinearProgressIndicator()), + ); + } else { + return GroupCard( + group: groups[index], + ); + } + }, + ); + } + + void _showLogoutDialog() { + showDialog( + context: context, + builder: (context) => AlertDialog( + backgroundColor: Color(0xffFAFAFA), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + contentPadding: EdgeInsets.symmetric( + horizontal: 5.w, + vertical: 3.h, + ), + title: Text( + 'Logout', + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + ), + ), + content: Text( + 'Are you sure you want to logout?', + style: TextStyle( + fontSize: 14.sp, + color: kBlack, + ), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + HikeButton( + buttonWidth: 25.w, + buttonHeight: 5.h, + isDotted: true, + onTap: () => AutoRouter.of(context).maybePop(false), + text: 'No', + textSize: 14.sp, + ), + HikeButton( + buttonWidth: 25.w, + buttonHeight: 5.h, + onTap: () async { + appRouter.replaceNamed('/auth'); + localApi.deleteUser(); + context.read().googleSignOut(); + }, + text: 'Yes', + textSize: 14.sp, + ), + ], + ), + ], + ), + ); + } + + String _getCapitalizedName() { + final name = localApi.userModel.name.toString(); + if (name.isEmpty) return ''; + return name[0].toUpperCase() + name.substring(1); + } } diff --git a/lib/presentation/home/profile_screen.dart b/lib/presentation/home/profile_screen.dart new file mode 100644 index 0000000..fca37be --- /dev/null +++ b/lib/presentation/home/profile_screen.dart @@ -0,0 +1,199 @@ +import 'package:beacon/locator.dart'; +import 'package:beacon/presentation/home/home_cubit/home_cubit.dart'; +import 'package:beacon/presentation/widgets/hike_button.dart'; +import 'package:flutter/material.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; + +class ProfileScreen extends StatefulWidget { + final HomeCubit homeCubit; + const ProfileScreen({super.key, required this.homeCubit}); + + @override + State createState() => _ProfileScreenState(); +} + +class _ProfileScreenState extends State { + bool showSelectImage = false; + int selectedImageIndex = -1; + + final List imageOptions = [ + "https://cdn.jsdelivr.net/gh/alohe/avatars/png/memo_2.png", + "https://cdn.jsdelivr.net/gh/alohe/avatars/png/memo_3.png", + "https://cdn.jsdelivr.net/gh/alohe/avatars/png/memo_5.png", + "https://cdn.jsdelivr.net/gh/alohe/avatars/png/memo_35.png", + "https://cdn.jsdelivr.net/gh/alohe/avatars/png/memo_34.png", + "https://cdn.jsdelivr.net/gh/alohe/avatars/png/memo_8.png", + "https://cdn.jsdelivr.net/gh/alohe/avatars/png/memo_10.png", + "https://cdn.jsdelivr.net/gh/alohe/avatars/png/memo_16.png", + "https://cdn.jsdelivr.net/gh/alohe/avatars/png/memo_24.png", + ]; + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Column( + children: [ + showSelectImage == true + ? SizedBox( + height: 45.h, + width: double.infinity, + child: LayoutBuilder( + builder: (context, constraints) { + // Calculate the number of columns based on screen width + int columns = (constraints.maxWidth / 100).floor(); + + return GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: columns, // Number of columns + crossAxisSpacing: 10, // Space between columns + mainAxisSpacing: 10, // Space between rows + ), + itemCount: imageOptions.length, // Total number of items + itemBuilder: (context, index) { + return GestureDetector( + onTap: () { + setState(() { + selectedImageIndex = index; + }); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + border: Border.all( + color: selectedImageIndex == index + ? Colors.deepPurple + : Colors.transparent, + width: 2, + ), + color: Colors.grey.shade200, + image: DecorationImage( + image: NetworkImage(imageOptions[index]), + fit: BoxFit.cover, + ), + )), + ); + }, + ); + }, + ), + ) + : Center(), + + SizedBox(height: 2.h), + + // -- IMAGE with ICON + Stack( + children: [ + SizedBox( + width: 120, + height: 120, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: Colors.grey.shade200, + image: DecorationImage( + image: selectedImageIndex != -1 + ? NetworkImage(imageOptions[selectedImageIndex]) + : NetworkImage(localApi.userModel.imageUrl!), + fit: BoxFit.cover, + ), + )), + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: () { + print("select image clicked"); + print("showSelectImage: $showSelectImage"); + setState(() { + showSelectImage = !showSelectImage; + }); + }, + child: Container( + width: 35, + height: 35, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + color: Colors.deepPurple, + ), + child: const Icon( + Icons.camera_alt_rounded, + color: Colors.white, + size: 20, + ), + ), + ), + ), + ], + ), + const SizedBox(height: 20), + + // -- Form Fields + !showSelectImage + ? Column( + children: [ + Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w), + child: TextFormField( + readOnly: true, + initialValue: localApi.userModel.name, + decoration: InputDecoration( + labelText: 'Name', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + ), + SizedBox(height: 2.h), + Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w), + child: TextFormField( + readOnly: true, + initialValue: localApi.userModel.email, + decoration: InputDecoration( + labelText: 'Email', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ), + ), + SizedBox(height: 2.h), + ], + ) + : const SizedBox(), + + Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w), + child: HikeButton( + buttonWidth: double.infinity, + buttonHeight: 6.h, + text: 'Update Profile Image', + textSize: 14.sp, + onTap: () { + if (selectedImageIndex != -1) { + localApi.userModel.copyWithModel( + imageUrl: imageOptions[selectedImageIndex], + ); + widget.homeCubit.updateUserImage( + localApi.userModel.id!, + imageOptions[selectedImageIndex], + ); + setState(() { + showSelectImage = false; + }); + } else { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Please select an image')), + ); + } + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/home/widgets/group_card.dart b/lib/presentation/home/widgets/group_card.dart index 244e0c4..03eed20 100644 --- a/lib/presentation/home/widgets/group_card.dart +++ b/lib/presentation/home/widgets/group_card.dart @@ -15,6 +15,8 @@ class GroupCard extends StatelessWidget { @override Widget build(BuildContext context) { + print( + "GroupCard member IDs: ${group.members?.map((m) => m?.imageUrl).toList()}"); String noMembers = group.members!.length.toString(); String noBeacons = group.beacons!.length.toString(); diff --git a/lib/presentation/widgets/hike_button.dart b/lib/presentation/widgets/hike_button.dart index 538922b..5a45fe9 100644 --- a/lib/presentation/widgets/hike_button.dart +++ b/lib/presentation/widgets/hike_button.dart @@ -17,7 +17,7 @@ class HikeButton extends StatelessWidget { HikeButton( {this.onTap, this.borderColor = Colors.white, - this.buttonColor = kYellow, + this.buttonColor = Colors.teal, this.text, this.textColor = Colors.white, this.buttonWidth = 100, From 18ed155d86b8d60638251af66b388801d69429a8 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sat, 5 Jul 2025 14:04:18 +0530 Subject: [PATCH 05/19] user image instead of location icon --- .env.example | 0 images/location-marker.png | Bin 0 -> 1583 bytes images/rain.png | Bin 0 -> 5028 bytes images/wind.png | Bin 0 -> 3524 bytes lib/core/queries/beacon.dart | 7 +++ .../cubit/location_cubit/location_cubit.dart | 48 ++++++++++++++---- lib/presentation/hike/hike_screen.dart | 4 +- .../hike/widgets/hike_screen_widget.dart | 6 ++- 8 files changed, 52 insertions(+), 13 deletions(-) create mode 100644 .env.example create mode 100644 images/location-marker.png create mode 100644 images/rain.png create mode 100644 images/wind.png diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e69de29 diff --git a/images/location-marker.png b/images/location-marker.png new file mode 100644 index 0000000000000000000000000000000000000000..7c7b62043bb2982a847336dcab91e344d227a211 GIT binary patch literal 1583 zcmV+~2GIG5P)o7BS?i~=Z+PYWk-bHKGY9HFV z){QoTv9?;Lbg{+6s+C6L1852Xq-=vMGay(fQ>m6(WDv1dkYyAQa3Dbi3O>}D{G0O) zG^CGBI&c$Da*~_5^KgFW{LlaUuEFpmp2X;h3S}%`rHSuWsbWE;RcfeE#DQNd!oYcx z*id5R^9v53nld2jft7w`L3h(Sm@IF5Y-Wj22oTA}oh>UBRj7qB?q9jPb zSy@1iz+X${ICVsezi#1`x)5VW)AR|rJ%1`%UzvkDugyo>iUnw0ItR@Qr!kQFP!ofw zP$ZO@MSO1*;F5S`30(VkJZ~8>+0+KhSDU7Vpn1`Bw7zICsmYs(szz6~qTzGewp zUYyAwNLD1La*K!`2B(lM^2p%0D=C`xbie^>R5`lg83niJJ&U$gi_rDXYTVnk5k33D z(PIzCz4tevYs(u9;`V~6Xq=+5ouDb4Hwh>*3dj?AI0PQ49QRqk-R8O`_ccuoM$1dH zaQE$3(YbXDbGd?8BqPBls&hhJifTw;f%Z*RZ|t&JJTTs9xwopA{Gz;b+vWrTy$<+iTfXJ!N8%t`19MZ@F?Lh2EMlA!AINBwRtt} zET0GOtT5F53&1%HAk!$i0tRr|B6@$f$!L7ehL$BWare!o=#E&6-cNU+|Ii)`9NvrG z&m)+ud~27Z<>lERr@CSD+bK!t+;9nT92`cuAz%QP6rsOHlTaUK#;v)NnMBBSx_7L_ z{SP*?X>>=fW729{wU8}3E&Okqj9(QIKhhG*0&+zjnYI)cvJBV+*WD%LK zldcJZCegfj26G%b#7J7VUYG)MDq7weRbt&I3muYA*3be=B61XSeZT;UjeI_xs8kR^ z&?M?7ni&Xbosym#!Yr)wS5=AmKWV>sSobpr<#WblM4nY~1`MD;;=`$sGLs0J1bI8{ z1RYcaLrF_czme1~DIzO==pIDc|HnQ>0XY^E3T)P`0RzYxJC;8y3%z88Zj-<+X#_&I zPI4JadZxnZ9W8CK(O*2Bz9gLto@3hgci+;JuW92Afs9;{-=8n>>`Fk>psOL-Rk2VKC<@Z1&ZD1lISsnBzSNza9{z~1FaJkz{m zSm(2hGIA9&@-@|SVI(ed6m%7K2weLafk%$Wvknsw6->|c8@;2#X<^fciVyM^@6yz^ z0;|<*7|C!5f;pWV@1X)I9m5Kyq>c|2o+*js_e?e}o3*AeBxGbiXoky)uEePv*Gt+S zN-8>?fMkg+D|OweDm|q6;?U5i45KYFRh7#!8nopS~yH(=$L^T%7&^IyyQ~T3V{#bD+JwolW5M>C^fHh>wp)Yilb5NJ&Z2A3$Pa zBD`KNlTcb(n*IP%Q&Uk}Tgw0(4u}2#va_>MU0uy2MBfDly+v+ruBW`b{IT2Ze(Z9& hDo47WCt>)X;%^oACdOO1g|+|y002ovPDHLkV1j-g;(7o8 literal 0 HcmV?d00001 diff --git a/images/rain.png b/images/rain.png new file mode 100644 index 0000000000000000000000000000000000000000..44ba7d1f11cf7e42388eb3db383e40076f294489 GIT binary patch literal 5028 zcmV;V6I<+wP){4> zr@n|Ux~sdZDl@;z%s>D9Gg~H;AVGoz2@)hokRU;V1PKx(NRS{wf&>Zbk@KwlUvldz z{+F;oq4=GKpGoKnhbkG7d&YUk7^aR9!O{hE<2@HQl@U=)C10OTJzLH29Fm@#9%irli{ z(9$If;X1_-7DI=?+s*=Wtt_zIXev%ku|k5WEkaGr5bWZB74v-IZD$Fy!2{r7Zi3KN z%W*s{nRLo(1<9#VKvDk^m2X~Y<wJ{*7(fSn&XTlOVmYQH~yYdWys zI-?PTem=f88V`zdQNUbAw1)>CO}0nPXq#sI$;biQJiU;A?GoO;sYdb5OdQx6 z1&^sla50>Ss4W|iTKYh(FgI!|!PUP&+3OcnB=TBVSB-}+YU$@KS@Gu8BLJ&a=q~#b zVe;oc|Et-c0nuiI`oD@^wdBpyM+Ip3P)nH>e+=AVl`Yj8a? z8B1r&VKs6%A~!`LQ}MK!Ao;Zwq{GJJSI=lOMqWvWPMRt5c}qTd`RwQVhWA8>kdHK% zeF;c?{P-W4nu@-RP(E?Kug@y;A%k9QTCt#Bs3>VFP{NE0r;upjs8tLlFIYPx?d?JAQdC!s2QO++ z{9I7?{dL*r3;=(VC3238z&%!G>hhyv$n9;gYQ7)pUsb3rysfilqH>f~y9KElZH+B6 z<+RCCcU`gSC(LoMMu4{$G8B*7Z4xsA_cL2j@r~?r1S{~p7?Dd=%}`3oUAUPT;lP$C zjfIwzl8AJZ)*ZO6%%19i%%pg=*Cu@ZvI0wdX29Fo9@qE|h*DE-D*N2P)bL{%fvZ!^ zNLuMbcv_hwX46`Yh1Kw(7TdjNbyy4~cgW>!-sAl{0c#fp!q3wkSyg52tRfR7v-mo03wpvs@zM!dClt4gcYWoC~j4}Y=N)bZV00So0GSh;d(J8KEI zU-r2mM^qTFBKM+YCN}Nb244qjysfWlvykF_oxpqgqtG`H zX}$qSoZ*8rF4K`S!xtr9^H5_kU9BLO&Ft~$Zl2nEsT%{`zL5bpW24RpLTXA)W?kj~ zb*mV0z~8X~U)wTq$5JlB*TI&WpSz>N;bYtAA~!VX1vVy-vRFPx&@Vy6jXkywz^ zDNz`aPwpO?};vB$INbV}A?{1}AJ z_k*JBE{@1&(L&v8?*zB0hS(Onuhqm8Q(X?Dz6QV4#L7|}N=iWRyg6|DVI;20{ZKR3 z7DpXiQG6o{^F3VXybAGQk%_G2`?shsi}=$n6y;+}%r4Aww1uaM5q#||v1#|V&eaks zio))bi9zFH)tI*}>FeQ22ZP$o$*ig}DvX!;R7CswAYkHTtXa2O;|RK?AgWBrXW&1} z3#-j7vBAX!j^oE*!;UR&Iun)L6?YgW1N&oPkbkEIVFm6{mz%>RIGK!6Ti*QHySI4p z{vDlPQ2qQ$$sLWecIp_uW)+P4^+jmd^0uG8HUw$9*4i-IG{&5XYxX&*rQvJCY2*s2 zwWMZ|>nXoKc>WBQFaNRgf)LhlKXjQG7x3UWtiWGXcVjZCLuT4Kn}KxhCpb=?fXfeW zcUllqBX%A=*mRsya);JdSHlU|u_hFW$6|13`$qWKSz@}S8P46z){h9PX%P6GT_y+# z;aA?M`t57H|GA+V-!(MgdTpiNGsSjL5LFV7?ldzK?1~Ii>$*ug84IV0<8bkAp}y1l zP;UYN_-6pWCoFzZH!)XK{G3-)0YcI=k@;vs@?LqIEh;FLzsnI7JwlMBOBT|Rcv~j- zsRKLVFZZOsPkwNVT64>mE~3rn{IsU5`BCNq%QJio*M z2n}07&1fRv>4Up)bjK$6x!6;Sj~EQ?PIqm2f4$U@kHY+>?*02o1+|ql^uMsKibiLU zrisKVX`?=xu(Q`$1o`{Ibm(B(NgEI72a8c7>3c$}kt48t#S;2dE6*Y3EHSAQ^0KIz zOU6IC3``?}{b{rc_q15;^sn96QSENl$h?p~1sVFTp`nCWQMok=Jb(X|hF0?RFY)sI zJFO(Y>u`;zEXA_ri(%Ba59}<=;T5$30m;WB9gEQSri{AtcQ2||PrDw_vds4_Ns9TxcewzJt%43p^-ba70J zo7{TVW)MK9nOA*$&Z34kAvA8@L0Bj_A3@c2ji!EH<}7D zJt-cWS1!hfcOt44k5I|o*E%PoiO=iGXwV+1f!)$%Q9(p5MpKwfXKH{ZW>uA8w%0UR zj2Vr&2}#X`&>)DC-g^&XO258{*|esae=2mI56p)QY-;AFeAfpdo^D{SoYOe3uZiwc zCgFThzRvl%_MHDWy$ZsLTwGVw0!@tEydF~r_J?2mIrU<+Aqb&-^ET?I%FDQjjmsBO z0bCp%Fk}5@%sG0BUUz2nPPlr_pv~crv!6Z`?p7P$WjA^xwd6GBN#G{wU64$%e8i^`#D3CvxGz}LcJ0V3f}^bs4GT5y z{~aw*ZNFE?`6-!jo)>_S+ycywJxZIlqWtuB^(S$Aqp^5l;HPyirUGBFB3Iev&DDcj zv`~DH#cM50Co3!5x_%8dV@4t1a$1K3p-tB5^VFOs?;Y%HHP#YMEWVKmxt*1U{)2l* zVwYuI0aoP0x=IM*2Dw;#lR5&4Ac5DiKbateZXpZltLK_^bl!%{Zj$+RT?Dyzi=~c9 zjd>%1f?W|Vlv?}vYdVRqMij$e)pQ1sH*|q=CkI5QF%nEWRHO>&cTaW9@qQ zojgkmMB}(ajUZ%v(NYl7*~_DM(9T*{LCDx8ASI*m{k~YtIdO)j#VcYhY{rj+!`M-H za66}!7>crcbSk*bPQ9x}>q@XS+=9-EL7FFv5yRl*>D8`+3sED8?buNWNKU1n1!d*K zY{W3kcC>@LpI@hHjgmo`d2n)Zp-UK)HHE|kGb!ibJaHVh?AfkkzPa_Xt^y-+UW~}U z>Zl;|=gx-Jlqm?#zS-e|a&dA(PU;2BadE_~9ee3#GoyE5?lfmyJr@Uexm+)T5C${( zF#Ta9EQSt(>*NX4H8aE360sN0cF(+sT2U%X|Humb;g7rM=$tUJ=%2VYNa1y+kUYa8 zuyn3B3UX3mIdVAM)qkn!3tcG ztUE!%@--!n1PT}r><`!Z!L4Tk^R#nbVse|`6Ay38PF=or2izAgZNDJ71#t461Ct?x zXvk^D1yM}v_O&9c$UWDTK+-uH59kjYTN~PZ1*U8FUn%Jt(%c*Dd-Mby6gC|nild`% z)!z{Rn3b8S&i9y~nuS@rV_;=&j;VtN(CEevcjh8r)+4r~C0pdRmC%H|Ow63_POTS1 z6(5pX_BBA@fmlQo-oflOkyvq!rSAzB96N!m7c9MewdpvN z&4=-z#z=g1Be7np6HN)0~wF<7@c5pAES}evPJ~egsBk8&N_bioBUJ7nSkKAUKJRyw8=j%Ju3Hb~i^(&80WTYS@u=$#f z1Bvmi&Li=VMcwvj&2}a~EwcHdzWncLivQA&tlR@SJx?&gV(1=jgrxI>vQ=7&4 zlD?+p$}18E>f_syFOoA>T$HK7)lkWXxkbeE4q-4gPvlCX+Qi*@nG)&ou7UOWH_Z*V;nYybF9r z2m6r{5__!rhhK&Uy;hKIXO+jsy?g&*a_`=A^#4eG;mE8m{|gB-qFWuQ*`}khljYx# zKXf6M6Im*zYtu66yYK$J>vQmhgRQ9eh77o}tNB4p6*rI(O1~v>#@(GR2@)hokRU;V u1PKx(NRS{wf&>W?BuJ1TL4xda!T$reqUmhaj5_N80000P@q780tE^bDCqBS z88ql$z78XHcn!BzyAFQsiMO4t;Oj7A^Vq?IpELKh4LG=1Te*1K+1~#oGr3t+T?I{# z>H&Xl&q#XgHGJrOm%*<%egW{Sih63t3M7u>$w5{i7df7)Ii9qzJZ-|?S%G}V3G_=& zAj`S>+W+RmmEW1WZG8^otzUit#rW=QDLiRu=)?<^#o#x}u_?dmn>(C9K`-*Of#qo~ zTStbOyPbUoz78XHU}SV9MjN-JCBlavybrqyk3eLQKX}>Mip1ZyAKWWZ98yr)pc-oK z_F6o=?QGSUp1Kp{#?^D+KGYhfyN!pFhxft#J1Y3;E`idcUqOhw3nV12lqyOyC(zWQ z>gwN{yWJK6S1YUk_&V4hoM=1rzQ<7O2fmIY@?ET6d(!yeR=0vQKjtBMaV)6of9Ukw z9pNTS8}o+rND(NoJUz$N{rD0y(8cf?K1{u9Q)V-N`#RhvmtlKmQj6`!nt~Lz>)ehO`KEol_3-^V|$^nrmo;JZ|D);xHSC z3JHWA`TO+}h2?4efJk3HBkfYAjB;qiK^O<%$4$IeC+^v@0k)>CfsIKk;p24)y-Ha~ z9AA#?+X?<---P7!WIfXtxxm)c{DJAuk%401fZJ8)ohm*Im8C~|ErKp#{GG?ZuHyyz zrmqyHzXH<~Mn?ONYJ6}DKFvzOfgEzzt`KKV#t;Nqc<>9+qv#nWE&bM~c-RbiglVdM zCw-Qa4!2a7jV3~S^0%&om)$VE2YHq!hnRsP$et}5OdFynhu&4!09Tj?d$;Alu8kQ`uxAHUoh^Zu$AYBSW4hGampn^d*-U@K1glpj z28?<0CtZX36CJF%egT%nM1i-x9R&LOL&$=KFg@-)m>v-cZ+p0b-)JZJG&>C*+^^O7 z9(Yb%V%eUz&p<(_x(cd(%k*_%Bg*S*$Fe+0VtHB;AHTE#mo(5tqj7uEgBW@L+Xg;P z4lr~1N{BsK4vS7-=*0PXMqy zzWql|Ad5Jj)OBnat}kKgzya39>Q$X|wH^!;UmqoQcvnJnWLSC@xY^ohy$7BH&t-6A zXitGZV)`P=>WPo&bbHbGUB?fD_v@qJ-J?a?(UAW&gr;Ob$VV$6EHfKoPq13g&(6w$ z$>YXCvFf@E0(9a&rZ1rg;MZuRa8}Z*)W8!6@SX^BazEB|^g^~0g2E%f+sOeIESL}R ztCmC5oKWy`u!mV|lC(q#44VxJ$*W}-gyl&U(^mihzu*MAS&oM9KV1gCmn zo)!=Y??y+$Nm{LSZ2pBxnC9UMGgq(G92-+`47?p3fUW;gW3Y`v-{QA%00$`dmJV~Hr|`9HBdt&v+0wq6C>4(x^XTQSZ*;m1Bek4maZKkS=&F>I%fU0jo{_z05jsd_cQ^sLSeG=SaB@t zUKDb-OkF#Xu@qoU6vxw><`yHr>Wnys#1@umI(CYu_878mLk9RdJ4^O7vokh`(bAJB zSRldjbUI@R-~{SsZZQsBsD!ERE--7&S}m{AewL;2G+&=Ej;G;C3-*O22$G;r=M~+& z3Yl9sii0RJIWtupT=BX6r%N@D>mnY5$ClRKgw@GwATn&G7#X37$sK%17_3TKt?g-Y zHXGH`uspfL-mZDk+=3j_M-0r%0bya{AS!b4Qiv^nn#-fV%zO5@4&pI*Y#etM-icoY zVOcp4dxF#cRA*+3=V=?1QAAUD8dji3O)dgX_r%3Qfu?Z+eJE8BoGqakQu6mA1Bre` z%=?wBPEQk-vB9XG2B*X(69e<4SWIR>uE$hmEJ30V6hOd?8SwUmaiRbn&pj_`%AA?vmp@yK5tOnr_LHc}5ci zzq*vOv6jw!37)3MC=tkRZ;AtcZa?v<~@bsJBG_a%Z(8c6-ene~# zParjSeUGn`i>uZqNb)phU8ZMwm2+1oYiwy2X^`*G#l_G3h{#jtA!J#6&##k-AxoD@ z@-&jklVQVcee`q{HFEln6$Wx``Z{rv z@b$ItVAYzHCh;`o0*z(l1}hNS+Y1}0tdPU0w~Lf3`1hlhmd36kK^GoM7+3l_OKPjd z>c@cbeC3l0Q_>$C|%ucV<*H=g`SkQyGR3AFU z50=cC1((jVVjc12Z_D9>$S?>Gm@0lp(<(@?Q3Sy{*7ngMWo9LZFPXmN!%j0FeSL+# z1q)KRIw?UEqhswcR^Fp1A1r+z7Dh+HB&U&2Jce37^0Bu&>@sA~AlU=~fM2#vedVAz ze93%03Kn`Irm0I8*e(mguUQuEpB_m6jWb(&EVZ$C|=pI1K?(Dh4=U>95>{pf6FRLZ`Za= z%|qW-g8xJ}$ps52#^g6f!LCi2;wQV~{&{_gcc3^|Q3N-xorldSt6RK=+X}LZ(ryL# zjJZ3TlSq9E7BDhC-BR6pNRi>tIusm7E3FH(fP|{i` z2A0TZi3JOorZ6((5~Qmw*`C8}?lGop}jJJPPego03&vBfxWh6CRYMQ?#V zo;ttp)~8@0?_7mAsG}%yMhZp-is5N%bJzLRS50?!A>8t8Y(e&Swbd;!5Mt&vn*ZM@joOXkcFf6j29AQFGa=h`2OK4g;ph^^g*i$zJ&Ka!402golQynrja#!CkpZq_YPLp)jiMjRYR8dzXn|3J zvqn~^oy_zHutMz=b8Tf{dGdr6$kyI0c;b6-Y8t`x7qmsnoyU(fk)ThVS8t+**}$ z{e$Vx!1TolG7Sr@p$b0sR4|rr|74^2C3O^mL~55RDu@hV{6(+Q1AH;EnC^ z)<(vO3|dm&Q2TP@q780tE^bC{Un4fdU0gC;T6V41^+ { // // adding leader location if (beacon.leader != null) { _leader = beacon.leader!; + print('location state leader: ${_leader!.imageUrl}'); // creating leader location @@ -114,8 +115,16 @@ class LocationCubit extends Cubit { position: locationToLatLng(_leader!.location!), ripple: false, infoWindow: InfoWindow( - title: '${_beacon!.leader?.name ?? 'Anonymous'}}', + title: 'this is ${_beacon!.leader?.name ?? 'Anonymous'}}', ), + icon: BitmapDescriptor.fromBytes( + await getCircularImageWithBorderAndPointer( + _leader!.imageUrl ?? + 'https://cdn.jsdelivr.net/gh/alohe/avatars/png/toon_5.png', + size: 80, + borderColor: Colors.red, + borderWidth: 4, + )), onTap: () { log('${beacon.leader?.name}'); })); @@ -135,13 +144,13 @@ class LocationCubit extends Cubit { } if (beacon.route != null) { - var marker = Marker( - icon: - BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueAzure), - markerId: MarkerId('leader initial position'), - position: locationToLatLng(beacon.route!.first!)); + // var marker = Marker( + // markerId: MarkerId('leader initial position'), + // icon: + // BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueYellow), + // position: locationToLatLng(beacon.route!.first!)); - _hikeMarkers.add(marker); + // _hikeMarkers.add(marker); // handling polyline here for (var point in beacon.route!) { @@ -346,7 +355,7 @@ class LocationCubit extends Cubit { if (beaconLocationsEntity.landmark != null) { LandMarkEntity newLandMark = beaconLocationsEntity.landmark!; - await _createLandMarkMarker(newLandMark); + // await _createLandMarkMarker(newLandMark); emit(LoadedLocationState( polyline: _polyline, @@ -401,6 +410,14 @@ class LocationCubit extends Cubit { if (markers.isEmpty) { _hikeMarkers.add(Marker( markerId: MarkerId(_beacon!.leader!.id.toString()), + icon: BitmapDescriptor.fromBytes( + await getCircularImageWithBorderAndPointer( + _leader!.imageUrl ?? + 'https://cdn.jsdelivr.net/gh/alohe/avatars/png/toon_5.png', + size: 80, + borderColor: Colors.red, + borderWidth: 4, + )), position: _points.last)); } var leaderRipplingMarker = markers.first; @@ -420,6 +437,8 @@ class LocationCubit extends Cubit { if (initialMarkers.isEmpty) { _hikeMarkers.add(RippleMarker( markerId: MarkerId('leader initial position'), + icon: BitmapDescriptor.defaultMarkerWithHue( + BitmapDescriptor.hueYellow), ripple: false, infoWindow: InfoWindow(title: 'Leader initial position'), position: _points.first)); @@ -638,7 +657,7 @@ class LocationCubit extends Cubit { return _address.isNotEmpty ? _address : null; } - // update here + // update here Future createLandmark( String beaconId, String title, LatLng latlng) async { var dataState = await _hikeUseCase.createLandMark(beaconId, title, @@ -719,6 +738,14 @@ class LocationCubit extends Cubit { final markerId = MarkerId(user.id!); final markerPosition = locationToLatLng(user.location!); + final Uint8List markerIcon = await getCircularImageWithBorderAndPointer( + user.imageUrl ?? + 'https://cdn.jsdelivr.net/gh/alohe/avatars/png/toon_5.png', + size: 80, + borderColor: Colors.deepPurple, + borderWidth: 4, + ); + // final bitmap = await _createCustomMarkerBitmap(); print("logging user marker creation: ${user.name}"); @@ -731,8 +758,7 @@ class LocationCubit extends Cubit { markerId: markerId, position: markerPosition, infoWindow: InfoWindow(title: user.name ?? 'Anonymous'), - icon: BitmapDescriptor.defaultMarkerWithHue( - isLeader ? BitmapDescriptor.hueRed : BitmapDescriptor.hueOrange)); + icon: BitmapDescriptor.fromBytes(markerIcon)); _hikeMarkers.add(newMarker); } else { // If the marker exists, update its position diff --git a/lib/presentation/hike/hike_screen.dart b/lib/presentation/hike/hike_screen.dart index 9ac0000..5a7f874 100644 --- a/lib/presentation/hike/hike_screen.dart +++ b/lib/presentation/hike/hike_screen.dart @@ -257,11 +257,13 @@ class _HikeScreenState extends State color: kYellow, ); } else if (state is LoadedLocationState) { + print('Location State: ${state.locationMarkers.length}'); return GoogleMap( circles: state.geofence, polylines: state.polyline, + trafficEnabled: true, + mapType: MapType.normal, onLongPress: (latlng) { - // share user info HikeScreenWidget.showCreateLandMarkDialogueDialog( context, widget.beacon.id!, diff --git a/lib/presentation/hike/widgets/hike_screen_widget.dart b/lib/presentation/hike/widgets/hike_screen_widget.dart index fdfc4d6..16af81f 100644 --- a/lib/presentation/hike/widgets/hike_screen_widget.dart +++ b/lib/presentation/hike/widgets/hike_screen_widget.dart @@ -215,7 +215,7 @@ class HikeScreenWidget { ), child: Container( width: size.width * 0.85, // Set a reasonable width - height: isSmallScreen ? size.height * 0.25 : size.height * 0.2, + height: isSmallScreen ? size.height * 0.45 : size.height * 0.4, padding: const EdgeInsets.all(24), child: Form( key: _landmarkFormKey, @@ -223,6 +223,10 @@ class HikeScreenWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.center, children: [ + Text( + 'Create Landmark', + style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + ), Container( height: 60, decoration: BoxDecoration( From 842798dfcb7474846176d292b995c271cb3757f9 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sun, 6 Jul 2025 14:31:49 +0530 Subject: [PATCH 06/19] added info icons for landmarkers --- images/icons/camp.png | Bin 0 -> 1524 bytes images/icons/destination.png | Bin 0 -> 1898 bytes images/icons/forest.png | Bin 0 -> 1926 bytes images/{ => icons}/location-marker.png | Bin images/{ => icons}/rain.png | Bin images/{ => icons}/wind.png | Bin lib/core/queries/beacon.dart | 9 +- .../datasource/remote/remote_hike_api.dart | 6 +- lib/data/models/landmark/landmark_model.dart | 8 +- .../models/landmark/landmark_model.g.dart | 9 +- .../hike_repository_implementation.dart | 4 +- .../entities/landmark/landmark_entity.dart | 34 +-- .../landmark/landmark_entity.freezed.dart | 26 +- lib/domain/repositories/hike_repository.dart | 2 +- lib/domain/usecase/hike_usecase.dart | 4 +- .../cubit/location_cubit/location_cubit.dart | 51 +++- .../hike/widgets/hike_screen_widget.dart | 233 +++++++++++++----- pubspec.yaml | 1 + 18 files changed, 277 insertions(+), 110 deletions(-) create mode 100644 images/icons/camp.png create mode 100644 images/icons/destination.png create mode 100644 images/icons/forest.png rename images/{ => icons}/location-marker.png (100%) rename images/{ => icons}/rain.png (100%) rename images/{ => icons}/wind.png (100%) diff --git a/images/icons/camp.png b/images/icons/camp.png new file mode 100644 index 0000000000000000000000000000000000000000..780a798f18c09799819897cf3ec5ef370c238404 GIT binary patch literal 1524 zcmV#7`5oX zA#Rr~155uBM7);|$iT1E(@=Lva|V>E*J z3*Nc$h`9Fj0tRTOukte?&tpUyeimE-7!43%XV{sOx6>wHBH}1BCC~SHw>OuVLX3C? z6F8*$8IYGP^I0Y7&8H#HgV-&}ca9Oa-r-$OZt@{nd~^bO>EC=-IUD2&F3H3VvnybB zSg$*=^sOkW&oue^K7`MI-Ln{F#Vhfid;``g(y_NJ0k_V*9GMVrsbZaMVvgUL?2@X; z=E9bFhHO8 zxP`=1v417IZe4GEmhd1y3Aa0~34_Ig3`yF>~g`G|XYto5_dq7zzL`~DJBbx zi%@UcgsW@w@1%f>!6*Lk@_byfu7RO8 zm`flw8!eoG42=#{YfVZ8`H-?jhr$Dmf*aPT3!)aVQ@dK+g*M{3EXj#q=t@RObyY~> zfym#bn`)|(JE+~|d3T6_%wnV9LLzEKnks4mDOpO4Ta)n3U_5S*E#`Vz=ytu$E(Ci@ z0iC5bBI%-o4QTkJ_{Q1O8CkvomTPp}apYE13vS9OKNvN6f>rYDpgcP)buc#?aH6FE zj*c{ppG-taSw56HLqy_Q^Ip6mi}H| zTlq+1^0ZUN4*iS-yt^?gEU8>$6<4^8+jfUsqqEeyo0_(LWjm={PDLA|$5Q9_w#ya! zD^-1YTJ_KJyqx<<3bi{SMJY}|x+<)Iyowr5K)R-M+IJ;2RZ}g^g;#9t$*)th9rb(E z3n~vPHyaF@?6zIm->V9;|6aFAJ+&e&Ge|&UYG(K$+j7GWQ!!ku@kuK}otgYF9ni|UQ?zGgng#;Arujd4;&(~qk;fe>RT0dxW zb|lTEUc_KKf+8rT5Q&tup}I5M2i3}(qPB%yq|z5DyCsA% zXD++ly?eXeE_-*k&@VaZ=I-sC`M#Ms=ggVC2=$>4edvS1*o|@$Y$iI9D`X{C$Xl3dOONZWGKgU4Oi#|u29hvV+v{Dn0&^uWeDnR zs7%ft#1y)PDWs#P#C3$|;0oDfOQGqgH^7!e=5b7}+%9oHAu#!mnaIm1R#Ay^@8JsF z+7*F{^lWkFh4I3kmmyj<3>57<6Gg|qcxmt%uG#a3i!(FEi;A@D0Ai3$CFr?+D3(x( zvK5Z$LA?%FkH`{^cQb@*Z-Tgf00m`muVYz;sD3D`tJzRwML8J7%*bPZ^5p~Oh=#XE zh>pUzTg2jotMqZvUb9kMtveuE9TlRr$*!8cTwZIhSs`4dV?9E^>}bdxDW9by!hx8W z30!B|!^pL5zSl$C$t|MQQ7KwlT7ohV$H|HOUwB6I=V_v9@Jzkgurc}$Px0+k&7;%g znwBG2oGIEGjs#6yYi$jOMMrUld{+AoEdCfZ-A@FoiM|va@+mZ(VcMu2c{h3I(Fvlp z@m$E{J+q^p?Wvh>#a(RlLm8y&8u&FQ>k3u3_Ze=458s*2Tq{ z+F5V`HeH4bV3WH#-waPpa^iXv>bkQA`z29z{H*^{u$QaJ(w+prjM0ke(}oMc<(9Uy zQAlrPhf1Fa8~M}c>%>TsC*sG?7aPC(+3$N-$r#z@nXIQY&X6uQTmYufAH5re++K#L zKi>~4c{FaGG!P-bsMP14_L}9gP5aW}S^<7AUH}Y*i_S)269b)rR5sk?-L`DqcAxvM z*6sIXh(3M*(;CAC;F_)OwO##XXIdRqw?+Q!-}T}V%Q8RmlNWp-&efjx^#hIyb=DOm zY9aw`G+Y3tiT|)+kpf(~*d@8O+xCf+XWtYX3J&&Ye3hBf-bpr+Y3e9lk+7Z_`5#8$dCL8^U|Qns{%AcQ2n{QQ5IA(! zA|GPjB7{QTI}~b{0LSEC;{{Mit-cfV9a*<+-Bl#|Kl7TO2p?|xJ|yx1ouER_8ZQ7Q zRlZ?xxH=j&D|9>Sa5%)4orNM%BZ8iR!#i>ER6z1-7<^9+gG>CvV1?lVFiej4BtkRX ziO|?CI~8oWhx8>Q^N&d z*s@q#m4O1L+zfFNu=9E%^n^T^lVz*ZKhJmit4`3e$Z!F8l4ZJH4Nwh-upK_!tq7Y7 z3I(3{y2#4?xLb$kZEf|3WE;@dvCJ9%h0-K4)o=kSld}i;DGvpa+>vTQy`C4xTph^RUOX;M-S`?kEhT43Ss2ZYM9*4T%=7t=!B`7CZyC?9O3HQ$uO>5IT4@Q_-rmsPd zy4@z6Y>wxINFI!6YJl230Q3n(5)9Mg03DL+6sossj&SSDuVjoYWNLnzX)CRyeO>Xf%%fC8qs5ZxY*?n#i<9=%YfD?l`k&5xs; z{v6Xw!F14_F+vRw^7rQT1LaQ6b39&mgxtPZxLh8d=x zplG4els$|qW-?NJ#0Yw#;H6bs;*v%<)X zKKT^I3VuyDr$9X}g5)prw3+v!SOQnbs}U2xL|;L%2&Ryo5g7e^6sxFCB?DkvY7}`m zmM=@4JrKn*SQ43POm0AK*i7ars5gLP@>ck#K79xECa`d($hyNQGf;2FZj_S%KWPnt{8x9xTO)2TOqVc)-t3+@y~!~72;GTb ziun!sj!@U5ejV;XcQUL=<}8Lui%y)s$`tYY!2kdN literal 0 HcmV?d00001 diff --git a/images/icons/forest.png b/images/icons/forest.png new file mode 100644 index 0000000000000000000000000000000000000000..a9d59d064edc8016d4a12b534cb1d40422c255c3 GIT binary patch literal 1926 zcmV;12YL93P)x_nbP!pwJFb zbfBOFSVqL4p!h<>0YO1@;&5&6+0+S&!a!|7@quoFjGj{`_?Qu=wh6vPiEo9uedaq` zN7=@D+ujjP_>z;`dwb7!zTf$u*FA@jV|L8`KNfQNXNH~rYAC5Y!N~h{&6bnNf6cxw z-gH{n>HjU{^0l{&-ZC)O`_+<8)Fu0@uCBxo^ii{r%U=?9`62A|y&db_$EEEYF4^~7 zGN_jHYH~D94{DRRps#XCTezeja>3Sm19U{>|FFxqS%DP$rsI8cN!=MCmw#!<>7Urc zmtfB#1SVb3MgVeLzXM<)T+*i^1xF`cuy$S0xt$>E!%qL+PsaJq3^{!TVW+P}fw}ze zM*H&yG-nS#L3Trra?8o_;34Ok2I3X8v^R)DiMD!zT1Mo3R3PN?wIW2w<=+)9-jo^n zuTtcn#Rc8l14Od#u(=%u;vo-%hqQuD@{)$&O~X&)l7{;RVZa4@B$6jw?7KbW^tXjw zzU9p&b%%{gM^ct|sJo>c$njkuZt@m*2!Mx-Oaj1h0heqw)^0i?;81NI=xa`>*;$yzCHuvIvTfb~9n}f4Pl0f9F7}tW zL3Bws8U^s|pfL5$Au#Sn3##%nHbcx`BiQhWzuJbP0lHwj4Ofhy-#)QJyAV929z0|l z4mRK>Z-AS82NmR0LlEcyMWHz)lLzS-qlMk+&Qlm!^Z4T-~Y0mrzRv7zU}>S$L{0>0gP|U@z&bh+kc@yB~a^ z0B(IGR|Sh{#=SWT06`|tvBLGYSQX&?&)EKQ2OSr6HmiFSh?|rehER$K<{f*Z1s>Q~ zkTeL+3+NMr(gppUO9#C6>D};Rb6M=Xk}g&toSXz+k{%z#&P%P>(eHBS!O+}PSo~aa zFuGtfm0w9W;Jw)E2DJ9}HrV*lv>5qmD-YUjaREfBu_WT?9-ap|!%|`ChQwjK0e0}V z$8wt^_xpa|0}0YzwyNu6A!tEOi`*}@$4yZ-YfU21&u>u%$I1Z(3#hZZkLrCAU-<7Z{3 zK*eizb>H&i>s=u7OCFy;!S>+lz*w^qY^IrN%v&`aMiysw-S7W1+y(MN^NhIt>4Gke z3E^q9pNC5dGeMjaEr1hXKgsL zCO9H-zk;<@OKoVZzENQ&$coK{ke!vH)?wqPTHxBUY`AWAHq3oA7ZyHkgJmxjs%1!J zy{2ICT3N5DXAizu0Qc3}U`|CY%wE=^s4nQI)xr_`<4O;(>1Kjp!oV<~TUO*k=HNjv z>Z(k&N-i3m0XBOYZ7b0#oM=L0_-)rr{?Mwzb@DT-em9k=8tLEk@QCGTdb`qbBBQYo#ZCNNTp<3 zQ(&2|FeyK}U|UH%<`vAcw65TQS|{a8xi0;^9~9N{y9UyL~w( zngrcj)lhVXK@i6@vp`C2%7!U(tY)bJ9WZRUArKyLTw_Lo`28NeJTWWjMmv7(?xM4~ zp!<4rvS@9-8cvNAIDLK&TvM85t{O7Vj?aa51($4pbmse4+rVl~f%~4$H&PJf6{NzF zb+O+~G*wH-G(#{ofr9g^B`f2SF2d^-6EgpqUt>&-g==hW?DCFpkp4;ZL)@|=iI^_w zLk0_Zx$0G0*2=B+`u=HFv=MhfNupNSGWryB(q|!(et@vT7 zq`fE|a)zeD;Iu)InK1~z?|;G_NpiG|OZG7-hvQpQ}-YeIrQ&1ONsI$*t-6S_{ag=!3F#A(8$7m zO3067uc#G-U{md3b^>g*O$Qo2)N904> createLandMark( - String id, String lat, String lon, String title) async { + String id, String lat, String lon, String title, String icon) async { bool isConnected = await utils.checkInternetConnectivity(); if (!isConnected) { @@ -92,7 +92,8 @@ class RemoteHikeApi { } final result = await _authClient.mutate(MutationOptions( - document: gql(beaconQueries.createLandmark(id, lat, lon, title)))); + document: + gql(beaconQueries.createLandmark(id, lat, lon, title, icon)))); print("Result: ${result.data}"); @@ -101,6 +102,7 @@ class RemoteHikeApi { result.data!['createLandmark'] != null) { final newLandMark = LandMarkModel.fromJson(result.data!['createLandmark']); + print("result data: ${result.data!['createLandmark']}"); return DataSuccess(newLandMark); } else { return DataFailed(encounteredExceptionOrError(result.exception!)); diff --git a/lib/data/models/landmark/landmark_model.dart b/lib/data/models/landmark/landmark_model.dart index 1a16319..e6a09ef 100644 --- a/lib/data/models/landmark/landmark_model.dart +++ b/lib/data/models/landmark/landmark_model.dart @@ -20,7 +20,11 @@ class LandMarkModel implements LandMarkEntity { @HiveField(3) UserModel? createdBy; - LandMarkModel({this.title, this.location, this.id, this.createdBy}); + @HiveField(4) + String? icon; + + LandMarkModel( + {this.title, this.location, this.id, this.createdBy, this.icon}); @override $LandMarkEntityCopyWith get copyWith => @@ -34,12 +38,14 @@ class LandMarkModel implements LandMarkEntity { LandMarkModel copyWithModel( {String? id, String? title, + String? icon, LocationModel? location, UserModel? createdBy}) { return LandMarkModel( id: id, title: title ?? this.title, location: location ?? this.location, + icon: icon ?? this.icon, createdBy: createdBy ?? this.createdBy); } } diff --git a/lib/data/models/landmark/landmark_model.g.dart b/lib/data/models/landmark/landmark_model.g.dart index 96a5995..f034814 100644 --- a/lib/data/models/landmark/landmark_model.g.dart +++ b/lib/data/models/landmark/landmark_model.g.dart @@ -21,13 +21,14 @@ class LandMarkModelAdapter extends TypeAdapter { location: fields[1] as LocationModel?, id: fields[2] as String?, createdBy: fields[3] as UserModel?, + icon: fields[4] as String?, ); } @override void write(BinaryWriter writer, LandMarkModel obj) { writer - ..writeByte(4) + ..writeByte(5) ..writeByte(0) ..write(obj.title) ..writeByte(1) @@ -35,7 +36,9 @@ class LandMarkModelAdapter extends TypeAdapter { ..writeByte(2) ..write(obj.id) ..writeByte(3) - ..write(obj.createdBy); + ..write(obj.createdBy) + ..writeByte(4) + ..write(obj.icon); } @override @@ -63,6 +66,7 @@ LandMarkModel _$LandMarkModelFromJson(Map json) => createdBy: json['createdBy'] == null ? null : UserModel.fromJson(json['createdBy'] as Map), + icon: json['icon'] as String?, ); Map _$LandMarkModelToJson(LandMarkModel instance) => @@ -71,4 +75,5 @@ Map _$LandMarkModelToJson(LandMarkModel instance) => 'location': instance.location, '_id': instance.id, 'createdBy': instance.createdBy, + 'icon': instance.icon, }; diff --git a/lib/data/repositories/hike_repository_implementation.dart b/lib/data/repositories/hike_repository_implementation.dart index 93c0d56..1407762 100644 --- a/lib/data/repositories/hike_repository_implementation.dart +++ b/lib/data/repositories/hike_repository_implementation.dart @@ -28,8 +28,8 @@ class HikeRepositoryImplementatioin implements HikeRepository { @override Future> createLandMark( - String id, String title, String lat, String lon) { - return remoteHikeApi.createLandMark(id, lat, lon, title); + String id, String title, String lat, String lon, String icon) { + return remoteHikeApi.createLandMark(id, lat, lon, title, icon); } @override diff --git a/lib/domain/entities/landmark/landmark_entity.dart b/lib/domain/entities/landmark/landmark_entity.dart index e6d6a13..f29960f 100644 --- a/lib/domain/entities/landmark/landmark_entity.dart +++ b/lib/domain/entities/landmark/landmark_entity.dart @@ -5,23 +5,29 @@ part 'landmark_entity.freezed.dart'; @freezed class LandMarkEntity with _$LandMarkEntity { - const factory LandMarkEntity( - {String? id, - String? title, - LocationEntity? location, - UserEntity? createdBy}) = _LandMarkEntity; + const factory LandMarkEntity({ + String? id, + String? title, + String? icon, + LocationEntity? location, + UserEntity? createdBy, + }) = _LandMarkEntity; } extension LandMarkEntityCopyWithExtension on LandMarkEntity { - LandMarkEntity copywith( - {String? id, - String? title, - LocationEntity? location, - UserEntity? createdBy}) { + LandMarkEntity copywith({ + String? id, + String? title, + String? icon, + LocationEntity? location, + UserEntity? createdBy, + }) { return LandMarkEntity( - id: id ?? this.id, - title: title ?? this.title, - location: location ?? this.location, - createdBy: createdBy ?? this.createdBy); + id: id ?? this.id, + title: title ?? this.title, + icon: icon ?? this.icon, + location: location ?? this.location, + createdBy: createdBy ?? this.createdBy, + ); } } diff --git a/lib/domain/entities/landmark/landmark_entity.freezed.dart b/lib/domain/entities/landmark/landmark_entity.freezed.dart index 0fe6e87..4afc9a0 100644 --- a/lib/domain/entities/landmark/landmark_entity.freezed.dart +++ b/lib/domain/entities/landmark/landmark_entity.freezed.dart @@ -18,6 +18,7 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$LandMarkEntity { String? get id => throw _privateConstructorUsedError; String? get title => throw _privateConstructorUsedError; + String? get icon => throw _privateConstructorUsedError; LocationEntity? get location => throw _privateConstructorUsedError; UserEntity? get createdBy => throw _privateConstructorUsedError; @@ -37,6 +38,7 @@ abstract class $LandMarkEntityCopyWith<$Res> { $Res call( {String? id, String? title, + String? icon, LocationEntity? location, UserEntity? createdBy}); @@ -61,6 +63,7 @@ class _$LandMarkEntityCopyWithImpl<$Res, $Val extends LandMarkEntity> $Res call({ Object? id = freezed, Object? title = freezed, + Object? icon = freezed, Object? location = freezed, Object? createdBy = freezed, }) { @@ -73,6 +76,10 @@ class _$LandMarkEntityCopyWithImpl<$Res, $Val extends LandMarkEntity> ? _value.title : title // ignore: cast_nullable_to_non_nullable as String?, + icon: freezed == icon + ? _value.icon + : icon // ignore: cast_nullable_to_non_nullable + as String?, location: freezed == location ? _value.location : location // ignore: cast_nullable_to_non_nullable @@ -124,6 +131,7 @@ abstract class _$$LandMarkEntityImplCopyWith<$Res> $Res call( {String? id, String? title, + String? icon, LocationEntity? location, UserEntity? createdBy}); @@ -148,6 +156,7 @@ class __$$LandMarkEntityImplCopyWithImpl<$Res> $Res call({ Object? id = freezed, Object? title = freezed, + Object? icon = freezed, Object? location = freezed, Object? createdBy = freezed, }) { @@ -160,6 +169,10 @@ class __$$LandMarkEntityImplCopyWithImpl<$Res> ? _value.title : title // ignore: cast_nullable_to_non_nullable as String?, + icon: freezed == icon + ? _value.icon + : icon // ignore: cast_nullable_to_non_nullable + as String?, location: freezed == location ? _value.location : location // ignore: cast_nullable_to_non_nullable @@ -176,20 +189,22 @@ class __$$LandMarkEntityImplCopyWithImpl<$Res> class _$LandMarkEntityImpl implements _LandMarkEntity { const _$LandMarkEntityImpl( - {this.id, this.title, this.location, this.createdBy}); + {this.id, this.title, this.icon, this.location, this.createdBy}); @override final String? id; @override final String? title; @override + final String? icon; + @override final LocationEntity? location; @override final UserEntity? createdBy; @override String toString() { - return 'LandMarkEntity(id: $id, title: $title, location: $location, createdBy: $createdBy)'; + return 'LandMarkEntity(id: $id, title: $title, icon: $icon, location: $location, createdBy: $createdBy)'; } @override @@ -199,6 +214,7 @@ class _$LandMarkEntityImpl implements _LandMarkEntity { other is _$LandMarkEntityImpl && (identical(other.id, id) || other.id == id) && (identical(other.title, title) || other.title == title) && + (identical(other.icon, icon) || other.icon == icon) && (identical(other.location, location) || other.location == location) && (identical(other.createdBy, createdBy) || @@ -206,7 +222,8 @@ class _$LandMarkEntityImpl implements _LandMarkEntity { } @override - int get hashCode => Object.hash(runtimeType, id, title, location, createdBy); + int get hashCode => + Object.hash(runtimeType, id, title, icon, location, createdBy); /// Create a copy of LandMarkEntity /// with the given fields replaced by the non-null parameter values. @@ -222,6 +239,7 @@ abstract class _LandMarkEntity implements LandMarkEntity { const factory _LandMarkEntity( {final String? id, final String? title, + final String? icon, final LocationEntity? location, final UserEntity? createdBy}) = _$LandMarkEntityImpl; @@ -230,6 +248,8 @@ abstract class _LandMarkEntity implements LandMarkEntity { @override String? get title; @override + String? get icon; + @override LocationEntity? get location; @override UserEntity? get createdBy; diff --git a/lib/domain/repositories/hike_repository.dart b/lib/domain/repositories/hike_repository.dart index 8b8a31d..38485d7 100644 --- a/lib/domain/repositories/hike_repository.dart +++ b/lib/domain/repositories/hike_repository.dart @@ -12,7 +12,7 @@ abstract class HikeRepository { String beaconId, LatLng position); Future> fetchBeaconDetails(String beaconId); Future> createLandMark( - String id, String title, String lat, String lon); + String id, String title, String lat, String lon, String icon); Future> changeUserLocation(String id, LatLng latLng); Future> sos(String beaconId); Stream> beaconLocationsSubscription( diff --git a/lib/domain/usecase/hike_usecase.dart b/lib/domain/usecase/hike_usecase.dart index 49dfb46..bb39b01 100644 --- a/lib/domain/usecase/hike_usecase.dart +++ b/lib/domain/usecase/hike_usecase.dart @@ -23,8 +23,8 @@ class HikeUseCase { } Future> createLandMark( - String id, String title, String lat, String lon) { - return hikeRepository.createLandMark(id, title, lat, lon); + String id, String title, String lat, String lon, String icon) { + return hikeRepository.createLandMark(id, title, lat, lon, icon); } Future> changeUserLocation(String id, LatLng latlng) { diff --git a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart index bac79bb..e1506db 100644 --- a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart +++ b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart @@ -50,6 +50,13 @@ class LocationCubit extends Cubit { LocationData? _lastLocation; Set _geofence = {}; MapType _mapType = MapType.normal; + List infoMarkers = [ + "location-marker", + "wind", + "rain", + "camp", + ]; + String selectedInfoMarker = "location-marker"; StreamSubscription>? _beaconlocationsSubscription; @@ -60,6 +67,11 @@ class LocationCubit extends Cubit { BuildContext? context; TickerProvider? vsync; + void setLandmarkIcon(String icon) { + selectedInfoMarker = icon; + // emit(InitialLocationState()); + } + void onMapCreated(GoogleMapController controller) { mapController = controller; } @@ -659,13 +671,13 @@ class LocationCubit extends Cubit { // update here Future createLandmark( - String beaconId, String title, LatLng latlng) async { + String beaconId, String title, LatLng latlng, String icon) async { + print("Creating landmark with title: $title at $latlng"); var dataState = await _hikeUseCase.createLandMark(beaconId, title, - latlng.latitude.toString(), latlng.longitude.toString()); - + latlng.latitude.toString(), latlng.longitude.toString(), icon); + print("Data state: $dataState"); if (dataState is DataSuccess && dataState.data != null) { - print( - 'Creating marker for landmark: 1: ${dataState.data!.createdBy?.imageUrl}'); + print('result Creating marker for landmark: 1: ${dataState.data?.icon}'); await _createLandMarkMarker(dataState.data!); emit(LoadedLocationState( polyline: _polyline, @@ -721,7 +733,9 @@ class LocationCubit extends Cubit { _hikeMarkers.where((element) => element.markerId == markerId); if (existingMarkers.isEmpty) { - var newMarker = await createMarkerWithCircularNetworkImage(landMark); + var newMarker = await createMarkerWithCircularNetworkImage( + landMark, + ); _hikeMarkers.add(newMarker); } else { // If the marker exists, update its position @@ -775,11 +789,11 @@ class LocationCubit extends Cubit { LandMarkEntity landmark) async { print("Creating marker for landmark: ${landmark.createdBy?.imageUrl}"); final Uint8List markerIcon = await getCircularImageWithBorderAndPointer( - landmark.createdBy?.imageUrl ?? - 'https://cdn.jsdelivr.net/gh/alohe/avatars/png/toon_5.png', + landmark.icon ?? 'assets/icons/location-marker.png', size: 80, - borderColor: Colors.deepPurple, + borderColor: Colors.teal, borderWidth: 4, + isUrl: false, ); return Marker( @@ -787,19 +801,30 @@ class LocationCubit extends Cubit { position: locationToLatLng(landmark.location!), icon: BitmapDescriptor.fromBytes(markerIcon), infoWindow: InfoWindow( - title: 'Created by: ${landmark.createdBy?.name ?? 'Anonymous'}', + title: + '${landmark.title} by ${landmark.createdBy?.name ?? 'Anonymous'}', ), ); } Future getCircularImageWithBorderAndPointer( - String imageUrl, { + String imagePath, { int size = 150, Color borderColor = Colors.red, double borderWidth = 6, + bool isUrl = true, }) async { - final http.Response response = await http.get(Uri.parse(imageUrl)); - final Uint8List bytes = response.bodyBytes; + Uint8List bytes; + + if (isUrl) { + // Handle URL images + final http.Response response = await http.get(Uri.parse(imagePath)); + bytes = response.bodyBytes; + } else { + // Handle asset images + final ByteData byteData = await rootBundle.load(imagePath); + bytes = byteData.buffer.asUint8List(); + } final ui.Codec codec = await ui.instantiateImageCodec( bytes, diff --git a/lib/presentation/hike/widgets/hike_screen_widget.dart b/lib/presentation/hike/widgets/hike_screen_widget.dart index 16af81f..0c20fea 100644 --- a/lib/presentation/hike/widgets/hike_screen_widget.dart +++ b/lib/presentation/hike/widgets/hike_screen_widget.dart @@ -198,87 +198,186 @@ class HikeScreenWidget { ); } +// Updated static method to show the dialog static void showCreateLandMarkDialogueDialog( BuildContext context, String beaconId, LatLng loc, ) { + showDialog( + context: context, + builder: (context) => CreateLandmarkDialog( + beaconId: beaconId, + loc: loc, + ), + ); + } +} + +class CreateLandmarkDialog extends StatefulWidget { + final String beaconId; + final LatLng loc; + + const CreateLandmarkDialog({ + Key? key, + required this.beaconId, + required this.loc, + }) : super(key: key); + + @override + State createState() => _CreateLandmarkDialogState(); +} + +class _CreateLandmarkDialogState extends State { + final _landmarkFormKey = GlobalKey(); + final _landMarkeController = TextEditingController(); + String? _selectedIcon; + + // List of available icons + final List _iconOptions = [ + 'images/icons/camp.png', + 'images/icons/wind.png', + 'images/icons/location-marker.png', + 'images/icons/rain.png', + 'images/icons/forest.png', + 'images/icons/destination.png', + ]; + + @override + void dispose() { + _landMarkeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { final size = MediaQuery.of(context).size; final isSmallScreen = size.height < 800; - showDialog( - context: context, - builder: (context) => Dialog( - backgroundColor: Colors.grey[100], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.0), - ), - child: Container( - width: size.width * 0.85, // Set a reasonable width - height: isSmallScreen ? size.height * 0.45 : size.height * 0.4, - padding: const EdgeInsets.all(24), - child: Form( - key: _landmarkFormKey, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - 'Create Landmark', - style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + return Dialog( + backgroundColor: Colors.grey[100], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.0), + ), + child: Container( + width: size.width * 0.85, + height: isSmallScreen ? size.height * 0.45 : size.height * 0.4, + padding: const EdgeInsets.all(24), + child: Form( + key: _landmarkFormKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + 'Select an icon', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey[600], ), - Container( - height: 60, - decoration: BoxDecoration( - color: kLightBlue, - borderRadius: BorderRadius.circular(8), - ), - padding: - const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: TextFormField( - controller: _landMarkeController, - style: TextStyle(fontSize: 18.0), - validator: (value) { - if (value == null || value.isEmpty) { - return "Please enter title for landmark"; - } - return null; + ), + + // Icon selection row + Wrap( + spacing: 8, + runSpacing: 8, + children: _iconOptions.map((iconPath) { + final isSelected = _selectedIcon == iconPath; + return GestureDetector( + onTap: () { + setState(() { + _selectedIcon = iconPath; + }); }, - decoration: InputDecoration( - hintText: 'Enter Landmark Title', - labelText: 'Title', - labelStyle: TextStyle( - fontSize: 14, - color: Theme.of(context).primaryColor, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isSelected + ? Theme.of(context).primaryColor + : Colors.transparent, + width: 2, + ), + color: isSelected + ? Theme.of(context).primaryColor.withOpacity(0.1) + : Colors.transparent, + ), + child: Image.asset( + iconPath, + width: 40, + height: 40, ), - hintStyle: - TextStyle(fontSize: 16, color: Colors.grey[400]), - alignLabelWithHint: true, - floatingLabelBehavior: FloatingLabelBehavior.always, - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, - contentPadding: EdgeInsets.zero, ), - ), + ); + }).toList(), + ), + + // Text input field + Container( + height: 60, + margin: const EdgeInsets.only(top: 16), + decoration: BoxDecoration( + color: kLightBlue, + borderRadius: BorderRadius.circular(8), ), - SizedBox(height: 16), - HikeButton( - buttonHeight: 5.5.h, - buttonWidth: 50.w, - textSize: 14.0, - textColor: Colors.white, - onTap: () { - if (!_landmarkFormKey.currentState!.validate()) return; - appRouter.maybePop(); - locator().createLandmark( - beaconId, _landMarkeController.text.trim(), loc); - _landMarkeController.clear(); + padding: + const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: TextFormField( + controller: _landMarkeController, + style: const TextStyle(fontSize: 18.0), + validator: (value) { + if (value == null || value.isEmpty) { + return "Please enter title for landmark"; + } + if (_selectedIcon == null) { + return "Please select an icon"; + } + return null; }, - text: 'Create Landmark', - ) - ], - ), + decoration: InputDecoration( + hintText: 'Enter Landmark Title', + labelText: 'Title', + labelStyle: TextStyle( + fontSize: 14, + color: Theme.of(context).primaryColor, + ), + hintStyle: TextStyle( + fontSize: 16, + color: Colors.grey[400], + ), + alignLabelWithHint: true, + floatingLabelBehavior: FloatingLabelBehavior.always, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + contentPadding: EdgeInsets.zero, + ), + ), + ), + + const SizedBox(height: 16), + + // Create button + HikeButton( + buttonHeight: 5.5.h, + buttonWidth: 50.w, + textSize: 14.0, + textColor: Colors.white, + onTap: () { + if (!_landmarkFormKey.currentState!.validate()) return; + appRouter.maybePop(); + locator().createLandmark( + widget.beaconId, + _landMarkeController.text.trim(), + widget.loc, + _selectedIcon!); + _landMarkeController.clear(); + }, + text: 'Create Landmark', + ) + ], ), ), ), diff --git a/pubspec.yaml b/pubspec.yaml index c89fcf4..7ff88dc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -96,6 +96,7 @@ flutter: assets: - images/ - animations/ + - images/icons/ fonts: - family: Inter From 8fca0f5d50b08bec9bb2c2896f0d5d52fe0b7d9b Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Fri, 11 Jul 2025 12:58:22 +0530 Subject: [PATCH 07/19] maps screen finish --- lib/core/queries/auth.dart | 4 +- lib/core/queries/beacon.dart | 1 + .../datasource/remote/remote_auth_api.dart | 4 +- .../datasource/remote/remote_hike_api.dart | 2 + .../models/landmark/location_suggestion.dart | 27 +++++++ .../auth_repository_implementation.dart | 4 +- lib/domain/repositories/auth_repository.dart | 2 +- lib/domain/usecase/auth_usecase.dart | 4 +- .../auth/auth_cubit/auth_cubit.dart | 4 +- .../cubit/location_cubit/location_cubit.dart | 80 +++++++++++++++---- lib/presentation/hike/hike_screen.dart | 7 +- .../hike/services/geoapify_service.dart | 39 ++++++--- .../hike/widgets/search_places.dart | 50 ++++++++++-- lib/presentation/home/home_screen.dart | 13 ++- pubspec.lock | 80 ------------------- pubspec.yaml | 3 - 16 files changed, 192 insertions(+), 132 deletions(-) create mode 100644 lib/data/models/landmark/location_suggestion.dart diff --git a/lib/core/queries/auth.dart b/lib/core/queries/auth.dart index ca74e6d..b6c9670 100644 --- a/lib/core/queries/auth.dart +++ b/lib/core/queries/auth.dart @@ -13,10 +13,10 @@ class AuthQueries { '''; } - String gAuth(String? name, String email) { + String gAuth(String? name, String email, String? imageUrl) { return ''' mutation{ - oAuth(userInput: {email: "$email", name: "$name"}) + oAuth(userInput: {email: "$email", name: "$name", imageUrl: "$imageUrl"}) } '''; } diff --git a/lib/core/queries/beacon.dart b/lib/core/queries/beacon.dart index 52ffc2a..74f3f02 100644 --- a/lib/core/queries/beacon.dart +++ b/lib/core/queries/beacon.dart @@ -254,6 +254,7 @@ deleteBeacon(id: "$id") landmarks{ _id title + icon location{ lat lon diff --git a/lib/data/datasource/remote/remote_auth_api.dart b/lib/data/datasource/remote/remote_auth_api.dart index 94786a3..2e684e7 100644 --- a/lib/data/datasource/remote/remote_auth_api.dart +++ b/lib/data/datasource/remote/remote_auth_api.dart @@ -86,7 +86,7 @@ class RemoteAuthApi { } } - Future> gAuth(String name, String email) async { + Future> gAuth(String name, String email, String? imageUrl) async { log('name: $name'); log('email: $email'); @@ -97,7 +97,7 @@ class RemoteAuthApi { } final QueryResult result = await clientNonAuth.mutate( - MutationOptions(document: gql(_authQueries.gAuth(name, email)))); + MutationOptions(document: gql(_authQueries.gAuth(name, email, imageUrl)))); log(result.toString()); diff --git a/lib/data/datasource/remote/remote_hike_api.dart b/lib/data/datasource/remote/remote_hike_api.dart index 957d702..f047bef 100644 --- a/lib/data/datasource/remote/remote_hike_api.dart +++ b/lib/data/datasource/remote/remote_hike_api.dart @@ -174,6 +174,8 @@ class RemoteHikeApi { final result = await _authClient .mutate(MutationOptions(document: gql(beaconQueries.sos(id)))); + print("result sos: ${result.data}"); + if (result.isConcrete && result.data != null && result.data!['sos'] != null) { diff --git a/lib/data/models/landmark/location_suggestion.dart b/lib/data/models/landmark/location_suggestion.dart new file mode 100644 index 0000000..b431db2 --- /dev/null +++ b/lib/data/models/landmark/location_suggestion.dart @@ -0,0 +1,27 @@ +class LocationSuggestion { + final String name; + final double latitude; + final double longitude; + final String fullAddress; + + LocationSuggestion({ + required this.name, + required this.latitude, + required this.longitude, + required this.fullAddress, + }); + + factory LocationSuggestion.fromJson(Map json) { + return LocationSuggestion( + name: json['formatted'] ?? json['address_line1'] ?? 'Unknown Location', + latitude: (json['lat'] ?? 0.0).toDouble(), + longitude: (json['lon'] ?? 0.0).toDouble(), + fullAddress: json['formatted'] ?? 'Unknown Address', + ); + } + + @override + String toString() { + return 'LocationSuggestion(name: $name, lat: $latitude, lon: $longitude)'; + } +} diff --git a/lib/data/repositories/auth_repository_implementation.dart b/lib/data/repositories/auth_repository_implementation.dart index a6a6311..5bb2f81 100644 --- a/lib/data/repositories/auth_repository_implementation.dart +++ b/lib/data/repositories/auth_repository_implementation.dart @@ -20,8 +20,8 @@ class AuthRepositoryImplementation implements AuthRepository { } @override - Future> oAuth(String name, String email) { - return remoteAuthApi.gAuth(name, email); + Future> oAuth(String name, String email, String? imageUrl) { + return remoteAuthApi.gAuth(name, email, imageUrl); } @override diff --git a/lib/domain/repositories/auth_repository.dart b/lib/domain/repositories/auth_repository.dart index ff88d7d..95bb421 100644 --- a/lib/domain/repositories/auth_repository.dart +++ b/lib/domain/repositories/auth_repository.dart @@ -12,7 +12,7 @@ abstract class AuthRepository { // Login function Future> login(String email, String password); - Future> oAuth(String name, String email); + Future> oAuth(String name, String email, String? imageUrl); Future> sendVerificationCode(); diff --git a/lib/domain/usecase/auth_usecase.dart b/lib/domain/usecase/auth_usecase.dart index 7a4a875..6856ad1 100644 --- a/lib/domain/usecase/auth_usecase.dart +++ b/lib/domain/usecase/auth_usecase.dart @@ -17,8 +17,8 @@ class AuthUseCase { return authRepository.login(email, password); } - Future> oAuthUseCase(String name, String email) async { - return authRepository.oAuth(name, email); + Future> oAuthUseCase(String name, String email, String? imageUrl) async { + return authRepository.oAuth(name, email, imageUrl); } Future> getUserInfoUseCase() async { diff --git a/lib/presentation/auth/auth_cubit/auth_cubit.dart b/lib/presentation/auth/auth_cubit/auth_cubit.dart index b168f49..9e43942 100644 --- a/lib/presentation/auth/auth_cubit/auth_cubit.dart +++ b/lib/presentation/auth/auth_cubit/auth_cubit.dart @@ -79,10 +79,12 @@ class AuthCubit extends Cubit { ); final gAuth = await _googleSignIn.signIn(); + print("Google Auth: ${gAuth}"); if (gAuth != null && gAuth.displayName != null) { + // pass imageurl var dataState = - await authUseCase.oAuthUseCase(gAuth.displayName!, gAuth.email); + await authUseCase.oAuthUseCase(gAuth.displayName!, gAuth.email, gAuth.photoUrl); if (dataState is DataSuccess && dataState.data != null) { emit(SuccessState()); diff --git a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart index e1506db..9127399 100644 --- a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart +++ b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart @@ -247,7 +247,7 @@ class LocationCubit extends Cubit { emit(LoadedLocationState( geofence: _geofence, - locationMarkers: _hikeMarkers, + locationMarkers: Set.from(_hikeMarkers), polyline: _polyline, version: DateTime.now().millisecondsSinceEpoch, mapType: _mapType, @@ -260,7 +260,7 @@ class LocationCubit extends Cubit { PolylinePoints polylinePoints = PolylinePoints(); try { PolylineResult result = await polylinePoints.getRouteBetweenCoordinates( - 'AIzaSyBdIpiEfBE5DohHgBvwPTljZQAcNWcKwCs', + 'AIzaSyC72TkXzQTsnbGdZy5ldeX64y0mofn_iUs', PointLatLng(_points.first.latitude, _points.first.longitude), PointLatLng(_points.last.longitude, _points.last.longitude)); @@ -363,18 +363,29 @@ class LocationCubit extends Cubit { if (dataState is DataSuccess && dataState.data != null) { BeaconLocationsEntity beaconLocationsEntity = dataState.data!; + print( + 'Location update subscription: ${beaconLocationsEntity.toString()}'); + + // when new landmark is created // when new landmark is created if (beaconLocationsEntity.landmark != null) { LandMarkEntity newLandMark = beaconLocationsEntity.landmark!; - // await _createLandMarkMarker(newLandMark); + print('Creating landmark marker for: ${newLandMark.title}'); + print('Markers before: ${_hikeMarkers.length}'); + + await _createLandMarkMarker(newLandMark); + + print('Markers after: ${_hikeMarkers.length}'); emit(LoadedLocationState( polyline: _polyline, - locationMarkers: _hikeMarkers, + locationMarkers: + Set.from(_hikeMarkers), // Create new Set instance mapType: _mapType, geofence: _geofence, - version: DateTime.now().millisecond, + version: DateTime.now() + .millisecondsSinceEpoch, // Use millisecondsSinceEpoch message: 'A landmark is created by ${beaconLocationsEntity.landmark!.createdBy!.name ?? 'Anonymous'}')); } @@ -388,9 +399,9 @@ class LocationCubit extends Cubit { emit(LoadedLocationState( polyline: _polyline, geofence: _geofence, - locationMarkers: _hikeMarkers, + locationMarkers: Set.from(_hikeMarkers), mapType: _mapType, - version: DateTime.now().microsecond)); + version: DateTime.now().millisecondsSinceEpoch)); // add marker for user } @@ -460,10 +471,10 @@ class LocationCubit extends Cubit { calculateMapBoundsFromListOfLatLng(_points), 50)); emit(LoadedLocationState( + polyline: _polyline, geofence: _geofence, - locationMarkers: _hikeMarkers, + locationMarkers: Set.from(_hikeMarkers), mapType: _mapType, - polyline: _polyline, version: DateTime.now().millisecondsSinceEpoch)); } else if (beaconLocationsEntity.userSOS != null) { var user = beaconLocationsEntity.userSOS!; @@ -669,21 +680,25 @@ class LocationCubit extends Cubit { return _address.isNotEmpty ? _address : null; } - // update here Future createLandmark( String beaconId, String title, LatLng latlng, String icon) async { - print("Creating landmark with title: $title at $latlng"); var dataState = await _hikeUseCase.createLandMark(beaconId, title, latlng.latitude.toString(), latlng.longitude.toString(), icon); - print("Data state: $dataState"); + if (dataState is DataSuccess && dataState.data != null) { - print('result Creating marker for landmark: 1: ${dataState.data?.icon}'); + print('Local landmark created: ${dataState.data!.title}'); await _createLandMarkMarker(dataState.data!); + + await locationUpdateSubscription(beaconId); + emit(LoadedLocationState( polyline: _polyline, geofence: _geofence, mapType: _mapType, - locationMarkers: Set.from(_hikeMarkers), + locationMarkers: + Set.from(_hikeMarkers), // Create new Set instance + version: + DateTime.now().millisecondsSinceEpoch, // Consistent versioning message: 'New marker created by ${dataState.data!.createdBy!.name}')); } } @@ -733,7 +748,7 @@ class LocationCubit extends Cubit { _hikeMarkers.where((element) => element.markerId == markerId); if (existingMarkers.isEmpty) { - var newMarker = await createMarkerWithCircularNetworkImage( + var newMarker = await createMarkerWithLocalAsset( landMark, ); _hikeMarkers.add(newMarker); @@ -785,11 +800,44 @@ class LocationCubit extends Cubit { } } + Future createMarkerWithLocalAsset(LandMarkEntity landmark) async { + Future getResizedMarkerIcon( + String assetPath, int width) async { + final ByteData data = await rootBundle.load(assetPath); + final codec = await ui.instantiateImageCodec( + data.buffer.asUint8List(), + targetWidth: width, + ); + final frame = await codec.getNextFrame(); + final ui.Image image = frame.image; + + final byteData = await image.toByteData(format: ui.ImageByteFormat.png); + final Uint8List resizedBytes = byteData!.buffer.asUint8List(); + + return BitmapDescriptor.fromBytes(resizedBytes); + } + + BitmapDescriptor customIcon = await getResizedMarkerIcon( + landmark.icon ?? 'images/icons/location-marker.png', + 80, // desired width in pixels + ); + + return Marker( + markerId: MarkerId(landmark.id!.toString()), + position: locationToLatLng(landmark.location!), + icon: customIcon, + infoWindow: InfoWindow( + title: + '${landmark.title} by ${landmark.createdBy?.name ?? 'Anonymous'}', + ), + ); + } + Future createMarkerWithCircularNetworkImage( LandMarkEntity landmark) async { print("Creating marker for landmark: ${landmark.createdBy?.imageUrl}"); final Uint8List markerIcon = await getCircularImageWithBorderAndPointer( - landmark.icon ?? 'assets/icons/location-marker.png', + landmark.icon ?? 'images/icons/location-marker.png', size: 80, borderColor: Colors.teal, borderWidth: 4, diff --git a/lib/presentation/hike/hike_screen.dart b/lib/presentation/hike/hike_screen.dart index 5a7f874..e13117a 100644 --- a/lib/presentation/hike/hike_screen.dart +++ b/lib/presentation/hike/hike_screen.dart @@ -153,7 +153,7 @@ class _HikeScreenState extends State body: Stack( children: [ _mapScreen(), - LocationSearchWidget(), + LocationSearchWidget(widget.beacon.id!), Positioned( bottom: 200, right: 10, @@ -225,6 +225,10 @@ class _HikeScreenState extends State buttonWidth: 70.w, buttonHeight: 50, text: 'Send SOS', + onTap: () { + locator().sendSOS( + widget.beacon.id!, context); + }, textSize: 18.0, buttonColor: Colors.red, textColor: Colors.white, @@ -245,6 +249,7 @@ class _HikeScreenState extends State Widget _mapScreen() { return BlocConsumer( listener: (context, state) { + if (state is LoadedLocationState) { state.message != null ? utils.showSnackBar(state.message!, context) diff --git a/lib/presentation/hike/services/geoapify_service.dart b/lib/presentation/hike/services/geoapify_service.dart index b0b5470..dcde65a 100644 --- a/lib/presentation/hike/services/geoapify_service.dart +++ b/lib/presentation/hike/services/geoapify_service.dart @@ -1,28 +1,41 @@ import 'dart:convert'; +import 'package:beacon/data/models/landmark/location_suggestion.dart'; import 'package:http/http.dart' as http; class GeoapifyService { - static const String _apiKey = '1e810532f9934cd3a0d964b4caabd5d0'; + static const String _apiKey = '9ee96579425c4945ab53a5134d533b2c'; static const String _baseUrl = 'https://api.geoapify.com/v1/geocode/autocomplete'; - Future> getLocationSuggestions(String query) async { - final url = Uri.parse("$_baseUrl?text=$query&limit=5&apiKey=$_apiKey"); - + Future> getLocationSuggestions(String query) async { if (query.isEmpty) { return []; } - final response = await http.get(url); - if (response.statusCode == 200) { - final data = jsonDecode(response.body); - List suggestions = data['features']; + final url = Uri.parse( + "$_baseUrl?text=${Uri.encodeComponent(query)}&format=json&type=city&apiKey=$_apiKey"); + + try { + final response = await http.get(url); + print("Fetching location suggestions for query: $query"); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + print("Response received with ${data['results']?.length ?? 0} results"); + + List results = data['results'] ?? []; - return suggestions - .map((item) => item['properties']['formatted'] as String) - .toList(); - } else { - throw Exception("Failed to fetch location suggestions"); + return results.map((item) { + return LocationSuggestion.fromJson(item); + }).toList(); + } else { + print("Error: ${response.statusCode} - ${response.body}"); + throw Exception( + "Failed to fetch location suggestions: ${response.statusCode}"); + } + } catch (e) { + print("Exception occurred: $e"); + throw Exception("Failed to fetch location suggestions: $e"); } } } diff --git a/lib/presentation/hike/widgets/search_places.dart b/lib/presentation/hike/widgets/search_places.dart index 40d24bf..f9bfaea 100644 --- a/lib/presentation/hike/widgets/search_places.dart +++ b/lib/presentation/hike/widgets/search_places.dart @@ -1,17 +1,33 @@ +import 'package:beacon/data/models/landmark/location_suggestion.dart'; +import 'package:beacon/locator.dart'; +import 'package:beacon/presentation/hike/cubit/location_cubit/location_cubit.dart'; import 'package:beacon/presentation/hike/services/geoapify_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_typeahead/flutter_typeahead.dart'; +import 'package:google_maps_flutter/google_maps_flutter.dart'; class LocationSearchWidget extends StatelessWidget { + final String beaconId; + final Function(LocationSuggestion)? onLocationSelected; // Add callback + + LocationSearchWidget( + this.beaconId, { + Key? key, + this.onLocationSelected, + }) : super(key: key); + final GeoapifyService geoapifyService = GeoapifyService(); @override Widget build(BuildContext context) { return Container( margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), - child: TypeAheadField( + child: TypeAheadField( + // Changed from String to LocationSuggestion suggestionsCallback: (pattern) async { - return await geoapifyService.getLocationSuggestions(pattern); + List res = + await geoapifyService.getLocationSuggestions(pattern); + return res; // Return the LocationSuggestion objects directly }, builder: (context, controller, focusNode) { return Container( @@ -44,7 +60,6 @@ class LocationSearchWidget extends StatelessWidget { onPressed: () { controller.clear(); focusNode.unfocus(); - // remove keyboard }, )), style: TextStyle( @@ -56,13 +71,36 @@ class LocationSearchWidget extends StatelessWidget { }, itemBuilder: (context, suggestion) { return ListTile( - title: Text(suggestion), + title: Text( + suggestion.name), // Use suggestion.name instead of suggestion + subtitle: Text( + 'Lat: ${suggestion.latitude.toStringAsFixed(4)}, Lon: ${suggestion.longitude.toStringAsFixed(4)} \n ${suggestion.fullAddress}', + style: TextStyle(fontSize: 12, color: Colors.grey[600]), + ), leading: Icon(Icons.location_on, color: Colors.blue), ); }, onSelected: (suggestion) { + print("Location selected: ${suggestion.name}"); + print("Coordinates: ${suggestion.latitude}, ${suggestion.longitude}"); + + // Call the callback if provided + if (onLocationSelected != null) { + onLocationSelected!(suggestion); + } + + locator().createLandmark( + beaconId, + suggestion.name, + LatLng(suggestion.latitude, suggestion.longitude), + "images/icons/location-marker.png"); + ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('You selected: $suggestion')), + SnackBar( + content: Text( + 'Selected: ${suggestion.name}\nLat: ${suggestion.latitude}, Lon: ${suggestion.longitude}', + ), + ), ); }, hideOnEmpty: true, @@ -76,7 +114,7 @@ class LocationSearchWidget extends StatelessWidget { color: Colors.grey.withOpacity(0.2), spreadRadius: 2, blurRadius: 5, - offset: Offset(0, 3), // changes position of shadow + offset: Offset(0, 3), ), ], ), diff --git a/lib/presentation/home/home_screen.dart b/lib/presentation/home/home_screen.dart index 16f2c83..3aab58d 100644 --- a/lib/presentation/home/home_screen.dart +++ b/lib/presentation/home/home_screen.dart @@ -209,7 +209,7 @@ class _HomeScreenState extends State { Widget HomePage() { return Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildHeader(), _buildList(), @@ -365,7 +365,7 @@ class _HomeScreenState extends State { width: 50.w, height: 25.h, ), - SizedBox(height: 3.h), + SizedBox(height: 0.2.h), Text( 'You haven\'t joined or created any group yet', textAlign: TextAlign.center, @@ -374,7 +374,7 @@ class _HomeScreenState extends State { fontSize: 14.sp, ), ), - SizedBox(height: 3.h), + SizedBox(height: 0.2.h), RichText( textAlign: TextAlign.center, text: TextSpan( @@ -396,6 +396,13 @@ class _HomeScreenState extends State { ], ), ), + SizedBox(height: 3.h), + IconButton( + icon: Icon(Icons.refresh, size: 6.w, color: Colors.teal), + onPressed: () { + _homeCubit.fetchUserGroups(); + }, + ) ], ), ), diff --git a/pubspec.lock b/pubspec.lock index caa5c7e..a5e87fc 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,14 +22,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.11.0" - animate_do: - dependency: "direct main" - description: - name: animate_do - sha256: e5c8b92e8495cba5adfff17c0b017d50f46b2766226e9faaf68bc08c91aef034 - url: "https://pub.dev" - source: hosted - version: "4.2.0" archive: dependency: transitive description: @@ -515,14 +507,6 @@ packages: url: "https://pub.dev" source: hosted version: "7.2.0" - flutter_map: - dependency: "direct main" - description: - name: flutter_map - sha256: "52c65a977daae42f9aae6748418dd1535eaf27186e9bac9bf431843082bc75a3" - url: "https://pub.dev" - source: hosted - version: "4.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -950,14 +934,6 @@ packages: url: "https://pub.dev" source: hosted version: "6.9.0" - latlong2: - dependency: "direct main" - description: - name: latlong2 - sha256: "08ef7282ba9f76e8495e49e2dc4d653015ac929dce5f92b375a415d30b407ea0" - url: "https://pub.dev" - source: hosted - version: "0.8.2" leak_tracker: dependency: transitive description: @@ -982,14 +958,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" - lists: - dependency: transitive - description: - name: lists - sha256: "4ca5c19ae4350de036a7e996cdd1ee39c93ac0a2b840f4915459b7d0a7d4ab27" - url: "https://pub.dev" - source: hosted - version: "1.0.1" location: dependency: "direct main" description: @@ -1062,14 +1030,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" - mgrs_dart: - dependency: transitive - description: - name: mgrs_dart - sha256: fb89ae62f05fa0bb90f70c31fc870bcbcfd516c843fb554452ab3396f78586f7 - url: "https://pub.dev" - source: hosted - version: "2.0.0" mime: dependency: transitive description: @@ -1278,14 +1238,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.10.3" - polylabel: - dependency: transitive - description: - name: polylabel - sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b" - url: "https://pub.dev" - source: hosted - version: "1.0.1" pool: dependency: transitive description: @@ -1294,14 +1246,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - proj4dart: - dependency: transitive - description: - name: proj4dart - sha256: c8a659ac9b6864aa47c171e78d41bbe6f5e1d7bd790a5814249e6b68bc44324e - url: "https://pub.dev" - source: hosted - version: "2.1.0" provider: dependency: "direct main" description: @@ -1611,14 +1555,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" - tuple: - dependency: transitive - description: - name: tuple - sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 - url: "https://pub.dev" - source: hosted - version: "2.0.2" typed_data: dependency: transitive description: @@ -1651,14 +1587,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.0" - unicode: - dependency: transitive - description: - name: unicode - sha256: "0f69e46593d65245774d4f17125c6084d2c20b4e473a983f6e21b7d7762218f1" - url: "https://pub.dev" - source: hosted - version: "0.3.1" universal_platform: dependency: transitive description: @@ -1787,14 +1715,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.5" - wkt_parser: - dependency: transitive - description: - name: wkt_parser - sha256: "8a555fc60de3116c00aad67891bcab20f81a958e4219cc106e3c037aa3937f13" - url: "https://pub.dev" - source: hosted - version: "2.0.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7ff88dc..6caf6ce 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -9,9 +9,6 @@ environment: sdk: ">=3.3.0 <4.0.0" dependencies: - animate_do: - flutter_map: - latlong2: flutter_typeahead: ^5.2.0 connectivity_plus: any cupertino_icons: ^1.0.5 From c16561ed8cc4dc5f99c705769bae16c712de2e70 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sun, 13 Jul 2025 18:25:54 +0530 Subject: [PATCH 08/19] add shimmer and responsiveness --- lib/core/queries/beacon.dart | 2 + lib/core/queries/group.dart | 2 + .../group/Advanceoptions_screen.dart | 12 + lib/presentation/group/group_screen.dart | 746 +++++++----------- .../group/widgets/beacon_card.dart | 476 +++++------ .../group/widgets/create_join_dialog.dart | 12 + lib/presentation/home/home_screen.dart | 397 ++++------ lib/presentation/home/widgets/group_card.dart | 260 +++--- lib/presentation/widgets/shimmer.dart | 142 ++-- 9 files changed, 873 insertions(+), 1176 deletions(-) create mode 100644 lib/presentation/group/Advanceoptions_screen.dart diff --git a/lib/core/queries/beacon.dart b/lib/core/queries/beacon.dart index 74f3f02..3aed0e1 100644 --- a/lib/core/queries/beacon.dart +++ b/lib/core/queries/beacon.dart @@ -27,6 +27,7 @@ class BeaconQueries { leader { _id name + imageUrl } group{ _id @@ -39,6 +40,7 @@ class BeaconQueries { followers { _id name + imageUrl } startsAt expiresAt diff --git a/lib/core/queries/group.dart b/lib/core/queries/group.dart index bae132b..042d600 100644 --- a/lib/core/queries/group.dart +++ b/lib/core/queries/group.dart @@ -13,6 +13,7 @@ class GroupQueries { leader{ _id name + imageUrl } members{ _id @@ -87,6 +88,7 @@ class GroupQueries { members { _id name + imageUrl } beacons{ _id diff --git a/lib/presentation/group/Advanceoptions_screen.dart b/lib/presentation/group/Advanceoptions_screen.dart new file mode 100644 index 0000000..c989da6 --- /dev/null +++ b/lib/presentation/group/Advanceoptions_screen.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import 'package:auto_route/auto_route.dart'; + +@RoutePage() +class AdvanceoptionsScreen extends StatelessWidget { + const AdvanceoptionsScreen({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/presentation/group/group_screen.dart b/lib/presentation/group/group_screen.dart index 6351315..c386d7b 100644 --- a/lib/presentation/group/group_screen.dart +++ b/lib/presentation/group/group_screen.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:auto_route/auto_route.dart'; import 'package:beacon/domain/entities/beacon/beacon_entity.dart'; import 'package:beacon/domain/entities/group/group_entity.dart'; @@ -17,24 +15,20 @@ import 'package:beacon/locator.dart'; import 'package:beacon/core/utils/constants.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:gap/gap.dart'; import 'package:modal_progress_hud_nsn/modal_progress_hud_nsn.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; +import 'package:lottie/lottie.dart'; @RoutePage() class GroupScreen extends StatefulWidget { final GroupEntity group; - - GroupScreen(this.group); + const GroupScreen(this.group, {super.key}); @override - _GroupScreenState createState() => _GroupScreenState(); + State createState() => _GroupScreenState(); } -class _GroupScreenState extends State - with TickerProviderStateMixin { - late List fetchingUserBeacons; - late List fetchingNearbyBeacons; +class _GroupScreenState extends State { late GroupCubit _groupCubit; late MembersCubit _membersCubit; late ScrollController _scrollController; @@ -42,16 +36,19 @@ class _GroupScreenState extends State @override void initState() { super.initState(); - _scrollController = ScrollController(); - _scrollController.addListener(_listener); - _groupCubit = BlocProvider.of(context); - _membersCubit = BlocProvider.of(context); + _scrollController = ScrollController()..addListener(_onScroll); + _groupCubit = context.read(); + _membersCubit = context.read(); + _initializeData(); + } + + void _initializeData() { _groupCubit.init(widget.group); _groupCubit.allHikes(widget.group.id!); _membersCubit.init(widget.group); } - void _listener() { + void _onScroll() { if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) { final state = _groupCubit.state; @@ -63,6 +60,7 @@ class _GroupScreenState extends State @override void dispose() { + _scrollController.dispose(); _groupCubit.clear(); _membersCubit.clear(); super.dispose(); @@ -70,8 +68,6 @@ class _GroupScreenState extends State @override Widget build(BuildContext context) { - final screensize = MediaQuery.of(context).size; - return Scaffold( resizeToAvoidBottomInset: false, body: SafeArea( @@ -83,177 +79,23 @@ class _GroupScreenState extends State }, builder: (context, state) { return ModalProgressHUD( - progressIndicator: const LoadingScreen(), inAsyncCall: state is LoadingGroupState, + progressIndicator: const LoadingScreen(), child: Padding( - padding: EdgeInsets.only( - left: screensize.width * 0.04, - right: screensize.width * 0.04, - top: 12), + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - visualDensity: VisualDensity.compact, - padding: EdgeInsets.all(0), - icon: const Icon(Icons.arrow_back_outlined, - color: Colors.grey), - onPressed: () => AutoRouter.of(context).maybePop(), - ), - Image.asset( - 'images/beacon_logo.png', - height: 28, - ), - IconButton( - icon: const Icon(Icons.power_settings_new, - color: Colors.grey), - onPressed: () => showDialog( - context: context, - builder: (context) => AlertDialog( - backgroundColor: Color(0xffFAFAFA), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12.0), - ), - title: - Text('Logout', style: Style.heading), - content: Text( - 'Are you sure you want to logout?', - style: TextStyle( - fontSize: 16, color: kBlack), - ), - actions: [ - HikeButton( - buttonWidth: 80, - buttonHeight: 40, - isDotted: true, - onTap: () => AutoRouter.of(context) - .maybePop(false), - text: 'No', - textSize: 18.0, - ), - SizedBox( - height: 5, - ), - HikeButton( - buttonWidth: 80, - buttonHeight: 40, - onTap: () async { - appRouter.replaceNamed('/auth'); - localApi.deleteUser(); - context - .read() - .googleSignOut(); - }, - text: 'Yes', - textSize: 18.0, - ), - ], - ))), - ], - ), - const SizedBox(height: 10), - _buildGroupName(), - const SizedBox(height: 10), - Row( - children: [ - widget.group.members != null && - widget.group.members!.isNotEmpty - ? SizedBox( - width: 40 * - widget.group.members!.length.toDouble(), - // 40 is the width of each profile circle - height: 40, - child: Stack( - children: (widget.group.members != null && - widget.group.members!.length > 3 - ? widget.group.members!.sublist(0, 3) - : widget.group.members ?? []) - .map((member) { - if (member != null) { - print( - 'Member Image URL: ${member.imageUrl}'); - return Positioned( - left: widget.group.members! - .indexOf(member) * - 20.0, - child: _buildProfileCircle( - member.imageUrl!), - ); - } else { - return const SizedBox.shrink(); - } - }).toList(), - ), - ) - : Container(), - const SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Group has ${widget.group.members!.length.toString()} ${widget.group.members!.length == 1 ? 'member' : 'members'}', - style: TextStyle( - color: Colors.grey, - fontSize: 14, - ), - ), - const SizedBox(height: 2), - // view all members button - GestureDetector( - onTap: () { - GroupWidgetUtils.showMembers(context); - }, - child: Text( - "View all members", - style: TextStyle( - color: kBlack, - fontSize: 14, - decoration: TextDecoration.underline, - ), - textAlign: TextAlign.start, - ), - ) - ], - ), - ], - ), - const SizedBox(height: 30), - _buildJoinCreateButton(), - const SizedBox(height: 30), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - 'All Beacons', - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.purple, - ), - ), - const SizedBox(width: 10), - IconButton( - padding: EdgeInsets.all(0), - visualDensity: VisualDensity.compact, - onPressed: () { - GroupWidgetUtils.showFilterBeaconAlertBox( - context, widget.group.id!, _groupCubit); - }, - icon: Icon( - Icons.filter_alt_outlined, - color: Colors.purple, - ), - ) - ], - ), - const SizedBox(height: 10), - Expanded(child: _groupBeacons(state)), + children: [ + _buildAppBar(), + SizedBox(height: 2.h), + _buildGroupHeader(), + SizedBox(height: 2.h), + _buildMembersSection(), + SizedBox(height: 3.h), + _buildActionButtons(), + SizedBox(height: 3.h), + _buildBeaconsHeader(), + SizedBox(height: 1.h), + Expanded(child: _buildBeaconsList(state)), ], ), ), @@ -264,338 +106,328 @@ class _GroupScreenState extends State ); } - Widget _buildJoinCreateButton() { - Size screensize = MediaQuery.of(context).size; + Widget _buildAppBar() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.grey), + onPressed: () => context.router.maybePop(), + ), + Image.asset('images/beacon_logo.png', height: 4.h), + IconButton( + icon: const Icon(Icons.power_settings_new, color: Colors.grey), + onPressed: _showLogoutDialog, + ), + ], + ); + } + + Widget _buildGroupHeader() { + return Row( + children: [ + Text( + 'Welcome to Group ', + style: TextStyle(fontSize: 18.sp), + ), + SizedBox(width: 2.w), + Text( + widget.group.title ?? '', + style: TextStyle( + fontSize: 20.sp, + color: Colors.teal, + fontWeight: FontWeight.bold, + ), + ), + ], + ); + } + + Widget _buildMembersSection() { + final memberCount = (widget.group.members?.length ?? 0) + 1; + print("leaders image: ${widget.group.leader?.imageUrl}"); return Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Container( - width: 45.w, + children: [ + if (widget.group.members?.isNotEmpty ?? false) + SizedBox( + width: 10.w * + (widget.group.members!.length > 3 + ? 3 + : widget.group.members!.length), + height: 5.h, + child: Stack( + children: (widget.group.members!.length > 3 + ? widget.group.members!.sublist(0, 3) + : widget.group.members!) + .map((member) => Positioned( + left: widget.group.members!.indexOf(member) * 8.w, + child: _buildProfileCircle(member?.imageUrl), + )) + .toList(), + ), + ), + SizedBox(width: 3.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Group has $memberCount ${memberCount == 1 ? 'member' : 'members'}', + style: TextStyle(fontSize: 14.sp, color: Colors.grey), + ), + SizedBox(height: 0.5.h), + GestureDetector( + onTap: () => GroupWidgetUtils.showMembers(context), + child: Text( + "View all members", + style: TextStyle( + fontSize: 14.sp, + decoration: TextDecoration.underline, + ), + ), + ), + ], + ), + ], + ); + } + + Widget _buildActionButtons() { + return Row( + children: [ + Expanded( child: HikeButton( text: 'Create Hike', widget: Icon( Icons.add, - color: Colors.black, - size: 18, + size: 18.sp, + color: Colors.white, ), - textColor: Colors.white, - borderColor: Colors.white, - buttonWidth: screensize.width * 0.44, - buttonHeight: 45, - onTap: () { - CreateJoinBeaconDialog.createHikeDialog( - context, widget.group.id!); - }, + buttonWidth: double.infinity, + buttonHeight: 6.h, + onTap: () => CreateJoinBeaconDialog.createHikeDialog( + context, widget.group.id!), ), ), - SizedBox(width: 1.w), - Container( - width: 45.w, + SizedBox(width: 3.w), + Expanded( child: HikeButton( text: 'Join a Hike', - widget: Icon( - Icons.add, - color: Colors.black, - size: 18, - ), - buttonColor: Colors.white, + widget: Icon(Icons.add, size: 18.sp), + buttonWidth: double.infinity, + buttonHeight: 6.h, isDotted: true, - buttonWidth: screensize.width * 0.44, - buttonHeight: 45, - onTap: () async { - CreateJoinBeaconDialog.joinBeaconDialog(context); - }, + onTap: () => CreateJoinBeaconDialog.joinBeaconDialog(context), ), ), ], ); } - Widget _groupBeacons(GroupState state) { - return Padding( - padding: EdgeInsets.symmetric(horizontal: 2.0), - child: Builder( - builder: (context) { - if (state is ShrimmerGroupState) { - return Center(child: ShimmerWidget.getPlaceholder()); - } else if (state is AllBeaconGroupState) { - final beacons = state.beacons; - String message = 'You haven\'t joined or created any beacon yet'; - return _buildBeaconsList(beacons, state.isLoadingMore, - state.isCompletelyFetched, message); - } else if (state is NearbyBeaconGroupState) { - final beacons = state.beacons; - String message = - 'No beacons found under ${state.radius.toStringAsFixed(2)} m... radius'; - return _buildBeaconsList(beacons, false, false, message); - } else if (state is StatusFilterBeaconGroupState) { - final beacons = state.beacons; - var type = state.type!.name; - String message = - 'No ${type[0].toUpperCase() + type.substring(1).toLowerCase()} beacons found'; - return _buildBeaconsList(beacons, false, false, message); - } else if (state is ErrorGroupState) { - return _buildErrorWidget(state.message); - } - return _buildErrorWidget('Something went wrong!'); - }, - ), - ); - } - - Widget _buildGroupName() { + Widget _buildBeaconsHeader() { return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - 'Welcome to Group ', - textAlign: TextAlign.center, - style: TextStyle(fontSize: 20, color: Colors.black), - ), - SizedBox( - width: 2.w, - ), - Text( - widget.group.title!, - textAlign: TextAlign.center, + 'All Beacons', style: TextStyle( - fontSize: 24, - color: Colors.tealAccent, - fontWeight: FontWeight.bold), + fontSize: 18.sp, + fontWeight: FontWeight.bold, + color: Colors.purple, + ), + ), + IconButton( + icon: Icon(Icons.filter_alt_outlined, color: Colors.purple), + onPressed: () => GroupWidgetUtils.showFilterBeaconAlertBox( + context, widget.group.id!, _groupCubit), ), ], ); } - Widget _buildBeaconsList(List beacons, bool isLoadingMore, - bool isCompletelyFetched, String message) { - return Container( - alignment: Alignment.center, - child: beacons.isEmpty - ? SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: _noBeaconsWidget(message), - ) - : ListView.builder( - controller: _scrollController, - physics: AlwaysScrollableScrollPhysics(), - scrollDirection: Axis.vertical, - itemCount: beacons.length + - (isLoadingMore && !isCompletelyFetched ? 1 : 0), - padding: EdgeInsets.all(8), - itemBuilder: (context, index) { - if (index == beacons.length) { - return LinearProgressIndicator(); - } - return _buildBeaconCard(beacons[index]); - }, - ), - ); + Widget _buildBeaconsList(GroupState state) { + if (state is ShrimmerGroupState) { + return Center(child: ShimmerWidget.getPlaceholder()); + } else if (state is AllBeaconGroupState) { + return _buildBeaconListView( + state.beacons, + state.isLoadingMore, + state.isCompletelyFetched, + 'You haven\'t joined or created any beacon yet', + ); + } else if (state is NearbyBeaconGroupState) { + return _buildBeaconListView( + state.beacons, + false, + false, + 'No beacons found under ${state.radius.toStringAsFixed(2)} m radius', + ); + } else if (state is StatusFilterBeaconGroupState) { + final type = state.type!.name; + return _buildBeaconListView( + state.beacons, + false, + false, + 'No ${type[0].toUpperCase() + type.substring(1).toLowerCase()} beacons found', + ); + } else if (state is ErrorGroupState) { + return _buildErrorWidget(state.message); + } + return _buildErrorWidget('Something went wrong!'); + } + + Widget _buildBeaconListView( + List beacons, + bool isLoadingMore, + bool isCompletelyFetched, + String emptyMessage, + ) { + return beacons.isEmpty + ? _buildEmptyState(emptyMessage) + : ListView.builder( + controller: _scrollController, + itemCount: beacons.length + + (isLoadingMore && !isCompletelyFetched ? 1 : 0), + padding: EdgeInsets.only(top: 1.h), + itemBuilder: (context, index) { + if (index == beacons.length) { + return Padding( + padding: EdgeInsets.symmetric(vertical: 2.h), + child: const Center(child: LinearProgressIndicator()), + ); + } + return BeaconCard( + beacon: beacons[index], + onDelete: () => _handleDeleteBeacon(beacons[index]), + onReschedule: () => GroupWidgetUtils.reScheduleHikeDialog( + context, beacons[index]), + ); + }, + ); } - Widget _buildBeaconCard(BeaconEntity beacon) { - return BeaconCard( - beacon: beacon, - onDelete: () async { - bool? value = await GroupWidgetUtils.deleteDialog(context); - if (value == null || !value) { - return; - } - await _groupCubit.deleteBeacon(beacon); - _groupCubit.reloadState(message: 'Beacon deleted'); - return; - }, - onReschedule: () { - GroupWidgetUtils.reScheduleHikeDialog(context, beacon); - }, + Future _handleDeleteBeacon(BeaconEntity beacon) async { + final shouldDelete = await GroupWidgetUtils.deleteDialog(context); + if (shouldDelete == true) { + await _groupCubit.deleteBeacon(beacon); + _groupCubit.reloadState(message: 'Beacon deleted'); + } + } + + Widget _buildEmptyState(String message) { + return SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Lottie.asset('animations/empty.json', width: 50.w, height: 25.h), + SizedBox(height: 3.h), + Text(message, style: TextStyle(fontSize: 16.sp)), + SizedBox(height: 3.h), + RichText( + text: TextSpan( + style: TextStyle(fontSize: 16.sp), + children: [ + const TextSpan( + text: 'Join', + style: TextStyle(fontWeight: FontWeight.bold)), + const TextSpan(text: ' a Hike or '), + const TextSpan( + text: 'Create', + style: TextStyle(fontWeight: FontWeight.bold)), + const TextSpan(text: ' a new one!'), + ], + ), + ), + ], + ), ); } Widget _buildErrorWidget(String message) { return Center( child: SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), child: Column( children: [ - Text( - message, - textAlign: TextAlign.center, - style: TextStyle(color: kBlack, fontSize: 20), - ), - Gap(5), + Text(message, style: TextStyle(fontSize: 16.sp)), + SizedBox(height: 2.h), FloatingActionButton( - onPressed: () async { - try { - await locationService.openSettings(); - } catch (e) { - log('error: $e'); - } - }, - child: Icon( - Icons.settings, - color: kBlack, - ), + onPressed: () => locationService.openSettings(), + child: const Icon(Icons.settings), backgroundColor: kYellow, ), - Gap(15), + SizedBox(height: 2.h), RichText( text: TextSpan( - style: TextStyle(color: kBlack, fontSize: 20), + style: TextStyle(fontSize: 16.sp), children: [ - TextSpan( + const TextSpan( text: 'Join', style: TextStyle(fontWeight: FontWeight.bold)), - TextSpan(text: ' a Hike or '), - TextSpan( + const TextSpan(text: ' a Hike or '), + const TextSpan( text: 'Create', style: TextStyle(fontWeight: FontWeight.bold)), - TextSpan(text: ' a new one! '), + const TextSpan(text: ' a new one!'), ], ), ), - SizedBox( - height: 2.h, - ), ], ), ), ); } - Widget _noBeaconsWidget(String message) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: Column( - children: [ - Text( - message, - textAlign: TextAlign.center, - style: TextStyle(color: kBlack, fontSize: 20), - ), - SizedBox( - height: 2.h, - ), - RichText( - text: TextSpan( - style: TextStyle(color: kBlack, fontSize: 20), - children: [ - TextSpan( - text: 'Join', - style: TextStyle(fontWeight: FontWeight.bold)), - TextSpan(text: ' a Hike or '), - TextSpan( - text: 'Create', - style: TextStyle(fontWeight: FontWeight.bold)), - TextSpan(text: ' a new one! '), - ], - ), - ), - SizedBox( - height: 2.h, - ), - ], + Widget _buildProfileCircle(String? imageUrl) { + return Container( + width: 10.w, + height: 10.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey[300], + border: Border.all(color: Colors.white, width: 1.w), + image: imageUrl != null + ? DecorationImage(image: NetworkImage(imageUrl), fit: BoxFit.cover) + : null, ), ); } -} - -Widget _buildProfileCircle(String url) { - return Container( - width: 40, - height: 40, - decoration: BoxDecoration( - shape: BoxShape.circle, - color: Colors.grey[300], - border: Border.all(color: Colors.white, width: 2), - image: DecorationImage( - image: NetworkImage(url), - fit: BoxFit.cover, - ), - ), - ); -} -class HikeCard extends StatelessWidget { - final bool isActive; - final String startTime; - final String endTime; - final String passkey; - - const HikeCard({ - super.key, - required this.isActive, - required this.startTime, - required this.endTime, - required this.passkey, - }); - - @override - Widget build(BuildContext context) { - return Card( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), - elevation: 2, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: const [ - Icon(Icons.hiking, size: 28), - SizedBox(width: 8), - Text('Hike 1', - style: - TextStyle(fontSize: 20, fontWeight: FontWeight.bold)), - ], - ), - const SizedBox(height: 8), - Text( - 'Hike is ${isActive ? "Active" : "inactive"}', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: isActive ? Colors.teal : Colors.black87, - ), - ), - const SizedBox(height: 8), - Text('Started at: $startTime'), - Text('Expires at: $endTime'), - const SizedBox(height: 8), - Row( - children: [ - Text('Passkey: $passkey', - style: const TextStyle(fontWeight: FontWeight.w500)), - const Spacer(), - IconButton( - icon: const Icon(Icons.copy), - onPressed: () { - // TODO: Copy to clipboard - }, - ), - ], - ), - const SizedBox(height: 8), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - OutlinedButton( - onPressed: () { - // TODO: Delete action - }, - child: const Text("Delete"), - ), - const SizedBox(width: 8), - OutlinedButton( - onPressed: () { - // TODO: Reschedule action - }, - child: const Text("Reschedule"), - ), - ], - ), - ], + void _showLogoutDialog() { + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + title: Text('Logout', style: TextStyle(fontSize: 18.sp)), + content: Text( + 'Are you sure you want to logout?', + style: TextStyle(fontSize: 16.sp), ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + HikeButton( + buttonWidth: 25.w, + buttonHeight: 5.h, + isDotted: true, + onTap: () => context.router.maybePop(false), + text: 'No', + textSize: 14.sp, + ), + HikeButton( + buttonWidth: 25.w, + buttonHeight: 5.h, + onTap: () { + context.router.replaceNamed('/auth'); + localApi.deleteUser(); + context.read().googleSignOut(); + }, + text: 'Yes', + textSize: 14.sp, + ), + ], + ), + ], ), ); } diff --git a/lib/presentation/group/widgets/beacon_card.dart b/lib/presentation/group/widgets/beacon_card.dart index 6f9cc35..0811843 100644 --- a/lib/presentation/group/widgets/beacon_card.dart +++ b/lib/presentation/group/widgets/beacon_card.dart @@ -18,12 +18,13 @@ class BeaconCard extends StatefulWidget { final BeaconEntity beacon; final void Function()? onDelete; final void Function()? onReschedule; - BeaconCard( - {required this.beacon, - required this.onDelete, - required this.onReschedule, - Key? key}) - : super(key: key); + + const BeaconCard({ + required this.beacon, + required this.onDelete, + required this.onReschedule, + Key? key, + }) : super(key: key); @override State createState() => _BeaconCardState(); @@ -33,13 +34,14 @@ class _BeaconCardState extends State { late bool hasStarted; late bool hasEnded; late bool willStart; - DateTime now = DateTime.now(); + Timer? _rebuildTimer; + late DateTime startAt; late DateTime expiresAt; - Timer? _rebuildTimer; @override void initState() { + final now = DateTime.now(); startAt = DateTime.fromMillisecondsSinceEpoch(widget.beacon.startsAt!); expiresAt = DateTime.fromMillisecondsSinceEpoch(widget.beacon.expiresAt!); hasStarted = now.isAfter(startAt); @@ -52,47 +54,47 @@ class _BeaconCardState extends State { void scheduleRebuild() { if (hasEnded) return; + final now = DateTime.now(); late int seconds; if (willStart) { - Duration difference = startAt.difference(now); - seconds = difference.inSeconds; - } else if (hasStarted && !hasEnded) { - Duration difference = expiresAt.difference(now); - seconds = difference.inSeconds; + seconds = startAt.difference(now).inSeconds; + } else { + seconds = expiresAt.difference(now).inSeconds; } - _rebuildTimer?.cancel(); - _rebuildTimer = Timer(Duration(milliseconds: seconds * 1000 + 1000), () { - var now = DateTime.now(); - hasStarted = now.isAfter(startAt); - hasEnded = now.isAfter(expiresAt); - willStart = now.isBefore(startAt); - setState(() {}); + _rebuildTimer?.cancel(); + _rebuildTimer = Timer(Duration(seconds: seconds + 1), () { + setState(() { + final now = DateTime.now(); + hasStarted = now.isAfter(startAt); + hasEnded = now.isAfter(expiresAt); + willStart = now.isBefore(startAt); + }); + // Show notification if the beacon becomes active Future.delayed(Duration(seconds: 1), () { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - duration: Duration(seconds: 5), + duration: const Duration(seconds: 5), content: Text( - '${widget.beacon.title} is now active! \nYou can join the hike', - style: TextStyle(color: Colors.black), + '${widget.beacon.title} is now active!\nYou can join the hike', + style: const TextStyle(color: Colors.black), ), - backgroundColor: kLightBlue.withValues(alpha: 0.8), + backgroundColor: kLightBlue.withAlpha(200), shape: RoundedRectangleBorder( - borderRadius: BorderRadius.all( - Radius.circular(10), - ), + borderRadius: BorderRadius.circular(10), ), behavior: SnackBarBehavior.floating, elevation: 5, action: SnackBarAction( - textColor: kBlue, label: 'Click to Join', - onPressed: () async { + textColor: kBlue, + onPressed: () { appRouter.push(HikeScreenRoute( - beacon: widget.beacon, - isLeader: widget.beacon.id! == localApi.userModel.id!)); + beacon: widget.beacon, + isLeader: widget.beacon.id == localApi.userModel.id, + )); }, ), ), @@ -107,253 +109,205 @@ class _BeaconCardState extends State { super.dispose(); } + String formatDate(int timestamp) { + return DateFormat("hh:mm a, d/M/y") + .format(DateTime.fromMillisecondsSinceEpoch(timestamp)); + } + @override Widget build(BuildContext context) { - BeaconEntity beacon = widget.beacon; + final beacon = widget.beacon; return InkWell( - onTap: () async { + onTap: () { locator().joinBeacon(beacon, hasEnded, hasStarted); }, child: Container( - margin: const EdgeInsets.only(bottom: 10.0), - padding: EdgeInsets.only(left: 16.0, right: 16.0, bottom: 8, top: 8), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, + margin: const EdgeInsets.only(bottom: 10), + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.circular(8.0), + boxShadow: const [ + BoxShadow( + color: Colors.black12, + offset: Offset(0, 1), + blurRadius: 6.0, + ) + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + /// Header Row + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon(Icons.hiking, color: Colors.black), + Gap(8), + Expanded( + child: Text( + '${beacon.title?.toUpperCase()} by ${beacon.leader?.name}', + style: TextStyle( + fontSize: 17.sp, + fontWeight: FontWeight.w700, + color: Colors.black, + ), + ), + ), + if (hasStarted && !hasEnded) + BlinkIcon() + else if (willStart) + Icon(Icons.circle, color: kYellow, size: 10), + ], + ), + Gap(8), + + /// Status Row + if (hasStarted && !hasEnded) + RichText( + text: TextSpan( + style: Style.commonTextStyle, + children: [ + const TextSpan(text: 'Hike is '), + TextSpan( + text: 'Active', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ], + ), + ) + else if (willStart) + Row( children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 70.w, - child: Row( - children: [ - Icon( - Icons.hiking, - color: Colors.black, - ), - SizedBox(width: 8.0), - Text( - '${beacon.title!.toUpperCase()} by ${beacon.leader!.name} ', - style: TextStyle( - fontSize: 18.0, - color: Colors.black, - fontWeight: FontWeight.w700, - ), - ), - ], + RichText( + text: TextSpan( + style: Style.commonTextStyle, + children: [ + const TextSpan(text: 'Hike '), + TextSpan( + text: 'Starts ', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + color: Colors.black, + ), ), - ), - Align( - alignment: Alignment.topRight, - child: (hasStarted && !hasEnded) - ? BlinkIcon() - : willStart - ? Align( - alignment: Alignment.topRight, - child: Icon( - Icons.circle, - color: kYellow, - size: 10, - ), - ) - : null, - ), - ], - ), - SizedBox(height: 8.0), - (hasStarted && !hasEnded) - ? RichText( - text: TextSpan( - text: 'Hike is ', - style: Style.commonTextStyle, - children: const [ - TextSpan( - text: 'Active', - style: TextStyle( - fontSize: 16.0, - color: Colors.black, - fontWeight: FontWeight.bold, - letterSpacing: 1.0), - ), - ], + const TextSpan( + text: 'in ', + style: TextStyle( + color: Color(0xffb6b2df), + fontSize: 14, + fontWeight: FontWeight.w400, ), - ) - : willStart - ? Row( - children: [ - RichText( - text: TextSpan( - text: 'Hike ', - style: Style.commonTextStyle, - children: const [ - TextSpan( - text: 'Starts ', - style: TextStyle( - fontSize: 16.0, - color: Colors.black, - fontWeight: FontWeight.bold, - letterSpacing: 1.0), - ), - TextSpan( - text: 'in ', - style: TextStyle( - color: const Color(0xffb6b2df), - fontSize: 14.0, - fontWeight: FontWeight.w400), - ), - ], - ), - ), - SizedBox( - width: 3.0, - ), - CountdownTimerPage( - dateTime: DateTime.fromMillisecondsSinceEpoch( - beacon.startsAt!), - name: beacon.title, - beacon: beacon, - ) - ], - ) - : Row( - children: [ - RichText( - text: TextSpan( - text: 'Hike ', - style: Style.commonTextStyle, - children: const [ - TextSpan( - text: 'is Ended', - style: TextStyle( - fontSize: 16.0, - color: Colors.black, - fontWeight: FontWeight.bold, - letterSpacing: 1.0), - ), - ], - ), - ), - ], - ), - SizedBox(height: 4.0), - Row( - children: [ - Text('Passkey: ${beacon.shortcode}', - style: Style.commonTextStyle), - Gap(10), - InkWell( - onTap: () { - Clipboard.setData(ClipboardData( - text: beacon.shortcode.toString())); - utils.showSnackBar('Shortcode copied!', context); - }, - child: Icon( - Icons.copy, - color: Colors.black, - size: 15, - )) - ], + ), + ], + ), ), - SizedBox(height: 4.0), - (beacon.startsAt != null) - ? Text( - willStart - ? 'Starting At: ${DateFormat("hh:mm a, d/M/y").format(DateTime.fromMillisecondsSinceEpoch(beacon.startsAt!)).toString()}' - : 'Started At: ${DateFormat("hh:mm a, d/M/y").format(DateTime.fromMillisecondsSinceEpoch(beacon.startsAt!)).toString()}', - style: Style.commonTextStyle) - : Container(), - SizedBox(height: 4.0), - (beacon.expiresAt != null) - ? willStart - ? Text( - 'Expiring At: ${DateFormat("hh:mm a, d/M/y").format(DateTime.fromMillisecondsSinceEpoch(beacon.expiresAt!)).toString()}', - style: Style.commonTextStyle) - : Text( - 'Expires At: ${DateFormat("hh:mm a, d/M/y").format(DateTime.fromMillisecondsSinceEpoch(beacon.expiresAt!)).toString()}', - style: Style.commonTextStyle) - : Container(), - SizedBox(height: 4.0), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - OutlinedButton( - style: OutlinedButton.styleFrom( - foregroundColor: Colors.black, - padding: const EdgeInsets.symmetric(horizontal: 2), - visualDensity: const VisualDensity( - horizontal: -2.0, vertical: -2.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0)), - side: const BorderSide(color: Colors.black)), - onPressed: widget.onDelete, - child: const Text("Delete"), - ), - const SizedBox(width: 10), - OutlinedButton( - style: OutlinedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 4), - visualDensity: const VisualDensity( - horizontal: .0, vertical: -2.0), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8.0)), - foregroundColor: Colors.black, - side: const BorderSide(color: Colors.black)), - onPressed: widget.onReschedule, - child: const Text("Reschedule"), - ), - ], + Gap(4), + CountdownTimerPage( + dateTime: startAt, + name: beacon.title ?? '', + beacon: beacon, ) ], ) - ], - ), - decoration: BoxDecoration( - color: Colors.grey[50], - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(8.0), - boxShadow: const [ - BoxShadow( - color: Colors.black12, - offset: Offset(0.0, 1.0), - blurRadius: 6.0, + else + RichText( + text: TextSpan( + style: Style.commonTextStyle, + children: [ + const TextSpan(text: 'Hike '), + TextSpan( + text: 'is Ended', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + ), + ], + ), ), - ], - )), - ); - } + Gap(8), - Future deleteDialog(BuildContext context) async { - return showDialog( - context: context, - builder: (context) => AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - // actionsAlignment: MainAxisAlignment.spaceEvenly, - contentPadding: EdgeInsets.all(25.0), - content: Text( - 'Are you sure you want to delete this beacon?', - style: TextStyle(fontSize: 18, color: kBlack), + /// Passkey Row + Row( + children: [ + Text( + 'Passkey: ${beacon.shortcode}', + style: Style.commonTextStyle, + ), + Gap(10), + InkWell( + onTap: () { + Clipboard.setData( + ClipboardData(text: beacon.shortcode ?? ''), + ); + utils.showSnackBar('Shortcode copied!', context); + }, + child: Icon(Icons.copy, color: Colors.black, size: 16.sp), + ), + ], + ), + Gap(8), + + /// Start and expiry + if (beacon.startsAt != null) + Text( + willStart + ? 'Starting At: ${formatDate(beacon.startsAt!)}' + : 'Started At: ${formatDate(beacon.startsAt!)}', + style: Style.commonTextStyle, + ), + if (beacon.expiresAt != null) + Text( + willStart + ? 'Expiring At: ${formatDate(beacon.expiresAt!)}' + : 'Expires At: ${formatDate(beacon.expiresAt!)}', + style: Style.commonTextStyle, + ), + Gap(12), + + /// Action buttons + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.black, + side: const BorderSide(color: Colors.black), + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + visualDensity: VisualDensity(horizontal: -1, vertical: -2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + onPressed: widget.onDelete, + child: const Text("Delete"), + ), + Gap(8), + OutlinedButton( + style: OutlinedButton.styleFrom( + foregroundColor: Colors.black, + side: const BorderSide(color: Colors.black), + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), + visualDensity: VisualDensity(horizontal: -1, vertical: -2), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + onPressed: widget.onReschedule, + child: const Text("Reschedule"), + ), + ], + ) + ], ), - actions: [ - HikeButton( - buttonHeight: 2.5.h, - buttonWidth: 8.w, - onTap: () => AutoRouter.of(context).maybePop(false), - text: 'No', - ), - HikeButton( - buttonHeight: 2.5.h, - buttonWidth: 8.w, - onTap: () => AutoRouter.of(context).maybePop(true), - text: 'Yes', - ), - ], ), ); } diff --git a/lib/presentation/group/widgets/create_join_dialog.dart b/lib/presentation/group/widgets/create_join_dialog.dart index a1fd5ff..846ac55 100644 --- a/lib/presentation/group/widgets/create_join_dialog.dart +++ b/lib/presentation/group/widgets/create_join_dialog.dart @@ -539,6 +539,18 @@ class CreateJoinBeaconDialog { ), ), ), + + GestureDetector( + onTap: () => {}, + child: Text( + "advanced options", + style: TextStyle( + fontSize: 15.sp, + color: Theme.of(context).primaryColor, + decoration: TextDecoration.underline, + ), + ), + ), SizedBox(height: 2.h), Flexible( flex: 2, diff --git a/lib/presentation/home/home_screen.dart b/lib/presentation/home/home_screen.dart index 3aab58d..cb8cafb 100644 --- a/lib/presentation/home/home_screen.dart +++ b/lib/presentation/home/home_screen.dart @@ -1,5 +1,3 @@ -import 'dart:ui'; - import 'package:auto_route/auto_route.dart'; import 'package:beacon/domain/entities/group/group_entity.dart'; import 'package:beacon/presentation/auth/auth_cubit/auth_cubit.dart'; @@ -30,6 +28,34 @@ class HomeScreen extends StatefulWidget { class _HomeScreenState extends State { int _currentIndex = 0; + late ScrollController _scrollController; + late HomeCubit _homeCubit; + + @override + void initState() { + super.initState(); + _scrollController = ScrollController(); + locationService.getCurrentLocation(); + _homeCubit = BlocProvider.of(context); + _homeCubit.init(); + _homeCubit.fetchUserGroups(); + _scrollController.addListener(_onScroll); + } + + void _onScroll() { + if (_scrollController.position.pixels == + _scrollController.position.maxScrollExtent) { + _homeCubit.fetchUserGroups(); + } + } + + @override + void dispose() { + _scrollController.dispose(); + _homeCubit.clear(); + super.dispose(); + } + Future _onPopHome(BuildContext context) async { return showDialog( context: context, @@ -39,7 +65,7 @@ class _HomeScreenState extends State { ), contentPadding: EdgeInsets.symmetric( horizontal: 5.w, - vertical: 3.h, + vertical: 2.h, ), title: Text( 'Confirm Exit', @@ -80,54 +106,19 @@ class _HomeScreenState extends State { ); } - late ScrollController _scrollController; - late HomeCubit _homeCubit; - - @override - void initState() { - super.initState(); - _scrollController = ScrollController(); - locationService.getCurrentLocation(); - _homeCubit = BlocProvider.of(context); - _homeCubit.init(); - _homeCubit.fetchUserGroups(); - _scrollController.addListener(_onScroll); - } - - void _onScroll() { - if (_scrollController.position.pixels == - _scrollController.position.maxScrollExtent) { - _homeCubit.fetchUserGroups(); - } - } - - @override - void dispose() { - _scrollController.dispose(); - _homeCubit.clear(); - super.dispose(); - } - @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (bool didPop, Object? result) async { - if (didPop) { - return; - } - + if (didPop) return; bool? popped = await _onPopHome(context); - if (popped == true) { - await SystemNavigator.pop(); - } + if (popped == true) await SystemNavigator.pop(); }, child: BlocConsumer( listener: (context, state) { - if (state is LoadedHomeState) { - state.message != null - ? utils.showSnackBar(state.message!, context) - : null; + if (state is LoadedHomeState && state.message != null) { + utils.showSnackBar(state.message!, context); } }, builder: (context, state) { @@ -135,94 +126,54 @@ class _HomeScreenState extends State { resizeToAvoidBottomInset: false, body: SafeArea( child: ModalProgressHUD( - inAsyncCall: state is LoadingHomeState ? true : false, - progressIndicator: LoadingScreen(), + inAsyncCall: state is LoadingHomeState, + progressIndicator: const LoadingScreen(), child: Padding( - padding: EdgeInsets.symmetric( - horizontal: 4.w, - vertical: 2.h, - ), + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ + children: [ // App bar - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Image.asset( - 'images/beacon_logo.png', - height: 4.h, - ), - IconButton( - icon: Icon( - Icons.power_settings_new, - color: Colors.grey, - size: 6.w, - ), - onPressed: () => _showLogoutDialog(), - ), - ], - ), - - SizedBox(height: 3.h), - + _buildAppBar(), SizedBox(height: 2.h), - Expanded( - child: _currentIndex == 0 - ? HomePage() - : _currentIndex == 1 - ? ProfileScreen(homeCubit: _homeCubit) - : SettingsPage()) + child: _currentIndex == 0 + ? _buildHomePage() + : _currentIndex == 1 + ? ProfileScreen(homeCubit: _homeCubit) + : _buildSettingsPage(), + ), ], ), ), ), ), - bottomNavigationBar: BottomNavigationBar( - currentIndex: 0, - onTap: (index) { - setState(() { - _currentIndex = index; - }); - }, - items: [ - BottomNavigationBarItem( - icon: Icon(Icons.home, size: 6.w), - label: 'Home', - ), - BottomNavigationBarItem( - icon: Icon(Icons.person_4, size: 6.w), - label: 'Profile', - ), - BottomNavigationBarItem( - icon: Icon(Icons.hiking, size: 6.w), - label: 'Hike', - ), - ], - ), + bottomNavigationBar: _buildBottomNavigationBar(), ); }, ), ); } - Widget HomePage() { - return Column( - crossAxisAlignment: CrossAxisAlignment.center, + Widget _buildAppBar() { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - _buildHeader(), - _buildList(), + Image.asset('images/beacon_logo.png', height: 4.h), + IconButton( + icon: Icon(Icons.power_settings_new, color: Colors.grey, size: 20.sp), + onPressed: _showLogoutDialog, + ), ], ); } - Widget SettingsPage() { - return Center( - child: Text( - 'Settings Page', - style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.bold), - ), + Widget _buildHomePage() { + return Column( + children: [ + _buildHeader(), + SizedBox(height: 2.h), + Expanded(child: _buildGroupList()), + ], ); } @@ -230,17 +181,13 @@ class _HomeScreenState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Welcome message Row( children: [ - Flexible( - child: Text( - 'Welcome back, ', - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - color: Colors.black87, - ), + Text( + 'Welcome back, ', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, ), ), Flexible( @@ -256,10 +203,7 @@ class _HomeScreenState extends State { ), ], ), - SizedBox(height: 1.h), - - // Ready to explore Text( 'Ready to explore?', style: TextStyle( @@ -268,140 +212,99 @@ class _HomeScreenState extends State { color: Color(0xFF673AB7), ), ), - SizedBox(height: 3.h), - - // Create and Join Group buttons - Row( - children: [ - Expanded( - child: HikeButton( - widget: Icon( - Icons.add, - color: Colors.white, - size: 5.w, - ), - buttonWidth: double.infinity, - buttonHeight: 6.h, - text: 'Create Group', - textSize: 14.sp, - onTap: () async { - CreateJoinGroupDialog.createGroupDialog(context); - }, - ), - ), - SizedBox(width: 3.w), - Expanded( - child: HikeButton( - widget: Icon( - Icons.add, - color: Colors.teal, - size: 5.w, - ), - isDotted: true, - buttonWidth: double.infinity, - buttonHeight: 6.h, - text: 'Join a Group', - textSize: 14.sp, - onTap: () async { - CreateJoinGroupDialog.joinGroupDialog(context); - }, - ), - ), - ], - ), - - SizedBox(height: 4.h), - + _buildActionButtons(), + SizedBox(height: 3.h), Text( 'Your Groups', style: TextStyle( fontSize: 16.sp, fontWeight: FontWeight.w600, - color: Colors.black87, ), ), ], ); } - Widget _buildList() { - return Expanded( - child: BlocBuilder( - buildWhen: (previous, current) => true, - builder: (context, state) { - if (state is ShimmerHomeState) { - return Center( - child: ShimmerWidget.getPlaceholder(), - ); - } else if (state is LoadedHomeState) { - List groups = state.groups; - if (groups.isEmpty) { - return _buildEmptyState(); - } else { - return _buildGroupsList(groups, state); - } - } + Widget _buildActionButtons() { + return Row( + children: [ + Expanded( + child: HikeButton( + widget: Icon(Icons.add, color: Colors.white, size: 20.sp), + buttonHeight: 6.h, + buttonWidth: double.infinity, + text: 'Create Group', + textSize: 14.sp, + onTap: () => CreateJoinGroupDialog.createGroupDialog(context), + ), + ), + SizedBox(width: 3.w), + Expanded( + child: HikeButton( + widget: Icon(Icons.add, color: Colors.teal, size: 20.sp), + isDotted: true, + buttonHeight: 6.h, + buttonWidth: double.infinity, + text: 'Join a Group', + textSize: 14.sp, + onTap: () => CreateJoinGroupDialog.joinGroupDialog(context), + ), + ), + ], + ); + } - return Center( - child: Text(''), - ); - }, - ), + Widget _buildGroupList() { + return BlocBuilder( + builder: (context, state) { + if (state is ShimmerHomeState) { + return Center(child: ShimmerWidget.getPlaceholder()); + } else if (state is LoadedHomeState) { + if (state.groups.isEmpty) { + return _buildEmptyState(); + } + return _buildGroupsList(state.groups, state); + } + return const SizedBox(); + }, ); } Widget _buildEmptyState() { return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), child: Padding( padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 4.h), child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, children: [ - Lottie.asset( - 'animations/empty.json', - width: 50.w, - height: 25.h, - ), - SizedBox(height: 0.2.h), + Lottie.asset('animations/empty.json', width: 50.w, height: 25.h), + SizedBox(height: 3.h), Text( 'You haven\'t joined or created any group yet', textAlign: TextAlign.center, - style: TextStyle( - color: Colors.black87, - fontSize: 14.sp, - ), + style: TextStyle(fontSize: 14.sp), ), - SizedBox(height: 0.2.h), + SizedBox(height: 3.h), RichText( textAlign: TextAlign.center, text: TextSpan( - style: TextStyle( - color: Colors.black, - fontSize: 16.sp, - ), + style: TextStyle(fontSize: 16.sp, color: Colors.black), children: [ TextSpan( - text: 'Join', - style: TextStyle(fontWeight: FontWeight.bold), - ), + text: 'Join', + style: TextStyle(fontWeight: FontWeight.bold)), TextSpan(text: ' a Group or '), TextSpan( - text: 'Create', - style: TextStyle(fontWeight: FontWeight.bold), - ), + text: 'Create', + style: TextStyle(fontWeight: FontWeight.bold)), TextSpan(text: ' a new one!'), ], ), ), SizedBox(height: 3.h), IconButton( - icon: Icon(Icons.refresh, size: 6.w, color: Colors.teal), - onPressed: () { - _homeCubit.fetchUserGroups(); - }, + icon: Icon(Icons.refresh, size: 20.sp, color: Colors.teal), + onPressed: () => _homeCubit.fetchUserGroups(), ) ], ), @@ -411,55 +314,69 @@ class _HomeScreenState extends State { Widget _buildGroupsList(List groups, LoadedHomeState state) { return ListView.builder( - shrinkWrap: true, controller: _scrollController, - physics: AlwaysScrollableScrollPhysics(), - scrollDirection: Axis.vertical, + padding: EdgeInsets.only(top: 1.h), itemCount: groups.length + (state.isLoadingmore && !state.hasReachedEnd ? 1 : 0), - padding: EdgeInsets.symmetric(horizontal: 2.w, vertical: 1.h), itemBuilder: (context, index) { if (index == groups.length) { return Padding( padding: EdgeInsets.symmetric(vertical: 2.h), - child: Center(child: LinearProgressIndicator()), - ); - } else { - return GroupCard( - group: groups[index], + child: const Center(child: LinearProgressIndicator()), ); } + return GroupCard(group: groups[index]); }, ); } + Widget _buildSettingsPage() { + return Center( + child: Text( + 'Settings Page', + style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.bold), + ), + ); + } + + Widget _buildBottomNavigationBar() { + return BottomNavigationBar( + currentIndex: _currentIndex, + onTap: (index) => setState(() => _currentIndex = index), + items: [ + BottomNavigationBarItem( + icon: Icon(Icons.home, size: 20.sp), + label: 'Home', + ), + BottomNavigationBarItem( + icon: Icon(Icons.person, size: 20.sp), + label: 'Profile', + ), + BottomNavigationBarItem( + icon: Icon(Icons.hiking, size: 20.sp), + label: 'Hike', + ), + ], + ); + } + void _showLogoutDialog() { showDialog( context: context, builder: (context) => AlertDialog( - backgroundColor: Color(0xffFAFAFA), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.0), ), - contentPadding: EdgeInsets.symmetric( - horizontal: 5.w, - vertical: 3.h, - ), + contentPadding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 2.h), title: Text( 'Logout', - style: TextStyle( - fontSize: 18.sp, - fontWeight: FontWeight.w600, - ), + style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.w600), ), content: Text( 'Are you sure you want to logout?', - style: TextStyle( - fontSize: 14.sp, - color: kBlack, - ), + style: TextStyle(fontSize: 14.sp), ), - actions: [ + actions: [ Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -474,7 +391,7 @@ class _HomeScreenState extends State { HikeButton( buttonWidth: 25.w, buttonHeight: 5.h, - onTap: () async { + onTap: () { appRouter.replaceNamed('/auth'); localApi.deleteUser(); context.read().googleSignOut(); diff --git a/lib/presentation/home/widgets/group_card.dart b/lib/presentation/home/widgets/group_card.dart index 03eed20..295ec83 100644 --- a/lib/presentation/home/widgets/group_card.dart +++ b/lib/presentation/home/widgets/group_card.dart @@ -8,64 +8,37 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; class GroupCard extends StatelessWidget { final GroupEntity group; - GroupCard({super.key, required this.group}); + const GroupCard({super.key, required this.group}); @override Widget build(BuildContext context) { - print( - "GroupCard member IDs: ${group.members?.map((m) => m?.imageUrl).toList()}"); - String noMembers = group.members!.length.toString(); - String noBeacons = group.beacons!.length.toString(); + final memberCount = group.members?.length ?? 0; + final beaconCount = group.beacons?.length ?? 0; + final isMember = + group.members?.any((m) => m?.id == localApi.userModel.id) ?? false; + final isLeader = group.leader?.id == localApi.userModel.id; return GestureDetector( - onTap: () async { - bool isMember = false; - for (var member in group.members!) { - if (member!.id == localApi.userModel.id) { - isMember = true; - } - } - if (group.leader!.id == localApi.userModel.id || isMember) { - var homeCubit = locator(); - homeCubit.updateCurrentGroupId(group.id!); - appRouter.push(GroupScreenRoute(group: group)).then((value) { - homeCubit.resetGroupActivity(groupId: group.id!); - homeCubit.updateCurrentGroupId(null); - }); - } else { - HomeUseCase _homeUseCase = locator(); - DataState state = - await _homeUseCase.joinGroup(group.shortcode!); - if (state is DataSuccess && state.data != null) { - var homeCubit = locator(); - homeCubit.updateCurrentGroupId(group.id!); - appRouter.push(GroupScreenRoute(group: state.data!)).then((value) { - homeCubit.resetGroupActivity(groupId: group.id); - homeCubit.updateCurrentGroupId(null); - }); - } - } - }, + onTap: () => _handleGroupTap(context, isLeader || isMember), child: Container( - margin: const EdgeInsets.only(left: 10, right: 10, bottom: 14), - //padding: const EdgeInsets.all(12), + margin: EdgeInsets.symmetric(horizontal: 2.w, vertical: 1.h), decoration: BoxDecoration( - color: Colors.grey[100], + color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(12), ), child: Slidable( - key: ValueKey(group.id!.toString()), + key: ValueKey(group.id), endActionPane: ActionPane( - motion: ScrollMotion(), + motion: const ScrollMotion(), children: [ SlidableAction( - padding: EdgeInsets.symmetric(horizontal: 0), - onPressed: (context) { - context.read().changeShortCode(group); - }, + padding: EdgeInsets.zero, + onPressed: (context) => + context.read().changeShortCode(group), backgroundColor: Colors.teal, foregroundColor: Colors.white, icon: Icons.code, @@ -75,102 +48,17 @@ class GroupCard extends StatelessWidget { ], ), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + padding: EdgeInsets.symmetric(horizontal: 3.w, vertical: 2.h), child: Column( - mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - const Icon(Icons.groups_2_rounded, color: Colors.grey), - const SizedBox(width: 8), - Text( - '${group.title.toString().toUpperCase()} by ${group.leader!.name} ', - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - ), - ), - ], - ), - const SizedBox(height: 12), - Row( - children: [ - // Stack of profile circles - // noMembers != "0" - // ? SizedBox( - // width: 30 * group.members!.length.toDouble(), - // // 30 is the width of each profile circle - // height: 30, - // child: Stack( - // children: - // (group.members != null && group.members!.length > 3 - // ? group.members!.sublist(0, 3) - // : group.members ?? []) - // .map((member) { - // if (member != null) { - // return Positioned( - // left: group.members!.indexOf(member) * 20.0, - // child: _buildProfileCircle( - // member.id == localApi.userModel.id - // ? Colors.teal - // : Colors.grey, - // ), - // ); - // } else { - // return const SizedBox.shrink(); - // } - // }).toList(), - // ), - // ) - // : Container(), - Text( - 'Group has $noMembers members ', - style: TextStyle( - color: Colors.grey, - fontSize: 14, - ), - ), - ], - ), - const SizedBox(height: 8), - Text( - 'Group has $noBeacons ${noBeacons == '1' ? 'beacon' : 'beacons'} ', - style: TextStyle( - color: Colors.black87, - fontSize: 14, - ), - ), - const SizedBox(height: 4), - Row( - children: [ - Text( - 'Passkey: ${group.shortcode}', - style: TextStyle( - color: Colors.black87, - fontSize: 14, - ), - ), - const SizedBox(width: 8), - InkWell( - onTap: () { - Clipboard.setData( - ClipboardData(text: group.shortcode!)); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Shortcode copied!'), - duration: Duration(seconds: 2), - ), - ); - }, - child: Icon( - Icons.copy, - size: 17, - color: Colors.grey, - ), - ) - ], - ), + _buildGroupHeader(context), + SizedBox(height: 1.h), + _buildMemberInfo(context, memberCount), + SizedBox(height: 0.5.h), + _buildBeaconInfo(context, beaconCount), + SizedBox(height: 0.5.h), + _buildShortcodeRow(context), ], ), ), @@ -178,4 +66,106 @@ class GroupCard extends StatelessWidget { ), ); } + + Widget _buildGroupHeader(BuildContext context) { + return Row( + children: [ + Icon(Icons.groups_rounded, color: Colors.grey, size: 20.sp), + SizedBox(width: 2.w), + Expanded( + child: Text( + '${group.title?.toUpperCase() ?? ''} by ${group.leader?.name ?? ''}', + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16.sp, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ); + } + + Widget _buildMemberInfo(BuildContext context, int count) { + return Row( + children: [ + Text( + 'Group has $count ${count == 1 ? 'member' : 'members'}', + style: TextStyle( + color: Colors.grey, + fontSize: 14.sp, + ), + ), + ], + ); + } + + Widget _buildBeaconInfo(BuildContext context, int count) { + return Text( + 'Group has $count ${count == 1 ? 'beacon' : 'beacons'}', + style: TextStyle( + color: Colors.black87, + fontSize: 14.sp, + ), + ); + } + + Widget _buildShortcodeRow(BuildContext context) { + return Row( + children: [ + Text( + 'Passkey: ${group.shortcode ?? ''}', + style: TextStyle( + color: Colors.black87, + fontSize: 14.sp, + ), + ), + SizedBox(width: 2.w), + GestureDetector( + onTap: () => _copyShortcode(context), + child: Icon( + Icons.copy, + size: 16.sp, + color: Colors.grey, + ), + ), + ], + ); + } + + Future _handleGroupTap(BuildContext context, bool hasAccess) async { + if (hasAccess) { + await _navigateToGroupScreen(context, group); + } else { + await _joinAndNavigateToGroup(context); + } + } + + Future _navigateToGroupScreen( + BuildContext context, GroupEntity group) async { + final homeCubit = locator(); + homeCubit.updateCurrentGroupId(group.id!); + await appRouter.push(GroupScreenRoute(group: group)); + homeCubit.resetGroupActivity(groupId: group.id!); + homeCubit.updateCurrentGroupId(null); + } + + Future _joinAndNavigateToGroup(BuildContext context) async { + final homeUseCase = locator(); + final state = await homeUseCase.joinGroup(group.shortcode!); + + if (state is DataSuccess && state.data != null) { + await _navigateToGroupScreen(context, state.data!); + } + } + + void _copyShortcode(BuildContext context) { + Clipboard.setData(ClipboardData(text: group.shortcode!)); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Shortcode copied!'), + duration: Duration(seconds: 2), + ), + ); + } } diff --git a/lib/presentation/widgets/shimmer.dart b/lib/presentation/widgets/shimmer.dart index f50c717..f3c7d96 100644 --- a/lib/presentation/widgets/shimmer.dart +++ b/lib/presentation/widgets/shimmer.dart @@ -1,95 +1,71 @@ -import 'package:beacon/core/utils/constants.dart'; import 'package:flutter/material.dart'; -import 'package:skeleton_text/skeleton_text.dart'; +import 'package:shimmer/shimmer.dart'; class ShimmerWidget { static ListView getPlaceholder() { - final BorderRadius borderRadius = BorderRadius.circular(10.0); return ListView.builder( - scrollDirection: Axis.vertical, - physics: BouncingScrollPhysics(), - itemCount: 3, - padding: const EdgeInsets.all(8.0), - itemBuilder: (BuildContext context, int index) { - return Container( - margin: const EdgeInsets.symmetric( - vertical: 10.0, - horizontal: 10.0, - ), - height: 110, - decoration: BoxDecoration( - color: kBlue, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.circular(8.0), - boxShadow: [ - BoxShadow( - color: Colors.black26, - blurRadius: 10.0, - offset: Offset(0.0, 10.0), - ), - ], - ), - padding: - EdgeInsets.only(left: 16.0, right: 16.0, bottom: 10, top: 10), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 15.0, bottom: 10.0, right: 15.0), - child: ClipRRect( - borderRadius: borderRadius, - child: SkeletonAnimation( - child: Container( - height: 15.0, - decoration: BoxDecoration(color: shimmerSkeletonColor), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 30.0, bottom: 10.0), - child: ClipRRect( - borderRadius: borderRadius, - child: SkeletonAnimation( - child: Container( - height: 10.0, - decoration: BoxDecoration(color: shimmerSkeletonColor), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only( - left: 15.0, right: 45.0, bottom: 10.0), - child: ClipRRect( - borderRadius: BorderRadius.circular(10.0), - child: SkeletonAnimation( - child: Container( - height: 10.0, - decoration: BoxDecoration(color: shimmerSkeletonColor), - ), - ), - ), - ), - Padding( - padding: const EdgeInsets.only(left: 15.0, right: 60.0), - child: ClipRRect( - borderRadius: BorderRadius.circular(10.0), - child: SkeletonAnimation( + physics: const BouncingScrollPhysics(), + itemCount: 3, + padding: const EdgeInsets.all(8.0), + itemBuilder: (context, index) { + return _ShimmerCard(); + }, + ); + } +} + +class _ShimmerCard extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.symmetric(vertical: 10.0, horizontal: 10.0), + elevation: 5, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)), + child: Shimmer.fromColors( + baseColor: Colors.grey.shade300, + highlightColor: Colors.grey.shade100, + child: Container( + height: 110, + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + 4, + (index) => Padding( + padding: EdgeInsets.only(bottom: index == 3 ? 0 : 8.0), child: Container( - height: 10.0, - decoration: BoxDecoration(color: shimmerSkeletonColor), + height: 10, + width: _getLineWidth(index, context), + decoration: BoxDecoration( + color: Colors.grey, + borderRadius: BorderRadius.circular(4.0), + ), ), ), ), ), - ], - ), - ); - }); + ) + ], + ), + ), + ), + ); + } + + double _getLineWidth(int index, BuildContext context) { + switch (index) { + case 0: + return MediaQuery.of(context).size.width * 0.5; + case 1: + return MediaQuery.of(context).size.width * 0.4; + case 2: + return MediaQuery.of(context).size.width * 0.3; + default: + return MediaQuery.of(context).size.width * 0.2; + } } } From be5730575d0917bdaf796447496f6fd3e4a43a43 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Mon, 14 Jul 2025 12:56:37 +0530 Subject: [PATCH 09/19] fix auto router --- lib/config/router/router.dart | 4 +- lib/config/router/router.gr.dart | 75 ++- .../group/Advanceoptions_screen.dart | 12 - .../group/advance_options_screen.dart | 68 +++ .../group/widgets/create_join_dialog.dart | 9 +- .../group/widgets/group_widgets.dart | 533 +++++++++++------- pubspec.lock | 8 + pubspec.yaml | 1 + 8 files changed, 480 insertions(+), 230 deletions(-) delete mode 100644 lib/presentation/group/Advanceoptions_screen.dart create mode 100644 lib/presentation/group/advance_options_screen.dart diff --git a/lib/config/router/router.dart b/lib/config/router/router.dart index 2f197b4..ad8fa4f 100644 --- a/lib/config/router/router.dart +++ b/lib/config/router/router.dart @@ -6,6 +6,7 @@ import 'package:beacon/presentation/home/home_screen.dart'; import 'package:flutter/material.dart'; import 'package:beacon/presentation/auth/auth_screen.dart'; import 'package:beacon/presentation/group/group_screen.dart'; +import 'package:beacon/presentation/group/advance_options_screen.dart'; import 'package:beacon/presentation/hike/hike_screen.dart'; import 'package:beacon/domain/entities/beacon/beacon_entity.dart'; part 'router.gr.dart'; @@ -25,6 +26,7 @@ class AppRouter extends _$AppRouter { AutoRoute( page: VerificationScreenRoute.page, ), - + AutoRoute( + page: AdvancedOptionsScreenRoute.page, path: '/advanced-options'), ]; } diff --git a/lib/config/router/router.gr.dart b/lib/config/router/router.gr.dart index 994646b..e079dd1 100644 --- a/lib/config/router/router.gr.dart +++ b/lib/config/router/router.gr.dart @@ -15,6 +15,17 @@ abstract class _$AppRouter extends RootStackRouter { @override final Map pagesMap = { + AdvancedOptionsScreenRoute.name: (routeData) { + final args = routeData.argsAs(); + return AutoRoutePage( + routeData: routeData, + child: AdvancedOptionsScreen( + key: args.key, + title: args.title, + durationController: args.durationController, + ), + ); + }, AuthScreenRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, @@ -25,7 +36,10 @@ abstract class _$AppRouter extends RootStackRouter { final args = routeData.argsAs(); return AutoRoutePage( routeData: routeData, - child: GroupScreen(args.group), + child: GroupScreen( + args.group, + key: args.key, + ), ); }, HikeScreenRoute.name: (routeData) { @@ -60,6 +74,50 @@ abstract class _$AppRouter extends RootStackRouter { }; } +/// generated route for +/// [AdvancedOptionsScreen] +class AdvancedOptionsScreenRoute + extends PageRouteInfo { + AdvancedOptionsScreenRoute({ + Key? key, + required String title, + required TextEditingController durationController, + List? children, + }) : super( + AdvancedOptionsScreenRoute.name, + args: AdvancedOptionsScreenRouteArgs( + key: key, + title: title, + durationController: durationController, + ), + initialChildren: children, + ); + + static const String name = 'AdvancedOptionsScreenRoute'; + + static const PageInfo page = + PageInfo(name); +} + +class AdvancedOptionsScreenRouteArgs { + const AdvancedOptionsScreenRouteArgs({ + this.key, + required this.title, + required this.durationController, + }); + + final Key? key; + + final String title; + + final TextEditingController durationController; + + @override + String toString() { + return 'AdvancedOptionsScreenRouteArgs{key: $key, title: $title, durationController: $durationController}'; + } +} + /// generated route for /// [AuthScreen] class AuthScreenRoute extends PageRouteInfo { @@ -79,10 +137,14 @@ class AuthScreenRoute extends PageRouteInfo { class GroupScreenRoute extends PageRouteInfo { GroupScreenRoute({ required GroupEntity group, + Key? key, List? children, }) : super( GroupScreenRoute.name, - args: GroupScreenRouteArgs(group: group), + args: GroupScreenRouteArgs( + group: group, + key: key, + ), initialChildren: children, ); @@ -93,13 +155,18 @@ class GroupScreenRoute extends PageRouteInfo { } class GroupScreenRouteArgs { - const GroupScreenRouteArgs({required this.group}); + const GroupScreenRouteArgs({ + required this.group, + this.key, + }); final GroupEntity group; + final Key? key; + @override String toString() { - return 'GroupScreenRouteArgs{group: $group}'; + return 'GroupScreenRouteArgs{group: $group, key: $key}'; } } diff --git a/lib/presentation/group/Advanceoptions_screen.dart b/lib/presentation/group/Advanceoptions_screen.dart deleted file mode 100644 index c989da6..0000000 --- a/lib/presentation/group/Advanceoptions_screen.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:auto_route/auto_route.dart'; - -@RoutePage() -class AdvanceoptionsScreen extends StatelessWidget { - const AdvanceoptionsScreen({super.key}); - - @override - Widget build(BuildContext context) { - return const Placeholder(); - } -} diff --git a/lib/presentation/group/advance_options_screen.dart b/lib/presentation/group/advance_options_screen.dart new file mode 100644 index 0000000..f83a4c2 --- /dev/null +++ b/lib/presentation/group/advance_options_screen.dart @@ -0,0 +1,68 @@ +import 'package:auto_route/annotations.dart'; +import 'package:duration_picker/duration_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; + +@RoutePage() +class AdvancedOptionsScreen extends StatelessWidget { + String title; + TextEditingController durationController; + + // Chang + AdvancedOptionsScreen( + {super.key, required this.title, required this.durationController}); + + @override + Widget build(BuildContext context) { + DateTime? startDate = DateTime.now(); + TimeOfDay? startTime = TimeOfDay( + hour: TimeOfDay.now().hour, minute: TimeOfDay.now().minute + 1); + Duration? duration = Duration(minutes: 5); + return Scaffold( + body: Form( + child: Container( + height: 12.h, + child: Padding( + padding: const EdgeInsets.all(4.0), + child: InkWell( + onTap: () async { + duration = await showDurationPicker( + context: context, + initialTime: duration ?? Duration(minutes: 5), + ); + if (duration == null) return; + if (duration!.inHours != 0 && duration!.inMinutes != 0) { + durationController.text = + '${duration!.inHours.toString()} hour ${(duration!.inMinutes % 60)} minutes'; + } else if (duration!.inMinutes != 0) { + durationController.text = + '${duration!.inMinutes.toString()} minutes'; + } + }, + child: TextFormField( + enabled: false, + controller: durationController, + decoration: InputDecoration( + fillColor: Colors.grey[200], + filled: true, + contentPadding: + EdgeInsets.symmetric(horizontal: 16, vertical: 8), + border: InputBorder.none, + alignLabelWithHint: true, + errorStyle: TextStyle(color: Colors.red[800]), + floatingLabelBehavior: FloatingLabelBehavior.always, + labelText: 'Duration', + labelStyle: TextStyle( + fontSize: 16, color: Theme.of(context).primaryColor), + hintStyle: TextStyle(fontSize: 14, color: Colors.grey), + hintText: 'Enter duration of hike', + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none), + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/presentation/group/widgets/create_join_dialog.dart b/lib/presentation/group/widgets/create_join_dialog.dart index 846ac55..9331166 100644 --- a/lib/presentation/group/widgets/create_join_dialog.dart +++ b/lib/presentation/group/widgets/create_join_dialog.dart @@ -9,6 +9,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; +import 'package:beacon/config/router/router.dart'; class CreateJoinGroupDialog { static GlobalKey _groupKey = GlobalKey(); @@ -541,7 +542,13 @@ class CreateJoinBeaconDialog { ), GestureDetector( - onTap: () => {}, + onTap: () => { + context.router.push(AdvancedOptionsScreenRoute( + durationController: _durationController, + title: title)), + print("advanced options clicked"), + print(_durationController.text), + }, child: Text( "advanced options", style: TextStyle( diff --git a/lib/presentation/group/widgets/group_widgets.dart b/lib/presentation/group/widgets/group_widgets.dart index 6c36742..ddddea5 100644 --- a/lib/presentation/group/widgets/group_widgets.dart +++ b/lib/presentation/group/widgets/group_widgets.dart @@ -2,6 +2,7 @@ import 'dart:developer'; import 'package:beacon/core/utils/validators.dart'; import 'package:beacon/domain/entities/beacon/beacon_entity.dart'; +import 'package:beacon/domain/entities/user/user_entity.dart'; import 'package:beacon/presentation/group/cubit/members_cubit/members_cubit.dart'; import 'package:beacon/presentation/group/cubit/members_cubit/members_state.dart'; import 'package:beacon/locator.dart'; @@ -30,97 +31,91 @@ class GroupWidgetUtils { } static void showMembers(BuildContext context) { - // Dialog for filtering beacons locator().loadMembers(); + showDialog( context: context, + barrierDismissible: true, builder: (context) { - bool isSmallSized = 100.h < 800; + final bool isSmallSized = 100.h < 800; + final double dialogHeight = isSmallSized ? 30.h : 25.h; + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + backgroundColor: Colors.white, title: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.group), - Gap(5), + Icon(Icons.group, color: Colors.black, size: 30), + Gap(8), Text( 'Members', - textAlign: TextAlign.center, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.black, + ), ) ], ), - content: Container( - height: isSmallSized ? 30.h : 25.h, - width: isSmallSized ? 200 : 300, - child: BlocConsumer( - listener: (context, state) { - if (state is LoadedMemberState && state.message != null) { - utils.showSnackBar(state.message!, context); - } - }, - builder: (context, state) { - if (state is LoadingMemberState) { - return ShimmerWidget.getPlaceholder(); - } else if (state is LoadedMemberState) { - var members = state.members; - return members!.isEmpty - ? Container( - child: - Text('Please check your internet connection'), + content: SizedBox( + height: dialogHeight, + width: isSmallSized ? 280 : 350, + child: BlocConsumer( + listener: (context, state) { + if (state is LoadedMemberState && state.message != null) { + utils.showSnackBar(state.message!, context); + } + }, + builder: (context, state) { + if (state is LoadingMemberState) { + return ShimmerWidget.getPlaceholder(); + } else if (state is LoadedMemberState) { + var members = state.members; + + if (members == null || members.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.wifi_off, + size: 40, color: Colors.grey.shade500), + const SizedBox(height: 8), + Text( + 'No members found.\nCheck your internet connection.', + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.grey.shade600, + fontSize: 14, + ), ) - : ListView.builder( - shrinkWrap: true, - itemCount: members.length, - itemBuilder: (context, index) { - bool isLeader = - localApi.userModel.id! == members[0].id!; - return Container( - margin: EdgeInsets.symmetric(vertical: 10), - decoration: BoxDecoration( - color: kLightBlue, - borderRadius: - BorderRadius.all(Radius.circular(10))), - child: ListTile( - leading: index == 0 - ? Icon( - Icons.star, - color: kYellow, - ) - : Icon(Icons.person), - trailing: index == 0 - ? Text('Leader') - : isLeader - ? IconButton( - onPressed: () { - context - .read() - .removeMember( - members[index].id ?? - ''); - }, - icon: Icon( - Icons.person_remove_alt_1, - weight: 20, - color: const Color.fromARGB( - 255, 215, 103, 95), - )) - : null, - subtitle: localApi.userModel.id! == - members[index].id! - ? Text( - '(YOU)', - style: TextStyle(fontSize: 12), - ) - : null, - title: - Text(members[index].name ?? 'Anonymous'), - ), - ); - }, - ); + ], + ), + ); } - return Container(); - }, - )), + + return ListView.separated( + itemCount: members.length, + separatorBuilder: (_, __) => Gap(8), + itemBuilder: (context, index) { + final member = members[index]; + final isCurrentUser = localApi.userModel.id == member.id; + final isLeader = + localApi.userModel.id == members.first.id; + return _MemberTile( + member: member, + isLeader: index == 0, + canRemove: isLeader && index != 0, + isCurrentUser: isCurrentUser, + ); + }, + ); + } + return SizedBox.shrink(); + }, + ), + ), ); }, ); @@ -141,14 +136,15 @@ class GroupWidgetUtils { ), actions: [ HikeButton( - buttonHeight: 2.5.h, - buttonWidth: 8.w, + buttonHeight: 5.h, + buttonWidth: 20.w, onTap: () => appRouter.maybePop(false), text: 'No', + isDotted: true, ), HikeButton( - buttonHeight: 2.5.h, - buttonWidth: 8.w, + buttonHeight: 5.h, + buttonWidth: 20.w, onTap: () => appRouter.maybePop(true), text: 'Yes', ), @@ -174,130 +170,136 @@ class GroupWidgetUtils { } static Future reScheduleHikeDialog( - BuildContext context, BeaconEntity beacon) { - var startsAt = beacon.startsAt!; - var expiresAt = beacon.expiresAt!; - var previousStartDate = DateTime.fromMillisecondsSinceEpoch(startsAt); - var previousExpireDate = DateTime.fromMillisecondsSinceEpoch(expiresAt); - + BuildContext context, + BeaconEntity beacon, + ) { + var previousStartDate = + DateTime.fromMillisecondsSinceEpoch(beacon.startsAt!); + var previousExpireDate = + DateTime.fromMillisecondsSinceEpoch(beacon.expiresAt!); var previousDuration = previousExpireDate.difference(previousStartDate); - DateTime? newstartDate = previousStartDate; - TextEditingController _dateController = TextEditingController( - text: DateFormat('yyyy-MM-dd').format(previousStartDate)); + DateTime? newStartDate = previousStartDate; + var _dateController = TextEditingController( + text: DateFormat('yyyy-MM-dd').format(previousStartDate), + ); TimeOfDay? startTime = TimeOfDay( - hour: previousStartDate.hour, minute: previousStartDate.minute); - TextEditingController _startTimeController = TextEditingController( - text: DateFormat('HH:mm').format(previousStartDate)); + hour: previousStartDate.hour, + minute: previousStartDate.minute, + ); + var _startTimeController = TextEditingController( + text: DateFormat('hh:mm a').format(previousStartDate), + ); Duration? duration = previousDuration; - TextEditingController _durationController = TextEditingController( - text: previousDuration.inMinutes < 60 - ? '${previousDuration.inMinutes} minutes' - : '${previousDuration.inHours} hours'); + var _durationController = TextEditingController( + text: previousDuration.inHours > 0 + ? '${previousDuration.inHours} hour${previousDuration.inHours > 1 ? 's' : ''} ${previousDuration.inMinutes % 60} minutes' + : '${previousDuration.inMinutes} minutes', + ); - GlobalKey _createFormKey = GlobalKey(); + final _formKey = GlobalKey(); bool isSmallSized = 100.h < 800; - bool isExpired = DateTime.now() - .isAfter(DateTime.fromMillisecondsSinceEpoch(beacon.expiresAt!)); + bool isExpired = DateTime.now().isAfter(previousExpireDate); + return showDialog( context: context, builder: (context) => GestureDetector( - onTap: () => FocusManager.instance.primaryFocus?.unfocus(), + onTap: () => FocusScope.of(context).unfocus(), child: Dialog( + backgroundColor: Colors.grey[100], shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), + borderRadius: BorderRadius.circular(16.0), ), child: SingleChildScrollView( child: Form( - key: _createFormKey, + key: _formKey, child: Container( - height: isSmallSized ? 68.h : 62.h, + height: isSmallSized ? 55.h : 50.h, child: Padding( padding: - const EdgeInsets.symmetric(horizontal: 32, vertical: 16), + const EdgeInsets.symmetric(horizontal: 20, vertical: 16), child: Column( children: [ Text( isExpired ? 'Activate Hike' : 'Reschedule Hike', - style: TextStyle(fontSize: 30), + style: TextStyle( + fontSize: 24, + color: Colors.black, + fontWeight: FontWeight.bold, + ), ), SizedBox(height: 2.h), - // start date field + + /// Start Date Container( - height: isSmallSized ? 14.h : 12.h, + height: isSmallSized ? 11.h : 9.h, child: Padding( padding: const EdgeInsets.all(4.0), child: InkWell( onTap: () async { - newstartDate = await showDatePicker( + newStartDate = await showDatePicker( context: context, - initialDate: newstartDate ?? DateTime.now(), - firstDate: newstartDate ?? DateTime.now(), + initialDate: newStartDate ?? DateTime.now(), + firstDate: DateTime.now(), lastDate: DateTime(2100), - // builder: (context, child) => Theme( - // data: ThemeData().copyWith( - // textTheme: Theme.of(context).textTheme, - // colorScheme: ColorScheme.light( - // primary: kLightBlue, - // onPrimary: Colors.grey, - // surface: kBlue, - // ), - // ), - // child: child!), ); - if (newstartDate == null) return; - _dateController.text = DateFormat('yyyy-MM-dd') - .format(newstartDate!); + if (newStartDate != null) { + _dateController.text = DateFormat('yyyy-MM-dd') + .format(newStartDate!); + } }, child: TextFormField( validator: (value) => Validator.validateDate(value), controller: _dateController, enabled: false, - onEditingComplete: () {}, decoration: InputDecoration( - border: InputBorder.none, - hintText: 'Choose Start Date', - labelStyle: TextStyle( - fontSize: labelsize, color: kYellow), - hintStyle: TextStyle( - fontSize: hintsize, color: hintColor), - labelText: 'Start Date', - alignLabelWithHint: true, - floatingLabelBehavior: - FloatingLabelBehavior.always, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none), + fillColor: Colors.grey[200], + filled: true, + contentPadding: EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + border: InputBorder.none, + hintText: 'Choose Start Date', + labelStyle: TextStyle( + fontSize: 16, + color: Theme.of(context).primaryColor, + ), + hintStyle: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + labelText: 'Start Date', + floatingLabelBehavior: + FloatingLabelBehavior.always, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), ), ), ), - color: kLightBlue, ), - SizedBox(height: 2.h), - // Start Time Field + + /// Start Time Container( - height: isSmallSized ? 14.h : 12.h, + height: isSmallSized ? 11.h : 9.h, child: Padding( padding: const EdgeInsets.all(4.0), child: InkWell( onTap: () async { startTime = await showTimePicker( - context: context, - initialTime: startTime ?? - TimeOfDay( - hour: DateTime.now().hour, - minute: DateTime.now().minute + 1)); + context: context, + initialTime: startTime ?? + TimeOfDay( + hour: DateTime.now().hour, + minute: DateTime.now().minute + 1, + ), + ); if (startTime != null) { - if (startTime!.minute < 10) { - _startTimeController.text = - '${startTime!.hour}:0${startTime!.minute} ${startTime!.period == DayPeriod.am ? 'AM' : 'PM'}'; - } else { - _startTimeController.text = - '${startTime!.hour}:${startTime!.minute} ${startTime!.period == DayPeriod.am ? 'AM' : 'PM'}'; - } + _startTimeController.text = + startTime!.format(context); } }, child: TextFormField( @@ -305,31 +307,35 @@ class GroupWidgetUtils { value, _dateController.text), controller: _startTimeController, enabled: false, - onEditingComplete: () {}, decoration: InputDecoration( + fillColor: Colors.grey[200], + filled: true, + contentPadding: EdgeInsets.symmetric( + horizontal: 16, vertical: 8), border: InputBorder.none, - alignLabelWithHint: true, - errorStyle: TextStyle(color: Colors.red[800]), - floatingLabelBehavior: - FloatingLabelBehavior.always, - labelText: 'Start Time', + hintText: 'Choose Start Time', labelStyle: TextStyle( - fontSize: labelsize, color: kYellow), + fontSize: 16, + color: Theme.of(context).primaryColor, + ), hintStyle: TextStyle( - fontSize: hintsize, color: hintColor), - hintText: 'Choose start time', + fontSize: 14, + color: Colors.grey[600], + ), + labelText: 'Start Time', + floatingLabelBehavior: + FloatingLabelBehavior.always, focusedBorder: InputBorder.none, enabledBorder: InputBorder.none, ), ), ), ), - color: kLightBlue, ), - SizedBox(height: 2.h), - // // Duration Field + + /// Duration Container( - height: isSmallSized ? 14.h : 12.h, + height: isSmallSized ? 11.h : 9.h, child: Padding( padding: const EdgeInsets.all(4.0), child: InkWell( @@ -338,77 +344,97 @@ class GroupWidgetUtils { context: context, initialTime: duration ?? Duration(minutes: 5), ); - if (duration == null) return; - if (duration!.inHours != 0 && - duration!.inMinutes != 0) { - _durationController.text = - '${duration!.inHours.toString()} hour ${(duration!.inMinutes % 60)} minutes'; - } else if (duration!.inMinutes != 0) { - _durationController.text = - '${duration!.inMinutes.toString()} minutes'; + if (duration != null) { + if (duration!.inHours > 0) { + _durationController.text = + '${duration!.inHours} hour${duration!.inHours > 1 ? 's' : ''} ${duration!.inMinutes % 60} minutes'; + } else { + _durationController.text = + '${duration!.inMinutes} minutes'; + } } }, child: TextFormField( enabled: false, controller: _durationController, validator: (value) => - Validator.validateDuration(value), + Validator.validateDuration(value.toString()), decoration: InputDecoration( - border: InputBorder.none, - alignLabelWithHint: true, - errorStyle: TextStyle(color: Colors.red[800]), - floatingLabelBehavior: - FloatingLabelBehavior.always, - labelText: 'Duration', - labelStyle: TextStyle( - fontSize: labelsize, color: kYellow), - hintStyle: TextStyle( - fontSize: hintsize, color: hintColor), - hintText: 'Enter duration of hike', - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none), + fillColor: Colors.grey[200], + filled: true, + contentPadding: EdgeInsets.symmetric( + horizontal: 16, vertical: 8), + border: InputBorder.none, + hintText: 'Enter duration of hike', + labelStyle: TextStyle( + fontSize: 16, + color: Theme.of(context).primaryColor, + ), + hintStyle: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + labelText: 'Duration', + floatingLabelBehavior: + FloatingLabelBehavior.always, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), ), ), ), - color: kLightBlue, ), - SizedBox(height: 2.h), + SizedBox(height: 1.h), + + /// Button Flexible( flex: 2, - child: HikeButton( - text: 'Update', - textSize: 18.0, - textColor: Colors.white, - buttonColor: kYellow, - onTap: () async { - if (!_createFormKey.currentState!.validate()) - return; + child: ElevatedButton( + onPressed: () async { + if (_formKey.currentState!.validate()) { DateTime startsAt = DateTime( - newstartDate!.year, - newstartDate!.month, - newstartDate!.day, - startTime!.hour, - startTime!.minute); + newStartDate!.year, + newStartDate!.month, + newStartDate!.day, + startTime!.hour, + startTime!.minute, + ); final newStartsAt = startsAt.millisecondsSinceEpoch; - - final newExpiresAT = startsAt - .copyWith( - hour: startsAt.hour + duration!.inHours, - minute: - startsAt.minute + duration!.inMinutes) + final newExpiresAt = startsAt + .add(duration!) .millisecondsSinceEpoch; context.read().rescheduleHike( - newExpiresAT, newStartsAt, beacon.id!); + newExpiresAt, newStartsAt, beacon.id!); + _dateController.clear(); _startTimeController.clear(); _durationController.clear(); + appRouter.maybePop(); - // } - }), - ), + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).primaryColor, + minimumSize: Size(160, 48), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + padding: EdgeInsets.symmetric( + horizontal: 24, vertical: 12), + ), + child: Text( + 'Update', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), + ), + ), + ) ], ), ), @@ -552,3 +578,86 @@ class GroupWidgetUtils { ); } } + +Widget _buildProfileCircle(String? url) { + return Container( + width: 40, + height: 40, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey[300], + border: Border.all(color: Colors.white, width: 2), + image: DecorationImage( + image: NetworkImage(url!), + fit: BoxFit.cover, + ), + ), + ); +} + +class _MemberTile extends StatelessWidget { + final UserEntity member; + final bool isLeader; + final bool canRemove; + final bool isCurrentUser; + + const _MemberTile({ + required this.member, + required this.isLeader, + required this.canRemove, + required this.isCurrentUser, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: kLightBlue, + borderRadius: BorderRadius.circular(10), + ), + child: ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + leading: _buildProfileCircle(member.imageUrl), + title: Text( + member.name ?? 'Anonymous', + style: TextStyle( + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + ), + subtitle: isCurrentUser + ? Text( + '(YOU)', + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade700, + ), + ) + : null, + trailing: isLeader + ? Text( + 'Leader', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Colors.green.shade700, + ), + ) + : canRemove + ? IconButton( + onPressed: () { + context + .read() + .removeMember(member.id ?? ''); + }, + icon: Icon( + Icons.person_remove_alt_1, + color: Colors.red.shade400, + ), + ) + : null, + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index a5e87fc..7c30c06 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -54,6 +54,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.3.0" + auto_route_generator: + dependency: "direct dev" + description: + name: auto_route_generator + sha256: a21d7a936c917488653c972f62d884d8adcf8c5d37acc7cd24da33cf784546c0 + url: "https://pub.dev" + source: hosted + version: "8.1.0" bloc: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6caf6ce..7f7183b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -72,6 +72,7 @@ dependencies: dotted_border: ^2.1.0 shimmer: ^3.0.0 dev_dependencies: + auto_route_generator: build_runner: ^2.1.2 flutter_launcher_icons: ^0.13.1 flutter_test: From a93d90ca55a009c6dee7ed666eca5f962a5ebe39 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Mon, 14 Jul 2025 14:36:43 +0530 Subject: [PATCH 10/19] add forecast --- lib/config/router/router.gr.dart | 36 +- .../group/advance_options_screen.dart | 866 +++++++++++++++++- .../group/widgets/create_join_dialog.dart | 12 +- 3 files changed, 858 insertions(+), 56 deletions(-) diff --git a/lib/config/router/router.gr.dart b/lib/config/router/router.gr.dart index e079dd1..a1e4318 100644 --- a/lib/config/router/router.gr.dart +++ b/lib/config/router/router.gr.dart @@ -21,8 +21,12 @@ abstract class _$AppRouter extends RootStackRouter { routeData: routeData, child: AdvancedOptionsScreen( key: args.key, - title: args.title, durationController: args.durationController, + title: args.title, + isScheduled: args.isScheduled, + startDate: args.startDate, + startTime: args.startTime, + groupId: args.groupId, ), ); }, @@ -80,15 +84,23 @@ class AdvancedOptionsScreenRoute extends PageRouteInfo { AdvancedOptionsScreenRoute({ Key? key, - required String title, required TextEditingController durationController, + required String title, + required bool isScheduled, + DateTime? startDate, + TimeOfDay? startTime, + required String groupId, List? children, }) : super( AdvancedOptionsScreenRoute.name, args: AdvancedOptionsScreenRouteArgs( key: key, - title: title, durationController: durationController, + title: title, + isScheduled: isScheduled, + startDate: startDate, + startTime: startTime, + groupId: groupId, ), initialChildren: children, ); @@ -102,19 +114,31 @@ class AdvancedOptionsScreenRoute class AdvancedOptionsScreenRouteArgs { const AdvancedOptionsScreenRouteArgs({ this.key, - required this.title, required this.durationController, + required this.title, + required this.isScheduled, + this.startDate, + this.startTime, + required this.groupId, }); final Key? key; + final TextEditingController durationController; + final String title; - final TextEditingController durationController; + final bool isScheduled; + + final DateTime? startDate; + + final TimeOfDay? startTime; + + final String groupId; @override String toString() { - return 'AdvancedOptionsScreenRouteArgs{key: $key, title: $title, durationController: $durationController}'; + return 'AdvancedOptionsScreenRouteArgs{key: $key, durationController: $durationController, title: $title, isScheduled: $isScheduled, startDate: $startDate, startTime: $startTime, groupId: $groupId}'; } } diff --git a/lib/presentation/group/advance_options_screen.dart b/lib/presentation/group/advance_options_screen.dart index f83a4c2..290cb3c 100644 --- a/lib/presentation/group/advance_options_screen.dart +++ b/lib/presentation/group/advance_options_screen.dart @@ -1,66 +1,838 @@ import 'package:auto_route/annotations.dart'; +import 'package:beacon/locator.dart'; +import 'package:beacon/presentation/group/cubit/group_cubit/group_cubit.dart'; +import 'package:beacon/presentation/hike/services/geoapify_service.dart'; import 'package:duration_picker/duration_picker.dart'; import 'package:flutter/material.dart'; +import 'package:geolocator/geolocator.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import 'package:beacon/data/models/landmark/location_suggestion.dart'; @RoutePage() -class AdvancedOptionsScreen extends StatelessWidget { - String title; +class AdvancedOptionsScreen extends StatefulWidget { TextEditingController durationController; + String title; + bool isScheduled; + DateTime? startDate; + TimeOfDay? startTime; + String groupId; + + AdvancedOptionsScreen({ + super.key, + required this.durationController, + required this.title, + required this.isScheduled, + this.startDate, + this.startTime, + required this.groupId, + }); + + @override + State createState() => _AdvancedOptionsScreenState(); +} + +class _AdvancedOptionsScreenState extends State { + Duration? duration = Duration(minutes: 5); + Map? weatherData; + bool isLoadingWeather = false; + String? weatherError; + final GeoapifyService _geoapifyService = GeoapifyService(); + final TextEditingController _locationController = TextEditingController(); + List _locationSuggestions = []; + bool _showLocationSuggestions = false; + LocationSuggestion? _selectedLocation; + DateTime? startDate = DateTime.now(); + TimeOfDay? startTime = + TimeOfDay(hour: TimeOfDay.now().hour, minute: TimeOfDay.now().minute + 1); + + @override + void initState() { + super.initState(); + _fetchWeatherForCurrentLocation(); + } + + Future getCurrentLocation() async { + bool serviceEnabled; + LocationPermission permission; + + // Check if location services are enabled + serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + // Location services are not enabled + return Future.error('Location services are disabled.'); + } + + // Check for permission + permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + // Permissions are denied + return Future.error('Location permissions are denied.'); + } + } + + if (permission == LocationPermission.deniedForever) { + // Permissions are permanently denied + return Future.error( + 'Location permissions are permanently denied. Cannot request permissions.'); + } + + // Get current location + return await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.high); + } + + Future _fetchWeatherForCurrentLocation() async { + Position? position = await getCurrentLocation(); + + await _fetchWeatherData( + position?.latitude ?? 0.0, // Default to 0.0 if position is null + position?.longitude ?? 0.0, // Default to 0.0 if position is null + ); // Default to 0.0 if parsing fails + } + + Future _fetchWeatherData(double lat, double lon) async { + setState(() { + isLoadingWeather = true; + weatherError = null; + }); + + try { + const String apiKey = '03fe30be078c0bfb823d954404de6a6b'; + + String apiUrl; + if (widget.isScheduled && widget.startDate != null) { + // Use forecast API for scheduled hikes + apiUrl = + 'https://api.openweathermap.org/data/2.5/forecast?lat=$lat&lon=$lon&appid=$apiKey&units=metric'; + } else { + // Use current weather API for immediate hikes + apiUrl = + 'https://api.openweathermap.org/data/2.5/weather?lat=$lat&lon=$lon&appid=$apiKey&units=metric'; + } + + final response = await http.get(Uri.parse(apiUrl)); + + if (response.statusCode == 200) { + final data = jsonDecode(response.body); + if (widget.isScheduled && widget.startDate != null) { + // Process forecast data to find weather for the scheduled date + final forecastWeather = _extractWeatherForScheduledDate(data); + setState(() { + weatherData = forecastWeather; + isLoadingWeather = false; + }); + } else { + // Use current weather data + setState(() { + weatherData = data; + isLoadingWeather = false; + }); + } + } else { + throw Exception('Failed to load weather data: ${response.statusCode}'); + } + } catch (e) { + print('Weather fetch error: $e'); + // Fallback to mock data if API fails + //await _loadMockWeatherData(); + } + } + + Map _extractWeatherForScheduledDate( + Map forecastData) { + final scheduledDate = widget.startDate!; + final scheduledTime = widget.startTime; + + // Convert scheduled date to timestamp + DateTime targetDateTime; + if (scheduledTime != null) { + targetDateTime = DateTime( + scheduledDate.year, + scheduledDate.month, + scheduledDate.day, + scheduledTime.hour, + scheduledTime.minute, + ); + } else { + targetDateTime = DateTime( + scheduledDate.year, + scheduledDate.month, + scheduledDate.day, + 12, // Default to noon if no time specified + 0, + ); + } + + final List forecasts = forecastData['list']; + Map? closestForecast; + Duration smallestDifference = Duration(days: 365); + + // Find the forecast closest to the scheduled time + for (var forecast in forecasts) { + final forecastTime = DateTime.fromMillisecondsSinceEpoch( + forecast['dt'] * 1000, + isUtc: true, + ).toLocal(); + + final difference = (forecastTime.difference(targetDateTime)).abs(); + if (difference < smallestDifference) { + smallestDifference = difference; + closestForecast = forecast; + } + } + + if (closestForecast != null) { + // Transform forecast data to match current weather API format + return { + 'main': closestForecast['main'], + 'weather': closestForecast['weather'], + 'wind': closestForecast['wind'], + 'visibility': closestForecast['visibility'] ?? 10000, + 'name': _selectedLocation?.name ?? + forecastData['city']['name'] ?? + 'Selected Location', + 'dt': closestForecast['dt'], + 'isScheduled': true, + 'scheduledDate': targetDateTime.toIso8601String(), + }; + } + + // Fallback to first forecast if no suitable match found + return { + 'main': forecasts[0]['main'], + 'weather': forecasts[0]['weather'], + 'wind': forecasts[0]['wind'], + 'visibility': forecasts[0]['visibility'] ?? 10000, + 'name': _selectedLocation?.name ?? + forecastData['city']['name'] ?? + 'Selected Location', + 'dt': forecasts[0]['dt'], + 'isScheduled': true, + 'scheduledDate': targetDateTime.toIso8601String(), + }; + } + + Future _loadMockWeatherData() async { + await Future.delayed(Duration(seconds: 1)); + + setState(() { + weatherData = { + 'main': { + 'temp': 22.5, + 'feels_like': 24.0, + 'humidity': 65, + }, + 'weather': [ + {'main': 'Clear', 'description': 'clear sky', 'icon': '01d'} + ], + 'wind': { + 'speed': 3.2, + }, + 'visibility': 10000, + 'name': _selectedLocation?.name ?? 'Your Location', + 'isScheduled': widget.isScheduled, + 'scheduledDate': widget.isScheduled && widget.startDate != null + ? widget.startDate!.toIso8601String() + : null, + }; + isLoadingWeather = false; + }); + } + + Future _searchLocations(String query) async { + if (query.isEmpty) { + setState(() { + _locationSuggestions = []; + _showLocationSuggestions = false; + }); + return; + } + + try { + final suggestions = await _geoapifyService.getLocationSuggestions(query); + setState(() { + _locationSuggestions = suggestions; + _showLocationSuggestions = true; + }); + } catch (e) { + print('Location search error: $e'); + setState(() { + _locationSuggestions = []; + _showLocationSuggestions = false; + }); + } + } + + void _selectLocation(LocationSuggestion location) { + setState(() { + _selectedLocation = location; + _locationController.text = location.name; + _showLocationSuggestions = false; + }); + + // Fetch weather for selected location + _fetchWeatherData(location.latitude, location.longitude); + } + + String _getWeatherTitle() { + if (weatherData == null) return 'Weather'; + + if (widget.isScheduled && widget.startDate != null) { + final scheduledDate = widget.startDate!; + final now = DateTime.now(); + final difference = scheduledDate.difference(now).inDays; + + if (difference == 0) { + return 'Today\'s Weather'; + } else if (difference == 1) { + return 'Tomorrow\'s Weather'; + } else { + return 'Weather Forecast \n (${difference} days from now)'; + } + } + + return 'Current Weather'; + } + + Widget _buildAppBar(context) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(Icons.arrow_back, color: Colors.grey), + onPressed: () => context.router.maybePop(), + ), + Image.asset('images/beacon_logo.png', height: 4.h), + IconButton( + icon: const Icon(Icons.power_settings_new, color: Colors.grey), + onPressed: () {}, + ), + ], + ); + } + + Widget _buildWeatherCard() { + if (isLoadingWeather) { + return Card( + elevation: 4, + margin: EdgeInsets.all(16), + child: Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + CircularProgressIndicator(), + // SizedBox(width: 16), + Text(widget.isScheduled + ? 'Loading weather forecast...' + : 'Loading weather conditions...'), + ], + ), + ), + ); + } + + if (weatherError != null) { + return Card( + elevation: 4, + margin: EdgeInsets.all(16), + child: Padding( + padding: EdgeInsets.all(16), + child: Row( + children: [ + Icon(Icons.error, color: Colors.red), + SizedBox(width: 16), + Expanded(child: Text(weatherError!)), + IconButton( + icon: Icon(Icons.refresh), + onPressed: () { + if (_selectedLocation != null) { + _fetchWeatherData(_selectedLocation!.latitude, + _selectedLocation!.longitude); + } else { + _fetchWeatherForCurrentLocation(); + } + }, + ), + ], + ), + ), + ); + } + + if (weatherData == null) return SizedBox.shrink(); + + final temp = weatherData!['main']['temp']; + final feelsLike = weatherData!['main']['feels_like']; + final humidity = weatherData!['main']['humidity']; + final windSpeed = weatherData!['wind']['speed']; + final description = weatherData!['weather'][0]['description']; + final location = weatherData!['name']; + + return Card( + elevation: 4, + margin: EdgeInsets.all(16), + child: Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + _getWeatherTitle(), + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.bold, + color: Theme.of(context).primaryColor, + ), + ), + IconButton( + icon: Icon(Icons.refresh), + onPressed: () { + if (_selectedLocation != null) { + _fetchWeatherData(_selectedLocation!.latitude, + _selectedLocation!.longitude); + } else { + _fetchWeatherForCurrentLocation(); + } + }, + ), + ], + ), + // SizedBox(height: 8), + Text( + location, + style: TextStyle( + fontSize: 14, + color: Colors.grey[600], + ), + ), + if (widget.isScheduled && widget.startDate != null) ...[ + // SizedBox(height: 4), + Text( + 'Scheduled for: ${widget.startDate!.day}/${widget.startDate!.month}/${widget.startDate!.year}' + + (widget.startTime != null + ? ' at ${widget.startTime!.format(context)}' + : ''), + style: TextStyle( + fontSize: 12, + color: Colors.blue[600], + fontWeight: FontWeight.w500, + ), + ), + ], + // SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${temp.toStringAsFixed(1)}°C', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + ), + ), + Text( + 'Feels like ${feelsLike.toStringAsFixed(1)}°C', + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + description.toUpperCase(), + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ], + ), + SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buildWeatherInfo(Icons.water_drop, 'Humidity', '$humidity%'), + _buildWeatherInfo( + Icons.air, 'Wind', '${windSpeed.toStringAsFixed(1)} m/s'), + _buildWeatherInfo(Icons.visibility, 'Visibility', + '${(weatherData!['visibility'] / 1000).toStringAsFixed(1)} km'), + ], + ), + SizedBox(height: 8), + Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: _getWeatherRecommendationColor().withOpacity(0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + children: [ + Icon( + _getWeatherRecommendationIcon(), + color: _getWeatherRecommendationColor(), + size: 16, + ), + SizedBox(width: 8), + Expanded( + child: Text( + _getWeatherRecommendation(), + style: TextStyle( + fontSize: 12, + color: _getWeatherRecommendationColor(), + ), + ), + ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget _buildWeatherInfo(IconData icon, String label, String value) { + return Column( + children: [ + Icon(icon, size: 20, color: Colors.grey[600]), + SizedBox(height: 4), + Text( + label, + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + ), + Text( + value, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + ), + ), + ], + ); + } + + Color _getWeatherRecommendationColor() { + if (weatherData == null) return Colors.grey; + + final temp = weatherData!['main']['temp']; + final windSpeed = weatherData!['wind']['speed']; + final description = weatherData!['weather'][0]['main'].toLowerCase(); + + if (description.contains('rain') || description.contains('storm')) { + return Colors.red; + } else if (temp < 10 || temp > 35 || windSpeed > 10) { + return Colors.orange; + } else { + return Colors.green; + } + } + + IconData _getWeatherRecommendationIcon() { + Color color = _getWeatherRecommendationColor(); + if (color == Colors.red) return Icons.warning; + if (color == Colors.orange) return Icons.info; + return Icons.check_circle; + } - // Chang - AdvancedOptionsScreen( - {super.key, required this.title, required this.durationController}); + String _getWeatherRecommendation() { + if (weatherData == null) return 'Weather data unavailable'; + + final temp = weatherData!['main']['temp']; + final windSpeed = weatherData!['wind']['speed']; + final description = weatherData!['weather'][0]['main'].toLowerCase(); + + String baseRecommendation; + if (description.contains('rain') || description.contains('storm')) { + baseRecommendation = 'Not recommended for hiking due to rain/storms'; + } else if (temp < 10) { + baseRecommendation = + 'Cold weather - dress warmly and check trail conditions'; + } else if (temp > 35) { + baseRecommendation = + 'Very hot - bring extra water and consider early morning hikes'; + } else if (windSpeed > 10) { + baseRecommendation = 'Strong winds - be cautious on exposed trails'; + } else { + baseRecommendation = 'Good conditions for hiking - enjoy your adventure!'; + } + + if (widget.isScheduled && widget.startDate != null) { + final difference = widget.startDate!.difference(DateTime.now()).inDays; + if (difference > 3) { + baseRecommendation += ' (Forecast may change - check closer to date)'; + } + } + + return baseRecommendation; + } @override Widget build(BuildContext context) { - DateTime? startDate = DateTime.now(); - TimeOfDay? startTime = TimeOfDay( - hour: TimeOfDay.now().hour, minute: TimeOfDay.now().minute + 1); - Duration? duration = Duration(minutes: 5); return Scaffold( - body: Form( - child: Container( - height: 12.h, - child: Padding( - padding: const EdgeInsets.all(4.0), - child: InkWell( - onTap: () async { - duration = await showDurationPicker( - context: context, - initialTime: duration ?? Duration(minutes: 5), - ); - if (duration == null) return; - if (duration!.inHours != 0 && duration!.inMinutes != 0) { - durationController.text = - '${duration!.inHours.toString()} hour ${(duration!.inMinutes % 60)} minutes'; - } else if (duration!.inMinutes != 0) { - durationController.text = - '${duration!.inMinutes.toString()} minutes'; + resizeToAvoidBottomInset: false, + body: SafeArea( + child: Column( + children: [ + _buildAppBar(context), + SizedBox(height: 2.h), + SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: _locationController, + decoration: InputDecoration( + fillColor: Colors.grey[200], + filled: true, + contentPadding: EdgeInsets.symmetric( + horizontal: 16, vertical: 16), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + labelText: 'Location', + labelStyle: TextStyle( + fontSize: 16, + color: Theme.of(context).primaryColor, + ), + hintText: 'Search for a location to get weather', + hintStyle: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + prefixIcon: Icon( + Icons.location_on, + color: Theme.of(context).primaryColor, + ), + suffixIcon: _locationController.text.isNotEmpty + ? IconButton( + icon: Icon(Icons.clear), + onPressed: () { + _locationController.clear(); + setState(() { + _showLocationSuggestions = false; + _selectedLocation = null; + }); + }, + ) + : null, + ), + onChanged: (value) { + _searchLocations(value); + }, + ), + if (_showLocationSuggestions && + _locationSuggestions.isNotEmpty) + Container( + margin: EdgeInsets.only(top: 8), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: _locationSuggestions.length, + itemBuilder: (context, index) { + final suggestion = _locationSuggestions[index]; + return ListTile( + leading: Icon(Icons.location_on, + color: Theme.of(context).primaryColor), + title: Text(suggestion.name), + onTap: () => _selectLocation(suggestion), + ); + }, + ), + ), + ], + ), + ), + + // Weather Card + _buildWeatherCard(), + + // Form Fields + Padding( + padding: EdgeInsets.all(16), + child: Form( + child: Column( + children: [ + // Title Field + Container( + margin: EdgeInsets.only(bottom: 16), + child: TextFormField( + onChanged: (name) { + widget.title = name; + }, + decoration: InputDecoration( + fillColor: Colors.grey[200], + filled: true, + contentPadding: EdgeInsets.symmetric( + horizontal: 16, vertical: 16), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + labelText: 'Title', + labelStyle: TextStyle( + fontSize: 16, + color: Theme.of(context).primaryColor, + ), + hintText: 'Enter your hike title', + hintStyle: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + prefixIcon: Icon( + Icons.title, + color: Theme.of(context).primaryColor, + ), + ), + ), + ), + + // Duration Field + Container( + margin: EdgeInsets.only(bottom: 16), + child: InkWell( + onTap: () async { + final selectedDuration = + await showDurationPicker( + context: context, + initialTime: duration ?? Duration(minutes: 5), + ); + if (selectedDuration == null) return; + + setState(() { + duration = selectedDuration; + }); + + // Format duration text + if (duration!.inHours != 0 && + duration!.inMinutes != 0) { + widget.durationController.text = + '${duration!.inHours} hour ${(duration!.inMinutes % 60)} minutes'; + } else if (duration!.inMinutes != 0) { + widget.durationController.text = + '${duration!.inMinutes} minutes'; + } + }, + child: TextFormField( + enabled: false, + controller: widget.durationController, + decoration: InputDecoration( + fillColor: Colors.grey[200], + filled: true, + contentPadding: EdgeInsets.symmetric( + horizontal: 16, vertical: 16), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + borderSide: BorderSide.none, + ), + labelText: 'Duration', + labelStyle: TextStyle( + fontSize: 16, + color: Theme.of(context).primaryColor, + ), + hintText: 'Tap to select duration', + hintStyle: TextStyle( + fontSize: 14, + color: Colors.grey, + ), + prefixIcon: Icon( + Icons.timer, + color: Theme.of(context).primaryColor, + ), + suffixIcon: Icon( + Icons.arrow_drop_down, + color: Theme.of(context).primaryColor, + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ElevatedButton( + onPressed: () async { + var groupCubit = locator(); + if (widget.isScheduled) { + DateTime start = DateTime(startDate!.year, startDate!.month, + startDate!.day, startTime!.hour, startTime!.minute); + + final startsAt = start.millisecondsSinceEpoch; + final expiresAt = start.add(duration!).millisecondsSinceEpoch; + + groupCubit.createHike( + widget.title, startsAt, expiresAt, widget.groupId, false); + + widget.durationController.clear(); + + appRouter.maybePop(); + } else { + int startsAt = DateTime.now().millisecondsSinceEpoch; + int expiresAt = + DateTime.now().add(duration!).millisecondsSinceEpoch; + + groupCubit.createHike( + widget.title, startsAt, expiresAt, widget.groupId, true); + + widget.durationController.clear(); + appRouter.maybePop(); } }, - child: TextFormField( - enabled: false, - controller: durationController, - decoration: InputDecoration( - fillColor: Colors.grey[200], - filled: true, - contentPadding: - EdgeInsets.symmetric(horizontal: 16, vertical: 8), - border: InputBorder.none, - alignLabelWithHint: true, - errorStyle: TextStyle(color: Colors.red[800]), - floatingLabelBehavior: FloatingLabelBehavior.always, - labelText: 'Duration', - labelStyle: TextStyle( - fontSize: 16, color: Theme.of(context).primaryColor), - hintStyle: TextStyle(fontSize: 14, color: Colors.grey), - hintText: 'Enter duration of hike', - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none), + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(context).primaryColor, + minimumSize: Size(160, 48), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(24), + ), + padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), + ), + child: Text( + !widget.isScheduled ? 'Start' : 'Create', + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold, + ), ), ), - ), + ], ), ), ); diff --git a/lib/presentation/group/widgets/create_join_dialog.dart b/lib/presentation/group/widgets/create_join_dialog.dart index 9331166..4cf32ee 100644 --- a/lib/presentation/group/widgets/create_join_dialog.dart +++ b/lib/presentation/group/widgets/create_join_dialog.dart @@ -544,10 +544,16 @@ class CreateJoinBeaconDialog { GestureDetector( onTap: () => { context.router.push(AdvancedOptionsScreenRoute( + title: title, durationController: _durationController, - title: title)), - print("advanced options clicked"), - print(_durationController.text), + isScheduled: !isInstant, + startDate: startDate, + startTime: startTime, + groupId: groupID!)), + _durationController.clear(), + _startTimeController.clear(), + _durationController.clear(), + appRouter.maybePop(), }, child: Text( "advanced options", From e2afac1040b153da1889312c91574a8b41817bcc Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Tue, 15 Jul 2025 14:04:29 +0530 Subject: [PATCH 11/19] add achievements screen --- lib/config/router/router.dart | 4 + lib/config/router/router.gr.dart | 20 + .../group/advance_options_screen.dart | 661 +++++++++--------- lib/presentation/group/group_screen.dart | 62 +- lib/presentation/hike/hike_screen.dart | 232 +++--- lib/presentation/home/home_screen.dart | 90 +-- lib/presentation/home/profile_screen.dart | 555 +++++++++++---- 7 files changed, 875 insertions(+), 749 deletions(-) diff --git a/lib/config/router/router.dart b/lib/config/router/router.dart index ad8fa4f..a159533 100644 --- a/lib/config/router/router.dart +++ b/lib/config/router/router.dart @@ -1,6 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:beacon/domain/entities/group/group_entity.dart'; import 'package:beacon/presentation/auth/verfication_screen.dart'; +import 'package:beacon/presentation/home/profile_screen.dart'; import 'package:beacon/presentation/splash/splash_screen.dart'; import 'package:beacon/presentation/home/home_screen.dart'; import 'package:flutter/material.dart'; @@ -26,6 +27,9 @@ class AppRouter extends _$AppRouter { AutoRoute( page: VerificationScreenRoute.page, ), + AutoRoute( + page: ProfileScreenRoute.page, + ), AutoRoute( page: AdvancedOptionsScreenRoute.page, path: '/advanced-options'), ]; diff --git a/lib/config/router/router.gr.dart b/lib/config/router/router.gr.dart index a1e4318..139717b 100644 --- a/lib/config/router/router.gr.dart +++ b/lib/config/router/router.gr.dart @@ -63,6 +63,12 @@ abstract class _$AppRouter extends RootStackRouter { child: const HomeScreen(), ); }, + ProfileScreenRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const ProfileScreen(), + ); + }, SplashScreenRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, @@ -251,6 +257,20 @@ class HomeScreenRoute extends PageRouteInfo { static const PageInfo page = PageInfo(name); } +/// generated route for +/// [ProfileScreen] +class ProfileScreenRoute extends PageRouteInfo { + const ProfileScreenRoute({List? children}) + : super( + ProfileScreenRoute.name, + initialChildren: children, + ); + + static const String name = 'ProfileScreenRoute'; + + static const PageInfo page = PageInfo(name); +} + /// generated route for /// [SplashScreen] class SplashScreenRoute extends PageRouteInfo { diff --git a/lib/presentation/group/advance_options_screen.dart b/lib/presentation/group/advance_options_screen.dart index 290cb3c..b6a1d10 100644 --- a/lib/presentation/group/advance_options_screen.dart +++ b/lib/presentation/group/advance_options_screen.dart @@ -10,6 +10,10 @@ import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:beacon/data/models/landmark/location_suggestion.dart'; +import '../../core/utils/constants.dart'; +import '../widgets/hike_button.dart'; +import '../widgets/screen_template.dart'; + @RoutePage() class AdvancedOptionsScreen extends StatefulWidget { TextEditingController durationController; @@ -104,7 +108,7 @@ class _AdvancedOptionsScreenState extends State { const String apiKey = '03fe30be078c0bfb823d954404de6a6b'; String apiUrl; - if (widget.isScheduled && widget.startDate != null) { + if (widget.isScheduled) { // Use forecast API for scheduled hikes apiUrl = 'https://api.openweathermap.org/data/2.5/forecast?lat=$lat&lon=$lon&appid=$apiKey&units=metric'; @@ -118,7 +122,7 @@ class _AdvancedOptionsScreenState extends State { if (response.statusCode == 200) { final data = jsonDecode(response.body); - if (widget.isScheduled && widget.startDate != null) { + if (widget.isScheduled) { // Process forecast data to find weather for the scheduled date final forecastWeather = _extractWeatherForScheduledDate(data); setState(() { @@ -235,9 +239,8 @@ class _AdvancedOptionsScreenState extends State { 'visibility': 10000, 'name': _selectedLocation?.name ?? 'Your Location', 'isScheduled': widget.isScheduled, - 'scheduledDate': widget.isScheduled && widget.startDate != null - ? widget.startDate!.toIso8601String() - : null, + 'scheduledDate': + widget.isScheduled ? widget.startDate!.toIso8601String() : null, }; isLoadingWeather = false; }); @@ -281,7 +284,7 @@ class _AdvancedOptionsScreenState extends State { String _getWeatherTitle() { if (weatherData == null) return 'Weather'; - if (widget.isScheduled && widget.startDate != null) { + if (widget.isScheduled) { final scheduledDate = widget.startDate!; final now = DateTime.now(); final difference = scheduledDate.difference(now).inDays; @@ -298,37 +301,69 @@ class _AdvancedOptionsScreenState extends State { return 'Current Weather'; } - Widget _buildAppBar(context) { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.grey), - onPressed: () => context.router.maybePop(), - ), - Image.asset('images/beacon_logo.png', height: 4.h), - IconButton( - icon: const Icon(Icons.power_settings_new, color: Colors.grey), - onPressed: () {}, - ), - ], - ); + Color _getWeatherRecommendationColor() { + // Example logic: change color based on weather condition + if (weatherData != null && weatherData!['weather'] != null) { + String main = weatherData!['weather'][0]['main'].toString().toLowerCase(); + if (main.contains('rain') || main.contains('storm')) { + return Colors.red; + } else if (main.contains('cloud')) { + return Colors.orange; + } else if (main.contains('clear')) { + return Colors.green; + } + } + return Colors.blue; + } + + IconData _getWeatherRecommendationIcon() { + if (weatherData != null && weatherData!['weather'] != null) { + String main = weatherData!['weather'][0]['main'].toString().toLowerCase(); + if (main.contains('rain') || main.contains('storm')) { + return Icons.warning; + } else if (main.contains('cloud')) { + return Icons.cloud; + } else if (main.contains('clear')) { + return Icons.wb_sunny; + } + } + return Icons.info; + } + + String _getWeatherRecommendation() { + if (weatherData != null && weatherData!['weather'] != null) { + String main = weatherData!['weather'][0]['main'].toString().toLowerCase(); + if (main.contains('rain') || main.contains('storm')) { + return 'It may be unsafe to hike due to rain or storm. Please check local advisories.'; + } else if (main.contains('cloud')) { + return 'Cloudy weather. Stay alert and check for rain updates.'; + } else if (main.contains('clear')) { + return 'Great weather for hiking! Enjoy your hike.'; + } + } + return 'Check weather conditions before starting your hike.'; } Widget _buildWeatherCard() { if (isLoadingWeather) { return Card( - elevation: 4, - margin: EdgeInsets.all(16), + elevation: 2, + margin: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), child: Padding( padding: EdgeInsets.all(16), child: Row( children: [ - CircularProgressIndicator(), - // SizedBox(width: 16), - Text(widget.isScheduled - ? 'Loading weather forecast...' - : 'Loading weather conditions...'), + CircularProgressIndicator(color: kYellow), + SizedBox(width: 4.w), + Text( + widget.isScheduled + ? 'Loading weather forecast...' + : 'Loading weather conditions...', + style: TextStyle(fontSize: 14.sp), + ), ], ), ), @@ -337,21 +372,31 @@ class _AdvancedOptionsScreenState extends State { if (weatherError != null) { return Card( - elevation: 4, - margin: EdgeInsets.all(16), + elevation: 2, + margin: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), child: Padding( padding: EdgeInsets.all(16), child: Row( children: [ - Icon(Icons.error, color: Colors.red), - SizedBox(width: 16), - Expanded(child: Text(weatherError!)), + Icon(Icons.error, color: Colors.red, size: 20.sp), + SizedBox(width: 4.w), + Expanded( + child: Text( + weatherError!, + style: TextStyle(fontSize: 14.sp), + ), + ), IconButton( - icon: Icon(Icons.refresh), + icon: Icon(Icons.refresh, size: 20.sp), onPressed: () { if (_selectedLocation != null) { - _fetchWeatherData(_selectedLocation!.latitude, - _selectedLocation!.longitude); + _fetchWeatherData( + _selectedLocation!.latitude, + _selectedLocation!.longitude, + ); } else { _fetchWeatherForCurrentLocation(); } @@ -373,8 +418,11 @@ class _AdvancedOptionsScreenState extends State { final location = weatherData!['name']; return Card( - elevation: 4, - margin: EdgeInsets.all(16), + elevation: 2, + margin: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), child: Padding( padding: EdgeInsets.all(16), child: Column( @@ -386,17 +434,19 @@ class _AdvancedOptionsScreenState extends State { Text( _getWeatherTitle(), style: TextStyle( - fontSize: 18.sp, - fontWeight: FontWeight.bold, - color: Theme.of(context).primaryColor, + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: kBlack, ), ), IconButton( - icon: Icon(Icons.refresh), + icon: Icon(Icons.refresh, size: 20.sp), onPressed: () { if (_selectedLocation != null) { - _fetchWeatherData(_selectedLocation!.latitude, - _selectedLocation!.longitude); + _fetchWeatherData( + _selectedLocation!.latitude, + _selectedLocation!.longitude, + ); } else { _fetchWeatherForCurrentLocation(); } @@ -404,29 +454,29 @@ class _AdvancedOptionsScreenState extends State { ), ], ), - // SizedBox(height: 8), + SizedBox(height: 1.h), Text( location, style: TextStyle( - fontSize: 14, + fontSize: 14.sp, color: Colors.grey[600], ), ), - if (widget.isScheduled && widget.startDate != null) ...[ - // SizedBox(height: 4), + if (widget.isScheduled) ...[ + SizedBox(height: 1.h), Text( 'Scheduled for: ${widget.startDate!.day}/${widget.startDate!.month}/${widget.startDate!.year}' + (widget.startTime != null ? ' at ${widget.startTime!.format(context)}' : ''), style: TextStyle( - fontSize: 12, + fontSize: 12.sp, color: Colors.blue[600], fontWeight: FontWeight.w500, ), ), ], - // SizedBox(height: 12), + SizedBox(height: 2.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -436,14 +486,15 @@ class _AdvancedOptionsScreenState extends State { Text( '${temp.toStringAsFixed(1)}°C', style: TextStyle( - fontSize: 24, + fontSize: 20.sp, fontWeight: FontWeight.bold, + color: Color(0xFF673AB7), ), ), Text( 'Feels like ${feelsLike.toStringAsFixed(1)}°C', style: TextStyle( - fontSize: 12, + fontSize: 12.sp, color: Colors.grey[600], ), ), @@ -455,15 +506,16 @@ class _AdvancedOptionsScreenState extends State { Text( description.toUpperCase(), style: TextStyle( - fontSize: 14, + fontSize: 14.sp, fontWeight: FontWeight.w500, + color: kBlack, ), ), ], ), ], ), - SizedBox(height: 12), + SizedBox(height: 2.h), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ @@ -474,9 +526,9 @@ class _AdvancedOptionsScreenState extends State { '${(weatherData!['visibility'] / 1000).toStringAsFixed(1)} km'), ], ), - SizedBox(height: 8), + SizedBox(height: 2.h), Container( - padding: EdgeInsets.all(8), + padding: EdgeInsets.all(2.w), decoration: BoxDecoration( color: _getWeatherRecommendationColor().withOpacity(0.1), borderRadius: BorderRadius.circular(8), @@ -486,14 +538,14 @@ class _AdvancedOptionsScreenState extends State { Icon( _getWeatherRecommendationIcon(), color: _getWeatherRecommendationColor(), - size: 16, + size: 16.sp, ), - SizedBox(width: 8), + SizedBox(width: 2.w), Expanded( child: Text( _getWeatherRecommendation(), style: TextStyle( - fontSize: 12, + fontSize: 12.sp, color: _getWeatherRecommendationColor(), ), ), @@ -510,298 +562,260 @@ class _AdvancedOptionsScreenState extends State { Widget _buildWeatherInfo(IconData icon, String label, String value) { return Column( children: [ - Icon(icon, size: 20, color: Colors.grey[600]), - SizedBox(height: 4), + Icon(icon, size: 20.sp, color: Colors.grey[600]), + SizedBox(height: 1.h), Text( label, style: TextStyle( - fontSize: 12, + fontSize: 12.sp, color: Colors.grey[600], ), ), Text( value, style: TextStyle( - fontSize: 12, + fontSize: 12.sp, fontWeight: FontWeight.w500, + color: kBlack, ), ), ], ); } - Color _getWeatherRecommendationColor() { - if (weatherData == null) return Colors.grey; - - final temp = weatherData!['main']['temp']; - final windSpeed = weatherData!['wind']['speed']; - final description = weatherData!['weather'][0]['main'].toLowerCase(); - - if (description.contains('rain') || description.contains('storm')) { - return Colors.red; - } else if (temp < 10 || temp > 35 || windSpeed > 10) { - return Colors.orange; - } else { - return Colors.green; - } + Widget _buildLocationField() { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: _locationController, + decoration: InputDecoration( + filled: true, + fillColor: Colors.grey[200], + contentPadding: EdgeInsets.symmetric( + horizontal: 4.w, + vertical: 2.h, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + labelText: 'Starting Location', + labelStyle: TextStyle( + fontSize: 16.sp, + color: kBlack, + ), + hintText: 'Search for a location to get weather', + hintStyle: TextStyle( + fontSize: 14.sp, + color: Colors.grey, + ), + prefixIcon: Icon( + Icons.location_on, + color: kBlack, + ), + suffixIcon: _locationController.text.isNotEmpty + ? IconButton( + icon: Icon(Icons.clear), + onPressed: () { + _locationController.clear(); + setState(() { + _showLocationSuggestions = false; + _selectedLocation = null; + }); + }, + ) + : null, + ), + onChanged: (value) { + _searchLocations(value); + }, + ), + if (_showLocationSuggestions && _locationSuggestions.isNotEmpty) + Container( + margin: EdgeInsets.only(top: 1.h), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + offset: Offset(0, 2), + ), + ], + ), + child: ListView.builder( + shrinkWrap: true, + itemCount: _locationSuggestions.length, + itemBuilder: (context, index) { + final suggestion = _locationSuggestions[index]; + return ListTile( + leading: Icon(Icons.location_on, color: kBlack), + title: Text( + suggestion.name, + style: TextStyle(fontSize: 14.sp), + ), + onTap: () => _selectLocation(suggestion), + ); + }, + ), + ), + ], + ), + ); } - IconData _getWeatherRecommendationIcon() { - Color color = _getWeatherRecommendationColor(); - if (color == Colors.red) return Icons.warning; - if (color == Colors.orange) return Icons.info; - return Icons.check_circle; + Widget _buildTitleField() { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), + child: TextFormField( + initialValue: widget.title, + onChanged: (name) { + widget.title = name; + }, + decoration: InputDecoration( + filled: true, + fillColor: Colors.grey[200], + contentPadding: EdgeInsets.symmetric( + horizontal: 4.w, + vertical: 2.h, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + labelText: 'Title', + labelStyle: TextStyle( + fontSize: 16.sp, + color: kBlack, + ), + hintText: 'Enter your hike title', + hintStyle: TextStyle( + fontSize: 14.sp, + color: Colors.grey, + ), + prefixIcon: Icon( + Icons.title, + color: kBlack, + ), + ), + ), + ); } - String _getWeatherRecommendation() { - if (weatherData == null) return 'Weather data unavailable'; + Widget _buildDurationField() { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), + child: InkWell( + onTap: () async { + final selectedDuration = await showDurationPicker( + context: context, + initialTime: duration ?? Duration(minutes: 5), + ); + if (selectedDuration == null) return; - final temp = weatherData!['main']['temp']; - final windSpeed = weatherData!['wind']['speed']; - final description = weatherData!['weather'][0]['main'].toLowerCase(); - - String baseRecommendation; - if (description.contains('rain') || description.contains('storm')) { - baseRecommendation = 'Not recommended for hiking due to rain/storms'; - } else if (temp < 10) { - baseRecommendation = - 'Cold weather - dress warmly and check trail conditions'; - } else if (temp > 35) { - baseRecommendation = - 'Very hot - bring extra water and consider early morning hikes'; - } else if (windSpeed > 10) { - baseRecommendation = 'Strong winds - be cautious on exposed trails'; - } else { - baseRecommendation = 'Good conditions for hiking - enjoy your adventure!'; - } - - if (widget.isScheduled && widget.startDate != null) { - final difference = widget.startDate!.difference(DateTime.now()).inDays; - if (difference > 3) { - baseRecommendation += ' (Forecast may change - check closer to date)'; - } - } + setState(() { + duration = selectedDuration; + }); - return baseRecommendation; + // Format duration text + if (duration!.inHours != 0 && duration!.inMinutes != 0) { + widget.durationController.text = + '${duration!.inHours} hour ${(duration!.inMinutes % 60)} minutes'; + } else if (duration!.inMinutes != 0) { + widget.durationController.text = '${duration!.inMinutes} minutes'; + } + }, + child: TextFormField( + enabled: false, + controller: widget.durationController, + decoration: InputDecoration( + filled: true, + fillColor: Colors.grey[200], + contentPadding: EdgeInsets.symmetric( + horizontal: 4.w, + vertical: 2.h, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + labelText: 'Duration', + labelStyle: TextStyle( + fontSize: 16.sp, + color: kBlack, + ), + hintText: 'Tap to select duration', + hintStyle: TextStyle( + fontSize: 14.sp, + color: Colors.grey, + ), + prefixIcon: Icon( + Icons.timer, + color: kBlack, + ), + suffixIcon: Icon( + Icons.arrow_drop_down, + color: kBlack, + ), + ), + ), + ), + ); } @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: Column( - children: [ - _buildAppBar(context), - SizedBox(height: 2.h), - SingleChildScrollView( + return BeaconScreenTemplate( + body: Column( + children: [ + SizedBox(height: 2.h), + Expanded( + child: SingleChildScrollView( child: Column( children: [ - Padding( - padding: EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - controller: _locationController, - decoration: InputDecoration( - fillColor: Colors.grey[200], - filled: true, - contentPadding: EdgeInsets.symmetric( - horizontal: 16, vertical: 16), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - labelText: 'Location', - labelStyle: TextStyle( - fontSize: 16, - color: Theme.of(context).primaryColor, - ), - hintText: 'Search for a location to get weather', - hintStyle: TextStyle( - fontSize: 14, - color: Colors.grey, - ), - prefixIcon: Icon( - Icons.location_on, - color: Theme.of(context).primaryColor, - ), - suffixIcon: _locationController.text.isNotEmpty - ? IconButton( - icon: Icon(Icons.clear), - onPressed: () { - _locationController.clear(); - setState(() { - _showLocationSuggestions = false; - _selectedLocation = null; - }); - }, - ) - : null, - ), - onChanged: (value) { - _searchLocations(value); - }, - ), - if (_showLocationSuggestions && - _locationSuggestions.isNotEmpty) - Container( - margin: EdgeInsets.only(top: 8), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - boxShadow: [ - BoxShadow( - color: Colors.black12, - blurRadius: 4, - offset: Offset(0, 2), - ), - ], - ), - child: ListView.builder( - shrinkWrap: true, - itemCount: _locationSuggestions.length, - itemBuilder: (context, index) { - final suggestion = _locationSuggestions[index]; - return ListTile( - leading: Icon(Icons.location_on, - color: Theme.of(context).primaryColor), - title: Text(suggestion.name), - onTap: () => _selectLocation(suggestion), - ); - }, - ), - ), - ], - ), - ), - - // Weather Card + _buildLocationField(), _buildWeatherCard(), - - // Form Fields - Padding( - padding: EdgeInsets.all(16), - child: Form( - child: Column( - children: [ - // Title Field - Container( - margin: EdgeInsets.only(bottom: 16), - child: TextFormField( - onChanged: (name) { - widget.title = name; - }, - decoration: InputDecoration( - fillColor: Colors.grey[200], - filled: true, - contentPadding: EdgeInsets.symmetric( - horizontal: 16, vertical: 16), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - labelText: 'Title', - labelStyle: TextStyle( - fontSize: 16, - color: Theme.of(context).primaryColor, - ), - hintText: 'Enter your hike title', - hintStyle: TextStyle( - fontSize: 14, - color: Colors.grey, - ), - prefixIcon: Icon( - Icons.title, - color: Theme.of(context).primaryColor, - ), - ), - ), - ), - - // Duration Field - Container( - margin: EdgeInsets.only(bottom: 16), - child: InkWell( - onTap: () async { - final selectedDuration = - await showDurationPicker( - context: context, - initialTime: duration ?? Duration(minutes: 5), - ); - if (selectedDuration == null) return; - - setState(() { - duration = selectedDuration; - }); - - // Format duration text - if (duration!.inHours != 0 && - duration!.inMinutes != 0) { - widget.durationController.text = - '${duration!.inHours} hour ${(duration!.inMinutes % 60)} minutes'; - } else if (duration!.inMinutes != 0) { - widget.durationController.text = - '${duration!.inMinutes} minutes'; - } - }, - child: TextFormField( - enabled: false, - controller: widget.durationController, - decoration: InputDecoration( - fillColor: Colors.grey[200], - filled: true, - contentPadding: EdgeInsets.symmetric( - horizontal: 16, vertical: 16), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - borderSide: BorderSide.none, - ), - labelText: 'Duration', - labelStyle: TextStyle( - fontSize: 16, - color: Theme.of(context).primaryColor, - ), - hintText: 'Tap to select duration', - hintStyle: TextStyle( - fontSize: 14, - color: Colors.grey, - ), - prefixIcon: Icon( - Icons.timer, - color: Theme.of(context).primaryColor, - ), - suffixIcon: Icon( - Icons.arrow_drop_down, - color: Theme.of(context).primaryColor, - ), - ), - ), - ), - ), - ], - ), - ), - ), + _buildTitleField(), + _buildDurationField(), + SizedBox(height: 2.h), ], ), ), - ElevatedButton( - onPressed: () async { + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.h), + child: HikeButton( + buttonHeight: 6.h, + buttonWidth: double.infinity, + text: !widget.isScheduled ? 'Start Hike' : 'Schedule Hike', + textSize: 16.sp, + onTap: () async { var groupCubit = locator(); if (widget.isScheduled) { - DateTime start = DateTime(startDate!.year, startDate!.month, - startDate!.day, startTime!.hour, startTime!.minute); + DateTime start = DateTime( + startDate!.year, + startDate!.month, + startDate!.day, + startTime!.hour, + startTime!.minute, + ); final startsAt = start.millisecondsSinceEpoch; final expiresAt = start.add(duration!).millisecondsSinceEpoch; groupCubit.createHike( - widget.title, startsAt, expiresAt, widget.groupId, false); + widget.title, + startsAt, + expiresAt, + widget.groupId, + false, + ); widget.durationController.clear(); - appRouter.maybePop(); } else { int startsAt = DateTime.now().millisecondsSinceEpoch; @@ -809,31 +823,20 @@ class _AdvancedOptionsScreenState extends State { DateTime.now().add(duration!).millisecondsSinceEpoch; groupCubit.createHike( - widget.title, startsAt, expiresAt, widget.groupId, true); + widget.title, + startsAt, + expiresAt, + widget.groupId, + true, + ); widget.durationController.clear(); appRouter.maybePop(); } }, - style: ElevatedButton.styleFrom( - backgroundColor: Theme.of(context).primaryColor, - minimumSize: Size(160, 48), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(24), - ), - padding: EdgeInsets.symmetric(horizontal: 24, vertical: 12), - ), - child: Text( - !widget.isScheduled ? 'Start' : 'Create', - style: TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), ), - ], - ), + ), + ], ), ); } diff --git a/lib/presentation/group/group_screen.dart b/lib/presentation/group/group_screen.dart index c386d7b..8e713fc 100644 --- a/lib/presentation/group/group_screen.dart +++ b/lib/presentation/group/group_screen.dart @@ -8,6 +8,7 @@ import 'package:beacon/presentation/group/cubit/members_cubit/members_cubit.dart import 'package:beacon/presentation/group/widgets/create_join_dialog.dart'; import 'package:beacon/presentation/group/widgets/beacon_card.dart'; import 'package:beacon/presentation/group/widgets/group_widgets.dart'; +import 'package:beacon/presentation/widgets/screen_template.dart'; import 'package:beacon/presentation/widgets/shimmer.dart'; import 'package:beacon/presentation/widgets/hike_button.dart'; import 'package:beacon/presentation/widgets/loading_screen.dart'; @@ -68,40 +69,35 @@ class _GroupScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - resizeToAvoidBottomInset: false, - body: SafeArea( - child: BlocConsumer( - listener: (context, state) { - if (state is AllBeaconGroupState && state.message != null) { - utils.showSnackBar(state.message!, context); - } - }, - builder: (context, state) { - return ModalProgressHUD( - inAsyncCall: state is LoadingGroupState, - progressIndicator: const LoadingScreen(), - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), - child: Column( - children: [ - _buildAppBar(), - SizedBox(height: 2.h), - _buildGroupHeader(), - SizedBox(height: 2.h), - _buildMembersSection(), - SizedBox(height: 3.h), - _buildActionButtons(), - SizedBox(height: 3.h), - _buildBeaconsHeader(), - SizedBox(height: 1.h), - Expanded(child: _buildBeaconsList(state)), - ], - ), + return BeaconScreenTemplate( + body: BlocConsumer( + listener: (context, state) { + if (state is AllBeaconGroupState && state.message != null) { + utils.showSnackBar(state.message!, context); + } + }, + builder: (context, state) { + return ModalProgressHUD( + inAsyncCall: state is LoadingGroupState, + progressIndicator: const LoadingScreen(), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), + child: Column( + children: [ + _buildGroupHeader(), + SizedBox(height: 2.h), + _buildMembersSection(), + SizedBox(height: 3.h), + _buildActionButtons(), + SizedBox(height: 3.h), + _buildBeaconsHeader(), + SizedBox(height: 1.h), + Expanded(child: _buildBeaconsList(state)), + ], ), - ); - }, - ), + ), + ); + }, ), ); } diff --git a/lib/presentation/hike/hike_screen.dart b/lib/presentation/hike/hike_screen.dart index e13117a..dcbcf4e 100644 --- a/lib/presentation/hike/hike_screen.dart +++ b/lib/presentation/hike/hike_screen.dart @@ -12,6 +12,7 @@ import 'package:beacon/presentation/hike/cubit/location_cubit/location_state.dar import 'package:beacon/presentation/hike/widgets/hike_screen_widget.dart'; import 'package:beacon/presentation/hike/widgets/search_places.dart'; import 'package:beacon/presentation/widgets/hike_button.dart'; +import 'package:beacon/presentation/widgets/screen_template.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -85,159 +86,95 @@ class _HikeScreenState extends State child: Center(child: Text('Restart beacon')), ); } else { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.grey[50], - leading: IconButton( - icon: Icon(Icons.arrow_back_ios_new, - size: 20, color: Colors.grey), - onPressed: () { - _panelController.close(); - appRouter.maybePop(); - }, - ), - centerTitle: true, - title: Image.asset( - 'images/beacon_logo.png', - height: 28, + return BeaconScreenTemplate( + body: Stack( + children: [ + _mapScreen(), + LocationSearchWidget(widget.beacon.id!), + Positioned( + bottom: 200, + right: 10, + child: Column( + children: [ + FloatingActionButton( + backgroundColor: Colors.white, + mini: true, + onPressed: () => _locationCubit.zoomIn(), + child: Icon(Icons.add), + ), + //SizedBox(height: 2), + FloatingActionButton( + backgroundColor: Colors.white, + mini: true, + onPressed: () => _locationCubit.zoomOut(), + child: Icon(Icons.remove), + ), + SizedBox(height: 2), + FloatingActionButton( + backgroundColor: Colors.white, + mini: true, + onPressed: () => _locationCubit.centerMap(), + child: Icon(Icons.map), + ), + ], ), - actions: [ - IconButton( - icon: const Icon(Icons.power_settings_new, - color: Colors.grey), - onPressed: () => showDialog( - context: context, - builder: (context) => AlertDialog( - backgroundColor: Color(0xffFAFAFA), - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(12.0), - ), - title: Text('Logout', - style: Style.heading), - content: Text( - 'Are you sure you want to logout?', - style: TextStyle( - fontSize: 16, color: kBlack), - ), - actions: [ - HikeButton( - buttonWidth: 80, - buttonHeight: 40, - isDotted: true, - onTap: () => AutoRouter.of(context) - .maybePop(false), - text: 'No', - textSize: 18.0, - ), - SizedBox( - height: 5, - ), - HikeButton( - buttonWidth: 80, - buttonHeight: 40, - onTap: () async { - appRouter.replaceNamed('/auth'); - localApi.deleteUser(); - context - .read() - .googleSignOut(); - }, - text: 'Yes', - textSize: 18.0, - ), - ], - ))), - ], ), - body: Stack( - children: [ - _mapScreen(), - LocationSearchWidget(widget.beacon.id!), - Positioned( - bottom: 200, - right: 10, - child: Column( - children: [ - FloatingActionButton( - backgroundColor: Colors.white, - mini: true, - onPressed: () => _locationCubit.zoomIn(), - child: Icon(Icons.add), - ), - //SizedBox(height: 2), - FloatingActionButton( - backgroundColor: Colors.white, - mini: true, - onPressed: () => _locationCubit.zoomOut(), - child: Icon(Icons.remove), - ), - SizedBox(height: 2), - FloatingActionButton( - backgroundColor: Colors.white, - mini: true, - onPressed: () => _locationCubit.centerMap(), - child: Icon(Icons.map), - ), - ], + Positioned( + bottom: 0, + left: 0, + right: 0, + height: 100, + child: Container( + height: 100, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), + ), ), - ), - Positioned( - bottom: 0, - left: 0, - right: 0, - height: 100, - child: Container( - height: 100, - width: double.infinity, - decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Left Circular Icon Button + Container( + width: 50, + height: 50, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + ), + ], + image: DecorationImage( + image: NetworkImage( + "https://media.istockphoto.com/id/1253926432/vector/flashlight-warning-alarm-light-and-siren-light-flat-design-vector-design.jpg?s=612x612&w=0&k=20&c=yOj6Jpu7XDrPJCTfUIpQm-LWI9q9RWQB91s-N7CgQDQ="))), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Left Circular Icon Button - Container( - width: 50, - height: 50, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black12, - blurRadius: 4, - ), - ], - image: DecorationImage( - image: NetworkImage( - "https://media.istockphoto.com/id/1253926432/vector/flashlight-warning-alarm-light-and-siren-light-flat-design-vector-design.jpg?s=612x612&w=0&k=20&c=yOj6Jpu7XDrPJCTfUIpQm-LWI9q9RWQB91s-N7CgQDQ="))), - ), - const SizedBox(width: 10), + const SizedBox(width: 10), - // Right Red SOS Button - HikeButton( - buttonWidth: 70.w, - buttonHeight: 50, - text: 'Send SOS', - onTap: () { - locator().sendSOS( - widget.beacon.id!, context); - }, - textSize: 18.0, - buttonColor: Colors.red, - textColor: Colors.white, - ) - ], - ), - )), - ], - )); + // Right Red SOS Button + HikeButton( + buttonWidth: 70.w, + buttonHeight: 50, + text: 'Send SOS', + onTap: () { + locator() + .sendSOS(widget.beacon.id!, context); + }, + textSize: 18.0, + buttonColor: Colors.red, + textColor: Colors.white, + ) + ], + ), + )), + ], + )); } }, ); @@ -249,7 +186,6 @@ class _HikeScreenState extends State Widget _mapScreen() { return BlocConsumer( listener: (context, state) { - if (state is LoadedLocationState) { state.message != null ? utils.showSnackBar(state.message!, context) diff --git a/lib/presentation/home/home_screen.dart b/lib/presentation/home/home_screen.dart index cb8cafb..1a561f2 100644 --- a/lib/presentation/home/home_screen.dart +++ b/lib/presentation/home/home_screen.dart @@ -5,6 +5,7 @@ import 'package:beacon/presentation/home/home_cubit/home_cubit.dart'; import 'package:beacon/presentation/home/home_cubit/home_state.dart'; import 'package:beacon/presentation/group/widgets/create_join_dialog.dart'; import 'package:beacon/presentation/home/profile_screen.dart'; +import 'package:beacon/presentation/widgets/screen_template.dart'; import 'package:beacon/presentation/widgets/shimmer.dart'; import 'package:beacon/presentation/home/widgets/group_card.dart'; import 'package:beacon/presentation/widgets/hike_button.dart'; @@ -124,22 +125,20 @@ class _HomeScreenState extends State { builder: (context, state) { return Scaffold( resizeToAvoidBottomInset: false, - body: SafeArea( - child: ModalProgressHUD( + body: BeaconScreenTemplate( + body: ModalProgressHUD( inAsyncCall: state is LoadingHomeState, progressIndicator: const LoadingScreen(), child: Padding( padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), child: Column( children: [ - // App bar - _buildAppBar(), SizedBox(height: 2.h), Expanded( child: _currentIndex == 0 ? _buildHomePage() : _currentIndex == 1 - ? ProfileScreen(homeCubit: _homeCubit) + ? ProfileScreen() : _buildSettingsPage(), ), ], @@ -147,26 +146,12 @@ class _HomeScreenState extends State { ), ), ), - bottomNavigationBar: _buildBottomNavigationBar(), ); }, ), ); } - Widget _buildAppBar() { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Image.asset('images/beacon_logo.png', height: 4.h), - IconButton( - icon: Icon(Icons.power_settings_new, color: Colors.grey, size: 20.sp), - onPressed: _showLogoutDialog, - ), - ], - ); - } - Widget _buildHomePage() { return Column( children: [ @@ -339,73 +324,6 @@ class _HomeScreenState extends State { ); } - Widget _buildBottomNavigationBar() { - return BottomNavigationBar( - currentIndex: _currentIndex, - onTap: (index) => setState(() => _currentIndex = index), - items: [ - BottomNavigationBarItem( - icon: Icon(Icons.home, size: 20.sp), - label: 'Home', - ), - BottomNavigationBarItem( - icon: Icon(Icons.person, size: 20.sp), - label: 'Profile', - ), - BottomNavigationBarItem( - icon: Icon(Icons.hiking, size: 20.sp), - label: 'Hike', - ), - ], - ); - } - - void _showLogoutDialog() { - showDialog( - context: context, - builder: (context) => AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.0), - ), - contentPadding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 2.h), - title: Text( - 'Logout', - style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.w600), - ), - content: Text( - 'Are you sure you want to logout?', - style: TextStyle(fontSize: 14.sp), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - HikeButton( - buttonWidth: 25.w, - buttonHeight: 5.h, - isDotted: true, - onTap: () => AutoRouter.of(context).maybePop(false), - text: 'No', - textSize: 14.sp, - ), - HikeButton( - buttonWidth: 25.w, - buttonHeight: 5.h, - onTap: () { - appRouter.replaceNamed('/auth'); - localApi.deleteUser(); - context.read().googleSignOut(); - }, - text: 'Yes', - textSize: 14.sp, - ), - ], - ), - ], - ), - ); - } - String _getCapitalizedName() { final name = localApi.userModel.name.toString(); if (name.isEmpty) return ''; diff --git a/lib/presentation/home/profile_screen.dart b/lib/presentation/home/profile_screen.dart index fca37be..448057d 100644 --- a/lib/presentation/home/profile_screen.dart +++ b/lib/presentation/home/profile_screen.dart @@ -1,12 +1,17 @@ +import 'package:auto_route/annotations.dart'; +import 'package:beacon/domain/entities/user/user_entity.dart'; import 'package:beacon/locator.dart'; import 'package:beacon/presentation/home/home_cubit/home_cubit.dart'; import 'package:beacon/presentation/widgets/hike_button.dart'; +import 'package:beacon/presentation/widgets/screen_template.dart'; import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; +import '../../core/utils/constants.dart'; +@RoutePage() class ProfileScreen extends StatefulWidget { - final HomeCubit homeCubit; - const ProfileScreen({super.key, required this.homeCubit}); + const ProfileScreen({super.key}); @override State createState() => _ProfileScreenState(); @@ -15,6 +20,15 @@ class ProfileScreen extends StatefulWidget { class _ProfileScreenState extends State { bool showSelectImage = false; int selectedImageIndex = -1; + int selectedBadgeCategory = 0; + + final List badgeCategories = [ + 'All', + 'Exploration', + 'Social', + 'Achievements', + 'Milestones' + ]; final List imageOptions = [ "https://cdn.jsdelivr.net/gh/alohe/avatars/png/memo_2.png", @@ -29,171 +43,406 @@ class _ProfileScreenState extends State { ]; @override - Widget build(BuildContext context) { - return SingleChildScrollView( - child: Column( - children: [ - showSelectImage == true - ? SizedBox( - height: 45.h, - width: double.infinity, - child: LayoutBuilder( - builder: (context, constraints) { - // Calculate the number of columns based on screen width - int columns = (constraints.maxWidth / 100).floor(); - - return GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: columns, // Number of columns - crossAxisSpacing: 10, // Space between columns - mainAxisSpacing: 10, // Space between rows - ), - itemCount: imageOptions.length, // Total number of items - itemBuilder: (context, index) { - return GestureDetector( - onTap: () { - setState(() { - selectedImageIndex = index; - }); - }, - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - border: Border.all( - color: selectedImageIndex == index - ? Colors.deepPurple - : Colors.transparent, - width: 2, - ), - color: Colors.grey.shade200, - image: DecorationImage( - image: NetworkImage(imageOptions[index]), - fit: BoxFit.cover, - ), - )), - ); - }, - ); - }, - ), - ) - : Center(), - - SizedBox(height: 2.h), + void initState() { + super.initState(); + selectedImageIndex = + imageOptions.indexOf(localApi.userModel.imageUrl ?? ''); + } - // -- IMAGE with ICON - Stack( + @override + Widget build(BuildContext context) { + return BeaconScreenTemplate( + showLogout: true, + body: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 2.h), + child: Column( children: [ - SizedBox( - width: 120, - height: 120, + if (showSelectImage) _buildImageSelectionGrid(), + _buildProfilePicture(), + SizedBox(height: 3.h), + if (!showSelectImage) ...[ + _buildProfileInfoCard(), + SizedBox(height: 3.h), + _buildGamificationSection(), + ], + if (showSelectImage) _buildActionButtons(), + ], + ), + ), + ), + ); + } + + Widget _buildImageSelectionGrid() { + return Container( + height: 45.h, + margin: EdgeInsets.only(bottom: 2.h), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(12), + ), + child: LayoutBuilder( + builder: (context, constraints) { + int columns = (constraints.maxWidth / 100).floor(); + return GridView.builder( + padding: EdgeInsets.all(2.w), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: columns, + crossAxisSpacing: 2.w, + mainAxisSpacing: 2.w, + childAspectRatio: 1, + ), + itemCount: imageOptions.length, + itemBuilder: (context, index) { + return GestureDetector( + onTap: () => setState(() => selectedImageIndex = index), child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - color: Colors.grey.shade200, - image: DecorationImage( - image: selectedImageIndex != -1 - ? NetworkImage(imageOptions[selectedImageIndex]) - : NetworkImage(localApi.userModel.imageUrl!), - fit: BoxFit.cover, - ), - )), - ), - Positioned( - bottom: 0, - right: 0, - child: GestureDetector( - onTap: () { - print("select image clicked"); - print("showSelectImage: $showSelectImage"); - setState(() { - showSelectImage = !showSelectImage; - }); - }, - child: Container( - width: 35, - height: 35, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(100), - color: Colors.deepPurple, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + border: Border.all( + color: selectedImageIndex == index + ? Colors.deepPurple + : Colors.transparent, + width: 2, ), - child: const Icon( - Icons.camera_alt_rounded, - color: Colors.white, - size: 20, + color: Colors.grey[200], + image: DecorationImage( + image: NetworkImage(imageOptions[index]), + fit: BoxFit.cover, ), ), ), + ); + }, + ); + }, + ), + ); + } + + Widget _buildProfilePicture() { + return Stack( + alignment: Alignment.center, + children: [ + Container( + width: 30.w, + height: 30.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.deepPurple, width: 2), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 10, + spreadRadius: 2, ), ], ), - const SizedBox(height: 20), - - // -- Form Fields - !showSelectImage - ? Column( - children: [ - Padding( - padding: EdgeInsets.symmetric(horizontal: 4.w), - child: TextFormField( - readOnly: true, - initialValue: localApi.userModel.name, - decoration: InputDecoration( - labelText: 'Name', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - ), - ), - SizedBox(height: 2.h), - Padding( - padding: EdgeInsets.symmetric(horizontal: 4.w), - child: TextFormField( - readOnly: true, - initialValue: localApi.userModel.email, - decoration: InputDecoration( - labelText: 'Email', - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(10), - ), - ), - ), - ), - SizedBox(height: 2.h), - ], - ) - : const SizedBox(), - - Padding( - padding: EdgeInsets.symmetric(horizontal: 4.w), - child: HikeButton( - buttonWidth: double.infinity, - buttonHeight: 6.h, - text: 'Update Profile Image', - textSize: 14.sp, - onTap: () { - if (selectedImageIndex != -1) { - localApi.userModel.copyWithModel( - imageUrl: imageOptions[selectedImageIndex], - ); - widget.homeCubit.updateUserImage( - localApi.userModel.id!, - imageOptions[selectedImageIndex], - ); - setState(() { - showSelectImage = false; - }); - } else { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('Please select an image')), - ); - } - }, + child: ClipOval( + child: Image.network( + selectedImageIndex != -1 + ? imageOptions[selectedImageIndex] + : localApi.userModel.imageUrl!, + fit: BoxFit.cover, + errorBuilder: (_, __, ___) => Icon( + Icons.person, + size: 20.w, + color: Colors.grey[400], + ), + ), + ), + ), + Positioned( + bottom: 0, + right: 0, + child: GestureDetector( + onTap: () => setState(() => showSelectImage = !showSelectImage), + child: Container( + width: 8.w, + height: 8.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Colors.deepPurple, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 6, + spreadRadius: 1, + ), + ], + ), + child: Icon( + showSelectImage ? Icons.close : Icons.camera_alt, + color: Colors.white, + size: 4.w, + ), + ), + ), + ), + ], + ); + } + + Widget _buildProfileInfoCard() { + return Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + child: Padding( + padding: EdgeInsets.all(4.w), + child: Column( + children: [ + ListTile( + leading: Icon(Icons.person, color: Colors.deepPurple), + title: Text( + 'Name', + style: TextStyle(fontSize: 12.sp, color: Colors.grey), + ), + subtitle: Text( + localApi.userModel.name ?? 'Not provided', + style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500), + ), + ), + Divider(height: 1, color: Colors.grey[200]), + ListTile( + leading: Icon(Icons.email, color: Colors.deepPurple), + title: Text( + 'Email', + style: TextStyle(fontSize: 12.sp, color: Colors.grey), + ), + subtitle: Text( + localApi.userModel.email ?? 'Not provided', + style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w500), + ), + ), + ], + ), + ), + ); + } + + Widget _buildGamificationSection() { + return Container( + width: double.infinity, + padding: EdgeInsets.all(4.w), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + spreadRadius: 2, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Your Adventure Progress', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.bold, + color: Color(0xFF673AB7), + ), + ), + SizedBox(height: 2.h), + _buildProgressBar(), + SizedBox(height: 3.h), + Text( + 'Earned Badges', + style: TextStyle( + fontSize: 14.sp, + fontWeight: FontWeight.w600, ), ), + SizedBox(height: 2.h), + _buildBadgesGrid(), ], ), ); } + + Widget _buildProgressBar() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'Explorer Level 2', + style: TextStyle(fontSize: 14.sp, fontWeight: FontWeight.w600), + ), + Text( + '65%', + style: TextStyle(fontSize: 12.sp, color: Colors.grey), + ), + ], + ), + SizedBox(height: 1.h), + LinearProgressIndicator( + value: 0.65, + backgroundColor: Colors.grey[200], + valueColor: AlwaysStoppedAnimation(Colors.deepPurple), + minHeight: 1.5.h, + borderRadius: BorderRadius.circular(10), + ), + SizedBox(height: 1.h), + Text( + 'Complete 3 more hikes to reach next level', + style: TextStyle(fontSize: 12.sp, color: Colors.grey), + ), + ], + ); + } + + Widget _buildBadgesGrid() { + final achievements = [ + {'title': 'Trailblazer', 'earned': true, 'icon': Icons.flag}, + {'title': 'Nature Lover', 'earned': true, 'icon': Icons.forest}, + {'title': 'Pathfinder', 'earned': false, 'icon': Icons.directions}, + {'title': 'Marathoner', 'earned': false, 'icon': Icons.timer}, + {'title': 'Social Hiker', 'earned': true, 'icon': Icons.group}, + {'title': 'Peak Conqueror', 'earned': false, 'icon': Icons.star}, + ]; + + if (achievements.isEmpty) { + return _buildEmptyBadgesState(); + } + + return GridView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 4.w, + mainAxisSpacing: 4.w, + childAspectRatio: 0.9, + ), + itemCount: achievements.length, + itemBuilder: (context, index) { + final achievement = achievements[index]; + return Column( + children: [ + Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: achievement['earned'] as bool + ? Colors.deepPurple + : Colors.grey[300], + boxShadow: [ + BoxShadow( + color: (achievement['earned'] as bool + ? Colors.deepPurple + : Colors.grey[300]!) + .withOpacity(0.3), + blurRadius: 8, + spreadRadius: 2, + ), + ], + ), + child: Center( + child: Icon( + achievement['icon'] as IconData, + size: 10.w, + color: Colors.white, + ), + ), + ), + SizedBox(height: 1.h), + Text( + achievement['title'] as String, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 12.sp), + maxLines: 2, + ), + ], + ); + }, + ); + } + + Widget _buildEmptyBadgesState() { + return Column( + children: [ + Lottie.asset( + 'animations/empty_badges.json', + width: 50.w, + height: 20.h, + ), + SizedBox(height: 2.h), + RichText( + textAlign: TextAlign.center, + text: TextSpan( + style: TextStyle(fontSize: 16.sp, color: Colors.black), + children: [ + TextSpan(text: 'No achievements yet. '), + TextSpan( + text: 'Start exploring and engaging to earn badges!', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Color(0xFF673AB7), + ), + ), + ], + ), + ), + ], + ); + } + + Widget _buildActionButtons() { + return Column( + children: [ + SizedBox(height: 3.h), + HikeButton( + buttonWidth: double.infinity, + buttonHeight: 6.h, + text: 'Update Profile Image', + textSize: 14.sp, + onTap: _updateProfileImage, + ), + SizedBox(height: 1.h), + HikeButton( + buttonWidth: double.infinity, + buttonHeight: 6.h, + text: 'Cancel', + textSize: 14.sp, + isDotted: true, + onTap: () => setState(() => showSelectImage = false), + ), + ], + ); + } + + void _updateProfileImage() { + if (selectedImageIndex == -1) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text('Please select an image'), + behavior: SnackBarBehavior.floating, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10), + ), + ), + ); + return; + } + + HomeCubit homeCubit = locator(); + localApi.userModel.copyWithModel( + imageUrl: imageOptions[selectedImageIndex], + ); + homeCubit.updateUserImage( + localApi.userModel.id!, + imageOptions[selectedImageIndex], + ); + setState(() => showSelectImage = false); + } } From f60e48265124833dfae417cde76d47848f9164a8 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Thu, 17 Jul 2025 14:29:36 +0530 Subject: [PATCH 12/19] sos changes --- lib/core/queries/beacon.dart | 24 +- lib/core/queries/group.dart | 11 + lib/presentation/auth/auth_screen.dart | 367 ++++++++++++------ lib/presentation/auth/verfication_screen.dart | 10 + lib/presentation/group/group_screen.dart | 1 - .../cubit/location_cubit/location_cubit.dart | 19 - lib/presentation/hike/hike_screen.dart | 1 - .../hike/widgets/hike_screen_widget.dart | 11 - lib/presentation/widgets/screen_template.dart | 138 +++++++ 9 files changed, 433 insertions(+), 149 deletions(-) create mode 100644 lib/presentation/widgets/screen_template.dart diff --git a/lib/core/queries/beacon.dart b/lib/core/queries/beacon.dart index 3aed0e1..c7e93e0 100644 --- a/lib/core/queries/beacon.dart +++ b/lib/core/queries/beacon.dart @@ -10,6 +10,7 @@ class BeaconQueries { leader{ _id name + imageUrl } startsAt expiresAt @@ -67,6 +68,7 @@ deleteBeacon(id: "$id") leader { _id name + imageUrl } group{ _id @@ -79,6 +81,7 @@ deleteBeacon(id: "$id") followers { _id name + imageUrl } startsAt expiresAt @@ -116,6 +119,7 @@ deleteBeacon(id: "$id") _id name email + imageUrl beacons{ _id } @@ -131,6 +135,7 @@ deleteBeacon(id: "$id") followers { _id name + imageUrl } startsAt expiresAt @@ -208,7 +213,7 @@ deleteBeacon(id: "$id") leader { _id name - + imageUrl } location { lat @@ -217,6 +222,7 @@ deleteBeacon(id: "$id") followers { _id name + imageUrl } startsAt expiresAt @@ -397,10 +403,12 @@ deleteBeacon(id: "$id") landmark{ _id title + icon location{ lat lon } + } } @@ -443,6 +451,7 @@ deleteBeacon(id: "$id") _id name email + imageUrl location{ lat lon @@ -456,6 +465,16 @@ deleteBeacon(id: "$id") subscription StreamLocationUpdate($id: ID!){ beaconLocations(id: $id){ + userSOS { + _id + name + email + location{ + lat + lon + } + } + route{ lat lon @@ -474,6 +493,7 @@ deleteBeacon(id: "$id") landmark{ _id title + icon location{ lat lon @@ -482,7 +502,7 @@ deleteBeacon(id: "$id") _id name email - imageUrl + imageUrl } } diff --git a/lib/core/queries/group.dart b/lib/core/queries/group.dart index 042d600..9b127cc 100644 --- a/lib/core/queries/group.dart +++ b/lib/core/queries/group.dart @@ -41,10 +41,12 @@ class GroupQueries { leader { _id name + imageUrl } members { _id name + imageUrl } beacons { @@ -54,6 +56,7 @@ class GroupQueries { leader { _id name + imageUrl } location{ lat @@ -62,6 +65,7 @@ class GroupQueries { followers { _id name + imageUrl } startsAt expiresAt @@ -84,6 +88,7 @@ class GroupQueries { leader { _id name + imageUrl } members { _id @@ -97,6 +102,7 @@ class GroupQueries { leader { _id name + imageUrl } location{ lat @@ -105,6 +111,7 @@ class GroupQueries { followers { _id name + imageUrl } startsAt expiresAt @@ -245,10 +252,12 @@ query{ _id name email + imageUrl } followers { _id name + imageUrl } group{ _id @@ -298,6 +307,7 @@ query{ _id name email + imageUrl } } '''; @@ -307,6 +317,7 @@ query{ subscription StreamNewlyJoinedGroups($id: ID!){ groupJoined(id: $id){ name + imageUrl location{ lat lon diff --git a/lib/presentation/auth/auth_screen.dart b/lib/presentation/auth/auth_screen.dart index 06ac2cb..8361def 100644 --- a/lib/presentation/auth/auth_screen.dart +++ b/lib/presentation/auth/auth_screen.dart @@ -65,78 +65,161 @@ class _AuthScreenState extends State @override Widget build(BuildContext context) { - print( - "_currentPage: $_currentPage", - ); - Size screensize = MediaQuery.of(context).size; + + final screenSize = MediaQuery.of(context).size; + final screenWidth = screenSize.width; + final screenHeight = screenSize.height; + + // Adaptive padding + double getHorizontalPadding() { + if (screenWidth < 360) return screenWidth * 0.05; // 5% for small phones + if (screenWidth < 400) return screenWidth * 0.06; // 6% for medium phones + if (screenWidth < 600) return screenWidth * 0.08; // 8% for large phones + return screenWidth * 0.12; // 12% for tablets + } + + // Adaptive top spacing + double getTopSpacing() { + if (screenHeight < 600) return screenHeight * 0.08; // Small screens + if (screenHeight < 700) return screenHeight * 0.10; // Medium screens + if (screenHeight < 800) return screenHeight * 0.12; // Large screens + return screenHeight * 0.15; // Very large screens + } + + // Adaptive logo size + double getLogoWidth() { + if (screenWidth < 360) + return screenWidth * 0.65; // Smaller logo for small phones + if (screenWidth < 400) return screenWidth * 0.68; // Medium phones + if (screenWidth < 600) return screenWidth * 0.70; // Large phones + return screenWidth * 0.60; // Tablets (smaller relative size) + } + + // Adaptive spacing after logo + double getLogoBottomSpacing() { + if (screenHeight < 600) return screenHeight * 0.04; // Small screens + if (screenHeight < 700) return screenHeight * 0.06; // Medium screens + if (screenHeight < 800) return screenHeight * 0.07; // Large screens + return screenHeight * 0.08; // Very large screens + } + + // Adaptive text size for welcome message + TextStyle getWelcomeTextStyle() { + final baseStyle = Theme.of(context).textTheme.headlineMedium; + if (screenWidth < 360) { + return baseStyle?.copyWith(fontSize: 20) ?? TextStyle(fontSize: 20); + } + if (screenWidth < 400) { + return baseStyle?.copyWith(fontSize: 22) ?? TextStyle(fontSize: 22); + } + return baseStyle ?? TextStyle(fontSize: 24); + } + + final horizontalPadding = getHorizontalPadding(); + final topSpacing = getTopSpacing(); + final logoWidth = getLogoWidth(); + final logoBottomSpacing = getLogoBottomSpacing(); + final welcomeTextStyle = getWelcomeTextStyle(); return PopScope( - canPop: false, - onPopInvokedWithResult: (bool didPop, Object? result) async { - if (didPop) { - return; - } + canPop: false, + onPopInvokedWithResult: (bool didPop, Object? result) async { + if (didPop) { + return; + } - bool? popped = await onPopHome(); - if (popped == true) { - await SystemNavigator.pop(); + bool? popped = await onPopHome(); + if (popped == true) { + await SystemNavigator.pop(); + } + return; + }, + child: BlocConsumer( + listener: (context, state) { + if (state is SuccessState) { + appRouter.replaceNamed('/home'); + state.message != null + ? utils.showSnackBar(state.message!, context) + : null; + } else if (state is AuthVerificationState) { + context.read().navigate(); + } else if (state is AuthErrorState) { + utils.showSnackBar( + state.error!, + context, + duration: Duration(seconds: 2), + ); } - return; }, - child: BlocConsumer( - listener: (context, state) { - if (state is SuccessState) { - appRouter.replaceNamed('/home'); - state.message != null - ? utils.showSnackBar(state.message!, context) - : null; - } else if (state is AuthVerificationState) { - context.read().navigate(); - } else if (state is AuthErrorState) { - utils.showSnackBar(state.error!, context, - duration: Duration(seconds: 2)); - } - }, - builder: (context, state) { - return state is AuthLoadingState - ? LoadingScreen() - : Scaffold( - resizeToAvoidBottomInset: true, - body: SafeArea( - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: screensize.width * 0.08), - child: Column( - children: [ - SizedBox(height: screensize.height * 0.15), - Text( - 'welcome to', - style: Theme.of(context).textTheme.headlineMedium, - ), - SizedBox(height: screensize.height * 0.01), - Image.asset( - 'images/beacon_logo.png', - width: screensize.width * 0.7, - fit: BoxFit.contain, - ), - SizedBox(height: screensize.height * 0.08), - Expanded( - child: PageView( - physics: const NeverScrollableScrollPhysics(), - controller: _pageController, - children: [ - _buildSignIn(context), - _buildSignUp(context), + builder: (context, state) { + return state is AuthLoadingState + ? LoadingScreen() + : Scaffold( + resizeToAvoidBottomInset: true, + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: horizontalPadding, + ), + child: Column( + children: [ + SizedBox(height: topSpacing), + Text( + 'welcome to', + style: welcomeTextStyle, + textAlign: TextAlign.center, + ), + SizedBox(height: screenHeight * 0.01), + Container( + constraints: BoxConstraints( + maxWidth: 300, // Maximum logo width + minWidth: 200, // Minimum logo width + ), + child: Image.asset( + 'images/beacon_logo.png', + width: logoWidth, + fit: BoxFit.contain, + ), + ), + SizedBox(height: logoBottomSpacing), + Expanded( + child: Container( + constraints: BoxConstraints( + maxWidth: + 500, // Maximum form width for tablets + ), + child: PageView( + physics: + const NeverScrollableScrollPhysics(), + controller: _pageController, + children: [ + _buildSignIn(context), + _buildSignUp(context), + ], + ), + ), + ), + // Add some bottom padding for very small screens + if (screenHeight < 600) SizedBox(height: 20), ], ), ), - ], - ), - ), + ), + ); + }, ), - ); - }, - )); + ), + ); + }, + ), + ); } GlobalKey _signInFormKey = GlobalKey(); @@ -234,17 +317,61 @@ class _AuthScreenState extends State Widget _buildSignUp(BuildContext context) { final authCubit = BlocProvider.of(context); - Size screensize = MediaQuery.of(context).size; + final screenSize = MediaQuery.of(context).size; + final screenWidth = screenSize.width; + final screenHeight = screenSize.height; + + // Adaptive padding based on screen size + double getHorizontalPadding() { + if (screenWidth < 360) return 20; // Small phones + if (screenWidth < 400) return 35; // Medium phones + if (screenWidth < 600) return 50; // Large phones + return 70; // Tablets + } + + // Adaptive spacing + double getVerticalSpacing() { + if (screenHeight < 600) return 0.8.h; // Small screens + if (screenHeight < 800) return 1.2.h; // Medium screens + return 1.5.h; // Large screens + } + + // Adaptive button height + double getButtonHeight() { + if (screenHeight < 600) return 40; // Small screens + if (screenHeight < 800) return 45; // Medium screens + return 50; // Large screens + } + + // Adaptive font size + double getButtonFontSize() { + if (screenWidth < 360) return 14; + if (screenWidth < 400) return 15; + return 16; + } + + final horizontalPadding = getHorizontalPadding(); + final verticalSpacing = getVerticalSpacing(); + final buttonHeight = getButtonHeight(); + final buttonFontSize = getButtonFontSize(); + return Container( - width: screensize.width, + width: screenWidth, + padding: + EdgeInsets.symmetric(horizontal: 16), // Base padding for container child: SingleChildScrollView( child: Column( children: [ Form( key: _registerFormKey, child: Container( - width: screensize.width - 70, - child: Column(children: [ + width: screenWidth - (horizontalPadding * 2), + constraints: BoxConstraints( + maxWidth: 400, // Maximum width for tablets + minWidth: 280, // Minimum width for small phones + ), + child: Column( + children: [ CustomTextField( iconData: Icons.person_2_sharp, hintText: 'Name', @@ -253,9 +380,7 @@ class _AuthScreenState extends State nextFocusNode: signUpEmailFocus, validator: Validator.validateName, ), - SizedBox( - height: 1.2.h, - ), + SizedBox(height: verticalSpacing), CustomTextField( iconData: Icons.mail, hintText: 'Email Address', @@ -264,55 +389,67 @@ class _AuthScreenState extends State nextFocusNode: signUpPasswordFocus, validator: Validator.validateEmail, ), - SizedBox( - height: 1.2.h, - ), + SizedBox(height: verticalSpacing), CustomTextField( - iconData: Icons.lock, - hintText: 'Password', - controller: signUpPasswordController, - focusNode: signUpPasswordFocus, - showTrailing: true, - validator: Validator.validatePassword), - ])), - ), - SizedBox( - height: 1.2.h, + iconData: Icons.lock, + hintText: 'Password', + controller: signUpPasswordController, + focusNode: signUpPasswordFocus, + showTrailing: true, + validator: Validator.validatePassword, + ), + ], + ), + ), ), + SizedBox(height: verticalSpacing), Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(5.0)), - ), - child: BlocBuilder( - builder: (context, state) { - return ElevatedButton( - onPressed: () { - if (_registerFormKey.currentState!.validate()) { - authCubit.register( - signUpNameController.text.trim(), - signUpEmailController.text.trim(), - signUpPasswordController.text.trim()); - } else { - utils.showSnackBar( - 'Please complete all the fields', context); - } - }, - style: ElevatedButton.styleFrom( - backgroundColor: Colors.teal, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), - ), - minimumSize: Size(screensize.width - 70, 45)), - child: const Text( - 'Continue with Email', - style: TextStyle( - color: Colors.black, - fontSize: 16, - ), + width: screenWidth - (horizontalPadding * 2), + constraints: BoxConstraints( + maxWidth: 400, // Maximum width for tablets + minWidth: 280, // Minimum width for small phones + ), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(5.0)), + ), + child: BlocBuilder( + builder: (context, state) { + return ElevatedButton( + onPressed: () { + if (_registerFormKey.currentState!.validate()) { + authCubit.register( + signUpNameController.text.trim(), + signUpEmailController.text.trim(), + signUpPasswordController.text.trim(), + ); + } else { + utils.showSnackBar( + 'Please complete all the fields', + context, + ); + } + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.teal, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(14), ), - ); - }, - )), + minimumSize: Size(double.infinity, buttonHeight), + padding: EdgeInsets.symmetric(vertical: 12), + ), + child: Text( + 'Continue with Email', + style: TextStyle( + color: Colors.black, + fontSize: buttonFontSize, + fontWeight: FontWeight.w500, + ), + ), + ); + }, + ), + ), + SizedBox(height: verticalSpacing), _switchPageHelper(), ], ), diff --git a/lib/presentation/auth/verfication_screen.dart b/lib/presentation/auth/verfication_screen.dart index 9b1753d..2758ec3 100644 --- a/lib/presentation/auth/verfication_screen.dart +++ b/lib/presentation/auth/verfication_screen.dart @@ -95,6 +95,16 @@ class _VerificationScreenState extends State { color: Colors.black45, ), ), + // check spam folder + SizedBox(height: 2.h), + Text( + 'Check your spam folder if you can\'t \nfind the email', + style: TextStyle( + fontSize: 14, + color: Colors.redAccent, + ), + ), + SizedBox(height: 5.h), // OTP input fields Row( diff --git a/lib/presentation/group/group_screen.dart b/lib/presentation/group/group_screen.dart index 8e713fc..9fdda35 100644 --- a/lib/presentation/group/group_screen.dart +++ b/lib/presentation/group/group_screen.dart @@ -141,7 +141,6 @@ class _GroupScreenState extends State { Widget _buildMembersSection() { final memberCount = (widget.group.members?.length ?? 0) + 1; - print("leaders image: ${widget.group.leader?.imageUrl}"); return Row( children: [ if (widget.group.members?.isNotEmpty ?? false) diff --git a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart index 9127399..bf4c6c7 100644 --- a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart +++ b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart @@ -114,7 +114,6 @@ class LocationCubit extends Cubit { // // adding leader location if (beacon.leader != null) { _leader = beacon.leader!; - print('location state leader: ${_leader!.imageUrl}'); // creating leader location @@ -362,22 +361,11 @@ class LocationCubit extends Cubit { .listen((dataState) async { if (dataState is DataSuccess && dataState.data != null) { BeaconLocationsEntity beaconLocationsEntity = dataState.data!; - - print( - 'Location update subscription: ${beaconLocationsEntity.toString()}'); - - // when new landmark is created // when new landmark is created if (beaconLocationsEntity.landmark != null) { LandMarkEntity newLandMark = beaconLocationsEntity.landmark!; - - print('Creating landmark marker for: ${newLandMark.title}'); - print('Markers before: ${_hikeMarkers.length}'); - await _createLandMarkMarker(newLandMark); - print('Markers after: ${_hikeMarkers.length}'); - emit(LoadedLocationState( polyline: _polyline, locationMarkers: @@ -686,7 +674,6 @@ class LocationCubit extends Cubit { latlng.latitude.toString(), latlng.longitude.toString(), icon); if (dataState is DataSuccess && dataState.data != null) { - print('Local landmark created: ${dataState.data!.title}'); await _createLandMarkMarker(dataState.data!); await locationUpdateSubscription(beaconId); @@ -705,9 +692,7 @@ class LocationCubit extends Cubit { Future sendSOS(String id, BuildContext context) async { final dataState = await _hikeUseCase.sos(id); - if (dataState is DataSuccess) { - log('data coming from sos: ${dataState.data.toString()}'); // // Ensure _hikeMarkers is a Set of marker objects var userId = localApi.userModel.id; @@ -775,9 +760,6 @@ class LocationCubit extends Cubit { borderWidth: 4, ); - // final bitmap = await _createCustomMarkerBitmap(); - print("logging user marker creation: ${user.name}"); - final existingMarkers = _hikeMarkers.where((element) => element.markerId == markerId); @@ -835,7 +817,6 @@ class LocationCubit extends Cubit { Future createMarkerWithCircularNetworkImage( LandMarkEntity landmark) async { - print("Creating marker for landmark: ${landmark.createdBy?.imageUrl}"); final Uint8List markerIcon = await getCircularImageWithBorderAndPointer( landmark.icon ?? 'images/icons/location-marker.png', size: 80, diff --git a/lib/presentation/hike/hike_screen.dart b/lib/presentation/hike/hike_screen.dart index dcbcf4e..6cd9212 100644 --- a/lib/presentation/hike/hike_screen.dart +++ b/lib/presentation/hike/hike_screen.dart @@ -198,7 +198,6 @@ class _HikeScreenState extends State color: kYellow, ); } else if (state is LoadedLocationState) { - print('Location State: ${state.locationMarkers.length}'); return GoogleMap( circles: state.geofence, polylines: state.polyline, diff --git a/lib/presentation/hike/widgets/hike_screen_widget.dart b/lib/presentation/hike/widgets/hike_screen_widget.dart index 0c20fea..ad37e08 100644 --- a/lib/presentation/hike/widgets/hike_screen_widget.dart +++ b/lib/presentation/hike/widgets/hike_screen_widget.dart @@ -28,17 +28,6 @@ class HikeScreenWidget { Share.share('To join beacon follow this link: $url'); } - static Widget sosButton(String id, BuildContext context) { - return FloatingActionButton( - heroTag: 'sos', - backgroundColor: kYellow, - onPressed: () { - locator().sendSOS(id, context); - }, - child: Icon(Icons.sos), - ); - } - static Widget shareButton( BuildContext context, String? passkey, BeaconEntity beacon) { return FloatingActionButton( diff --git a/lib/presentation/widgets/screen_template.dart b/lib/presentation/widgets/screen_template.dart new file mode 100644 index 0000000..e6f78f1 --- /dev/null +++ b/lib/presentation/widgets/screen_template.dart @@ -0,0 +1,138 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:beacon/config/router/router.dart'; +import 'package:beacon/locator.dart'; +import 'package:beacon/presentation/auth/auth_cubit/auth_cubit.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; + +import 'hike_button.dart'; + +class BeaconScreenTemplate extends StatelessWidget { + final Widget body; + final bool showAppBar; + final bool showLogout; + final bool showBottomNav; + + const BeaconScreenTemplate({ + super.key, + required this.body, + this.showAppBar = true, + this.showBottomNav = false, + this.showLogout = false, + }); + + @override + Widget build(BuildContext context) { + return Scaffold( + resizeToAvoidBottomInset: false, + body: SafeArea( + child: Column( + children: [ + if (showAppBar) _buildAppBar(context), + Expanded(child: body), + ], + ), + ), + bottomNavigationBar: showBottomNav ? _buildBottomNavigationBar() : null, + ); + } + + Widget _buildAppBar(BuildContext context) { + return Padding( + padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon(Icons.arrow_back, color: Colors.grey, size: 20.sp), + onPressed: () => AutoRouter.of(context).maybePop(), + ), + Image.asset('images/beacon_logo.png', height: 4.h), + showLogout + ? IconButton( + icon: Icon(Icons.power_settings_new, + color: Colors.grey, size: 20.sp), + onPressed: () => _showLogoutDialog(context), + ) + : // profile icon + IconButton( + icon: SizedBox( + width: 34, + height: 34, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(100), + border: Border.all(color: Colors.purple, width: 1), + color: Colors.grey.shade200, + image: DecorationImage( + image: NetworkImage(localApi.userModel.imageUrl!), + fit: BoxFit.cover, + ), + )), + ), + onPressed: () { + AutoRouter.of(context).push(ProfileScreenRoute()); + }, + ), + ], + ), + ); + } + + void _showLogoutDialog(BuildContext context) { + showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.0), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 5.w, vertical: 2.h), + title: Text( + 'Logout', + style: TextStyle(fontSize: 18.sp, fontWeight: FontWeight.w600), + ), + content: Text( + 'Are you sure you want to logout?', + style: TextStyle(fontSize: 14.sp), + ), + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + HikeButton( + buttonWidth: 25.w, + buttonHeight: 5.h, + isDotted: true, + onTap: () => AutoRouter.of(context).maybePop(false), + text: 'No', + textSize: 14.sp, + ), + HikeButton( + buttonWidth: 25.w, + buttonHeight: 5.h, + onTap: () { + appRouter.replaceNamed('/auth'); + localApi.deleteUser(); + context.read().googleSignOut(); + }, + text: 'Yes', + textSize: 14.sp, + ), + ], + ), + ], + ), + ); + } + + Widget _buildBottomNavigationBar() { + return BottomNavigationBar( + items: const [ + BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'), + BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Profile'), + BottomNavigationBarItem(icon: Icon(Icons.hiking), label: 'Hike'), + ], + ); + } +} From b376ccf127c45b2c62bf10f9ca5c8846b1ebfed2 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sat, 16 Aug 2025 12:53:04 +0530 Subject: [PATCH 13/19] upgrade gradle version --- android/app/build.gradle | 27 +- android/app/proguard-rules.pro | 1 + .../ccextractor/beaconmobile/MainActivity.kt | 8 - android/build.gradle | 16 +- android/gradle.properties | 2 +- .../gradle/wrapper/gradle-wrapper.properties | 4 +- lib/config/enviornment_config.dart | 18 +- lib/config/graphql_config.dart | 4 +- lib/config/local_notification.dart | 5 +- .../datasource/remote/remote_auth_api.dart | 10 +- .../datasource/remote/remote_hike_api.dart | 6 + .../auth_repository_implementation.dart | 3 +- lib/domain/repositories/auth_repository.dart | 3 +- lib/domain/usecase/auth_usecase.dart | 3 +- .../auth/auth_cubit/auth_cubit.dart | 4 +- lib/presentation/auth/auth_screen.dart | 1 - .../group/advance_options_screen.dart | 4 +- lib/presentation/hike/hike_screen.dart | 216 ++++++++-------- .../hike/services/geoapify_service.dart | 8 +- lib/presentation/splash/splash_screen.dart | 91 ++----- pubspec.lock | 236 ++++++++---------- pubspec.yaml | 11 +- 22 files changed, 308 insertions(+), 373 deletions(-) create mode 100644 android/app/proguard-rules.pro diff --git a/android/app/build.gradle b/android/app/build.gradle index b795e20..35c7e65 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,6 @@ apply plugin: 'com.google.gms.google-services' // END: FlutterFire Configuration apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" -apply from: project(':flutter_config').projectDir.getPath() + "/dotenv.gradle" def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') @@ -36,13 +35,28 @@ if (keystorePropertiesFile.exists()) { android { + namespace "com.ccextractor.beaconmobile" compileSdkVersion 34 buildToolsVersion '29.0.0' + compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = '17' // Match the Java version + } + sourceSets { main.java.srcDirs += 'src/main/kotlin' } + buildFeatures { + buildConfig true + } + defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.ccextractor.beaconmobile" @@ -79,7 +93,18 @@ flutter { source '../..' } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +kotlin { + jvmToolchain(17) +} + dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "com.android.support:multidex:1.0.3" + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..068d7d2 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1 @@ +-keep class com.ccextractor.beaconmobile.BuildConfig { *; } \ No newline at end of file diff --git a/android/app/src/main/kotlin/com/ccextractor/beaconmobile/MainActivity.kt b/android/app/src/main/kotlin/com/ccextractor/beaconmobile/MainActivity.kt index b1daf95..b51d3f2 100644 --- a/android/app/src/main/kotlin/com/ccextractor/beaconmobile/MainActivity.kt +++ b/android/app/src/main/kotlin/com/ccextractor/beaconmobile/MainActivity.kt @@ -2,20 +2,12 @@ package com.ccextractor.beaconmobile import android.content.res.Configuration import androidx.annotation.NonNull -import cl.puntito.simple_pip_mode.PipCallbackHelper import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine class MainActivity : FlutterActivity() { - private var callbackHelper = PipCallbackHelper() override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) - callbackHelper.configureFlutterEngine(flutterEngine) - } - - override fun onPictureInPictureModeChanged(active: Boolean, newConfig: Configuration?) { - super.onPictureInPictureModeChanged(active, newConfig) - callbackHelper.onPictureInPictureModeChanged(active) } } diff --git a/android/build.gradle b/android/build.gradle index 8a87ad1..f5804e6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.8.20' + ext.kotlin_version = '2.2.0' repositories { google() jcenter() @@ -9,7 +9,7 @@ buildscript { // START: FlutterFire Configuration classpath 'com.google.gms:google-services:4.3.15' // END: FlutterFire Configuration - classpath 'com.android.tools.build:gradle:7.1.0' + classpath 'com.android.tools.build:gradle:8.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -35,6 +35,15 @@ subprojects { buildToolsVersion '29.0.0' } } + + // Fix namespace for geolocator_android + if (project.name == 'geolocator_android') { + project.android { + namespace 'com.baseflow.geolocator' + } + } + + } } @@ -42,10 +51,11 @@ rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" } + subprojects { project.evaluationDependsOn(':app') } tasks.register("clean", Delete) { delete rootProject.buildDir -} +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index 4bc5c4b..faa4095 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true -android.enableJetifier=true +android.enableJetifier=false kotlin.version=1.8.20 diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 302da31..a363877 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip \ No newline at end of file diff --git a/lib/config/enviornment_config.dart b/lib/config/enviornment_config.dart index 0b8b8a4..71059e5 100644 --- a/lib/config/enviornment_config.dart +++ b/lib/config/enviornment_config.dart @@ -1,21 +1,25 @@ import 'dart:io'; -import 'package:flutter_config/flutter_config.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; class EnvironmentConfig { - static String? get httpEndpoint => FlutterConfig.get('HTTP_ENDPOINT'); + static String? get httpEndpoint => dotenv.env['HTTP_ENDPOINT']; - static String? get websocketEndpoint => - FlutterConfig.get('WEBSOCKET_ENDPOINT'); + static String? get websocketEndpoint => dotenv.env['WEBSOCKET_ENDPOINT']; static String? get googleMapApi { if (Platform.isAndroid) { - return FlutterConfig.get('ANDROID_MAP_API_KEY'); + return dotenv.env['ANDROID_MAP_API_KEY']; } - return FlutterConfig.get('IOS_MAP_API_KEY'); + return dotenv.env['IOS_MAP_API_KEY']; } + static String? get geoApifyApiKey => dotenv.env['GEOAPIFY_API_KEY']; + + static String? get openWeatherMapApiKey => + dotenv.env['OPEN_WEATHER_MAP_API_KEY']; + static Future loadEnvVariables() async { - await FlutterConfig.loadEnvVariables(); + await dotenv.load(fileName: '.env'); } } diff --git a/lib/config/graphql_config.dart b/lib/config/graphql_config.dart index 44fde83..0f7ee65 100644 --- a/lib/config/graphql_config.dart +++ b/lib/config/graphql_config.dart @@ -12,7 +12,7 @@ class GraphQLConfig { WebSocketLink? _webSocketLink; static final HttpLink httpLink = HttpLink( EnvironmentConfig.httpEndpoint ?? - 'https://beacon-backend-25.onrender.com/graphql', + 'https://beacon-backend-0kpr.onrender.com/graphql', ); Future _loadAuthLink() async { @@ -24,7 +24,7 @@ class GraphQLConfig { await _getToken(); _webSocketLink = WebSocketLink( EnvironmentConfig.websocketEndpoint ?? - 'ws://beacon-backend-25.onrender.com/graphql', + 'wss://beacon-backend-0kpr.onrender.com/graphql', config: SocketClientConfig( autoReconnect: true, initialPayload: {"Authorization": token}, diff --git a/lib/config/local_notification.dart b/lib/config/local_notification.dart index 99bf4cb..e0c09c1 100644 --- a/lib/config/local_notification.dart +++ b/lib/config/local_notification.dart @@ -18,10 +18,7 @@ class LocalNotification { const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('app_icon'); final DarwinInitializationSettings initializationSettingsIOS = - DarwinInitializationSettings( - onDidReceiveLocalNotification: (_, __, ___, ____) {} - // as Future Function(int, String?, String?, String?)? - ); + DarwinInitializationSettings(); final InitializationSettings initializationSettings = InitializationSettings( android: initializationSettingsAndroid, diff --git a/lib/data/datasource/remote/remote_auth_api.dart b/lib/data/datasource/remote/remote_auth_api.dart index 2e684e7..41233fa 100644 --- a/lib/data/datasource/remote/remote_auth_api.dart +++ b/lib/data/datasource/remote/remote_auth_api.dart @@ -86,18 +86,16 @@ class RemoteAuthApi { } } - Future> gAuth(String name, String email, String? imageUrl) async { - log('name: $name'); - log('email: $email'); - + Future> gAuth( + String name, String email, String? imageUrl) async { final isConnected = await utils.checkInternetConnectivity(); if (!isConnected) { return DataFailed('Beacon is trying to connect with internet...'); } - final QueryResult result = await clientNonAuth.mutate( - MutationOptions(document: gql(_authQueries.gAuth(name, email, imageUrl)))); + final QueryResult result = await clientNonAuth.mutate(MutationOptions( + document: gql(_authQueries.gAuth(name, email, imageUrl)))); log(result.toString()); diff --git a/lib/data/datasource/remote/remote_hike_api.dart b/lib/data/datasource/remote/remote_hike_api.dart index f047bef..ce9820a 100644 --- a/lib/data/datasource/remote/remote_hike_api.dart +++ b/lib/data/datasource/remote/remote_hike_api.dart @@ -128,8 +128,14 @@ class RemoteHikeApi { if (stream.hasException) { yield DataFailed('Something went wrong'); } else { + print('Stream data: ${stream.data}'); var locations = BeaconLocationsModel.fromJson(stream.data!['beaconLocations']); + print('Locations: ${locations.user}'); + print('Locations: ${locations.route}'); + print('Locations: ${locations.userSOS}'); + //print('Locations: ${locations.landmarks}'); + yield DataSuccess(locations); } } diff --git a/lib/data/repositories/auth_repository_implementation.dart b/lib/data/repositories/auth_repository_implementation.dart index 5bb2f81..533452d 100644 --- a/lib/data/repositories/auth_repository_implementation.dart +++ b/lib/data/repositories/auth_repository_implementation.dart @@ -20,7 +20,8 @@ class AuthRepositoryImplementation implements AuthRepository { } @override - Future> oAuth(String name, String email, String? imageUrl) { + Future> oAuth( + String name, String email, String? imageUrl) { return remoteAuthApi.gAuth(name, email, imageUrl); } diff --git a/lib/domain/repositories/auth_repository.dart b/lib/domain/repositories/auth_repository.dart index 95bb421..b53350c 100644 --- a/lib/domain/repositories/auth_repository.dart +++ b/lib/domain/repositories/auth_repository.dart @@ -12,7 +12,8 @@ abstract class AuthRepository { // Login function Future> login(String email, String password); - Future> oAuth(String name, String email, String? imageUrl); + Future> oAuth( + String name, String email, String? imageUrl); Future> sendVerificationCode(); diff --git a/lib/domain/usecase/auth_usecase.dart b/lib/domain/usecase/auth_usecase.dart index 6856ad1..8081e91 100644 --- a/lib/domain/usecase/auth_usecase.dart +++ b/lib/domain/usecase/auth_usecase.dart @@ -17,7 +17,8 @@ class AuthUseCase { return authRepository.login(email, password); } - Future> oAuthUseCase(String name, String email, String? imageUrl) async { + Future> oAuthUseCase( + String name, String email, String? imageUrl) async { return authRepository.oAuth(name, email, imageUrl); } diff --git a/lib/presentation/auth/auth_cubit/auth_cubit.dart b/lib/presentation/auth/auth_cubit/auth_cubit.dart index 9e43942..550893f 100644 --- a/lib/presentation/auth/auth_cubit/auth_cubit.dart +++ b/lib/presentation/auth/auth_cubit/auth_cubit.dart @@ -83,8 +83,8 @@ class AuthCubit extends Cubit { if (gAuth != null && gAuth.displayName != null) { // pass imageurl - var dataState = - await authUseCase.oAuthUseCase(gAuth.displayName!, gAuth.email, gAuth.photoUrl); + var dataState = await authUseCase.oAuthUseCase( + gAuth.displayName!, gAuth.email, gAuth.photoUrl); if (dataState is DataSuccess && dataState.data != null) { emit(SuccessState()); diff --git a/lib/presentation/auth/auth_screen.dart b/lib/presentation/auth/auth_screen.dart index 8361def..928fdde 100644 --- a/lib/presentation/auth/auth_screen.dart +++ b/lib/presentation/auth/auth_screen.dart @@ -65,7 +65,6 @@ class _AuthScreenState extends State @override Widget build(BuildContext context) { - final screenSize = MediaQuery.of(context).size; final screenWidth = screenSize.width; final screenHeight = screenSize.height; diff --git a/lib/presentation/group/advance_options_screen.dart b/lib/presentation/group/advance_options_screen.dart index b6a1d10..0374724 100644 --- a/lib/presentation/group/advance_options_screen.dart +++ b/lib/presentation/group/advance_options_screen.dart @@ -1,4 +1,5 @@ import 'package:auto_route/annotations.dart'; +import 'package:beacon/config/enviornment_config.dart'; import 'package:beacon/locator.dart'; import 'package:beacon/presentation/group/cubit/group_cubit/group_cubit.dart'; import 'package:beacon/presentation/hike/services/geoapify_service.dart'; @@ -105,7 +106,8 @@ class _AdvancedOptionsScreenState extends State { }); try { - const String apiKey = '03fe30be078c0bfb823d954404de6a6b'; + final String apiKey = EnvironmentConfig.openWeatherMapApiKey!; + // '03fe30be078c0bfb823d954404de6a6b'; String apiUrl; if (widget.isScheduled) { diff --git a/lib/presentation/hike/hike_screen.dart b/lib/presentation/hike/hike_screen.dart index 6cd9212..f02ef50 100644 --- a/lib/presentation/hike/hike_screen.dart +++ b/lib/presentation/hike/hike_screen.dart @@ -1,28 +1,20 @@ import 'package:auto_route/auto_route.dart'; -import 'package:beacon/config/pip_manager.dart'; import 'package:beacon/core/utils/constants.dart'; import 'package:beacon/domain/entities/beacon/beacon_entity.dart'; import 'package:beacon/locator.dart'; -import 'package:beacon/presentation/auth/auth_cubit/auth_cubit.dart'; import 'package:beacon/presentation/hike/cubit/hike_cubit/hike_cubit.dart'; import 'package:beacon/presentation/hike/cubit/hike_cubit/hike_state.dart'; import 'package:beacon/presentation/hike/cubit/location_cubit/location_cubit.dart'; import 'package:beacon/presentation/hike/cubit/location_cubit/location_state.dart'; - import 'package:beacon/presentation/hike/widgets/hike_screen_widget.dart'; import 'package:beacon/presentation/hike/widgets/search_places.dart'; import 'package:beacon/presentation/widgets/hike_button.dart'; import 'package:beacon/presentation/widgets/screen_template.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; - import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; -import 'package:simple_pip_mode/pip_widget.dart'; -import 'package:simple_pip_mode/simple_pip.dart'; -import 'package:sliding_up_panel/sliding_up_panel.dart'; @RoutePage() class HikeScreen extends StatefulWidget { @@ -44,14 +36,12 @@ class _HikeScreenState extends State void initState() { WidgetsBinding.instance.addObserver(this); _hikeCubit.startHike(widget.beacon.id!, this, context); - SimplePip().setAutoPipMode(aspectRatio: (2, 3)); super.initState(); } @override void dispose() { WidgetsBinding.instance.removeObserver(this); - PIPMode.disablePIPMode(); _hikeCubit.clear(); _locationCubit.clear(); super.dispose(); @@ -60,126 +50,116 @@ class _HikeScreenState extends State @override void didChangeAppLifecycleState(AppLifecycleState state) { super.didChangeAppLifecycleState(state); - if (state == AppLifecycleState.paused) {} } bool isSmallsized = 100.h < 800; - PanelController _panelController = PanelController(); @override Widget build(BuildContext context) { return Scaffold( - body: PipWidget( - onPipExited: () { - _panelController.open(); - }, - builder: (context) { - return BlocBuilder( - builder: (context, state) { - if (state is InitialHikeState) { - return Center( - child: SpinKitWave( - color: kYellow, - )); - } else if (state is ErrorHikeState) { - return Container( - child: Center(child: Text('Restart beacon')), - ); - } else { - return BeaconScreenTemplate( - body: Stack( + body: BlocBuilder( + builder: (context, state) { + if (state is InitialHikeState) { + return Center( + child: SpinKitWave( + color: kYellow, + )); + } else if (state is ErrorHikeState) { + return Container( + child: Center(child: Text('Restart beacon')), + ); + } else { + return BeaconScreenTemplate( + body: Stack( + children: [ + _mapScreen(), + LocationSearchWidget(widget.beacon.id!), + Positioned( + bottom: 200, + right: 10, + child: Column( children: [ - _mapScreen(), - LocationSearchWidget(widget.beacon.id!), - Positioned( - bottom: 200, - right: 10, - child: Column( - children: [ - FloatingActionButton( - backgroundColor: Colors.white, - mini: true, - onPressed: () => _locationCubit.zoomIn(), - child: Icon(Icons.add), - ), - //SizedBox(height: 2), - FloatingActionButton( - backgroundColor: Colors.white, - mini: true, - onPressed: () => _locationCubit.zoomOut(), - child: Icon(Icons.remove), - ), - SizedBox(height: 2), - FloatingActionButton( - backgroundColor: Colors.white, - mini: true, - onPressed: () => _locationCubit.centerMap(), - child: Icon(Icons.map), - ), - ], + FloatingActionButton( + backgroundColor: Colors.white, + mini: true, + onPressed: () => _locationCubit.zoomIn(), + child: Icon(Icons.add), + ), + FloatingActionButton( + backgroundColor: Colors.white, + mini: true, + onPressed: () => _locationCubit.zoomOut(), + child: Icon(Icons.remove), + ), + SizedBox(height: 2), + FloatingActionButton( + backgroundColor: Colors.white, + mini: true, + onPressed: () => _locationCubit.centerMap(), + child: Icon(Icons.map), + ), + ], + ), + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + height: 100, + child: Container( + height: 100, + width: double.infinity, + decoration: BoxDecoration( + color: Colors.grey[50], + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), + topRight: Radius.circular(20), ), ), - Positioned( - bottom: 0, - left: 0, - right: 0, - height: 100, - child: Container( - height: 100, - width: double.infinity, - decoration: BoxDecoration( - color: Colors.grey[50], - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20), - topRight: Radius.circular(20), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Left Circular Icon Button - Container( - width: 50, - height: 50, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.white, - boxShadow: [ - BoxShadow( - color: Colors.black12, - blurRadius: 4, - ), - ], - image: DecorationImage( - image: NetworkImage( - "https://media.istockphoto.com/id/1253926432/vector/flashlight-warning-alarm-light-and-siren-light-flat-design-vector-design.jpg?s=612x612&w=0&k=20&c=yOj6Jpu7XDrPJCTfUIpQm-LWI9q9RWQB91s-N7CgQDQ="))), - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // Left Circular Icon Button + Container( + width: 50, + height: 50, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 4, + ), + ], + image: DecorationImage( + image: NetworkImage( + "https://media.istockphoto.com/id/1253926432/vector/flashlight-warning-alarm-light-and-siren-light-flat-design-vector-design.jpg?s=612x612&w=0&k=20&c=yOj6Jpu7XDrPJCTfUIpQm-LWI9q9RWQB91s-N7CgQDQ="))), + ), - const SizedBox(width: 10), + const SizedBox(width: 10), - // Right Red SOS Button - HikeButton( - buttonWidth: 70.w, - buttonHeight: 50, - text: 'Send SOS', - onTap: () { - locator() - .sendSOS(widget.beacon.id!, context); - }, - textSize: 18.0, - buttonColor: Colors.red, - textColor: Colors.white, - ) - ], - ), - )), - ], - )); - } - }, - ); - }, - pipChild: _mapScreen()), + // Right Red SOS Button + HikeButton( + buttonWidth: 70.w, + buttonHeight: 50, + text: 'Send SOS', + onTap: () { + locator() + .sendSOS(widget.beacon.id!, context); + }, + textSize: 18.0, + buttonColor: Colors.red, + textColor: Colors.white, + ) + ], + ), + )), + ], + )); + } + }, + ), ); } diff --git a/lib/presentation/hike/services/geoapify_service.dart b/lib/presentation/hike/services/geoapify_service.dart index dcde65a..31ff9d8 100644 --- a/lib/presentation/hike/services/geoapify_service.dart +++ b/lib/presentation/hike/services/geoapify_service.dart @@ -1,9 +1,11 @@ import 'dart:convert'; +import 'package:beacon/config/enviornment_config.dart'; import 'package:beacon/data/models/landmark/location_suggestion.dart'; import 'package:http/http.dart' as http; class GeoapifyService { - static const String _apiKey = '9ee96579425c4945ab53a5134d533b2c'; + static String _apiKey = EnvironmentConfig.geoApifyApiKey!; + static const String _baseUrl = 'https://api.geoapify.com/v1/geocode/autocomplete'; @@ -17,11 +19,9 @@ class GeoapifyService { try { final response = await http.get(url); - print("Fetching location suggestions for query: $query"); if (response.statusCode == 200) { final data = jsonDecode(response.body); - print("Response received with ${data['results']?.length ?? 0} results"); List results = data['results'] ?? []; @@ -29,12 +29,10 @@ class GeoapifyService { return LocationSuggestion.fromJson(item); }).toList(); } else { - print("Error: ${response.statusCode} - ${response.body}"); throw Exception( "Failed to fetch location suggestions: ${response.statusCode}"); } } catch (e) { - print("Exception occurred: $e"); throw Exception("Failed to fetch location suggestions: $e"); } } diff --git a/lib/presentation/splash/splash_screen.dart b/lib/presentation/splash/splash_screen.dart index ad8aa20..87f4b20 100644 --- a/lib/presentation/splash/splash_screen.dart +++ b/lib/presentation/splash/splash_screen.dart @@ -1,16 +1,10 @@ -import 'dart:async'; - import 'package:auto_route/auto_route.dart'; import 'package:beacon/config/router/router.dart'; -import 'package:beacon/core/resources/data_state.dart'; import 'package:beacon/domain/entities/user/user_entity.dart'; import 'package:beacon/domain/usecase/auth_usecase.dart'; -import 'package:beacon/domain/usecase/group_usecase.dart'; import 'package:beacon/locator.dart'; import 'package:beacon/presentation/auth/verification_cubit/verification_cubit.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:uni_links/uni_links.dart'; import '../widgets/loading_screen.dart'; @@ -23,83 +17,40 @@ class SplashScreen extends StatefulWidget { } class _SplashScreenState extends State { - bool isCheckingUrl = false; - @override void initState() { - handleLinks(); super.initState(); + _initApp(); } - StreamSubscription? _sub; - Uri? _latestUri; - Uri? _initialUri; - - handleLinks() async { - _sub = uriLinkStream.listen((Uri? uri) { - if (!mounted) return; - setState(() { - _latestUri = uri; - }); - }, onError: (Object err) { - if (!mounted) return; - setState(() { - _latestUri = null; - }); - }); - - try { - final uri = await getInitialUri(); - if (!mounted) return; - setState(() => _initialUri = uri); - } on PlatformException { - if (!mounted) return; - setState(() => _initialUri = null); - } on FormatException catch (err) { - debugPrint(err.toString()); - if (!mounted) return; - setState(() => _initialUri = null); - } - + Future _initApp() async { await sp.init(); await localApi.init(); final authUseCase = locator(); - await localApi.userloggedIn().then((value) async { - if (_latestUri == null && _initialUri == null) { - bool isConnected = await utils.checkInternetConnectivity(); - if (isConnected) { - final userInfo = await authUseCase.getUserInfoUseCase(); - if (userInfo.data != null) { - await func(userInfo.data!); - } else { - appRouter.replaceNamed('/auth'); - } + await localApi.userloggedIn().then((loggedIn) async { + if (!loggedIn!) { + appRouter.replaceNamed('/auth'); + return; + } + + bool isConnected = await utils.checkInternetConnectivity(); + if (isConnected) { + final userInfo = await authUseCase.getUserInfoUseCase(); + if (userInfo.data != null) { + await _handleUser(userInfo.data!); } else { - appRouter.replaceNamed('/home'); - utils.showSnackBar( - 'Please connect to your internet connection!', context); + appRouter.replaceNamed('/auth'); } } else { - if (_initialUri != null) { - var shortcode = _initialUri!.queryParameters['shortcode']; - if (value == true && shortcode != null) { - await locator().joinHike(shortcode).then((dataState) { - if (dataState is DataSuccess) { - appRouter.push(HikeScreenRoute( - beacon: dataState.data!, - isLeader: dataState.data!.id == localApi.userModel.id)); - } else { - appRouter.push(HomeScreenRoute()); - } - }); - } - } + appRouter.replaceNamed('/home'); + utils.showSnackBar( + 'Please connect to your internet connection!', context); } }); } - Future func(UserEntity user) async { + Future _handleUser(UserEntity user) async { var time = await sp.loadData('time'); var otp = await sp.loadData('otp'); if (user.isVerified == true) { @@ -125,12 +76,6 @@ class _SplashScreenState extends State { } } - @override - void dispose() { - _sub?.cancel(); - super.dispose(); - } - @override Widget build(BuildContext context) { return Scaffold( diff --git a/pubspec.lock b/pubspec.lock index 7c30c06..f6c011e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -34,10 +34,10 @@ packages: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" async: dependency: transitive description: @@ -98,10 +98,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "294a2edaf4814a378725bfe6358210196f5ea37af89ecd81bfa32960113d4948" + sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" url: "https://pub.dev" source: hosted - version: "4.0.3" + version: "4.0.4" build_resolvers: dependency: transitive description: @@ -138,10 +138,10 @@ packages: dependency: transitive description: name: built_value - sha256: "28a712df2576b63c6c005c465989a348604960c0958d28be5303ba9baa841ac2" + sha256: "0b1b12a0a549605e5f04476031cd0bc91ead1d7c8e830773a18ee54179b3cb62" url: "https://pub.dev" source: hosted - version: "8.9.3" + version: "8.11.0" change_app_package_name: dependency: "direct main" description: @@ -166,6 +166,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: @@ -202,10 +210,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: b74247fad72c171381dbe700ca17da24deac637ab6d43c343b42867acb95c991 + sha256: "224a77051d52a11fbad53dd57827594d3bd24f945af28bd70bab376d68d437f0" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "5.0.2" connectivity_plus_platform_interface: dependency: transitive description: @@ -226,10 +234,10 @@ packages: dependency: transitive description: name: coverage - sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.15.0" cross_file: dependency: transitive description: @@ -362,10 +370,10 @@ packages: dependency: transitive description: name: firebase_core_platform_interface - sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf + sha256: "8bcfad6d7033f5ea951d15b867622a824b13812178bfec0c779b9d81de011bbb" url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.4.2" firebase_core_web: dependency: transitive description: @@ -403,14 +411,6 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.6" - flutter_config: - dependency: "direct main" - description: - name: flutter_config - sha256: a07e6156bb6e776e29c6357be433155acda87d1dab1a3f787a72091a1b71ffbf - url: "https://pub.dev" - source: hosted - version: "2.0.2" flutter_countdown_timer: dependency: "direct main" description: @@ -419,6 +419,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: b7c7be5cd9f6ef7a78429cabd2774d3c4af50e79cb2b7593e3d5d763ef95c61b + url: "https://pub.dev" + source: hosted + version: "5.2.1" flutter_geocoder_alternative: dependency: "direct main" description: @@ -431,10 +439,10 @@ packages: dependency: transitive description: name: flutter_hooks - sha256: "6a126f703b89499818d73305e4ce1e3de33b4ae1c5512e3b8eab4b986f46774c" + sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 url: "https://pub.dev" source: hosted - version: "0.18.6" + version: "0.20.5" flutter_keyboard_visibility: dependency: transitive description: @@ -495,34 +503,34 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" + sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 url: "https://pub.dev" source: hosted - version: "17.2.4" + version: "18.0.1" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af + sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "5.0.0" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "8.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e url: "https://pub.dev" source: hosted - version: "2.0.24" + version: "2.0.28" flutter_polyline_points: dependency: "direct main" description: @@ -626,18 +634,18 @@ packages: dependency: transitive description: name: geolocator_apple - sha256: c4ecead17985ede9634f21500072edfcb3dba0ef7b97f8d7bc556d2d722b3ba3 + sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 url: "https://pub.dev" source: hosted - version: "2.3.9" + version: "2.3.13" geolocator_platform_interface: dependency: transitive description: name: geolocator_platform_interface - sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012" + sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "4.2.6" geolocator_web: dependency: transitive description: @@ -650,10 +658,10 @@ packages: dependency: transitive description: name: geolocator_windows - sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e" + sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.2.5" get_it: dependency: "direct main" description: @@ -674,10 +682,10 @@ packages: dependency: transitive description: name: google_identity_services_web - sha256: "55580f436822d64c8ff9a77e37d61f5fb1e6c7ec9d632a43ee324e2a05c3c6c9" + sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454" url: "https://pub.dev" source: hosted - version: "0.3.3" + version: "0.3.3+1" google_maps: dependency: transitive description: @@ -690,154 +698,154 @@ packages: dependency: "direct main" description: name: google_maps_flutter - sha256: "209856c8e5571626afba7182cf634b2910069dc567954e76ec3e3fb37f5e9db3" + sha256: e1805e5a5885bd14a1c407c59229f478af169bf4d04388586b19f53145a5db3a url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.12.3" google_maps_flutter_android: dependency: transitive description: name: google_maps_flutter_android - sha256: "1b69fbb3ab76e7a7dfcf25e60f32f81ae5d9b88285343eecb5479116d54be869" + sha256: "67745f7850655faa8a596606b627fece63f3011078eaa0c151a4774568c23ac4" url: "https://pub.dev" source: hosted - version: "2.14.12" + version: "2.17.0" google_maps_flutter_ios: dependency: transitive description: name: google_maps_flutter_ios - sha256: "6f798adb0aa1db5adf551f2e39e24bd06c8c0fbe4de912fb2d9b5b3f48147b02" + sha256: d03678415da9de8ce7208c674b264fc75946f326e696b4b7f84c80920fc58df6 url: "https://pub.dev" source: hosted - version: "2.13.2" + version: "2.15.4" google_maps_flutter_platform_interface: dependency: transitive description: name: google_maps_flutter_platform_interface - sha256: "74bf554a3697765654d3b9140e47e6bff2fdc1e91b8a4f8eafe37de44932423c" + sha256: f8293f072ed8b068b092920a72da6693aa8b3d62dc6b5c5f0bc44c969a8a776c url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.12.1" google_maps_flutter_web: dependency: transitive description: name: google_maps_flutter_web - sha256: ff39211bd25d7fad125d19f757eba85bd154460907cd4d135e07e3d0f98a4130 + sha256: ce2cac714e5462bf761ff2fdfc3564c7e5d7ed0578268dccb0a54dbdb1e6214e url: "https://pub.dev" source: hosted - version: "0.5.10" + version: "0.5.12+2" google_sign_in: dependency: "direct main" description: name: google_sign_in - sha256: fad6ddc80c427b0bba705f2116204ce1173e09cf299f85e053d57a55e5b2dd56 + sha256: d0a2c3bcb06e607bb11e4daca48bd4b6120f0bbc4015ccebbe757d24ea60ed2a url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" google_sign_in_android: dependency: transitive description: name: google_sign_in_android - sha256: "7af72e5502c313865c729223b60e8ae7bce0a1011b250c24edcf30d3d7032748" + sha256: d5e23c56a4b84b6427552f1cf3f98f716db3b1d1a647f16b96dbb5b93afa2805 url: "https://pub.dev" source: hosted - version: "6.1.35" + version: "6.2.1" google_sign_in_ios: dependency: transitive description: name: google_sign_in_ios - sha256: "8468465516a6fdc283ffbbb06ec03a860ee34e9ff84b0454074978705b42379b" + sha256: "102005f498ce18442e7158f6791033bbc15ad2dcc0afa4cf4752e2722a516c96" url: "https://pub.dev" source: hosted - version: "5.8.0" + version: "5.9.0" google_sign_in_platform_interface: dependency: transitive description: name: google_sign_in_platform_interface - sha256: "1f6e5787d7a120cc0359ddf315c92309069171306242e181c09472d1b00a2971" + sha256: "5f6f79cf139c197261adb6ac024577518ae48fdff8e53205c5373b5f6430a8aa" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "2.5.0" google_sign_in_web: dependency: transitive description: name: google_sign_in_web - sha256: ada595df6c30cead48e66b1f3a050edf0c5cf2ba60c185d69690e08adcc6281b + sha256: "460547beb4962b7623ac0fb8122d6b8268c951cf0b646dd150d60498430e4ded" url: "https://pub.dev" source: hosted - version: "0.12.4+3" + version: "0.12.4+4" gql: dependency: transitive description: name: gql - sha256: "998304fbb88a3956cfea10cd27a56f8e5d4b3bc110f03c952c18a9310774e8bb" + sha256: "650e79ed60c21579ca3bd17ebae8a8c8d22cde267b03a19bf3b35996baaa843a" url: "https://pub.dev" source: hosted - version: "0.14.0" + version: "1.0.1-alpha+1730759315362" gql_dedupe_link: dependency: transitive description: name: gql_dedupe_link - sha256: "89681048cf956348e865da872a40081499b8c087fc84dd4d4b9c134bd70d27b3" + sha256: "10bee0564d67c24e0c8bd08bd56e0682b64a135e58afabbeed30d85d5e9fea96" url: "https://pub.dev" source: hosted - version: "2.0.3+1" + version: "2.0.4-alpha+1715521079596" gql_error_link: dependency: transitive description: name: gql_error_link - sha256: e7bfdd2b6232f3e15861cd96c2ad6b7c9c94693843b3dea18295136a5fb5b534 + sha256: "93901458f3c050e33386dedb0ca7173e08cebd7078e4e0deca4bf23ab7a71f63" url: "https://pub.dev" source: hosted - version: "0.2.3+1" + version: "1.0.0+1" gql_exec: dependency: transitive description: name: gql_exec - sha256: "0d1fdb2e4154efbfc1dcf3f35ec36d19c8428ff0d560eb4c45b354f8f871dc50" + sha256: "394944626fae900f1d34343ecf2d62e44eb984826189c8979d305f0ae5846e38" url: "https://pub.dev" source: hosted - version: "0.4.3" + version: "1.1.1-alpha+1699813812660" gql_http_link: dependency: transitive description: name: gql_http_link - sha256: "89ef87b32947acf4189f564c095f1148b0ab9bb9996fe518716dbad66708b834" + sha256: ef6ad24d31beb5a30113e9b919eec20876903cc4b0ee0d31550047aaaba7d5dd url: "https://pub.dev" source: hosted - version: "0.4.5" + version: "1.1.0" gql_link: dependency: transitive description: name: gql_link - sha256: f7973279126bc922d465c4f4da6ed93d187085e597b3480f5e14e74d28fe14bd + sha256: c2b0adb2f6a60c2599b9128fb095316db5feb99ce444c86fb141a6964acedfa4 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.1-alpha+1730759315378" gql_transform_link: dependency: transitive description: name: gql_transform_link - sha256: b1735a9a92d25a92960002a8b40dfaede95ec1e5ed848906125d69efd878661f + sha256: "0645fdd874ca1be695fd327271fdfb24c0cd6fa40774a64b946062f321a59709" url: "https://pub.dev" source: hosted - version: "0.2.2+1" + version: "1.0.0" graphql: dependency: transitive description: name: graphql - sha256: b061201579040e9548cec2bae17bbdea0ab30666cb4e7ba48b9675f14d982199 + sha256: ad11e6d12de4d73971ae1dd80885b09f3cbc0bf143b1cbc5622a6dc6d85735e7 url: "https://pub.dev" source: hosted - version: "5.1.3" + version: "5.2.0-beta.4" graphql_flutter: dependency: "direct main" description: name: graphql_flutter - sha256: "06059ac9e8417c71582f05e28a59b1416d43959d34a6a0d9565341e3a362e117" + sha256: "39b5e830bc654ab02c5b776c31675841d1a8c95840fdd284efba713b1d47e65d" url: "https://pub.dev" source: hosted - version: "5.1.2" + version: "5.2.0-beta.6" graphs: dependency: transitive description: @@ -866,10 +874,10 @@ packages: dependency: transitive description: name: html - sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" url: "https://pub.dev" source: hosted - version: "0.15.5" + version: "0.15.6" http: dependency: "direct main" description: @@ -1090,10 +1098,10 @@ packages: dependency: transitive description: name: normalize - sha256: baf8caf2d8b745af5737cca6c24f7fe3cf3158897fdbcde9a909b9c8d3e2e5af + sha256: "8a60e37de5b608eeaf9b839273370c71ebba445e9f73b08eee7725e0d92dbc43" url: "https://pub.dev" source: hosted - version: "0.7.2" + version: "0.8.2+1" overlay_support: dependency: "direct main" description: @@ -1106,10 +1114,10 @@ packages: dependency: transitive description: name: package_config - sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" path: dependency: transitive description: @@ -1146,10 +1154,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.15" + version: "2.2.17" path_provider_foundation: dependency: transitive description: @@ -1258,18 +1266,18 @@ packages: dependency: "direct main" description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.5" pub_semver: dependency: transitive description: name: pub_semver - sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" pubspec_parse: dependency: transitive description: @@ -1322,18 +1330,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.10" shared_preferences_foundation: dependency: transitive description: @@ -1414,14 +1422,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.0" - simple_pip_mode: - dependency: "direct main" - description: - name: simple_pip_mode - sha256: "89f8137fa5a8d113f39c61007d4b658048a9359362447b8cfb8bce93631882ad" - url: "https://pub.dev" - source: hosted - version: "0.8.0" skeleton_text: dependency: "direct main" description: @@ -1571,30 +1571,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" - uni_links: - dependency: "direct main" - description: - name: uni_links - sha256: "051098acfc9e26a9fde03b487bef5d3d228ca8f67693480c6f33fd4fbb8e2b6e" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - uni_links_platform_interface: - dependency: transitive - description: - name: uni_links_platform_interface - sha256: "929cf1a71b59e3b7c2d8a2605a9cf7e0b125b13bc858e55083d88c62722d4507" - url: "https://pub.dev" - source: hosted - version: "1.0.0" - uni_links_web: - dependency: transitive - description: - name: uni_links_web - sha256: "7539db908e25f67de2438e33cc1020b30ab94e66720b5677ba6763b25f6394df" - url: "https://pub.dev" - source: hosted - version: "0.1.0" universal_platform: dependency: transitive description: @@ -1623,10 +1599,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" url_launcher_windows: dependency: transitive description: @@ -1679,10 +1655,10 @@ packages: dependency: transitive description: name: watcher - sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104" + sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" web: dependency: transitive description: @@ -1695,10 +1671,10 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: "3a969ddcc204a3e34e863d204b29c0752716f78b6f9cc8235083208d268a4ccd" + sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.4.5" webkit_inspection_protocol: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7f7183b..38db6f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -10,21 +10,21 @@ environment: dependencies: flutter_typeahead: ^5.2.0 - connectivity_plus: any + connectivity_plus: ^5.0.2 cupertino_icons: ^1.0.5 date_time_picker: ^2.1.0 duration_picker: ^1.1.1 flutter: sdk: flutter flutter_animarker: ^3.2.0 - flutter_config: ^2.0.2 + flutter_dotenv: ^5.1.0 flutter_countdown_timer: ^4.1.0 - flutter_local_notifications: ^17.2.1+1 + flutter_local_notifications: ^18.0.0 flutter_polyline_points: ^1.0.0 flutter_spinkit: ^5.2.0 fluttertoast: ^8.2.4 geolocator: any - geolocator_android: 4.1.4 + geolocator_android: ^4.1.4 get_it: ^7.6.4 google_maps_flutter: ^2.5.3 hive: ^2.2.3 @@ -41,8 +41,6 @@ dependencies: responsive_sizer: ^3.3.1 skeleton_text: ^3.0.1 sliding_up_panel: ^2.0.0+1 - uni_links: ^0.5.1 - simple_pip_mode: ^0.8.0 data_connection_checker_nulls: ^0.0.2 flutter_geocoder_alternative: any gap: ^3.0.1 @@ -95,6 +93,7 @@ flutter: - images/ - animations/ - images/icons/ + - .env fonts: - family: Inter From 001c599382806b979a59bc0a3a3a940a5e2e8f7d Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sat, 16 Aug 2025 13:15:30 +0530 Subject: [PATCH 14/19] removed unused imports --- .../group/advance_options_screen.dart | 49 ++++--------------- lib/presentation/group/group_screen.dart | 17 ------- .../group/widgets/beacon_card.dart | 2 - .../group/widgets/group_widgets.dart | 1 - .../cubit/location_cubit/location_cubit.dart | 11 ++--- .../hike/widgets/hike_screen_widget.dart | 8 ++- .../hike/widgets/search_places.dart | 2 +- lib/presentation/home/home_screen.dart | 1 - lib/presentation/home/profile_screen.dart | 10 ++-- lib/presentation/widgets/hike_button.dart | 1 - lib/presentation/widgets/label_marker.dart | 2 +- pubspec.yaml | 2 +- 12 files changed, 25 insertions(+), 81 deletions(-) diff --git a/lib/presentation/group/advance_options_screen.dart b/lib/presentation/group/advance_options_screen.dart index 0374724..ef09c35 100644 --- a/lib/presentation/group/advance_options_screen.dart +++ b/lib/presentation/group/advance_options_screen.dart @@ -17,14 +17,14 @@ import '../widgets/screen_template.dart'; @RoutePage() class AdvancedOptionsScreen extends StatefulWidget { - TextEditingController durationController; - String title; - bool isScheduled; - DateTime? startDate; - TimeOfDay? startTime; - String groupId; - - AdvancedOptionsScreen({ + final TextEditingController durationController; + final String title; + final bool isScheduled; + final DateTime? startDate; + final TimeOfDay? startTime; + final String groupId; + + const AdvancedOptionsScreen({ super.key, required this.durationController, required this.title, @@ -107,7 +107,6 @@ class _AdvancedOptionsScreenState extends State { try { final String apiKey = EnvironmentConfig.openWeatherMapApiKey!; - // '03fe30be078c0bfb823d954404de6a6b'; String apiUrl; if (widget.isScheduled) { @@ -222,32 +221,6 @@ class _AdvancedOptionsScreenState extends State { }; } - Future _loadMockWeatherData() async { - await Future.delayed(Duration(seconds: 1)); - - setState(() { - weatherData = { - 'main': { - 'temp': 22.5, - 'feels_like': 24.0, - 'humidity': 65, - }, - 'weather': [ - {'main': 'Clear', 'description': 'clear sky', 'icon': '01d'} - ], - 'wind': { - 'speed': 3.2, - }, - 'visibility': 10000, - 'name': _selectedLocation?.name ?? 'Your Location', - 'isScheduled': widget.isScheduled, - 'scheduledDate': - widget.isScheduled ? widget.startDate!.toIso8601String() : null, - }; - isLoadingWeather = false; - }); - } - Future _searchLocations(String query) async { if (query.isEmpty) { setState(() { @@ -532,7 +505,7 @@ class _AdvancedOptionsScreenState extends State { Container( padding: EdgeInsets.all(2.w), decoration: BoxDecoration( - color: _getWeatherRecommendationColor().withOpacity(0.1), + color: _getWeatherRecommendationColor().withValues(alpha: 0.1), borderRadius: BorderRadius.circular(8), ), child: Row( @@ -675,9 +648,7 @@ class _AdvancedOptionsScreenState extends State { padding: EdgeInsets.symmetric(horizontal: 4.w, vertical: 1.h), child: TextFormField( initialValue: widget.title, - onChanged: (name) { - widget.title = name; - }, + readOnly: true, // Make it read-only since title is now final decoration: InputDecoration( filled: true, fillColor: Colors.grey[200], diff --git a/lib/presentation/group/group_screen.dart b/lib/presentation/group/group_screen.dart index 9fdda35..d72eba7 100644 --- a/lib/presentation/group/group_screen.dart +++ b/lib/presentation/group/group_screen.dart @@ -102,23 +102,6 @@ class _GroupScreenState extends State { ); } - Widget _buildAppBar() { - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: const Icon(Icons.arrow_back, color: Colors.grey), - onPressed: () => context.router.maybePop(), - ), - Image.asset('images/beacon_logo.png', height: 4.h), - IconButton( - icon: const Icon(Icons.power_settings_new, color: Colors.grey), - onPressed: _showLogoutDialog, - ), - ], - ); - } - Widget _buildGroupHeader() { return Row( children: [ diff --git a/lib/presentation/group/widgets/beacon_card.dart b/lib/presentation/group/widgets/beacon_card.dart index 0811843..a50ab4f 100644 --- a/lib/presentation/group/widgets/beacon_card.dart +++ b/lib/presentation/group/widgets/beacon_card.dart @@ -1,11 +1,9 @@ import 'dart:async'; -import 'package:auto_route/auto_route.dart'; import 'package:beacon/core/utils/constants.dart'; import 'package:beacon/domain/entities/beacon/beacon_entity.dart'; import 'package:beacon/locator.dart'; import 'package:beacon/presentation/group/cubit/group_cubit/group_cubit.dart'; import 'package:beacon/presentation/hike/widgets/active_beacon.dart'; -import 'package:beacon/presentation/widgets/hike_button.dart'; import 'package:beacon/presentation/group/widgets/timer.dart'; import 'package:beacon/config/router/router.dart'; import 'package:flutter/material.dart'; diff --git a/lib/presentation/group/widgets/group_widgets.dart b/lib/presentation/group/widgets/group_widgets.dart index ddddea5..72ef2f1 100644 --- a/lib/presentation/group/widgets/group_widgets.dart +++ b/lib/presentation/group/widgets/group_widgets.dart @@ -606,7 +606,6 @@ class _MemberTile extends StatelessWidget { required this.isLeader, required this.canRemove, required this.isCurrentUser, - super.key, }); @override diff --git a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart index bf4c6c7..633867d 100644 --- a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart +++ b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:developer'; -import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:http/http.dart' as http; import 'package:flutter/services.dart'; @@ -128,7 +127,7 @@ class LocationCubit extends Cubit { infoWindow: InfoWindow( title: 'this is ${_beacon!.leader?.name ?? 'Anonymous'}}', ), - icon: BitmapDescriptor.fromBytes( + icon: BitmapDescriptor.bytes( await getCircularImageWithBorderAndPointer( _leader!.imageUrl ?? 'https://cdn.jsdelivr.net/gh/alohe/avatars/png/toon_5.png', @@ -421,7 +420,7 @@ class LocationCubit extends Cubit { if (markers.isEmpty) { _hikeMarkers.add(Marker( markerId: MarkerId(_beacon!.leader!.id.toString()), - icon: BitmapDescriptor.fromBytes( + icon: BitmapDescriptor.bytes( await getCircularImageWithBorderAndPointer( _leader!.imageUrl ?? 'https://cdn.jsdelivr.net/gh/alohe/avatars/png/toon_5.png', @@ -769,7 +768,7 @@ class LocationCubit extends Cubit { markerId: markerId, position: markerPosition, infoWindow: InfoWindow(title: user.name ?? 'Anonymous'), - icon: BitmapDescriptor.fromBytes(markerIcon)); + icon: BitmapDescriptor.bytes(markerIcon)); _hikeMarkers.add(newMarker); } else { // If the marker exists, update its position @@ -796,7 +795,7 @@ class LocationCubit extends Cubit { final byteData = await image.toByteData(format: ui.ImageByteFormat.png); final Uint8List resizedBytes = byteData!.buffer.asUint8List(); - return BitmapDescriptor.fromBytes(resizedBytes); + return BitmapDescriptor.bytes(resizedBytes); } BitmapDescriptor customIcon = await getResizedMarkerIcon( @@ -828,7 +827,7 @@ class LocationCubit extends Cubit { return Marker( markerId: MarkerId(landmark.id!.toString()), position: locationToLatLng(landmark.location!), - icon: BitmapDescriptor.fromBytes(markerIcon), + icon: BitmapDescriptor.bytes(markerIcon), infoWindow: InfoWindow( title: '${landmark.title} by ${landmark.createdBy?.name ?? 'Anonymous'}', diff --git a/lib/presentation/hike/widgets/hike_screen_widget.dart b/lib/presentation/hike/widgets/hike_screen_widget.dart index ad37e08..e41a4e2 100644 --- a/lib/presentation/hike/widgets/hike_screen_widget.dart +++ b/lib/presentation/hike/widgets/hike_screen_widget.dart @@ -4,7 +4,6 @@ import 'package:beacon/locator.dart'; import 'package:beacon/presentation/hike/cubit/location_cubit/location_cubit.dart'; import 'package:beacon/presentation/widgets/hike_button.dart'; import 'package:beacon/core/utils/constants.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_geocoder_alternative/flutter_geocoder_alternative.dart'; @@ -152,9 +151,6 @@ class HikeScreenWidget { ); } - static TextEditingController _landMarkeController = TextEditingController(); - static GlobalKey _landmarkFormKey = GlobalKey(); - static void selectionButton( BuildContext context, String beaconId, LatLng loc) { showDialog( @@ -290,7 +286,9 @@ class _CreateLandmarkDialogState extends State { width: 2, ), color: isSelected - ? Theme.of(context).primaryColor.withOpacity(0.1) + ? Theme.of(context) + .primaryColor + .withValues(alpha: 0.1) : Colors.transparent, ), child: Image.asset( diff --git a/lib/presentation/hike/widgets/search_places.dart b/lib/presentation/hike/widgets/search_places.dart index f9bfaea..2348534 100644 --- a/lib/presentation/hike/widgets/search_places.dart +++ b/lib/presentation/hike/widgets/search_places.dart @@ -111,7 +111,7 @@ class LocationSearchWidget extends StatelessWidget { borderRadius: BorderRadius.circular(8.0), boxShadow: [ BoxShadow( - color: Colors.grey.withOpacity(0.2), + color: Colors.grey.withValues(alpha: 0.2), spreadRadius: 2, blurRadius: 5, offset: Offset(0, 3), diff --git a/lib/presentation/home/home_screen.dart b/lib/presentation/home/home_screen.dart index 1a561f2..dff8693 100644 --- a/lib/presentation/home/home_screen.dart +++ b/lib/presentation/home/home_screen.dart @@ -1,6 +1,5 @@ import 'package:auto_route/auto_route.dart'; import 'package:beacon/domain/entities/group/group_entity.dart'; -import 'package:beacon/presentation/auth/auth_cubit/auth_cubit.dart'; import 'package:beacon/presentation/home/home_cubit/home_cubit.dart'; import 'package:beacon/presentation/home/home_cubit/home_state.dart'; import 'package:beacon/presentation/group/widgets/create_join_dialog.dart'; diff --git a/lib/presentation/home/profile_screen.dart b/lib/presentation/home/profile_screen.dart index 448057d..a2a4755 100644 --- a/lib/presentation/home/profile_screen.dart +++ b/lib/presentation/home/profile_screen.dart @@ -1,5 +1,4 @@ import 'package:auto_route/annotations.dart'; -import 'package:beacon/domain/entities/user/user_entity.dart'; import 'package:beacon/locator.dart'; import 'package:beacon/presentation/home/home_cubit/home_cubit.dart'; import 'package:beacon/presentation/widgets/hike_button.dart'; @@ -7,7 +6,6 @@ import 'package:beacon/presentation/widgets/screen_template.dart'; import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import 'package:responsive_sizer/responsive_sizer.dart'; -import '../../core/utils/constants.dart'; @RoutePage() class ProfileScreen extends StatefulWidget { @@ -133,7 +131,7 @@ class _ProfileScreenState extends State { border: Border.all(color: Colors.deepPurple, width: 2), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.1), + color: Colors.black.withValues(alpha: 0.1), blurRadius: 10, spreadRadius: 2, ), @@ -166,7 +164,7 @@ class _ProfileScreenState extends State { color: Colors.deepPurple, boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.2), + color: Colors.black.withValues(alpha: 0.2), blurRadius: 6, spreadRadius: 1, ), @@ -232,7 +230,7 @@ class _ProfileScreenState extends State { borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: Colors.black.withOpacity(0.05), + color: Colors.black.withValues(alpha: 0.05), blurRadius: 10, spreadRadius: 2, ), @@ -341,7 +339,7 @@ class _ProfileScreenState extends State { color: (achievement['earned'] as bool ? Colors.deepPurple : Colors.grey[300]!) - .withOpacity(0.3), + .withValues(alpha: 0.3), blurRadius: 8, spreadRadius: 2, ), diff --git a/lib/presentation/widgets/hike_button.dart b/lib/presentation/widgets/hike_button.dart index 5a45fe9..65ab8df 100644 --- a/lib/presentation/widgets/hike_button.dart +++ b/lib/presentation/widgets/hike_button.dart @@ -1,4 +1,3 @@ -import 'package:beacon/core/utils/constants.dart'; import 'package:flutter/material.dart'; import 'package:dotted_border/dotted_border.dart'; diff --git a/lib/presentation/widgets/label_marker.dart b/lib/presentation/widgets/label_marker.dart index 8d471e0..476df74 100644 --- a/lib/presentation/widgets/label_marker.dart +++ b/lib/presentation/widgets/label_marker.dart @@ -42,7 +42,7 @@ extension AddExtension on Set { infoWindow: labelMarker.infoWindow, rotation: labelMarker.rotation, visible: labelMarker.visible, - zIndex: labelMarker.zIndex, + zIndexInt: labelMarker.zIndex.toInt(), onTap: labelMarker.onTap, onDragStart: labelMarker.onDragStart, onDrag: labelMarker.onDrag, diff --git a/pubspec.yaml b/pubspec.yaml index 38db6f6..1ddc482 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -93,7 +93,7 @@ flutter: - images/ - animations/ - images/icons/ - - .env + # - .env fonts: - family: Inter From 20db49ef6bf6ba452b07ddc40eb0362e4182a3fc Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sat, 16 Aug 2025 13:23:28 +0530 Subject: [PATCH 15/19] lint fixes --- lib/presentation/group/group_screen.dart | 40 ------------------- .../cubit/location_cubit/location_cubit.dart | 2 +- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/lib/presentation/group/group_screen.dart b/lib/presentation/group/group_screen.dart index d72eba7..f74be04 100644 --- a/lib/presentation/group/group_screen.dart +++ b/lib/presentation/group/group_screen.dart @@ -369,44 +369,4 @@ class _GroupScreenState extends State { ), ); } - - void _showLogoutDialog() { - showDialog( - context: context, - builder: (context) => AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: Text('Logout', style: TextStyle(fontSize: 18.sp)), - content: Text( - 'Are you sure you want to logout?', - style: TextStyle(fontSize: 16.sp), - ), - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - HikeButton( - buttonWidth: 25.w, - buttonHeight: 5.h, - isDotted: true, - onTap: () => context.router.maybePop(false), - text: 'No', - textSize: 14.sp, - ), - HikeButton( - buttonWidth: 25.w, - buttonHeight: 5.h, - onTap: () { - context.router.replaceNamed('/auth'); - localApi.deleteUser(); - context.read().googleSignOut(); - }, - text: 'Yes', - textSize: 14.sp, - ), - ], - ), - ], - ), - ); - } } diff --git a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart index 633867d..6d9c493 100644 --- a/lib/presentation/hike/cubit/location_cubit/location_cubit.dart +++ b/lib/presentation/hike/cubit/location_cubit/location_cubit.dart @@ -747,7 +747,7 @@ class LocationCubit extends Cubit { } } - void _createUserMarker(UserEntity user, {bool isLeader = false}) async { + void _createUserMarker(UserEntity user) async { final markerId = MarkerId(user.id!); final markerPosition = locationToLatLng(user.location!); From 70d888eacee808067ad5a0e9141913f0e5f96e09 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sat, 16 Aug 2025 13:27:31 +0530 Subject: [PATCH 16/19] fix dart analyze --- lib/presentation/group/group_screen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/presentation/group/group_screen.dart b/lib/presentation/group/group_screen.dart index f74be04..e20fbb7 100644 --- a/lib/presentation/group/group_screen.dart +++ b/lib/presentation/group/group_screen.dart @@ -1,7 +1,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:beacon/domain/entities/beacon/beacon_entity.dart'; import 'package:beacon/domain/entities/group/group_entity.dart'; -import 'package:beacon/presentation/auth/auth_cubit/auth_cubit.dart'; import 'package:beacon/presentation/group/cubit/group_cubit/group_cubit.dart'; import 'package:beacon/presentation/group/cubit/group_cubit/group_state.dart'; import 'package:beacon/presentation/group/cubit/members_cubit/members_cubit.dart'; From 117d507b80cf55ae60c0bdb09f374a09dca974f9 Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sat, 16 Aug 2025 13:55:10 +0530 Subject: [PATCH 17/19] add build apk in workflow --- .github/workflows/flutter-ci.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml index b3d2b92..6b21f7b 100644 --- a/.github/workflows/flutter-ci.yml +++ b/.github/workflows/flutter-ci.yml @@ -12,6 +12,7 @@ jobs: matrix: platform: [windows-latest, ubuntu-latest, macos-latest] runs-on: ${{matrix.platform}} + steps: - uses: actions/checkout@v1 - uses: actions/setup-java@v1 @@ -42,8 +43,8 @@ jobs: - run: flutter pub get - run: dart format . --set-exit-if-changed - run: flutter analyze . - # - run: flutter build apk - # - uses: actions/upload-artifact@v1 - # with: - # name: release-apk - # path: build/app/outputs/apk/release/app-release.apk + - run: flutter build apk + - uses: actions/upload-artifact@v1 + with: + name: release-apk + path: build/app/outputs/apk/release/app-release.apk From 669502da2df0868e6b44fbfe995b9624fef6006e Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sat, 16 Aug 2025 13:58:09 +0530 Subject: [PATCH 18/19] upload artifact v4 --- .github/workflows/flutter-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml index 6b21f7b..89b1f68 100644 --- a/.github/workflows/flutter-ci.yml +++ b/.github/workflows/flutter-ci.yml @@ -44,7 +44,7 @@ jobs: - run: dart format . --set-exit-if-changed - run: flutter analyze . - run: flutter build apk - - uses: actions/upload-artifact@v1 + - uses: actions/upload-artifact@v4 with: name: release-apk path: build/app/outputs/apk/release/app-release.apk From 5cc2ffd556f24104487b26a9d7b921a0e39267db Mon Sep 17 00:00:00 2001 From: himanshu goyal Date: Sat, 16 Aug 2025 14:03:08 +0530 Subject: [PATCH 19/19] remove build artifact --- .github/workflows/flutter-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/flutter-ci.yml b/.github/workflows/flutter-ci.yml index 89b1f68..b979389 100644 --- a/.github/workflows/flutter-ci.yml +++ b/.github/workflows/flutter-ci.yml @@ -43,8 +43,8 @@ jobs: - run: flutter pub get - run: dart format . --set-exit-if-changed - run: flutter analyze . - - run: flutter build apk - - uses: actions/upload-artifact@v4 - with: - name: release-apk - path: build/app/outputs/apk/release/app-release.apk + # - run: flutter build apk + # - uses: actions/upload-artifact@v1 + # with: + # name: release-apk + # path: build/app/outputs/apk/release/app-release.apk