Rain/lib/app/ui/geolocation.dart

542 lines
27 KiB
Dart
Raw Normal View History

2024-02-11 22:00:37 +03:00
import 'dart:ui';
import 'package:flutter/material.dart';
2024-08-20 22:19:02 +03:00
import 'package:flutter_map/flutter_map.dart';
2024-08-04 11:48:25 +03:00
import 'package:gap/gap.dart';
2024-02-11 22:00:37 +03:00
import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
2024-08-12 21:03:35 +03:00
import 'package:iconsax_plus/iconsax_plus.dart';
2024-08-20 22:19:02 +03:00
import 'package:latlong2/latlong.dart';
2024-02-11 22:00:37 +03:00
import 'package:rain/app/api/api.dart';
2024-08-02 22:52:33 +03:00
import 'package:rain/app/api/city_api.dart';
2024-02-11 22:00:37 +03:00
import 'package:rain/app/controller/controller.dart';
2024-09-06 22:07:50 +03:00
import 'package:rain/app/ui/home.dart';
import 'package:rain/app/ui/widgets/button.dart';
import 'package:rain/app/ui/widgets/text_form.dart';
2024-02-11 22:00:37 +03:00
import 'package:rain/main.dart';
class SelectGeolocation extends StatefulWidget {
2025-03-15 23:40:48 +03:00
const SelectGeolocation({super.key, required this.isStart});
2024-02-12 19:04:42 +03:00
final bool isStart;
2024-02-11 22:00:37 +03:00
@override
State<SelectGeolocation> createState() => _SelectGeolocationState();
}
class _SelectGeolocationState extends State<SelectGeolocation> {
bool isLoading = false;
final formKeySearch = GlobalKey<FormState>();
final _focusNode = FocusNode();
final weatherController = Get.put(WeatherController());
final _controller = TextEditingController();
final _controllerLat = TextEditingController();
final _controllerLon = TextEditingController();
final _controllerCity = TextEditingController();
final _controllerDistrict = TextEditingController();
2024-08-20 22:19:02 +03:00
static const colorFilter = ColorFilter.matrix(<double>[
-0.2, -0.7, -0.08, 0, 255, // Red channel
-0.2, -0.7, -0.08, 0, 255, // Green channel
-0.2, -0.7, -0.08, 0, 255, // Blue channel
0, 0, 0, 1, 0, // Alpha channel
]);
final bool _isDarkMode = Get.theme.brightness == Brightness.dark;
final mapController = MapController();
2024-02-11 22:00:37 +03:00
textTrim(value) {
value.text = value.text.trim();
while (value.text.contains(' ')) {
value.text = value.text.replaceAll(' ', ' ');
}
}
void fillController(selection) {
_controllerLat.text = '${selection.latitude}';
_controllerLon.text = '${selection.longitude}';
_controllerCity.text = selection.name;
_controllerDistrict.text = selection.admin1;
_controller.clear();
_focusNode.unfocus();
setState(() {});
}
void fillControllerGeo(location) {
_controllerLat.text = '${location['lat']}';
_controllerLon.text = '${location['lon']}';
_controllerCity.text = location['district'];
_controllerDistrict.text = location['city'];
setState(() {});
}
2024-08-20 22:19:02 +03:00
void fillMap(double latitude, double longitude) {
_controllerLat.text = '$latitude';
_controllerLon.text = '$longitude';
setState(() {});
}
Widget _buildMapTileLayer() {
return TileLayer(
2024-08-23 07:15:37 +03:00
urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
2024-08-20 22:19:02 +03:00
userAgentPackageName: 'com.darkmoonight.rain',
);
}
2024-02-11 22:00:37 +03:00
@override
Widget build(BuildContext context) {
const kTextFieldElevation = 4.0;
return Form(
key: formKeySearch,
child: Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
centerTitle: true,
2025-03-15 23:40:48 +03:00
leading:
widget.isStart
? null
: IconButton(
onPressed: () {
Get.back();
},
icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
2024-02-12 19:04:42 +03:00
),
2024-02-11 22:00:37 +03:00
automaticallyImplyLeading: false,
title: Text(
'searchCity'.tr,
style: context.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600,
fontSize: 18,
),
),
),
body: SafeArea(
child: Stack(
children: [
Column(
children: [
Flexible(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Flexible(
child: SingleChildScrollView(
child: Column(
children: [
Padding(
padding: const EdgeInsets.symmetric(
2025-03-15 23:40:48 +03:00
horizontal: 10,
),
2024-08-20 22:19:02 +03:00
child: ClipRRect(
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
child: SizedBox(
height: Get.size.height * 0.3,
child: FlutterMap(
mapController: mapController,
options: MapOptions(
backgroundColor:
context.theme.colorScheme.surface,
initialCenter: const LatLng(
55.7522,
37.6156,
),
initialZoom: 3,
2024-08-28 21:52:42 +03:00
interactionOptions:
const InteractionOptions(
2025-03-15 23:40:48 +03:00
flags:
InteractiveFlag.all &
~InteractiveFlag.rotate,
),
2024-08-20 22:19:02 +03:00
cameraConstraint:
CameraConstraint.contain(
2025-03-15 23:40:48 +03:00
bounds: LatLngBounds(
const LatLng(-90, -180),
const LatLng(90, 180),
),
),
onLongPress:
(tapPosition, point) => fillMap(
point.latitude,
point.longitude,
),
2024-08-20 22:19:02 +03:00
),
children: [
if (_isDarkMode)
ColorFiltered(
colorFilter: colorFilter,
child: _buildMapTileLayer(),
)
else
_buildMapTileLayer(),
RichAttributionWidget(
animationConfig: const ScaleRAWA(),
attributions: [
TextSourceAttribution(
2024-08-23 07:15:37 +03:00
'OpenStreetMap contributors',
2025-03-15 23:40:48 +03:00
onTap:
() => weatherController
.urlLauncher(
'https://openstreetmap.org/copyright',
),
2024-08-20 22:19:02 +03:00
),
],
),
],
),
),
),
),
Padding(
2025-03-15 23:40:48 +03:00
padding: const EdgeInsets.fromLTRB(
10,
15,
10,
5,
),
2024-02-11 22:00:37 +03:00
child: Text(
'searchMethod'.tr,
style: context.theme.textTheme.bodyLarge
?.copyWith(fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
),
Row(
children: [
Flexible(
child: RawAutocomplete<Result>(
focusNode: _focusNode,
textEditingController: _controller,
2025-03-15 23:40:48 +03:00
fieldViewBuilder: (
BuildContext context,
TextEditingController
fieldTextEditingController,
FocusNode fieldFocusNode,
VoidCallback onFieldSubmitted,
) {
2024-02-11 22:00:37 +03:00
return MyTextForm(
elevation: kTextFieldElevation,
labelText: 'search'.tr,
type: TextInputType.text,
2025-03-15 23:40:48 +03:00
icon: const Icon(
IconsaxPlusLinear.global_search,
),
2024-02-11 22:00:37 +03:00
controller: _controller,
margin: const EdgeInsets.only(
2025-03-15 23:40:48 +03:00
left: 10,
right: 10,
top: 10,
),
2024-02-11 22:00:37 +03:00
focusNode: _focusNode,
);
},
2025-03-15 23:40:48 +03:00
optionsBuilder: (
TextEditingValue textEditingValue,
) {
2024-02-11 22:00:37 +03:00
if (textEditingValue.text.isEmpty) {
return const Iterable<
2025-03-15 23:40:48 +03:00
Result
>.empty();
2024-02-11 22:00:37 +03:00
}
return WeatherAPI().getCity(
2025-03-15 23:40:48 +03:00
textEditingValue.text,
locale,
);
2024-02-11 22:00:37 +03:00
},
2025-03-15 23:40:48 +03:00
onSelected:
(Result selection) =>
fillController(selection),
displayStringForOption:
(Result option) =>
'${option.name}, ${option.admin1}',
optionsViewBuilder: (
BuildContext context,
AutocompleteOnSelected<Result>
onSelected,
Iterable<Result> options,
) {
2024-02-11 22:00:37 +03:00
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
2024-08-20 22:19:02 +03:00
vertical: 5,
),
2024-02-11 22:00:37 +03:00
child: Align(
alignment: Alignment.topCenter,
child: Material(
borderRadius:
BorderRadius.circular(20),
elevation: 4.0,
child: ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
itemCount: options.length,
2025-03-15 23:40:48 +03:00
itemBuilder: (
BuildContext context,
int index,
) {
2024-02-11 22:00:37 +03:00
final Result option =
2025-03-15 23:40:48 +03:00
options.elementAt(
index,
);
2024-02-11 22:00:37 +03:00
return InkWell(
2025-03-15 23:40:48 +03:00
onTap:
() => onSelected(
option,
),
2024-02-11 22:00:37 +03:00
child: ListTile(
title: Text(
'${option.name}, ${option.admin1}',
2025-03-15 23:40:48 +03:00
style:
context
.textTheme
.labelLarge,
2024-02-11 22:00:37 +03:00
),
),
);
},
),
),
),
);
},
),
),
Card(
elevation: kTextFieldElevation,
margin: const EdgeInsets.only(
top: 10,
right: 10,
),
2024-02-11 22:00:37 +03:00
child: Container(
margin: const EdgeInsets.all(2.5),
child: IconButton(
onPressed: () async {
bool serviceEnabled =
2025-03-15 23:40:48 +03:00
await Geolocator.isLocationServiceEnabled();
2024-02-11 22:00:37 +03:00
if (!serviceEnabled) {
2024-02-15 22:55:14 +03:00
if (!context.mounted) return;
2024-02-11 22:00:37 +03:00
await showAdaptiveDialog(
context: context,
2025-03-15 23:40:48 +03:00
builder: (
BuildContext context,
) {
2024-02-11 22:00:37 +03:00
return AlertDialog.adaptive(
title: Text(
'location'.tr,
2025-03-15 23:40:48 +03:00
style:
context
.textTheme
.titleLarge,
2024-02-11 22:00:37 +03:00
),
content: Text(
'no_location'.tr,
2025-03-15 23:40:48 +03:00
style:
context
.textTheme
.titleMedium,
),
2024-02-11 22:00:37 +03:00
actions: [
TextButton(
2025-03-15 23:40:48 +03:00
onPressed:
() => Get.back(
result: false,
),
2024-02-11 22:00:37 +03:00
child: Text(
'cancel'.tr,
style: context
.textTheme
.titleMedium
?.copyWith(
2025-03-15 23:40:48 +03:00
color:
Colors
.blueAccent,
),
2024-02-11 22:00:37 +03:00
),
),
TextButton(
onPressed: () {
2025-03-15 23:40:48 +03:00
Geolocator.openLocationSettings();
2024-02-11 22:00:37 +03:00
Get.back(
2025-03-15 23:40:48 +03:00
result: true,
);
2024-02-11 22:00:37 +03:00
},
child: Text(
'settings'.tr,
style: context
.textTheme
.titleMedium
?.copyWith(
2025-03-15 23:40:48 +03:00
color:
Colors
.green,
),
2024-02-11 22:00:37 +03:00
),
),
],
);
},
);
return;
}
setState(() => isLoading = true);
final location =
await weatherController
.getCurrentLocationSearch();
fillControllerGeo(location);
setState(() => isLoading = false);
},
icon: const Icon(
2024-08-12 21:03:35 +03:00
IconsaxPlusLinear.location,
2024-02-11 22:00:37 +03:00
),
),
),
),
],
),
MyTextForm(
elevation: kTextFieldElevation,
controller: _controllerLat,
labelText: 'lat'.tr,
type: TextInputType.number,
2024-08-12 21:03:35 +03:00
icon: const Icon(IconsaxPlusLinear.location),
2024-02-11 22:00:37 +03:00
margin: const EdgeInsets.only(
left: 10,
right: 10,
top: 10,
),
2024-02-11 22:00:37 +03:00
validator: (value) {
if (value == null || value.isEmpty) {
return 'validateValue'.tr;
}
2025-03-15 23:40:48 +03:00
double? numericValue = double.tryParse(
value,
);
2024-02-11 22:00:37 +03:00
if (numericValue == null) {
return 'validateNumber'.tr;
}
if (numericValue < -90 ||
numericValue > 90) {
return 'validate90'.tr;
}
return null;
},
),
MyTextForm(
elevation: kTextFieldElevation,
controller: _controllerLon,
labelText: 'lon'.tr,
type: TextInputType.number,
2024-08-12 21:03:35 +03:00
icon: const Icon(IconsaxPlusLinear.location),
2024-02-11 22:00:37 +03:00
margin: const EdgeInsets.only(
left: 10,
right: 10,
top: 10,
),
2024-02-11 22:00:37 +03:00
validator: (value) {
if (value == null || value.isEmpty) {
return 'validateValue'.tr;
}
2025-03-15 23:40:48 +03:00
double? numericValue = double.tryParse(
value,
);
2024-02-11 22:00:37 +03:00
if (numericValue == null) {
return 'validateNumber'.tr;
}
if (numericValue < -180 ||
numericValue > 180) {
return 'validate180'.tr;
}
return null;
},
),
MyTextForm(
elevation: kTextFieldElevation,
controller: _controllerCity,
labelText: 'city'.tr,
type: TextInputType.name,
2025-03-15 23:40:48 +03:00
icon: const Icon(
IconsaxPlusLinear.building_3,
),
2024-02-11 22:00:37 +03:00
margin: const EdgeInsets.only(
2025-03-15 23:40:48 +03:00
left: 10,
right: 10,
top: 10,
),
2024-02-11 22:00:37 +03:00
validator: (value) {
if (value == null || value.isEmpty) {
return 'validateName'.tr;
}
return null;
},
),
MyTextForm(
elevation: kTextFieldElevation,
controller: _controllerDistrict,
labelText: 'district'.tr,
type: TextInputType.streetAddress,
2024-08-12 21:03:35 +03:00
icon: const Icon(IconsaxPlusLinear.global),
2024-02-11 22:00:37 +03:00
margin: const EdgeInsets.only(
2025-03-15 23:40:48 +03:00
left: 10,
right: 10,
top: 10,
),
2024-02-11 22:00:37 +03:00
),
2024-08-04 11:48:25 +03:00
const Gap(20),
2024-02-11 22:00:37 +03:00
],
),
),
),
],
),
),
Padding(
2025-03-15 23:40:48 +03:00
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 8,
),
2024-02-11 22:00:37 +03:00
child: MyTextButton(
buttonName: 'done'.tr,
onPressed: () async {
if (formKeySearch.currentState!.validate()) {
textTrim(_controllerLat);
textTrim(_controllerLon);
textTrim(_controllerCity);
textTrim(_controllerDistrict);
try {
await weatherController.deleteAll(true);
await weatherController.getLocation(
double.parse(_controllerLat.text),
double.parse(_controllerLon.text),
_controllerDistrict.text,
_controllerCity.text,
);
2024-02-12 19:04:42 +03:00
widget.isStart
2025-03-15 23:40:48 +03:00
? Get.off(
() => const HomePage(),
transition: Transition.downToUp,
)
2024-02-12 19:04:42 +03:00
: Get.back();
2024-02-11 22:00:37 +03:00
} catch (error) {
Future.error(error);
}
}
},
),
),
],
),
if (isLoading)
BackdropFilter(
filter: ImageFilter.blur(sigmaY: 3, sigmaX: 3),
2025-03-15 23:40:48 +03:00
child: const Center(child: CircularProgressIndicator()),
2024-02-11 22:00:37 +03:00
),
],
),
),
),
);
}
}