add locations by search

This commit is contained in:
Konstantin Kollar
2022-09-05 22:42:04 +02:00
parent 9de8e319c3
commit e93dc0f771
5 changed files with 158 additions and 5 deletions
@@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
import 'package:wetter/models/location_model.dart';
import 'package:wetter/services/openstreetmap_api_service.dart';
import 'package:wetter/services/utils.dart';
class AddLocationBySearchForm extends StatefulWidget {
final Function saveNewLocation;
const AddLocationBySearchForm({Key? key, required this.saveNewLocation})
: super(key: key);
@override
State<AddLocationBySearchForm> createState() =>
_AddLocationBySearchFormState();
}
class _AddLocationBySearchFormState extends State<AddLocationBySearchForm> {
final _formKey = GlobalKey<FormState>();
final searchLocationFormController = TextEditingController();
final _debouncer = Debouncer(milliseconds: 500);
bool _showResults = false;
bool _locationSearchHasFocus = false;
List<LocationModel>? _locations;
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
FocusScope(
child: Focus(
onFocusChange: (focus) {
setState(() {
_locationSearchHasFocus = !_locationSearchHasFocus;
});
},
child: TextFormField(
onChanged: (value) {
_debouncer.run(() {
OpenStreetMapAPI.getLocationsFromSearch(value)
.then((l) {
setState(() {
_locations = l;
_showResults = true;
});
});
});
},
decoration: const InputDecoration(
border: UnderlineInputBorder(),
labelText: "Location Name"),
))),
if (_showResults && _locationSearchHasFocus) _showResultList(),
],
));
}
Column _showResultList() {
if (_locations == null) {
return Column(
children: const [],
);
}
if (_locations!.isEmpty) {
return Column(
children: [_singleResult(name: "No result")],
);
}
return Column(children: [
for (LocationModel location in _locations!.getRange(0, 3))
_singleResult(name: location.name, location: location)
]);
}
Widget _singleResult({required String name, LocationModel? location}) {
return GestureDetector(
onTap: () {
if (location != null) {
widget.saveNewLocation(
location.name, location.latitude, location.longitude);
}
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(5),
child: Row(children: [
Flexible(
child: Text(name,
style: Theme.of(context).textTheme.titleMedium))
]))));
}
}
+13
View File
@@ -0,0 +1,13 @@
class LocationModel {
late double latitude;
late double longitude;
late String name;
LocationModel();
LocationModel.fromJson(Map<String, dynamic> json) {
name = json["display_name"];
latitude = double.parse(json["lat"]);
longitude = double.parse(json["lon"]);
}
}
+11 -5
View File
@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:wetter/components/locations/add_location_by_search_form.dart';
import 'package:wetter/components/locations/add_location_form.dart';
import 'package:wetter/components/locations/manage_single_location.dart';
import 'package:wetter/services/user_preferences.dart';
@@ -45,11 +46,16 @@ class _LocationsState extends State<Locations> {
for (String name in locations)
ManageSingleLocation(name: name, delete: deleteLocation),
...[
Expanded(
child: AddLocationsForm(
saveNewLocation: saveNewLocation,
savedLocations: locations),
)
const SizedBox(height: 20),
const Text("Add location by search"),
AddLocationBySearchForm(saveNewLocation: saveNewLocation),
const SizedBox(height: 20),
const Text("Add location by coordinates"),
Flexible(
fit: FlexFit.loose,
child: AddLocationsForm(
saveNewLocation: saveNewLocation,
savedLocations: locations)),
],
]))));
}
@@ -0,0 +1,24 @@
import 'dart:convert';
import 'package:wetter/models/location_model.dart';
import 'package:http/http.dart' as http;
class OpenStreetMapAPI {
static Future<List<LocationModel>> getLocationsFromSearch(
String search) async {
final response = await http.get(
Uri.parse(
'https://nominatim.openstreetmap.org/search.php?q=$search&format=jsonv2'),
headers: {"Accept": "application/json"});
if (response.statusCode == 200) {
List<LocationModel> locations = [];
List<dynamic> results = jsonDecode(response.body);
for (Map<String, dynamic> result in results) {
locations.add(LocationModel.fromJson(result));
}
return locations;
} else {
throw Exception('Failed to load weather data');
}
}
}
+17
View File
@@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:weather_icons/weather_icons.dart';
@@ -31,3 +33,18 @@ IconData weatherIcon(String icon) {
return WeatherIcons.alien;
}
}
class Debouncer {
final int milliseconds;
VoidCallback? action;
Timer? _timer;
Debouncer({required this.milliseconds});
run(VoidCallback action) {
if (_timer != null) {
_timer!.cancel();
}
_timer = Timer(Duration(milliseconds: milliseconds), action);
}
}