feat:
1-fake location 2-bottom navigation 3-bottom sheet 4-lazy map loading
This commit is contained in:
96
features/supervision/lib/presentation/filter/logic.dart
Normal file
96
features/supervision/lib/presentation/filter/logic.dart
Normal file
@@ -0,0 +1,96 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:supervision/data/utils/marker_generator.dart';
|
||||
|
||||
class SupervisionFilterLogic extends GetxController {
|
||||
Rx<LatLng> currentLocation = LatLng(35.824891, 50.948025).obs;
|
||||
RxList<LatLng> allMarkers = <LatLng>[].obs;
|
||||
RxList<LatLng> markers = <LatLng>[].obs;
|
||||
Timer? _debounceTimer;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
RxBool isSelected = false.obs;
|
||||
|
||||
Future<void> determineCurrentPosition() async {
|
||||
bool serviceEnabled;
|
||||
LocationPermission permission;
|
||||
|
||||
serviceEnabled = await Geolocator.isLocationServiceEnabled();
|
||||
if (!serviceEnabled) {
|
||||
return Future.error('Location services are disabled.');
|
||||
}
|
||||
|
||||
permission = await Geolocator.checkPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
permission = await Geolocator.requestPermission();
|
||||
if (permission == LocationPermission.denied) {
|
||||
return Future.error('Location permissions are denied.');
|
||||
}
|
||||
}
|
||||
|
||||
if (permission == LocationPermission.deniedForever) {
|
||||
return Future.error('Location permissions are permanently denied.');
|
||||
}
|
||||
|
||||
final position = await Geolocator.getCurrentPosition(
|
||||
locationSettings: AndroidSettings(accuracy: LocationAccuracy.best),
|
||||
);
|
||||
final latLng = LatLng(position.latitude, position.longitude);
|
||||
currentLocation.value = latLng;
|
||||
markers.add(latLng);
|
||||
}
|
||||
|
||||
void debouncedUpdateVisibleMarkers({required LatLng center}) {
|
||||
_debounceTimer?.cancel();
|
||||
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
|
||||
final filtered = filterNearbyMarkers({
|
||||
'markers': allMarkers,
|
||||
'centerLat': center.latitude,
|
||||
'centerLng': center.longitude,
|
||||
'radius': 2000.0,
|
||||
});
|
||||
|
||||
markers.addAll(filtered);
|
||||
});
|
||||
}
|
||||
|
||||
List<LatLng> filterNearbyMarkers(Map<String, dynamic> args) {
|
||||
final List<LatLng> rawMarkers = args['markers'];
|
||||
final double centerLat = args['centerLat'];
|
||||
final double centerLng = args['centerLng'];
|
||||
final double radiusInMeters = args['radius'];
|
||||
final center = LatLng(centerLat, centerLng);
|
||||
final distance = Distance();
|
||||
|
||||
return rawMarkers
|
||||
.where((marker) => distance(center, marker) <= radiusInMeters)
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<void> generatedMarkers() async {
|
||||
final generatedMarkers = await generateLocationsUsingCompute(100000);
|
||||
allMarkers.value = generatedMarkers;
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
super.onReady();
|
||||
|
||||
determineCurrentPosition();
|
||||
generatedMarkers();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
// TODO: implement onClose
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
174
features/supervision/lib/presentation/filter/view.dart
Normal file
174
features/supervision/lib/presentation/filter/view.dart
Normal file
@@ -0,0 +1,174 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:rasadyar_core/presentation/widget/buttons/fab.dart';
|
||||
|
||||
import 'logic.dart';
|
||||
|
||||
class SupervisionFilterPage extends GetView<SupervisionFilterLogic> {
|
||||
SupervisionFilterPage({super.key});
|
||||
|
||||
final Map styleTypes = <String, String>{
|
||||
'satellite': 's',
|
||||
'satelliteWithLabel': 'y',
|
||||
'street': 'p',
|
||||
'streetWithTraffic': 'p,traffic',
|
||||
};
|
||||
|
||||
final String token =
|
||||
"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjJlMGQyZTUyYzU1ZDJmMDkxNzBmNTdmM2JmOGVkODgxOTg5YTQxN2E5M2JiNDRiZTk1YzEyMjI1NzRlYWM3ZGYwOTlkNzhkNDUyMDRlMDJiIn0.eyJhdWQiOiIyODYyMiIsImp0aSI6IjJlMGQyZTUyYzU1ZDJmMDkxNzBmNTdmM2JmOGVkODgxOTg5YTQxN2E5M2JiNDRiZTk1YzEyMjI1NzRlYWM3ZGYwOTlkNzhkNDUyMDRlMDJiIiwiaWF0IjoxNzI1MTc2MDI5LCJuYmYiOjE3MjUxNzYwMjksImV4cCI6MTcyNzc2ODAyOSwic3ViIjoiIiwic2NvcGVzIjpbImJhc2ljIl19.bYQSwq48FZ9IM-Y7aTQAblEmIAz1b4m3nOWuKg-VTalx-eZqcIlzIOJI7XarBeS35CGvPbu6W9tAFoliz_HPARSMJM_Ni0YwESjscVqvPNJqV2vZlmgMj_wRDa0W5FmBipZ-AzKGcsuoMEpD8WzUgnlrhUdqVob1UHXQmZt1BFJlFHLm_grzKFf1_Lw_cHA_mRhhe36d_hU5Qg0jY3NP2NXG_TmecQcqch4YEwCh5_R_dUyMIlt70FL2OJ37ESyPYl-XMg3qOOjgI5YD8h6rciSfFsQpnOgl84HhV3aQ4MvWrIPl9ekEnwLM71He8o5KY1hhSQdOHSvO4yL6MBg9Yw";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
ObxValue((currentLocation) {
|
||||
return FlutterMap(
|
||||
options: MapOptions(
|
||||
initialCenter: currentLocation.value,
|
||||
initialZoom: 18,
|
||||
onPositionChanged: (camera, hasGesture) {
|
||||
controller.debouncedUpdateVisibleMarkers(center: camera.center);
|
||||
},
|
||||
),
|
||||
children: [
|
||||
TileLayer(
|
||||
urlTemplate:
|
||||
"https://map.ir/shiveh/xyz/1.0.0/Shiveh:Shiveh@EPSG:3857@png/{z}/{x}/{y}.png"
|
||||
"?x-api-key=$token",
|
||||
),
|
||||
|
||||
ObxValue((markers) {
|
||||
return MarkerLayer(
|
||||
markers:
|
||||
markers
|
||||
.map(
|
||||
(marker) => Marker(
|
||||
point: marker,
|
||||
child: const Icon(
|
||||
Icons.location_on,
|
||||
color: Colors.red,
|
||||
size: 30,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}, controller.markers),
|
||||
],
|
||||
);
|
||||
}, controller.currentLocation),
|
||||
|
||||
Positioned(
|
||||
right: 10,
|
||||
bottom: 100,
|
||||
child: RFab.smallAdd(
|
||||
onPressed: () {
|
||||
controller.isSelected.value = !controller.isSelected.value;
|
||||
},
|
||||
),
|
||||
),
|
||||
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: ObxValue((isSelected) {
|
||||
return DraggableBottomSheet(isVisible: isSelected.value);
|
||||
}, controller.isSelected),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DraggableBottomSheet extends StatefulWidget {
|
||||
final bool isVisible;
|
||||
final double initialHeight;
|
||||
final double minHeight;
|
||||
final double maxHeight;
|
||||
|
||||
const DraggableBottomSheet({
|
||||
super.key,
|
||||
required this.isVisible,
|
||||
this.initialHeight = 200,
|
||||
this.minHeight = 0,
|
||||
this.maxHeight = 700,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DraggableBottomSheet> createState() => _DraggableBottomSheetState();
|
||||
}
|
||||
|
||||
class _DraggableBottomSheetState extends State<DraggableBottomSheet> {
|
||||
late double _sheetHeight;
|
||||
bool isWidgetVisible = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_sheetHeight = widget.initialHeight;
|
||||
isWidgetVisible = widget.isVisible;
|
||||
}
|
||||
|
||||
void _onVerticalDragUpdate(DragUpdateDetails details) {
|
||||
setState(() {
|
||||
_sheetHeight -= details.delta.dy;
|
||||
_sheetHeight = _sheetHeight.clamp(widget.minHeight, widget.maxHeight);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant DraggableBottomSheet oldWidget) {
|
||||
if (_sheetHeight <= oldWidget.initialHeight) {
|
||||
setState(() {
|
||||
isWidgetVisible = !isWidgetVisible;
|
||||
});
|
||||
}
|
||||
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AnimatedPositioned(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOut,
|
||||
bottom: widget.isVisible ? 0 : -widget.maxHeight,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: GestureDetector(
|
||||
onVerticalDragUpdate: _onVerticalDragUpdate,
|
||||
child: Container(
|
||||
height: _sheetHeight,
|
||||
decoration: const BoxDecoration(
|
||||
color: Colors.white,
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(50)),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Container(
|
||||
width: 40,
|
||||
height: 5,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.grey[400],
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: 15,
|
||||
itemBuilder:
|
||||
(context, index) =>
|
||||
ListTile(title: Text('Item ${index + 1}')),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
28
features/supervision/lib/presentation/root/logic.dart
Normal file
28
features/supervision/lib/presentation/root/logic.dart
Normal file
@@ -0,0 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:supervision/presentation/filter/view.dart';
|
||||
|
||||
class RootLogic extends GetxController {
|
||||
RxInt currentIndex = 0.obs;
|
||||
List<Widget> pages = [
|
||||
SupervisionFilterPage(),
|
||||
Placeholder(color: Colors.red),
|
||||
Placeholder(color: Colors.amber),
|
||||
];
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
// TODO: implement onReady
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
void changePage(int index) {
|
||||
currentIndex.value = index;
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
// TODO: implement onClose
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
62
features/supervision/lib/presentation/root/view.dart
Normal file
62
features/supervision/lib/presentation/root/view.dart
Normal file
@@ -0,0 +1,62 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
|
||||
import 'logic.dart';
|
||||
|
||||
class RootPage extends GetView<RootLogic> {
|
||||
const RootPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Stack(
|
||||
children: [
|
||||
ObxValue(
|
||||
(currentIndex) => IndexedStack(
|
||||
index: currentIndex.value,
|
||||
children: controller.pages,
|
||||
),
|
||||
controller.currentIndex,
|
||||
),
|
||||
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: ObxValue(
|
||||
(index) => BottomNavigation1(
|
||||
items: [
|
||||
BottomNavigation1Item(
|
||||
icon: Assets.vecMapSvg,
|
||||
label: 'نقشه',
|
||||
isSelected: controller.currentIndex.value == 0,
|
||||
onTap: () {
|
||||
controller.changePage(0);
|
||||
},
|
||||
),
|
||||
BottomNavigation1Item(
|
||||
icon: Assets.vecSettingSvg,
|
||||
label: 'اقدام',
|
||||
isSelected: controller.currentIndex.value == 1,
|
||||
onTap: () {
|
||||
controller.changePage(1);
|
||||
},
|
||||
),
|
||||
BottomNavigation1Item(
|
||||
icon: Assets.vecProfileCircleSvg,
|
||||
label: 'پروفایل',
|
||||
isSelected: controller.currentIndex.value == 2,
|
||||
onTap: () {
|
||||
controller.changePage(2);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
controller.currentIndex,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:rasadyar_core/core.dart';
|
||||
import 'package:supervision/presentation/filter/logic.dart';
|
||||
import 'package:supervision/presentation/filter/view.dart';
|
||||
import 'package:supervision/presentation/root/logic.dart';
|
||||
import 'package:supervision/presentation/root/view.dart';
|
||||
import 'package:supervision/presentation/routes/app_routes.dart';
|
||||
|
||||
sealed class SupervisionPages {
|
||||
@@ -9,8 +10,11 @@ sealed class SupervisionPages {
|
||||
static final pages = [
|
||||
GetPage(
|
||||
name: SupervisionRoutes.supervision,
|
||||
page: () => SupervisionFilterPage(),
|
||||
binding: BindingsBuilder.put(() => SupervisionLogic()),
|
||||
page: () => RootPage(),
|
||||
bindings: [
|
||||
BindingsBuilder.put(() => RootLogic()),
|
||||
BindingsBuilder.put(() => SupervisionFilterLogic()),
|
||||
],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user