Added osm_nominatim for better cross-platform support, as geocoding is limited to Android/iOS only.
parent
e057d428a5
commit
35c79de771
|
@ -1,7 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:geocoding/geocoding.dart';
|
import 'package:geocoding/geocoding.dart';
|
||||||
import 'package:geolocator/geolocator.dart';
|
import 'package:geolocator/geolocator.dart';
|
||||||
|
import 'package:osm_nominatim/osm_nominatim.dart';
|
||||||
import '../models/location.dart';
|
import '../models/location.dart';
|
||||||
|
import '../utils/helper.dart';
|
||||||
|
|
||||||
class LocationSelector extends StatefulWidget {
|
class LocationSelector extends StatefulWidget {
|
||||||
final Function(MyLocation) onLocationChanged; // Callback function
|
final Function(MyLocation) onLocationChanged; // Callback function
|
||||||
|
@ -46,18 +48,19 @@ class LocationSelectorState extends State<LocationSelector> {
|
||||||
style: const TextStyle(color: Colors.red),
|
style: const TextStyle(color: Colors.red),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
ElevatedButton(
|
ElevatedButton.icon(
|
||||||
|
icon: const Icon(Icons.my_location),
|
||||||
onPressed: _getCurrentLocation,
|
onPressed: _getCurrentLocation,
|
||||||
child: const Text('Use Current Position'),
|
label: const Text('Current Position'),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Text('Country: $_country'),
|
Text('Country: $_country'),
|
||||||
Text('City: $_city'),
|
Text('City: $_city'),
|
||||||
Text('Postal Code: $_postalCode'),
|
Text('Postal Code: ${_postalCode ?? ''}'),
|
||||||
Text('Street: $_street'),
|
Text('Street: $_street'),
|
||||||
Text('Administrative Area: $_administrativeArea'),
|
Text('State/Area: ${_administrativeArea ?? ''}'),
|
||||||
Text('Latitude: $_latitude'),
|
Text('Latitude: ${_latitude ?? '--'}'),
|
||||||
Text('Longitude: $_longitude'),
|
Text('Longitude: ${_longitude ?? '--'}'),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -72,46 +75,90 @@ class LocationSelectorState extends State<LocationSelector> {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
List<Location> locations = await locationFromAddress(locationQuery);
|
if (isMobile) {
|
||||||
if (locations.isNotEmpty) {
|
List<Location> locations = await locationFromAddress(locationQuery);
|
||||||
// Take first match
|
if (locations.isNotEmpty) {
|
||||||
Location firstLocation = locations.first;
|
// Take first match
|
||||||
|
Location firstLocation = locations.first;
|
||||||
|
|
||||||
// Extract country and city information
|
// Extract country and city information
|
||||||
List<Placemark> placeMarks = await placemarkFromCoordinates(
|
List<Placemark> placeMarks = await placemarkFromCoordinates(
|
||||||
firstLocation.latitude, firstLocation.longitude);
|
firstLocation.latitude, firstLocation.longitude);
|
||||||
if (placeMarks.isNotEmpty) {
|
if (placeMarks.isNotEmpty) {
|
||||||
Placemark placeMark = placeMarks.first;
|
Placemark placeMark = placeMarks.first;
|
||||||
setState(() {
|
setState(() {
|
||||||
_latitude = firstLocation.latitude;
|
_latitude = firstLocation.latitude;
|
||||||
_longitude = firstLocation.longitude;
|
_longitude = firstLocation.longitude;
|
||||||
_street = '${placeMark.street}';
|
_street = placeMark.street ?? '';
|
||||||
_country = placeMark.country!;
|
_country = placeMark.country ?? '';
|
||||||
_city = placeMark.locality!;
|
_city = placeMark.locality ?? '';
|
||||||
_subLocality = placeMark.subLocality;
|
_subLocality = placeMark.subLocality;
|
||||||
_postalCode = placeMark.postalCode;
|
_postalCode = placeMark.postalCode;
|
||||||
_administrativeArea = placeMark.administrativeArea;
|
_administrativeArea = placeMark.administrativeArea;
|
||||||
errorText = null;
|
errorText = null;
|
||||||
});
|
});
|
||||||
// location is found, trigger callback
|
// location is found, trigger callback
|
||||||
triggerCallback();
|
triggerCallback();
|
||||||
|
} else {
|
||||||
|
setState(() {
|
||||||
|
// should placeMarks be empty return latitude and longitude anyway
|
||||||
|
_latitude = firstLocation.latitude;
|
||||||
|
_longitude = firstLocation.longitude;
|
||||||
|
errorText = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setState(() {
|
_resetLocationData('Location $locationQuery not found');
|
||||||
// should placeMarks be empty return latitude and longitude anyway
|
|
||||||
_latitude = firstLocation.latitude;
|
|
||||||
_longitude = firstLocation.longitude;
|
|
||||||
errorText = null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_resetLocationData('Location $locationQuery not found');
|
// other platforms than Android or iOS
|
||||||
|
|
||||||
|
try {
|
||||||
|
List<Place> searchResult = await Nominatim.searchByName(
|
||||||
|
query: locationQuery,
|
||||||
|
limit: 1,
|
||||||
|
addressDetails: true,
|
||||||
|
nameDetails: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
debugPrint(searchResult.single.address.toString()); // TODO remove
|
||||||
|
|
||||||
|
Map<String, dynamic>? addressData = searchResult.single.address;
|
||||||
|
if (addressData != null) {
|
||||||
|
String street = _getStreetInfo(addressData);
|
||||||
|
String city = _getCityInfo(addressData);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_latitude = searchResult.single.lat;
|
||||||
|
_longitude = searchResult.single.lon;
|
||||||
|
_street = street;
|
||||||
|
_country = addressData.containsKey('country')
|
||||||
|
? addressData['country']
|
||||||
|
: '';
|
||||||
|
_administrativeArea = addressData.containsKey('state')
|
||||||
|
? addressData['state']
|
||||||
|
: null;
|
||||||
|
_city = city;
|
||||||
|
_subLocality = addressData.containsKey('suburb')
|
||||||
|
? addressData['suburb']
|
||||||
|
: null;
|
||||||
|
_postalCode = addressData.containsKey('postcode')
|
||||||
|
? addressData['postcode']
|
||||||
|
: null;
|
||||||
|
errorText = null;
|
||||||
|
});
|
||||||
|
triggerCallback();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_resetLocationData('Error looking up location $locationQuery: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_resetLocationData('Error searching location $locationQuery: $e');
|
_resetLocationData('Error searching location $locationQuery: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void triggerCallback(){
|
void triggerCallback() {
|
||||||
widget.onLocationChanged(MyLocation(
|
widget.onLocationChanged(MyLocation(
|
||||||
street: _street,
|
street: _street,
|
||||||
country: _country,
|
country: _country,
|
||||||
|
@ -139,7 +186,7 @@ class LocationSelectorState extends State<LocationSelector> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine users position using geolocator package
|
/// Determine users current position
|
||||||
void _getCurrentLocation() async {
|
void _getCurrentLocation() async {
|
||||||
bool serviceEnabled;
|
bool serviceEnabled;
|
||||||
LocationPermission permission;
|
LocationPermission permission;
|
||||||
|
@ -182,21 +229,96 @@ class LocationSelectorState extends State<LocationSelector> {
|
||||||
Position position = await Geolocator.getCurrentPosition(
|
Position position = await Geolocator.getCurrentPosition(
|
||||||
desiredAccuracy: LocationAccuracy.high);
|
desiredAccuracy: LocationAccuracy.high);
|
||||||
|
|
||||||
List<Placemark> placeMarks =
|
debugPrint(position.toString()); // TODO remove
|
||||||
await placemarkFromCoordinates(position.latitude, position.longitude);
|
|
||||||
if (placeMarks.isNotEmpty) {
|
if (isMobile) {
|
||||||
Placemark placeMark = placeMarks.first;
|
// using package geocoding which works for Android and iOS only
|
||||||
setState(() {
|
List<Placemark> placeMarks = await placemarkFromCoordinates(
|
||||||
_street = placeMark.street!;
|
position.latitude, position.longitude);
|
||||||
_country = placeMark.country!;
|
|
||||||
_city = placeMark.locality!;
|
if (placeMarks.isNotEmpty) {
|
||||||
_latitude = position.latitude;
|
Placemark placeMark = placeMarks.first;
|
||||||
_longitude = position.longitude;
|
setState(() {
|
||||||
});
|
_street = placeMark.street ?? '';
|
||||||
triggerCallback();
|
_country = placeMark.country ?? '';
|
||||||
|
_city = placeMark.locality ?? '';
|
||||||
|
_latitude = position.latitude;
|
||||||
|
_longitude = position.longitude;
|
||||||
|
});
|
||||||
|
triggerCallback();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// using osm_nominatim for other platforms than Android and iOS
|
||||||
|
try {
|
||||||
|
Place reverseSearchResult = await Nominatim.reverseSearch(
|
||||||
|
lat: position.latitude,
|
||||||
|
lon: position.longitude,
|
||||||
|
addressDetails: true,
|
||||||
|
nameDetails: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic>? addressData = reverseSearchResult.address;
|
||||||
|
|
||||||
|
if (addressData != null) {
|
||||||
|
String street = _getStreetInfo(addressData);
|
||||||
|
String city = _getCityInfo(addressData);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_latitude = position.latitude;
|
||||||
|
_longitude = position.longitude;
|
||||||
|
_street = street;
|
||||||
|
_country = addressData.containsKey('country')
|
||||||
|
? addressData['country']
|
||||||
|
: '';
|
||||||
|
_administrativeArea = addressData.containsKey('state')
|
||||||
|
? addressData['state']
|
||||||
|
: null;
|
||||||
|
_city = city;
|
||||||
|
_subLocality = addressData.containsKey('suburb')
|
||||||
|
? addressData['suburb']
|
||||||
|
: null;
|
||||||
|
_postalCode = addressData.containsKey('postcode')
|
||||||
|
? addressData['postcode']
|
||||||
|
: null;
|
||||||
|
errorText = null;
|
||||||
|
});
|
||||||
|
triggerCallback();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('Error looking up current location: $e');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint('Error getting current location: $e');
|
debugPrint('Error getting current location: $e');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Categories according to OSM tagging are not always consistent.
|
||||||
|
// https://nominatim.org/release-docs/latest/api/Output/#addressdetails
|
||||||
|
|
||||||
|
/// Try to get nominatim street information.
|
||||||
|
String _getStreetInfo(Map<String, dynamic> addressData) {
|
||||||
|
String street = '';
|
||||||
|
if (addressData.containsKey('road')) {
|
||||||
|
street = addressData['road'];
|
||||||
|
} else if (addressData.containsKey('locality')) {
|
||||||
|
street = addressData['locality'];
|
||||||
|
} else if (addressData.containsKey('city_block')) {
|
||||||
|
street = addressData['city_block'];
|
||||||
|
}
|
||||||
|
return street;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to get nominatim city information.
|
||||||
|
String _getCityInfo(Map<String, dynamic> addressData) {
|
||||||
|
String city = '';
|
||||||
|
if (addressData.containsKey('city')) {
|
||||||
|
city = addressData['city'];
|
||||||
|
} else if (addressData.containsKey('village')) {
|
||||||
|
city = addressData['village'];
|
||||||
|
} else if (addressData.containsKey('town')) {
|
||||||
|
city = addressData['town'];
|
||||||
|
}
|
||||||
|
return city;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import '../pages/conversations_page.dart';
|
import '../pages/conversations_page.dart';
|
||||||
import '../pages/liked_users_page.dart';
|
import '../pages/liked_users_page.dart';
|
||||||
import '../pages/user_data_page.dart';
|
|
||||||
import '../pages/settings_page.dart';
|
import '../pages/settings_page.dart';
|
||||||
import '../pages/user_matching_page.dart';
|
import '../pages/user_matching_page.dart';
|
||||||
import '../pages/user_profile_page.dart';
|
import '../pages/user_profile_page.dart';
|
||||||
|
@ -42,15 +41,9 @@ class MyDrawer extends StatelessWidget {
|
||||||
title: const Text('Home'),
|
title: const Text('Home'),
|
||||||
leading: const Icon(Icons.home),
|
leading: const Icon(Icons.home),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// pop the drawer
|
// home screen is the only place this drawer is used on,
|
||||||
|
// so just pop the drawer, else add page navigation.
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
// Optional: Navigate to HomePage
|
|
||||||
/* Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (BuildContext context) => HomePage(),
|
|
||||||
),
|
|
||||||
);*/
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -152,28 +145,6 @@ class MyDrawer extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
// TODO TESTING - user data tile
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(left: 25),
|
|
||||||
child: ListTile(
|
|
||||||
title: const Text("TESTING - User Data"),
|
|
||||||
leading: const Icon(Icons.supervised_user_circle),
|
|
||||||
onTap: () {
|
|
||||||
// pop the drawer first, then navigate to destination
|
|
||||||
Navigator.pop(context);
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => const UserDataPage(
|
|
||||||
isRegProcess: false,
|
|
||||||
isEditMode: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// horizontal line
|
// horizontal line
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
|
|
@ -34,7 +34,6 @@ class _UserDataPageState extends State<UserDataPage> {
|
||||||
List<LanguageSetting> languagesList = [];
|
List<LanguageSetting> languagesList = [];
|
||||||
final List<Language> _selectedLanguages = [];
|
final List<Language> _selectedLanguages = [];
|
||||||
|
|
||||||
// get instance of firestore and auth
|
|
||||||
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
|
||||||
final AuthService _authService = AuthService();
|
final AuthService _authService = AuthService();
|
||||||
|
|
||||||
|
@ -57,7 +56,6 @@ class _UserDataPageState extends State<UserDataPage> {
|
||||||
|
|
||||||
Future<void> _fetchSettings() async {
|
Future<void> _fetchSettings() async {
|
||||||
try {
|
try {
|
||||||
// Fetch user ID
|
|
||||||
String currentUserId = _authService.getCurrentUser()!.uid;
|
String currentUserId = _authService.getCurrentUser()!.uid;
|
||||||
|
|
||||||
// Fetch user document fields (email, uid, gender, ...) from database
|
// Fetch user document fields (email, uid, gender, ...) from database
|
||||||
|
@ -355,11 +353,7 @@ class _UserDataPageState extends State<UserDataPage> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
_showSnackBar('Failed to save user data.');
|
||||||
const SnackBar(
|
|
||||||
content: Text('Failed to save user data.'),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -377,10 +371,11 @@ class _UserDataPageState extends State<UserDataPage> {
|
||||||
actions: [
|
actions: [
|
||||||
if (widget.isRegProcess)
|
if (widget.isRegProcess)
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
AuthService().signOut();
|
AuthService().signOut();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.logout))
|
icon: const Icon(Icons.logout),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
|
@ -450,6 +445,7 @@ class _UserDataPageState extends State<UserDataPage> {
|
||||||
],
|
],
|
||||||
// Display selected secondary location or remove button
|
// Display selected secondary location or remove button
|
||||||
if (_secondaryLocation != null) ...[
|
if (_secondaryLocation != null) ...[
|
||||||
|
const SizedBox(height: 12),
|
||||||
Center(
|
Center(
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: _removeSecondaryLocation,
|
onPressed: _removeSecondaryLocation,
|
||||||
|
@ -505,12 +501,10 @@ class _UserDataPageState extends State<UserDataPage> {
|
||||||
ButtonSegment<Gender>(
|
ButtonSegment<Gender>(
|
||||||
value: Gender.male,
|
value: Gender.male,
|
||||||
label: Text('male'),
|
label: Text('male'),
|
||||||
//icon: Icon(Icons.male_sharp),
|
|
||||||
),
|
),
|
||||||
ButtonSegment<Gender>(
|
ButtonSegment<Gender>(
|
||||||
value: Gender.female,
|
value: Gender.female,
|
||||||
label: Text('female'),
|
label: Text('female'),
|
||||||
//icon: Icon(Icons.female_sharp),
|
|
||||||
),
|
),
|
||||||
ButtonSegment<Gender>(
|
ButtonSegment<Gender>(
|
||||||
value: Gender.divers,
|
value: Gender.divers,
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
import 'package:cloud_firestore/cloud_firestore.dart';
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/foundation.dart' show kIsWeb;
|
||||||
|
import 'dart:io' show Platform;
|
||||||
import '../enumerations.dart';
|
import '../enumerations.dart';
|
||||||
|
|
||||||
///
|
/// Returns [true] if app is running on Mobile (Android or iOS), else [false].
|
||||||
|
bool get isMobile {
|
||||||
|
if (kIsWeb) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return Platform.isIOS || Platform.isAndroid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Compare two lists by their content ignoring their elements order.
|
/// Compare two lists by their content ignoring their elements order.
|
||||||
///
|
|
||||||
bool equalContent(List<dynamic> list1, List<dynamic> list2) {
|
bool equalContent(List<dynamic> list1, List<dynamic> list2) {
|
||||||
return const DeepCollectionEquality.unordered().equals(list1, list2);
|
return const DeepCollectionEquality.unordered().equals(list1, list2);
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// Creates a composite ID from the passed [ids].
|
/// Creates a composite ID from the passed [ids].
|
||||||
/// In the format id(1)_id(n)
|
/// In the format id(1)_id(n)
|
||||||
///
|
|
||||||
String getCompoundId(List<String> ids) {
|
String getCompoundId(List<String> ids) {
|
||||||
ids.sort(); // sort to ensure the result is the same for any order of ids
|
ids.sort(); // sort to ensure the result is the same for any order of ids
|
||||||
return ids.join('_');
|
return ids.join('_');
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a date format of '$weekday, $day. $month $year $hours:$minutes'.
|
/// Returns a date format of '$weekday, $day. $month $year $hours:$minutes'.
|
||||||
/// For example: Sat. 3 Jun. 2024 15:03.
|
/// For example: [Sat. 3 Jun. 2024 15:03].
|
||||||
/// If any errors occur, an empty string will be returned.
|
/// If any errors occur, an empty string will be returned.
|
||||||
String formatTimestamp(Timestamp timestamp) {
|
String formatTimestamp(Timestamp timestamp) {
|
||||||
try {
|
try {
|
||||||
|
@ -63,10 +69,8 @@ String formatTimestamp(Timestamp timestamp) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// Get pronoun for given [userGender].
|
/// Get pronoun for given [userGender].
|
||||||
/// Returns [He] if male, [She] if female, else [He/She].
|
/// Returns [He] if male, [She] if female, else [He/She].
|
||||||
///
|
|
||||||
String getPronoun(Gender? userGender) {
|
String getPronoun(Gender? userGender) {
|
||||||
switch (userGender) {
|
switch (userGender) {
|
||||||
case Gender.male:
|
case Gender.male:
|
||||||
|
@ -78,9 +82,7 @@ String getPronoun(Gender? userGender) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// Get the [displayName] of our own Enumerations.
|
/// Get the [displayName] of our own Enumerations.
|
||||||
///
|
|
||||||
String getDisplayText(dynamic option) {
|
String getDisplayText(dynamic option) {
|
||||||
// Check if the option is an enum and has a displayName property
|
// Check if the option is an enum and has a displayName property
|
||||||
if (option is Enum) {
|
if (option is Enum) {
|
||||||
|
@ -93,9 +95,7 @@ String getDisplayText(dynamic option) {
|
||||||
return option.toString().split('.').last;
|
return option.toString().split('.').last;
|
||||||
}
|
}
|
||||||
|
|
||||||
///
|
|
||||||
/// Show a simple message dialog
|
/// Show a simple message dialog
|
||||||
///
|
|
||||||
void showMsg(BuildContext context, String title, String content) {
|
void showMsg(BuildContext context, String title, String content) {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -114,6 +114,7 @@ void showMsg(BuildContext context, String title, String content) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Show a red colored SnackBar with a [Dismiss] Button
|
||||||
void showErrorSnackBar(BuildContext context, String message) {
|
void showErrorSnackBar(BuildContext context, String message) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
|
|
@ -528,6 +528,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.0"
|
||||||
|
osm_nominatim:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: osm_nominatim
|
||||||
|
sha256: "037f1af3abee92cf34e33b562cec1acbb0210234b1bdf3d8975bdef3849e6287"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -48,6 +48,7 @@ dependencies:
|
||||||
firebase_storage: ^11.7.7
|
firebase_storage: ^11.7.7
|
||||||
image_cropper: ^6.0.0
|
image_cropper: ^6.0.0
|
||||||
percent_indicator: ^4.2.3
|
percent_indicator: ^4.2.3
|
||||||
|
osm_nominatim: ^3.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in New Issue