feat: proper routing

This commit is contained in:
Yandrik 2024-04-21 16:27:48 +02:00
parent a6f9cfdaf4
commit 1211c9b16e
9 changed files with 409 additions and 7 deletions

View File

@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:uninav/nav/graph.dart';
import 'package:uninav/util/util.dart';
class EventLog extends StatefulWidget {
final List<GraphFeature> events;
const EventLog({Key? key, required this.events}) : super(key: key);
@override
_EventLogState createState() => _EventLogState();
}
class _EventLogState extends State<EventLog> {
int _selectedIndex = -1;
void _onEventTapped(int index) {
setState(() {
_selectedIndex = index;
});
_scrollController.animateTo(
index * _itemExtent,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
}
final ScrollController _scrollController = ScrollController();
static const double _itemExtent = 60.0;
@override
Widget build(BuildContext context) {
return Theme(
data: ThemeData.light(),
child: SizedBox(
height: 200,
child: ListView.builder(
controller: _scrollController,
itemExtent: _itemExtent + 8.0,
itemCount: widget.events.length,
itemBuilder: (BuildContext context, int index) {
final event = widget.events[index];
final isActive = index >= _selectedIndex;
return GestureDetector(
onTap: () => _onEventTapped(index),
child: Container(
margin: const EdgeInsets.symmetric(vertical: 4.0),
padding: const EdgeInsets.all(4.0),
decoration: BoxDecoration(
color:
isActive ? Colors.white : Colors.white.withOpacity(0.6),
borderRadius: BorderRadius.circular(8.0),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4.0,
offset: const Offset(0, 2),
),
],
),
child: Row(
children: [
Icon(getIconForEvent(event)),
const SizedBox(width: 8.0),
Text(
getLabelForEvent(event),
style: TextStyle(color: Colors.black),
),
],
),
),
);
},
),
),
);
}
}
IconData getIconForEvent(GraphFeature event) => event.when(
buildingFloor: (level, feature) => Icons.directions_walk,
portal: (fromFloor, from, toFloor, to, baseFeature) =>
baseFeature.type.maybeWhen(
door: (connects) => Icons.door_front_door,
stairs: (connects) => Icons.stairs,
lift: (connects_levels) => Icons.elevator,
orElse: () => Icons.question_mark,
),
basicFeature: (level, building, baseFeature) => Icons.location_on,
);
String getLabelForEvent(GraphFeature event) => event.when(
buildingFloor: (level, feature) => feature.name,
portal: (fromFloor, from, toFloor, to, baseFeature) =>
"$from:$fromFloor -> $to:$toFloor",
basicFeature: (level, building, baseFeature) =>
formatFeatureTitle(baseFeature),
);

View File

@ -3,7 +3,9 @@ import 'package:flutter/widgets.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:uninav/components/colorful_chips.dart'; import 'package:uninav/components/colorful_chips.dart';
import 'package:uninav/controllers/map_controller.dart'; import 'package:uninav/controllers/map_controller.dart';
import 'package:uninav/controllers/navigation_controller.dart';
import 'package:uninav/data/geo/model.dart'; import 'package:uninav/data/geo/model.dart';
import 'package:uninav/nav/graph.dart';
import 'package:uninav/util/util.dart'; import 'package:uninav/util/util.dart';
final _colorfulBoxDeco = BoxDecoration( final _colorfulBoxDeco = BoxDecoration(
@ -90,7 +92,39 @@ Future<void> showFeatureBottomSheet(
), ),
], ],
), ),
onPressed: () => {}, onPressed: () {
print("trying to start navigation...");
final navController = Get.find<NavigationController>();
final mapController = Get.find<MyMapController>();
// make feature into graphFeature
final wrapped = wrap(
feature,
mapController.currentLevel.value,
feature.buildingName ?? "")
.first;
print("1");
Get.back();
if (navController.position.value == null) {
print("2");
Get.snackbar(
"Navigation failed!",
"Please set your position first!",
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.only(
bottom: 20, left: 10, right: 10),
colorText: Colors.white,
backgroundColor: Colors.red,
duration: const Duration(seconds: 3),
);
} else {
print("3");
navController.navigate(navController.position.value!,
(feature) => feature.id == wrapped.id);
}
print("4");
},
), ),
], ],
) )

View File

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:rust_core/iter.dart';
import 'package:uninav/controllers/navigation_controller.dart';
import 'package:uninav/nav/graph.dart';
class NavigationPathLayer extends StatelessWidget {
const NavigationPathLayer({super.key});
@override
Widget build(BuildContext context) {
return Obx(() {
// compute the polylines and markers
List<(GraphFeature, double)> route =
Get.find<NavigationController>().nav.iter().toList();
// distance-position pairs
// List<Polyline> polylines = [];
List<LatLng> polylinePoints =
route.map((e) => e.$1.getCenter().unwrap()).toList();
return PolylineLayer(polylines: [
Polyline(
points: polylinePoints,
strokeWidth: 4.0,
color: Colors.blue,
),
]);
});
}
}

View File

@ -41,6 +41,7 @@ class MyMapController extends GetxController {
} }
newLevels.sort(); newLevels.sort();
levels.value = newLevels; levels.value = newLevels;
update();
} }
Result<void> setLevel(int level) { Result<void> setLevel(int level) {
@ -50,6 +51,7 @@ class MyMapController extends GetxController {
} }
currentLevel.value = level; currentLevel.value = level;
update();
return const Ok(()); return const Ok(());
} }
@ -111,6 +113,7 @@ class MyMapController extends GetxController {
} }
features.value = featuresList; features.value = featuresList;
update();
} catch (e) { } catch (e) {
print('Error parsing GeoJSON: $e'); print('Error parsing GeoJSON: $e');
} }

View File

@ -0,0 +1,72 @@
import 'package:anyhow/anyhow.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:rust_core/iter.dart';
import 'package:uninav/controllers/map_controller.dart';
import 'package:uninav/data/geo/model.dart';
import 'package:uninav/nav/graph.dart';
class NavigationController extends GetxController {
// Add controller logic and variables here
final RxList<(GraphFeature, double)> nav = RxList();
final Rx<GraphFeature?> position = Rx(null);
void navigate(
GraphFeature start, bool Function(GraphFeature) endSelector) async {
position.value = start;
final path = await compute(
(data) {
final start = data.$1;
final endSelector = data.$2;
final features = data.$3;
final path = findShortestPath(start, endSelector, features);
return path;
},
(
start,
endSelector,
Get.find<MyMapController>().features.iter().toList()
),
);
if (path.isErr()) {
Get.snackbar(
'Navigation Error',
'Unable to find a path to the destination\nMessage: ${path.unwrapErr().toString().split('\n')[0]}',
snackPosition: SnackPosition.BOTTOM,
margin: const EdgeInsets.only(bottom: 20, left: 10, right: 10),
colorText: Colors.white,
backgroundColor: Colors.red,
duration: const Duration(seconds: 3),
);
return;
}
nav.value = path.unwrap();
update();
}
void updatePosition(GraphFeature? newPosition) {
position.value = newPosition;
update();
}
}
class _PathFindingData {
final GraphFeature start;
final bool Function(GraphFeature) endSelector;
final List<Feature> features;
_PathFindingData(this.start, this.endSelector, this.features);
}
Result<List<(GraphFeature, double)>> _findShortestPathIsolate(
_PathFindingData data) {
return findShortestPath(data.start, data.endSelector, data.features);
}

View File

@ -92,6 +92,19 @@ class Feature with _$Feature {
return bail("Feature Geometry is not a Polygon or Point"); return bail("Feature Geometry is not a Polygon or Point");
} }
} }
String? get buildingName => type.when(
building: () => name,
lectureHall: () => building,
room: (_) => building,
door: (_) => null,
toilet: (_) => building,
stairs: (_) => building,
lift: (_) => building,
foodDrink: () => building,
publicTransport: (_, __) => null,
pcPool: (_) => building,
);
} }
@freezed @freezed

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:uninav/controllers/map_controller.dart'; import 'package:uninav/controllers/map_controller.dart';
import 'package:uninav/controllers/navigation_controller.dart';
import 'package:uninav/controllers/shared_prefs_controller.dart'; import 'package:uninav/controllers/shared_prefs_controller.dart';
import 'package:uninav/map.dart'; import 'package:uninav/map.dart';
import 'package:uninav/settings.dart'; import 'package:uninav/settings.dart';
@ -13,6 +14,8 @@ void main() {
.loadString('assets/geo/uulm_beta.geojson') .loadString('assets/geo/uulm_beta.geojson')
.then((value) => Get.find<MyMapController>().loadGeoJson(value)); .then((value) => Get.find<MyMapController>().loadGeoJson(value));
Get.put(NavigationController());
Get.putAsync(() async { Get.putAsync(() async {
final controller = SharedPrefsController(); final controller = SharedPrefsController();
await controller.initialize(); await controller.initialize();
@ -43,7 +46,7 @@ class MyApp extends StatelessWidget {
), ),
initialRoute: '/map', initialRoute: '/map',
getPages: [ getPages: [
GetPage(name: '/map', page: () => const MapPage()), GetPage(name: '/map', page: () => MapPage()),
GetPage(name: '/settings', page: () => const SettingsPage()), GetPage(name: '/settings', page: () => const SettingsPage()),
], ],
); );

View File

@ -6,18 +6,45 @@ import 'package:flutter_map/flutter_map.dart';
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart'; import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:latlong2/latlong.dart'; import 'package:latlong2/latlong.dart';
import 'package:rust_core/iter.dart';
import 'package:rust_core/slice.dart'; import 'package:rust_core/slice.dart';
import 'package:uninav/components/EventLog.dart';
import 'package:uninav/components/drawer.dart'; import 'package:uninav/components/drawer.dart';
import 'package:uninav/components/hamburger_menu.dart'; import 'package:uninav/components/hamburger_menu.dart';
import 'package:uninav/components/map_render_level.dart'; import 'package:uninav/components/map_render_level.dart';
import 'package:uninav/components/render_route.dart';
import 'package:uninav/controllers/map_controller.dart'; import 'package:uninav/controllers/map_controller.dart';
import 'package:uninav/controllers/navigation_controller.dart';
import 'package:uninav/data/geo/model.dart'; import 'package:uninav/data/geo/model.dart';
import 'package:uninav/nav/graph.dart';
import 'package:uninav/util/geojson_util.dart'; import 'package:uninav/util/geojson_util.dart';
import 'package:uninav/util/geomath.dart'; import 'package:uninav/util/geomath.dart';
import 'package:uninav/util/util.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class MapPage extends StatelessWidget { class MapPage extends StatefulWidget {
const MapPage({Key? key}) : super(key: key); @override
State<MapPage> createState() => _MapPageState();
}
class _MapPageState extends State<MapPage> {
late final Stream<LocationMarkerPosition?> _positionStream;
late final Stream<LocationMarkerHeading?> _headingStream;
/*
@override
void initState() {
super.initState();
const factory = LocationMarkerDataStreamFactory();
_positionStream =
factory.fromGeolocatorPositionStream().asBroadcastStream();
_headingStream = factory.fromCompassHeadingStream().asBroadcastStream();
_geolocatorStream =
factory.defaultPositionStreamSource().asBroadcastStream();
_compassStream = factory.defaultHeadingStreamSource().asBroadcastStream();
}
*/
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -67,6 +94,13 @@ class MapPage extends StatelessWidget {
TileLayer( TileLayer(
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png", urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
maxZoom: 19, maxZoom: 19,
tileBuilder: (context, tile, child) => ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.white.withOpacity(0.7),
BlendMode.srcATop,
),
child: tile,
),
), ),
TranslucentPointer( TranslucentPointer(
child: LevelLayer( child: LevelLayer(
@ -110,8 +144,26 @@ class MapPage extends StatelessWidget {
)), )),
), ),
CurrentLocationLayer(), CurrentLocationLayer(),
NavigationPathLayer(),
], ],
), ),
Positioned(
left: 16,
top: 16,
child: Container(
height: 450,
width: 150,
child: GetBuilder<NavigationController>(
builder: (controller) {
if (controller.nav.isNotEmpty) {
return EventLog(
events: controller.nav.map((e) => e.$1).toList());
} else {
return SizedBox();
}
},
),
)),
Positioned( Positioned(
left: 16, left: 16,
bottom: 16, bottom: 16,
@ -231,9 +283,89 @@ void locationBottomSheet() {
Expanded( Expanded(
flex: 2, flex: 2,
child: Container( child: Container(
height: 300, height: 300,
color: Colors.transparent, color: Colors.transparent,
), child: Column(
children: [
const Text(
'Level',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 10),
GetBuilder<MyMapController>(
builder: (controller) {
return Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final level in controller.levels)
GestureDetector(
onTap: () => controller.setLevel(level),
child: Chip(
label: Text("$level"),
backgroundColor:
controller.currentLevel == level
? Colors.blue
: Colors.grey[800],
),
),
Obx(() {
final navController =
Get.find<NavigationController>();
String? curBuilding;
if (navController.position.value
is BuildingFloor) {
curBuilding = (navController
.position.value as BuildingFloor)
.building
.name;
}
final buildingList = controller.features
.where((f) => f.type is Building);
return Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final building in buildingList)
GestureDetector(
onTap: () {
// print(building.name);
// print(curBuilding);
// print(navController.position);
navController.updatePosition(wrap(
building,
controller
.currentLevel.value,
building.name)
.firstOrNull);
},
child: Chip(
label: Text(building.name),
backgroundColor:
eq(curBuilding, building.name)
? building.level ==
controller
.currentLevel
? Colors.blue
: Colors.orange
: Colors.grey[800],
),
),
],
);
})
],
);
},
),
],
)),
), ),
Expanded( Expanded(
flex: 1, flex: 1,

View File

@ -44,6 +44,19 @@ class GraphFeature with _$GraphFeature {
basicFeature: (floor, building, feature) => feature.id, basicFeature: (floor, building, feature) => feature.id,
); );
// String? get buildingName => when(
// buildingFloor: (floor, building) => building.name,
// portal: (fromFloor, from, toFloor, to, baseFeature) =>
// baseFeature.type.maybeWhen(
// door: (_) => null,
// orElse: () => baseFeature.building,
// ),
// basicFeature: (floor, building, feature) => feature.type.maybeWhen(
// door: (_) => null,
// orElse: () => building,
// ),
// );
@override @override
String toString() { String toString() {
return when( return when(