feat: proper routing
This commit is contained in:
parent
a6f9cfdaf4
commit
1211c9b16e
98
lib/components/EventLog.dart
Normal file
98
lib/components/EventLog.dart
Normal 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),
|
||||
);
|
@ -3,7 +3,9 @@ import 'package:flutter/widgets.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:uninav/components/colorful_chips.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/nav/graph.dart';
|
||||
import 'package:uninav/util/util.dart';
|
||||
|
||||
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");
|
||||
},
|
||||
),
|
||||
],
|
||||
)
|
||||
|
34
lib/components/render_route.dart
Normal file
34
lib/components/render_route.dart
Normal 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,
|
||||
),
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
@ -41,6 +41,7 @@ class MyMapController extends GetxController {
|
||||
}
|
||||
newLevels.sort();
|
||||
levels.value = newLevels;
|
||||
update();
|
||||
}
|
||||
|
||||
Result<void> setLevel(int level) {
|
||||
@ -50,6 +51,7 @@ class MyMapController extends GetxController {
|
||||
}
|
||||
|
||||
currentLevel.value = level;
|
||||
update();
|
||||
return const Ok(());
|
||||
}
|
||||
|
||||
@ -111,6 +113,7 @@ class MyMapController extends GetxController {
|
||||
}
|
||||
|
||||
features.value = featuresList;
|
||||
update();
|
||||
} catch (e) {
|
||||
print('Error parsing GeoJSON: $e');
|
||||
}
|
||||
|
72
lib/controllers/navigation_controller.dart
Normal file
72
lib/controllers/navigation_controller.dart
Normal 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);
|
||||
}
|
@ -92,6 +92,19 @@ class Feature with _$Feature {
|
||||
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
|
||||
|
@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.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/map.dart';
|
||||
import 'package:uninav/settings.dart';
|
||||
@ -13,6 +14,8 @@ void main() {
|
||||
.loadString('assets/geo/uulm_beta.geojson')
|
||||
.then((value) => Get.find<MyMapController>().loadGeoJson(value));
|
||||
|
||||
Get.put(NavigationController());
|
||||
|
||||
Get.putAsync(() async {
|
||||
final controller = SharedPrefsController();
|
||||
await controller.initialize();
|
||||
@ -43,7 +46,7 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
initialRoute: '/map',
|
||||
getPages: [
|
||||
GetPage(name: '/map', page: () => const MapPage()),
|
||||
GetPage(name: '/map', page: () => MapPage()),
|
||||
GetPage(name: '/settings', page: () => const SettingsPage()),
|
||||
],
|
||||
);
|
||||
|
136
lib/map.dart
136
lib/map.dart
@ -6,18 +6,45 @@ import 'package:flutter_map/flutter_map.dart';
|
||||
import 'package:flutter_map_location_marker/flutter_map_location_marker.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:rust_core/iter.dart';
|
||||
import 'package:rust_core/slice.dart';
|
||||
import 'package:uninav/components/EventLog.dart';
|
||||
import 'package:uninav/components/drawer.dart';
|
||||
import 'package:uninav/components/hamburger_menu.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/navigation_controller.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/geomath.dart';
|
||||
import 'package:uninav/util/util.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class MapPage extends StatelessWidget {
|
||||
const MapPage({Key? key}) : super(key: key);
|
||||
class MapPage extends StatefulWidget {
|
||||
@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
|
||||
Widget build(BuildContext context) {
|
||||
@ -67,6 +94,13 @@ class MapPage extends StatelessWidget {
|
||||
TileLayer(
|
||||
urlTemplate: "https://tile.openstreetmap.org/{z}/{x}/{y}.png",
|
||||
maxZoom: 19,
|
||||
tileBuilder: (context, tile, child) => ColorFiltered(
|
||||
colorFilter: ColorFilter.mode(
|
||||
Colors.white.withOpacity(0.7),
|
||||
BlendMode.srcATop,
|
||||
),
|
||||
child: tile,
|
||||
),
|
||||
),
|
||||
TranslucentPointer(
|
||||
child: LevelLayer(
|
||||
@ -110,8 +144,26 @@ class MapPage extends StatelessWidget {
|
||||
)),
|
||||
),
|
||||
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(
|
||||
left: 16,
|
||||
bottom: 16,
|
||||
@ -233,8 +285,88 @@ void locationBottomSheet() {
|
||||
child: Container(
|
||||
height: 300,
|
||||
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(
|
||||
flex: 1,
|
||||
child: StatefulBuilder(builder: (context, setState) {
|
||||
|
@ -44,6 +44,19 @@ class GraphFeature with _$GraphFeature {
|
||||
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
|
||||
String toString() {
|
||||
return when(
|
||||
|
Loading…
Reference in New Issue
Block a user