2023-06-17 20:57:57 +03:00
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:get/get.dart';
|
2024-08-12 21:03:35 +03:00
|
|
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
2023-06-17 20:57:57 +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';
|
2023-06-17 20:57:57 +03:00
|
|
|
import 'package:rain/app/controller/controller.dart';
|
2024-07-24 23:07:35 +03:00
|
|
|
import 'package:rain/app/widgets/button.dart';
|
2023-06-17 20:57:57 +03:00
|
|
|
import 'package:rain/app/widgets/text_form.dart';
|
2023-08-03 20:52:20 +03:00
|
|
|
import 'package:rain/main.dart';
|
2023-06-17 20:57:57 +03:00
|
|
|
|
|
|
|
class CreateWeatherCard extends StatefulWidget {
|
|
|
|
const CreateWeatherCard({super.key});
|
|
|
|
|
|
|
|
@override
|
|
|
|
State<CreateWeatherCard> createState() => _CreateWeatherCardState();
|
|
|
|
}
|
|
|
|
|
2024-07-29 22:37:08 +03:00
|
|
|
class _CreateWeatherCardState extends State<CreateWeatherCard>
|
|
|
|
with SingleTickerProviderStateMixin {
|
2023-06-17 20:57:57 +03:00
|
|
|
bool isLoading = false;
|
|
|
|
final formKey = GlobalKey<FormState>();
|
2023-08-03 20:52:20 +03:00
|
|
|
final _focusNode = FocusNode();
|
2023-09-01 20:18:40 +03:00
|
|
|
final weatherController = Get.put(WeatherController());
|
2023-08-03 20:52:20 +03:00
|
|
|
final _controller = TextEditingController();
|
|
|
|
final _controllerLat = TextEditingController();
|
|
|
|
final _controllerLon = TextEditingController();
|
|
|
|
final _controllerCity = TextEditingController();
|
|
|
|
final _controllerDistrict = TextEditingController();
|
2023-06-17 20:57:57 +03:00
|
|
|
|
2024-07-29 22:37:08 +03:00
|
|
|
late AnimationController _animationController;
|
|
|
|
late Animation<double> _animation;
|
|
|
|
|
|
|
|
@override
|
|
|
|
void initState() {
|
|
|
|
super.initState();
|
|
|
|
_animationController = AnimationController(
|
|
|
|
duration: const Duration(milliseconds: 300),
|
|
|
|
vsync: this,
|
|
|
|
);
|
|
|
|
_animation = CurvedAnimation(
|
|
|
|
parent: _animationController,
|
|
|
|
curve: Curves.easeInOut,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
void dispose() {
|
|
|
|
_animationController.dispose();
|
|
|
|
_controller.dispose();
|
|
|
|
_controllerLat.dispose();
|
|
|
|
_controllerLon.dispose();
|
|
|
|
_controllerCity.dispose();
|
|
|
|
_controllerDistrict.dispose();
|
|
|
|
super.dispose();
|
|
|
|
}
|
|
|
|
|
|
|
|
void textTrim(TextEditingController value) {
|
2023-06-17 20:57:57 +03:00
|
|
|
value.text = value.text.trim();
|
2023-09-24 18:52:15 +03:00
|
|
|
while (value.text.contains(' ')) {
|
|
|
|
value.text = value.text.replaceAll(' ', ' ');
|
2023-06-17 20:57:57 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-07-29 22:37:08 +03:00
|
|
|
void fillController(Result selection) {
|
2023-06-17 20:57:57 +03:00
|
|
|
_controllerLat.text = '${selection.latitude}';
|
|
|
|
_controllerLon.text = '${selection.longitude}';
|
|
|
|
_controllerCity.text = selection.name;
|
|
|
|
_controllerDistrict.text = selection.admin1;
|
|
|
|
_controller.clear();
|
|
|
|
_focusNode.unfocus();
|
|
|
|
setState(() {});
|
|
|
|
}
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
2023-10-30 23:10:17 +05:30
|
|
|
const kTextFieldElevation = 4.0;
|
2024-07-29 22:37:08 +03:00
|
|
|
bool showButton = _controllerLon.text.isNotEmpty &&
|
|
|
|
_controllerLat.text.isNotEmpty &&
|
|
|
|
_controllerCity.text.isNotEmpty &&
|
|
|
|
_controllerDistrict.text.isNotEmpty;
|
|
|
|
|
|
|
|
if (showButton) {
|
|
|
|
_animationController.forward();
|
|
|
|
} else {
|
|
|
|
_animationController.reverse();
|
|
|
|
}
|
|
|
|
|
2024-07-09 23:03:40 +03:00
|
|
|
return Padding(
|
|
|
|
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
|
|
|
child: Form(
|
|
|
|
key: formKey,
|
|
|
|
child: SingleChildScrollView(
|
|
|
|
child: Stack(
|
|
|
|
children: [
|
|
|
|
Padding(
|
|
|
|
padding: EdgeInsets.only(
|
|
|
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
|
|
|
),
|
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
children: [
|
|
|
|
Padding(
|
2024-07-29 22:37:08 +03:00
|
|
|
padding: const EdgeInsets.only(top: 14, bottom: 7),
|
2024-07-24 23:07:35 +03:00
|
|
|
child: Text(
|
|
|
|
'create'.tr,
|
2024-07-29 22:37:08 +03:00
|
|
|
style: context.textTheme.titleLarge
|
|
|
|
?.copyWith(fontWeight: FontWeight.bold),
|
2024-07-24 23:07:35 +03:00
|
|
|
textAlign: TextAlign.center,
|
2024-07-09 23:03:40 +03:00
|
|
|
),
|
2023-06-17 20:57:57 +03:00
|
|
|
),
|
2024-07-09 23:03:40 +03:00
|
|
|
RawAutocomplete<Result>(
|
|
|
|
focusNode: _focusNode,
|
|
|
|
textEditingController: _controller,
|
|
|
|
fieldViewBuilder: (BuildContext context,
|
|
|
|
TextEditingController fieldTextEditingController,
|
|
|
|
FocusNode fieldFocusNode,
|
|
|
|
VoidCallback onFieldSubmitted) {
|
|
|
|
return MyTextForm(
|
|
|
|
elevation: kTextFieldElevation,
|
|
|
|
labelText: 'search'.tr,
|
|
|
|
type: TextInputType.text,
|
2024-08-12 21:03:35 +03:00
|
|
|
icon: const Icon(IconsaxPlusLinear.global_search),
|
2024-07-09 23:03:40 +03:00
|
|
|
controller: _controller,
|
2024-07-24 23:07:35 +03:00
|
|
|
margin: const EdgeInsets.only(
|
|
|
|
left: 10, right: 10, top: 10),
|
2024-07-09 23:03:40 +03:00
|
|
|
focusNode: _focusNode,
|
|
|
|
);
|
|
|
|
},
|
|
|
|
optionsBuilder: (TextEditingValue textEditingValue) {
|
|
|
|
if (textEditingValue.text.isEmpty) {
|
|
|
|
return const Iterable<Result>.empty();
|
|
|
|
}
|
|
|
|
return WeatherAPI()
|
|
|
|
.getCity(textEditingValue.text, locale);
|
|
|
|
},
|
2024-07-24 23:07:35 +03:00
|
|
|
onSelected: (Result selection) =>
|
|
|
|
fillController(selection),
|
2024-07-09 23:03:40 +03:00
|
|
|
displayStringForOption: (Result option) =>
|
|
|
|
'${option.name}, ${option.admin1}',
|
|
|
|
optionsViewBuilder: (BuildContext context,
|
|
|
|
AutocompleteOnSelected<Result> onSelected,
|
|
|
|
Iterable<Result> options) {
|
|
|
|
return Padding(
|
|
|
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
|
|
|
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,
|
|
|
|
itemBuilder: (BuildContext context, int index) {
|
2024-07-24 23:07:35 +03:00
|
|
|
final Result option =
|
|
|
|
options.elementAt(index);
|
2024-07-09 23:03:40 +03:00
|
|
|
return InkWell(
|
|
|
|
onTap: () => onSelected(option),
|
|
|
|
child: ListTile(
|
|
|
|
title: Text(
|
|
|
|
'${option.name}, ${option.admin1}',
|
|
|
|
style: context.textTheme.labelLarge,
|
|
|
|
),
|
2023-07-10 21:33:43 +03:00
|
|
|
),
|
2024-07-09 23:03:40 +03:00
|
|
|
);
|
|
|
|
},
|
|
|
|
),
|
2023-07-10 21:33:43 +03:00
|
|
|
),
|
|
|
|
),
|
2024-07-09 23:03:40 +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-07-24 23:07:35 +03:00
|
|
|
onChanged: (value) => setState(() {}),
|
|
|
|
margin:
|
|
|
|
const EdgeInsets.only(left: 10, right: 10, top: 10),
|
2024-07-09 23:03:40 +03:00
|
|
|
validator: (value) {
|
|
|
|
if (value == null || value.isEmpty) {
|
|
|
|
return 'validateValue'.tr;
|
|
|
|
}
|
|
|
|
double? numericValue = double.tryParse(value);
|
|
|
|
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-07-24 23:07:35 +03:00
|
|
|
onChanged: (value) => setState(() {}),
|
|
|
|
margin:
|
|
|
|
const EdgeInsets.only(left: 10, right: 10, top: 10),
|
2024-07-09 23:03:40 +03:00
|
|
|
validator: (value) {
|
|
|
|
if (value == null || value.isEmpty) {
|
|
|
|
return 'validateValue'.tr;
|
|
|
|
}
|
|
|
|
double? numericValue = double.tryParse(value);
|
|
|
|
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,
|
2024-08-12 21:03:35 +03:00
|
|
|
icon: const Icon(IconsaxPlusLinear.building_3),
|
2024-07-24 23:07:35 +03:00
|
|
|
onChanged: (value) => setState(() {}),
|
|
|
|
margin:
|
|
|
|
const EdgeInsets.only(left: 10, right: 10, top: 10),
|
2024-07-09 23:03:40 +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-07-24 23:07:35 +03:00
|
|
|
onChanged: (value) => setState(() {}),
|
|
|
|
margin:
|
|
|
|
const EdgeInsets.only(left: 10, right: 10, top: 10),
|
2024-07-09 23:03:40 +03:00
|
|
|
validator: (value) {
|
|
|
|
if (value == null || value.isEmpty) {
|
|
|
|
return 'validateName'.tr;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
),
|
2024-07-24 23:07:35 +03:00
|
|
|
Padding(
|
|
|
|
padding: const EdgeInsets.symmetric(
|
|
|
|
horizontal: 10, vertical: 10),
|
2024-07-29 22:37:08 +03:00
|
|
|
child: SizeTransition(
|
|
|
|
sizeFactor: _animation,
|
|
|
|
axisAlignment: -1.0,
|
2024-07-24 23:07:35 +03:00
|
|
|
child: MyTextButton(
|
|
|
|
buttonName: 'done'.tr,
|
|
|
|
onPressed: () async {
|
|
|
|
if (formKey.currentState!.validate()) {
|
|
|
|
textTrim(_controllerLat);
|
|
|
|
textTrim(_controllerLon);
|
|
|
|
textTrim(_controllerCity);
|
|
|
|
textTrim(_controllerDistrict);
|
|
|
|
setState(() => isLoading = true);
|
|
|
|
await weatherController.addCardWeather(
|
|
|
|
double.parse(_controllerLat.text),
|
|
|
|
double.parse(_controllerLon.text),
|
|
|
|
_controllerCity.text,
|
|
|
|
_controllerDistrict.text,
|
|
|
|
);
|
|
|
|
setState(() => isLoading = false);
|
|
|
|
Get.back();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
2024-07-09 23:03:40 +03:00
|
|
|
],
|
|
|
|
),
|
2023-06-17 20:57:57 +03:00
|
|
|
),
|
2024-07-09 23:03:40 +03:00
|
|
|
if (isLoading)
|
|
|
|
const Center(
|
|
|
|
child: CircularProgressIndicator(),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
2023-06-17 20:57:57 +03:00
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|