.from(json['sunset'] ?? []),
lat: json['lat'],
diff --git a/lib/app/data/weather.g.dart b/lib/app/data/db.g.dart
old mode 100644
new mode 100755
similarity index 99%
rename from lib/app/data/weather.g.dart
rename to lib/app/data/db.g.dart
index 0b516b4..e2dbe3b
--- a/lib/app/data/weather.g.dart
+++ b/lib/app/data/db.g.dart
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
-part of 'weather.dart';
+part of 'db.dart';
// **************************************************************************
// IsarCollectionGenerator
@@ -27,88 +27,93 @@ const SettingsSchema = CollectionSchema(
name: r'degrees',
type: IsarType.string,
),
- r'language': PropertySchema(
+ r'hideMap': PropertySchema(
id: 2,
+ name: r'hideMap',
+ type: IsarType.bool,
+ ),
+ r'language': PropertySchema(
+ id: 3,
name: r'language',
type: IsarType.string,
),
r'largeElement': PropertySchema(
- id: 3,
+ id: 4,
name: r'largeElement',
type: IsarType.bool,
),
r'location': PropertySchema(
- id: 4,
+ id: 5,
name: r'location',
type: IsarType.bool,
),
r'materialColor': PropertySchema(
- id: 5,
+ id: 6,
name: r'materialColor',
type: IsarType.bool,
),
r'measurements': PropertySchema(
- id: 6,
+ id: 7,
name: r'measurements',
type: IsarType.string,
),
r'notifications': PropertySchema(
- id: 7,
+ id: 8,
name: r'notifications',
type: IsarType.bool,
),
r'onboard': PropertySchema(
- id: 8,
+ id: 9,
name: r'onboard',
type: IsarType.bool,
),
r'pressure': PropertySchema(
- id: 9,
+ id: 10,
name: r'pressure',
type: IsarType.string,
),
r'roundDegree': PropertySchema(
- id: 10,
+ id: 11,
name: r'roundDegree',
type: IsarType.bool,
),
r'theme': PropertySchema(
- id: 11,
+ id: 12,
name: r'theme',
type: IsarType.string,
),
r'timeEnd': PropertySchema(
- id: 12,
+ id: 13,
name: r'timeEnd',
type: IsarType.string,
),
r'timeRange': PropertySchema(
- id: 13,
+ id: 14,
name: r'timeRange',
type: IsarType.long,
),
r'timeStart': PropertySchema(
- id: 14,
+ id: 15,
name: r'timeStart',
type: IsarType.string,
),
r'timeformat': PropertySchema(
- id: 15,
+ id: 16,
name: r'timeformat',
type: IsarType.string,
),
r'widgetBackgroundColor': PropertySchema(
- id: 16,
+ id: 17,
name: r'widgetBackgroundColor',
type: IsarType.string,
),
r'widgetTextColor': PropertySchema(
- id: 17,
+ id: 18,
name: r'widgetTextColor',
type: IsarType.string,
),
r'wind': PropertySchema(
- id: 18,
+ id: 19,
name: r'wind',
type: IsarType.string,
)
@@ -185,23 +190,24 @@ void _settingsSerialize(
) {
writer.writeBool(offsets[0], object.amoledTheme);
writer.writeString(offsets[1], object.degrees);
- writer.writeString(offsets[2], object.language);
- writer.writeBool(offsets[3], object.largeElement);
- writer.writeBool(offsets[4], object.location);
- writer.writeBool(offsets[5], object.materialColor);
- writer.writeString(offsets[6], object.measurements);
- writer.writeBool(offsets[7], object.notifications);
- writer.writeBool(offsets[8], object.onboard);
- writer.writeString(offsets[9], object.pressure);
- writer.writeBool(offsets[10], object.roundDegree);
- writer.writeString(offsets[11], object.theme);
- writer.writeString(offsets[12], object.timeEnd);
- writer.writeLong(offsets[13], object.timeRange);
- writer.writeString(offsets[14], object.timeStart);
- writer.writeString(offsets[15], object.timeformat);
- writer.writeString(offsets[16], object.widgetBackgroundColor);
- writer.writeString(offsets[17], object.widgetTextColor);
- writer.writeString(offsets[18], object.wind);
+ writer.writeBool(offsets[2], object.hideMap);
+ writer.writeString(offsets[3], object.language);
+ writer.writeBool(offsets[4], object.largeElement);
+ writer.writeBool(offsets[5], object.location);
+ writer.writeBool(offsets[6], object.materialColor);
+ writer.writeString(offsets[7], object.measurements);
+ writer.writeBool(offsets[8], object.notifications);
+ writer.writeBool(offsets[9], object.onboard);
+ writer.writeString(offsets[10], object.pressure);
+ writer.writeBool(offsets[11], object.roundDegree);
+ writer.writeString(offsets[12], object.theme);
+ writer.writeString(offsets[13], object.timeEnd);
+ writer.writeLong(offsets[14], object.timeRange);
+ writer.writeString(offsets[15], object.timeStart);
+ writer.writeString(offsets[16], object.timeformat);
+ writer.writeString(offsets[17], object.widgetBackgroundColor);
+ writer.writeString(offsets[18], object.widgetTextColor);
+ writer.writeString(offsets[19], object.wind);
}
Settings _settingsDeserialize(
@@ -213,24 +219,25 @@ Settings _settingsDeserialize(
final object = Settings();
object.amoledTheme = reader.readBool(offsets[0]);
object.degrees = reader.readString(offsets[1]);
+ object.hideMap = reader.readBool(offsets[2]);
object.id = id;
- object.language = reader.readStringOrNull(offsets[2]);
- object.largeElement = reader.readBool(offsets[3]);
- object.location = reader.readBool(offsets[4]);
- object.materialColor = reader.readBool(offsets[5]);
- object.measurements = reader.readString(offsets[6]);
- object.notifications = reader.readBool(offsets[7]);
- object.onboard = reader.readBool(offsets[8]);
- object.pressure = reader.readString(offsets[9]);
- object.roundDegree = reader.readBool(offsets[10]);
- object.theme = reader.readStringOrNull(offsets[11]);
- object.timeEnd = reader.readStringOrNull(offsets[12]);
- object.timeRange = reader.readLongOrNull(offsets[13]);
- object.timeStart = reader.readStringOrNull(offsets[14]);
- object.timeformat = reader.readString(offsets[15]);
- object.widgetBackgroundColor = reader.readStringOrNull(offsets[16]);
- object.widgetTextColor = reader.readStringOrNull(offsets[17]);
- object.wind = reader.readString(offsets[18]);
+ object.language = reader.readStringOrNull(offsets[3]);
+ object.largeElement = reader.readBool(offsets[4]);
+ object.location = reader.readBool(offsets[5]);
+ object.materialColor = reader.readBool(offsets[6]);
+ object.measurements = reader.readString(offsets[7]);
+ object.notifications = reader.readBool(offsets[8]);
+ object.onboard = reader.readBool(offsets[9]);
+ object.pressure = reader.readString(offsets[10]);
+ object.roundDegree = reader.readBool(offsets[11]);
+ object.theme = reader.readStringOrNull(offsets[12]);
+ object.timeEnd = reader.readStringOrNull(offsets[13]);
+ object.timeRange = reader.readLongOrNull(offsets[14]);
+ object.timeStart = reader.readStringOrNull(offsets[15]);
+ object.timeformat = reader.readString(offsets[16]);
+ object.widgetBackgroundColor = reader.readStringOrNull(offsets[17]);
+ object.widgetTextColor = reader.readStringOrNull(offsets[18]);
+ object.wind = reader.readString(offsets[19]);
return object;
}
@@ -246,38 +253,40 @@ P _settingsDeserializeProp(
case 1:
return (reader.readString(offset)) as P;
case 2:
- return (reader.readStringOrNull(offset)) as P;
- case 3:
return (reader.readBool(offset)) as P;
+ case 3:
+ return (reader.readStringOrNull(offset)) as P;
case 4:
return (reader.readBool(offset)) as P;
case 5:
return (reader.readBool(offset)) as P;
case 6:
- return (reader.readString(offset)) as P;
- case 7:
return (reader.readBool(offset)) as P;
+ case 7:
+ return (reader.readString(offset)) as P;
case 8:
return (reader.readBool(offset)) as P;
case 9:
- return (reader.readString(offset)) as P;
- case 10:
return (reader.readBool(offset)) as P;
+ case 10:
+ return (reader.readString(offset)) as P;
case 11:
- return (reader.readStringOrNull(offset)) as P;
+ return (reader.readBool(offset)) as P;
case 12:
return (reader.readStringOrNull(offset)) as P;
case 13:
- return (reader.readLongOrNull(offset)) as P;
+ return (reader.readStringOrNull(offset)) as P;
case 14:
- return (reader.readStringOrNull(offset)) as P;
+ return (reader.readLongOrNull(offset)) as P;
case 15:
- return (reader.readString(offset)) as P;
- case 16:
return (reader.readStringOrNull(offset)) as P;
+ case 16:
+ return (reader.readString(offset)) as P;
case 17:
return (reader.readStringOrNull(offset)) as P;
case 18:
+ return (reader.readStringOrNull(offset)) as P;
+ case 19:
return (reader.readString(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
@@ -513,6 +522,16 @@ extension SettingsQueryFilter
});
}
+ QueryBuilder hideMapEqualTo(
+ bool value) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'hideMap',
+ value: value,
+ ));
+ });
+ }
+
QueryBuilder idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
@@ -2145,6 +2164,18 @@ extension SettingsQuerySortBy on QueryBuilder {
});
}
+ QueryBuilder sortByHideMap() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'hideMap', Sort.asc);
+ });
+ }
+
+ QueryBuilder sortByHideMapDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'hideMap', Sort.desc);
+ });
+ }
+
QueryBuilder sortByLanguage() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'language', Sort.asc);
@@ -2377,6 +2408,18 @@ extension SettingsQuerySortThenBy
});
}
+ QueryBuilder thenByHideMap() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'hideMap', Sort.asc);
+ });
+ }
+
+ QueryBuilder thenByHideMapDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'hideMap', Sort.desc);
+ });
+ }
+
QueryBuilder thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
@@ -2610,6 +2653,12 @@ extension SettingsQueryWhereDistinct
});
}
+ QueryBuilder distinctByHideMap() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addDistinctBy(r'hideMap');
+ });
+ }
+
QueryBuilder distinctByLanguage(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
@@ -2745,6 +2794,12 @@ extension SettingsQueryProperty
});
}
+ QueryBuilder hideMapProperty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addPropertyName(r'hideMap');
+ });
+ }
+
QueryBuilder languageProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'language');
diff --git a/lib/app/modules/cards/view/info_weather_card.dart b/lib/app/modules/cards/view/info_weather_card.dart
deleted file mode 100644
index 8079045..0000000
--- a/lib/app/modules/cards/view/info_weather_card.dart
+++ /dev/null
@@ -1,192 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax_plus/iconsax_plus.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/data/weather.dart';
-import 'package:rain/app/widgets/daily/weather_daily.dart';
-import 'package:rain/app/widgets/daily/weather_more.dart';
-import 'package:rain/app/widgets/desc/desc_container.dart';
-import 'package:rain/app/widgets/hourly/weather_hourly.dart';
-import 'package:rain/app/widgets/now/weather_now.dart';
-import 'package:rain/app/widgets/sun_moon/sunset_sunrise.dart';
-import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
-
-class InfoWeatherCard extends StatefulWidget {
- const InfoWeatherCard({
- super.key,
- required this.weatherCard,
- });
- final WeatherCard weatherCard;
-
- @override
- State createState() => _InfoWeatherCardState();
-}
-
-class _InfoWeatherCardState extends State {
- int timeNow = 0;
- int dayNow = 0;
- final weatherController = Get.put(WeatherController());
- final itemScrollController = ItemScrollController();
-
- @override
- void initState() {
- getTime();
- super.initState();
- }
-
- void getTime() {
- final weatherCard = widget.weatherCard;
-
- timeNow =
- weatherController.getTime(weatherCard.time!, weatherCard.timezone!);
- dayNow =
- weatherController.getDay(weatherCard.timeDaily!, weatherCard.timezone!);
- Future.delayed(const Duration(milliseconds: 30), () {
- itemScrollController.scrollTo(
- index: timeNow,
- duration: const Duration(seconds: 2),
- curve: Curves.easeInOutCubic,
- );
- });
- }
-
- @override
- Widget build(BuildContext context) {
- final weatherCard = widget.weatherCard;
-
- return RefreshIndicator(
- onRefresh: () async {
- await weatherController.updateCard(weatherCard);
- getTime();
- setState(() {});
- },
- child: Scaffold(
- appBar: AppBar(
- centerTitle: true,
- automaticallyImplyLeading: false,
- leading: IconButton(
- onPressed: () => Get.back(),
- icon: const Icon(
- IconsaxPlusLinear.arrow_left_3,
- size: 20,
- ),
- ),
- title: Text(
- weatherCard.district!.isNotEmpty
- ? '${weatherCard.city}'
- ', ${weatherCard.district}'
- : '${weatherCard.city}',
- style: context.textTheme.titleMedium?.copyWith(
- fontWeight: FontWeight.w600,
- fontSize: 18,
- ),
- ),
- ),
- body: SafeArea(
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 10),
- child: ListView(
- children: [
- WeatherNow(
- time: weatherCard.time![timeNow],
- weather: weatherCard.weathercode![timeNow],
- degree: weatherCard.temperature2M![timeNow],
- feels: weatherCard.apparentTemperature![timeNow]!,
- timeDay: weatherCard.sunrise![dayNow],
- timeNight: weatherCard.sunset![dayNow],
- tempMax: weatherCard.temperature2MMax![dayNow]!,
- tempMin: weatherCard.temperature2MMin![dayNow]!,
- ),
- Card(
- margin: const EdgeInsets.only(bottom: 15),
- child: Padding(
- padding:
- const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
- child: SizedBox(
- height: 135,
- child: ScrollablePositionedList.separated(
- key: const PageStorageKey(1),
- separatorBuilder: (BuildContext context, int index) {
- return const VerticalDivider(
- width: 10,
- indent: 40,
- endIndent: 40,
- );
- },
- scrollDirection: Axis.horizontal,
- itemScrollController: itemScrollController,
- itemCount: weatherCard.time!.length,
- itemBuilder: (ctx, i) => GestureDetector(
- onTap: () {
- timeNow = i;
- dayNow = (i / 24).floor();
- setState(() {});
- },
- child: Container(
- margin: const EdgeInsets.symmetric(vertical: 5),
- padding: const EdgeInsets.symmetric(
- horizontal: 20,
- vertical: 5,
- ),
- decoration: BoxDecoration(
- color: i == timeNow
- ? context.theme.colorScheme.secondaryContainer
- : Colors.transparent,
- borderRadius: const BorderRadius.all(
- Radius.circular(20),
- ),
- ),
- child: WeatherHourly(
- time: weatherCard.time![i],
- weather: weatherCard.weathercode![i],
- degree: weatherCard.temperature2M![i],
- timeDay: weatherCard.sunrise![(i / 24).floor()],
- timeNight: weatherCard.sunset![(i / 24).floor()],
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- SunsetSunrise(
- timeSunrise: weatherCard.sunrise![dayNow],
- timeSunset: weatherCard.sunset![dayNow],
- ),
- DescContainer(
- humidity: weatherCard.relativehumidity2M?[timeNow],
- wind: weatherCard.windspeed10M?[timeNow],
- visibility: weatherCard.visibility?[timeNow],
- feels: weatherCard.apparentTemperature?[timeNow],
- evaporation: weatherCard.evapotranspiration?[timeNow],
- precipitation: weatherCard.precipitation?[timeNow],
- direction: weatherCard.winddirection10M?[timeNow],
- pressure: weatherCard.surfacePressure?[timeNow],
- rain: weatherCard.rain?[timeNow],
- cloudcover: weatherCard.cloudcover?[timeNow],
- windgusts: weatherCard.windgusts10M?[timeNow],
- uvIndex: weatherCard.uvIndex?[timeNow],
- dewpoint2M: weatherCard.dewpoint2M?[timeNow],
- precipitationProbability:
- weatherCard.precipitationProbability?[timeNow],
- shortwaveRadiation: weatherCard.shortwaveRadiation?[timeNow],
- initiallyExpanded: false,
- title: 'hourlyVariables'.tr,
- ),
- WeatherDaily(
- weatherData: weatherCard,
- onTap: () => Get.to(
- () => WeatherMore(
- weatherData: weatherCard,
- ),
- transition: Transition.downToUp,
- ),
- ),
- ],
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/cards/view/list_weather_card.dart b/lib/app/modules/cards/view/list_weather_card.dart
deleted file mode 100644
index 76b6cb3..0000000
--- a/lib/app/modules/cards/view/list_weather_card.dart
+++ /dev/null
@@ -1,103 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax_plus/iconsax_plus.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/modules/cards/widgets/weather_card_list.dart';
-import 'package:rain/app/widgets/text_form.dart';
-
-class ListWeatherCard extends StatefulWidget {
- const ListWeatherCard({super.key});
-
- @override
- State createState() => _ListWeatherCardState();
-}
-
-class _ListWeatherCardState extends State {
- final weatherController = Get.put(WeatherController());
- TextEditingController searchTasks = TextEditingController();
- String filter = '';
-
- applyFilter(String value) async {
- filter = value.toLowerCase();
- setState(() {});
- }
-
- @override
- void initState() {
- super.initState();
- applyFilter('');
- }
-
- @override
- Widget build(BuildContext context) {
- final textTheme = context.textTheme;
- final titleMedium = textTheme.titleMedium;
- return Obx(
- () => weatherController.weatherCards.isEmpty
- ? Center(
- child: SingleChildScrollView(
- child: Column(
- children: [
- Image.asset(
- 'assets/icons/City.png',
- scale: 6,
- ),
- SizedBox(
- width: Get.size.width * 0.8,
- child: Text(
- 'noWeatherCard'.tr,
- textAlign: TextAlign.center,
- style: titleMedium?.copyWith(
- fontWeight: FontWeight.w600,
- fontSize: 18,
- ),
- ),
- ),
- ],
- ),
- ),
- )
- : NestedScrollView(
- physics: const NeverScrollableScrollPhysics(),
- headerSliverBuilder: (context, innerBoxIsScrolled) {
- return [
- SliverToBoxAdapter(
- child: MyTextForm(
- labelText: 'search'.tr,
- type: TextInputType.text,
- icon: const Icon(
- IconsaxPlusLinear.search_normal_1,
- size: 20,
- ),
- controller: searchTasks,
- margin: const EdgeInsets.symmetric(
- horizontal: 10, vertical: 5),
- onChanged: applyFilter,
- iconButton: searchTasks.text.isNotEmpty
- ? IconButton(
- onPressed: () {
- searchTasks.clear();
- applyFilter('');
- },
- icon: const Icon(
- IconsaxPlusLinear.close_circle,
- color: Colors.grey,
- size: 20,
- ),
- )
- : null,
- ),
- ),
- ];
- },
- body: RefreshIndicator(
- onRefresh: () async {
- await weatherController.updateCacheCard(true);
- setState(() {});
- },
- child: WeatherCardList(searchCity: filter),
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/cards/widgets/create_card_weather.dart b/lib/app/modules/cards/widgets/create_card_weather.dart
deleted file mode 100644
index d2d4c97..0000000
--- a/lib/app/modules/cards/widgets/create_card_weather.dart
+++ /dev/null
@@ -1,304 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax_plus/iconsax_plus.dart';
-import 'package:rain/app/api/api.dart';
-import 'package:rain/app/api/city_api.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/widgets/button.dart';
-import 'package:rain/app/widgets/text_form.dart';
-import 'package:rain/main.dart';
-
-class CreateWeatherCard extends StatefulWidget {
- const CreateWeatherCard({
- super.key,
- this.latitude,
- this.longitude,
- });
- final String? latitude;
- final String? longitude;
-
- @override
- State createState() => _CreateWeatherCardState();
-}
-
-class _CreateWeatherCardState extends State
- with SingleTickerProviderStateMixin {
- bool isLoading = false;
- final formKey = GlobalKey();
- final _focusNode = FocusNode();
- final weatherController = Get.put(WeatherController());
- late final TextEditingController _controller = TextEditingController();
- late TextEditingController _controllerLat = TextEditingController();
- late TextEditingController _controllerLon = TextEditingController();
- late final TextEditingController _controllerCity = TextEditingController();
- late final TextEditingController _controllerDistrict =
- TextEditingController();
-
- late AnimationController _animationController;
- late Animation _animation;
-
- @override
- void initState() {
- super.initState();
- if (widget.latitude != null && widget.longitude != null) {
- _controllerLat = TextEditingController(text: widget.latitude);
- _controllerLon = TextEditingController(text: widget.longitude);
- }
- _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) {
- value.text = value.text.trim();
- while (value.text.contains(' ')) {
- value.text = value.text.replaceAll(' ', ' ');
- }
- }
-
- void fillController(Result selection) {
- _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) {
- const kTextFieldElevation = 4.0;
- bool showButton = _controllerLon.text.isNotEmpty &&
- _controllerLat.text.isNotEmpty &&
- _controllerCity.text.isNotEmpty &&
- _controllerDistrict.text.isNotEmpty;
-
- if (showButton) {
- _animationController.forward();
- } else {
- _animationController.reverse();
- }
-
- 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(
- padding: const EdgeInsets.only(top: 14, bottom: 7),
- child: Text(
- 'create'.tr,
- style: context.textTheme.titleLarge
- ?.copyWith(fontWeight: FontWeight.bold),
- textAlign: TextAlign.center,
- ),
- ),
- RawAutocomplete(
- focusNode: _focusNode,
- textEditingController: _controller,
- fieldViewBuilder: (BuildContext context,
- TextEditingController fieldTextEditingController,
- FocusNode fieldFocusNode,
- VoidCallback onFieldSubmitted) {
- return MyTextForm(
- elevation: kTextFieldElevation,
- labelText: 'search'.tr,
- type: TextInputType.text,
- icon: const Icon(IconsaxPlusLinear.global_search),
- controller: _controller,
- margin: const EdgeInsets.only(
- left: 10, right: 10, top: 10),
- focusNode: _focusNode,
- );
- },
- optionsBuilder: (TextEditingValue textEditingValue) {
- if (textEditingValue.text.isEmpty) {
- return const Iterable.empty();
- }
- return WeatherAPI()
- .getCity(textEditingValue.text, locale);
- },
- onSelected: (Result selection) =>
- fillController(selection),
- displayStringForOption: (Result option) =>
- '${option.name}, ${option.admin1}',
- optionsViewBuilder: (BuildContext context,
- AutocompleteOnSelected onSelected,
- Iterable 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) {
- final Result option =
- options.elementAt(index);
- return InkWell(
- onTap: () => onSelected(option),
- child: ListTile(
- title: Text(
- '${option.name}, ${option.admin1}',
- style: context.textTheme.labelLarge,
- ),
- ),
- );
- },
- ),
- ),
- ),
- );
- },
- ),
- MyTextForm(
- elevation: kTextFieldElevation,
- controller: _controllerLat,
- labelText: 'lat'.tr,
- type: TextInputType.number,
- icon: const Icon(IconsaxPlusLinear.location),
- onChanged: (value) => setState(() {}),
- margin:
- const EdgeInsets.only(left: 10, right: 10, top: 10),
- 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,
- icon: const Icon(IconsaxPlusLinear.location),
- onChanged: (value) => setState(() {}),
- margin:
- const EdgeInsets.only(left: 10, right: 10, top: 10),
- 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,
- icon: const Icon(IconsaxPlusLinear.building_3),
- onChanged: (value) => setState(() {}),
- margin:
- const EdgeInsets.only(left: 10, right: 10, top: 10),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return 'validateName'.tr;
- }
- return null;
- },
- ),
- MyTextForm(
- elevation: kTextFieldElevation,
- controller: _controllerDistrict,
- labelText: 'district'.tr,
- type: TextInputType.streetAddress,
- icon: const Icon(IconsaxPlusLinear.global),
- onChanged: (value) => setState(() {}),
- margin:
- const EdgeInsets.only(left: 10, right: 10, top: 10),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return 'validateName'.tr;
- }
- return null;
- },
- ),
- Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 10, vertical: 10),
- child: SizeTransition(
- sizeFactor: _animation,
- axisAlignment: -1.0,
- 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();
- }
- },
- ),
- ),
- ),
- ],
- ),
- ),
- if (isLoading)
- const Center(
- child: CircularProgressIndicator(),
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/cards/widgets/weather_card_container.dart b/lib/app/modules/cards/widgets/weather_card_container.dart
deleted file mode 100644
index 7c977b4..0000000
--- a/lib/app/modules/cards/widgets/weather_card_container.dart
+++ /dev/null
@@ -1,125 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:gap/gap.dart';
-import 'package:get/get.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/widgets/status/status_weather.dart';
-import 'package:rain/app/widgets/status/status_data.dart';
-import 'package:timezone/standalone.dart' as tz;
-
-class WeatherCardContainer extends StatefulWidget {
- const WeatherCardContainer({
- super.key,
- required this.time,
- required this.weather,
- required this.degree,
- required this.district,
- required this.city,
- required this.timezone,
- required this.timeDay,
- required this.timeNight,
- required this.timeDaily,
- });
- final List time;
- final List timeDay;
- final List timeNight;
- final List timeDaily;
- final String district;
- final String city;
- final List weather;
- final List degree;
- final String timezone;
-
- @override
- State createState() => _WeatherCardContainerState();
-}
-
-class _WeatherCardContainerState extends State {
- final statusWeather = StatusWeather();
- final statusData = StatusData();
- final weatherController = Get.put(WeatherController());
-
- @override
- Widget build(BuildContext context) {
- return Card(
- margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
- child: Padding(
- padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
- child: Row(
- children: [
- Expanded(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- Text(
- statusData.getDegree(widget.degree[weatherController
- .getTime(widget.time, widget.timezone)]
- .round()
- .toInt()),
- style: context.textTheme.titleLarge?.copyWith(
- fontSize: 22,
- fontWeight: FontWeight.w600,
- ),
- ),
- const Gap(7),
- Text(
- statusWeather.getText(widget.weather[weatherController
- .getTime(widget.time, widget.timezone)]),
- style: context.textTheme.titleMedium?.copyWith(
- color: Colors.grey,
- fontWeight: FontWeight.w400,
- ),
- ),
- ],
- ),
- const Gap(10),
- Text(
- widget.district.isEmpty
- ? widget.city
- : widget.city.isEmpty
- ? widget.district
- : widget.city == widget.district
- ? widget.city
- : '${widget.city}'
- ', ${widget.district}',
- style: context.textTheme.titleMedium?.copyWith(
- fontWeight: FontWeight.w500,
- ),
- ),
- const Gap(5),
- StreamBuilder(
- stream: Stream.periodic(const Duration(seconds: 1)),
- builder: (context, snapshot) {
- return Text(
- '${'time'.tr}: ${statusData.getTimeFormatTz(tz.TZDateTime.now(tz.getLocation(widget.timezone)))}',
- style: context.textTheme.titleMedium?.copyWith(
- color: Colors.grey,
- fontWeight: FontWeight.w400,
- ),
- );
- },
- ),
- ],
- ),
- ),
- const Gap(5),
- Image.asset(
- statusWeather.getImageNow(
- widget.weather[
- weatherController.getTime(widget.time, widget.timezone)],
- widget.time[
- weatherController.getTime(widget.time, widget.timezone)],
- widget.timeDay[weatherController.getDay(
- widget.timeDaily, widget.timezone)],
- widget.timeNight[weatherController.getDay(
- widget.timeDaily, widget.timezone)]),
- scale: 6.5,
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/cards/widgets/weather_card_list.dart b/lib/app/modules/cards/widgets/weather_card_list.dart
deleted file mode 100644
index a0e0b88..0000000
--- a/lib/app/modules/cards/widgets/weather_card_list.dart
+++ /dev/null
@@ -1,115 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax_plus/iconsax_plus.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/modules/cards/view/info_weather_card.dart';
-import 'package:rain/app/modules/cards/widgets/weather_card_container.dart';
-
-class WeatherCardList extends StatefulWidget {
- const WeatherCardList({
- super.key,
- required this.searchCity,
- });
- final String searchCity;
-
- @override
- State createState() => _WeatherCardListState();
-}
-
-class _WeatherCardListState extends State {
- final weatherController = Get.put(WeatherController());
-
- @override
- Widget build(BuildContext context) {
- final textTheme = context.textTheme;
- final titleMedium = textTheme.titleMedium;
-
- var weatherCards = weatherController.weatherCards
- .where((weatherCard) => (widget.searchCity.isEmpty ||
- weatherCard.city!.toLowerCase().contains(widget.searchCity)))
- .toList()
- .obs;
-
- return ReorderableListView(
- onReorder: (oldIndex, newIndex) =>
- weatherController.reorder(oldIndex, newIndex),
- children: [
- ...weatherCards.map(
- (weatherCardList) => Dismissible(
- key: ValueKey(weatherCardList),
- direction: DismissDirection.endToStart,
- background: Container(
- alignment: Alignment.centerRight,
- child: const Padding(
- padding: EdgeInsets.only(right: 15),
- child: Icon(
- IconsaxPlusLinear.trash_square,
- color: Colors.red,
- ),
- ),
- ),
- confirmDismiss: (DismissDirection direction) async {
- return await showAdaptiveDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog.adaptive(
- title: Text(
- 'deletedCardWeather'.tr,
- style: textTheme.titleLarge,
- ),
- content: Text(
- 'deletedCardWeatherQuery'.tr,
- style: titleMedium,
- ),
- actions: [
- TextButton(
- onPressed: () => Get.back(result: false),
- child: Text(
- 'cancel'.tr,
- style: titleMedium?.copyWith(
- color: Colors.blueAccent,
- ),
- ),
- ),
- TextButton(
- onPressed: () => Get.back(result: true),
- child: Text(
- 'delete'.tr,
- style: titleMedium?.copyWith(
- color: Colors.red,
- ),
- ),
- ),
- ],
- );
- },
- );
- },
- onDismissed: (DismissDirection direction) async {
- await weatherController.deleteCardWeather(weatherCardList);
- },
- child: GestureDetector(
- onTap: () => Get.to(
- () => InfoWeatherCard(
- weatherCard: weatherCardList,
- ),
- transition: Transition.downToUp,
- ),
- child: WeatherCardContainer(
- time: weatherCardList.time!,
- timeDaily: weatherCardList.timeDaily!,
- timeDay: weatherCardList.sunrise!,
- timeNight: weatherCardList.sunset!,
- weather: weatherCardList.weathercode!,
- degree: weatherCardList.temperature2M!,
- district: weatherCardList.district!,
- city: weatherCardList.city!,
- timezone: weatherCardList.timezone!,
- ),
- ),
- ),
- ),
- ],
- );
- }
-}
diff --git a/lib/app/modules/geolocation.dart b/lib/app/modules/geolocation.dart
deleted file mode 100644
index 6e3dc67..0000000
--- a/lib/app/modules/geolocation.dart
+++ /dev/null
@@ -1,495 +0,0 @@
-import 'dart:ui';
-import 'package:flutter/material.dart';
-import 'package:flutter_map/flutter_map.dart';
-import 'package:gap/gap.dart';
-import 'package:geolocator/geolocator.dart';
-import 'package:get/get.dart';
-import 'package:iconsax_plus/iconsax_plus.dart';
-import 'package:latlong2/latlong.dart';
-import 'package:rain/app/api/api.dart';
-import 'package:rain/app/api/city_api.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/modules/home.dart';
-import 'package:rain/app/widgets/button.dart';
-import 'package:rain/app/widgets/text_form.dart';
-import 'package:rain/main.dart';
-
-class SelectGeolocation extends StatefulWidget {
- const SelectGeolocation({
- super.key,
- required this.isStart,
- });
- final bool isStart;
-
- @override
- State createState() => _SelectGeolocationState();
-}
-
-class _SelectGeolocationState extends State {
- bool isLoading = false;
- final formKeySearch = GlobalKey();
- final _focusNode = FocusNode();
- final weatherController = Get.put(WeatherController());
- final _controller = TextEditingController();
- final _controllerLat = TextEditingController();
- final _controllerLon = TextEditingController();
- final _controllerCity = TextEditingController();
- final _controllerDistrict = TextEditingController();
-
- static const colorFilter = ColorFilter.matrix([
- -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();
-
- 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(() {});
- }
-
- void fillMap(double latitude, double longitude) {
- _controllerLat.text = '$latitude';
- _controllerLon.text = '$longitude';
- setState(() {});
- }
-
- Widget _buildMapTileLayer() {
- return TileLayer(
- urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
- userAgentPackageName: 'com.darkmoonight.rain',
- );
- }
-
- @override
- Widget build(BuildContext context) {
- const kTextFieldElevation = 4.0;
- return Form(
- key: formKeySearch,
- child: Scaffold(
- resizeToAvoidBottomInset: true,
- appBar: AppBar(
- centerTitle: true,
- leading: widget.isStart
- ? null
- : IconButton(
- onPressed: () {
- Get.back();
- },
- icon: const Icon(
- IconsaxPlusLinear.arrow_left_3,
- size: 20,
- ),
- splashColor: Colors.transparent,
- highlightColor: Colors.transparent,
- ),
- 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(
- horizontal: 10),
- 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,
- cameraConstraint:
- CameraConstraint.contain(
- bounds: LatLngBounds(
- const LatLng(-90, -180),
- const LatLng(90, 180),
- ),
- ),
- onLongPress: (tapPosition, point) =>
- fillMap(point.latitude,
- point.longitude),
- ),
- children: [
- if (_isDarkMode)
- ColorFiltered(
- colorFilter: colorFilter,
- child: _buildMapTileLayer(),
- )
- else
- _buildMapTileLayer(),
- RichAttributionWidget(
- animationConfig: const ScaleRAWA(),
- attributions: [
- TextSourceAttribution(
- 'OpenStreetMap contributors',
- onTap: () => weatherController
- .urlLauncher(
- 'https://openstreetmap.org/copyright'),
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- ),
- Padding(
- padding:
- const EdgeInsets.fromLTRB(10, 15, 10, 5),
- child: Text(
- 'searchMethod'.tr,
- style: context.theme.textTheme.bodyLarge
- ?.copyWith(fontWeight: FontWeight.bold),
- textAlign: TextAlign.center,
- ),
- ),
- Row(
- children: [
- Flexible(
- child: RawAutocomplete(
- focusNode: _focusNode,
- textEditingController: _controller,
- fieldViewBuilder: (BuildContext context,
- TextEditingController
- fieldTextEditingController,
- FocusNode fieldFocusNode,
- VoidCallback onFieldSubmitted) {
- return MyTextForm(
- elevation: kTextFieldElevation,
- labelText: 'search'.tr,
- type: TextInputType.text,
- icon: const Icon(IconsaxPlusLinear
- .global_search),
- controller: _controller,
- margin: const EdgeInsets.only(
- left: 10, right: 10, top: 10),
- focusNode: _focusNode,
- );
- },
- optionsBuilder: (TextEditingValue
- textEditingValue) {
- if (textEditingValue.text.isEmpty) {
- return const Iterable<
- Result>.empty();
- }
- return WeatherAPI().getCity(
- textEditingValue.text, locale);
- },
- onSelected: (Result selection) =>
- fillController(selection),
- displayStringForOption: (Result
- option) =>
- '${option.name}, ${option.admin1}',
- optionsViewBuilder:
- (BuildContext context,
- AutocompleteOnSelected
- onSelected,
- Iterable options) {
- return Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 10,
- vertical: 5,
- ),
- 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) {
- final Result option =
- options
- .elementAt(index);
- return InkWell(
- onTap: () =>
- onSelected(option),
- child: ListTile(
- title: Text(
- '${option.name}, ${option.admin1}',
- style: context
- .textTheme
- .labelLarge,
- ),
- ),
- );
- },
- ),
- ),
- ),
- );
- },
- ),
- ),
- Card(
- elevation: kTextFieldElevation,
- margin: const EdgeInsets.only(
- top: 10,
- right: 10,
- ),
- child: Container(
- margin: const EdgeInsets.all(2.5),
- child: IconButton(
- onPressed: () async {
- bool serviceEnabled =
- await Geolocator
- .isLocationServiceEnabled();
- if (!serviceEnabled) {
- if (!context.mounted) return;
- await showAdaptiveDialog(
- context: context,
- builder:
- (BuildContext context) {
- return AlertDialog.adaptive(
- title: Text(
- 'location'.tr,
- style: context
- .textTheme.titleLarge,
- ),
- content: Text(
- 'no_location'.tr,
- style: context.textTheme
- .titleMedium,
- ),
- actions: [
- TextButton(
- onPressed: () =>
- Get.back(
- result: false),
- child: Text(
- 'cancel'.tr,
- style: context
- .textTheme
- .titleMedium
- ?.copyWith(
- color: Colors
- .blueAccent,
- ),
- ),
- ),
- TextButton(
- onPressed: () {
- Geolocator
- .openLocationSettings();
- Get.back(
- result: true);
- },
- child: Text(
- 'settings'.tr,
- style: context
- .textTheme
- .titleMedium
- ?.copyWith(
- color: Colors
- .green),
- ),
- ),
- ],
- );
- },
- );
- return;
- }
- setState(() => isLoading = true);
- final location =
- await weatherController
- .getCurrentLocationSearch();
- fillControllerGeo(location);
- setState(() => isLoading = false);
- },
- icon: const Icon(
- IconsaxPlusLinear.location,
- ),
- ),
- ),
- ),
- ],
- ),
- MyTextForm(
- elevation: kTextFieldElevation,
- controller: _controllerLat,
- labelText: 'lat'.tr,
- type: TextInputType.number,
- icon: const Icon(IconsaxPlusLinear.location),
- margin: const EdgeInsets.only(
- left: 10,
- right: 10,
- top: 10,
- ),
- 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,
- icon: const Icon(IconsaxPlusLinear.location),
- margin: const EdgeInsets.only(
- left: 10,
- right: 10,
- top: 10,
- ),
- 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,
- icon:
- const Icon(IconsaxPlusLinear.building_3),
- margin: const EdgeInsets.only(
- left: 10, right: 10, top: 10),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return 'validateName'.tr;
- }
- return null;
- },
- ),
- MyTextForm(
- elevation: kTextFieldElevation,
- controller: _controllerDistrict,
- labelText: 'district'.tr,
- type: TextInputType.streetAddress,
- icon: const Icon(IconsaxPlusLinear.global),
- margin: const EdgeInsets.only(
- left: 10, right: 10, top: 10),
- ),
- const Gap(20),
- ],
- ),
- ),
- ),
- ],
- ),
- ),
- Padding(
- padding:
- const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
- 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,
- );
- widget.isStart
- ? Get.off(() => const HomePage(),
- transition: Transition.downToUp)
- : Get.back();
- } catch (error) {
- Future.error(error);
- }
- }
- },
- ),
- ),
- ],
- ),
- if (isLoading)
- BackdropFilter(
- filter: ImageFilter.blur(sigmaY: 3, sigmaX: 3),
- child: const Center(
- child: CircularProgressIndicator(),
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/home.dart b/lib/app/modules/home.dart
deleted file mode 100644
index 2d97edc..0000000
--- a/lib/app/modules/home.dart
+++ /dev/null
@@ -1,286 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax_plus/iconsax_plus.dart';
-import 'package:isar/isar.dart';
-import 'package:rain/app/api/api.dart';
-import 'package:rain/app/api/city_api.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/data/weather.dart';
-import 'package:rain/app/modules/cards/view/list_weather_card.dart';
-import 'package:rain/app/modules/cards/widgets/create_card_weather.dart';
-import 'package:rain/app/modules/geolocation.dart';
-import 'package:rain/app/modules/main/view/weather_main.dart';
-import 'package:rain/app/modules/map/view/map.dart';
-import 'package:rain/app/modules/settings/view/settings.dart';
-import 'package:rain/app/services/utils.dart';
-import 'package:rain/main.dart';
-
-class HomePage extends StatefulWidget {
- const HomePage({super.key});
-
- @override
- State createState() => _HomePageState();
-}
-
-class _HomePageState extends State with TickerProviderStateMixin {
- int tabIndex = 0;
- bool visible = false;
- final _focusNode = FocusNode();
- late TabController tabController;
- final weatherController = Get.put(WeatherController());
- final _controller = TextEditingController();
-
- final pages = [
- const WeatherPage(),
- const ListWeatherCard(),
- const MapWeather(),
- const SettingsPage(),
- ];
-
- @override
- void initState() {
- getData();
- tabController = TabController(
- initialIndex: tabIndex,
- length: pages.length,
- vsync: this,
- );
- tabController.animation?.addListener(() {
- int value = (tabController.animation!.value).round();
- if (value != tabIndex) setState(() => tabIndex = value);
- });
- tabController.addListener(() {
- setState(() {
- tabIndex = tabController.index;
- });
- });
- super.initState();
- }
-
- void getData() async {
- await weatherController.deleteCache();
- await weatherController.updateCacheCard(false);
- await weatherController.setLocation();
- }
-
- void changeTabIndex(int index) {
- setState(() {
- tabIndex = index;
- });
- tabController.animateTo(tabIndex);
- }
-
- @override
- Widget build(BuildContext context) {
- final textTheme = context.textTheme;
- final labelLarge = textTheme.labelLarge;
-
- final textStyle = textTheme.titleMedium?.copyWith(
- fontWeight: FontWeight.w600,
- fontSize: 18,
- );
-
- return DefaultTabController(
- length: pages.length,
- child: ScaffoldMessenger(
- key: globalKey,
- child: Scaffold(
- appBar: AppBar(
- centerTitle: true,
- automaticallyImplyLeading: false,
- leading: switch (tabIndex) {
- 0 => IconButton(
- onPressed: () {
- Get.to(() => const SelectGeolocation(isStart: false),
- transition: Transition.downToUp);
- },
- icon: const Icon(
- IconsaxPlusLinear.global_search,
- size: 18,
- ),
- ),
- int() => null,
- },
- title: switch (tabIndex) {
- 0 => visible
- ? RawAutocomplete(
- focusNode: _focusNode,
- textEditingController: _controller,
- fieldViewBuilder: (_, __, ___, ____) {
- return TextField(
- controller: _controller,
- focusNode: _focusNode,
- style: labelLarge?.copyWith(fontSize: 16),
- decoration: InputDecoration(
- hintText: 'search'.tr,
- ),
- );
- },
- optionsBuilder: (TextEditingValue textEditingValue) {
- if (textEditingValue.text.isEmpty) {
- return const Iterable.empty();
- }
- return WeatherAPI()
- .getCity(textEditingValue.text, locale);
- },
- onSelected: (Result selection) async {
- await weatherController.deleteAll(true);
- await weatherController.getLocation(
- double.parse('${selection.latitude}'),
- double.parse('${selection.longitude}'),
- selection.admin1,
- selection.name,
- );
- visible = false;
- _controller.clear();
- _focusNode.unfocus();
- setState(() {});
- },
- displayStringForOption: (Result option) =>
- '${option.name}, ${option.admin1}',
- optionsViewBuilder: (BuildContext context,
- AutocompleteOnSelected onSelected,
- Iterable options) {
- return Align(
- alignment: Alignment.topLeft,
- child: Material(
- borderRadius: BorderRadius.circular(20),
- elevation: 4.0,
- child: SizedBox(
- width: 250,
- child: ListView.builder(
- padding: EdgeInsets.zero,
- shrinkWrap: true,
- itemCount: options.length,
- itemBuilder: (BuildContext context, int index) {
- final Result option =
- options.elementAt(index);
- return InkWell(
- onTap: () => onSelected(option),
- child: ListTile(
- title: Text(
- '${option.name}, ${option.admin1}',
- style: labelLarge,
- ),
- ),
- );
- },
- ),
- ),
- ),
- );
- },
- )
- : Obx(
- () {
- final location = weatherController.location;
- final city = location.city;
- final district = location.district;
- return Text(
- weatherController.isLoading.isFalse
- ? district!.isEmpty
- ? '$city'
- : city!.isEmpty
- ? district
- : city == district
- ? city
- : '$city' ', $district'
- : settings.location
- ? 'search'.tr
- : (isar.locationCaches.where().findAllSync())
- .isNotEmpty
- ? 'loading'.tr
- : 'searchCity'.tr,
- style: textStyle,
- );
- },
- ),
- 1 => Text(
- 'cities'.tr,
- style: textStyle,
- ),
- 2 => Text(
- 'map'.tr,
- style: textStyle,
- ),
- 3 => Text(
- 'settings_full'.tr,
- style: textStyle,
- ),
- int() => null,
- },
- actions: switch (tabIndex) {
- 0 => [
- IconButton(
- onPressed: () {
- if (visible) {
- _controller.clear();
- _focusNode.unfocus();
- visible = false;
- } else {
- visible = true;
- }
- setState(() {});
- },
- icon: Icon(
- visible
- ? IconsaxPlusLinear.close_circle
- : IconsaxPlusLinear.search_normal_1,
- size: 18,
- ),
- )
- ],
- int() => null,
- },
- ),
- body: SafeArea(
- child: TabBarView(
- controller: tabController,
- children: pages,
- ),
- ),
- bottomNavigationBar: NavigationBar(
- onDestinationSelected: (int index) => changeTabIndex(index),
- selectedIndex: tabIndex,
- destinations: [
- NavigationDestination(
- icon: const Icon(IconsaxPlusLinear.cloud_sunny),
- selectedIcon: const Icon(IconsaxPlusBold.cloud_sunny),
- label: 'name'.tr,
- ),
- NavigationDestination(
- icon: const Icon(IconsaxPlusLinear.buildings),
- selectedIcon: const Icon(IconsaxPlusBold.buildings),
- label: 'cities'.tr,
- ),
- NavigationDestination(
- icon: const Icon(IconsaxPlusLinear.map),
- selectedIcon: const Icon(IconsaxPlusBold.map),
- label: 'map'.tr,
- ),
- NavigationDestination(
- icon: const Icon(IconsaxPlusLinear.category),
- selectedIcon: const Icon(IconsaxPlusBold.category),
- label: 'settings_full'.tr,
- ),
- ],
- ),
- floatingActionButton: tabIndex == 1
- ? FloatingActionButton(
- onPressed: () => showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- enableDrag: false,
- builder: (BuildContext context) =>
- const CreateWeatherCard(),
- ),
- child: const Icon(
- IconsaxPlusLinear.add,
- ),
- )
- : null,
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/main/view/weather_main.dart b/lib/app/modules/main/view/weather_main.dart
deleted file mode 100644
index 4cba272..0000000
--- a/lib/app/modules/main/view/weather_main.dart
+++ /dev/null
@@ -1,178 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/data/weather.dart';
-import 'package:rain/app/widgets/daily/weather_daily.dart';
-import 'package:rain/app/widgets/daily/weather_more.dart';
-import 'package:rain/app/widgets/desc/desc_container.dart';
-import 'package:rain/app/widgets/hourly/weather_hourly.dart';
-import 'package:rain/app/widgets/now/weather_now.dart';
-import 'package:rain/app/widgets/shimmer.dart';
-import 'package:rain/app/widgets/sun_moon/sunset_sunrise.dart';
-import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
-
-class WeatherPage extends StatefulWidget {
- const WeatherPage({super.key});
-
- @override
- State createState() => _WeatherPageState();
-}
-
-class _WeatherPageState extends State {
- final weatherController = Get.put(WeatherController());
-
- @override
- Widget build(BuildContext context) {
- return RefreshIndicator(
- onRefresh: () async {
- await weatherController.deleteAll(false);
- await weatherController.setLocation();
- setState(() {});
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 10),
- child: Obx(() {
- if (weatherController.isLoading.isTrue) {
- return ListView(
- children: const [
- MyShimmer(
- hight: 200,
- ),
- MyShimmer(
- hight: 130,
- edgeInsetsMargin: EdgeInsets.symmetric(vertical: 15),
- ),
- MyShimmer(
- hight: 90,
- edgeInsetsMargin: EdgeInsets.only(bottom: 15),
- ),
- MyShimmer(
- hight: 400,
- edgeInsetsMargin: EdgeInsets.only(bottom: 15),
- ),
- MyShimmer(
- hight: 450,
- edgeInsetsMargin: EdgeInsets.only(bottom: 15),
- )
- ],
- );
- }
-
- final mainWeather = weatherController.mainWeather;
- final weatherCard = WeatherCard.fromJson(mainWeather.toJson());
- final hourOfDay = weatherController.hourOfDay.value;
- final dayOfNow = weatherController.dayOfNow.value;
- final sunrise = mainWeather.sunrise![dayOfNow];
- final sunset = mainWeather.sunset![dayOfNow];
- final tempMax = mainWeather.temperature2MMax![dayOfNow];
- final tempMin = mainWeather.temperature2MMin![dayOfNow];
-
- return ListView(
- children: [
- WeatherNow(
- time: mainWeather.time![hourOfDay],
- weather: mainWeather.weathercode![hourOfDay],
- degree: mainWeather.temperature2M![hourOfDay],
- feels: mainWeather.apparentTemperature![hourOfDay]!,
- timeDay: sunrise,
- timeNight: sunset,
- tempMax: tempMax!,
- tempMin: tempMin!,
- ),
- Card(
- margin: const EdgeInsets.only(bottom: 15),
- child: Padding(
- padding:
- const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
- child: SizedBox(
- height: 135,
- child: ScrollablePositionedList.separated(
- key: const PageStorageKey(0),
- separatorBuilder: (BuildContext context, int index) {
- return const VerticalDivider(
- width: 10,
- indent: 40,
- endIndent: 40,
- );
- },
- scrollDirection: Axis.horizontal,
- itemScrollController:
- weatherController.itemScrollController,
- itemCount: mainWeather.time!.length,
- itemBuilder: (ctx, i) {
- final i24 = (i / 24).floor();
-
- return GestureDetector(
- onTap: () {
- weatherController.hourOfDay.value = i;
- weatherController.dayOfNow.value = i24;
- setState(() {});
- },
- child: Container(
- margin: const EdgeInsets.symmetric(vertical: 5),
- padding: const EdgeInsets.symmetric(
- horizontal: 20,
- vertical: 5,
- ),
- decoration: BoxDecoration(
- color: i == hourOfDay
- ? context.theme.colorScheme.secondaryContainer
- : Colors.transparent,
- borderRadius: const BorderRadius.all(
- Radius.circular(20),
- ),
- ),
- child: WeatherHourly(
- time: mainWeather.time![i],
- weather: mainWeather.weathercode![i],
- degree: mainWeather.temperature2M![i],
- timeDay: mainWeather.sunrise![i24],
- timeNight: mainWeather.sunset![i24],
- ),
- ),
- );
- },
- ),
- ),
- ),
- ),
- SunsetSunrise(
- timeSunrise: sunrise,
- timeSunset: sunset,
- ),
- DescContainer(
- humidity: mainWeather.relativehumidity2M?[hourOfDay],
- wind: mainWeather.windspeed10M?[hourOfDay],
- visibility: mainWeather.visibility?[hourOfDay],
- feels: mainWeather.apparentTemperature?[hourOfDay],
- evaporation: mainWeather.evapotranspiration?[hourOfDay],
- precipitation: mainWeather.precipitation?[hourOfDay],
- direction: mainWeather.winddirection10M?[hourOfDay],
- pressure: mainWeather.surfacePressure?[hourOfDay],
- rain: mainWeather.rain?[hourOfDay],
- cloudcover: mainWeather.cloudcover?[hourOfDay],
- windgusts: mainWeather.windgusts10M?[hourOfDay],
- uvIndex: mainWeather.uvIndex?[hourOfDay],
- dewpoint2M: mainWeather.dewpoint2M?[hourOfDay],
- precipitationProbability:
- mainWeather.precipitationProbability?[hourOfDay],
- shortwaveRadiation: mainWeather.shortwaveRadiation?[hourOfDay],
- initiallyExpanded: false,
- title: 'hourlyVariables'.tr,
- ),
- WeatherDaily(
- weatherData: weatherCard,
- onTap: () => Get.to(
- () => WeatherMore(
- weatherData: weatherCard,
- ),
- transition: Transition.downToUp,
- ),
- )
- ],
- );
- }),
- ),
- );
- }
-}
diff --git a/lib/app/modules/settings/view/settings.dart b/lib/app/modules/settings/view/settings.dart
deleted file mode 100644
index d22f027..0000000
--- a/lib/app/modules/settings/view/settings.dart
+++ /dev/null
@@ -1,1118 +0,0 @@
-import 'dart:io';
-import 'package:dio_cache_interceptor_file_store/dio_cache_interceptor_file_store.dart';
-import 'package:flutter/material.dart';
-import 'package:flutter_hsvcolor_picker/flutter_hsvcolor_picker.dart';
-import 'package:flutter_local_notifications/flutter_local_notifications.dart';
-import 'package:gap/gap.dart';
-import 'package:geolocator/geolocator.dart';
-import 'package:get/get.dart';
-import 'package:iconsax_plus/iconsax_plus.dart';
-import 'package:intl/intl.dart';
-import 'package:line_awesome_flutter/line_awesome_flutter.dart';
-import 'package:package_info_plus/package_info_plus.dart';
-import 'package:path_provider/path_provider.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/data/weather.dart';
-import 'package:rain/app/modules/settings/widgets/setting_card.dart';
-import 'package:rain/main.dart';
-import 'package:rain/theme/theme_controller.dart';
-import 'package:rain/utils/color_converter.dart';
-
-class SettingsPage extends StatefulWidget {
- const SettingsPage({super.key});
-
- @override
- State createState() => _SettingsPageState();
-}
-
-class _SettingsPageState extends State {
- final themeController = Get.put(ThemeController());
- final weatherController = Get.put(WeatherController());
- String? appVersion;
- String? colorBackground;
- String? colorText;
-
- Future infoVersion() async {
- final packageInfo = await PackageInfo.fromPlatform();
- setState(() {
- appVersion = packageInfo.version;
- });
- }
-
- @override
- void initState() {
- infoVersion();
- super.initState();
- }
-
- updateLanguage(Locale locale) {
- settings.language = '$locale';
- isar.writeTxnSync(() => isar.settings.putSync(settings));
- Get.updateLocale(locale);
- Get.back();
- }
-
- @override
- Widget build(BuildContext context) {
- return SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- children: [
- SettingCard(
- icon: const Icon(IconsaxPlusLinear.brush_1),
- text: 'appearance'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).padding.bottom),
- child: StatefulBuilder(
- builder: (BuildContext context, setState) {
- return SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisSize: MainAxisSize.min,
- children: [
- Padding(
- padding:
- const EdgeInsets.symmetric(vertical: 15),
- child: Text(
- 'appearance'.tr,
- style: context.textTheme.titleLarge?.copyWith(
- fontSize: 20,
- ),
- ),
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.moon),
- text: 'theme'.tr,
- dropdown: true,
- dropdownName: settings.theme?.tr,
- dropdownList: [
- 'system'.tr,
- 'dark'.tr,
- 'light'.tr
- ],
- dropdownCange: (String? newValue) {
- final newThemeMode = newValue?.tr;
- final darkTheme = 'dark'.tr;
- final systemTheme = 'system'.tr;
- ThemeMode themeMode =
- newThemeMode == systemTheme
- ? ThemeMode.system
- : newThemeMode == darkTheme
- ? ThemeMode.dark
- : ThemeMode.light;
- String theme = newThemeMode == systemTheme
- ? 'system'
- : newThemeMode == darkTheme
- ? 'dark'
- : 'light';
- themeController.saveTheme(theme);
- themeController.changeThemeMode(themeMode);
- setState(() {});
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.mobile),
- text: 'amoledTheme'.tr,
- switcher: true,
- value: settings.amoledTheme,
- onChange: (value) {
- themeController.saveOledTheme(value);
- MyApp.updateAppState(context,
- newAmoledTheme: value);
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.colorfilter),
- text: 'materialColor'.tr,
- switcher: true,
- value: settings.materialColor,
- onChange: (value) {
- themeController.saveMaterialTheme(value);
- MyApp.updateAppState(context,
- newMaterialColor: value);
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.additem),
- text: 'largeElement'.tr,
- switcher: true,
- value: settings.largeElement,
- onChange: (value) {
- settings.largeElement = value;
- isar.writeTxnSync(
- () => isar.settings.putSync(settings),
- );
- MyApp.updateAppState(
- context,
- newLargeElement: value,
- );
- setState(() {});
- },
- ),
- const Gap(10),
- ],
- ),
- );
- },
- ),
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(IconsaxPlusLinear.code_1),
- text: 'functions'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).padding.bottom),
- child: StatefulBuilder(
- builder: (BuildContext context, setState) {
- return SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisSize: MainAxisSize.min,
- children: [
- Padding(
- padding:
- const EdgeInsets.symmetric(vertical: 15),
- child: Text(
- 'functions'.tr,
- style: context.textTheme.titleLarge?.copyWith(
- fontSize: 20,
- ),
- ),
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.map),
- text: 'location'.tr,
- switcher: true,
- value: settings.location,
- onChange: (value) async {
- if (value) {
- bool serviceEnabled = await Geolocator
- .isLocationServiceEnabled();
- if (!serviceEnabled) {
- if (!context.mounted) return;
- await showAdaptiveDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog.adaptive(
- title: Text(
- 'location'.tr,
- style:
- context.textTheme.titleLarge,
- ),
- content: Text('no_location'.tr,
- style: context
- .textTheme.titleMedium),
- actions: [
- TextButton(
- onPressed: () =>
- Get.back(result: false),
- child: Text(
- 'cancel'.tr,
- style: context
- .textTheme.titleMedium
- ?.copyWith(
- color: Colors
- .blueAccent),
- ),
- ),
- TextButton(
- onPressed: () {
- Geolocator
- .openLocationSettings();
- Get.back(result: true);
- },
- child: Text(
- 'settings'.tr,
- style: context
- .textTheme.titleMedium
- ?.copyWith(
- color: Colors.green),
- ),
- ),
- ],
- );
- },
- );
-
- return;
- }
- weatherController.getCurrentLocation();
- }
- isar.writeTxnSync(() {
- settings.location = value;
- isar.settings.putSync(settings);
- });
- setState(() {});
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(
- IconsaxPlusLinear.notification_1),
- text: 'notifications'.tr,
- switcher: true,
- value: settings.notifications,
- onChange: (value) async {
- final resultExact =
- await flutterLocalNotificationsPlugin
- .resolvePlatformSpecificImplementation<
- AndroidFlutterLocalNotificationsPlugin>()
- ?.requestExactAlarmsPermission();
- final result = Platform.isIOS
- ? await flutterLocalNotificationsPlugin
- .resolvePlatformSpecificImplementation<
- IOSFlutterLocalNotificationsPlugin>()
- ?.requestPermissions()
- : await flutterLocalNotificationsPlugin
- .resolvePlatformSpecificImplementation<
- AndroidFlutterLocalNotificationsPlugin>()
- ?.requestNotificationsPermission();
- if (result != null && resultExact != null) {
- isar.writeTxnSync(() {
- settings.notifications = value;
- isar.settings.putSync(settings);
- });
- if (value) {
- weatherController.notification(
- weatherController.mainWeather);
- } else {
- flutterLocalNotificationsPlugin
- .cancelAll();
- }
- setState(() {});
- }
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(
- IconsaxPlusLinear.notification_status),
- text: 'timeRange'.tr,
- dropdown: true,
- dropdownName: '$timeRange',
- dropdownList: const [
- '1',
- '2',
- '3',
- '4',
- '5',
- ],
- dropdownCange: (String? newValue) {
- isar.writeTxnSync(() {
- settings.timeRange = int.parse(newValue!);
- isar.settings.putSync(settings);
- });
- MyApp.updateAppState(context,
- newTimeRange: int.parse(newValue!));
- if (settings.notifications) {
- flutterLocalNotificationsPlugin.cancelAll();
- weatherController.notification(
- weatherController.mainWeather);
- }
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.timer_start),
- text: 'timeStart'.tr,
- info: true,
- infoSettings: true,
- infoWidget: _TextInfo(
- info: settings.timeformat == '12'
- ? DateFormat.jm(locale.languageCode)
- .format(
- DateFormat.Hm(locale.languageCode)
- .parse(weatherController
- .timeConvert(timeStart)
- .format(context)))
- : DateFormat.Hm(locale.languageCode)
- .format(
- DateFormat.Hm(locale.languageCode)
- .parse(weatherController
- .timeConvert(timeStart)
- .format(context))),
- ),
- onPressed: () async {
- final TimeOfDay? timeStartPicker =
- await showTimePicker(
- context: context,
- initialTime: weatherController
- .timeConvert(timeStart),
- builder: (context, child) {
- final Widget mediaQueryWrapper =
- MediaQuery(
- data: MediaQuery.of(context).copyWith(
- alwaysUse24HourFormat:
- settings.timeformat == '12'
- ? false
- : true,
- ),
- child: child!,
- );
- return mediaQueryWrapper;
- },
- );
- if (timeStartPicker != null) {
- isar.writeTxnSync(() {
- settings.timeStart =
- timeStartPicker.format(context);
- isar.settings.putSync(settings);
- });
- if (!context.mounted) return;
- MyApp.updateAppState(context,
- newTimeStart:
- timeStartPicker.format(context));
- if (settings.notifications) {
- flutterLocalNotificationsPlugin
- .cancelAll();
- weatherController.notification(
- weatherController.mainWeather);
- }
- }
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.timer_pause),
- text: 'timeEnd'.tr,
- info: true,
- infoSettings: true,
- infoWidget: _TextInfo(
- info: settings.timeformat == '12'
- ? DateFormat.jm(locale.languageCode)
- .format(
- DateFormat.Hm(locale.languageCode)
- .parse(weatherController
- .timeConvert(timeEnd)
- .format(context)))
- : DateFormat.Hm(locale.languageCode)
- .format(
- DateFormat.Hm(locale.languageCode)
- .parse(weatherController
- .timeConvert(timeEnd)
- .format(context))),
- ),
- onPressed: () async {
- final TimeOfDay? timeEndPicker =
- await showTimePicker(
- context: context,
- initialTime:
- weatherController.timeConvert(timeEnd),
- builder: (context, child) {
- final Widget mediaQueryWrapper =
- MediaQuery(
- data: MediaQuery.of(context).copyWith(
- alwaysUse24HourFormat:
- settings.timeformat == '12'
- ? false
- : true,
- ),
- child: child!,
- );
- return mediaQueryWrapper;
- },
- );
- if (timeEndPicker != null) {
- isar.writeTxnSync(() {
- settings.timeEnd =
- timeEndPicker.format(context);
- isar.settings.putSync(settings);
- });
- if (!context.mounted) return;
- MyApp.updateAppState(context,
- newTimeEnd:
- timeEndPicker.format(context));
- if (settings.notifications) {
- flutterLocalNotificationsPlugin
- .cancelAll();
- weatherController.notification(
- weatherController.mainWeather);
- }
- }
- },
- ),
- const Gap(10),
- ],
- ),
- );
- },
- ),
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(IconsaxPlusLinear.d_square),
- text: 'data'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).padding.bottom),
- child: StatefulBuilder(
- builder: (BuildContext context, setState) {
- return SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisSize: MainAxisSize.min,
- children: [
- Padding(
- padding:
- const EdgeInsets.symmetric(vertical: 15),
- child: Text(
- 'data'.tr,
- style: context.textTheme.titleLarge?.copyWith(
- fontSize: 20,
- ),
- ),
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.cloud_notif),
- text: 'roundDegree'.tr,
- switcher: true,
- value: settings.roundDegree,
- onChange: (value) {
- settings.roundDegree = value;
- isar.writeTxnSync(
- () => isar.settings.putSync(settings),
- );
- MyApp.updateAppState(
- context,
- newRoundDegree: value,
- );
- setState(() {});
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.sun_1),
- text: 'degrees'.tr,
- dropdown: true,
- dropdownName: settings.degrees.tr,
- dropdownList: [
- 'celsius'.tr,
- 'fahrenheit'.tr
- ],
- dropdownCange: (String? newValue) async {
- isar.writeTxnSync(() {
- settings.degrees = newValue == 'celsius'.tr
- ? 'celsius'
- : 'fahrenheit';
- isar.settings.putSync(settings);
- });
- await weatherController.deleteAll(false);
- await weatherController.setLocation();
- await weatherController.updateCacheCard(true);
- setState(() {});
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.rulerpen),
- text: 'measurements'.tr,
- dropdown: true,
- dropdownName: settings.measurements.tr,
- dropdownList: [
- 'metric'.tr,
- 'imperial'.tr
- ],
- dropdownCange: (String? newValue) async {
- isar.writeTxnSync(() {
- settings.measurements =
- newValue == 'metric'.tr
- ? 'metric'
- : 'imperial';
- isar.settings.putSync(settings);
- });
- await weatherController.deleteAll(false);
- await weatherController.setLocation();
- await weatherController.updateCacheCard(true);
- setState(() {});
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.wind),
- text: 'wind'.tr,
- dropdown: true,
- dropdownName: settings.wind.tr,
- dropdownList: ['kph'.tr, 'm/s'.tr],
- dropdownCange: (String? newValue) async {
- isar.writeTxnSync(() {
- settings.wind =
- newValue == 'kph'.tr ? 'kph' : 'm/s';
- isar.settings.putSync(settings);
- });
- setState(() {});
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.ruler),
- text: 'pressure'.tr,
- dropdown: true,
- dropdownName: settings.pressure.tr,
- dropdownList: ['hPa'.tr, 'mmHg'.tr],
- dropdownCange: (String? newValue) async {
- isar.writeTxnSync(() {
- settings.pressure =
- newValue == 'hPa'.tr ? 'hPa' : 'mmHg';
- isar.settings.putSync(settings);
- });
- setState(() {});
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.clock_1),
- text: 'timeformat'.tr,
- dropdown: true,
- dropdownName: settings.timeformat.tr,
- dropdownList: ['12'.tr, '24'.tr],
- dropdownCange: (String? newValue) {
- isar.writeTxnSync(() {
- settings.timeformat =
- newValue == '12'.tr ? '12' : '24';
- isar.settings.putSync(settings);
- });
- setState(() {});
- },
- ),
- const Gap(10),
- ],
- ),
- );
- },
- ),
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(IconsaxPlusLinear.setting_3),
- text: 'widget'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).padding.bottom),
- child: StatefulBuilder(
- builder: (BuildContext context, setState) {
- return SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisSize: MainAxisSize.min,
- children: [
- Padding(
- padding:
- const EdgeInsets.symmetric(vertical: 15),
- child: Text(
- 'widget'.tr,
- style: context.textTheme.titleLarge?.copyWith(
- fontSize: 20,
- ),
- ),
- ),
- SettingCard(
- elevation: 4,
- icon:
- const Icon(IconsaxPlusLinear.bucket_square),
- text: 'widgetBackground'.tr,
- info: true,
- infoWidget: CircleAvatar(
- backgroundColor: context.theme.indicatorColor,
- radius: 11,
- child: CircleAvatar(
- backgroundColor:
- widgetBackgroundColor.isEmpty
- ? context.theme.primaryColor
- : HexColor.fromHex(
- widgetBackgroundColor),
- radius: 10,
- ),
- ),
- onPressed: () {
- colorBackground = null;
- showDialog(
- context: context,
- builder: (context) => Dialog(
- child: SingleChildScrollView(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment:
- CrossAxisAlignment.center,
- children: [
- Padding(
- padding:
- const EdgeInsets.symmetric(
- vertical: 15),
- child: Text(
- 'widgetBackground'.tr,
- style: context
- .textTheme.titleMedium
- ?.copyWith(fontSize: 18),
- ),
- ),
- Padding(
- padding:
- const EdgeInsets.symmetric(
- horizontal: 15),
- child: Theme(
- data: context.theme.copyWith(
- inputDecorationTheme:
- InputDecorationTheme(
- border: OutlineInputBorder(
- borderRadius:
- BorderRadius.circular(
- 8),
- ),
- ),
- ),
- child: ColorPicker(
- color: widgetBackgroundColor
- .isEmpty
- ? context
- .theme.primaryColor
- : HexColor.fromHex(
- widgetBackgroundColor),
- onChanged: (pickedColor) {
- colorBackground =
- pickedColor.toHex();
- },
- ),
- ),
- ),
- IconButton(
- icon: const Icon(
- IconsaxPlusLinear.tick_square,
- ),
- onPressed: () {
- if (colorBackground == null) {
- return;
- }
- weatherController
- .updateWidgetBackgroundColor(
- colorBackground!);
- MyApp.updateAppState(context,
- newWidgetBackgroundColor:
- colorBackground);
- Get.back();
- },
- ),
- ],
- ),
- ),
- ),
- );
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.text_block),
- text: 'widgetText'.tr,
- info: true,
- infoWidget: CircleAvatar(
- backgroundColor: context.theme.indicatorColor,
- radius: 11,
- child: CircleAvatar(
- backgroundColor: widgetTextColor.isEmpty
- ? context.theme.primaryColor
- : HexColor.fromHex(widgetTextColor),
- radius: 10,
- ),
- ),
- onPressed: () {
- colorText = null;
- showDialog(
- context: context,
- builder: (context) => Dialog(
- child: SingleChildScrollView(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment:
- CrossAxisAlignment.center,
- children: [
- Padding(
- padding:
- const EdgeInsets.symmetric(
- vertical: 15),
- child: Text(
- 'widgetText'.tr,
- style: context
- .textTheme.titleMedium
- ?.copyWith(fontSize: 18),
- ),
- ),
- Padding(
- padding:
- const EdgeInsets.symmetric(
- horizontal: 15),
- child: Theme(
- data: context.theme.copyWith(
- inputDecorationTheme:
- InputDecorationTheme(
- border: OutlineInputBorder(
- borderRadius:
- BorderRadius.circular(
- 8),
- ),
- ),
- ),
- child: ColorPicker(
- color: widgetTextColor.isEmpty
- ? context
- .theme.primaryColor
- : HexColor.fromHex(
- widgetTextColor),
- onChanged: (pickedColor) {
- colorText =
- pickedColor.toHex();
- },
- ),
- ),
- ),
- IconButton(
- icon: const Icon(
- IconsaxPlusLinear.tick_square,
- ),
- onPressed: () {
- if (colorText == null) return;
- weatherController
- .updateWidgetTextColor(
- colorText!);
- MyApp.updateAppState(context,
- newWidgetTextColor:
- colorText);
- Get.back();
- },
- ),
- ],
- ),
- ),
- ),
- );
- },
- ),
- const Gap(10),
- ],
- ),
- );
- },
- ),
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(IconsaxPlusLinear.trash_square),
- text: 'clearCacheStore'.tr,
- onPressed: () => showAdaptiveDialog(
- context: context,
- builder: (context) => AlertDialog.adaptive(
- title: Text(
- 'deletedCacheStore'.tr,
- style: context.textTheme.titleLarge,
- ),
- content: Text(
- 'deletedCacheStoreQuery'.tr,
- style: context.textTheme.titleMedium,
- ),
- actions: [
- TextButton(
- onPressed: () => Get.back(),
- child: Text(
- 'cancel'.tr,
- style: context.textTheme.titleMedium?.copyWith(
- color: Colors.blueAccent,
- ),
- ),
- ),
- TextButton(
- onPressed: () async {
- final dir = await getTemporaryDirectory();
- final cacheStoreFuture = FileCacheStore(
- '${dir.path}${Platform.pathSeparator}MapTiles');
- cacheStoreFuture.clean();
- Get.back();
- },
- child: Text(
- 'delete'.tr,
- style: context.textTheme.titleMedium?.copyWith(
- color: Colors.red,
- ),
- ),
- ),
- ],
- ),
- ),
- ),
- SettingCard(
- icon: const Icon(IconsaxPlusLinear.language_square),
- text: 'language'.tr,
- info: true,
- infoSettings: true,
- infoWidget: _TextInfo(
- info: appLanguages.firstWhere(
- (element) => (element['locale'] == locale),
- orElse: () => appLanguages.first)['name'],
- ),
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).padding.bottom),
- child: StatefulBuilder(
- builder: (BuildContext context, setState) {
- return ListView(
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(vertical: 15),
- child: Text(
- 'language'.tr,
- style: context.textTheme.titleLarge?.copyWith(
- fontSize: 20,
- ),
- textAlign: TextAlign.center,
- ),
- ),
- ListView.builder(
- shrinkWrap: true,
- physics: const BouncingScrollPhysics(),
- itemCount: appLanguages.length,
- itemBuilder: (context, index) {
- return Card(
- elevation: 4,
- margin: const EdgeInsets.symmetric(
- horizontal: 10, vertical: 5),
- child: ListTile(
- title: Text(
- appLanguages[index]['name'],
- style: context.textTheme.labelLarge,
- textAlign: TextAlign.center,
- ),
- onTap: () {
- MyApp.updateAppState(context,
- newLocale: appLanguages[index]
- ['locale']);
- updateLanguage(
- appLanguages[index]['locale']);
- },
- ),
- );
- },
- ),
- const Gap(10),
- ],
- );
- },
- ),
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(IconsaxPlusLinear.dollar_square),
- text: 'support'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).padding.bottom),
- child: StatefulBuilder(
- builder: (BuildContext context, setState) {
- return SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisSize: MainAxisSize.min,
- children: [
- Padding(
- padding:
- const EdgeInsets.symmetric(vertical: 15),
- child: Text(
- 'support'.tr,
- style: context.textTheme.titleLarge?.copyWith(
- fontSize: 20,
- ),
- ),
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.card),
- text: 'DonationAlerts',
- onPressed: () => weatherController.urlLauncher(
- 'https://www.donationalerts.com/r/darkmoonight'),
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(IconsaxPlusLinear.wallet),
- text: 'ЮMoney',
- onPressed: () => weatherController.urlLauncher(
- 'https://yoomoney.ru/to/4100117672775961'),
- ),
- const Gap(10),
- ],
- ),
- );
- },
- ),
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(IconsaxPlusLinear.link_square),
- text: 'groups'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return Padding(
- padding: EdgeInsets.only(
- bottom: MediaQuery.of(context).padding.bottom),
- child: StatefulBuilder(
- builder: (BuildContext context, setState) {
- return SingleChildScrollView(
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.center,
- mainAxisSize: MainAxisSize.min,
- children: [
- Padding(
- padding: const EdgeInsets.symmetric(
- horizontal: 20, vertical: 15),
- child: Text(
- 'groups'.tr,
- style: context.textTheme.titleLarge?.copyWith(
- fontSize: 20,
- ),
- ),
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(LineAwesomeIcons.discord),
- text: 'Discord',
- onPressed: () => weatherController.urlLauncher(
- 'https://discord.gg/JMMa9aHh8f'),
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(LineAwesomeIcons.telegram),
- text: 'Telegram',
- onPressed: () => weatherController
- .urlLauncher('https://t.me/darkmoonightX'),
- ),
- const Gap(10),
- ],
- ),
- );
- },
- ),
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(IconsaxPlusLinear.document),
- text: 'license'.tr,
- onPressed: () => Get.to(
- LicensePage(
- applicationIcon: Container(
- width: 100,
- height: 100,
- margin: const EdgeInsets.symmetric(vertical: 5),
- decoration: const BoxDecoration(
- borderRadius: BorderRadius.all(Radius.circular(20)),
- image: DecorationImage(
- image: AssetImage('assets/icons/icon.png'),
- ),
- ),
- ),
- applicationName: 'Rain',
- applicationVersion: appVersion,
- ),
- transition: Transition.downToUp,
- ),
- ),
- SettingCard(
- icon: const Icon(IconsaxPlusLinear.hierarchy_square_2),
- text: 'version'.tr,
- info: true,
- infoWidget: _TextInfo(
- info: '$appVersion',
- ),
- ),
- SettingCard(
- icon: const Icon(LineAwesomeIcons.github),
- text: '${'project'.tr} GitHub',
- onPressed: () => weatherController
- .urlLauncher('https://github.com/darkmoonight/Rain'),
- ),
- Padding(
- padding: const EdgeInsets.all(10),
- child: GestureDetector(
- child: Text(
- 'openMeteo'.tr,
- style: context.textTheme.bodyMedium,
- overflow: TextOverflow.visible,
- textAlign: TextAlign.center,
- ),
- onTap: () =>
- weatherController.urlLauncher('https://open-meteo.com/'),
- ),
- ),
- const Gap(10),
- ],
- ),
- );
- }
-}
-
-class _TextInfo extends StatelessWidget {
- const _TextInfo({required this.info});
-
- final String info;
-
- @override
- Widget build(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.only(right: 5),
- child: Text(
- info,
- style: context.textTheme.bodyMedium,
- overflow: TextOverflow.visible,
- ),
- );
- }
-}
diff --git a/lib/app/modules/settings/widgets/setting_card.dart b/lib/app/modules/settings/widgets/setting_card.dart
deleted file mode 100644
index 0a2f96c..0000000
--- a/lib/app/modules/settings/widgets/setting_card.dart
+++ /dev/null
@@ -1,101 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax_plus/iconsax_plus.dart';
-
-class SettingCard extends StatelessWidget {
- const SettingCard({
- super.key,
- required this.icon,
- required this.text,
- this.switcher = false,
- this.dropdown = false,
- this.info = false,
- this.infoSettings = false,
- this.elevation,
- this.dropdownName,
- this.dropdownList,
- this.dropdownCange,
- this.value,
- this.onPressed,
- this.onChange,
- this.infoWidget,
- });
- final Widget icon;
- final String text;
- final bool switcher;
- final bool dropdown;
- final bool info;
- final bool infoSettings;
- final Widget? infoWidget;
- final String? dropdownName;
- final List? dropdownList;
- final Function(String?)? dropdownCange;
- final bool? value;
- final Function()? onPressed;
- final Function(bool)? onChange;
- final double? elevation;
-
- @override
- Widget build(BuildContext context) {
- return Card(
- elevation: elevation ?? 1,
- margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
- child: ListTile(
- shape: RoundedRectangleBorder(
- borderRadius: BorderRadius.circular(15),
- ),
- onTap: onPressed,
- leading: icon,
- title: Text(
- text,
- style: context.textTheme.titleMedium,
- overflow: TextOverflow.visible,
- ),
- trailing: switcher
- ? Transform.scale(
- scale: 0.8,
- child: Switch(
- value: value!,
- onChanged: onChange,
- ),
- )
- : dropdown
- ? DropdownButton(
- icon: const Padding(
- padding: EdgeInsets.only(left: 7),
- child: Icon(IconsaxPlusLinear.arrow_down),
- ),
- iconSize: 15,
- alignment: AlignmentDirectional.centerEnd,
- borderRadius: const BorderRadius.all(Radius.circular(15)),
- underline: Container(),
- value: dropdownName,
- items: dropdownList!
- .map>((String value) {
- return DropdownMenuItem(
- value: value,
- child: Text(value),
- );
- }).toList(),
- onChanged: dropdownCange,
- )
- : info
- ? infoSettings
- ? Wrap(
- children: [
- infoWidget!,
- const Icon(
- IconsaxPlusLinear.arrow_right_3,
- size: 18,
- ),
- ],
- )
- : infoWidget!
- : const Icon(
- IconsaxPlusLinear.arrow_right_3,
- size: 18,
- ),
- ),
- );
- }
-}
diff --git a/lib/app/services/notification.dart b/lib/app/services/notification.dart
deleted file mode 100644
index ba07067..0000000
--- a/lib/app/services/notification.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'package:flutter_local_notifications/flutter_local_notifications.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/main.dart';
-import 'package:timezone/timezone.dart' as tz;
-
-class NotificationShow {
- Future showNotification(
- int id,
- String title,
- String body,
- DateTime date,
- String icon,
- ) async {
- final imagePath = await WeatherController().getLocalImagePath(icon);
-
- AndroidNotificationDetails androidNotificationDetails =
- AndroidNotificationDetails(
- 'Rain',
- 'DARK NIGHT',
- priority: Priority.high,
- importance: Importance.max,
- playSound: false,
- enableVibration: false,
- largeIcon: FilePathAndroidBitmap(imagePath),
- );
- NotificationDetails notificationDetails =
- NotificationDetails(android: androidNotificationDetails);
-
- var scheduledTime = tz.TZDateTime.from(date, tz.local);
- flutterLocalNotificationsPlugin.zonedSchedule(
- id,
- title,
- body,
- scheduledTime,
- notificationDetails,
- uiLocalNotificationDateInterpretation:
- UILocalNotificationDateInterpretation.absoluteTime,
- androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
- payload: imagePath,
- );
- }
-}
diff --git a/lib/app/services/utils.dart b/lib/app/services/utils.dart
deleted file mode 100644
index a7dd8a1..0000000
--- a/lib/app/services/utils.dart
+++ /dev/null
@@ -1,20 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-
-final globalKey = GlobalKey();
-
-void showSnackBar({required String content, Function? onPressed}) {
- globalKey.currentState?.showSnackBar(
- SnackBar(
- content: Text(content),
- action: onPressed != null
- ? SnackBarAction(
- label: 'settings'.tr,
- onPressed: () {
- onPressed();
- },
- )
- : null,
- ),
- );
-}
diff --git a/lib/app/ui/geolocation.dart b/lib/app/ui/geolocation.dart
new file mode 100755
index 0000000..2b6165c
--- /dev/null
+++ b/lib/app/ui/geolocation.dart
@@ -0,0 +1,479 @@
+import 'dart:ui';
+import 'package:flutter/material.dart';
+import 'package:flutter_map/flutter_map.dart';
+import 'package:gap/gap.dart';
+import 'package:geolocator/geolocator.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:latlong2/latlong.dart';
+import 'package:rain/app/api/api.dart';
+import 'package:rain/app/api/city_api.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/ui/home.dart';
+import 'package:rain/app/ui/widgets/button.dart';
+import 'package:rain/app/ui/widgets/text_form.dart';
+import 'package:rain/main.dart';
+
+class SelectGeolocation extends StatefulWidget {
+ const SelectGeolocation({super.key, required this.isStart});
+ final bool isStart;
+
+ @override
+ State createState() => _SelectGeolocationState();
+}
+
+class _SelectGeolocationState extends State {
+ bool isLoading = false;
+ final formKeySearch = GlobalKey();
+ final _focusNode = FocusNode();
+ final weatherController = Get.put(WeatherController());
+ final _controller = TextEditingController();
+ final _controllerLat = TextEditingController();
+ final _controllerLon = TextEditingController();
+ final _controllerCity = TextEditingController();
+ final _controllerDistrict = TextEditingController();
+
+ static const kTextFieldElevation = 4.0;
+
+ static const colorFilter = ColorFilter.matrix([
+ -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();
+
+ void textTrim(TextEditingController value) {
+ value.text = value.text.trim();
+ while (value.text.contains(' ')) {
+ value.text = value.text.replaceAll(' ', ' ');
+ }
+ }
+
+ void fillController(Result selection) {
+ _controllerLat.text = '${selection.latitude}';
+ _controllerLon.text = '${selection.longitude}';
+ _controllerCity.text = selection.name;
+ _controllerDistrict.text = selection.admin1;
+ _controller.clear();
+ _focusNode.unfocus();
+ setState(() {});
+ }
+
+ void fillControllerGeo(Map location) {
+ _controllerLat.text = '${location['lat']}';
+ _controllerLon.text = '${location['lon']}';
+ _controllerCity.text = location['district'];
+ _controllerDistrict.text = location['city'];
+ setState(() {});
+ }
+
+ void fillMap(double latitude, double longitude) {
+ _controllerLat.text = '$latitude';
+ _controllerLon.text = '$longitude';
+ setState(() {});
+ }
+
+ Widget _buildMapTileLayer() {
+ return TileLayer(
+ urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
+ userAgentPackageName: 'com.darkmoonight.rain',
+ );
+ }
+
+ Widget _buildMap() {
+ return 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,
+ interactionOptions: const InteractionOptions(
+ flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
+ ),
+ cameraConstraint: CameraConstraint.contain(
+ bounds: LatLngBounds(
+ const LatLng(-90, -180),
+ const LatLng(90, 180),
+ ),
+ ),
+ onLongPress: (tapPosition, point) =>
+ fillMap(point.latitude, point.longitude),
+ ),
+ children: [
+ if (_isDarkMode)
+ ColorFiltered(
+ colorFilter: colorFilter,
+ child: _buildMapTileLayer(),
+ )
+ else
+ _buildMapTileLayer(),
+ RichAttributionWidget(
+ animationConfig: const ScaleRAWA(),
+ attributions: [
+ TextSourceAttribution(
+ 'OpenStreetMap contributors',
+ onTap: () => weatherController.urlLauncher(
+ 'https://openstreetmap.org/copyright',
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSearchField() {
+ return RawAutocomplete(
+ focusNode: _focusNode,
+ textEditingController: _controller,
+ fieldViewBuilder:
+ (
+ BuildContext context,
+ TextEditingController fieldTextEditingController,
+ FocusNode fieldFocusNode,
+ VoidCallback onFieldSubmitted,
+ ) {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ labelText: 'search'.tr,
+ type: TextInputType.text,
+ icon: const Icon(IconsaxPlusLinear.global_search),
+ controller: _controller,
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ focusNode: _focusNode,
+ );
+ },
+ optionsBuilder: (TextEditingValue textEditingValue) {
+ if (textEditingValue.text.isEmpty) {
+ return const Iterable.empty();
+ }
+ return WeatherAPI().getCity(textEditingValue.text, locale);
+ },
+ onSelected: (Result selection) => fillController(selection),
+ displayStringForOption: (Result option) =>
+ '${option.name}, ${option.admin1}',
+ optionsViewBuilder:
+ (
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ ) {
+ return _buildOptionsView(context, onSelected, options);
+ },
+ );
+ }
+
+ Widget _buildOptionsView(
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ ) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ 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) {
+ final Result option = options.elementAt(index);
+ return InkWell(
+ onTap: () => onSelected(option),
+ child: ListTile(
+ title: Text(
+ '${option.name}, ${option.admin1}',
+ style: context.textTheme.labelLarge,
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildLocationButton() {
+ return Card(
+ elevation: kTextFieldElevation,
+ margin: const EdgeInsets.only(top: 10, right: 10),
+ child: Container(
+ margin: const EdgeInsets.all(2.5),
+ child: IconButton(
+ onPressed: _handleLocationButtonPress,
+ icon: const Icon(IconsaxPlusLinear.location),
+ ),
+ ),
+ );
+ }
+
+ Future _handleLocationButtonPress() async {
+ bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
+ if (!serviceEnabled) {
+ if (!context.mounted) return;
+ await _showLocationDialog();
+ return;
+ }
+ setState(() => isLoading = true);
+ final location = await weatherController.getCurrentLocationSearch();
+ fillControllerGeo(location);
+ setState(() => isLoading = false);
+ }
+
+ Future _showLocationDialog() async {
+ await showAdaptiveDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog.adaptive(
+ title: Text('location'.tr, style: context.textTheme.titleLarge),
+ content: Text('no_location'.tr, style: context.textTheme.titleMedium),
+ actions: [
+ TextButton(
+ onPressed: () => Get.back(result: false),
+ child: Text(
+ 'cancel'.tr,
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.blueAccent,
+ ),
+ ),
+ ),
+ TextButton(
+ onPressed: () {
+ Geolocator.openLocationSettings();
+ Get.back(result: true);
+ },
+ child: Text(
+ 'settings'.tr,
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.green,
+ ),
+ ),
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ Widget _buildLatitudeField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerLat,
+ labelText: 'lat'.tr,
+ type: TextInputType.number,
+ icon: const Icon(IconsaxPlusLinear.location),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ validator: (value) => _validateLatitude(value),
+ );
+ }
+
+ Widget _buildLongitudeField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerLon,
+ labelText: 'lon'.tr,
+ type: TextInputType.number,
+ icon: const Icon(IconsaxPlusLinear.location),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ validator: (value) => _validateLongitude(value),
+ );
+ }
+
+ Widget _buildCityField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerCity,
+ labelText: 'city'.tr,
+ type: TextInputType.name,
+ icon: const Icon(IconsaxPlusLinear.building_3),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ validator: (value) => _validateCity(value),
+ );
+ }
+
+ Widget _buildDistrictField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerDistrict,
+ labelText: 'district'.tr,
+ type: TextInputType.streetAddress,
+ icon: const Icon(IconsaxPlusLinear.global),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ );
+ }
+
+ Widget _buildSubmitButton() {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
+ child: MyTextButton(buttonName: 'done'.tr, onPressed: _handleSubmit),
+ );
+ }
+
+ String? _validateLatitude(String? 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;
+ }
+
+ String? _validateLongitude(String? 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;
+ }
+
+ String? _validateCity(String? value) {
+ if (value == null || value.isEmpty) {
+ return 'validateName'.tr;
+ }
+ return null;
+ }
+
+ Future _handleSubmit() 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,
+ );
+ widget.isStart
+ ? Get.off(() => const HomePage(), transition: Transition.downToUp)
+ : Get.back();
+ } catch (error) {
+ Future.error(error);
+ }
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Form(
+ key: formKeySearch,
+ child: Scaffold(
+ resizeToAvoidBottomInset: true,
+ appBar: _buildAppBar(),
+ 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(
+ horizontal: 10,
+ ),
+ child: _buildMap(),
+ ),
+ Padding(
+ padding: const EdgeInsets.fromLTRB(
+ 10,
+ 15,
+ 10,
+ 5,
+ ),
+ child: Text(
+ 'searchMethod'.tr,
+ style: context.theme.textTheme.bodyLarge
+ ?.copyWith(fontWeight: FontWeight.bold),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ Row(
+ children: [
+ Flexible(child: _buildSearchField()),
+ _buildLocationButton(),
+ ],
+ ),
+ _buildLatitudeField(),
+ _buildLongitudeField(),
+ _buildCityField(),
+ _buildDistrictField(),
+ const Gap(20),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ _buildSubmitButton(),
+ ],
+ ),
+ if (isLoading)
+ BackdropFilter(
+ filter: ImageFilter.blur(sigmaY: 3, sigmaX: 3),
+ child: const Center(child: CircularProgressIndicator()),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ AppBar _buildAppBar() {
+ return AppBar(
+ centerTitle: true,
+ leading: widget.isStart
+ ? null
+ : IconButton(
+ onPressed: () {
+ Get.back();
+ },
+ icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
+ splashColor: Colors.transparent,
+ highlightColor: Colors.transparent,
+ ),
+ automaticallyImplyLeading: false,
+ title: Text(
+ 'searchCity'.tr,
+ style: context.textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/app/ui/home.dart b/lib/app/ui/home.dart
new file mode 100755
index 0000000..655d2ba
--- /dev/null
+++ b/lib/app/ui/home.dart
@@ -0,0 +1,311 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:isar/isar.dart';
+import 'package:rain/app/api/api.dart';
+import 'package:rain/app/api/city_api.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/places/view/place_list.dart';
+import 'package:rain/app/ui/places/widgets/create_place.dart';
+import 'package:rain/app/ui/geolocation.dart';
+import 'package:rain/app/ui/main/view/main.dart';
+import 'package:rain/app/ui/map/view/map.dart';
+import 'package:rain/app/ui/settings/view/settings.dart';
+import 'package:rain/app/utils/show_snack_bar.dart';
+import 'package:rain/main.dart';
+
+class HomePage extends StatefulWidget {
+ const HomePage({super.key});
+
+ @override
+ State createState() => _HomePageState();
+}
+
+class _HomePageState extends State with TickerProviderStateMixin {
+ int tabIndex = 0;
+ bool visible = false;
+ final _focusNode = FocusNode();
+ late TabController tabController;
+ final weatherController = Get.put(WeatherController());
+ final _controller = TextEditingController();
+
+ final List pages = [
+ const MainPage(),
+ const PlaceList(),
+ if (!settings.hideMap) const MapPage(),
+ const SettingsPage(),
+ ];
+
+ @override
+ void initState() {
+ super.initState();
+ getData();
+ setupTabController();
+ }
+
+ @override
+ void dispose() {
+ tabController.dispose();
+ super.dispose();
+ }
+
+ void setupTabController() {
+ tabController = TabController(
+ initialIndex: tabIndex,
+ length: pages.length,
+ vsync: this,
+ );
+
+ tabController.animation?.addListener(() {
+ int value = (tabController.animation!.value).round();
+ if (value != tabIndex) setState(() => tabIndex = value);
+ });
+
+ tabController.addListener(() {
+ setState(() {
+ tabIndex = tabController.index;
+ });
+ });
+ }
+
+ void getData() async {
+ await weatherController.deleteCache();
+ await weatherController.updateCacheCard(false);
+ await weatherController.setLocation();
+ }
+
+ void changeTabIndex(int index) {
+ setState(() {
+ tabIndex = index;
+ });
+ tabController.animateTo(tabIndex);
+ }
+
+ Widget _buildAppBarTitle(
+ int tabIndex,
+ TextStyle? textStyle,
+ TextStyle? labelLarge,
+ ) {
+ switch (tabIndex) {
+ case 0:
+ return visible
+ ? _buildSearchField(labelLarge)
+ : Obx(() {
+ final location = weatherController.location;
+ final city = location.city;
+ final district = location.district;
+ return Text(
+ weatherController.isLoading.isFalse
+ ? district!.isEmpty
+ ? '$city'
+ : city!.isEmpty
+ ? district
+ : city == district
+ ? city
+ : '$city, $district'
+ : settings.location
+ ? 'search'.tr
+ : (isar.locationCaches.where().findAllSync()).isNotEmpty
+ ? 'loading'.tr
+ : 'searchCity'.tr,
+ style: textStyle,
+ );
+ });
+ case 1:
+ return Text('cities'.tr, style: textStyle);
+ case 2:
+ return settings.hideMap
+ ? Text('settings_full'.tr, style: textStyle)
+ : Text('map'.tr, style: textStyle);
+ case 3:
+ return Text('settings_full'.tr, style: textStyle);
+ default:
+ return const SizedBox.shrink();
+ }
+ }
+
+ Widget _buildSearchField(TextStyle? labelLarge) {
+ return RawAutocomplete(
+ focusNode: _focusNode,
+ textEditingController: _controller,
+ fieldViewBuilder: (_, __, ___, ____) {
+ return TextField(
+ controller: _controller,
+ focusNode: _focusNode,
+ style: labelLarge?.copyWith(fontSize: 16),
+ decoration: InputDecoration(hintText: 'search'.tr),
+ );
+ },
+ optionsBuilder: (TextEditingValue textEditingValue) {
+ if (textEditingValue.text.isEmpty) {
+ return const Iterable.empty();
+ }
+ return WeatherAPI().getCity(textEditingValue.text, locale);
+ },
+ onSelected: (Result selection) async {
+ await weatherController.deleteAll(true);
+ await weatherController.getLocation(
+ double.parse('${selection.latitude}'),
+ double.parse('${selection.longitude}'),
+ selection.admin1,
+ selection.name,
+ );
+ visible = false;
+ _controller.clear();
+ _focusNode.unfocus();
+ setState(() {});
+ },
+ displayStringForOption:
+ (Result option) => '${option.name}, ${option.admin1}',
+ optionsViewBuilder: (
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ ) {
+ return _buildOptionsView(context, onSelected, options, labelLarge);
+ },
+ );
+ }
+
+ Widget _buildOptionsView(
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ TextStyle? labelLarge,
+ ) {
+ return Align(
+ alignment: Alignment.topLeft,
+ child: Material(
+ borderRadius: BorderRadius.circular(20),
+ elevation: 4.0,
+ child: SizedBox(
+ width: 250,
+ child: ListView.builder(
+ padding: EdgeInsets.zero,
+ shrinkWrap: true,
+ itemCount: options.length,
+ itemBuilder: (BuildContext context, int index) {
+ final Result option = options.elementAt(index);
+ return InkWell(
+ onTap: () => onSelected(option),
+ child: ListTile(
+ title: Text(
+ '${option.name}, ${option.admin1}',
+ style: labelLarge,
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSearchIconButton() {
+ return IconButton(
+ onPressed: () {
+ if (visible) {
+ _controller.clear();
+ _focusNode.unfocus();
+ visible = false;
+ } else {
+ visible = true;
+ }
+ setState(() {});
+ },
+ icon: Icon(
+ visible
+ ? IconsaxPlusLinear.close_circle
+ : IconsaxPlusLinear.search_normal_1,
+ size: 18,
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final textTheme = context.textTheme;
+ final labelLarge = textTheme.labelLarge;
+
+ final textStyle = textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ );
+
+ return DefaultTabController(
+ length: pages.length,
+ child: ScaffoldMessenger(
+ key: globalKey,
+ child: Scaffold(
+ appBar: AppBar(
+ centerTitle: true,
+ automaticallyImplyLeading: false,
+ leading:
+ tabIndex == 0
+ ? IconButton(
+ onPressed: () {
+ Get.to(
+ () => const SelectGeolocation(isStart: false),
+ transition: Transition.downToUp,
+ );
+ },
+ icon: const Icon(
+ IconsaxPlusLinear.global_search,
+ size: 18,
+ ),
+ )
+ : null,
+ title: _buildAppBarTitle(tabIndex, textStyle, labelLarge),
+ actions: tabIndex == 0 ? [_buildSearchIconButton()] : null,
+ ),
+ body: SafeArea(
+ child: TabBarView(controller: tabController, children: pages),
+ ),
+ bottomNavigationBar: NavigationBar(
+ onDestinationSelected: (int index) => changeTabIndex(index),
+ selectedIndex: tabIndex,
+ destinations: [
+ NavigationDestination(
+ icon: const Icon(IconsaxPlusLinear.cloud_sunny),
+ selectedIcon: const Icon(IconsaxPlusBold.cloud_sunny),
+ label: 'name'.tr,
+ ),
+ NavigationDestination(
+ icon: const Icon(IconsaxPlusLinear.buildings),
+ selectedIcon: const Icon(IconsaxPlusBold.buildings),
+ label: 'cities'.tr,
+ ),
+ if (!settings.hideMap)
+ NavigationDestination(
+ icon: const Icon(IconsaxPlusLinear.map),
+ selectedIcon: const Icon(IconsaxPlusBold.map),
+ label: 'map'.tr,
+ ),
+ NavigationDestination(
+ icon: const Icon(IconsaxPlusLinear.category),
+ selectedIcon: const Icon(IconsaxPlusBold.category),
+ label: 'settings_full'.tr,
+ ),
+ ],
+ ),
+ floatingActionButton:
+ tabIndex == 1
+ ? FloatingActionButton(
+ onPressed:
+ () => showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ enableDrag: false,
+ builder:
+ (BuildContext context) => const CreatePlace(),
+ ),
+ child: const Icon(IconsaxPlusLinear.add),
+ )
+ : null,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/app/ui/main/view/main.dart b/lib/app/ui/main/view/main.dart
new file mode 100755
index 0000000..fe0987e
--- /dev/null
+++ b/lib/app/ui/main/view/main.dart
@@ -0,0 +1,242 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_card_list.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_container.dart';
+import 'package:rain/app/ui/widgets/weather/desc/desc_container.dart';
+import 'package:rain/app/ui/widgets/weather/hourly.dart';
+import 'package:rain/app/ui/widgets/weather/now.dart';
+import 'package:rain/app/ui/widgets/shimmer.dart';
+import 'package:rain/app/ui/widgets/weather/sunset_sunrise.dart';
+import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
+
+class MainPage extends StatefulWidget {
+ const MainPage({super.key});
+
+ @override
+ State createState() => _MainPageState();
+}
+
+class _MainPageState extends State {
+ final weatherController = Get.put(WeatherController());
+
+ @override
+ Widget build(BuildContext context) {
+ return RefreshIndicator(
+ onRefresh: _handleRefresh,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10),
+ child: Obx(() {
+ if (weatherController.isLoading.isTrue) {
+ return _buildLoadingView();
+ }
+
+ final mainWeather = weatherController.mainWeather;
+ final weatherCard = WeatherCard.fromJson(mainWeather.toJson());
+ final hourOfDay = weatherController.hourOfDay.value;
+ final dayOfNow = weatherController.dayOfNow.value;
+ final sunrise = mainWeather.sunrise![dayOfNow];
+ final sunset = mainWeather.sunset![dayOfNow];
+ final tempMax = mainWeather.temperature2MMax![dayOfNow];
+ final tempMin = mainWeather.temperature2MMin![dayOfNow];
+
+ return _buildMainView(
+ context,
+ mainWeather,
+ weatherCard,
+ hourOfDay,
+ dayOfNow,
+ sunrise,
+ sunset,
+ tempMax!,
+ tempMin!,
+ );
+ }),
+ ),
+ );
+ }
+
+ Future _handleRefresh() async {
+ await weatherController.deleteAll(false);
+ await weatherController.setLocation();
+ setState(() {});
+ }
+
+ Widget _buildLoadingView() {
+ return ListView(
+ children: const [
+ MyShimmer(height: 200),
+ MyShimmer(height: 130, margin: EdgeInsets.symmetric(vertical: 15)),
+ MyShimmer(height: 90, margin: EdgeInsets.only(bottom: 15)),
+ MyShimmer(height: 400, margin: EdgeInsets.only(bottom: 15)),
+ MyShimmer(height: 450, margin: EdgeInsets.only(bottom: 15)),
+ ],
+ );
+ }
+
+ Widget _buildMainView(
+ BuildContext context,
+ MainWeatherCache mainWeather,
+ WeatherCard weatherCard,
+ int hourOfDay,
+ int dayOfNow,
+ String sunrise,
+ String sunset,
+ double tempMax,
+ double tempMin,
+ ) {
+ return ListView(
+ children: [
+ _buildNowWidget(
+ mainWeather,
+ hourOfDay,
+ dayOfNow,
+ sunrise,
+ sunset,
+ tempMax,
+ tempMin,
+ ),
+ _buildHourlyList(context, mainWeather, hourOfDay, dayOfNow),
+ _buildSunsetSunriseWidget(sunrise, sunset),
+ _buildHourlyDescContainer(mainWeather, hourOfDay),
+ _buildDailyContainer(weatherCard),
+ ],
+ );
+ }
+
+ Widget _buildNowWidget(
+ MainWeatherCache mainWeather,
+ int hourOfDay,
+ int dayOfNow,
+ String sunrise,
+ String sunset,
+ double tempMax,
+ double tempMin,
+ ) {
+ return Now(
+ time: mainWeather.time![hourOfDay],
+ weather: mainWeather.weathercode![hourOfDay],
+ degree: mainWeather.temperature2M![hourOfDay],
+ feels: mainWeather.apparentTemperature![hourOfDay]!,
+ timeDay: sunrise,
+ timeNight: sunset,
+ tempMax: tempMax,
+ tempMin: tempMin,
+ );
+ }
+
+ Widget _buildHourlyList(
+ BuildContext context,
+ MainWeatherCache mainWeather,
+ int hourOfDay,
+ int dayOfNow,
+ ) {
+ return Card(
+ margin: const EdgeInsets.only(bottom: 15),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ child: SizedBox(
+ height: 135,
+ child: ScrollablePositionedList.separated(
+ key: const PageStorageKey(0),
+ separatorBuilder: (BuildContext context, int index) {
+ return const VerticalDivider(
+ width: 10,
+ indent: 40,
+ endIndent: 40,
+ );
+ },
+ scrollDirection: Axis.horizontal,
+ itemScrollController: weatherController.itemScrollController,
+ itemCount: mainWeather.time!.length,
+ itemBuilder: (ctx, i) {
+ return _buildHourlyItem(
+ context,
+ mainWeather,
+ i,
+ hourOfDay,
+ dayOfNow,
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildHourlyItem(
+ BuildContext context,
+ MainWeatherCache mainWeather,
+ int i,
+ int hourOfDay,
+ int dayOfNow,
+ ) {
+ final i24 = (i / 24).floor();
+
+ return GestureDetector(
+ onTap: () {
+ weatherController.hourOfDay.value = i;
+ weatherController.dayOfNow.value = i24;
+ setState(() {});
+ },
+ child: Container(
+ margin: const EdgeInsets.symmetric(vertical: 5),
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
+ decoration: BoxDecoration(
+ color: i == hourOfDay
+ ? context.theme.colorScheme.secondaryContainer
+ : Colors.transparent,
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ ),
+ child: Hourly(
+ time: mainWeather.time![i],
+ weather: mainWeather.weathercode![i],
+ degree: mainWeather.temperature2M![i],
+ timeDay: mainWeather.sunrise![i24],
+ timeNight: mainWeather.sunset![i24],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSunsetSunriseWidget(String sunrise, String sunset) {
+ return SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset);
+ }
+
+ Widget _buildHourlyDescContainer(
+ MainWeatherCache mainWeather,
+ int hourOfDay,
+ ) {
+ return DescContainer(
+ humidity: mainWeather.relativehumidity2M?[hourOfDay],
+ wind: mainWeather.windspeed10M?[hourOfDay],
+ visibility: mainWeather.visibility?[hourOfDay],
+ feels: mainWeather.apparentTemperature?[hourOfDay],
+ evaporation: mainWeather.evapotranspiration?[hourOfDay],
+ precipitation: mainWeather.precipitation?[hourOfDay],
+ direction: mainWeather.winddirection10M?[hourOfDay],
+ pressure: mainWeather.surfacePressure?[hourOfDay],
+ rain: mainWeather.rain?[hourOfDay],
+ cloudcover: mainWeather.cloudcover?[hourOfDay],
+ windgusts: mainWeather.windgusts10M?[hourOfDay],
+ uvIndex: mainWeather.uvIndex?[hourOfDay],
+ dewpoint2M: mainWeather.dewpoint2M?[hourOfDay],
+ precipitationProbability:
+ mainWeather.precipitationProbability?[hourOfDay],
+ shortwaveRadiation: mainWeather.shortwaveRadiation?[hourOfDay],
+ initiallyExpanded: false,
+ title: 'hourlyVariables'.tr,
+ );
+ }
+
+ Widget _buildDailyContainer(WeatherCard weatherCard) {
+ return DailyContainer(
+ weatherData: weatherCard,
+ onTap: () => Get.to(
+ () => DailyCardList(weatherData: weatherCard),
+ transition: Transition.downToUp,
+ ),
+ );
+ }
+}
diff --git a/lib/app/modules/map/view/map.dart b/lib/app/ui/map/view/map.dart
old mode 100644
new mode 100755
similarity index 51%
rename from lib/app/modules/map/view/map.dart
rename to lib/app/ui/map/view/map.dart
index 619c8a4..7f26040
--- a/lib/app/modules/map/view/map.dart
+++ b/lib/app/ui/map/view/map.dart
@@ -1,6 +1,5 @@
import 'dart:io';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
-import 'package:dio_cache_interceptor_file_store/dio_cache_interceptor_file_store.dart';
import 'package:flutter/material.dart';
import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
import 'package:flutter_map/flutter_map.dart';
@@ -8,29 +7,30 @@ import 'package:flutter_map_animations/flutter_map_animations.dart';
import 'package:flutter_map_cache/flutter_map_cache.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
+import 'package:http_cache_file_store/http_cache_file_store.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:latlong2/latlong.dart';
import 'package:path_provider/path_provider.dart';
import 'package:rain/app/api/api.dart';
import 'package:rain/app/api/city_api.dart';
import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/data/weather.dart';
-import 'package:rain/app/modules/cards/view/info_weather_card.dart';
-import 'package:rain/app/modules/cards/widgets/create_card_weather.dart';
-import 'package:rain/app/modules/cards/widgets/weather_card_container.dart';
-import 'package:rain/app/widgets/status/status_data.dart';
-import 'package:rain/app/widgets/status/status_weather.dart';
-import 'package:rain/app/widgets/text_form.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/places/view/place_info.dart';
+import 'package:rain/app/ui/places/widgets/create_place.dart';
+import 'package:rain/app/ui/places/widgets/place_card.dart';
+import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
+import 'package:rain/app/ui/widgets/weather/status/status_weather.dart';
+import 'package:rain/app/ui/widgets/text_form.dart';
import 'package:rain/main.dart';
-class MapWeather extends StatefulWidget {
- const MapWeather({super.key});
+class MapPage extends StatefulWidget {
+ const MapPage({super.key});
@override
- State createState() => _MapWeatherState();
+ State createState() => _MapPageState();
}
-class _MapWeatherState extends State with TickerProviderStateMixin {
+class _MapPageState extends State with TickerProviderStateMixin {
late final AnimatedMapController _animatedMapController =
AnimatedMapController(vsync: this);
final weatherController = Get.put(WeatherController());
@@ -38,11 +38,15 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
final statusData = StatusData();
final Future _cacheStoreFuture = _getCacheStore();
+ final GlobalKey _fabKey = GlobalKey();
+
final bool _isDarkMode = Get.theme.brightness == Brightness.dark;
WeatherCard? _selectedWeatherCard;
bool _isCardVisible = false;
late final AnimationController _animationController;
late final Animation _offsetAnimation;
+ static const _useTransformerId = 'useTransformerId';
+ final bool _useTransformer = true;
final _focusNode = FocusNode();
late final TextEditingController _controllerSearch = TextEditingController();
@@ -62,10 +66,9 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
_offsetAnimation = Tween(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
- ).animate(CurvedAnimation(
- parent: _animationController,
- curve: Curves.easeInOut,
- ));
+ ).animate(
+ CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
+ );
super.initState();
}
@@ -79,6 +82,7 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
void _resetMapOrientation({LatLng? center, double? zoom}) {
_animatedMapController.animateTo(
+ customId: _useTransformer ? _useTransformerId : null,
dest: center,
zoom: zoom,
rotation: 0,
@@ -93,6 +97,10 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
});
_animationController.forward();
_isCardVisible = true;
+
+ if (_fabKey.currentState?.isOpen == true) {
+ _fabKey.currentState?.toggle();
+ }
}
void _hideCard() {
@@ -105,25 +113,26 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
_focusNode.unfocus();
}
- Widget _buidStyleMarkers(int weathercode, String time, String sunrise,
- String sunset, double temperature2M) {
+ Widget _buildStyleMarkers(
+ int weathercode,
+ String time,
+ String sunrise,
+ String sunset,
+ double temperature2M,
+ ) {
return Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
- statusWeather.getImageNow(
- weathercode,
- time,
- sunrise,
- sunset,
- ),
+ statusWeather.getImageNow(weathercode, time, sunrise, sunset),
scale: 18,
),
const MaxGap(5),
Text(
- statusData
- .getDegree(roundDegree ? temperature2M.round() : temperature2M),
+ statusData.getDegree(
+ roundDegree ? temperature2M.round() : temperature2M,
+ ),
style: context.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.bold,
fontSize: 16,
@@ -135,14 +144,17 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
}
Marker _buildMainLocationMarker(
- WeatherCard weatherCard, int hourOfDay, int dayOfNow) {
+ WeatherCard weatherCard,
+ int hourOfDay,
+ int dayOfNow,
+ ) {
return Marker(
height: 50,
width: 100,
point: LatLng(weatherCard.lat!, weatherCard.lon!),
child: GestureDetector(
onTap: () => _onMarkerTap(weatherCard),
- child: _buidStyleMarkers(
+ child: _buildStyleMarkers(
weatherCard.weathercode![hourOfDay],
weatherCard.time![hourOfDay],
weatherCard.sunrise![dayOfNow],
@@ -154,23 +166,27 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
}
Marker _buildCardMarker(WeatherCard weatherCardList) {
+ final hourOfDay = weatherController.getTime(
+ weatherCardList.time!,
+ weatherCardList.timezone!,
+ );
+ final dayOfNow = weatherController.getDay(
+ weatherCardList.timeDaily!,
+ weatherCardList.timezone!,
+ );
+
return Marker(
height: 50,
width: 100,
point: LatLng(weatherCardList.lat!, weatherCardList.lon!),
child: GestureDetector(
onTap: () => _onMarkerTap(weatherCardList),
- child: _buidStyleMarkers(
- weatherCardList.weathercode![weatherController.getTime(
- weatherCardList.time!, weatherCardList.timezone!)],
- weatherCardList.time![weatherController.getTime(
- weatherCardList.time!, weatherCardList.timezone!)],
- weatherCardList.sunrise![weatherController.getDay(
- weatherCardList.timeDaily!, weatherCardList.timezone!)],
- weatherCardList.sunset![weatherController.getDay(
- weatherCardList.timeDaily!, weatherCardList.timezone!)],
- weatherCardList.temperature2M![weatherController.getTime(
- weatherCardList.time!, weatherCardList.timezone!)],
+ child: _buildStyleMarkers(
+ weatherCardList.weathercode![hourOfDay],
+ weatherCardList.time![hourOfDay],
+ weatherCardList.sunrise![dayOfNow],
+ weatherCardList.sunset![dayOfNow],
+ weatherCardList.temperature2M![hourOfDay],
),
),
);
@@ -190,26 +206,112 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
Widget _buildWeatherCard() {
return _isCardVisible && _selectedWeatherCard != null
? SlideTransition(
- position: _offsetAnimation,
- child: GestureDetector(
- onTap: () => Get.to(
- () => InfoWeatherCard(weatherCard: _selectedWeatherCard!),
- transition: Transition.downToUp,
- ),
- child: WeatherCardContainer(
- time: _selectedWeatherCard!.time!,
- timeDaily: _selectedWeatherCard!.timeDaily!,
- timeDay: _selectedWeatherCard!.sunrise!,
- timeNight: _selectedWeatherCard!.sunset!,
- weather: _selectedWeatherCard!.weathercode!,
- degree: _selectedWeatherCard!.temperature2M!,
- district: _selectedWeatherCard!.district!,
- city: _selectedWeatherCard!.city!,
- timezone: _selectedWeatherCard!.timezone!,
+ position: _offsetAnimation,
+ child: GestureDetector(
+ onTap:
+ () => Get.to(
+ () => PlaceInfo(weatherCard: _selectedWeatherCard!),
+ transition: Transition.downToUp,
+ ),
+ child: PlaceCard(
+ time: _selectedWeatherCard!.time!,
+ timeDaily: _selectedWeatherCard!.timeDaily!,
+ timeDay: _selectedWeatherCard!.sunrise!,
+ timeNight: _selectedWeatherCard!.sunset!,
+ weather: _selectedWeatherCard!.weathercode!,
+ degree: _selectedWeatherCard!.temperature2M!,
+ district: _selectedWeatherCard!.district!,
+ city: _selectedWeatherCard!.city!,
+ timezone: _selectedWeatherCard!.timezone!,
+ ),
+ ),
+ )
+ : const SizedBox.shrink();
+ }
+
+ Widget _buildSearchField() {
+ return RawAutocomplete(
+ focusNode: _focusNode,
+ textEditingController: _controllerSearch,
+ fieldViewBuilder: (
+ BuildContext context,
+ TextEditingController fieldTextEditingController,
+ FocusNode fieldFocusNode,
+ VoidCallback onFieldSubmitted,
+ ) {
+ return MyTextForm(
+ labelText: 'search'.tr,
+ type: TextInputType.text,
+ icon: const Icon(IconsaxPlusLinear.global_search),
+ controller: _controllerSearch,
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ focusNode: _focusNode,
+ onChanged: (value) => setState(() {}),
+ iconButton:
+ _controllerSearch.text.isNotEmpty
+ ? IconButton(
+ onPressed: () {
+ _controllerSearch.clear();
+ },
+ icon: const Icon(
+ IconsaxPlusLinear.close_circle,
+ color: Colors.grey,
+ size: 20,
+ ),
+ )
+ : null,
+ );
+ },
+ optionsBuilder: (TextEditingValue textEditingValue) {
+ if (textEditingValue.text.isEmpty) {
+ return const Iterable.empty();
+ }
+ return WeatherAPI().getCity(textEditingValue.text, locale);
+ },
+ onSelected: (Result selection) {
+ _animatedMapController.mapController.move(
+ LatLng(selection.latitude, selection.longitude),
+ 14,
+ );
+ _controllerSearch.clear();
+ _focusNode.unfocus();
+ },
+ displayStringForOption:
+ (Result option) => '${option.name}, ${option.admin1}',
+ optionsViewBuilder: (
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ ) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ 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) {
+ final Result option = options.elementAt(index);
+ return InkWell(
+ onTap: () => onSelected(option),
+ child: ListTile(
+ title: Text(
+ '${option.name}, ${option.admin1}',
+ style: context.textTheme.labelLarge,
+ ),
+ ),
+ );
+ },
),
),
- )
- : const SizedBox.shrink();
+ ),
+ );
+ },
+ );
}
@override
@@ -241,7 +343,10 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
options: MapOptions(
backgroundColor: context.theme.colorScheme.surface,
initialCenter: LatLng(mainLocation.lat!, mainLocation.lon!),
- initialZoom: 12,
+ initialZoom: 8,
+ interactionOptions: const InteractionOptions(
+ flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
+ ),
cameraConstraint: CameraConstraint.contain(
bounds: LatLngBounds(
const LatLng(-90, -180),
@@ -249,15 +354,17 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
),
),
onTap: (_, __) => _hideCard(),
- onLongPress: (tapPosition, point) => showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- enableDrag: false,
- builder: (BuildContext context) => CreateWeatherCard(
- latitude: '${point.latitude}',
- longitude: '${point.longitude}',
- ),
- ),
+ onLongPress:
+ (tapPosition, point) => showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ enableDrag: false,
+ builder:
+ (BuildContext context) => CreatePlace(
+ latitude: '${point.latitude}',
+ longitude: '${point.longitude}',
+ ),
+ ),
),
children: [
if (_isDarkMode)
@@ -278,8 +385,10 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
attributions: [
TextSourceAttribution(
'OpenStreetMap contributors',
- onTap: () => weatherController
- .urlLauncher('https://openstreetmap.org/copyright'),
+ onTap:
+ () => weatherController.urlLauncher(
+ 'https://openstreetmap.org/copyright',
+ ),
),
],
),
@@ -293,18 +402,20 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
dayOfNow,
);
- final cardMarkers = weatherController.weatherCards
- .map((weatherCardList) =>
- _buildCardMarker(weatherCardList))
- .toList();
+ final cardMarkers =
+ weatherController.weatherCards
+ .map(
+ (weatherCardList) =>
+ _buildCardMarker(weatherCardList),
+ )
+ .toList();
- return MarkerLayer(
- markers: [mainMarker, ...cardMarkers],
- );
+ return MarkerLayer(markers: [mainMarker, ...cardMarkers]);
}),
ExpandableFab(
+ key: _fabKey,
pos: ExpandableFabPos.right,
- type: ExpandableFabType.fan,
+ type: ExpandableFabType.up,
distance: 70,
openButtonBuilder: RotateFloatingActionButtonBuilder(
child: const Icon(IconsaxPlusLinear.menu),
@@ -317,16 +428,33 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
children: [
FloatingActionButton(
heroTag: null,
- child: const Icon(IconsaxPlusLinear.gps),
- onPressed: () => _resetMapOrientation(
- center:
- LatLng(mainLocation.lat!, mainLocation.lon!),
- zoom: 12),
+ child: const Icon(IconsaxPlusLinear.home_2),
+ onPressed:
+ () => _resetMapOrientation(
+ center: LatLng(
+ mainLocation.lat!,
+ mainLocation.lon!,
+ ),
+ zoom: 8,
+ ),
),
FloatingActionButton(
heroTag: null,
- child: const Icon(IconsaxPlusLinear.refresh_2),
- onPressed: () => _resetMapOrientation(),
+ child: const Icon(IconsaxPlusLinear.search_zoom_out_1),
+ onPressed:
+ () => _animatedMapController.animatedZoomOut(
+ customId:
+ _useTransformer ? _useTransformerId : null,
+ ),
+ ),
+ FloatingActionButton(
+ heroTag: null,
+ child: const Icon(IconsaxPlusLinear.search_zoom_in),
+ onPressed:
+ () => _animatedMapController.animatedZoomIn(
+ customId:
+ _useTransformer ? _useTransformerId : null,
+ ),
),
],
),
@@ -338,82 +466,7 @@ class _MapWeatherState extends State with TickerProviderStateMixin {
),
],
),
- RawAutocomplete(
- focusNode: _focusNode,
- textEditingController: _controllerSearch,
- fieldViewBuilder: (BuildContext context,
- TextEditingController fieldTextEditingController,
- FocusNode fieldFocusNode,
- VoidCallback onFieldSubmitted) {
- return MyTextForm(
- labelText: 'search'.tr,
- type: TextInputType.text,
- icon: const Icon(IconsaxPlusLinear.global_search),
- controller: _controllerSearch,
- margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
- focusNode: _focusNode,
- onChanged: (value) => setState(() {}),
- iconButton: _controllerSearch.text.isNotEmpty
- ? IconButton(
- onPressed: () {
- _controllerSearch.clear();
- },
- icon: const Icon(
- IconsaxPlusLinear.close_circle,
- color: Colors.grey,
- size: 20,
- ),
- )
- : null,
- );
- },
- optionsBuilder: (TextEditingValue textEditingValue) {
- if (textEditingValue.text.isEmpty) {
- return const Iterable.empty();
- }
- return WeatherAPI().getCity(textEditingValue.text, locale);
- },
- onSelected: (Result selection) {
- _animatedMapController.mapController.move(
- LatLng(selection.latitude, selection.longitude), 14);
- _controllerSearch.clear();
- _focusNode.unfocus();
- },
- displayStringForOption: (Result option) =>
- '${option.name}, ${option.admin1}',
- optionsViewBuilder: (BuildContext context,
- AutocompleteOnSelected onSelected,
- Iterable options) {
- return Padding(
- padding:
- const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
- 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) {
- final Result option = options.elementAt(index);
- return InkWell(
- onTap: () => onSelected(option),
- child: ListTile(
- title: Text(
- '${option.name}, ${option.admin1}',
- style: context.textTheme.labelLarge,
- ),
- ),
- );
- },
- ),
- ),
- ),
- );
- },
- ),
+ _buildSearchField(),
],
);
},
diff --git a/lib/app/modules/onboarding.dart b/lib/app/ui/onboarding.dart
old mode 100644
new mode 100755
similarity index 51%
rename from lib/app/modules/onboarding.dart
rename to lib/app/ui/onboarding.dart
index 07a2999..29561c3
--- a/lib/app/modules/onboarding.dart
+++ b/lib/app/ui/onboarding.dart
@@ -1,9 +1,9 @@
-import 'package:gap/gap.dart';
-import 'package:rain/app/data/weather.dart';
-import 'package:rain/app/modules/geolocation.dart';
-import 'package:rain/app/widgets/button.dart';
-import 'package:rain/main.dart';
import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/geolocation.dart';
+import 'package:rain/app/ui/widgets/button.dart';
+import 'package:rain/main.dart';
import 'package:get/get.dart';
class OnBording extends StatefulWidget {
@@ -19,8 +19,8 @@ class _OnBordingState extends State {
@override
void initState() {
- pageController = PageController(initialPage: 0);
super.initState();
+ pageController = PageController(initialPage: 0);
}
@override
@@ -32,8 +32,10 @@ class _OnBordingState extends State {
void onBoardHome() {
settings.onboard = true;
isar.writeTxnSync(() => isar.settings.putSync(settings));
- Get.off(() => const SelectGeolocation(isStart: true),
- transition: Transition.downToUp);
+ Get.off(
+ () => const SelectGeolocation(isStart: true),
+ transition: Transition.downToUp,
+ );
}
@override
@@ -43,60 +45,69 @@ class _OnBordingState extends State {
body: SafeArea(
child: Column(
children: [
- Expanded(
- child: PageView.builder(
- controller: pageController,
- itemCount: data.length,
- onPageChanged: (index) {
- setState(() {
- pageIndex = index;
- });
- },
- itemBuilder: (context, index) => OnboardContent(
- image: data[index].image,
- title: data[index].title,
- description: data[index].description,
- ),
- ),
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- ...List.generate(
- data.length,
- (index) => Padding(
- padding: const EdgeInsets.symmetric(horizontal: 5),
- child: DotIndicator(isActive: index == pageIndex),
- )),
- ],
- ),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
- child: MyTextButton(
- buttonName:
- pageIndex == data.length - 1 ? 'start'.tr : 'next'.tr,
- onPressed: () {
- pageIndex == data.length - 1
- ? onBoardHome()
- : pageController.nextPage(
- duration: const Duration(milliseconds: 300),
- curve: Curves.ease,
- );
- },
- ),
- )
+ _buildPageView(),
+ _buildDotIndicators(),
+ _buildActionButton(),
],
),
),
);
}
+
+ Widget _buildPageView() {
+ return Expanded(
+ child: PageView.builder(
+ controller: pageController,
+ itemCount: data.length,
+ onPageChanged: (index) {
+ setState(() {
+ pageIndex = index;
+ });
+ },
+ itemBuilder: (context, index) => OnboardContent(
+ image: data[index].image,
+ title: data[index].title,
+ description: data[index].description,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildDotIndicators() {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: List.generate(
+ data.length,
+ (index) => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 5),
+ child: DotIndicator(isActive: index == pageIndex),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildActionButton() {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
+ child: MyTextButton(
+ buttonName: pageIndex == data.length - 1 ? 'start'.tr : 'next'.tr,
+ onPressed: () {
+ if (pageIndex == data.length - 1) {
+ onBoardHome();
+ } else {
+ pageController.nextPage(
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.ease,
+ );
+ }
+ },
+ ),
+ );
+ }
}
class DotIndicator extends StatelessWidget {
- const DotIndicator({
- super.key,
- this.isActive = false,
- });
+ const DotIndicator({super.key, this.isActive = false});
final bool isActive;
@@ -128,17 +139,20 @@ class Onboard {
final List data = [
Onboard(
- image: 'assets/icons/Rain.png',
- title: 'name'.tr,
- description: 'description'.tr),
+ image: 'assets/icons/Rain.png',
+ title: 'name'.tr,
+ description: 'description'.tr,
+ ),
Onboard(
- image: 'assets/icons/Design.png',
- title: 'name2'.tr,
- description: 'description2'.tr),
+ image: 'assets/icons/Design.png',
+ title: 'name2'.tr,
+ description: 'description2'.tr,
+ ),
Onboard(
- image: 'assets/icons/Team.png',
- title: 'name3'.tr,
- description: 'description3'.tr),
+ image: 'assets/icons/Team.png',
+ title: 'name3'.tr,
+ description: 'description3'.tr,
+ ),
];
class OnboardContent extends StatelessWidget {
@@ -148,6 +162,7 @@ class OnboardContent extends StatelessWidget {
required this.title,
required this.description,
});
+
final String image, title, description;
@override
@@ -158,14 +173,12 @@ class OnboardContent extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
- Image.asset(
- image,
- scale: 5,
- ),
+ Image.asset(image, scale: 5),
Text(
title,
- style: context.textTheme.titleLarge
- ?.copyWith(fontWeight: FontWeight.w600),
+ style: context.textTheme.titleLarge?.copyWith(
+ fontWeight: FontWeight.w600,
+ ),
),
const Gap(10),
SizedBox(
diff --git a/lib/app/ui/places/view/place_info.dart b/lib/app/ui/places/view/place_info.dart
new file mode 100755
index 0000000..f56f709
--- /dev/null
+++ b/lib/app/ui/places/view/place_info.dart
@@ -0,0 +1,212 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_card_list.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_container.dart';
+import 'package:rain/app/ui/widgets/weather/desc/desc_container.dart';
+import 'package:rain/app/ui/widgets/weather/hourly.dart';
+import 'package:rain/app/ui/widgets/weather/now.dart';
+import 'package:rain/app/ui/widgets/weather/sunset_sunrise.dart';
+import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
+
+class PlaceInfo extends StatefulWidget {
+ const PlaceInfo({super.key, required this.weatherCard});
+ final WeatherCard weatherCard;
+
+ @override
+ State createState() => _PlaceInfoState();
+}
+
+class _PlaceInfoState extends State {
+ int timeNow = 0;
+ int dayNow = 0;
+ final weatherController = Get.put(WeatherController());
+ final itemScrollController = ItemScrollController();
+
+ @override
+ void initState() {
+ getTime();
+ super.initState();
+ }
+
+ void getTime() {
+ final weatherCard = widget.weatherCard;
+
+ timeNow = weatherController.getTime(
+ weatherCard.time!,
+ weatherCard.timezone!,
+ );
+ dayNow = weatherController.getDay(
+ weatherCard.timeDaily!,
+ weatherCard.timezone!,
+ );
+ Future.delayed(const Duration(milliseconds: 30), () {
+ itemScrollController.scrollTo(
+ index: timeNow,
+ duration: const Duration(seconds: 2),
+ curve: Curves.easeInOutCubic,
+ );
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final weatherCard = widget.weatherCard;
+
+ return RefreshIndicator(
+ onRefresh: _handleRefresh,
+ child: Scaffold(
+ appBar: _buildAppBar(context, weatherCard),
+ body: SafeArea(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10),
+ child: ListView(
+ children: [
+ _buildNowWidget(weatherCard),
+ _buildHourlyList(weatherCard),
+ _buildSunsetSunriseWidget(weatherCard),
+ _buildHourlyDescContainer(weatherCard),
+ _buildDailyContainer(weatherCard),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Future _handleRefresh() async {
+ await weatherController.updateCard(widget.weatherCard);
+ getTime();
+ setState(() {});
+ }
+
+ AppBar _buildAppBar(BuildContext context, WeatherCard weatherCard) {
+ return AppBar(
+ centerTitle: true,
+ automaticallyImplyLeading: false,
+ leading: IconButton(
+ onPressed: () => Get.back(),
+ icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
+ ),
+ title: Text(
+ weatherCard.district!.isNotEmpty
+ ? '${weatherCard.city}, ${weatherCard.district}'
+ : '${weatherCard.city}',
+ style: context.textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildNowWidget(WeatherCard weatherCard) {
+ return Now(
+ time: weatherCard.time![timeNow],
+ weather: weatherCard.weathercode![timeNow],
+ degree: weatherCard.temperature2M![timeNow],
+ feels: weatherCard.apparentTemperature![timeNow]!,
+ timeDay: weatherCard.sunrise![dayNow],
+ timeNight: weatherCard.sunset![dayNow],
+ tempMax: weatherCard.temperature2MMax![dayNow]!,
+ tempMin: weatherCard.temperature2MMin![dayNow]!,
+ );
+ }
+
+ Widget _buildHourlyList(WeatherCard weatherCard) {
+ return Card(
+ margin: const EdgeInsets.only(bottom: 15),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ child: SizedBox(
+ height: 135,
+ child: ScrollablePositionedList.separated(
+ key: const PageStorageKey(1),
+ separatorBuilder: (BuildContext context, int index) {
+ return const VerticalDivider(
+ width: 10,
+ indent: 40,
+ endIndent: 40,
+ );
+ },
+ scrollDirection: Axis.horizontal,
+ itemScrollController: itemScrollController,
+ itemCount: weatherCard.time!.length,
+ itemBuilder: (ctx, i) => _buildHourlyItem(weatherCard, i),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildHourlyItem(WeatherCard weatherCard, int i) {
+ return GestureDetector(
+ onTap: () {
+ timeNow = i;
+ dayNow = (i / 24).floor();
+ setState(() {});
+ },
+ child: Container(
+ margin: const EdgeInsets.symmetric(vertical: 5),
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
+ decoration: BoxDecoration(
+ color:
+ i == timeNow
+ ? context.theme.colorScheme.secondaryContainer
+ : Colors.transparent,
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ ),
+ child: Hourly(
+ time: weatherCard.time![i],
+ weather: weatherCard.weathercode![i],
+ degree: weatherCard.temperature2M![i],
+ timeDay: weatherCard.sunrise![(i / 24).floor()],
+ timeNight: weatherCard.sunset![(i / 24).floor()],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSunsetSunriseWidget(WeatherCard weatherCard) {
+ return SunsetSunrise(
+ timeSunrise: weatherCard.sunrise![dayNow],
+ timeSunset: weatherCard.sunset![dayNow],
+ );
+ }
+
+ Widget _buildHourlyDescContainer(WeatherCard weatherCard) {
+ return DescContainer(
+ humidity: weatherCard.relativehumidity2M?[timeNow],
+ wind: weatherCard.windspeed10M?[timeNow],
+ visibility: weatherCard.visibility?[timeNow],
+ feels: weatherCard.apparentTemperature?[timeNow],
+ evaporation: weatherCard.evapotranspiration?[timeNow],
+ precipitation: weatherCard.precipitation?[timeNow],
+ direction: weatherCard.winddirection10M?[timeNow],
+ pressure: weatherCard.surfacePressure?[timeNow],
+ rain: weatherCard.rain?[timeNow],
+ cloudcover: weatherCard.cloudcover?[timeNow],
+ windgusts: weatherCard.windgusts10M?[timeNow],
+ uvIndex: weatherCard.uvIndex?[timeNow],
+ dewpoint2M: weatherCard.dewpoint2M?[timeNow],
+ precipitationProbability: weatherCard.precipitationProbability?[timeNow],
+ shortwaveRadiation: weatherCard.shortwaveRadiation?[timeNow],
+ initiallyExpanded: false,
+ title: 'hourlyVariables'.tr,
+ );
+ }
+
+ Widget _buildDailyContainer(WeatherCard weatherCard) {
+ return DailyContainer(
+ weatherData: weatherCard,
+ onTap:
+ () => Get.to(
+ () => DailyCardList(weatherData: weatherCard),
+ transition: Transition.downToUp,
+ ),
+ );
+ }
+}
diff --git a/lib/app/ui/places/view/place_list.dart b/lib/app/ui/places/view/place_list.dart
new file mode 100755
index 0000000..7fafd5a
--- /dev/null
+++ b/lib/app/ui/places/view/place_list.dart
@@ -0,0 +1,116 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/ui/places/widgets/place_card_list.dart';
+import 'package:rain/app/ui/widgets/text_form.dart';
+
+class PlaceList extends StatefulWidget {
+ const PlaceList({super.key});
+
+ @override
+ State createState() => _PlaceListState();
+}
+
+class _PlaceListState extends State {
+ final weatherController = Get.put(WeatherController());
+ final TextEditingController searchTasks = TextEditingController();
+ String filter = '';
+
+ @override
+ void initState() {
+ super.initState();
+ applyFilter('');
+ }
+
+ void applyFilter(String value) {
+ filter = value.toLowerCase();
+ setState(() {});
+ }
+
+ void clearSearch() {
+ searchTasks.clear();
+ applyFilter('');
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final textTheme = context.textTheme;
+ final titleMedium = textTheme.titleMedium;
+
+ return Obx(() => _buildContent(context, titleMedium));
+ }
+
+ Widget _buildContent(BuildContext context, TextStyle? titleMedium) {
+ if (weatherController.weatherCards.isEmpty) {
+ return _buildEmptyState(context, titleMedium);
+ } else {
+ return _buildListView(context);
+ }
+ }
+
+ Widget _buildEmptyState(BuildContext context, TextStyle? titleMedium) {
+ return Center(
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ Image.asset('assets/icons/City.png', scale: 6),
+ SizedBox(
+ width: Get.size.width * 0.8,
+ child: Text(
+ 'noWeatherCard'.tr,
+ textAlign: TextAlign.center,
+ style: titleMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildListView(BuildContext context) {
+ return NestedScrollView(
+ physics: const NeverScrollableScrollPhysics(),
+ headerSliverBuilder: (context, innerBoxIsScrolled) {
+ return [_buildSearchField(context)];
+ },
+ body: RefreshIndicator(
+ onRefresh: _handleRefresh,
+ child: PlaceCardList(searchCity: filter),
+ ),
+ );
+ }
+
+ Widget _buildSearchField(BuildContext context) {
+ return SliverToBoxAdapter(
+ child: MyTextForm(
+ labelText: 'search'.tr,
+ type: TextInputType.text,
+ icon: const Icon(IconsaxPlusLinear.search_normal_1, size: 20),
+ controller: searchTasks,
+ margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ onChanged: applyFilter,
+ iconButton:
+ searchTasks.text.isNotEmpty
+ ? IconButton(
+ onPressed: clearSearch,
+ icon: const Icon(
+ IconsaxPlusLinear.close_circle,
+ color: Colors.grey,
+ size: 20,
+ ),
+ )
+ : null,
+ ),
+ );
+ }
+
+ Future _handleRefresh() async {
+ await weatherController.updateCacheCard(true);
+ setState(() {});
+ }
+}
diff --git a/lib/app/ui/places/widgets/create_place.dart b/lib/app/ui/places/widgets/create_place.dart
new file mode 100755
index 0000000..0ef872a
--- /dev/null
+++ b/lib/app/ui/places/widgets/create_place.dart
@@ -0,0 +1,346 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:rain/app/api/api.dart';
+import 'package:rain/app/api/city_api.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/ui/widgets/button.dart';
+import 'package:rain/app/ui/widgets/text_form.dart';
+import 'package:rain/main.dart';
+
+class CreatePlace extends StatefulWidget {
+ const CreatePlace({super.key, this.latitude, this.longitude});
+ final String? latitude;
+ final String? longitude;
+
+ @override
+ State createState() => _CreatePlaceState();
+}
+
+class _CreatePlaceState extends State
+ with SingleTickerProviderStateMixin {
+ bool isLoading = false;
+ final formKey = GlobalKey();
+ final _focusNode = FocusNode();
+ final weatherController = Get.put(WeatherController());
+
+ static const kTextFieldElevation = 4.0;
+
+ late TextEditingController _controller;
+ late TextEditingController _controllerLat;
+ late TextEditingController _controllerLon;
+ late TextEditingController _controllerCity;
+ late TextEditingController _controllerDistrict;
+
+ late AnimationController _animationController;
+ late Animation _animation;
+
+ @override
+ void initState() {
+ super.initState();
+ _controller = TextEditingController();
+ _controllerLat = TextEditingController(text: widget.latitude);
+ _controllerLon = TextEditingController(text: widget.longitude);
+ _controllerCity = TextEditingController();
+ _controllerDistrict = TextEditingController();
+
+ _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) {
+ value.text = value.text.trim();
+ while (value.text.contains(' ')) {
+ value.text = value.text.replaceAll(' ', ' ');
+ }
+ }
+
+ void fillController(Result selection) {
+ _controllerLat.text = '${selection.latitude}';
+ _controllerLon.text = '${selection.longitude}';
+ _controllerCity.text = selection.name;
+ _controllerDistrict.text = selection.admin1;
+ _controller.clear();
+ _focusNode.unfocus();
+ setState(() {});
+ }
+
+ bool get showButton {
+ return _controllerLon.text.isNotEmpty &&
+ _controllerLat.text.isNotEmpty &&
+ _controllerCity.text.isNotEmpty &&
+ _controllerDistrict.text.isNotEmpty;
+ }
+
+ void updateButtonVisibility() {
+ if (showButton) {
+ _animationController.forward();
+ } else {
+ _animationController.reverse();
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ updateButtonVisibility();
+
+ 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: [
+ _buildTitleText(),
+ _buildSearchField(),
+ _buildLatitudeField(),
+ _buildLongitudeField(),
+ _buildCityField(),
+ _buildDistrictField(),
+ _buildSubmitButton(),
+ ],
+ ),
+ ),
+ if (isLoading) const Center(child: CircularProgressIndicator()),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildTitleText() {
+ return Padding(
+ padding: const EdgeInsets.only(top: 14, bottom: 7),
+ child: Text(
+ 'create'.tr,
+ style: context.textTheme.titleLarge?.copyWith(
+ fontWeight: FontWeight.bold,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ );
+ }
+
+ Widget _buildSearchField() {
+ return RawAutocomplete(
+ focusNode: _focusNode,
+ textEditingController: _controller,
+ fieldViewBuilder: (
+ BuildContext context,
+ TextEditingController fieldTextEditingController,
+ FocusNode fieldFocusNode,
+ VoidCallback onFieldSubmitted,
+ ) {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ labelText: 'search'.tr,
+ type: TextInputType.text,
+ icon: const Icon(IconsaxPlusLinear.global_search),
+ controller: _controller,
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ focusNode: _focusNode,
+ );
+ },
+ optionsBuilder: (TextEditingValue textEditingValue) {
+ if (textEditingValue.text.isEmpty) {
+ return const Iterable.empty();
+ }
+ return WeatherAPI().getCity(textEditingValue.text, locale);
+ },
+ onSelected: (Result selection) => fillController(selection),
+ displayStringForOption:
+ (Result option) => '${option.name}, ${option.admin1}',
+ optionsViewBuilder: (
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ ) {
+ return _buildOptionsView(context, onSelected, options);
+ },
+ );
+ }
+
+ Widget _buildOptionsView(
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable 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) {
+ final Result option = options.elementAt(index);
+ return InkWell(
+ onTap: () => onSelected(option),
+ child: ListTile(
+ title: Text(
+ '${option.name}, ${option.admin1}',
+ style: context.textTheme.labelLarge,
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildLatitudeField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerLat,
+ labelText: 'lat'.tr,
+ type: TextInputType.number,
+ icon: const Icon(IconsaxPlusLinear.location),
+ onChanged: (value) => setState(() {}),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ validator: (value) => _validateLatitude(value),
+ );
+ }
+
+ Widget _buildLongitudeField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerLon,
+ labelText: 'lon'.tr,
+ type: TextInputType.number,
+ icon: const Icon(IconsaxPlusLinear.location),
+ onChanged: (value) => setState(() {}),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ validator: (value) => _validateLongitude(value),
+ );
+ }
+
+ Widget _buildCityField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerCity,
+ labelText: 'city'.tr,
+ type: TextInputType.name,
+ icon: const Icon(IconsaxPlusLinear.building_3),
+ onChanged: (value) => setState(() {}),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ validator: (value) => _validateCity(value),
+ );
+ }
+
+ Widget _buildDistrictField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerDistrict,
+ labelText: 'district'.tr,
+ type: TextInputType.streetAddress,
+ icon: const Icon(IconsaxPlusLinear.global),
+ onChanged: (value) => setState(() {}),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ validator: (value) => _validateDistrict(value),
+ );
+ }
+
+ Widget _buildSubmitButton() {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
+ child: SizeTransition(
+ sizeFactor: _animation,
+ axisAlignment: -1.0,
+ child: MyTextButton(buttonName: 'done'.tr, onPressed: _handleSubmit),
+ ),
+ );
+ }
+
+ String? _validateLatitude(String? 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;
+ }
+
+ String? _validateLongitude(String? 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;
+ }
+
+ String? _validateCity(String? value) {
+ if (value == null || value.isEmpty) {
+ return 'validateName'.tr;
+ }
+ return null;
+ }
+
+ String? _validateDistrict(String? value) {
+ if (value == null || value.isEmpty) {
+ return 'validateName'.tr;
+ }
+ return null;
+ }
+
+ Future _handleSubmit() 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();
+ }
+ }
+}
diff --git a/lib/app/ui/places/widgets/place_card.dart b/lib/app/ui/places/widgets/place_card.dart
new file mode 100755
index 0000000..3e35537
--- /dev/null
+++ b/lib/app/ui/places/widgets/place_card.dart
@@ -0,0 +1,155 @@
+import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
+import 'package:get/get.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/ui/widgets/weather/status/status_weather.dart';
+import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
+import 'package:timezone/standalone.dart' as tz;
+
+class PlaceCard extends StatefulWidget {
+ const PlaceCard({
+ super.key,
+ required this.time,
+ required this.weather,
+ required this.degree,
+ required this.district,
+ required this.city,
+ required this.timezone,
+ required this.timeDay,
+ required this.timeNight,
+ required this.timeDaily,
+ });
+
+ final List time;
+ final List timeDay;
+ final List timeNight;
+ final List timeDaily;
+ final String district;
+ final String city;
+ final List weather;
+ final List degree;
+ final String timezone;
+
+ @override
+ State createState() => _PlaceCardState();
+}
+
+class _PlaceCardState extends State {
+ final statusWeather = StatusWeather();
+ final statusData = StatusData();
+ final weatherController = Get.put(WeatherController());
+
+ @override
+ Widget build(BuildContext context) {
+ final currentTimeIndex = weatherController.getTime(
+ widget.time,
+ widget.timezone,
+ );
+ final currentDayIndex = weatherController.getDay(
+ widget.timeDaily,
+ widget.timezone,
+ );
+
+ return Card(
+ margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
+ child: Row(
+ children: [
+ _buildWeatherInfo(context, currentTimeIndex, currentDayIndex),
+ const Gap(5),
+ _buildWeatherImage(currentTimeIndex, currentDayIndex),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildWeatherInfo(
+ BuildContext context,
+ int currentTimeIndex,
+ int currentDayIndex,
+ ) {
+ return Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Text(
+ statusData.getDegree(
+ widget.degree[currentTimeIndex].round().toInt(),
+ ),
+ style: context.textTheme.titleLarge?.copyWith(
+ fontSize: 22,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ const Gap(7),
+ Text(
+ statusWeather.getText(widget.weather[currentTimeIndex]),
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.grey,
+ fontWeight: FontWeight.w400,
+ ),
+ ),
+ ],
+ ),
+ const Gap(10),
+ _buildLocationText(),
+ const Gap(5),
+ _buildCurrentTimeText(context),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildLocationText() {
+ String locationText;
+
+ if (widget.district.isEmpty) {
+ locationText = widget.city;
+ } else if (widget.city.isEmpty) {
+ locationText = widget.district;
+ } else if (widget.city == widget.district) {
+ locationText = widget.city;
+ } else {
+ locationText = '${widget.city}, ${widget.district}';
+ }
+
+ return Text(
+ locationText,
+ style: context.textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.w500,
+ ),
+ );
+ }
+
+ Widget _buildCurrentTimeText(BuildContext context) {
+ return StreamBuilder(
+ stream: Stream.periodic(const Duration(seconds: 1)),
+ builder: (context, snapshot) {
+ return Text(
+ '${'time'.tr}: ${statusData.getTimeFormatTz(tz.TZDateTime.now(tz.getLocation(widget.timezone)))}',
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.grey,
+ fontWeight: FontWeight.w400,
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildWeatherImage(int currentTimeIndex, int currentDayIndex) {
+ return Image.asset(
+ statusWeather.getImageNow(
+ widget.weather[currentTimeIndex],
+ widget.time[currentTimeIndex],
+ widget.timeDay[currentDayIndex],
+ widget.timeNight[currentDayIndex],
+ ),
+ scale: 6.5,
+ );
+ }
+}
diff --git a/lib/app/ui/places/widgets/place_card_list.dart b/lib/app/ui/places/widgets/place_card_list.dart
new file mode 100755
index 0000000..6f1a0b1
--- /dev/null
+++ b/lib/app/ui/places/widgets/place_card_list.dart
@@ -0,0 +1,155 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/places/view/place_info.dart';
+import 'package:rain/app/ui/places/widgets/place_card.dart';
+
+class PlaceCardList extends StatefulWidget {
+ const PlaceCardList({super.key, required this.searchCity});
+ final String searchCity;
+
+ @override
+ State createState() => _PlaceCardListState();
+}
+
+class _PlaceCardListState extends State {
+ final weatherController = Get.put(WeatherController());
+
+ @override
+ Widget build(BuildContext context) {
+ final textTheme = context.textTheme;
+ final titleMedium = textTheme.titleMedium;
+
+ final weatherCards = _filterWeatherCards(
+ weatherController.weatherCards,
+ widget.searchCity,
+ );
+
+ return ReorderableListView(
+ onReorder:
+ (oldIndex, newIndex) => weatherController.reorder(oldIndex, newIndex),
+ children: _buildWeatherCardList(
+ weatherCards,
+ context,
+ textTheme,
+ titleMedium,
+ ),
+ );
+ }
+
+ List _filterWeatherCards(
+ List weatherCards,
+ String searchCity,
+ ) {
+ return weatherCards
+ .where(
+ (weatherCard) =>
+ (searchCity.isEmpty ||
+ weatherCard.city!.toLowerCase().contains(searchCity)),
+ )
+ .toList();
+ }
+
+ List _buildWeatherCardList(
+ List weatherCards,
+ BuildContext context,
+ TextTheme textTheme,
+ TextStyle? titleMedium,
+ ) {
+ return weatherCards
+ .map(
+ (weatherCardList) => _buildDismissibleCard(
+ context,
+ weatherCardList,
+ textTheme,
+ titleMedium,
+ ),
+ )
+ .toList();
+ }
+
+ Widget _buildDismissibleCard(
+ BuildContext context,
+ WeatherCard weatherCardList,
+ TextTheme textTheme,
+ TextStyle? titleMedium,
+ ) {
+ return Dismissible(
+ key: ValueKey(weatherCardList),
+ direction: DismissDirection.endToStart,
+ background: _buildDismissibleBackground(),
+ confirmDismiss:
+ (DismissDirection direction) =>
+ _showDeleteConfirmationDialog(context, textTheme, titleMedium),
+ onDismissed: (DismissDirection direction) async {
+ await weatherController.deleteCardWeather(weatherCardList);
+ },
+ child: _buildCardGestureDetector(weatherCardList),
+ );
+ }
+
+ Widget _buildDismissibleBackground() {
+ return Container(
+ alignment: Alignment.centerRight,
+ child: const Padding(
+ padding: EdgeInsets.only(right: 15),
+ child: Icon(IconsaxPlusLinear.trash_square, color: Colors.red),
+ ),
+ );
+ }
+
+ Future _showDeleteConfirmationDialog(
+ BuildContext context,
+ TextTheme textTheme,
+ TextStyle? titleMedium,
+ ) async {
+ return await showAdaptiveDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog.adaptive(
+ title: Text('deletedCardWeather'.tr, style: textTheme.titleLarge),
+ content: Text('deletedCardWeatherQuery'.tr, style: titleMedium),
+ actions: [
+ TextButton(
+ onPressed: () => Get.back(result: false),
+ child: Text(
+ 'cancel'.tr,
+ style: titleMedium?.copyWith(color: Colors.blueAccent),
+ ),
+ ),
+ TextButton(
+ onPressed: () => Get.back(result: true),
+ child: Text(
+ 'delete'.tr,
+ style: titleMedium?.copyWith(color: Colors.red),
+ ),
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ Widget _buildCardGestureDetector(WeatherCard weatherCardList) {
+ return GestureDetector(
+ onTap:
+ () => Get.to(
+ () => PlaceInfo(weatherCard: weatherCardList),
+ transition: Transition.downToUp,
+ ),
+ child: PlaceCard(
+ time: weatherCardList.time!,
+ timeDaily: weatherCardList.timeDaily!,
+ timeDay: weatherCardList.sunrise!,
+ timeNight: weatherCardList.sunset!,
+ weather: weatherCardList.weathercode!,
+ degree: weatherCardList.temperature2M!,
+ district: weatherCardList.district!,
+ city: weatherCardList.city!,
+ timezone: weatherCardList.timezone!,
+ ),
+ );
+ }
+}
diff --git a/lib/app/ui/settings/view/settings.dart b/lib/app/ui/settings/view/settings.dart
new file mode 100755
index 0000000..884e284
--- /dev/null
+++ b/lib/app/ui/settings/view/settings.dart
@@ -0,0 +1,1275 @@
+import 'dart:io';
+import 'package:flutter/material.dart';
+import 'package:flutter_hsvcolor_picker/flutter_hsvcolor_picker.dart';
+import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+import 'package:gap/gap.dart';
+import 'package:geolocator/geolocator.dart';
+import 'package:get/get.dart';
+import 'package:home_widget/home_widget.dart';
+import 'package:http_cache_file_store/http_cache_file_store.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:intl/intl.dart';
+import 'package:line_awesome_flutter/line_awesome_flutter.dart';
+import 'package:package_info_plus/package_info_plus.dart';
+import 'package:path_provider/path_provider.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/settings/widgets/setting_card.dart';
+import 'package:rain/main.dart';
+import 'package:rain/theme/theme_controller.dart';
+import 'package:rain/app/utils/color_converter.dart';
+import 'package:restart_app/restart_app.dart';
+
+class SettingsPage extends StatefulWidget {
+ const SettingsPage({super.key});
+
+ @override
+ State createState() => _SettingsPageState();
+}
+
+class _SettingsPageState extends State {
+ final themeController = Get.put(ThemeController());
+ final weatherController = Get.put(WeatherController());
+ String? appVersion;
+ String? colorBackground;
+ String? colorText;
+
+ @override
+ void initState() {
+ super.initState();
+ _infoVersion();
+ }
+
+ Future _infoVersion() async {
+ final packageInfo = await PackageInfo.fromPlatform();
+ setState(() {
+ appVersion = packageInfo.version;
+ });
+ }
+
+ void _updateLanguage(Locale locale) {
+ settings.language = '$locale';
+ isar.writeTxnSync(() => isar.settings.putSync(settings));
+ Get.updateLocale(locale);
+ Get.back();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ _buildAppearanceCard(context),
+ _buildFunctionsCard(context),
+ _buildDataCard(context),
+ _buildWidgetCard(context),
+ _buildMapCard(context),
+ _buildLanguageCard(context),
+ _buildGroupsCard(context),
+ _buildLicenseCard(context),
+ _buildVersionCard(context),
+ _buildGitHubCard(context),
+ _buildOpenMeteoText(context),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildAppearanceCard(BuildContext context) {
+ return SettingCard(
+ icon: const Icon(IconsaxPlusLinear.brush_1),
+ text: 'appearance'.tr,
+ onPressed: () {
+ _showAppearanceBottomSheet(context);
+ },
+ );
+ }
+
+ void _showAppearanceBottomSheet(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ builder: (BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.only(
+ bottom: MediaQuery.of(context).padding.bottom,
+ ),
+ child: StatefulBuilder(
+ builder: (BuildContext context, setState) {
+ return SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ _buildAppearanceTitle(context),
+ _buildThemeSettingCard(context, setState),
+ _buildAmoledThemeSettingCard(context, setState),
+ _buildMaterialColorSettingCard(context, setState),
+ _buildLargeElementSettingCard(context, setState),
+ const Gap(10),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildAppearanceTitle(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ child: Text(
+ 'appearance'.tr,
+ style: context.textTheme.titleLarge?.copyWith(fontSize: 20),
+ ),
+ );
+ }
+
+ Widget _buildThemeSettingCard(BuildContext context, StateSetter setState) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.moon),
+ text: 'theme'.tr,
+ dropdown: true,
+ dropdownName: settings.theme?.tr,
+ dropdownList: ['system'.tr, 'dark'.tr, 'light'.tr],
+ dropdownChange: (String? newValue) {
+ _updateTheme(newValue, context, setState);
+ },
+ );
+ }
+
+ void _updateTheme(
+ String? newValue,
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ ThemeMode themeMode = newValue?.tr == 'system'.tr
+ ? ThemeMode.system
+ : newValue?.tr == 'dark'.tr
+ ? ThemeMode.dark
+ : ThemeMode.light;
+ String theme = newValue?.tr == 'system'.tr
+ ? 'system'
+ : newValue?.tr == 'dark'.tr
+ ? 'dark'
+ : 'light';
+ themeController.saveTheme(theme);
+ themeController.changeThemeMode(themeMode);
+ setState(() {});
+ }
+
+ Widget _buildAmoledThemeSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.mobile),
+ text: 'amoledTheme'.tr,
+ switcher: true,
+ value: settings.amoledTheme,
+ onChange: (value) {
+ themeController.saveOledTheme(value);
+ MyApp.updateAppState(context, newAmoledTheme: value);
+ },
+ );
+ }
+
+ Widget _buildMaterialColorSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.colorfilter),
+ text: 'materialColor'.tr,
+ switcher: true,
+ value: settings.materialColor,
+ onChange: (value) {
+ themeController.saveMaterialTheme(value);
+ MyApp.updateAppState(context, newMaterialColor: value);
+ },
+ );
+ }
+
+ Widget _buildLargeElementSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.additem),
+ text: 'largeElement'.tr,
+ switcher: true,
+ value: settings.largeElement,
+ onChange: (value) {
+ settings.largeElement = value;
+ isar.writeTxnSync(() => isar.settings.putSync(settings));
+ MyApp.updateAppState(context, newLargeElement: value);
+ setState(() {});
+ },
+ );
+ }
+
+ Widget _buildFunctionsCard(BuildContext context) {
+ return SettingCard(
+ icon: const Icon(IconsaxPlusLinear.code_1),
+ text: 'functions'.tr,
+ onPressed: () {
+ _showFunctionsBottomSheet(context);
+ },
+ );
+ }
+
+ void _showFunctionsBottomSheet(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ builder: (BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.only(
+ bottom: MediaQuery.of(context).padding.bottom,
+ ),
+ child: StatefulBuilder(
+ builder: (BuildContext context, setState) {
+ return SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ _buildFunctionsTitle(context),
+ _buildLocationSettingCard(context, setState),
+ _buildNotificationsSettingCard(context, setState),
+ _buildTimeRangeSettingCard(context, setState),
+ _buildTimeStartSettingCard(context, setState),
+ _buildTimeEndSettingCard(context, setState),
+ const Gap(10),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildFunctionsTitle(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ child: Text(
+ 'functions'.tr,
+ style: context.textTheme.titleLarge?.copyWith(fontSize: 20),
+ ),
+ );
+ }
+
+ Widget _buildLocationSettingCard(BuildContext context, StateSetter setState) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.map),
+ text: 'location'.tr,
+ switcher: true,
+ value: settings.location,
+ onChange: (value) async {
+ if (value) {
+ bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
+ if (!serviceEnabled) {
+ if (!context.mounted) return;
+ await _showLocationDialog(context);
+ return;
+ }
+ weatherController.getCurrentLocation();
+ }
+ isar.writeTxnSync(() {
+ settings.location = value;
+ isar.settings.putSync(settings);
+ });
+ setState(() {});
+ },
+ );
+ }
+
+ Future _showLocationDialog(BuildContext context) async {
+ await showAdaptiveDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog.adaptive(
+ title: Text('location'.tr, style: context.textTheme.titleLarge),
+ content: Text('no_location'.tr, style: context.textTheme.titleMedium),
+ actions: [
+ TextButton(
+ onPressed: () => Get.back(result: false),
+ child: Text(
+ 'cancel'.tr,
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.blueAccent,
+ ),
+ ),
+ ),
+ TextButton(
+ onPressed: () {
+ Geolocator.openLocationSettings();
+ Get.back(result: true);
+ },
+ child: Text(
+ 'settings'.tr,
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.green,
+ ),
+ ),
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ Widget _buildNotificationsSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.notification_1),
+ text: 'notifications'.tr,
+ switcher: true,
+ value: settings.notifications,
+ onChange: (value) async {
+ final resultExact = await flutterLocalNotificationsPlugin
+ .resolvePlatformSpecificImplementation<
+ AndroidFlutterLocalNotificationsPlugin
+ >()
+ ?.requestExactAlarmsPermission();
+ final result = Platform.isIOS
+ ? await flutterLocalNotificationsPlugin
+ .resolvePlatformSpecificImplementation<
+ IOSFlutterLocalNotificationsPlugin
+ >()
+ ?.requestPermissions()
+ : await flutterLocalNotificationsPlugin
+ .resolvePlatformSpecificImplementation<
+ AndroidFlutterLocalNotificationsPlugin
+ >()
+ ?.requestNotificationsPermission();
+ if (result != null && resultExact != null) {
+ isar.writeTxnSync(() {
+ settings.notifications = value;
+ isar.settings.putSync(settings);
+ });
+ if (value) {
+ weatherController.notification(weatherController.mainWeather);
+ } else {
+ flutterLocalNotificationsPlugin.cancelAll();
+ }
+ setState(() {});
+ }
+ },
+ );
+ }
+
+ Widget _buildTimeRangeSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.notification_status),
+ text: 'timeRange'.tr,
+ dropdown: true,
+ dropdownName: '$timeRange',
+ dropdownList: const ['1', '2', '3', '4', '5'],
+ dropdownChange: (String? newValue) {
+ isar.writeTxnSync(() {
+ settings.timeRange = int.parse(newValue!);
+ isar.settings.putSync(settings);
+ });
+ MyApp.updateAppState(context, newTimeRange: int.parse(newValue!));
+ if (settings.notifications) {
+ flutterLocalNotificationsPlugin.cancelAll();
+ weatherController.notification(weatherController.mainWeather);
+ }
+ },
+ );
+ }
+
+ Widget _buildTimeStartSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.timer_start),
+ text: 'timeStart'.tr,
+ info: true,
+ infoSettings: true,
+ infoWidget: _TextInfo(
+ info: settings.timeformat == '12'
+ ? DateFormat.jm(locale.languageCode).format(
+ DateFormat.Hm(locale.languageCode).parse(
+ weatherController.timeConvert(timeStart).format(context),
+ ),
+ )
+ : DateFormat.Hm(locale.languageCode).format(
+ DateFormat.Hm(locale.languageCode).parse(
+ weatherController.timeConvert(timeStart).format(context),
+ ),
+ ),
+ ),
+ onPressed: () async {
+ final TimeOfDay? timeStartPicker = await showTimePicker(
+ context: context,
+ initialTime: weatherController.timeConvert(timeStart),
+ builder: (context, child) {
+ final Widget mediaQueryWrapper = MediaQuery(
+ data: MediaQuery.of(context).copyWith(
+ alwaysUse24HourFormat: settings.timeformat == '12'
+ ? false
+ : true,
+ ),
+ child: child!,
+ );
+ return mediaQueryWrapper;
+ },
+ );
+ if (timeStartPicker != null) {
+ isar.writeTxnSync(() {
+ settings.timeStart = timeStartPicker.format(context);
+ isar.settings.putSync(settings);
+ });
+ if (!context.mounted) return;
+ MyApp.updateAppState(
+ context,
+ newTimeStart: timeStartPicker.format(context),
+ );
+ if (settings.notifications) {
+ flutterLocalNotificationsPlugin.cancelAll();
+ weatherController.notification(weatherController.mainWeather);
+ }
+ }
+ },
+ );
+ }
+
+ Widget _buildTimeEndSettingCard(BuildContext context, StateSetter setState) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.timer_pause),
+ text: 'timeEnd'.tr,
+ info: true,
+ infoSettings: true,
+ infoWidget: _TextInfo(
+ info: settings.timeformat == '12'
+ ? DateFormat.jm(locale.languageCode).format(
+ DateFormat.Hm(
+ locale.languageCode,
+ ).parse(weatherController.timeConvert(timeEnd).format(context)),
+ )
+ : DateFormat.Hm(locale.languageCode).format(
+ DateFormat.Hm(
+ locale.languageCode,
+ ).parse(weatherController.timeConvert(timeEnd).format(context)),
+ ),
+ ),
+ onPressed: () async {
+ final TimeOfDay? timeEndPicker = await showTimePicker(
+ context: context,
+ initialTime: weatherController.timeConvert(timeEnd),
+ builder: (context, child) {
+ final Widget mediaQueryWrapper = MediaQuery(
+ data: MediaQuery.of(context).copyWith(
+ alwaysUse24HourFormat: settings.timeformat == '12'
+ ? false
+ : true,
+ ),
+ child: child!,
+ );
+ return mediaQueryWrapper;
+ },
+ );
+ if (timeEndPicker != null) {
+ isar.writeTxnSync(() {
+ settings.timeEnd = timeEndPicker.format(context);
+ isar.settings.putSync(settings);
+ });
+ if (!context.mounted) return;
+ MyApp.updateAppState(
+ context,
+ newTimeEnd: timeEndPicker.format(context),
+ );
+ if (settings.notifications) {
+ flutterLocalNotificationsPlugin.cancelAll();
+ weatherController.notification(weatherController.mainWeather);
+ }
+ }
+ },
+ );
+ }
+
+ Widget _buildDataCard(BuildContext context) {
+ return SettingCard(
+ icon: const Icon(IconsaxPlusLinear.d_square),
+ text: 'data'.tr,
+ onPressed: () {
+ _showDataBottomSheet(context);
+ },
+ );
+ }
+
+ void _showDataBottomSheet(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ builder: (BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.only(
+ bottom: MediaQuery.of(context).padding.bottom,
+ ),
+ child: StatefulBuilder(
+ builder: (BuildContext context, setState) {
+ return SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ _buildDataTitle(context),
+ _buildRoundDegreeSettingCard(context, setState),
+ _buildDegreesSettingCard(context, setState),
+ _buildMeasurementsSettingCard(context, setState),
+ _buildWindSettingCard(context, setState),
+ _buildPressureSettingCard(context, setState),
+ _buildTimeFormatSettingCard(context, setState),
+ const Gap(10),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildDataTitle(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ child: Text(
+ 'data'.tr,
+ style: context.textTheme.titleLarge?.copyWith(fontSize: 20),
+ ),
+ );
+ }
+
+ Widget _buildRoundDegreeSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.cloud_notif),
+ text: 'roundDegree'.tr,
+ switcher: true,
+ value: settings.roundDegree,
+ onChange: (value) {
+ settings.roundDegree = value;
+ isar.writeTxnSync(() => isar.settings.putSync(settings));
+ MyApp.updateAppState(context, newRoundDegree: value);
+ setState(() {});
+ },
+ );
+ }
+
+ Widget _buildDegreesSettingCard(BuildContext context, StateSetter setState) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.sun_1),
+ text: 'degrees'.tr,
+ dropdown: true,
+ dropdownName: settings.degrees.tr,
+ dropdownList: ['celsius'.tr, 'fahrenheit'.tr],
+ dropdownChange: (String? newValue) async {
+ isar.writeTxnSync(() {
+ settings.degrees = newValue == 'celsius'.tr
+ ? 'celsius'
+ : 'fahrenheit';
+ isar.settings.putSync(settings);
+ });
+ await weatherController.deleteAll(false);
+ await weatherController.setLocation();
+ await weatherController.updateCacheCard(true);
+ setState(() {});
+ },
+ );
+ }
+
+ Widget _buildMeasurementsSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.rulerpen),
+ text: 'measurements'.tr,
+ dropdown: true,
+ dropdownName: settings.measurements.tr,
+ dropdownList: ['metric'.tr, 'imperial'.tr],
+ dropdownChange: (String? newValue) async {
+ isar.writeTxnSync(() {
+ settings.measurements = newValue == 'metric'.tr
+ ? 'metric'
+ : 'imperial';
+ isar.settings.putSync(settings);
+ });
+ await weatherController.deleteAll(false);
+ await weatherController.setLocation();
+ await weatherController.updateCacheCard(true);
+ setState(() {});
+ },
+ );
+ }
+
+ Widget _buildWindSettingCard(BuildContext context, StateSetter setState) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.wind),
+ text: 'wind'.tr,
+ dropdown: true,
+ dropdownName: settings.wind.tr,
+ dropdownList: ['kph'.tr, 'm/s'.tr],
+ dropdownChange: (String? newValue) async {
+ isar.writeTxnSync(() {
+ settings.wind = newValue == 'kph'.tr ? 'kph' : 'm/s';
+ isar.settings.putSync(settings);
+ });
+ setState(() {});
+ },
+ );
+ }
+
+ Widget _buildPressureSettingCard(BuildContext context, StateSetter setState) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.ruler),
+ text: 'pressure'.tr,
+ dropdown: true,
+ dropdownName: settings.pressure.tr,
+ dropdownList: ['hPa'.tr, 'mmHg'.tr],
+ dropdownChange: (String? newValue) async {
+ isar.writeTxnSync(() {
+ settings.pressure = newValue == 'hPa'.tr ? 'hPa' : 'mmHg';
+ isar.settings.putSync(settings);
+ });
+ setState(() {});
+ },
+ );
+ }
+
+ Widget _buildTimeFormatSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.clock_1),
+ text: 'timeformat'.tr,
+ dropdown: true,
+ dropdownName: settings.timeformat.tr,
+ dropdownList: ['12'.tr, '24'.tr],
+ dropdownChange: (String? newValue) {
+ isar.writeTxnSync(() {
+ settings.timeformat = newValue == '12'.tr ? '12' : '24';
+ isar.settings.putSync(settings);
+ });
+ setState(() {});
+ },
+ );
+ }
+
+ Widget _buildWidgetCard(BuildContext context) {
+ return SettingCard(
+ icon: const Icon(IconsaxPlusLinear.setting_3),
+ text: 'widget'.tr,
+ onPressed: () {
+ _showWidgetBottomSheet(context);
+ },
+ );
+ }
+
+ void _showWidgetBottomSheet(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ builder: (BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.only(
+ bottom: MediaQuery.of(context).padding.bottom,
+ ),
+ child: StatefulBuilder(
+ builder: (BuildContext context, setState) {
+ return SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ _buildWidgetTitle(context),
+ _buildAddWidgetSettingCard(context, setState),
+ _buildWidgetBackgroundSettingCard(context, setState),
+ _buildWidgetTextSettingCard(context, setState),
+ const Gap(10),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildWidgetTitle(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ child: Text(
+ 'widget'.tr,
+ style: context.textTheme.titleLarge?.copyWith(fontSize: 20),
+ ),
+ );
+ }
+
+ Widget _buildAddWidgetSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.add_square),
+ text: 'addWidget'.tr,
+ onPressed: () {
+ HomeWidget.requestPinWidget(
+ name: androidWidgetName,
+ androidName: androidWidgetName,
+ qualifiedAndroidName: 'com.yoshi.rain.OreoWidget',
+ );
+ },
+ );
+ }
+
+ Widget _buildWidgetBackgroundSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.bucket_square),
+ text: 'widgetBackground'.tr,
+ info: true,
+ infoWidget: CircleAvatar(
+ backgroundColor: context.theme.indicatorColor,
+ radius: 11,
+ child: CircleAvatar(
+ backgroundColor: widgetBackgroundColor.isEmpty
+ ? context.theme.primaryColor
+ : HexColor.fromHex(widgetBackgroundColor),
+ radius: 10,
+ ),
+ ),
+ onPressed: () {
+ colorBackground = null;
+ showDialog(
+ context: context,
+ builder: (context) => Dialog(
+ child: SingleChildScrollView(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ _buildWidgetBackgroundTitle(context),
+ _buildColorPicker(context),
+ _buildColorPickerButton(context),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildWidgetBackgroundTitle(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ child: Text(
+ 'widgetBackground'.tr,
+ style: context.textTheme.titleMedium?.copyWith(fontSize: 18),
+ ),
+ );
+ }
+
+ Widget _buildColorPicker(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 15),
+ child: Theme(
+ data: context.theme.copyWith(
+ inputDecorationTheme: InputDecorationTheme(
+ border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ ),
+ child: ColorPicker(
+ color: widgetBackgroundColor.isEmpty
+ ? context.theme.primaryColor
+ : HexColor.fromHex(widgetBackgroundColor),
+ onChanged: (pickedColor) {
+ colorBackground = pickedColor.toHex();
+ },
+ ),
+ ),
+ );
+ }
+
+ Widget _buildColorPickerButton(BuildContext context) {
+ return IconButton(
+ icon: const Icon(IconsaxPlusLinear.tick_square),
+ onPressed: () {
+ if (colorBackground == null) {
+ return;
+ }
+ weatherController.updateWidgetBackgroundColor(colorBackground!);
+ MyApp.updateAppState(
+ context,
+ newWidgetBackgroundColor: colorBackground,
+ );
+ Get.back();
+ },
+ );
+ }
+
+ Widget _buildWidgetTextSettingCard(
+ BuildContext context,
+ StateSetter setState,
+ ) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.text_block),
+ text: 'widgetText'.tr,
+ info: true,
+ infoWidget: CircleAvatar(
+ backgroundColor: context.theme.indicatorColor,
+ radius: 11,
+ child: CircleAvatar(
+ backgroundColor: widgetTextColor.isEmpty
+ ? context.theme.primaryColor
+ : HexColor.fromHex(widgetTextColor),
+ radius: 10,
+ ),
+ ),
+ onPressed: () {
+ colorText = null;
+ showDialog(
+ context: context,
+ builder: (context) => Dialog(
+ child: SingleChildScrollView(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ _buildWidgetTextTitle(context),
+ _buildTextColorPicker(context),
+ _buildTextColorPickerButton(context),
+ ],
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildWidgetTextTitle(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ child: Text(
+ 'widgetText'.tr,
+ style: context.textTheme.titleMedium?.copyWith(fontSize: 18),
+ ),
+ );
+ }
+
+ Widget _buildTextColorPicker(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 15),
+ child: Theme(
+ data: context.theme.copyWith(
+ inputDecorationTheme: InputDecorationTheme(
+ border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
+ ),
+ ),
+ child: ColorPicker(
+ color: widgetTextColor.isEmpty
+ ? context.theme.primaryColor
+ : HexColor.fromHex(widgetTextColor),
+ onChanged: (pickedColor) {
+ colorText = pickedColor.toHex();
+ },
+ ),
+ ),
+ );
+ }
+
+ Widget _buildTextColorPickerButton(BuildContext context) {
+ return IconButton(
+ icon: const Icon(IconsaxPlusLinear.tick_square),
+ onPressed: () {
+ if (colorText == null) {
+ return;
+ }
+ weatherController.updateWidgetTextColor(colorText!);
+ MyApp.updateAppState(context, newWidgetTextColor: colorText);
+ Get.back();
+ },
+ );
+ }
+
+ Widget _buildMapCard(BuildContext context) {
+ return SettingCard(
+ icon: const Icon(IconsaxPlusLinear.map),
+ text: 'map'.tr,
+ onPressed: () {
+ _showMapBottomSheet(context);
+ },
+ );
+ }
+
+ void _showMapBottomSheet(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ builder: (BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.only(
+ bottom: MediaQuery.of(context).padding.bottom,
+ ),
+ child: StatefulBuilder(
+ builder: (BuildContext context, setState) {
+ return SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ _buildMapTitle(context),
+ _buildHideMapSettingCard(context, setState),
+ _buildClearCacheStoreSettingCard(context),
+ const Gap(10),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildMapTitle(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ child: Text(
+ 'map'.tr,
+ style: context.textTheme.titleLarge?.copyWith(fontSize: 20),
+ ),
+ );
+ }
+
+ Widget _buildHideMapSettingCard(BuildContext context, StateSetter setState) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.location_slash),
+ text: 'hideMap'.tr,
+ switcher: true,
+ value: settings.hideMap,
+ onChange: (value) {
+ settings.hideMap = value;
+ isar.writeTxnSync(() => isar.settings.putSync(settings));
+ setState(() {});
+ Future.delayed(
+ const Duration(milliseconds: 500),
+ () => Restart.restartApp(),
+ );
+ },
+ );
+ }
+
+ Widget _buildClearCacheStoreSettingCard(BuildContext context) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(IconsaxPlusLinear.trash_square),
+ text: 'clearCacheStore'.tr,
+ onPressed: () => _showClearCacheStoreDialog(context),
+ );
+ }
+
+ void _showClearCacheStoreDialog(BuildContext context) {
+ showAdaptiveDialog(
+ context: context,
+ builder: (context) => AlertDialog.adaptive(
+ title: Text(
+ 'deletedCacheStore'.tr,
+ style: context.textTheme.titleLarge,
+ ),
+ content: Text(
+ 'deletedCacheStoreQuery'.tr,
+ style: context.textTheme.titleMedium,
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Get.back(),
+ child: Text(
+ 'cancel'.tr,
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.blueAccent,
+ ),
+ ),
+ ),
+ TextButton(
+ onPressed: () async {
+ final dir = await getTemporaryDirectory();
+ final cacheStoreFuture = FileCacheStore(
+ '${dir.path}${Platform.pathSeparator}MapTiles',
+ );
+ cacheStoreFuture.clean();
+ Get.back();
+ },
+ child: Text(
+ 'delete'.tr,
+ style: context.textTheme.titleMedium?.copyWith(color: Colors.red),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildLanguageCard(BuildContext context) {
+ return SettingCard(
+ icon: const Icon(IconsaxPlusLinear.language_square),
+ text: 'language'.tr,
+ info: true,
+ infoSettings: true,
+ infoWidget: _TextInfo(
+ info: appLanguages.firstWhere(
+ (element) => (element['locale'] == locale),
+ orElse: () => {'name': ''},
+ )['name'],
+ ),
+ onPressed: () {
+ _showLanguageBottomSheet(context);
+ },
+ );
+ }
+
+ void _showLanguageBottomSheet(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ builder: (BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.only(
+ bottom: MediaQuery.of(context).padding.bottom,
+ ),
+ child: StatefulBuilder(
+ builder: (BuildContext context, setState) {
+ return ListView(
+ children: [
+ _buildLanguageTitle(context),
+ _buildLanguageList(context),
+ const Gap(10),
+ ],
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildLanguageTitle(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ child: Text(
+ 'language'.tr,
+ style: context.textTheme.titleLarge?.copyWith(fontSize: 20),
+ textAlign: TextAlign.center,
+ ),
+ );
+ }
+
+ Widget _buildLanguageList(BuildContext context) {
+ return ListView.builder(
+ shrinkWrap: true,
+ physics: const BouncingScrollPhysics(),
+ itemCount: appLanguages.length,
+ itemBuilder: (context, index) {
+ return Card(
+ elevation: 4,
+ margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ child: ListTile(
+ title: Text(
+ appLanguages[index]['name'],
+ style: context.textTheme.labelLarge,
+ textAlign: TextAlign.center,
+ ),
+ onTap: () {
+ MyApp.updateAppState(
+ context,
+ newLocale: appLanguages[index]['locale'],
+ );
+ _updateLanguage(appLanguages[index]['locale']);
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildGroupsCard(BuildContext context) {
+ return SettingCard(
+ icon: const Icon(IconsaxPlusLinear.link_square),
+ text: 'groups'.tr,
+ onPressed: () {
+ _showGroupsBottomSheet(context);
+ },
+ );
+ }
+
+ void _showGroupsBottomSheet(BuildContext context) {
+ showModalBottomSheet(
+ context: context,
+ builder: (BuildContext context) {
+ return Padding(
+ padding: EdgeInsets.only(
+ bottom: MediaQuery.of(context).padding.bottom,
+ ),
+ child: StatefulBuilder(
+ builder: (BuildContext context, setState) {
+ return SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ _buildGroupsTitle(context),
+ _buildDiscordSettingCard(context),
+ _buildTelegramSettingCard(context),
+ const Gap(10),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ },
+ );
+ }
+
+ Widget _buildGroupsTitle(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15),
+ child: Text(
+ 'groups'.tr,
+ style: context.textTheme.titleLarge?.copyWith(fontSize: 20),
+ ),
+ );
+ }
+
+ Widget _buildDiscordSettingCard(BuildContext context) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(LineAwesomeIcons.discord),
+ text: 'Discord',
+ onPressed: () =>
+ weatherController.urlLauncher('https://discord.gg/JMMa9aHh8f'),
+ );
+ }
+
+ Widget _buildTelegramSettingCard(BuildContext context) {
+ return SettingCard(
+ elevation: 4,
+ icon: const Icon(LineAwesomeIcons.telegram),
+ text: 'Telegram',
+ onPressed: () =>
+ weatherController.urlLauncher('https://t.me/darkmoonightX'),
+ );
+ }
+
+ Widget _buildLicenseCard(BuildContext context) {
+ return SettingCard(
+ icon: const Icon(IconsaxPlusLinear.document),
+ text: 'license'.tr,
+ onPressed: () => Get.to(
+ () => LicensePage(
+ applicationIcon: Container(
+ width: 100,
+ height: 100,
+ margin: const EdgeInsets.symmetric(vertical: 5),
+ decoration: const BoxDecoration(
+ borderRadius: BorderRadius.all(Radius.circular(20)),
+ image: DecorationImage(
+ image: AssetImage('assets/icons/icon.png'),
+ ),
+ ),
+ ),
+ applicationName: 'Rain',
+ applicationVersion: appVersion,
+ ),
+ transition: Transition.downToUp,
+ ),
+ );
+ }
+
+ Widget _buildVersionCard(BuildContext context) {
+ return SettingCard(
+ icon: const Icon(IconsaxPlusLinear.hierarchy_square_2),
+ text: 'version'.tr,
+ info: true,
+ infoWidget: _TextInfo(info: '$appVersion'),
+ );
+ }
+
+ Widget _buildGitHubCard(BuildContext context) {
+ return SettingCard(
+ icon: const Icon(LineAwesomeIcons.github),
+ text: '${'project'.tr} GitHub',
+ onPressed: () =>
+ weatherController.urlLauncher('https://github.com/darkmoonight/Rain'),
+ );
+ }
+
+ Widget _buildOpenMeteoText(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(10),
+ child: GestureDetector(
+ child: Text(
+ 'openMeteo'.tr,
+ style: context.textTheme.bodyMedium,
+ overflow: TextOverflow.visible,
+ textAlign: TextAlign.center,
+ ),
+ onTap: () => weatherController.urlLauncher('https://open-meteo.com/'),
+ ),
+ );
+ }
+}
+
+class _TextInfo extends StatelessWidget {
+ const _TextInfo({required this.info});
+
+ final String info;
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.only(right: 5),
+ child: Text(
+ info,
+ style: context.textTheme.bodyMedium,
+ overflow: TextOverflow.visible,
+ ),
+ );
+ }
+}
diff --git a/lib/app/ui/settings/widgets/setting_card.dart b/lib/app/ui/settings/widgets/setting_card.dart
new file mode 100755
index 0000000..2721a7b
--- /dev/null
+++ b/lib/app/ui/settings/widgets/setting_card.dart
@@ -0,0 +1,107 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+
+class SettingCard extends StatelessWidget {
+ const SettingCard({
+ super.key,
+ required this.icon,
+ required this.text,
+ this.switcher = false,
+ this.dropdown = false,
+ this.info = false,
+ this.infoSettings = false,
+ this.elevation,
+ this.dropdownName,
+ this.dropdownList,
+ this.dropdownChange,
+ this.value,
+ this.onPressed,
+ this.onChange,
+ this.infoWidget,
+ });
+
+ final Widget icon;
+ final String text;
+ final bool switcher;
+ final bool dropdown;
+ final bool info;
+ final bool infoSettings;
+ final Widget? infoWidget;
+ final String? dropdownName;
+ final List? dropdownList;
+ final ValueChanged? dropdownChange;
+ final bool? value;
+ final VoidCallback? onPressed;
+ final ValueChanged? onChange;
+ final double? elevation;
+
+ @override
+ Widget build(BuildContext context) {
+ return Card(
+ elevation: elevation ?? 1,
+ margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ child: ListTile(
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
+ onTap: onPressed,
+ leading: icon,
+ title: Text(
+ text,
+ style: context.textTheme.titleMedium,
+ overflow: TextOverflow.visible,
+ ),
+ trailing: _buildTrailingWidget(context),
+ ),
+ );
+ }
+
+ Widget _buildTrailingWidget(BuildContext context) {
+ if (switcher) {
+ return _buildSwitchWidget();
+ } else if (dropdown) {
+ return _buildDropdownWidget();
+ } else if (info) {
+ return _buildInfoWidget();
+ } else {
+ return const Icon(IconsaxPlusLinear.arrow_right_3, size: 18);
+ }
+ }
+
+ Widget _buildSwitchWidget() {
+ return Transform.scale(
+ scale: 0.8,
+ child: Switch(value: value!, onChanged: onChange),
+ );
+ }
+
+ Widget _buildDropdownWidget() {
+ return DropdownButton(
+ icon: const Padding(
+ padding: EdgeInsets.only(left: 7),
+ child: Icon(IconsaxPlusLinear.arrow_down),
+ ),
+ iconSize: 15,
+ alignment: AlignmentDirectional.centerEnd,
+ borderRadius: const BorderRadius.all(Radius.circular(15)),
+ underline: Container(),
+ value: dropdownName,
+ items: dropdownList!.map>((String value) {
+ return DropdownMenuItem(value: value, child: Text(value));
+ }).toList(),
+ onChanged: dropdownChange,
+ );
+ }
+
+ Widget _buildInfoWidget() {
+ if (infoSettings) {
+ return Wrap(
+ children: [
+ infoWidget!,
+ const Icon(IconsaxPlusLinear.arrow_right_3, size: 18),
+ ],
+ );
+ } else {
+ return infoWidget!;
+ }
+ }
+}
diff --git a/lib/app/ui/widgets/button.dart b/lib/app/ui/widgets/button.dart
new file mode 100755
index 0000000..8aa03cf
--- /dev/null
+++ b/lib/app/ui/widgets/button.dart
@@ -0,0 +1,37 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+
+class MyTextButton extends StatelessWidget {
+ const MyTextButton({
+ super.key,
+ required this.buttonName,
+ required this.onPressed,
+ this.height = 50.0,
+ });
+
+ final String buttonName;
+ final VoidCallback? onPressed;
+ final double height;
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ height: height,
+ width: double.infinity,
+ child: ElevatedButton(
+ style: _buildButtonStyle(context),
+ onPressed: onPressed,
+ child: Text(buttonName, style: context.textTheme.titleMedium),
+ ),
+ );
+ }
+
+ ButtonStyle _buildButtonStyle(BuildContext context) {
+ return ButtonStyle(
+ shadowColor: const WidgetStatePropertyAll(Colors.transparent),
+ backgroundColor: WidgetStatePropertyAll(
+ context.theme.colorScheme.secondaryContainer.withAlpha(80),
+ ),
+ );
+ }
+}
diff --git a/lib/app/widgets/shimmer.dart b/lib/app/ui/widgets/shimmer.dart
old mode 100644
new mode 100755
similarity index 55%
rename from lib/app/widgets/shimmer.dart
rename to lib/app/ui/widgets/shimmer.dart
index 24a19ec..f470e1d
--- a/lib/app/widgets/shimmer.dart
+++ b/lib/app/ui/widgets/shimmer.dart
@@ -3,25 +3,21 @@ import 'package:get/get.dart';
import 'package:shimmer/shimmer.dart';
class MyShimmer extends StatelessWidget {
- const MyShimmer({
- super.key,
- required this.hight,
- this.edgeInsetsMargin,
- });
- final double hight;
- final EdgeInsets? edgeInsetsMargin;
+ const MyShimmer({super.key, required this.height, this.margin});
+
+ final double height;
+ final EdgeInsets? margin;
@override
Widget build(BuildContext context) {
return Shimmer.fromColors(
baseColor: context.theme.cardColor,
highlightColor: context.theme.primaryColor,
- child: Card(
- margin: edgeInsetsMargin,
- child: SizedBox(
- height: hight,
- ),
- ),
+ child: _buildShimmerCard(),
);
}
+
+ Widget _buildShimmerCard() {
+ return Card(margin: margin, child: SizedBox(height: height));
+ }
}
diff --git a/lib/app/widgets/text_form.dart b/lib/app/ui/widgets/text_form.dart
old mode 100644
new mode 100755
similarity index 57%
rename from lib/app/widgets/text_form.dart
rename to lib/app/ui/widgets/text_form.dart
index f0d9d9b..3c67219
--- a/lib/app/widgets/text_form.dart
+++ b/lib/app/ui/widgets/text_form.dart
@@ -15,6 +15,7 @@ class MyTextForm extends StatelessWidget {
this.focusNode,
this.onChanged,
});
+
final String labelText;
final TextInputType type;
final Icon icon;
@@ -31,23 +32,28 @@ class MyTextForm extends StatelessWidget {
return Card(
elevation: elevation,
margin: margin,
- child: TextFormField(
- focusNode: focusNode,
- controller: controller,
- keyboardType: type,
- style: context.textTheme.labelLarge,
- decoration: InputDecoration(
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 12.5,
- vertical: 0,
- ),
- prefixIcon: icon,
- suffixIcon: iconButton,
- labelText: labelText,
- ),
- validator: validator,
- onChanged: onChanged,
- ),
+ child: _buildTextFormField(context),
+ );
+ }
+
+ Widget _buildTextFormField(BuildContext context) {
+ return TextFormField(
+ focusNode: focusNode,
+ controller: controller,
+ keyboardType: type,
+ style: context.textTheme.labelLarge,
+ decoration: _buildInputDecoration(),
+ validator: validator,
+ onChanged: onChanged,
+ );
+ }
+
+ InputDecoration _buildInputDecoration() {
+ return InputDecoration(
+ contentPadding: const EdgeInsets.symmetric(horizontal: 12.5, vertical: 0),
+ prefixIcon: icon,
+ suffixIcon: iconButton,
+ labelText: labelText,
);
}
}
diff --git a/lib/app/ui/widgets/weather/daily/daily_card.dart b/lib/app/ui/widgets/weather/daily/daily_card.dart
new file mode 100755
index 0000000..b23cf68
--- /dev/null
+++ b/lib/app/ui/widgets/weather/daily/daily_card.dart
@@ -0,0 +1,99 @@
+import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
+import 'package:get/get.dart';
+import 'package:intl/intl.dart';
+import 'package:rain/app/ui/widgets/weather/status/status_weather.dart';
+import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
+import 'package:rain/main.dart';
+
+class DailyCard extends StatefulWidget {
+ const DailyCard({
+ super.key,
+ required this.timeDaily,
+ required this.weathercodeDaily,
+ required this.temperature2MMax,
+ required this.temperature2MMin,
+ });
+
+ final DateTime timeDaily;
+ final int? weathercodeDaily;
+ final double? temperature2MMax;
+ final double? temperature2MMin;
+
+ @override
+ State createState() => _DailyCardState();
+}
+
+class _DailyCardState extends State {
+ final statusWeather = StatusWeather();
+ final statusData = StatusData();
+
+ @override
+ Widget build(BuildContext context) {
+ if (widget.weathercodeDaily == null) {
+ return Container();
+ }
+
+ return Card(
+ margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 15, horizontal: 20),
+ child: Row(
+ children: [
+ _buildTemperatureInfo(context),
+ const Gap(5),
+ _buildWeatherImage(),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildTemperatureInfo(BuildContext context) {
+ return Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ '${statusData.getDegree(widget.temperature2MMin?.round())} / ${statusData.getDegree(widget.temperature2MMax?.round())}',
+ style: context.textTheme.titleLarge?.copyWith(
+ fontSize: 22,
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ const Gap(5),
+ _buildDateText(context),
+ const Gap(5),
+ _buildWeatherDescription(context),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildDateText(BuildContext context) {
+ return Text(
+ DateFormat.MMMMEEEEd(locale.languageCode).format(widget.timeDaily),
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.grey,
+ fontWeight: FontWeight.w400,
+ ),
+ );
+ }
+
+ Widget _buildWeatherDescription(BuildContext context) {
+ return Text(
+ statusWeather.getText(widget.weathercodeDaily),
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.grey,
+ fontWeight: FontWeight.w400,
+ ),
+ );
+ }
+
+ Widget _buildWeatherImage() {
+ return Image.asset(
+ statusWeather.getImageNowDaily(widget.weathercodeDaily),
+ scale: 6.5,
+ );
+ }
+}
diff --git a/lib/app/ui/widgets/weather/daily/daily_card_info.dart b/lib/app/ui/widgets/weather/daily/daily_card_info.dart
new file mode 100755
index 0000000..ccd57d5
--- /dev/null
+++ b/lib/app/ui/widgets/weather/daily/daily_card_info.dart
@@ -0,0 +1,348 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:intl/intl.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/widgets/weather/desc/desc_container.dart';
+import 'package:rain/app/ui/widgets/weather/desc/message.dart';
+import 'package:rain/app/ui/widgets/weather/hourly.dart';
+import 'package:rain/app/ui/widgets/weather/now.dart';
+import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
+import 'package:rain/app/ui/widgets/weather/status/status_weather.dart';
+import 'package:rain/app/ui/widgets/weather/sunset_sunrise.dart';
+import 'package:rain/main.dart';
+import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
+
+class DailyCardInfo extends StatefulWidget {
+ const DailyCardInfo({
+ super.key,
+ required this.weatherData,
+ required this.index,
+ });
+
+ final WeatherCard weatherData;
+ final int index;
+
+ @override
+ State createState() => _DailyCardInfoState();
+}
+
+class _DailyCardInfoState extends State {
+ final statusWeather = StatusWeather();
+ final statusData = StatusData();
+ final message = Message();
+ late PageController pageController;
+ int pageIndex = 0;
+ int hourOfDay = 0;
+
+ @override
+ void initState() {
+ pageController = PageController(initialPage: widget.index);
+ pageIndex = widget.index;
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ pageController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final weatherData = widget.weatherData;
+ final timeDaily = weatherData.timeDaily ?? [];
+
+ final textTheme = context.textTheme;
+
+ return Scaffold(
+ appBar: _buildAppBar(context, textTheme, timeDaily),
+ body: SafeArea(
+ child: PageView.builder(
+ controller: pageController,
+ onPageChanged: (index) {
+ setState(() {
+ pageIndex = index;
+ hourOfDay = 0;
+ });
+ },
+ itemCount: timeDaily.length,
+ itemBuilder: (context, index) {
+ return _buildPageContent(context, weatherData, index);
+ },
+ ),
+ ),
+ );
+ }
+
+ AppBar _buildAppBar(
+ BuildContext context,
+ TextTheme textTheme,
+ List timeDaily,
+ ) {
+ return AppBar(
+ automaticallyImplyLeading: false,
+ centerTitle: true,
+ leading: IconButton(
+ onPressed: () => Get.back(),
+ icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
+ splashColor: Colors.transparent,
+ highlightColor: Colors.transparent,
+ ),
+ title: Text(
+ DateFormat.MMMMEEEEd(locale.languageCode).format(timeDaily[pageIndex]),
+ style: textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildPageContent(
+ BuildContext context,
+ WeatherCard weatherData,
+ int index,
+ ) {
+ final weatherCodeDaily = weatherData.weathercodeDaily?[index];
+ if (weatherCodeDaily == null) {
+ return Container();
+ }
+
+ final startIndex = index * 24;
+ final temperature2MMin = weatherData.temperature2MMin?[index];
+ final temperature2MMax = weatherData.temperature2MMax?[index];
+ final apparentTemperatureMin = weatherData.apparentTemperatureMin?[index];
+ final apparentTemperatureMax = weatherData.apparentTemperatureMax?[index];
+ final uvIndexMax = weatherData.uvIndexMax?[index];
+ final windDirection10MDominant =
+ weatherData.winddirection10MDominant?[index];
+ final windSpeed10MMax = weatherData.windspeed10MMax?[index];
+ final windGusts10MMax = weatherData.windgusts10MMax?[index];
+ final precipitationProbabilityMax =
+ weatherData.precipitationProbabilityMax?[index];
+ final rainSum = weatherData.rainSum?[index];
+ final precipitationSum = weatherData.precipitationSum?[index];
+ final sunrise = weatherData.sunrise?[index];
+ final sunset = weatherData.sunset?[index];
+
+ if (sunrise == null || sunset == null) {
+ return Container();
+ }
+
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 10),
+ child: ListView(
+ children: [
+ _buildNowWidget(
+ weatherData,
+ index,
+ startIndex,
+ hourOfDay,
+ sunrise,
+ sunset,
+ ),
+ _buildHourlyList(context, weatherData, startIndex, sunrise, sunset),
+ _buildSunsetSunriseWidget(sunrise, sunset),
+ _buildHourlyDescContainer(weatherData, startIndex, hourOfDay),
+ _buildDailyDescContainer(
+ weatherData,
+ temperature2MMin,
+ temperature2MMax,
+ apparentTemperatureMin,
+ apparentTemperatureMax,
+ uvIndexMax,
+ windDirection10MDominant,
+ windSpeed10MMax,
+ windGusts10MMax,
+ precipitationProbabilityMax,
+ rainSum,
+ precipitationSum,
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildNowWidget(
+ WeatherCard weatherData,
+ int index,
+ int startIndex,
+ int hourOfDay,
+ String sunrise,
+ String sunset,
+ ) {
+ final weatherCode = weatherData.weathercode?[startIndex + hourOfDay];
+ final temperature = weatherData.temperature2M?[startIndex + hourOfDay];
+ final feels = weatherData.apparentTemperature?[startIndex + hourOfDay];
+ final time = weatherData.time?[startIndex + hourOfDay];
+ final tempMax = weatherData.temperature2MMax?[index];
+ final tempMin = weatherData.temperature2MMin?[index];
+
+ if (weatherCode == null ||
+ temperature == null ||
+ feels == null ||
+ time == null ||
+ tempMax == null ||
+ tempMin == null) {
+ return Container();
+ }
+
+ return Now(
+ weather: weatherCode,
+ degree: temperature,
+ feels: feels,
+ time: time,
+ timeDay: sunrise,
+ timeNight: sunset,
+ tempMax: tempMax,
+ tempMin: tempMin,
+ );
+ }
+
+ Widget _buildHourlyList(
+ BuildContext context,
+ WeatherCard weatherData,
+ int startIndex,
+ String sunrise,
+ String sunset,
+ ) {
+ return Card(
+ margin: const EdgeInsets.only(bottom: 15),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ child: SizedBox(
+ height: 135,
+ child: ScrollablePositionedList.separated(
+ separatorBuilder: (BuildContext context, int index) {
+ return const VerticalDivider(
+ width: 10,
+ indent: 40,
+ endIndent: 40,
+ );
+ },
+ scrollDirection: Axis.horizontal,
+ itemCount: 24,
+ itemBuilder: (ctx, i) {
+ return _buildHourlyItem(
+ context,
+ weatherData,
+ startIndex,
+ i,
+ sunrise,
+ sunset,
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildHourlyItem(
+ BuildContext context,
+ WeatherCard weatherData,
+ int startIndex,
+ int i,
+ String sunrise,
+ String sunset,
+ ) {
+ int hourlyIndex = startIndex + i;
+ bool isSelected = i == hourOfDay;
+
+ final time = weatherData.time?[hourlyIndex];
+ final weatherCode = weatherData.weathercode?[hourlyIndex];
+ final temperature = weatherData.temperature2M?[hourlyIndex];
+
+ if (time == null || weatherCode == null || temperature == null) {
+ return Container();
+ }
+
+ return GestureDetector(
+ onTap: () {
+ setState(() {
+ hourOfDay = i;
+ });
+ },
+ child: Container(
+ margin: const EdgeInsets.symmetric(vertical: 5),
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
+ decoration: BoxDecoration(
+ color: isSelected
+ ? context.theme.colorScheme.secondaryContainer
+ : Colors.transparent,
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ ),
+ child: Hourly(
+ time: time,
+ weather: weatherCode,
+ degree: temperature,
+ timeDay: sunrise,
+ timeNight: sunset,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSunsetSunriseWidget(String sunrise, String sunset) {
+ return SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset);
+ }
+
+ Widget _buildHourlyDescContainer(
+ WeatherCard weatherData,
+ int startIndex,
+ int hourOfDay,
+ ) {
+ final hourlyIndex = startIndex + hourOfDay;
+
+ return DescContainer(
+ humidity: weatherData.relativehumidity2M?[hourlyIndex],
+ wind: weatherData.windspeed10M?[hourlyIndex],
+ visibility: weatherData.visibility?[hourlyIndex],
+ feels: weatherData.apparentTemperature?[hourlyIndex],
+ evaporation: weatherData.evapotranspiration?[hourlyIndex],
+ precipitation: weatherData.precipitation?[hourlyIndex],
+ direction: weatherData.winddirection10M?[hourlyIndex],
+ pressure: weatherData.surfacePressure?[hourlyIndex],
+ rain: weatherData.rain?[hourlyIndex],
+ cloudcover: weatherData.cloudcover?[hourlyIndex],
+ windgusts: weatherData.windgusts10M?[hourlyIndex],
+ uvIndex: weatherData.uvIndex?[hourlyIndex],
+ dewpoint2M: weatherData.dewpoint2M?[hourlyIndex],
+ precipitationProbability:
+ weatherData.precipitationProbability?[hourlyIndex],
+ shortwaveRadiation: weatherData.shortwaveRadiation?[hourlyIndex],
+ initiallyExpanded: true,
+ title: 'hourlyVariables'.tr,
+ );
+ }
+
+ Widget _buildDailyDescContainer(
+ WeatherCard weatherData,
+ double? temperature2MMin,
+ double? temperature2MMax,
+ double? apparentTemperatureMin,
+ double? apparentTemperatureMax,
+ double? uvIndexMax,
+ int? windDirection10MDominant,
+ double? windSpeed10MMax,
+ double? windGusts10MMax,
+ int? precipitationProbabilityMax,
+ double? rainSum,
+ double? precipitationSum,
+ ) {
+ return DescContainer(
+ apparentTemperatureMin: apparentTemperatureMin,
+ apparentTemperatureMax: apparentTemperatureMax,
+ uvIndexMax: uvIndexMax,
+ windDirection10MDominant: windDirection10MDominant,
+ windSpeed10MMax: windSpeed10MMax,
+ windGusts10MMax: windGusts10MMax,
+ precipitationProbabilityMax: precipitationProbabilityMax,
+ rainSum: rainSum,
+ precipitationSum: precipitationSum,
+ initiallyExpanded: true,
+ title: 'dailyVariables'.tr,
+ );
+ }
+}
diff --git a/lib/app/ui/widgets/weather/daily/daily_card_list.dart b/lib/app/ui/widgets/weather/daily/daily_card_list.dart
new file mode 100755
index 0000000..a8e1b46
--- /dev/null
+++ b/lib/app/ui/widgets/weather/daily/daily_card_list.dart
@@ -0,0 +1,84 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_card_info.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_card.dart';
+
+class DailyCardList extends StatefulWidget {
+ const DailyCardList({super.key, required this.weatherData});
+ final WeatherCard weatherData;
+
+ @override
+ State createState() => _DailyCardListState();
+}
+
+class _DailyCardListState extends State {
+ @override
+ Widget build(BuildContext context) {
+ final weatherData = widget.weatherData;
+ final timeDaily = weatherData.timeDaily ?? [];
+
+ return Scaffold(
+ appBar: _buildAppBar(context),
+ body: SafeArea(
+ child: ListView.builder(
+ itemCount: timeDaily.length,
+ itemBuilder: (context, index) =>
+ _buildDailyCardItem(context, weatherData, index),
+ ),
+ ),
+ );
+ }
+
+ AppBar _buildAppBar(BuildContext context) {
+ return AppBar(
+ automaticallyImplyLeading: false,
+ centerTitle: true,
+ leading: IconButton(
+ onPressed: () => Get.back(),
+ icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
+ splashColor: Colors.transparent,
+ highlightColor: Colors.transparent,
+ ),
+ title: Text(
+ 'weatherMore'.tr,
+ style: context.textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildDailyCardItem(
+ BuildContext context,
+ WeatherCard weatherData,
+ int index,
+ ) {
+ final timeDaily = weatherData.timeDaily?[index];
+ final weathercodeDaily = weatherData.weathercodeDaily?[index];
+ final temperature2MMax = weatherData.temperature2MMax?[index];
+ final temperature2MMin = weatherData.temperature2MMin?[index];
+
+ if (timeDaily == null ||
+ weathercodeDaily == null ||
+ temperature2MMax == null ||
+ temperature2MMin == null) {
+ return Container();
+ }
+
+ return GestureDetector(
+ onTap: () => Get.to(
+ () => DailyCardInfo(weatherData: weatherData, index: index),
+ transition: Transition.downToUp,
+ ),
+ child: DailyCard(
+ timeDaily: timeDaily,
+ weathercodeDaily: weathercodeDaily,
+ temperature2MMax: temperature2MMax,
+ temperature2MMin: temperature2MMin,
+ ),
+ );
+ }
+}
diff --git a/lib/app/ui/widgets/weather/daily/daily_container.dart b/lib/app/ui/widgets/weather/daily/daily_container.dart
new file mode 100755
index 0000000..a63667c
--- /dev/null
+++ b/lib/app/ui/widgets/weather/daily/daily_container.dart
@@ -0,0 +1,206 @@
+import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
+import 'package:get/get.dart';
+import 'package:intl/intl.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_card_info.dart';
+import 'package:rain/app/ui/widgets/weather/status/status_data.dart';
+import 'package:rain/app/ui/widgets/weather/status/status_weather.dart';
+import 'package:rain/main.dart';
+
+class DailyContainer extends StatefulWidget {
+ const DailyContainer({
+ super.key,
+ required this.weatherData,
+ required this.onTap,
+ });
+
+ final WeatherCard weatherData;
+ final VoidCallback onTap;
+
+ @override
+ State createState() => _DailyContainerState();
+}
+
+class _DailyContainerState extends State {
+ final statusWeather = StatusWeather();
+ final statusData = StatusData();
+
+ @override
+ Widget build(BuildContext context) {
+ final splashColor = context.theme.colorScheme.primary.withValues(
+ alpha: 0.4,
+ );
+ const inkWellBorderRadius = BorderRadius.all(Radius.circular(16));
+
+ final weatherData = widget.weatherData;
+ final weatherCodeDaily = weatherData.weathercodeDaily ?? [];
+ final textTheme = context.textTheme;
+ final labelLarge = textTheme.labelLarge;
+
+ return Card(
+ margin: const EdgeInsets.only(bottom: 15),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 5),
+ child: Column(
+ children: [
+ _buildDailyListView(
+ context,
+ weatherData,
+ weatherCodeDaily,
+ labelLarge,
+ ),
+ const Divider(),
+ _buildMoreInfoButton(context, splashColor, inkWellBorderRadius),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildDailyListView(
+ BuildContext context,
+ WeatherCard weatherData,
+ List weatherCodeDaily,
+ TextStyle? labelLarge,
+ ) {
+ return ListView.builder(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ itemCount: 7,
+ itemBuilder: (ctx, index) {
+ return _buildDailyItem(
+ context,
+ weatherData,
+ weatherCodeDaily,
+ index,
+ labelLarge,
+ );
+ },
+ );
+ }
+
+ Widget _buildDailyItem(
+ BuildContext context,
+ WeatherCard weatherData,
+ List