.from(json['sunset'] ?? []),
lat: json['lat'],
diff --git a/lib/app/data/db.g.dart b/lib/app/data/weather.g.dart
old mode 100755
new mode 100644
similarity index 99%
rename from lib/app/data/db.g.dart
rename to lib/app/data/weather.g.dart
index e2dbe3b..0b516b4
--- a/lib/app/data/db.g.dart
+++ b/lib/app/data/weather.g.dart
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
-part of 'db.dart';
+part of 'weather.dart';
// **************************************************************************
// IsarCollectionGenerator
@@ -27,93 +27,88 @@ const SettingsSchema = CollectionSchema(
name: r'degrees',
type: IsarType.string,
),
- r'hideMap': PropertySchema(
- id: 2,
- name: r'hideMap',
- type: IsarType.bool,
- ),
r'language': PropertySchema(
- id: 3,
+ id: 2,
name: r'language',
type: IsarType.string,
),
r'largeElement': PropertySchema(
- id: 4,
+ id: 3,
name: r'largeElement',
type: IsarType.bool,
),
r'location': PropertySchema(
- id: 5,
+ id: 4,
name: r'location',
type: IsarType.bool,
),
r'materialColor': PropertySchema(
- id: 6,
+ id: 5,
name: r'materialColor',
type: IsarType.bool,
),
r'measurements': PropertySchema(
- id: 7,
+ id: 6,
name: r'measurements',
type: IsarType.string,
),
r'notifications': PropertySchema(
- id: 8,
+ id: 7,
name: r'notifications',
type: IsarType.bool,
),
r'onboard': PropertySchema(
- id: 9,
+ id: 8,
name: r'onboard',
type: IsarType.bool,
),
r'pressure': PropertySchema(
- id: 10,
+ id: 9,
name: r'pressure',
type: IsarType.string,
),
r'roundDegree': PropertySchema(
- id: 11,
+ id: 10,
name: r'roundDegree',
type: IsarType.bool,
),
r'theme': PropertySchema(
- id: 12,
+ id: 11,
name: r'theme',
type: IsarType.string,
),
r'timeEnd': PropertySchema(
- id: 13,
+ id: 12,
name: r'timeEnd',
type: IsarType.string,
),
r'timeRange': PropertySchema(
- id: 14,
+ id: 13,
name: r'timeRange',
type: IsarType.long,
),
r'timeStart': PropertySchema(
- id: 15,
+ id: 14,
name: r'timeStart',
type: IsarType.string,
),
r'timeformat': PropertySchema(
- id: 16,
+ id: 15,
name: r'timeformat',
type: IsarType.string,
),
r'widgetBackgroundColor': PropertySchema(
- id: 17,
+ id: 16,
name: r'widgetBackgroundColor',
type: IsarType.string,
),
r'widgetTextColor': PropertySchema(
- id: 18,
+ id: 17,
name: r'widgetTextColor',
type: IsarType.string,
),
r'wind': PropertySchema(
- id: 19,
+ id: 18,
name: r'wind',
type: IsarType.string,
)
@@ -190,24 +185,23 @@ void _settingsSerialize(
) {
writer.writeBool(offsets[0], object.amoledTheme);
writer.writeString(offsets[1], object.degrees);
- 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);
+ 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);
}
Settings _settingsDeserialize(
@@ -219,25 +213,24 @@ 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[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]);
+ 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]);
return object;
}
@@ -253,40 +246,38 @@ P _settingsDeserializeProp(
case 1:
return (reader.readString(offset)) as P;
case 2:
- return (reader.readBool(offset)) as P;
- case 3:
return (reader.readStringOrNull(offset)) as P;
+ case 3:
+ return (reader.readBool(offset)) as P;
case 4:
return (reader.readBool(offset)) as P;
case 5:
return (reader.readBool(offset)) as P;
case 6:
- return (reader.readBool(offset)) as P;
- case 7:
return (reader.readString(offset)) as P;
+ case 7:
+ return (reader.readBool(offset)) as P;
case 8:
return (reader.readBool(offset)) as P;
case 9:
- return (reader.readBool(offset)) as P;
- case 10:
return (reader.readString(offset)) as P;
- case 11:
+ case 10:
return (reader.readBool(offset)) as P;
+ case 11:
+ return (reader.readStringOrNull(offset)) as P;
case 12:
return (reader.readStringOrNull(offset)) as P;
case 13:
- return (reader.readStringOrNull(offset)) as P;
- case 14:
return (reader.readLongOrNull(offset)) as P;
- case 15:
+ case 14:
return (reader.readStringOrNull(offset)) as P;
- case 16:
+ case 15:
return (reader.readString(offset)) as P;
+ case 16:
+ return (reader.readStringOrNull(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');
@@ -522,16 +513,6 @@ 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(
@@ -2164,18 +2145,6 @@ 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);
@@ -2408,18 +2377,6 @@ 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);
@@ -2653,12 +2610,6 @@ extension SettingsQueryWhereDistinct
});
}
- QueryBuilder distinctByHideMap() {
- return QueryBuilder.apply(this, (query) {
- return query.addDistinctBy(r'hideMap');
- });
- }
-
QueryBuilder distinctByLanguage(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
@@ -2794,12 +2745,6 @@ 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
new file mode 100644
index 0000000..8079045
--- /dev/null
+++ b/lib/app/modules/cards/view/info_weather_card.dart
@@ -0,0 +1,192 @@
+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
new file mode 100644
index 0000000..76b6cb3
--- /dev/null
+++ b/lib/app/modules/cards/view/list_weather_card.dart
@@ -0,0 +1,103 @@
+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
new file mode 100644
index 0000000..d2d4c97
--- /dev/null
+++ b/lib/app/modules/cards/widgets/create_card_weather.dart
@@ -0,0 +1,304 @@
+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
new file mode 100644
index 0000000..7c977b4
--- /dev/null
+++ b/lib/app/modules/cards/widgets/weather_card_container.dart
@@ -0,0 +1,125 @@
+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
new file mode 100644
index 0000000..a0e0b88
--- /dev/null
+++ b/lib/app/modules/cards/widgets/weather_card_list.dart
@@ -0,0 +1,115 @@
+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
new file mode 100644
index 0000000..6e3dc67
--- /dev/null
+++ b/lib/app/modules/geolocation.dart
@@ -0,0 +1,495 @@
+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
new file mode 100644
index 0000000..2d97edc
--- /dev/null
+++ b/lib/app/modules/home.dart
@@ -0,0 +1,286 @@
+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
new file mode 100644
index 0000000..4cba272
--- /dev/null
+++ b/lib/app/modules/main/view/weather_main.dart
@@ -0,0 +1,178 @@
+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/ui/map/view/map.dart b/lib/app/modules/map/view/map.dart
old mode 100755
new mode 100644
similarity index 51%
rename from lib/app/ui/map/view/map.dart
rename to lib/app/modules/map/view/map.dart
index 7f26040..619c8a4
--- a/lib/app/ui/map/view/map.dart
+++ b/lib/app/modules/map/view/map.dart
@@ -1,5 +1,6 @@
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';
@@ -7,30 +8,29 @@ 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/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/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/main.dart';
-class MapPage extends StatefulWidget {
- const MapPage({super.key});
+class MapWeather extends StatefulWidget {
+ const MapWeather({super.key});
@override
- State createState() => _MapPageState();
+ State createState() => _MapWeatherState();
}
-class _MapPageState extends State with TickerProviderStateMixin {
+class _MapWeatherState extends State with TickerProviderStateMixin {
late final AnimatedMapController _animatedMapController =
AnimatedMapController(vsync: this);
final weatherController = Get.put(WeatherController());
@@ -38,15 +38,11 @@ class _MapPageState 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();
@@ -66,9 +62,10 @@ class _MapPageState 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();
}
@@ -82,7 +79,6 @@ class _MapPageState extends State with TickerProviderStateMixin {
void _resetMapOrientation({LatLng? center, double? zoom}) {
_animatedMapController.animateTo(
- customId: _useTransformer ? _useTransformerId : null,
dest: center,
zoom: zoom,
rotation: 0,
@@ -97,10 +93,6 @@ class _MapPageState extends State with TickerProviderStateMixin {
});
_animationController.forward();
_isCardVisible = true;
-
- if (_fabKey.currentState?.isOpen == true) {
- _fabKey.currentState?.toggle();
- }
}
void _hideCard() {
@@ -113,26 +105,25 @@ class _MapPageState extends State with TickerProviderStateMixin {
_focusNode.unfocus();
}
- Widget _buildStyleMarkers(
- int weathercode,
- String time,
- String sunrise,
- String sunset,
- double temperature2M,
- ) {
+ Widget _buidStyleMarkers(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,
@@ -144,17 +135,14 @@ class _MapPageState 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: _buildStyleMarkers(
+ child: _buidStyleMarkers(
weatherCard.weathercode![hourOfDay],
weatherCard.time![hourOfDay],
weatherCard.sunrise![dayOfNow],
@@ -166,27 +154,23 @@ class _MapPageState 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: _buildStyleMarkers(
- weatherCardList.weathercode![hourOfDay],
- weatherCardList.time![hourOfDay],
- weatherCardList.sunrise![dayOfNow],
- weatherCardList.sunset![dayOfNow],
- weatherCardList.temperature2M![hourOfDay],
+ 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!)],
),
),
);
@@ -206,112 +190,26 @@ class _MapPageState extends State with TickerProviderStateMixin {
Widget _buildWeatherCard() {
return _isCardVisible && _selectedWeatherCard != null
? SlideTransition(
- 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,
- ),
- ),
- );
- },
+ 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!,
),
),
- ),
- );
- },
- );
+ )
+ : const SizedBox.shrink();
}
@override
@@ -343,10 +241,7 @@ class _MapPageState extends State with TickerProviderStateMixin {
options: MapOptions(
backgroundColor: context.theme.colorScheme.surface,
initialCenter: LatLng(mainLocation.lat!, mainLocation.lon!),
- initialZoom: 8,
- interactionOptions: const InteractionOptions(
- flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
- ),
+ initialZoom: 12,
cameraConstraint: CameraConstraint.contain(
bounds: LatLngBounds(
const LatLng(-90, -180),
@@ -354,17 +249,15 @@ class _MapPageState extends State with TickerProviderStateMixin {
),
),
onTap: (_, __) => _hideCard(),
- onLongPress:
- (tapPosition, point) => showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- enableDrag: false,
- builder:
- (BuildContext context) => CreatePlace(
- latitude: '${point.latitude}',
- longitude: '${point.longitude}',
- ),
- ),
+ onLongPress: (tapPosition, point) => showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ enableDrag: false,
+ builder: (BuildContext context) => CreateWeatherCard(
+ latitude: '${point.latitude}',
+ longitude: '${point.longitude}',
+ ),
+ ),
),
children: [
if (_isDarkMode)
@@ -385,10 +278,8 @@ class _MapPageState extends State with TickerProviderStateMixin {
attributions: [
TextSourceAttribution(
'OpenStreetMap contributors',
- onTap:
- () => weatherController.urlLauncher(
- 'https://openstreetmap.org/copyright',
- ),
+ onTap: () => weatherController
+ .urlLauncher('https://openstreetmap.org/copyright'),
),
],
),
@@ -402,20 +293,18 @@ class _MapPageState 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.up,
+ type: ExpandableFabType.fan,
distance: 70,
openButtonBuilder: RotateFloatingActionButtonBuilder(
child: const Icon(IconsaxPlusLinear.menu),
@@ -428,33 +317,16 @@ class _MapPageState extends State with TickerProviderStateMixin {
children: [
FloatingActionButton(
heroTag: null,
- child: const Icon(IconsaxPlusLinear.home_2),
- onPressed:
- () => _resetMapOrientation(
- center: LatLng(
- mainLocation.lat!,
- mainLocation.lon!,
- ),
- zoom: 8,
- ),
+ child: const Icon(IconsaxPlusLinear.gps),
+ onPressed: () => _resetMapOrientation(
+ center:
+ LatLng(mainLocation.lat!, mainLocation.lon!),
+ zoom: 12),
),
FloatingActionButton(
heroTag: null,
- 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,
- ),
+ child: const Icon(IconsaxPlusLinear.refresh_2),
+ onPressed: () => _resetMapOrientation(),
),
],
),
@@ -466,7 +338,82 @@ class _MapPageState extends State with TickerProviderStateMixin {
),
],
),
- _buildSearchField(),
+ 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,
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ },
+ ),
],
);
},
diff --git a/lib/app/ui/onboarding.dart b/lib/app/modules/onboarding.dart
old mode 100755
new mode 100644
similarity index 51%
rename from lib/app/ui/onboarding.dart
rename to lib/app/modules/onboarding.dart
index 29561c3..07a2999
--- a/lib/app/ui/onboarding.dart
+++ b/lib/app/modules/onboarding.dart
@@ -1,9 +1,9 @@
-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/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:get/get.dart';
class OnBording extends StatefulWidget {
@@ -19,8 +19,8 @@ class _OnBordingState extends State {
@override
void initState() {
- super.initState();
pageController = PageController(initialPage: 0);
+ super.initState();
}
@override
@@ -32,10 +32,8 @@ 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
@@ -45,69 +43,60 @@ class _OnBordingState extends State {
body: SafeArea(
child: Column(
children: [
- _buildPageView(),
- _buildDotIndicators(),
- _buildActionButton(),
+ 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,
+ );
+ },
+ ),
+ )
],
),
),
);
}
-
- 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;
@@ -139,20 +128,17 @@ 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 {
@@ -162,7 +148,6 @@ class OnboardContent extends StatelessWidget {
required this.title,
required this.description,
});
-
final String image, title, description;
@override
@@ -173,12 +158,14 @@ 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/modules/settings/view/settings.dart b/lib/app/modules/settings/view/settings.dart
new file mode 100644
index 0000000..d22f027
--- /dev/null
+++ b/lib/app/modules/settings/view/settings.dart
@@ -0,0 +1,1118 @@
+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
new file mode 100644
index 0000000..0a2f96c
--- /dev/null
+++ b/lib/app/modules/settings/widgets/setting_card.dart
@@ -0,0 +1,101 @@
+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
new file mode 100644
index 0000000..ba07067
--- /dev/null
+++ b/lib/app/services/notification.dart
@@ -0,0 +1,42 @@
+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
new file mode 100644
index 0000000..a7dd8a1
--- /dev/null
+++ b/lib/app/services/utils.dart
@@ -0,0 +1,20 @@
+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
deleted file mode 100755
index 2b6165c..0000000
--- a/lib/app/ui/geolocation.dart
+++ /dev/null
@@ -1,479 +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/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
deleted file mode 100755
index 655d2ba..0000000
--- a/lib/app/ui/home.dart
+++ /dev/null
@@ -1,311 +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/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
deleted file mode 100755
index fe0987e..0000000
--- a/lib/app/ui/main/view/main.dart
+++ /dev/null
@@ -1,242 +0,0 @@
-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/ui/places/view/place_info.dart b/lib/app/ui/places/view/place_info.dart
deleted file mode 100755
index f56f709..0000000
--- a/lib/app/ui/places/view/place_info.dart
+++ /dev/null
@@ -1,212 +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/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
deleted file mode 100755
index 7fafd5a..0000000
--- a/lib/app/ui/places/view/place_list.dart
+++ /dev/null
@@ -1,116 +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/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
deleted file mode 100755
index 0ef872a..0000000
--- a/lib/app/ui/places/widgets/create_place.dart
+++ /dev/null
@@ -1,346 +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/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
deleted file mode 100755
index 3e35537..0000000
--- a/lib/app/ui/places/widgets/place_card.dart
+++ /dev/null
@@ -1,155 +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/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
deleted file mode 100755
index 6f1a0b1..0000000
--- a/lib/app/ui/places/widgets/place_card_list.dart
+++ /dev/null
@@ -1,155 +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/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
deleted file mode 100755
index 884e284..0000000
--- a/lib/app/ui/settings/view/settings.dart
+++ /dev/null
@@ -1,1275 +0,0 @@
-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
deleted file mode 100755
index 2721a7b..0000000
--- a/lib/app/ui/settings/widgets/setting_card.dart
+++ /dev/null
@@ -1,107 +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.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
deleted file mode 100755
index 8aa03cf..0000000
--- a/lib/app/ui/widgets/button.dart
+++ /dev/null
@@ -1,37 +0,0 @@
-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/ui/widgets/weather/daily/daily_card.dart b/lib/app/ui/widgets/weather/daily/daily_card.dart
deleted file mode 100755
index b23cf68..0000000
--- a/lib/app/ui/widgets/weather/daily/daily_card.dart
+++ /dev/null
@@ -1,99 +0,0 @@
-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
deleted file mode 100755
index ccd57d5..0000000
--- a/lib/app/ui/widgets/weather/daily/daily_card_info.dart
+++ /dev/null
@@ -1,348 +0,0 @@
-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
deleted file mode 100755
index a8e1b46..0000000
--- a/lib/app/ui/widgets/weather/daily/daily_card_list.dart
+++ /dev/null
@@ -1,84 +0,0 @@
-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
deleted file mode 100755
index a63667c..0000000
--- a/lib/app/ui/widgets/weather/daily/daily_container.dart
+++ /dev/null
@@ -1,206 +0,0 @@
-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