feat: GPS and web compatible

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

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',