Skip to content

Commit

Permalink
Add preliminary distance-sorting in spec edit
Browse files Browse the repository at this point in the history
  • Loading branch information
backspace committed Oct 14, 2024
1 parent d130669 commit 75f33e5
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 23 deletions.
10 changes: 7 additions & 3 deletions waydowntown_app/lib/models/region.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Region {
final String? description;
final double? latitude;
final double? longitude;
final double? distance;
Region? parentRegion;
List<Region> children = [];

Expand All @@ -14,6 +15,7 @@ class Region {
this.parentRegion,
this.latitude,
this.longitude,
this.distance,
});

factory Region.fromJson(Map<String, dynamic> json, List<dynamic> included) {
Expand All @@ -30,6 +32,7 @@ class Region {
longitude: attributes['longitude'] != null
? double.parse(attributes['longitude'])
: null,
distance: attributes['distance']?.toDouble(),
);

if (relationships != null &&
Expand All @@ -50,19 +53,20 @@ class Region {

static List<Region> parseRegions(Map<String, dynamic> apiResponse) {
final List<dynamic> data = apiResponse['data'];
final List<dynamic> included = apiResponse['included'] ?? [];

Map<String, Region> regionMap = {};

// Extract all regions
for (var item in data) {
for (var item in [...data, ...included]) {
if (item['type'] == 'regions') {
Region region = Region.fromJson(item, []);
Region region = Region.fromJson(item, included);
regionMap[region.id] = region;
}
}

// Nest children
for (var item in data) {
for (var item in [...data, ...included]) {
if (item['type'] == 'regions' && item['relationships'] != null) {
var relationships = item['relationships'];
if (relationships['parent'] != null &&
Expand Down
137 changes: 121 additions & 16 deletions waydowntown_app/lib/widgets/edit_specification_widget.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
import 'package:waydowntown/app.dart';
import 'package:waydowntown/models/region.dart';
import 'package:waydowntown/models/specification.dart';
Expand Down Expand Up @@ -27,6 +28,7 @@ class EditSpecificationWidgetState extends State<EditSpecificationWidget> {
String? _selectedRegionId;
List<Region> _regions = [];
Map<String, String> _fieldErrors = {};
bool _sortByDistance = false;

@override
void initState() {
Expand All @@ -48,13 +50,50 @@ class EditSpecificationWidgetState extends State<EditSpecificationWidget> {
if (response.statusCode == 200) {
setState(() {
_regions = Region.parseRegions(response.data);
_sortRegions();
});
}
} catch (e) {
talker.error('Error loading regions: $e');
}
}

Future<void> _loadNearestRegions() async {
try {
Position position = await Geolocator.getCurrentPosition();
final response = await widget.dio.get(
'/waydowntown/regions?filter[position]=${position.latitude},${position.longitude}');
if (response.statusCode == 200) {
setState(() {
_regions = Region.parseRegions(response.data);
_sortByDistance = true;
_sortRegions();
});
}
} catch (e) {
talker.error('Error loading nearest regions: $e');
}
}

void _sortRegions() {
_sortRegionList(_regions);
}

void _sortRegionList(List<Region> regions) {
if (_sortByDistance) {
regions.sort((a, b) => (a.distance ?? double.infinity)
.compareTo(b.distance ?? double.infinity));
} else {
regions.sort((a, b) => a.name.compareTo(b.name));
}

for (var region in regions) {
if (region.children.isNotEmpty) {
_sortRegionList(region.children);
}
}
}

Future<dynamic> _loadConcepts(context) async {
final yamlString =
await DefaultAssetBundle.of(context).loadString('assets/concepts.yaml');
Expand Down Expand Up @@ -84,7 +123,7 @@ class EditSpecificationWidgetState extends State<EditSpecificationWidget> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildConceptDropdown(snapshot.data),
_buildRegionDropdown(),
_buildRegionSection(),
_buildTextField('Start Description',
_startDescriptionController, 'start_description'),
_buildTextField('Task Description',
Expand Down Expand Up @@ -140,35 +179,85 @@ class EditSpecificationWidgetState extends State<EditSpecificationWidget> {
);
}

Widget _buildRegionSection() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildRegionDropdown(),
),
const SizedBox(width: 8),
_buildSortButton(
context: context,
label: 'A-Z',
isActive: !_sortByDistance,
onPressed: () {
setState(() {
_sortByDistance = false;
_sortRegions();
});
},
),
const SizedBox(width: 8),
_buildSortButton(
context: context,
label: 'Nearest',
isActive: _sortByDistance,
onPressed: _loadNearestRegions,
),
],
),
],
);
}

Widget _buildRegionDropdown() {
return DropdownButtonFormField<String>(
value: _selectedRegionId,
decoration: InputDecoration(
labelText: 'Region',
errorText: _fieldErrors['region_id'],
),
items: _buildRegionItems(_regions, 0),
onChanged: (String? newValue) {
return DropdownMenu<String>(
initialSelection: _selectedRegionId,
onSelected: (String? newValue) {
setState(() {
_selectedRegionId = newValue;
});
},
errorText: _fieldErrors['region_id'],
label: const Text('Region'),
dropdownMenuEntries: _buildNestedRegionEntries(_regions),
width: MediaQuery.of(context).size.width - 150,
);
}

List<DropdownMenuItem<String>> _buildRegionItems(
List<Region> regions, int depth) {
List<DropdownMenuItem<String>> items = [];
List<DropdownMenuEntry<String>> _buildNestedRegionEntries(
List<Region> regions,
{String indent = ''}) {
List<DropdownMenuEntry<String>> entries = [];

for (var region in regions) {
items.add(DropdownMenuItem<String>(
entries.add(DropdownMenuEntry<String>(
value: region.id,
child: Text('${' ' * depth}${region.name}'),
label: '$indent${region.name}',
trailingIcon: _sortByDistance && region.distance != null
? Text(_formatDistance(region.distance!))
: null,
));

if (region.children.isNotEmpty) {
items.addAll(_buildRegionItems(region.children, depth + 1));
entries.addAll(
_buildNestedRegionEntries(region.children, indent: '$indent '));
}
}
return items;

return entries;
}

String _formatDistance(double distanceInMeters) {
if (distanceInMeters >= 1000) {
return '${(distanceInMeters / 1000).round()} km';
} else {
return '${distanceInMeters.round()} m';
}
}

Widget _buildTextField(
Expand Down Expand Up @@ -272,3 +361,19 @@ class EditSpecificationWidgetState extends State<EditSpecificationWidget> {
super.dispose();
}
}

Widget _buildSortButton({
required BuildContext context,
required String label,
required bool isActive,
required VoidCallback onPressed,
}) {
return ElevatedButton(
onPressed: onPressed,
style: ElevatedButton.styleFrom(
backgroundColor: isActive ? Theme.of(context).primaryColor : null,
foregroundColor: isActive ? Colors.white : null,
),
child: Text(label),
);
}
2 changes: 1 addition & 1 deletion waydowntown_app/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ packages:
source: hosted
version: "2.3.7"
geolocator_platform_interface:
dependency: transitive
dependency: "direct main"
description:
name: geolocator_platform_interface
sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012"
Expand Down
1 change: 1 addition & 0 deletions waydowntown_app/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ dependencies:
talker_dio_logger: ^4.4.1
talker_logger: ^4.4.1
flutter_secure_storage: ^9.2.2
geolocator_platform_interface: ^4.2.4

dev_dependencies:
flutter_test:
Expand Down
Loading

0 comments on commit 75f33e5

Please sign in to comment.