Skip to content

Commit

Permalink
Add region popup for specification editor
Browse files Browse the repository at this point in the history
  • Loading branch information
backspace committed Oct 12, 2024
1 parent d34a6c7 commit 840b69f
Show file tree
Hide file tree
Showing 5 changed files with 232 additions and 20 deletions.
52 changes: 45 additions & 7 deletions waydowntown_app/lib/models/region.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ class Region {
final double? latitude;
final double? longitude;
Region? parentRegion;
List<Region> children = [];

Region(
{required this.id,
required this.name,
this.description,
this.parentRegion,
this.latitude,
this.longitude});
Region({
required this.id,
required this.name,
this.description,
this.parentRegion,
this.latitude,
this.longitude,
});

factory Region.fromJson(Map<String, dynamic> json, List<dynamic> included) {
final attributes = json['attributes'];
Expand Down Expand Up @@ -45,4 +47,40 @@ class Region {

return region;
}

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

Map<String, Region> regionMap = {};

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

// Nest children
for (var item in data) {
if (item['type'] == 'regions' && item['relationships'] != null) {
var relationships = item['relationships'];
if (relationships['parent'] != null &&
relationships['parent']['data'] != null) {
String parentId = relationships['parent']['data']['id'];
Region? parentRegion = regionMap[parentId];
Region? childRegion = regionMap[item['id']];
if (parentRegion != null && childRegion != null) {
childRegion.parentRegion = parentRegion;
parentRegion.children.add(childRegion);
}
}
}
}

// Return only root regions
return regionMap.values
.where((region) => region.parentRegion == null)
.toList();
}
}
51 changes: 51 additions & 0 deletions waydowntown_app/lib/widgets/edit_specification_widget.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:waydowntown/app.dart';
import 'package:waydowntown/models/region.dart';
import 'package:waydowntown/models/specification.dart';
import 'package:yaml/yaml.dart';

Expand All @@ -23,6 +24,8 @@ class EditSpecificationWidgetState extends State<EditSpecificationWidget> {
late TextEditingController _taskDescriptionController;
late TextEditingController _durationController;
String? _selectedConcept;
String? _selectedRegionId;
List<Region> _regions = [];
Map<String, String> _fieldErrors = {};

@override
Expand All @@ -35,6 +38,21 @@ class EditSpecificationWidgetState extends State<EditSpecificationWidget> {
_durationController = TextEditingController(
text: widget.specification.duration?.toString() ?? '');
_selectedConcept = widget.specification.concept;
_selectedRegionId = widget.specification.region?.id;
_loadRegions();
}

Future<void> _loadRegions() async {
try {
final response = await widget.dio.get('/waydowntown/regions');
if (response.statusCode == 200) {
setState(() {
_regions = Region.parseRegions(response.data);
});
}
} catch (e) {
talker.error('Error loading regions: $e');
}
}

Future<dynamic> _loadConcepts(context) async {
Expand Down Expand Up @@ -66,6 +84,7 @@ class EditSpecificationWidgetState extends State<EditSpecificationWidget> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildConceptDropdown(snapshot.data),
_buildRegionDropdown(),
_buildTextField('Start Description',
_startDescriptionController, 'start_description'),
_buildTextField('Task Description',
Expand Down Expand Up @@ -121,6 +140,37 @@ class EditSpecificationWidgetState extends State<EditSpecificationWidget> {
);
}

Widget _buildRegionDropdown() {
return DropdownButtonFormField<String>(
value: _selectedRegionId,
decoration: InputDecoration(
labelText: 'Region',
errorText: _fieldErrors['region_id'],
),
items: _buildRegionItems(_regions, 0),
onChanged: (String? newValue) {
setState(() {
_selectedRegionId = newValue;
});
},
);
}

List<DropdownMenuItem<String>> _buildRegionItems(
List<Region> regions, int depth) {
List<DropdownMenuItem<String>> items = [];
for (var region in regions) {
items.add(DropdownMenuItem<String>(
value: region.id,
child: Text('${' ' * depth}${region.name}'),
));
if (region.children.isNotEmpty) {
items.addAll(_buildRegionItems(region.children, depth + 1));
}
}
return items;
}

Widget _buildTextField(
String label, TextEditingController controller, String fieldName) {
return TextField(
Expand Down Expand Up @@ -185,6 +235,7 @@ class EditSpecificationWidgetState extends State<EditSpecificationWidget> {
'start_description': _startDescriptionController.text,
'task_description': _taskDescriptionController.text,
'duration': int.tryParse(_durationController.text),
'region_id': _selectedRegionId,
},
},
},
Expand Down
77 changes: 77 additions & 0 deletions waydowntown_app/test/models/region_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:waydowntown/models/region.dart';

void main() {
group('Region', () {
test('parseRegions parses API response and nests regions', () {
final apiResponse = {
'data': [
{
'id': '1',
'type': 'regions',
'attributes': {
'name': 'Region 1',
'description': 'Root region',
'latitude': '45.0',
'longitude': '-75.0',
},
'relationships': {
'parent': {'data': null}
}
},
{
'id': '2',
'type': 'regions',
'attributes': {
'name': 'Region 2',
'description': 'Child of Region 1',
},
'relationships': {
'parent': {
'data': {'id': '1', 'type': 'regions'}
}
}
},
{
'id': '3',
'type': 'regions',
'attributes': {
'name': 'Region 3',
'description': 'Child of Region 2',
},
'relationships': {
'parent': {
'data': {'id': '2', 'type': 'regions'}
}
}
},
]
};

List<Region> rootRegions = Region.parseRegions(apiResponse);

expect(rootRegions.length, 1);
expect(rootRegions[0].id, '1');
expect(rootRegions[0].name, 'Region 1');
expect(rootRegions[0].description, 'Root region');
expect(rootRegions[0].latitude, 45.0);
expect(rootRegions[0].longitude, -75.0);
expect(rootRegions[0].parentRegion, null);
expect(rootRegions[0].children.length, 1);

Region region2 = rootRegions[0].children[0];
expect(region2.id, '2');
expect(region2.name, 'Region 2');
expect(region2.description, 'Child of Region 1');
expect(region2.parentRegion, rootRegions[0]);
expect(region2.children.length, 1);

Region region3 = region2.children[0];
expect(region3.id, '3');
expect(region3.name, 'Region 3');
expect(region3.description, 'Child of Region 2');
expect(region3.parentRegion, region2);
expect(region3.children.length, 0);
});
});
}
24 changes: 13 additions & 11 deletions waydowntown_app/test/test_helpers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ class TestHelpers {
DateTime? startedAt,
int? durationSeconds = 300,
List<Answer>? answers,
Region? region,
}) {
return Run(
id: '22261813-2171-453f-a669-db08edc70d6d',
Expand All @@ -184,17 +185,18 @@ class TestHelpers {
const Answer(id: '3', label: 'Answer 3'),
],
duration: durationSeconds,
region: Region(
id: '324fd8f9-cd25-48be-a761-b8680fa72737',
name: 'Test Region',
latitude: latitude,
longitude: longitude,
parentRegion: Region(
id: '67cc2c5c-06c2-4e86-9aac-b575fc712862',
name: 'Parent Region',
description: null,
),
),
region: region ??
Region(
id: '324fd8f9-cd25-48be-a761-b8680fa72737',
name: 'Test Region',
latitude: latitude,
longitude: longitude,
parentRegion: Region(
id: '67cc2c5c-06c2-4e86-9aac-b575fc712862',
name: 'Parent Region',
description: null,
),
),
),
correctSubmissions: correctAnswers,
totalAnswers: totalAnswers,
Expand Down
48 changes: 46 additions & 2 deletions waydowntown_app/test/widgets/edit_specification_widget_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:http_mock_adapter/http_mock_adapter.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
import 'package:waydowntown/models/region.dart';
import 'package:waydowntown/models/specification.dart';
import 'package:waydowntown/widgets/edit_specification_widget.dart';

Expand Down Expand Up @@ -80,8 +81,43 @@ another_concept:
concept: 'bluetooth_collector',
start: 'This is the start',
description: 'This is the task',
durationSeconds: 100)
durationSeconds: 100,
region: Region(id: 'region1', name: 'Region 1'))
.specification;

dioAdapter.onGet(
'/waydowntown/regions',
(server) => server.reply(200, {
'data': [
{
'id': 'region1',
'type': 'regions',
'attributes': {'name': 'Region 1'},
'relationships': {
'parent': {'data': null}
}
},
{
'id': 'region2',
'type': 'regions',
'attributes': {'name': 'Region 2'},
'relationships': {
'parent': {
'data': {'id': 'region1', 'type': 'regions'}
},
}
},
{
'id': 'region3',
'type': 'regions',
'attributes': {'name': 'Region 3'},
'relationships': {
'parent': {'data': null},
}
}
]
}),
);
});

tearDown(() {
Expand All @@ -105,12 +141,19 @@ another_concept:
expect(find.text(specification.startDescription!), findsOneWidget);
expect(find.text(specification.taskDescription!), findsOneWidget);
expect(find.text(specification.duration.toString()), findsOneWidget);
expect(find.text('Region 1'), findsOneWidget);

await tester.tap(find.byType(DropdownButtonFormField<String>));
await tester.tap(find.byType(DropdownButtonFormField<String>).first);
await tester.pumpAndSettle();
await tester.tap(find.text('Fill in the Blank').last);
await tester.pumpAndSettle();

await tester.tap(find.byType(DropdownButtonFormField<String>).last);
await tester.pumpAndSettle();
expect(find.text(' Region 2'), findsOneWidget); // Assert on nesting
await tester.tap(find.text('Region 3').last);
await tester.pumpAndSettle();

await tester.enterText(
find.widgetWithText(TextField, 'Start Description'), 'New start');
await tester.enterText(
Expand All @@ -132,6 +175,7 @@ another_concept:
'start_description': 'New start',
'task_description': 'New task',
'duration': 60,
'region_id': 'region3',
},
},
},
Expand Down

0 comments on commit 840b69f

Please sign in to comment.