1-fake location
2-bottom navigation
3-bottom sheet
4-lazy map loading
This commit is contained in:
2025-04-12 18:07:36 +03:30
parent 39578bcff3
commit 503dcd91b8
52 changed files with 5435 additions and 121 deletions

View 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();
}
}

View 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}')),
),
),
],
),
),
),
);
}
}

View 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();
}
}

View 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,
),
),
],
),
);
}
}

View File

@@ -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()),
],
),
];
}