feat: GPS and web compatible

This commit is contained in:
Yandrik 2024-04-21 14:21:31 +02:00
parent ad0c8e3124
commit a6f9cfdaf4
25 changed files with 2364 additions and 1069 deletions

View File

@ -25,6 +25,7 @@ if (flutterVersionName == null) {
android {
namespace "com.example.uninav"
compileSdk flutter.compileSdkVersion
compileSdkVersion 34
ndkVersion flutter.ndkVersion
compileOptions {
@ -45,7 +46,8 @@ android {
applicationId "com.example.uninav"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
minSdkVersion flutter.minSdkVersion
// minSdkVersion flutter.minSdkVersion
minSdkVersion 20
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName

View File

@ -41,4 +41,6 @@
<data android:mimeType="text/plain"/>
</intent>
</queries>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

View File

@ -384,7 +384,7 @@
"properties": {
"name": "O25",
"layer": "buildings",
"description": "connections: [o26, mensa]"
"description": "connections: [o26, mensa, outside]"
},
"geometry": {
"type": "Polygon",
@ -2432,6 +2432,38 @@
]
},
"id": "gzMDI"
},
{
"type": "Feature",
"properties": {
"name": "door",
"description": "type: door\nlevel: 2\nconnects: [o25, outside]",
"layer": "layer_2"
},
"geometry": {
"type": "Point",
"coordinates": [
9.955606,
48.422081
]
},
"id": "I5NTI"
},
{
"type": "Feature",
"properties": {
"name": "door",
"description": "type: door\nlevel: 2\nconnects: [o25, mensa]",
"layer": "layer_2"
},
"geometry": {
"type": "Point",
"coordinates": [
9.955426,
48.422062
]
},
"id": "c1NDY"
}
]
}

View File

@ -107,7 +107,9 @@ List<Widget> _buildFeatureContent(Feature feature) {
return feature.type.when(
building: () => _buildBuildingContent(feature),
lectureHall: () => _buildLectureHallContent(feature),
room: () => _buildRoomContent(feature),
room: (number) => _buildRoomContent(feature, number),
pcPool: (number) => _buildPcPoolContent(feature, number),
foodDrink: () => _buildFoodAndDrinkContent(feature),
door: (connects) => _buildDoorContent(feature, connects),
toilet: (toiletType) => _buildToiletContent(feature, toiletType),
stairs: (connectsLevels) => _buildStairsContent(feature, connectsLevels),
@ -130,10 +132,18 @@ List<Widget> _buildLectureHallContent(Feature feature) {
}
/// Builds the content for the Room feature type.
List<Widget> _buildRoomContent(Feature feature) {
List<Widget> _buildRoomContent(Feature feature, String roomNumber) {
return [Text('Room: ${feature.name}')];
}
List<Widget> _buildPcPoolContent(Feature feature, String roomNumber) {
return [Text('PC Pool: ${feature.name}')];
}
List<Widget> _buildFoodAndDrinkContent(Feature feature) {
return [Text('${feature.name} (Food/Drink)')];
}
/// Builds the content for the Door feature type.
List<Widget> _buildDoorContent(Feature feature, List<String> connects) {
return [

View File

@ -14,6 +14,7 @@ List<Widget> renderLevel(
int level,
) {
return <Widget>[
// Lecture Halls
LevelLayer(
filter: (feature) =>
feature.level == level && feature.type is LectureHall,
@ -43,19 +44,39 @@ List<Widget> renderLevel(
alignment: Alignment.center,
),
),
// Rooms (Seminar Rooms)
LevelLayer(
filter: (feature) => feature.level == level && feature.type is Room,
polyConstructor: (feature) => feature
.getPolygon(
constructor: (pts) => Polygon(
points: pts,
color: Colors.green.withOpacity(1.2),
borderColor: Colors.green,
borderStrokeWidth: 2,
),
)
.unwrap(),
),
filter: (feature) => feature.level == level && feature.type is Room,
polyConstructor: (feature) => feature
.getPolygon(
constructor: (pts) => Polygon(
points: pts,
color: Colors.green.withOpacity(1.2),
borderColor: Colors.green,
borderStrokeWidth: 2,
),
)
.unwrap(),
markerConstructor: (feature) => Marker(
width: 100,
height: 70,
point: feature.getPoint().unwrap(),
child: Column(
children: [
const Icon(
Icons.co_present_rounded,
color: Colors.amber,
),
Text(
(feature.type as Room).roomNumber,
style: const TextStyle(color: Colors.black),
),
],
),
alignment: Alignment.center,
)),
// Doors Layer
LevelLayer(
filter: (feature) => feature.level == level && feature.type is Door,
markerConstructor: (feature) {
@ -72,6 +93,72 @@ List<Widget> renderLevel(
);
},
),
// Food and Drink Layer
LevelLayer(
filter: (feature) => feature.level == level && feature.type is FoodDrink,
markerConstructor: (feature) {
final point = feature.getPoint().unwrap();
return Marker(
width: 21,
height: 21,
point: point,
child: const Icon(
Icons.restaurant,
color: Colors.deepOrange,
),
alignment: Alignment.center,
);
},
polyConstructor: (feature) => feature
.getPolygon(
constructor: (pts) => Polygon(
points: pts,
color: Colors.deepOrange.withOpacity(0.2),
borderColor: Colors.deepOrange,
borderStrokeWidth: 2,
),
)
.unwrap(),
),
// PC Pools layer
LevelLayer(
filter: (feature) => feature.level == level && feature.type is PcPool,
markerConstructor: (feature) {
final point = feature.getPoint().unwrap();
return Marker(
width: 100,
height: 70,
point: point,
child: Column(
children: [
const Icon(
Icons.computer,
color: Colors.cyan,
),
Text(
(feature.type as PcPool).roomNumber,
style: const TextStyle(color: Colors.black),
),
],
),
alignment: Alignment.center,
);
},
polyConstructor: (feature) => feature
.getPolygon(
constructor: (pts) => Polygon(
points: pts,
color: Colors.cyan.withOpacity(0.2),
borderColor: Colors.cyan,
borderStrokeWidth: 2,
),
)
.unwrap(),
),
// Toilets Layer
LevelLayer(
filter: (feature) => feature.level == level && feature.type is Toilet,
markerConstructor: (feature) {
@ -84,12 +171,14 @@ List<Widget> renderLevel(
point: point,
child: Icon(
findToiletIcon(type),
color: Colors.purple,
color: Colors.blue.shade700,
),
alignment: Alignment.center,
);
},
),
// Stairs layer
LevelLayer(
filter: (feature) =>
feature.type is Stairs &&
@ -108,6 +197,8 @@ List<Widget> renderLevel(
);
},
),
// Lift layer
LevelLayer(
filter: (feature) =>
feature.type is Lift &&

View File

@ -1,910 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'isar_controller.dart';
// **************************************************************************
// IsarCollectionGenerator
// **************************************************************************
// coverage:ignore-file
// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters, always_specify_types
extension GetSettingsCollection on Isar {
IsarCollection<Settings> get settings => this.collection();
}
const SettingsSchema = CollectionSchema(
name: r'Settings',
id: -8656046621518759136,
properties: {
r'femaleToilets': PropertySchema(
id: 0,
name: r'femaleToilets',
type: IsarType.bool,
),
r'handicapToilets': PropertySchema(
id: 1,
name: r'handicapToilets',
type: IsarType.bool,
),
r'maleToilets': PropertySchema(
id: 2,
name: r'maleToilets',
type: IsarType.bool,
),
r'showComputerPools': PropertySchema(
id: 3,
name: r'showComputerPools',
type: IsarType.bool,
),
r'showDoors': PropertySchema(
id: 4,
name: r'showDoors',
type: IsarType.bool,
),
r'showElevators': PropertySchema(
id: 5,
name: r'showElevators',
type: IsarType.bool,
),
r'showFoodAndDrink': PropertySchema(
id: 6,
name: r'showFoodAndDrink',
type: IsarType.bool,
),
r'showIcons': PropertySchema(
id: 7,
name: r'showIcons',
type: IsarType.bool,
),
r'showLectureHalls': PropertySchema(
id: 8,
name: r'showLectureHalls',
type: IsarType.bool,
),
r'showSeminarRooms': PropertySchema(
id: 9,
name: r'showSeminarRooms',
type: IsarType.bool,
),
r'showStairs': PropertySchema(
id: 10,
name: r'showStairs',
type: IsarType.bool,
),
r'showToilets': PropertySchema(
id: 11,
name: r'showToilets',
type: IsarType.bool,
)
},
estimateSize: _settingsEstimateSize,
serialize: _settingsSerialize,
deserialize: _settingsDeserialize,
deserializeProp: _settingsDeserializeProp,
idName: r'id',
indexes: {},
links: {},
embeddedSchemas: {},
getId: _settingsGetId,
getLinks: _settingsGetLinks,
attach: _settingsAttach,
version: '3.1.0+1',
);
int _settingsEstimateSize(
Settings object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;
return bytesCount;
}
void _settingsSerialize(
Settings object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
writer.writeBool(offsets[0], object.femaleToilets);
writer.writeBool(offsets[1], object.handicapToilets);
writer.writeBool(offsets[2], object.maleToilets);
writer.writeBool(offsets[3], object.showComputerPools);
writer.writeBool(offsets[4], object.showDoors);
writer.writeBool(offsets[5], object.showElevators);
writer.writeBool(offsets[6], object.showFoodAndDrink);
writer.writeBool(offsets[7], object.showIcons);
writer.writeBool(offsets[8], object.showLectureHalls);
writer.writeBool(offsets[9], object.showSeminarRooms);
writer.writeBool(offsets[10], object.showStairs);
writer.writeBool(offsets[11], object.showToilets);
}
Settings _settingsDeserialize(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = Settings();
object.femaleToilets = reader.readBool(offsets[0]);
object.handicapToilets = reader.readBool(offsets[1]);
object.id = id;
object.maleToilets = reader.readBool(offsets[2]);
object.showComputerPools = reader.readBool(offsets[3]);
object.showDoors = reader.readBool(offsets[4]);
object.showElevators = reader.readBool(offsets[5]);
object.showFoodAndDrink = reader.readBool(offsets[6]);
object.showIcons = reader.readBool(offsets[7]);
object.showLectureHalls = reader.readBool(offsets[8]);
object.showSeminarRooms = reader.readBool(offsets[9]);
object.showStairs = reader.readBool(offsets[10]);
object.showToilets = reader.readBool(offsets[11]);
return object;
}
P _settingsDeserializeProp<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type, List<int>> allOffsets,
) {
switch (propertyId) {
case 0:
return (reader.readBool(offset)) as P;
case 1:
return (reader.readBool(offset)) as P;
case 2:
return (reader.readBool(offset)) as P;
case 3:
return (reader.readBool(offset)) as P;
case 4:
return (reader.readBool(offset)) as P;
case 5:
return (reader.readBool(offset)) as P;
case 6:
return (reader.readBool(offset)) as P;
case 7:
return (reader.readBool(offset)) as P;
case 8:
return (reader.readBool(offset)) as P;
case 9:
return (reader.readBool(offset)) as P;
case 10:
return (reader.readBool(offset)) as P;
case 11:
return (reader.readBool(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
}
Id _settingsGetId(Settings object) {
return object.id;
}
List<IsarLinkBase<dynamic>> _settingsGetLinks(Settings object) {
return [];
}
void _settingsAttach(IsarCollection<dynamic> col, Id id, Settings object) {
object.id = id;
}
extension SettingsQueryWhereSort on QueryBuilder<Settings, Settings, QWhere> {
QueryBuilder<Settings, Settings, QAfterWhere> anyId() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
}
extension SettingsQueryWhere on QueryBuilder<Settings, Settings, QWhereClause> {
QueryBuilder<Settings, Settings, QAfterWhereClause> idEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: id,
upper: id,
));
});
}
QueryBuilder<Settings, Settings, QAfterWhereClause> idNotEqualTo(Id id) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
)
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
);
} else {
return query
.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: false),
)
.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: false),
);
}
});
}
QueryBuilder<Settings, Settings, QAfterWhereClause> idGreaterThan(Id id,
{bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: id, includeLower: include),
);
});
}
QueryBuilder<Settings, Settings, QAfterWhereClause> idLessThan(Id id,
{bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: id, includeUpper: include),
);
});
}
QueryBuilder<Settings, Settings, QAfterWhereClause> idBetween(
Id lowerId,
Id upperId, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: lowerId,
includeLower: includeLower,
upper: upperId,
includeUpper: includeUpper,
));
});
}
}
extension SettingsQueryFilter
on QueryBuilder<Settings, Settings, QFilterCondition> {
QueryBuilder<Settings, Settings, QAfterFilterCondition> femaleToiletsEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'femaleToilets',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition>
handicapToiletsEqualTo(bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'handicapToilets',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'id',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> idGreaterThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> idLessThan(
Id value, {
bool include = false,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'id',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> idBetween(
Id lower,
Id upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'id',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> maleToiletsEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'maleToilets',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition>
showComputerPoolsEqualTo(bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'showComputerPools',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> showDoorsEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'showDoors',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> showElevatorsEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'showElevators',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition>
showFoodAndDrinkEqualTo(bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'showFoodAndDrink',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> showIconsEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'showIcons',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition>
showLectureHallsEqualTo(bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'showLectureHalls',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition>
showSeminarRoomsEqualTo(bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'showSeminarRooms',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> showStairsEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'showStairs',
value: value,
));
});
}
QueryBuilder<Settings, Settings, QAfterFilterCondition> showToiletsEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'showToilets',
value: value,
));
});
}
}
extension SettingsQueryObject
on QueryBuilder<Settings, Settings, QFilterCondition> {}
extension SettingsQueryLinks
on QueryBuilder<Settings, Settings, QFilterCondition> {}
extension SettingsQuerySortBy on QueryBuilder<Settings, Settings, QSortBy> {
QueryBuilder<Settings, Settings, QAfterSortBy> sortByFemaleToilets() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'femaleToilets', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByFemaleToiletsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'femaleToilets', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByHandicapToilets() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'handicapToilets', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByHandicapToiletsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'handicapToilets', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByMaleToilets() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'maleToilets', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByMaleToiletsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'maleToilets', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowComputerPools() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showComputerPools', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowComputerPoolsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showComputerPools', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowDoors() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showDoors', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowDoorsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showDoors', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowElevators() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showElevators', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowElevatorsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showElevators', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowFoodAndDrink() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showFoodAndDrink', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowFoodAndDrinkDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showFoodAndDrink', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowIcons() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showIcons', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowIconsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showIcons', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowLectureHalls() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showLectureHalls', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowLectureHallsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showLectureHalls', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowSeminarRooms() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showSeminarRooms', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowSeminarRoomsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showSeminarRooms', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowStairs() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showStairs', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowStairsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showStairs', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowToilets() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showToilets', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> sortByShowToiletsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showToilets', Sort.desc);
});
}
}
extension SettingsQuerySortThenBy
on QueryBuilder<Settings, Settings, QSortThenBy> {
QueryBuilder<Settings, Settings, QAfterSortBy> thenByFemaleToilets() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'femaleToilets', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByFemaleToiletsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'femaleToilets', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByHandicapToilets() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'handicapToilets', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByHandicapToiletsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'handicapToilets', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByIdDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByMaleToilets() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'maleToilets', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByMaleToiletsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'maleToilets', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowComputerPools() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showComputerPools', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowComputerPoolsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showComputerPools', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowDoors() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showDoors', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowDoorsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showDoors', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowElevators() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showElevators', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowElevatorsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showElevators', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowFoodAndDrink() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showFoodAndDrink', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowFoodAndDrinkDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showFoodAndDrink', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowIcons() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showIcons', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowIconsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showIcons', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowLectureHalls() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showLectureHalls', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowLectureHallsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showLectureHalls', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowSeminarRooms() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showSeminarRooms', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowSeminarRoomsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showSeminarRooms', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowStairs() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showStairs', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowStairsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showStairs', Sort.desc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowToilets() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showToilets', Sort.asc);
});
}
QueryBuilder<Settings, Settings, QAfterSortBy> thenByShowToiletsDesc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'showToilets', Sort.desc);
});
}
}
extension SettingsQueryWhereDistinct
on QueryBuilder<Settings, Settings, QDistinct> {
QueryBuilder<Settings, Settings, QDistinct> distinctByFemaleToilets() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'femaleToilets');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByHandicapToilets() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'handicapToilets');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByMaleToilets() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'maleToilets');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByShowComputerPools() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'showComputerPools');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByShowDoors() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'showDoors');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByShowElevators() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'showElevators');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByShowFoodAndDrink() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'showFoodAndDrink');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByShowIcons() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'showIcons');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByShowLectureHalls() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'showLectureHalls');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByShowSeminarRooms() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'showSeminarRooms');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByShowStairs() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'showStairs');
});
}
QueryBuilder<Settings, Settings, QDistinct> distinctByShowToilets() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'showToilets');
});
}
}
extension SettingsQueryProperty
on QueryBuilder<Settings, Settings, QQueryProperty> {
QueryBuilder<Settings, int, QQueryOperations> idProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'id');
});
}
QueryBuilder<Settings, bool, QQueryOperations> femaleToiletsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'femaleToilets');
});
}
QueryBuilder<Settings, bool, QQueryOperations> handicapToiletsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'handicapToilets');
});
}
QueryBuilder<Settings, bool, QQueryOperations> maleToiletsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'maleToilets');
});
}
QueryBuilder<Settings, bool, QQueryOperations> showComputerPoolsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'showComputerPools');
});
}
QueryBuilder<Settings, bool, QQueryOperations> showDoorsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'showDoors');
});
}
QueryBuilder<Settings, bool, QQueryOperations> showElevatorsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'showElevators');
});
}
QueryBuilder<Settings, bool, QQueryOperations> showFoodAndDrinkProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'showFoodAndDrink');
});
}
QueryBuilder<Settings, bool, QQueryOperations> showIconsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'showIcons');
});
}
QueryBuilder<Settings, bool, QQueryOperations> showLectureHallsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'showLectureHalls');
});
}
QueryBuilder<Settings, bool, QQueryOperations> showSeminarRoomsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'showSeminarRooms');
});
}
QueryBuilder<Settings, bool, QQueryOperations> showStairsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'showStairs');
});
}
QueryBuilder<Settings, bool, QQueryOperations> showToiletsProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'showToilets');
});
}
}

View File

@ -1,14 +1,17 @@
import 'dart:async';
import 'dart:convert';
import 'package:anyhow/anyhow.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:uninav/components/feature_bottom_sheet.dart';
import 'package:uninav/data/geo/model.dart';
import 'package:uninav/data/geo/parser.dart';
import 'package:uninav/util/geojson_util.dart';
import 'package:uninav/util/geolocator.dart';
import 'package:uninav/util/geomath.dart';
class MyMapController extends GetxController {
@ -17,6 +20,10 @@ class MyMapController extends GetxController {
final RxList<Feature> features = <Feature>[].obs;
final currentLevel = 1.obs;
final levels = <int>[1].obs;
final Rx<Position?> position = null.obs;
bool _locationEnsured = false;
@override
onInit() async {
print("init");
@ -98,6 +105,8 @@ class MyMapController extends GetxController {
feature.geometry, feature.id);
if (parsed case Ok(:final ok)) {
featuresList.add(ok);
} else {
print('Error parsing feature: $parsed');
}
}
@ -137,4 +146,46 @@ class MyMapController extends GetxController {
}
showFeatureBottomSheet(feature, closestFeatures);
}
Future<Result<Position>> getCurrentPosition() async {
if (!_locationEnsured) {
final ensureRes = await ensureLocationPermission();
if (ensureRes is Err) {
// TODO: check that it works
return ensureRes as Err<Position>;
}
}
_locationEnsured = true;
try {
final pos = await Geolocator.getCurrentPosition(
// desiredAccuracy: LocationAccuracy.high,
timeLimit: Duration(minutes: 1),
);
position.value = pos;
return Ok(pos);
} on TimeoutException catch (e) {
return bail("Timeout while waiting for location lock: $e");
}
}
Future<Result<()>> subscribePosition() async {
if (!_locationEnsured) {
final ensureRes = await ensureLocationPermission();
if (ensureRes is Err) {
// TODO: check that it works
return ensureRes;
}
}
_locationEnsured = true;
Geolocator.getPositionStream(
locationSettings: const LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 100,
//timeLimit: Duration(minutes: 10)
),
).listen((pos) {
position.value = pos;
});
return const Ok(());
}
}

View File

@ -0,0 +1,72 @@
import 'dart:convert';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'shared_prefs_controller.freezed.dart';
part 'shared_prefs_controller.g.dart';
class SharedPrefsController {
late SharedPreferences _sharedPrefs;
final Rx<Settings> settings = Settings().obs;
SharedPrefsController();
Future<void> initialize() async {
_sharedPrefs = await SharedPreferences.getInstance();
try {
final settingsJson = _sharedPrefs.getString("settings");
if (settingsJson != null) {
settings.value = Settings.fromJson(jsonDecode(settingsJson));
} else {
settings.value = const Settings();
}
} catch (e) {
settings.value = const Settings();
}
}
Future<void> persistSettings() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('settings', jsonEncode(settings.value));
}
Future<Settings> loadSettings() async {
final prefs = await SharedPreferences.getInstance();
final settingsJson = prefs.getString('settings');
return settingsJson != null
? Settings.fromJson(jsonDecode(settingsJson))
: const Settings();
}
}
@freezed
class Settings with _$Settings {
const factory Settings({
@Default(1) int id,
@Default(true) bool showIcons,
@Default(true) bool showElevators,
@Default(true) bool showFoodAndDrink,
@Default(true) bool showLectureHalls,
@Default(true) bool showComputerPools,
@Default(true) bool showSeminarRooms,
@Default(true) bool showToilets,
@Default(true) bool showStairs,
@Default(true) bool showDoors,
@Default(false) bool maleToilets,
@Default(false) bool femaleToilets,
@Default(false) bool handicapToilets,
}) = _Settings;
factory Settings.fromJson(Map<String, dynamic> json) =>
_$SettingsFromJson(json);
}
enum ToiletPreference {
male,
female,
disabled,
}

View File

@ -0,0 +1,433 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'shared_prefs_controller.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
Settings _$SettingsFromJson(Map<String, dynamic> json) {
return _Settings.fromJson(json);
}
/// @nodoc
mixin _$Settings {
int get id => throw _privateConstructorUsedError;
bool get showIcons => throw _privateConstructorUsedError;
bool get showElevators => throw _privateConstructorUsedError;
bool get showFoodAndDrink => throw _privateConstructorUsedError;
bool get showLectureHalls => throw _privateConstructorUsedError;
bool get showComputerPools => throw _privateConstructorUsedError;
bool get showSeminarRooms => throw _privateConstructorUsedError;
bool get showToilets => throw _privateConstructorUsedError;
bool get showStairs => throw _privateConstructorUsedError;
bool get showDoors => throw _privateConstructorUsedError;
bool get maleToilets => throw _privateConstructorUsedError;
bool get femaleToilets => throw _privateConstructorUsedError;
bool get handicapToilets => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$SettingsCopyWith<Settings> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SettingsCopyWith<$Res> {
factory $SettingsCopyWith(Settings value, $Res Function(Settings) then) =
_$SettingsCopyWithImpl<$Res, Settings>;
@useResult
$Res call(
{int id,
bool showIcons,
bool showElevators,
bool showFoodAndDrink,
bool showLectureHalls,
bool showComputerPools,
bool showSeminarRooms,
bool showToilets,
bool showStairs,
bool showDoors,
bool maleToilets,
bool femaleToilets,
bool handicapToilets});
}
/// @nodoc
class _$SettingsCopyWithImpl<$Res, $Val extends Settings>
implements $SettingsCopyWith<$Res> {
_$SettingsCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? showIcons = null,
Object? showElevators = null,
Object? showFoodAndDrink = null,
Object? showLectureHalls = null,
Object? showComputerPools = null,
Object? showSeminarRooms = null,
Object? showToilets = null,
Object? showStairs = null,
Object? showDoors = null,
Object? maleToilets = null,
Object? femaleToilets = null,
Object? handicapToilets = null,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
showIcons: null == showIcons
? _value.showIcons
: showIcons // ignore: cast_nullable_to_non_nullable
as bool,
showElevators: null == showElevators
? _value.showElevators
: showElevators // ignore: cast_nullable_to_non_nullable
as bool,
showFoodAndDrink: null == showFoodAndDrink
? _value.showFoodAndDrink
: showFoodAndDrink // ignore: cast_nullable_to_non_nullable
as bool,
showLectureHalls: null == showLectureHalls
? _value.showLectureHalls
: showLectureHalls // ignore: cast_nullable_to_non_nullable
as bool,
showComputerPools: null == showComputerPools
? _value.showComputerPools
: showComputerPools // ignore: cast_nullable_to_non_nullable
as bool,
showSeminarRooms: null == showSeminarRooms
? _value.showSeminarRooms
: showSeminarRooms // ignore: cast_nullable_to_non_nullable
as bool,
showToilets: null == showToilets
? _value.showToilets
: showToilets // ignore: cast_nullable_to_non_nullable
as bool,
showStairs: null == showStairs
? _value.showStairs
: showStairs // ignore: cast_nullable_to_non_nullable
as bool,
showDoors: null == showDoors
? _value.showDoors
: showDoors // ignore: cast_nullable_to_non_nullable
as bool,
maleToilets: null == maleToilets
? _value.maleToilets
: maleToilets // ignore: cast_nullable_to_non_nullable
as bool,
femaleToilets: null == femaleToilets
? _value.femaleToilets
: femaleToilets // ignore: cast_nullable_to_non_nullable
as bool,
handicapToilets: null == handicapToilets
? _value.handicapToilets
: handicapToilets // ignore: cast_nullable_to_non_nullable
as bool,
) as $Val);
}
}
/// @nodoc
abstract class _$$SettingsImplCopyWith<$Res>
implements $SettingsCopyWith<$Res> {
factory _$$SettingsImplCopyWith(
_$SettingsImpl value, $Res Function(_$SettingsImpl) then) =
__$$SettingsImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{int id,
bool showIcons,
bool showElevators,
bool showFoodAndDrink,
bool showLectureHalls,
bool showComputerPools,
bool showSeminarRooms,
bool showToilets,
bool showStairs,
bool showDoors,
bool maleToilets,
bool femaleToilets,
bool handicapToilets});
}
/// @nodoc
class __$$SettingsImplCopyWithImpl<$Res>
extends _$SettingsCopyWithImpl<$Res, _$SettingsImpl>
implements _$$SettingsImplCopyWith<$Res> {
__$$SettingsImplCopyWithImpl(
_$SettingsImpl _value, $Res Function(_$SettingsImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? showIcons = null,
Object? showElevators = null,
Object? showFoodAndDrink = null,
Object? showLectureHalls = null,
Object? showComputerPools = null,
Object? showSeminarRooms = null,
Object? showToilets = null,
Object? showStairs = null,
Object? showDoors = null,
Object? maleToilets = null,
Object? femaleToilets = null,
Object? handicapToilets = null,
}) {
return _then(_$SettingsImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
showIcons: null == showIcons
? _value.showIcons
: showIcons // ignore: cast_nullable_to_non_nullable
as bool,
showElevators: null == showElevators
? _value.showElevators
: showElevators // ignore: cast_nullable_to_non_nullable
as bool,
showFoodAndDrink: null == showFoodAndDrink
? _value.showFoodAndDrink
: showFoodAndDrink // ignore: cast_nullable_to_non_nullable
as bool,
showLectureHalls: null == showLectureHalls
? _value.showLectureHalls
: showLectureHalls // ignore: cast_nullable_to_non_nullable
as bool,
showComputerPools: null == showComputerPools
? _value.showComputerPools
: showComputerPools // ignore: cast_nullable_to_non_nullable
as bool,
showSeminarRooms: null == showSeminarRooms
? _value.showSeminarRooms
: showSeminarRooms // ignore: cast_nullable_to_non_nullable
as bool,
showToilets: null == showToilets
? _value.showToilets
: showToilets // ignore: cast_nullable_to_non_nullable
as bool,
showStairs: null == showStairs
? _value.showStairs
: showStairs // ignore: cast_nullable_to_non_nullable
as bool,
showDoors: null == showDoors
? _value.showDoors
: showDoors // ignore: cast_nullable_to_non_nullable
as bool,
maleToilets: null == maleToilets
? _value.maleToilets
: maleToilets // ignore: cast_nullable_to_non_nullable
as bool,
femaleToilets: null == femaleToilets
? _value.femaleToilets
: femaleToilets // ignore: cast_nullable_to_non_nullable
as bool,
handicapToilets: null == handicapToilets
? _value.handicapToilets
: handicapToilets // ignore: cast_nullable_to_non_nullable
as bool,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SettingsImpl implements _Settings {
const _$SettingsImpl(
{this.id = 1,
this.showIcons = true,
this.showElevators = true,
this.showFoodAndDrink = true,
this.showLectureHalls = true,
this.showComputerPools = true,
this.showSeminarRooms = true,
this.showToilets = true,
this.showStairs = true,
this.showDoors = true,
this.maleToilets = false,
this.femaleToilets = false,
this.handicapToilets = false});
factory _$SettingsImpl.fromJson(Map<String, dynamic> json) =>
_$$SettingsImplFromJson(json);
@override
@JsonKey()
final int id;
@override
@JsonKey()
final bool showIcons;
@override
@JsonKey()
final bool showElevators;
@override
@JsonKey()
final bool showFoodAndDrink;
@override
@JsonKey()
final bool showLectureHalls;
@override
@JsonKey()
final bool showComputerPools;
@override
@JsonKey()
final bool showSeminarRooms;
@override
@JsonKey()
final bool showToilets;
@override
@JsonKey()
final bool showStairs;
@override
@JsonKey()
final bool showDoors;
@override
@JsonKey()
final bool maleToilets;
@override
@JsonKey()
final bool femaleToilets;
@override
@JsonKey()
final bool handicapToilets;
@override
String toString() {
return 'Settings(id: $id, showIcons: $showIcons, showElevators: $showElevators, showFoodAndDrink: $showFoodAndDrink, showLectureHalls: $showLectureHalls, showComputerPools: $showComputerPools, showSeminarRooms: $showSeminarRooms, showToilets: $showToilets, showStairs: $showStairs, showDoors: $showDoors, maleToilets: $maleToilets, femaleToilets: $femaleToilets, handicapToilets: $handicapToilets)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SettingsImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.showIcons, showIcons) ||
other.showIcons == showIcons) &&
(identical(other.showElevators, showElevators) ||
other.showElevators == showElevators) &&
(identical(other.showFoodAndDrink, showFoodAndDrink) ||
other.showFoodAndDrink == showFoodAndDrink) &&
(identical(other.showLectureHalls, showLectureHalls) ||
other.showLectureHalls == showLectureHalls) &&
(identical(other.showComputerPools, showComputerPools) ||
other.showComputerPools == showComputerPools) &&
(identical(other.showSeminarRooms, showSeminarRooms) ||
other.showSeminarRooms == showSeminarRooms) &&
(identical(other.showToilets, showToilets) ||
other.showToilets == showToilets) &&
(identical(other.showStairs, showStairs) ||
other.showStairs == showStairs) &&
(identical(other.showDoors, showDoors) ||
other.showDoors == showDoors) &&
(identical(other.maleToilets, maleToilets) ||
other.maleToilets == maleToilets) &&
(identical(other.femaleToilets, femaleToilets) ||
other.femaleToilets == femaleToilets) &&
(identical(other.handicapToilets, handicapToilets) ||
other.handicapToilets == handicapToilets));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
id,
showIcons,
showElevators,
showFoodAndDrink,
showLectureHalls,
showComputerPools,
showSeminarRooms,
showToilets,
showStairs,
showDoors,
maleToilets,
femaleToilets,
handicapToilets);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$SettingsImplCopyWith<_$SettingsImpl> get copyWith =>
__$$SettingsImplCopyWithImpl<_$SettingsImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SettingsImplToJson(
this,
);
}
}
abstract class _Settings implements Settings {
const factory _Settings(
{final int id,
final bool showIcons,
final bool showElevators,
final bool showFoodAndDrink,
final bool showLectureHalls,
final bool showComputerPools,
final bool showSeminarRooms,
final bool showToilets,
final bool showStairs,
final bool showDoors,
final bool maleToilets,
final bool femaleToilets,
final bool handicapToilets}) = _$SettingsImpl;
factory _Settings.fromJson(Map<String, dynamic> json) =
_$SettingsImpl.fromJson;
@override
int get id;
@override
bool get showIcons;
@override
bool get showElevators;
@override
bool get showFoodAndDrink;
@override
bool get showLectureHalls;
@override
bool get showComputerPools;
@override
bool get showSeminarRooms;
@override
bool get showToilets;
@override
bool get showStairs;
@override
bool get showDoors;
@override
bool get maleToilets;
@override
bool get femaleToilets;
@override
bool get handicapToilets;
@override
@JsonKey(ignore: true)
_$$SettingsImplCopyWith<_$SettingsImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -0,0 +1,41 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'shared_prefs_controller.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$SettingsImpl _$$SettingsImplFromJson(Map<String, dynamic> json) =>
_$SettingsImpl(
id: json['id'] as int? ?? 1,
showIcons: json['showIcons'] as bool? ?? true,
showElevators: json['showElevators'] as bool? ?? true,
showFoodAndDrink: json['showFoodAndDrink'] as bool? ?? true,
showLectureHalls: json['showLectureHalls'] as bool? ?? true,
showComputerPools: json['showComputerPools'] as bool? ?? true,
showSeminarRooms: json['showSeminarRooms'] as bool? ?? true,
showToilets: json['showToilets'] as bool? ?? true,
showStairs: json['showStairs'] as bool? ?? true,
showDoors: json['showDoors'] as bool? ?? true,
maleToilets: json['maleToilets'] as bool? ?? false,
femaleToilets: json['femaleToilets'] as bool? ?? false,
handicapToilets: json['handicapToilets'] as bool? ?? false,
);
Map<String, dynamic> _$$SettingsImplToJson(_$SettingsImpl instance) =>
<String, dynamic>{
'id': instance.id,
'showIcons': instance.showIcons,
'showElevators': instance.showElevators,
'showFoodAndDrink': instance.showFoodAndDrink,
'showLectureHalls': instance.showLectureHalls,
'showComputerPools': instance.showComputerPools,
'showSeminarRooms': instance.showSeminarRooms,
'showToilets': instance.showToilets,
'showStairs': instance.showStairs,
'showDoors': instance.showDoors,
'maleToilets': instance.maleToilets,
'femaleToilets': instance.femaleToilets,
'handicapToilets': instance.handicapToilets,
};

View File

@ -99,11 +99,13 @@ class FeatureType with _$FeatureType {
// multiple feature types like lecture hall, toliet, ...
const factory FeatureType.building() = Building;
const factory FeatureType.lectureHall() = LectureHall;
const factory FeatureType.room() = Room;
const factory FeatureType.room(String roomNumber) = Room;
const factory FeatureType.door(List<String> connects) = Door;
const factory FeatureType.toilet(String toilet_type) = Toilet;
const factory FeatureType.stairs(List<int> connects_levels) = Stairs;
const factory FeatureType.lift(List<int> connects_levels) = Lift;
const factory FeatureType.foodDrink() = FoodDrink;
const factory FeatureType.publicTransport(
List<String> bus_lines, List<String> tram_lines) = PublicTransport;
const factory FeatureType.pcPool(String roomNumber) = PcPool;
}

File diff suppressed because it is too large Load Diff

View File

@ -20,17 +20,20 @@ Result<Feature> parseFeature(
// try parse yaml
if (description_yaml == null) {
print("warn: Description key is missing for feature $name");
print(
"warn: Description key is missing for feature $name\n\n$geometry\n\n$properties");
}
if (layer == null) {
return bail("Layer key \'layer\' is missing for feature $name");
return bail(
"Layer key \'layer\' is missing for feature $name\n\n$geometry\n\n$properties");
}
dynamic yaml;
try {
yaml = loadYaml(description_yaml);
} on YamlException catch (e) {
return bail("Couldn't parse YAML in description for feature $name: $e");
return bail(
"Couldn't parse YAML in description for feature $name\n\n$description_yaml\n\nError: $e");
}
yaml = yaml as YamlMap? ?? {};
@ -70,8 +73,16 @@ Result<Feature> parseFeature(
case 'lecture_hall':
type = FeatureType.lectureHall();
break;
case 'room':
type = FeatureType.room();
case 'seminar_room':
type = FeatureType.room(getYamlKeyStringify(yaml, 'number')
.expect("Couldn't parse 'number' for seminar_room feature $name"));
break;
case 'pc_pool':
type = FeatureType.pcPool(getYamlKeyStringify(yaml, 'number')
.expect("Couldn't parse 'number' for PcPool feature $name"));
break;
case 'food_drink':
type = FeatureType.foodDrink();
break;
case 'door':
final list = getYamlList<String>(yaml, 'connects')
@ -84,10 +95,8 @@ Result<Feature> parseFeature(
type = FeatureType.toilet(toiletType);
break;
case 'public_transport':
final busLines = getYamlList<dynamic>(yaml, 'bus_lines')
.expect("Couldn't parse 'bus_lines' for feature $name");
final tramLines = getYamlList<dynamic>(yaml, 'tram_lines')
.expect("Couldn't parse 'tram_lines' for feature $name");
final busLines = getYamlList<dynamic>(yaml, 'bus_lines').unwrapOr([]);
final tramLines = getYamlList<dynamic>(yaml, 'tram_lines').unwrapOr([]);
type = FeatureType.publicTransport(
stringifyList(busLines)
@ -146,3 +155,15 @@ Result<T> getYamlKey<T>(YamlMap yaml, String key) {
return bail("Failed to parse yaml key $key as ${T.toString()}: $e");
}
}
Result<String> getYamlKeyStringify<T>(YamlMap yaml, String key) {
try {
final val = yaml[key] as T?;
if (val == null) {
return bail("Key $key is missing in yaml");
}
return Ok(val.toString());
} catch (e) {
return bail("Failed to parse yaml key $key as ${T.toString()}: $e");
}
}

View File

@ -1,20 +1,21 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:uninav/controllers/isar_controller.dart';
import 'package:uninav/controllers/map_controller.dart';
import 'package:uninav/controllers/shared_prefs_controller.dart';
import 'package:uninav/map.dart';
import 'package:uninav/settings.dart';
// TODO: maybe make not async?
void main() async {
void main() {
Get.put(MyMapController());
await Get.find<MyMapController>()
.loadGeoJson(await rootBundle.loadString('assets/geo/uulm_beta.geojson'));
rootBundle
.loadString('assets/geo/uulm_beta.geojson')
.then((value) => Get.find<MyMapController>().loadGeoJson(value));
await Get.putAsync(() async {
final controller = IsarController();
await controller.initializeIsar();
Get.putAsync(() async {
final controller = SharedPrefsController();
await controller.initialize();
return controller;
});
runApp(const MyApp());

View File

@ -1,7 +1,9 @@
import 'package:anim_search_bar/anim_search_bar.dart';
import 'package:anyhow/anyhow.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
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/slice.dart';
@ -42,8 +44,10 @@ class MapPage extends StatelessWidget {
floatingActionButton: FloatingActionButton(
onPressed: () async {
// Add onPressed logic here
var future = Get.find<MyMapController>().getCurrentPosition();
locationBottomSheet();
},
child: const Icon(Icons.add),
child: const Icon(Icons.location_searching),
),
body: Stack(
children: [
@ -105,6 +109,7 @@ class MapPage extends StatelessWidget {
Get.find<MyMapController>().currentLevel.value,
)),
),
CurrentLocationLayer(),
],
),
Positioned(
@ -176,3 +181,171 @@ class MapPage extends StatelessWidget {
));
}
}
void locationBottomSheet() {
print(Get.find<MyMapController>().position.value);
String buttonText = "Search for Location";
IconData locationIcon = Icons.location_searching;
bool spinner = false;
Get.bottomSheet(
Theme(
data: ThemeData.light(),
child: Container(
constraints: const BoxConstraints(
// minHeight: 300,
),
width: Get.mediaQuery.size.width,
decoration: const BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30),
),
),
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Center(
child: Container(
width: 50,
height: 5,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
),
),
const SizedBox(height: 10),
const Text(
'Select Location',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
const SizedBox(height: 20),
Row(
children: [
Expanded(
flex: 2,
child: Container(
height: 300,
color: Colors.transparent,
),
),
Expanded(
flex: 1,
child: StatefulBuilder(builder: (context, setState) {
// TODO: make this persist closing the bottom sheet
return ElevatedButton(
style: ElevatedButton.styleFrom(
fixedSize: const Size(300, 300),
backgroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsets.all(15),
),
onPressed: () async {
if (spinner) {
return;
}
setState(() {
buttonText = "Searching...";
locationIcon = Icons.location_searching;
spinner = true;
});
final pos = await Get.find<MyMapController>()
.getCurrentPosition();
if (!context.mounted) {
return;
}
if (pos case Ok(:final ok)) {
setState(() {
buttonText = "Location found!";
locationIcon = Icons.my_location;
spinner = false;
});
} else {
setState(() {
buttonText = "Location not found! Try again";
locationIcon = Icons.error;
spinner = false;
});
}
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Stack(
alignment: Alignment.center,
children: [
Icon(locationIcon,
size: 80,
color: Get.theme.colorScheme.inversePrimary),
if (spinner)
CircularProgressIndicator(
color: Get.theme.colorScheme.inversePrimary,
),
],
),
const SizedBox(
height: 12,
),
Text(
buttonText,
softWrap: true,
textAlign: TextAlign.center,
style: const TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.bold),
),
],
),
);
}),
),
],
),
/*
ListTile(
title: const Text(
'Current Location',
style: TextStyle(color: Colors.white),
),
onTap: () {
Get.back();
},
),
ListTile(
title: const Text(
'Search Location',
style: TextStyle(color: Colors.white),
),
onTap: () {
Get.back();
},
),
const SizedBox(height: 20),
*/
ElevatedButton(
child: const Text(
'Cancel',
style: TextStyle(color: Colors.black),
),
onPressed: () {
Get.back();
},
),
],
),
),
),
isScrollControlled: true,
enterBottomSheetDuration: const Duration(milliseconds: 150),
exitBottomSheetDuration: const Duration(milliseconds: 200),
);
}

647
lib/nav/graph.dart.old Normal file
View File

@ -0,0 +1,647 @@
import 'package:anyhow/anyhow.dart';
import 'package:collection/collection.dart';
import 'package:directed_graph/directed_graph.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:geojson_vi/geojson_vi.dart';
import 'package:get/get.dart';
import 'package:latlong2/latlong.dart';
import 'package:rust_core/iter.dart';
import 'package:uninav/data/geo/model.dart';
import 'package:uninav/util/geojson_util.dart';
import 'package:uninav/util/util.dart';
import 'dart:collection';
part 'graph.freezed.dart';
@freezed
class GraphFeature with _$GraphFeature {
const factory GraphFeature.buildingFloor(int floor, Feature building) =
BuildingFloor;
const factory GraphFeature.portal(int fromFloor, String from, int toFloor,
String to, Feature baseFeature) = Portal;
const factory GraphFeature.basicFeature(
int floor, String building, Feature feature) = BasicFeature;
const GraphFeature._();
Result<LatLng> getCenter() {
return when(
buildingFloor: (floor, building) => building.getCenterPoint(),
portal: (fromFloor, from, toFloor, to, baseFeature) =>
baseFeature.getCenterPoint(),
basicFeature: (floor, building, feature) => feature.getCenterPoint(),
);
}
double distanceTo(GraphFeature other, String unit) => distanceBetweenLatLng(
getCenter().unwrap(), other.getCenter().unwrap(), unit);
double metersTo(GraphFeature other) => distanceTo(other, "meters");
@override
String toString() {
return when(
buildingFloor: (floor, building) => 'Floor (${building.name}:$floor)',
portal: (fromFloor, from, toFloor, to, _) =>
'Portal ($from:$fromFloor -> $to:$toFloor)',
basicFeature: (floor, building, feature) =>
'Feature (${formatFeatureTitle(feature)} ($building:$floor))',
);
}
@override
int get hashCode {
return when(
buildingFloor: (floor, building) => Object.hash(floor, building),
portal: (fromFloor, from, toFloor, to, baseFeature) =>
Object.hash(fromFloor, from, toFloor, to, baseFeature),
basicFeature: (floor, building, feature) =>
Object.hash(floor, building, feature),
);
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is GraphFeature &&
other.when(
buildingFloor: (floor, building) =>
this is BuildingFloor &&
(this as BuildingFloor).floor == floor &&
(this as BuildingFloor).building == building,
portal: (fromFloor, from, toFloor, to, baseFeature) =>
this is Portal &&
(this as Portal).fromFloor == fromFloor &&
(this as Portal).from == from &&
(this as Portal).toFloor == toFloor &&
(this as Portal).to == to &&
(this as Portal).baseFeature == baseFeature,
basicFeature: (floor, building, feature) =>
this is BasicFeature &&
(this as BasicFeature).floor == floor &&
(this as BasicFeature).building == building &&
(this as BasicFeature).feature == feature,
);
}
}
class Graph {
final List<(GraphFeature, double, GraphFeature)> _edges = [];
final HashSet<GraphFeature> _nodes = HashSet();
final HashSet<(GraphFeature, GraphFeature)> _edgesSet = HashSet();
Iterable<GraphFeature> get nodes => _nodes.iter();
void addNode(GraphFeature node) {
_nodes.add(node);
if (node is BasicFeature && node.feature.name == 'H22') {
print(node);
print(node.hashCode);
}
}
void addEdge(GraphFeature from, GraphFeature to, double weight) {
addNode(from);
addNode(to);
if (!_edgesSet.contains((from, to))) {
_edgesSet.add((from, to));
_edges.add((from, weight, to));
}
if (!_edgesSet.contains((to, from))) {
_edgesSet.add((to, from));
_edges.add((to, weight, from));
}
}
List<(GraphFeature, double, GraphFeature)> getEdges(GraphFeature node) {
return _edges.where((edge) => edge.$1 == node).toList();
}
bool contains(GraphFeature node) {
return _nodes.contains(node);
}
bool containsEdge(GraphFeature from, GraphFeature to) {
return _edgesSet.contains((from, to));
}
@override
String toString() {
return 'Graph(_edges: $_edges, _nodes: $_nodes, _edgesSet: $_edgesSet)';
}
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is Graph &&
listEquals(other._edges, _edges) &&
setEquals(other._nodes, _nodes) &&
setEquals(other._edgesSet, _edgesSet);
}
@override
int get hashCode => _edges.hashCode ^ _nodes.hashCode ^ _edgesSet.hashCode;
}
IList<GraphFeature> wrap(Feature feature, int floor, String buildingFrom) {
return feature.type
.maybeWhen(
building: () => [GraphFeature.buildingFloor(floor, feature)],
stairs: (floors) => stairPortalGenerator(floors, floor, feature),
lift: (floors) => stairPortalGenerator(floors, floor, feature, 99),
door: (connections) =>
doorPortalGenerator(connections, floor, buildingFrom, feature),
orElse: () => [
GraphFeature.basicFeature(
floor, feature.building ?? buildingFrom, feature)
],
)
.lock;
}
List<GraphFeature> doorPortalGenerator(
List<String> connections, int floor, String from, Feature feature) {
final portals = <GraphFeature>[];
for (final connection in connections.where((c) => !eq(c, from))) {
portals.add(GraphFeature.portal(floor, from, floor, connection, feature));
}
return portals;
}
List<GraphFeature> stairPortalGenerator(
List<int> floors, int floor, Feature feature,
[int maxDist = 1]) {
final portals = <GraphFeature>[];
for (int i = 1; i <= maxDist; i++) {
if (floors.contains(floor - i)) {
portals.add(GraphFeature.portal(
floor, feature.building!, floor - i, feature.building!, feature));
}
if (floors.contains(floor + i)) {
portals.add(GraphFeature.portal(
floor, feature.building!, floor + i, feature.building!, feature));
}
}
return portals;
}
Feature unwrap(GraphFeature feature) {
return feature.when(
buildingFloor: (floor, building) => building,
portal: (fromFloor, from, toFloor, to, baseFeature) => baseFeature,
basicFeature: (floor, building, f) => f,
);
}
double sum(double left, double right) => left + right;
// WeightedDirectedGraph createGraph(Feature origin, List<Feature> allFeatures) {
//
// }
List<GraphFeature> findAdjacent(
GraphFeature feature, Iterable<Feature> allFeatures) {
List<GraphFeature> adjacentFeatures = [];
if (feature is BuildingFloor) {
// find all features in the building on the right floor
adjacentFeatures = allFeatures
.where((f) => eq(f.building, feature.building.name) || f.type is Door)
.where((f) => f.type.maybeWhen(
lift: (levels) => levels.contains(feature.floor),
stairs: (levels) => levels.contains(feature.floor),
door: (connections) =>
f.level == feature.floor &&
connections
.map((e) => e.toLowerCase())
.contains(feature.building.name.toLowerCase()),
orElse: () => f.level == feature.floor))
.mapMany((f) => wrap(f, feature.floor, feature.building.name))
.toList();
} else if (feature is Portal) {
adjacentFeatures = allFeatures
.where((f) => eq(f.name, feature.to) && f.type is Building)
.mapMany((f) => wrap(f, feature.toFloor, feature.to))
.toList();
} else if (feature is BasicFeature) {
adjacentFeatures = allFeatures
.where(
(f) => eq(f.name, feature.feature.building) && f.type is Building)
.mapMany((f) => wrap(f, feature.feature.level!, f.name))
.toList();
}
return adjacentFeatures;
}
Graph makeGraph(GraphFeature origin, List<Feature> allFeatures,
[Graph? graph]) {
// final usedFeatures = <GraphFeature>[origin];
graph ??= Graph();
graph.addNode(origin);
final adjacent = findAdjacent(origin, allFeatures);
for (final feature in adjacent.asSet()..removeAll(graph.nodes)) {
graph.addEdge(origin, feature, origin.metersTo(feature));
final _ = makeGraph(feature, allFeatures, graph);
// graph.addAll(deeper);
}
return graph;
}
List<GraphFeature> createGraphList(
GraphFeature origin, List<Feature> allFeatures,
[Set<GraphFeature>? visited]) {
// final usedFeatures = <GraphFeature>[origin];
visited ??= <GraphFeature>{origin};
final adjacent = findAdjacent(origin, allFeatures);
for (final feature in adjacent.asSet()..removeAll(visited)) {
visited.add(feature);
final deeper = createGraphList(feature, allFeatures, visited);
visited.addAll(deeper);
}
return visited.toList();
}
Map<GraphFeature, Map<GraphFeature, double>> createGraphMap(
GraphFeature origin, List<Feature> allFeatures) {
final graphList = createGraphList(origin, allFeatures);
final graphMap = <GraphFeature, Map<GraphFeature, double>>{};
for (final node in graphList) {
final adjacents = node.when(
buildingFloor: (floor, building) {
return graphList
.where((f) =>
f is Portal &&
eq(f.from, building.name) &&
f.fromFloor == floor ||
f is BasicFeature &&
eq(f.building, building.name) &&
f.floor == floor)
.map((f) => f.when(
portal: (fromFloor, from, toFloor, to, baseFeature) => (
f,
f.metersTo(node),
),
basicFeature: (floor, building, feature) =>
(f, f.metersTo(node)),
buildingFloor: (floor, building) => throw StateError(
"BUG: createGraphMap(): BuildingFloors shouldn't "
"be matched by BuildingFloors"),
));
},
portal: (fromFloor, from, toFloor, to, baseFeature) {
return graphList
.where((f) =>
f is BuildingFloor &&
eq(f.building.name, to) &&
f.floor == toFloor)
.map((f) => f.when(
portal: (fromFloor, from, toFloor, to, baseFeature) =>
throw StateError(
"BUG: createGraphMap(): Portals shouldn't "
"be matched by Portals"),
basicFeature: (floor, building, feature) => throw StateError(
"BUG: createGraphMap(): BasicFeatures shouldn't "
"be matched by BasicFeatures"),
buildingFloor: (floor, building) => (
f,
f.metersTo(node) +
5 /* 5 extra meters for all portals. TODO: smarter!*/
),
));
},
basicFeature: (floor, building, feature) {
return graphList
.where((f) =>
f is BuildingFloor &&
eq(f.building.name, building) &&
f.floor == floor)
.map((f) => f.when(
portal: (fromFloor, from, toFloor, to, baseFeature) =>
throw StateError(
"BUG: createGraphMap(): Portal shouldn't be matched "
"by BasicFeature"),
basicFeature: (floor, building, feature) => throw StateError(
"BUG: createGraphMap(): BasicFeatures shouldn't "
"be matched by BasicFeatures"),
buildingFloor: (floor, building) => (f, f.metersTo(node)),
));
},
);
graphMap[node] =
Map.fromEntries(adjacents.map((tup) => MapEntry(tup.$1, tup.$2)));
}
return graphMap;
}
WeightedDirectedGraph<GraphFeature, double> createGraph(
GraphFeature origin, List<Feature> allFeatures) {
final map = createGraphMap(origin, allFeatures);
final graph = WeightedDirectedGraph<GraphFeature, double>(
map,
summation: sum,
zero: 0.0,
comparator: (a, b) => compareGraphFeatures(a, b),
);
return graph;
}
Result<List<(GraphFeature, double)>> findShortestPathUndir(GraphFeature origin,
bool Function(GraphFeature) destinationSelector, List<Feature> allFeatures,
{heuristicVariant = "zero", heuristicMultiplier = 0.2}) {
Graph graph = makeGraph(origin, allFeatures);
final GraphFeature? destination =
graph.nodes.firstWhereOrNull(destinationSelector);
if (!(graph.contains(origin) &&
destination != null &&
graph.contains(destination))) {
return bail("Origin or destination not in graph");
}
// euclidean distance heuristic
double Function(GraphFeature) heuristic =
(GraphFeature node) => 0.0; // standard zero
if (heuristicVariant == "zero") {
heuristic = (GraphFeature node) => 0.0;
} else if (heuristicVariant == "euclidean") {
heuristic =
(GraphFeature node) => node.metersTo(destination) * heuristicMultiplier;
}
//heuristic(GraphFeature node) => 0.0;
// openlist
// format: (heuristic, g-val, parent?, node)
PriorityQueue<(double, double, GraphFeature?, GraphFeature)> openlist =
HeapPriorityQueue(
// reverse order (cmp b to a) because lower f-val (shorter distance) is better
(a, b) => (b.$1 + b.$2).compareTo((a.$1 + a.$2)),
);
final Map<GraphFeature, (GraphFeature?, double)> bestPathMap = {
origin: (null, 0.0)
};
openlist.add((heuristic(origin), 0.0, null, origin));
// closed list
Set<GraphFeature> closedlist = {};
var cost = 0.0;
while (openlist.isNotEmpty) {
final (f, g, parent, node) = openlist.removeFirst();
closedlist.add(node);
bestPathMap[node] = (parent, g);
if (node == destination) {
cost = g;
break;
// TODO: restore path
}
// expand node
final edges = graph.getEdges(node);
for (final entry in edges) {
final adjNode = entry.$3;
final adjCost = entry.$2;
if (closedlist.contains(adjNode)) {
continue;
}
bool found = false;
for (final open in openlist.unorderedElements) {
if (open.$4 == adjNode) {
found = true;
if (g + adjCost < open.$2) {
openlist.remove(open);
openlist.add((
open.$1 /* heuristic stays the same */,
g + adjCost,
adjNode,
open.$4
));
}
break;
}
}
if (!found) {
openlist.add((
f + heuristic(adjNode),
g + adjCost,
node,
adjNode,
));
}
}
}
if (bestPathMap.isNotEmpty) {
final path = <(GraphFeature, double)>[];
(GraphFeature?, double)? currentNode = (destination, cost);
while (currentNode?.$1 != null) {
final nextNode = bestPathMap[currentNode!.$1];
path.insert(
0, (currentNode!.$1!, currentNode.$2 - (nextNode?.$2 ?? 0.0)));
currentNode = nextNode;
}
return Ok(path);
}
return bail("No path found");
}
Result<List<(GraphFeature, double)>> findShortestPath(
GraphFeature origin, GraphFeature destination, List<Feature> allFeatures,
[heuristicVariant = "zero", heuristicMultiplier = 0.2]) {
var graph = createGraphMap(origin, allFeatures);
if (!(graph.keys.contains(origin) &&
graph.values.firstWhereOrNull((vals) => vals.containsKey(destination)) !=
null)) {
return bail("Origin or destination not in graph");
}
// euclidean distance heuristic
double Function(GraphFeature) heuristic =
(GraphFeature node) => 0.0; // standard zero
if (heuristicVariant == "zero") {
heuristic = (GraphFeature node) => 0.0;
} else if (heuristicVariant == "euclidean") {
heuristic =
(GraphFeature node) => node.metersTo(destination) * heuristicMultiplier;
}
//heuristic(GraphFeature node) => 0.0;
// openlist
// format: (heuristic, g-val, parent?, node)
PriorityQueue<(double, double, GraphFeature?, GraphFeature)> openlist =
HeapPriorityQueue(
// reverse order (cmp b to a) because lower f-val (shorter distance) is better
(a, b) => (b.$1 + b.$2).compareTo((a.$1 + a.$2)),
);
final Map<GraphFeature, (GraphFeature?, double)> bestPathMap = {
origin: (null, 0.0)
};
openlist.add((heuristic(origin), 0.0, null, origin));
// closed list
Set<GraphFeature> closedlist = {};
var cost = 0.0;
while (openlist.isNotEmpty) {
final (f, g, parent, node) = openlist.removeFirst();
closedlist.add(node);
bestPathMap[node] = (parent, g);
if (node == destination) {
cost = g;
break;
// TODO: restore path
}
// expand node
final adjacents = graph[node]!;
for (final entry in adjacents.entries) {
final adjNode = entry.key;
final adjCost = entry.value;
if (closedlist.contains(adjNode)) {
continue;
}
bool found = false;
for (final open in openlist.unorderedElements) {
if (open.$4 == adjNode) {
found = true;
if (g + adjCost < open.$2) {
openlist.remove(open);
openlist.add((
open.$1 /* heuristic stays the same */,
g + adjCost,
adjNode,
open.$4
));
}
break;
}
}
if (!found) {
openlist.add((
f + heuristic(adjNode),
g + adjCost,
node,
adjNode,
));
}
}
}
if (bestPathMap.isNotEmpty) {
final path = <(GraphFeature, double)>[];
(GraphFeature?, double)? currentNode = (destination, cost);
while (currentNode?.$1 != null) {
final nextNode = bestPathMap[currentNode!.$1];
path.insert(
0, (currentNode!.$1!, currentNode.$2 - (nextNode?.$2 ?? 0.0)));
currentNode = nextNode;
}
return Ok(path);
}
return bail("No path found");
}
/// Compares two [GraphFeature] instances and determines their relative order.
///
/// The comparison is based on the specific subtypes and properties of the
/// [GraphFeature] instances. The comparison logic is as follows:
///
/// 1. If both instances are [BuildingFloor], they are compared first by the
/// building name and then by the floor number.
/// 2. If one instance is a [Portal] and the other is a [BuildingFloor] or
/// [BasicFeature], the [Portal] is considered greater.
/// 3. If both instances are [Portal], they are compared first by the `from`
/// property, then by the `to` property, and finally by the `baseFeature` name.
/// 4. If one instance is a [BasicFeature] and the other is a [BuildingFloor] or
/// [Portal], the [BasicFeature] is considered greater.
/// 5. If both instances are [BasicFeature], they are compared first by the
/// building name, then by the floor number, and finally by the feature name.
///
/// Returns a negative value if [a] is considered "less than" [b], a positive
/// value if [a] is considered "greater than" [b], and zero if they are considered
/// equal.
///
/// This function can be used as a comparator for sorting or ordering
/// [GraphFeature] instances.
int compareGraphFeatures(GraphFeature a, GraphFeature b) {
return a.when(
buildingFloor: (floorA, buildingA) {
return b.when(
buildingFloor: (floorB, buildingB) {
final buildingComparison = buildingA.name.compareTo(buildingB.name);
if (buildingComparison != 0) {
return buildingComparison;
}
return floorA.compareTo(floorB);
},
portal: (fromFloorB, fromB, toFloorB, toB, baseFeatureB) => -1,
basicFeature: (floorB, buildingB, featureB) => -1,
);
},
portal: (fromFloorA, fromA, toFloorA, toA, baseFeatureA) {
return b.when(
buildingFloor: (floorB, buildingB) => 1,
portal: (fromFloorB, fromB, toFloorB, toB, baseFeatureB) {
final fromComparison = fromA.compareTo(fromB);
if (fromComparison != 0) {
return fromComparison;
}
final toComparison = toA.compareTo(toB);
if (toComparison != 0) {
return toComparison;
}
return baseFeatureA.name.compareTo(baseFeatureB.name);
},
basicFeature: (floorB, buildingB, featureB) => -1,
);
},
basicFeature: (floorA, buildingA, featureA) {
return b.when(
buildingFloor: (floorB, buildingB) => 1,
portal: (fromFloorB, fromB, toFloorB, toB, baseFeatureB) => 1,
basicFeature: (floorB, buildingB, featureB) {
final buildingComparison = buildingA.compareTo(buildingB);
if (buildingComparison != 0) {
return buildingComparison;
}
final floorComparison = floorA.compareTo(floorB);
if (floorComparison != 0) {
return floorComparison;
}
return featureA.name.compareTo(featureB.name);
},
);
},
);
}

View File

@ -2,15 +2,15 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:uninav/components/drawer.dart';
import 'package:uninav/components/hamburger_menu.dart';
import 'package:uninav/controllers/isar_controller.dart';
import 'package:uninav/controllers/shared_prefs_controller.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
final isarController = Get.find<IsarController>();
final settings = isarController.settings;
final persistenceController = Get.find<SharedPrefsController>();
final settings = persistenceController.settings;
return Scaffold(
appBar: AppBar(
@ -32,90 +32,90 @@ class SettingsPage extends StatelessWidget {
),
value: settings.value.showIcons,
onChanged: (value) {
settings.update((val) {
val?.showIcons = value;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
showIcons: value,
);
persistenceController.persistSettings();
},
),
SwitchListTile(
title: const Text('Show Elevators'),
value: settings.value.showElevators,
onChanged: (value) {
settings.update((val) {
val?.showElevators = value;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
showElevators: value,
);
persistenceController.persistSettings();
},
),
SwitchListTile(
title: const Text('Show Food and Drink'),
value: settings.value.showFoodAndDrink,
onChanged: (value) {
settings.update((val) {
val?.showFoodAndDrink = value;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
showFoodAndDrink: value,
);
persistenceController.persistSettings();
},
),
SwitchListTile(
title: const Text('Show Lecture Halls'),
value: settings.value.showLectureHalls,
onChanged: (value) {
settings.update((val) {
val?.showLectureHalls = value;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
showLectureHalls: value,
);
persistenceController.persistSettings();
},
),
SwitchListTile(
title: const Text('Show Computer Pools'),
value: settings.value.showComputerPools,
onChanged: (value) {
settings.update((val) {
val?.showComputerPools = value;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
showComputerPools: value,
);
persistenceController.persistSettings();
},
),
SwitchListTile(
title: const Text('Show Seminar Rooms'),
value: settings.value.showSeminarRooms,
onChanged: (value) {
settings.update((val) {
val?.showSeminarRooms = value;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
showSeminarRooms: value,
);
persistenceController.persistSettings();
},
),
SwitchListTile(
title: const Text('Show Toilets'),
value: settings.value.showToilets,
onChanged: (value) {
settings.update((val) {
val?.showToilets = value;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
showToilets: value,
);
persistenceController.persistSettings();
},
),
SwitchListTile(
title: const Text('Show Stairs'),
value: settings.value.showStairs,
onChanged: (value) {
settings.update((val) {
val?.showStairs = value;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
showStairs: value,
);
persistenceController.persistSettings();
},
),
SwitchListTile(
title: const Text('Show Doors'),
value: settings.value.showDoors,
onChanged: (value) {
settings.update((val) {
val?.showDoors = value;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
showDoors: value,
);
persistenceController.persistSettings();
},
),
const SizedBox(height: 12),
@ -127,37 +127,37 @@ class SettingsPage extends StatelessWidget {
title: const Text('Male Toilets'),
value: settings.value.maleToilets,
onChanged: (value) {
settings.update((val) {
val?.maleToilets = value ?? false;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
maleToilets: value ?? false,
);
persistenceController.persistSettings();
},
),
CheckboxListTile(
title: const Text('Female Toilets'),
value: settings.value.femaleToilets,
onChanged: (value) {
settings.update((val) {
val?.femaleToilets = value ?? false;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
femaleToilets: value ?? false,
);
persistenceController.persistSettings();
},
),
CheckboxListTile(
title: const Text('Handicap Toilets'),
value: settings.value.handicapToilets,
onChanged: (value) {
settings.update((val) {
val?.handicapToilets = value ?? false;
});
isarController.persistSettings();
settings.value = settings.value.copyWith(
handicapToilets: value ?? false,
);
persistenceController.persistSettings();
},
),
const SizedBox(height: 12),
ElevatedButton(
onPressed: () {
settings.value = Settings();
isarController.persistSettings();
settings.value = const Settings();
persistenceController.persistSettings();
},
child: const Text("Reset Settings"),
),

48
lib/util/geolocator.dart Normal file
View File

@ -0,0 +1,48 @@
import 'package:anyhow/anyhow.dart';
import 'package:geolocator/geolocator.dart';
/// Determine the current position of the device.
///
/// When the location services are not enabled or permissions
/// are denied the `Future` will return an error.
Future<Result<()>> ensureLocationPermission() async {
try {
bool serviceEnabled;
LocationPermission permission;
// Test if location services are enabled.
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
// Location services are not enabled don't continue
// accessing the position and request users of the
// App to enable the location services.
return bail('Location services are disabled.');
}
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) {
// Permissions are denied, next time you could try
// requesting permissions again (this is also where
// Android's shouldShowRequestPermissionRationale
// returned true. According to Android guidelines
// your App should show an explanatory UI now.
return bail('Location permissions are denied');
}
}
if (permission == LocationPermission.deniedForever) {
// Permissions are denied forever, handle appropriately.
return bail(
'Location permissions are permanently denied, we cannot request permissions.');
}
// When we reach here, permissions are granted and we can
// continue accessing the position of the device.
return const Ok(());
} catch (e) {
return bail("Ensuring Location Permission failed: $e");
}
}

View File

@ -26,7 +26,9 @@ String formatFeatureTitle(Feature feature) {
return feature.type.when(
building: () => '${feature.name} (Building)',
lectureHall: () => '${feature.name} (Lecture Hall)',
room: () => 'Room ${feature.name}',
room: (number) => 'Room ${feature.building ?? "??"}/$number',
pcPool: (number) => 'PC Pool ${feature.name}',
foodDrink: () => '${feature.name} (Food/Drink)',
door: (_) => 'Door',
toilet: (type) => 'Toilet (${formatToiletType(feature.type as Toilet)})',
stairs: (_) => 'Stairs',

View File

@ -5,12 +5,16 @@
import FlutterMacOS
import Foundation
import geolocator_apple
import isar_flutter_libs
import path_provider_foundation
import shared_preferences_foundation
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
GeolocatorPlugin.register(with: registry.registrar(forPlugin: "GeolocatorPlugin"))
IsarFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "IsarFlutterLibsPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}

View File

@ -193,14 +193,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
dartx:
dependency: transitive
description:
name: dartx
sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
directed_graph:
dependency: "direct main"
description:
@ -262,6 +254,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_compass:
dependency: transitive
description:
name: flutter_compass
sha256: be642484f9f6975c1c6edff568281b001f2f1e604de27ecea18d97eebbdef22f
url: "https://pub.dev"
source: hosted
version: "0.8.0"
flutter_lints:
dependency: "direct dev"
description:
@ -278,6 +278,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.0"
flutter_map_location_marker:
dependency: "direct main"
description:
name: flutter_map_location_marker
sha256: "5873a47606b092bf181b6d17dd42a124e9a8d5d9caad58b5f98fc182e799994f"
url: "https://pub.dev"
source: hosted
version: "8.0.8"
flutter_test:
dependency: "direct dev"
description: flutter
@ -320,6 +328,54 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.3"
geolocator:
dependency: "direct main"
description:
name: geolocator
sha256: "694ec58afe97787b5b72b8a0ab78c1a9244811c3c10e72c4362ef3c0ceb005cd"
url: "https://pub.dev"
source: hosted
version: "11.0.0"
geolocator_android:
dependency: transitive
description:
name: geolocator_android
sha256: f15d1536cd01b1399578f1da1eb5d566e7a718db6a3648f2c24d2e2f859f0692
url: "https://pub.dev"
source: hosted
version: "4.5.4"
geolocator_apple:
dependency: transitive
description:
name: geolocator_apple
sha256: bc2aca02423ad429cb0556121f56e60360a2b7d694c8570301d06ea0c00732fd
url: "https://pub.dev"
source: hosted
version: "2.3.7"
geolocator_platform_interface:
dependency: transitive
description:
name: geolocator_platform_interface
sha256: "009a21c4bc2761e58dccf07c24f219adaebe0ff707abdfd40b0a763d4003fab9"
url: "https://pub.dev"
source: hosted
version: "4.2.2"
geolocator_web:
dependency: transitive
description:
name: geolocator_web
sha256: "49d8f846ebeb5e2b6641fe477a7e97e5dd73f03cbfef3fd5c42177b7300fb0ed"
url: "https://pub.dev"
source: hosted
version: "3.0.0"
geolocator_windows:
dependency: transitive
description:
name: geolocator_windows
sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e"
url: "https://pub.dev"
source: hosted
version: "0.2.3"
get:
dependency: "direct main"
description:
@ -400,14 +456,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
isar_generator:
dependency: "direct dev"
description:
name: isar_generator
sha256: "76c121e1295a30423604f2f819bc255bc79f852f3bc8743a24017df6068ad133"
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
js:
dependency: transitive
description:
@ -680,6 +728,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.5.3"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shelf:
dependency: transitive
description:
@ -789,14 +893,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.1"
time:
dependency: transitive
description:
name: time
sha256: ad8e018a6c9db36cb917a031853a1aae49467a93e0d464683e029537d848c221
url: "https://pub.dev"
source: hosted
version: "2.1.4"
timing:
dependency: transitive
description:
@ -957,14 +1053,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
xxh3:
dependency: transitive
description:
name: xxh3
sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7
url: "https://pub.dev"
source: hosted
version: "1.0.1"
yaml:
dependency: "direct main"
description:

View File

@ -51,12 +51,15 @@ dependencies:
json_annotation: ^4.8.1
rust_core: ^0.5.3
anyhow: ^1.3.0
isar: ^3.1.0+1
isar_flutter_libs: ^3.1.0+1
# isar: ^3.1.0+1
# isar_flutter_libs: ^3.1.0+1
path_provider: ^2.1.3
directed_graph: ^0.4.3
fast_immutable_collections: ^10.2.2
collection: ^1.18.0
flutter_map_location_marker: ^8.0.8
geolocator: ^11.0.0
shared_preferences: ^2.2.3
dev_dependencies:
flutter_test:
@ -71,7 +74,7 @@ dev_dependencies:
build_runner: ^2.4.9
freezed: ^2.5.2
json_serializable: ^6.7.1
isar_generator: ^3.1.0+1
# isar_generator: ^3.1.0+1
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

View File

@ -6,10 +6,13 @@
#include "generated_plugin_registrant.h"
#include <geolocator_windows/geolocator_windows.h>
#include <isar_flutter_libs/isar_flutter_libs_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
GeolocatorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("GeolocatorWindows"));
IsarFlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(

View File

@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
geolocator_windows
isar_flutter_libs
url_launcher_windows
)