.from(json['sunset'] ?? []),
lat: json['lat'],
diff --git a/lib/app/data/weather.g.dart b/lib/app/data/db.g.dart
old mode 100644
new mode 100755
similarity index 97%
rename from lib/app/data/weather.g.dart
rename to lib/app/data/db.g.dart
index 1103612..e2dbe3b
--- a/lib/app/data/weather.g.dart
+++ b/lib/app/data/db.g.dart
@@ -1,6 +1,6 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
-part of 'weather.dart';
+part of 'db.dart';
// **************************************************************************
// IsarCollectionGenerator
@@ -27,75 +27,95 @@ const SettingsSchema = CollectionSchema(
name: r'degrees',
type: IsarType.string,
),
- r'language': PropertySchema(
+ r'hideMap': PropertySchema(
id: 2,
+ name: r'hideMap',
+ type: IsarType.bool,
+ ),
+ r'language': PropertySchema(
+ id: 3,
name: r'language',
type: IsarType.string,
),
+ r'largeElement': PropertySchema(
+ id: 4,
+ name: r'largeElement',
+ type: IsarType.bool,
+ ),
r'location': PropertySchema(
- id: 3,
+ id: 5,
name: r'location',
type: IsarType.bool,
),
r'materialColor': PropertySchema(
- id: 4,
+ id: 6,
name: r'materialColor',
type: IsarType.bool,
),
r'measurements': PropertySchema(
- id: 5,
+ id: 7,
name: r'measurements',
type: IsarType.string,
),
r'notifications': PropertySchema(
- id: 6,
+ id: 8,
name: r'notifications',
type: IsarType.bool,
),
r'onboard': PropertySchema(
- id: 7,
+ id: 9,
name: r'onboard',
type: IsarType.bool,
),
+ r'pressure': PropertySchema(
+ id: 10,
+ name: r'pressure',
+ type: IsarType.string,
+ ),
r'roundDegree': PropertySchema(
- id: 8,
+ id: 11,
name: r'roundDegree',
type: IsarType.bool,
),
r'theme': PropertySchema(
- id: 9,
+ id: 12,
name: r'theme',
type: IsarType.string,
),
r'timeEnd': PropertySchema(
- id: 10,
+ id: 13,
name: r'timeEnd',
type: IsarType.string,
),
r'timeRange': PropertySchema(
- id: 11,
+ id: 14,
name: r'timeRange',
type: IsarType.long,
),
r'timeStart': PropertySchema(
- id: 12,
+ id: 15,
name: r'timeStart',
type: IsarType.string,
),
r'timeformat': PropertySchema(
- id: 13,
+ id: 16,
name: r'timeformat',
type: IsarType.string,
),
r'widgetBackgroundColor': PropertySchema(
- id: 14,
+ id: 17,
name: r'widgetBackgroundColor',
type: IsarType.string,
),
r'widgetTextColor': PropertySchema(
- id: 15,
+ id: 18,
name: r'widgetTextColor',
type: IsarType.string,
+ ),
+ r'wind': PropertySchema(
+ id: 19,
+ name: r'wind',
+ type: IsarType.string,
)
},
estimateSize: _settingsEstimateSize,
@@ -109,7 +129,7 @@ const SettingsSchema = CollectionSchema(
getId: _settingsGetId,
getLinks: _settingsGetLinks,
attach: _settingsAttach,
- version: '3.1.7',
+ version: '3.1.8',
);
int _settingsEstimateSize(
@@ -126,6 +146,7 @@ int _settingsEstimateSize(
}
}
bytesCount += 3 + object.measurements.length * 3;
+ bytesCount += 3 + object.pressure.length * 3;
{
final value = object.theme;
if (value != null) {
@@ -157,6 +178,7 @@ int _settingsEstimateSize(
bytesCount += 3 + value.length * 3;
}
}
+ bytesCount += 3 + object.wind.length * 3;
return bytesCount;
}
@@ -168,20 +190,24 @@ void _settingsSerialize(
) {
writer.writeBool(offsets[0], object.amoledTheme);
writer.writeString(offsets[1], object.degrees);
- writer.writeString(offsets[2], object.language);
- writer.writeBool(offsets[3], object.location);
- writer.writeBool(offsets[4], object.materialColor);
- writer.writeString(offsets[5], object.measurements);
- writer.writeBool(offsets[6], object.notifications);
- writer.writeBool(offsets[7], object.onboard);
- writer.writeBool(offsets[8], object.roundDegree);
- writer.writeString(offsets[9], object.theme);
- writer.writeString(offsets[10], object.timeEnd);
- writer.writeLong(offsets[11], object.timeRange);
- writer.writeString(offsets[12], object.timeStart);
- writer.writeString(offsets[13], object.timeformat);
- writer.writeString(offsets[14], object.widgetBackgroundColor);
- writer.writeString(offsets[15], object.widgetTextColor);
+ writer.writeBool(offsets[2], object.hideMap);
+ writer.writeString(offsets[3], object.language);
+ writer.writeBool(offsets[4], object.largeElement);
+ writer.writeBool(offsets[5], object.location);
+ writer.writeBool(offsets[6], object.materialColor);
+ writer.writeString(offsets[7], object.measurements);
+ writer.writeBool(offsets[8], object.notifications);
+ writer.writeBool(offsets[9], object.onboard);
+ writer.writeString(offsets[10], object.pressure);
+ writer.writeBool(offsets[11], object.roundDegree);
+ writer.writeString(offsets[12], object.theme);
+ writer.writeString(offsets[13], object.timeEnd);
+ writer.writeLong(offsets[14], object.timeRange);
+ writer.writeString(offsets[15], object.timeStart);
+ writer.writeString(offsets[16], object.timeformat);
+ writer.writeString(offsets[17], object.widgetBackgroundColor);
+ writer.writeString(offsets[18], object.widgetTextColor);
+ writer.writeString(offsets[19], object.wind);
}
Settings _settingsDeserialize(
@@ -193,21 +219,25 @@ Settings _settingsDeserialize(
final object = Settings();
object.amoledTheme = reader.readBool(offsets[0]);
object.degrees = reader.readString(offsets[1]);
+ object.hideMap = reader.readBool(offsets[2]);
object.id = id;
- object.language = reader.readStringOrNull(offsets[2]);
- object.location = reader.readBool(offsets[3]);
- object.materialColor = reader.readBool(offsets[4]);
- object.measurements = reader.readString(offsets[5]);
- object.notifications = reader.readBool(offsets[6]);
- object.onboard = reader.readBool(offsets[7]);
- object.roundDegree = reader.readBool(offsets[8]);
- object.theme = reader.readStringOrNull(offsets[9]);
- object.timeEnd = reader.readStringOrNull(offsets[10]);
- object.timeRange = reader.readLongOrNull(offsets[11]);
- object.timeStart = reader.readStringOrNull(offsets[12]);
- object.timeformat = reader.readString(offsets[13]);
- object.widgetBackgroundColor = reader.readStringOrNull(offsets[14]);
- object.widgetTextColor = reader.readStringOrNull(offsets[15]);
+ object.language = reader.readStringOrNull(offsets[3]);
+ object.largeElement = reader.readBool(offsets[4]);
+ object.location = reader.readBool(offsets[5]);
+ object.materialColor = reader.readBool(offsets[6]);
+ object.measurements = reader.readString(offsets[7]);
+ object.notifications = reader.readBool(offsets[8]);
+ object.onboard = reader.readBool(offsets[9]);
+ object.pressure = reader.readString(offsets[10]);
+ object.roundDegree = reader.readBool(offsets[11]);
+ object.theme = reader.readStringOrNull(offsets[12]);
+ object.timeEnd = reader.readStringOrNull(offsets[13]);
+ object.timeRange = reader.readLongOrNull(offsets[14]);
+ object.timeStart = reader.readStringOrNull(offsets[15]);
+ object.timeformat = reader.readString(offsets[16]);
+ object.widgetBackgroundColor = reader.readStringOrNull(offsets[17]);
+ object.widgetTextColor = reader.readStringOrNull(offsets[18]);
+ object.wind = reader.readString(offsets[19]);
return object;
}
@@ -223,33 +253,41 @@ P _settingsDeserializeProp(
case 1:
return (reader.readString(offset)) as P;
case 2:
- return (reader.readStringOrNull(offset)) as P;
- case 3:
return (reader.readBool(offset)) as P;
+ case 3:
+ return (reader.readStringOrNull(offset)) as P;
case 4:
return (reader.readBool(offset)) as P;
case 5:
- return (reader.readString(offset)) as P;
+ return (reader.readBool(offset)) as P;
case 6:
return (reader.readBool(offset)) as P;
case 7:
- return (reader.readBool(offset)) as P;
+ return (reader.readString(offset)) as P;
case 8:
return (reader.readBool(offset)) as P;
case 9:
- return (reader.readStringOrNull(offset)) as P;
+ return (reader.readBool(offset)) as P;
case 10:
- return (reader.readStringOrNull(offset)) as P;
+ return (reader.readString(offset)) as P;
case 11:
- return (reader.readLongOrNull(offset)) as P;
+ return (reader.readBool(offset)) as P;
case 12:
return (reader.readStringOrNull(offset)) as P;
case 13:
- return (reader.readString(offset)) as P;
- case 14:
return (reader.readStringOrNull(offset)) as P;
+ case 14:
+ return (reader.readLongOrNull(offset)) as P;
case 15:
return (reader.readStringOrNull(offset)) as P;
+ case 16:
+ return (reader.readString(offset)) as P;
+ case 17:
+ return (reader.readStringOrNull(offset)) as P;
+ case 18:
+ return (reader.readStringOrNull(offset)) as P;
+ case 19:
+ return (reader.readString(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
@@ -484,6 +522,16 @@ extension SettingsQueryFilter
});
}
+ QueryBuilder hideMapEqualTo(
+ bool value) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'hideMap',
+ value: value,
+ ));
+ });
+ }
+
QueryBuilder idEqualTo(Id value) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
@@ -682,6 +730,16 @@ extension SettingsQueryFilter
});
}
+ QueryBuilder largeElementEqualTo(
+ bool value) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'largeElement',
+ value: value,
+ ));
+ });
+ }
+
QueryBuilder locationEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
@@ -856,6 +914,136 @@ extension SettingsQueryFilter
});
}
+ QueryBuilder pressureEqualTo(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'pressure',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder pressureGreaterThan(
+ String value, {
+ bool include = false,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.greaterThan(
+ include: include,
+ property: r'pressure',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder pressureLessThan(
+ String value, {
+ bool include = false,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.lessThan(
+ include: include,
+ property: r'pressure',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder pressureBetween(
+ String lower,
+ String upper, {
+ bool includeLower = true,
+ bool includeUpper = true,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.between(
+ property: r'pressure',
+ lower: lower,
+ includeLower: includeLower,
+ upper: upper,
+ includeUpper: includeUpper,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder pressureStartsWith(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.startsWith(
+ property: r'pressure',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder pressureEndsWith(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.endsWith(
+ property: r'pressure',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder pressureContains(
+ String value,
+ {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.contains(
+ property: r'pressure',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder pressureMatches(
+ String pattern,
+ {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.matches(
+ property: r'pressure',
+ wildcard: pattern,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder pressureIsEmpty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'pressure',
+ value: '',
+ ));
+ });
+ }
+
+ QueryBuilder pressureIsNotEmpty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.greaterThan(
+ property: r'pressure',
+ value: '',
+ ));
+ });
+ }
+
QueryBuilder roundDegreeEqualTo(
bool value) {
return QueryBuilder.apply(this, (query) {
@@ -1813,6 +2001,136 @@ extension SettingsQueryFilter
));
});
}
+
+ QueryBuilder windEqualTo(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'wind',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder windGreaterThan(
+ String value, {
+ bool include = false,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.greaterThan(
+ include: include,
+ property: r'wind',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder windLessThan(
+ String value, {
+ bool include = false,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.lessThan(
+ include: include,
+ property: r'wind',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder windBetween(
+ String lower,
+ String upper, {
+ bool includeLower = true,
+ bool includeUpper = true,
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.between(
+ property: r'wind',
+ lower: lower,
+ includeLower: includeLower,
+ upper: upper,
+ includeUpper: includeUpper,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder windStartsWith(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.startsWith(
+ property: r'wind',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder windEndsWith(
+ String value, {
+ bool caseSensitive = true,
+ }) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.endsWith(
+ property: r'wind',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder windContains(
+ String value,
+ {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.contains(
+ property: r'wind',
+ value: value,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder windMatches(
+ String pattern,
+ {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.matches(
+ property: r'wind',
+ wildcard: pattern,
+ caseSensitive: caseSensitive,
+ ));
+ });
+ }
+
+ QueryBuilder windIsEmpty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.equalTo(
+ property: r'wind',
+ value: '',
+ ));
+ });
+ }
+
+ QueryBuilder windIsNotEmpty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addFilterCondition(FilterCondition.greaterThan(
+ property: r'wind',
+ value: '',
+ ));
+ });
+ }
}
extension SettingsQueryObject
@@ -1846,6 +2164,18 @@ extension SettingsQuerySortBy on QueryBuilder {
});
}
+ QueryBuilder sortByHideMap() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'hideMap', Sort.asc);
+ });
+ }
+
+ QueryBuilder sortByHideMapDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'hideMap', Sort.desc);
+ });
+ }
+
QueryBuilder sortByLanguage() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'language', Sort.asc);
@@ -1858,6 +2188,18 @@ extension SettingsQuerySortBy on QueryBuilder {
});
}
+ QueryBuilder sortByLargeElement() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'largeElement', Sort.asc);
+ });
+ }
+
+ QueryBuilder sortByLargeElementDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'largeElement', Sort.desc);
+ });
+ }
+
QueryBuilder sortByLocation() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'location', Sort.asc);
@@ -1918,6 +2260,18 @@ extension SettingsQuerySortBy on QueryBuilder {
});
}
+ QueryBuilder sortByPressure() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'pressure', Sort.asc);
+ });
+ }
+
+ QueryBuilder sortByPressureDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'pressure', Sort.desc);
+ });
+ }
+
QueryBuilder sortByRoundDegree() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'roundDegree', Sort.asc);
@@ -2014,6 +2368,18 @@ extension SettingsQuerySortBy on QueryBuilder {
return query.addSortBy(r'widgetTextColor', Sort.desc);
});
}
+
+ QueryBuilder sortByWind() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'wind', Sort.asc);
+ });
+ }
+
+ QueryBuilder sortByWindDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'wind', Sort.desc);
+ });
+ }
}
extension SettingsQuerySortThenBy
@@ -2042,6 +2408,18 @@ extension SettingsQuerySortThenBy
});
}
+ QueryBuilder thenByHideMap() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'hideMap', Sort.asc);
+ });
+ }
+
+ QueryBuilder thenByHideMapDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'hideMap', Sort.desc);
+ });
+ }
+
QueryBuilder thenById() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'id', Sort.asc);
@@ -2066,6 +2444,18 @@ extension SettingsQuerySortThenBy
});
}
+ QueryBuilder thenByLargeElement() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'largeElement', Sort.asc);
+ });
+ }
+
+ QueryBuilder thenByLargeElementDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'largeElement', Sort.desc);
+ });
+ }
+
QueryBuilder thenByLocation() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'location', Sort.asc);
@@ -2126,6 +2516,18 @@ extension SettingsQuerySortThenBy
});
}
+ QueryBuilder thenByPressure() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'pressure', Sort.asc);
+ });
+ }
+
+ QueryBuilder thenByPressureDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'pressure', Sort.desc);
+ });
+ }
+
QueryBuilder thenByRoundDegree() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'roundDegree', Sort.asc);
@@ -2222,6 +2624,18 @@ extension SettingsQuerySortThenBy
return query.addSortBy(r'widgetTextColor', Sort.desc);
});
}
+
+ QueryBuilder thenByWind() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'wind', Sort.asc);
+ });
+ }
+
+ QueryBuilder thenByWindDesc() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addSortBy(r'wind', Sort.desc);
+ });
+ }
}
extension SettingsQueryWhereDistinct
@@ -2239,6 +2653,12 @@ extension SettingsQueryWhereDistinct
});
}
+ QueryBuilder distinctByHideMap() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addDistinctBy(r'hideMap');
+ });
+ }
+
QueryBuilder distinctByLanguage(
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
@@ -2246,6 +2666,12 @@ extension SettingsQueryWhereDistinct
});
}
+ QueryBuilder distinctByLargeElement() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addDistinctBy(r'largeElement');
+ });
+ }
+
QueryBuilder distinctByLocation() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'location');
@@ -2277,6 +2703,13 @@ extension SettingsQueryWhereDistinct
});
}
+ QueryBuilder distinctByPressure(
+ {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addDistinctBy(r'pressure', caseSensitive: caseSensitive);
+ });
+ }
+
QueryBuilder distinctByRoundDegree() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'roundDegree');
@@ -2332,6 +2765,13 @@ extension SettingsQueryWhereDistinct
caseSensitive: caseSensitive);
});
}
+
+ QueryBuilder distinctByWind(
+ {bool caseSensitive = true}) {
+ return QueryBuilder.apply(this, (query) {
+ return query.addDistinctBy(r'wind', caseSensitive: caseSensitive);
+ });
+ }
}
extension SettingsQueryProperty
@@ -2354,12 +2794,24 @@ 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');
});
}
+ QueryBuilder largeElementProperty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addPropertyName(r'largeElement');
+ });
+ }
+
QueryBuilder locationProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'location');
@@ -2390,6 +2842,12 @@ extension SettingsQueryProperty
});
}
+ QueryBuilder pressureProperty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addPropertyName(r'pressure');
+ });
+ }
+
QueryBuilder roundDegreeProperty() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'roundDegree');
@@ -2438,6 +2896,12 @@ extension SettingsQueryProperty
return query.addPropertyName(r'widgetTextColor');
});
}
+
+ QueryBuilder windProperty() {
+ return QueryBuilder.apply(this, (query) {
+ return query.addPropertyName(r'wind');
+ });
+ }
}
// coverage:ignore-file
@@ -2638,7 +3102,7 @@ const MainWeatherCacheSchema = CollectionSchema(
getId: _mainWeatherCacheGetId,
getLinks: _mainWeatherCacheGetLinks,
attach: _mainWeatherCacheAttach,
- version: '3.1.7',
+ version: '3.1.8',
);
int _mainWeatherCacheEstimateSize(
@@ -10361,7 +10825,7 @@ const LocationCacheSchema = CollectionSchema(
getId: _locationCacheGetId,
getLinks: _locationCacheGetLinks,
attach: _locationCacheAttach,
- version: '3.1.7',
+ version: '3.1.8',
);
int _locationCacheEstimateSize(
@@ -11460,7 +11924,7 @@ const WeatherCardSchema = CollectionSchema(
getId: _weatherCardGetId,
getLinks: _weatherCardGetLinks,
attach: _weatherCardAttach,
- version: '3.1.7',
+ version: '3.1.8',
);
int _weatherCardEstimateSize(
diff --git a/lib/app/modules/cards/view/info_weather_card.dart b/lib/app/modules/cards/view/info_weather_card.dart
deleted file mode 100644
index 22def54..0000000
--- a/lib/app/modules/cards/view/info_weather_card.dart
+++ /dev/null
@@ -1,191 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax/iconsax.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(
- Iconsax.arrow_left_1,
- 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],
- timeDay: weatherCard.sunrise![dayNow],
- timeNight: weatherCard.sunset![dayNow],
- tempMax: weatherCard.temperature2MMax![dayNow]!,
- tempMin: weatherCard.temperature2MMin![dayNow]!,
- ),
- Card(
- margin: const EdgeInsets.only(bottom: 15),
- child: Padding(
- padding:
- const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
- child: SizedBox(
- height: 135,
- child: ScrollablePositionedList.separated(
- key: const PageStorageKey(1),
- separatorBuilder: (BuildContext context, int index) {
- return const VerticalDivider(
- width: 10,
- indent: 40,
- endIndent: 40,
- );
- },
- scrollDirection: Axis.horizontal,
- itemScrollController: itemScrollController,
- itemCount: weatherCard.time!.length,
- itemBuilder: (ctx, i) => GestureDetector(
- onTap: () {
- timeNow = i;
- dayNow = (i / 24).floor();
- setState(() {});
- },
- child: Container(
- margin: const EdgeInsets.symmetric(vertical: 5),
- padding: const EdgeInsets.symmetric(
- horizontal: 20,
- vertical: 5,
- ),
- decoration: BoxDecoration(
- color: i == timeNow
- ? context.theme.colorScheme.secondaryContainer
- : Colors.transparent,
- borderRadius: const BorderRadius.all(
- Radius.circular(20),
- ),
- ),
- child: WeatherHourly(
- time: weatherCard.time![i],
- weather: weatherCard.weathercode![i],
- degree: weatherCard.temperature2M![i],
- timeDay: weatherCard.sunrise![(i / 24).floor()],
- timeNight: weatherCard.sunset![(i / 24).floor()],
- ),
- ),
- ),
- ),
- ),
- ),
- ),
- SunsetSunrise(
- timeSunrise: weatherCard.sunrise![dayNow],
- timeSunset: weatherCard.sunset![dayNow],
- ),
- DescContainer(
- humidity: weatherCard.relativehumidity2M?[timeNow],
- wind: weatherCard.windspeed10M?[timeNow],
- visibility: weatherCard.visibility?[timeNow],
- feels: weatherCard.apparentTemperature?[timeNow],
- evaporation: weatherCard.evapotranspiration?[timeNow],
- precipitation: weatherCard.precipitation?[timeNow],
- direction: weatherCard.winddirection10M?[timeNow],
- pressure: weatherCard.surfacePressure?[timeNow],
- rain: weatherCard.rain?[timeNow],
- cloudcover: weatherCard.cloudcover?[timeNow],
- windgusts: weatherCard.windgusts10M?[timeNow],
- uvIndex: weatherCard.uvIndex?[timeNow],
- dewpoint2M: weatherCard.dewpoint2M?[timeNow],
- precipitationProbability:
- weatherCard.precipitationProbability?[timeNow],
- shortwaveRadiation: weatherCard.shortwaveRadiation?[timeNow],
- initiallyExpanded: false,
- title: 'hourlyVariables'.tr,
- ),
- WeatherDaily(
- weatherData: weatherCard,
- onTap: () => Get.to(
- () => WeatherMore(
- weatherData: weatherCard,
- ),
- transition: Transition.downToUp,
- ),
- ),
- ],
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/cards/view/list_weather_card.dart b/lib/app/modules/cards/view/list_weather_card.dart
deleted file mode 100644
index 73948b1..0000000
--- a/lib/app/modules/cards/view/list_weather_card.dart
+++ /dev/null
@@ -1,103 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax/iconsax.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(
- Iconsax.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(
- Iconsax.close_circle,
- color: Colors.grey,
- size: 20,
- ),
- )
- : null,
- ),
- ),
- ];
- },
- body: RefreshIndicator(
- onRefresh: () async {
- await weatherController.updateCacheCard(true);
- setState(() {});
- },
- child: WeatherCardList(searchCity: filter),
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/cards/widgets/create_card_weather.dart b/lib/app/modules/cards/widgets/create_card_weather.dart
deleted file mode 100644
index 8983091..0000000
--- a/lib/app/modules/cards/widgets/create_card_weather.dart
+++ /dev/null
@@ -1,251 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax/iconsax.dart';
-import 'package:rain/app/api/api.dart';
-import 'package:rain/app/api/city.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/widgets/text_form.dart';
-import 'package:rain/main.dart';
-
-class CreateWeatherCard extends StatefulWidget {
- const CreateWeatherCard({super.key});
-
- @override
- State createState() => _CreateWeatherCardState();
-}
-
-class _CreateWeatherCardState extends State {
- bool isLoading = false;
- final formKey = GlobalKey();
- final _focusNode = FocusNode();
- final weatherController = Get.put(WeatherController());
- final _controller = TextEditingController();
- final _controllerLat = TextEditingController();
- final _controllerLon = TextEditingController();
- final _controllerCity = TextEditingController();
- final _controllerDistrict = TextEditingController();
-
- 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(() {});
- }
-
- @override
- Widget build(BuildContext context) {
- const kTextFieldElevation = 4.0;
- return 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.fromLTRB(10, 10, 10, 0),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- IconButton(
- onPressed: () {
- Get.back();
- },
- icon: const Icon(
- Iconsax.close_square,
- size: 18,
- ),
- ),
- Text(
- 'create'.tr,
- style: context.textTheme.titleLarge?.copyWith(
- fontSize: 20,
- ),
- textAlign: TextAlign.center,
- ),
- IconButton(
- 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();
- }
- },
- icon: const Icon(
- Iconsax.tick_square,
- size: 18,
- ),
- ),
- ],
- ),
- ),
- 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(Iconsax.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(Iconsax.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(Iconsax.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(Icons.location_city_rounded),
- 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(Iconsax.global),
- margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
- validator: (value) {
- if (value == null || value.isEmpty) {
- return 'validateName'.tr;
- }
- return null;
- },
- ),
- const SizedBox(height: 20),
- ],
- ),
- ),
- if (isLoading)
- const Center(
- child: CircularProgressIndicator(),
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/cards/widgets/weather_card_container.dart b/lib/app/modules/cards/widgets/weather_card_container.dart
deleted file mode 100644
index 921cabd..0000000
--- a/lib/app/modules/cards/widgets/weather_card_container.dart
+++ /dev/null
@@ -1,124 +0,0 @@
-import 'package:flutter/material.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 SizedBox(width: 7),
- Text(
- statusWeather.getText(widget.weather[weatherController
- .getTime(widget.time, widget.timezone)]),
- style: context.textTheme.titleMedium?.copyWith(
- color: Colors.grey,
- fontWeight: FontWeight.w400,
- ),
- ),
- ],
- ),
- const SizedBox(height: 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 SizedBox(height: 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 SizedBox(width: 5),
- Image.asset(
- statusWeather.getImageNow(
- widget.weather[
- weatherController.getTime(widget.time, widget.timezone)],
- widget.time[
- weatherController.getTime(widget.time, widget.timezone)],
- widget.timeDay[weatherController.getDay(
- widget.timeDaily, widget.timezone)],
- widget.timeNight[weatherController.getDay(
- widget.timeDaily, widget.timezone)]),
- scale: 6.5,
- ),
- ],
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/cards/widgets/weather_card_list.dart b/lib/app/modules/cards/widgets/weather_card_list.dart
deleted file mode 100644
index aa3a5ec..0000000
--- a/lib/app/modules/cards/widgets/weather_card_list.dart
+++ /dev/null
@@ -1,115 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax/iconsax.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(
- Iconsax.trush_square,
- color: Colors.red,
- ),
- ),
- ),
- confirmDismiss: (DismissDirection direction) async {
- return await showAdaptiveDialog(
- context: context,
- builder: (BuildContext context) {
- return AlertDialog.adaptive(
- title: Text(
- 'deletedCardWeather'.tr,
- style: textTheme.titleLarge,
- ),
- content: Text(
- 'deletedCardWeatherQuery'.tr,
- style: titleMedium,
- ),
- actions: [
- TextButton(
- onPressed: () => Get.back(result: false),
- child: Text(
- 'cancel'.tr,
- style: titleMedium?.copyWith(
- color: Colors.blueAccent,
- ),
- ),
- ),
- TextButton(
- onPressed: () => Get.back(result: true),
- child: Text(
- 'delete'.tr,
- style: titleMedium?.copyWith(
- color: Colors.red,
- ),
- ),
- ),
- ],
- );
- },
- );
- },
- onDismissed: (DismissDirection direction) async {
- await weatherController.deleteCardWeather(weatherCardList);
- },
- child: GestureDetector(
- onTap: () => Get.to(
- () => InfoWeatherCard(
- weatherCard: weatherCardList,
- ),
- transition: Transition.downToUp,
- ),
- child: WeatherCardContainer(
- time: weatherCardList.time!,
- timeDaily: weatherCardList.timeDaily!,
- timeDay: weatherCardList.sunrise!,
- timeNight: weatherCardList.sunset!,
- weather: weatherCardList.weathercode!,
- degree: weatherCardList.temperature2M!,
- district: weatherCardList.district!,
- city: weatherCardList.city!,
- timezone: weatherCardList.timezone!,
- ),
- ),
- ),
- ),
- ],
- );
- }
-}
diff --git a/lib/app/modules/geolocation.dart b/lib/app/modules/geolocation.dart
deleted file mode 100644
index 51c7a89..0000000
--- a/lib/app/modules/geolocation.dart
+++ /dev/null
@@ -1,416 +0,0 @@
-import 'dart:ui';
-import 'package:flutter/material.dart';
-import 'package:geolocator/geolocator.dart';
-import 'package:get/get.dart';
-import 'package:iconsax/iconsax.dart';
-import 'package:rain/app/api/api.dart';
-import 'package:rain/app/api/city.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();
-
- 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(() {});
- }
-
- @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(
- Iconsax.arrow_left_1,
- 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: [
- Image.asset(
- 'assets/icons/Search.png',
- scale: 7,
- ),
- Padding(
- padding: const EdgeInsets.symmetric(
- vertical: 5, horizontal: 10),
- 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(
- Iconsax.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,
- ),
- 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(
- Iconsax.location,
- ),
- ),
- ),
- ),
- ],
- ),
- MyTextForm(
- elevation: kTextFieldElevation,
- controller: _controllerLat,
- labelText: 'lat'.tr,
- type: TextInputType.number,
- icon: const Icon(Iconsax.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(Iconsax.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(Icons.location_city_rounded),
- 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(Iconsax.global),
- margin: const EdgeInsets.only(
- left: 10, right: 10, top: 10),
- ),
- const SizedBox(height: 20),
- ],
- ),
- ),
- ),
- ],
- ),
- ),
- Padding(
- padding:
- const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
- child: MyTextButton(
- buttonName: 'done'.tr,
- onPressed: () async {
- if (formKeySearch.currentState!.validate()) {
- textTrim(_controllerLat);
- textTrim(_controllerLon);
- textTrim(_controllerCity);
- textTrim(_controllerDistrict);
- try {
- await weatherController.deleteAll(true);
- await weatherController.getLocation(
- double.parse(_controllerLat.text),
- double.parse(_controllerLon.text),
- _controllerDistrict.text,
- _controllerCity.text,
- );
- widget.isStart
- ? Get.off(() => const HomePage(),
- transition: Transition.downToUp)
- : Get.back();
- } catch (error) {
- Future.error(error);
- }
- }
- },
- ),
- ),
- ],
- ),
- if (isLoading)
- BackdropFilter(
- filter: ImageFilter.blur(sigmaY: 3, sigmaX: 3),
- child: const Center(
- child: CircularProgressIndicator(),
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/home.dart b/lib/app/modules/home.dart
deleted file mode 100644
index 61d7a28..0000000
--- a/lib/app/modules/home.dart
+++ /dev/null
@@ -1,277 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax/iconsax.dart';
-import 'package:isar/isar.dart';
-import 'package:rain/app/api/api.dart';
-import 'package:rain/app/api/city.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.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 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;
-
- 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(
- Iconsax.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: textTheme.titleMedium?.copyWith(
- fontWeight: FontWeight.w600,
- fontSize: 18,
- ),
- );
- },
- ),
- 1 => Text(
- 'cities'.tr,
- style: textTheme.titleMedium?.copyWith(
- fontWeight: FontWeight.w600,
- fontSize: 18,
- ),
- ),
- 2 => Text(
- 'settings_full'.tr,
- style: textTheme.titleMedium?.copyWith(
- fontWeight: FontWeight.w600,
- fontSize: 18,
- ),
- ),
- int() => null,
- },
- actions: switch (tabIndex) {
- 0 => [
- IconButton(
- onPressed: () {
- if (visible) {
- _controller.clear();
- _focusNode.unfocus();
- visible = false;
- } else {
- visible = true;
- }
- setState(() {});
- },
- icon: Icon(
- visible ? Icons.close : Iconsax.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(Iconsax.cloud_sunny),
- selectedIcon: const Icon(Iconsax.cloud_sunny5),
- label: 'name'.tr,
- ),
- NavigationDestination(
- icon: const Icon(Iconsax.map_1),
- selectedIcon: const Icon(Iconsax.map5),
- label: 'cities'.tr,
- ),
- NavigationDestination(
- icon: const Icon(Iconsax.category),
- selectedIcon: const Icon(Iconsax.category5),
- label: 'settings_full'.tr,
- ),
- ],
- ),
- floatingActionButton: tabIndex == 1
- ? FloatingActionButton(
- onPressed: () => showModalBottomSheet(
- context: context,
- isScrollControlled: true,
- enableDrag: false,
- builder: (BuildContext context) =>
- const CreateWeatherCard(),
- ),
- child: const Icon(
- Iconsax.add,
- ),
- )
- : null,
- ),
- ),
- );
- }
-}
diff --git a/lib/app/modules/main/view/weather.dart b/lib/app/modules/main/view/weather.dart
deleted file mode 100644
index efde67e..0000000
--- a/lib/app/modules/main/view/weather.dart
+++ /dev/null
@@ -1,177 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/app/data/weather.dart';
-import 'package:rain/app/widgets/daily/weather_daily.dart';
-import 'package:rain/app/widgets/daily/weather_more.dart';
-import 'package:rain/app/widgets/desc/desc_container.dart';
-import 'package:rain/app/widgets/hourly/weather_hourly.dart';
-import 'package:rain/app/widgets/now/weather_now.dart';
-import 'package:rain/app/widgets/shimmer.dart';
-import 'package:rain/app/widgets/sun_moon/sunset_sunrise.dart';
-import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
-
-class WeatherPage extends StatefulWidget {
- const WeatherPage({super.key});
-
- @override
- State createState() => _WeatherPageState();
-}
-
-class _WeatherPageState extends State {
- final weatherController = Get.put(WeatherController());
-
- @override
- Widget build(BuildContext context) {
- return RefreshIndicator(
- onRefresh: () async {
- await weatherController.deleteAll(false);
- await weatherController.setLocation();
- setState(() {});
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 10),
- child: Obx(() {
- if (weatherController.isLoading.isTrue) {
- return ListView(
- children: const [
- MyShimmer(
- hight: 200,
- ),
- MyShimmer(
- hight: 130,
- edgeInsetsMargin: EdgeInsets.symmetric(vertical: 15),
- ),
- MyShimmer(
- hight: 90,
- edgeInsetsMargin: EdgeInsets.only(bottom: 15),
- ),
- MyShimmer(
- hight: 400,
- edgeInsetsMargin: EdgeInsets.only(bottom: 15),
- ),
- MyShimmer(
- hight: 450,
- edgeInsetsMargin: EdgeInsets.only(bottom: 15),
- )
- ],
- );
- }
-
- final mainWeather = weatherController.mainWeather;
- final weatherCard = WeatherCard.fromJson(mainWeather.toJson());
- final hourOfDay = weatherController.hourOfDay.value;
- final dayOfNow = weatherController.dayOfNow.value;
- final sunrise = mainWeather.sunrise![dayOfNow];
- final sunset = mainWeather.sunset![dayOfNow];
- final tempMax = mainWeather.temperature2MMax![dayOfNow];
- final tempMin = mainWeather.temperature2MMin![dayOfNow];
-
- return ListView(
- children: [
- WeatherNow(
- time: mainWeather.time![hourOfDay],
- weather: mainWeather.weathercode![hourOfDay],
- degree: mainWeather.temperature2M![hourOfDay],
- timeDay: sunrise,
- timeNight: sunset,
- tempMax: tempMax!,
- tempMin: tempMin!,
- ),
- Card(
- margin: const EdgeInsets.only(bottom: 15),
- child: Padding(
- padding:
- const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
- child: SizedBox(
- height: 135,
- child: ScrollablePositionedList.separated(
- key: const PageStorageKey(0),
- separatorBuilder: (BuildContext context, int index) {
- return const VerticalDivider(
- width: 10,
- indent: 40,
- endIndent: 40,
- );
- },
- scrollDirection: Axis.horizontal,
- itemScrollController:
- weatherController.itemScrollController,
- itemCount: mainWeather.time!.length,
- itemBuilder: (ctx, i) {
- final i24 = (i / 24).floor();
-
- return GestureDetector(
- onTap: () {
- weatherController.hourOfDay.value = i;
- weatherController.dayOfNow.value = i24;
- setState(() {});
- },
- child: Container(
- margin: const EdgeInsets.symmetric(vertical: 5),
- padding: const EdgeInsets.symmetric(
- horizontal: 20,
- vertical: 5,
- ),
- decoration: BoxDecoration(
- color: i == hourOfDay
- ? context.theme.colorScheme.secondaryContainer
- : Colors.transparent,
- borderRadius: const BorderRadius.all(
- Radius.circular(20),
- ),
- ),
- child: WeatherHourly(
- time: mainWeather.time![i],
- weather: mainWeather.weathercode![i],
- degree: mainWeather.temperature2M![i],
- timeDay: mainWeather.sunrise![i24],
- timeNight: mainWeather.sunset![i24],
- ),
- ),
- );
- },
- ),
- ),
- ),
- ),
- SunsetSunrise(
- timeSunrise: sunrise,
- timeSunset: sunset,
- ),
- DescContainer(
- humidity: mainWeather.relativehumidity2M?[hourOfDay],
- wind: mainWeather.windspeed10M?[hourOfDay],
- visibility: mainWeather.visibility?[hourOfDay],
- feels: mainWeather.apparentTemperature?[hourOfDay],
- evaporation: mainWeather.evapotranspiration?[hourOfDay],
- precipitation: mainWeather.precipitation?[hourOfDay],
- direction: mainWeather.winddirection10M?[hourOfDay],
- pressure: mainWeather.surfacePressure?[hourOfDay],
- rain: mainWeather.rain?[hourOfDay],
- cloudcover: mainWeather.cloudcover?[hourOfDay],
- windgusts: mainWeather.windgusts10M?[hourOfDay],
- uvIndex: mainWeather.uvIndex?[hourOfDay],
- dewpoint2M: mainWeather.dewpoint2M?[hourOfDay],
- precipitationProbability:
- mainWeather.precipitationProbability?[hourOfDay],
- shortwaveRadiation: mainWeather.shortwaveRadiation?[hourOfDay],
- initiallyExpanded: false,
- title: 'hourlyVariables'.tr,
- ),
- WeatherDaily(
- weatherData: weatherCard,
- onTap: () => Get.to(
- () => WeatherMore(
- weatherData: weatherCard,
- ),
- transition: Transition.downToUp,
- ),
- )
- ],
- );
- }),
- ),
- );
- }
-}
diff --git a/lib/app/modules/onboarding.dart b/lib/app/modules/onboarding.dart
deleted file mode 100644
index 8140584..0000000
--- a/lib/app/modules/onboarding.dart
+++ /dev/null
@@ -1,183 +0,0 @@
-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 {
- const OnBording({super.key});
-
- @override
- State createState() => _OnBordingState();
-}
-
-class _OnBordingState extends State {
- late PageController pageController;
- int pageIndex = 0;
-
- @override
- void initState() {
- pageController = PageController(initialPage: 0);
- super.initState();
- }
-
- @override
- void dispose() {
- pageController.dispose();
- super.dispose();
- }
-
- void onBoardHome() {
- settings.onboard = true;
- isar.writeTxnSync(() => isar.settings.putSync(settings));
- Get.off(() => const SelectGeolocation(isStart: true),
- transition: Transition.downToUp);
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- body: SafeArea(
- child: Column(
- children: [
- Expanded(
- child: PageView.builder(
- controller: pageController,
- itemCount: data.length,
- onPageChanged: (index) {
- setState(() {
- pageIndex = index;
- });
- },
- itemBuilder: (context, index) => OnboardContent(
- image: data[index].image,
- title: data[index].title,
- description: data[index].description,
- ),
- ),
- ),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- ...List.generate(
- data.length,
- (index) => Padding(
- padding: const EdgeInsets.symmetric(horizontal: 5),
- child: DotIndicator(isActive: index == pageIndex),
- )),
- ],
- ),
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
- child: MyTextButton(
- buttonName:
- pageIndex == data.length - 1 ? 'start'.tr : 'next'.tr,
- onPressed: () {
- pageIndex == data.length - 1
- ? onBoardHome()
- : pageController.nextPage(
- duration: const Duration(milliseconds: 300),
- curve: Curves.ease,
- );
- },
- ),
- )
- ],
- ),
- ),
- );
- }
-}
-
-class DotIndicator extends StatelessWidget {
- const DotIndicator({
- super.key,
- this.isActive = false,
- });
-
- final bool isActive;
-
- @override
- Widget build(BuildContext context) {
- return AnimatedContainer(
- duration: const Duration(milliseconds: 300),
- height: 8,
- width: 8,
- decoration: BoxDecoration(
- color: isActive
- ? context.theme.colorScheme.secondary
- : context.theme.colorScheme.secondaryContainer,
- shape: BoxShape.circle,
- ),
- );
- }
-}
-
-class Onboard {
- final String image, title, description;
-
- Onboard({
- required this.image,
- required this.title,
- required this.description,
- });
-}
-
-final List data = [
- Onboard(
- image: 'assets/icons/Rain.png',
- title: 'name'.tr,
- description: 'description'.tr),
- Onboard(
- image: 'assets/icons/Design.png',
- title: 'name2'.tr,
- description: 'description2'.tr),
- Onboard(
- image: 'assets/icons/Team.png',
- title: 'name3'.tr,
- description: 'description3'.tr),
-];
-
-class OnboardContent extends StatelessWidget {
- const OnboardContent({
- super.key,
- required this.image,
- required this.title,
- required this.description,
- });
- final String image, title, description;
-
- @override
- Widget build(BuildContext context) {
- return Column(
- children: [
- Flexible(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- Image.asset(
- image,
- scale: 5,
- ),
- Text(
- title,
- style: context.textTheme.titleLarge
- ?.copyWith(fontWeight: FontWeight.w600),
- ),
- const SizedBox(height: 10),
- SizedBox(
- width: 300,
- child: Text(
- description,
- style: context.textTheme.labelLarge?.copyWith(fontSize: 14),
- textAlign: TextAlign.center,
- ),
- ),
- ],
- ),
- ),
- ],
- );
- }
-}
diff --git a/lib/app/modules/settings/view/settings.dart b/lib/app/modules/settings/view/settings.dart
deleted file mode 100644
index 9f03999..0000000
--- a/lib/app/modules/settings/view/settings.dart
+++ /dev/null
@@ -1,989 +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:geolocator/geolocator.dart';
-import 'package:get/get.dart';
-import 'package:iconsax/iconsax.dart';
-import 'package:intl/intl.dart';
-import 'package:package_info_plus/package_info_plus.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';
-import 'package:url_launcher/url_launcher.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;
- });
- }
-
- void urlLauncher(String uri) async {
- final Uri url = Uri.parse(uri);
- if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
- throw Exception('Could not launch $url');
- }
- }
-
- @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(Iconsax.brush_1),
- text: 'appearance'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return 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(Iconsax.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(Iconsax.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(Iconsax.colorfilter),
- text: 'materialColor'.tr,
- switcher: true,
- value: settings.materialColor,
- onChange: (value) {
- themeController.saveMaterialTheme(value);
- MyApp.updateAppState(context,
- newMaterialColor: value);
- },
- ),
- const SizedBox(height: 10),
- ],
- ),
- );
- },
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(Iconsax.code),
- text: 'functions'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return 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(Iconsax.map_1),
- 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(Iconsax.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(Iconsax.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(Iconsax.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(Iconsax.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 SizedBox(height: 10),
- ],
- ),
- );
- },
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(Iconsax.d_square),
- text: 'data'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return 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(Iconsax.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(Iconsax.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(Iconsax.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(Iconsax.clock),
- 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 SizedBox(height: 10),
- ],
- ),
- );
- },
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(Iconsax.setting_3),
- text: 'widget'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return 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(Iconsax.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(
- Iconsax.tick_square,
- ),
- onPressed: () {
- if (colorBackground == null) {
- return;
- }
- weatherController
- .updateWidgetBackgroundColor(
- colorBackground!);
- MyApp.updateAppState(context,
- newWidgetBackgroundColor:
- colorBackground);
- Get.back();
- },
- ),
- ],
- ),
- ),
- ),
- );
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(Iconsax.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(
- Iconsax.tick_square,
- ),
- onPressed: () {
- if (colorText == null) return;
- weatherController
- .updateWidgetTextColor(
- colorText!);
- MyApp.updateAppState(context,
- newWidgetTextColor:
- colorText);
- Get.back();
- },
- ),
- ],
- ),
- ),
- ),
- );
- },
- ),
- const SizedBox(height: 10),
- ],
- ),
- );
- },
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(Iconsax.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 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 SizedBox(height: 10),
- ],
- );
- },
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(Iconsax.dollar_square),
- text: 'support'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return 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(Iconsax.card),
- text: 'DonationAlerts',
- onPressed: () => urlLauncher(
- 'https://www.donationalerts.com/r/darkmoonight'),
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(Iconsax.wallet),
- text: 'ЮMoney',
- onPressed: () => urlLauncher(
- 'https://yoomoney.ru/to/4100117672775961'),
- ),
- const SizedBox(height: 10),
- ],
- ),
- );
- },
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(Iconsax.link_square),
- text: 'groups'.tr,
- onPressed: () {
- showModalBottomSheet(
- context: context,
- builder: (BuildContext context) {
- return 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(Iconsax.voice_square),
- text: 'Discord',
- onPressed: () async {
- final Uri url =
- Uri.parse('https://discord.gg/JMMa9aHh8f');
- if (!await launchUrl(url,
- mode: LaunchMode.externalApplication)) {
- throw Exception('Could not launch $url');
- }
- },
- ),
- SettingCard(
- elevation: 4,
- icon: const Icon(Iconsax.message_square),
- text: 'Telegram',
- onPressed: () async {
- final Uri url =
- Uri.parse('https://t.me/darkmoonightX');
- if (!await launchUrl(url,
- mode: LaunchMode.externalApplication)) {
- throw Exception('Could not launch $url');
- }
- },
- ),
- const SizedBox(height: 10),
- ],
- ),
- );
- },
- );
- },
- );
- },
- ),
- SettingCard(
- icon: const Icon(Iconsax.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(Iconsax.hierarchy_square_2),
- text: 'version'.tr,
- info: true,
- infoWidget: _TextInfo(
- info: '$appVersion',
- ),
- ),
- SettingCard(
- icon: Image.asset(
- 'assets/images/github.png',
- scale: 20,
- ),
- text: '${'project'.tr} GitHub',
- onPressed: () =>
- 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: () => urlLauncher('https://open-meteo.com/'),
- ),
- ),
- const SizedBox(height: 10),
- ],
- ),
- );
- }
-}
-
-class _TextInfo extends StatelessWidget {
- const _TextInfo({required this.info});
-
- final String info;
-
- @override
- Widget build(BuildContext context) {
- return Padding(
- padding: const EdgeInsets.only(right: 5),
- child: Text(
- info,
- style: context.textTheme.bodyMedium,
- overflow: TextOverflow.visible,
- ),
- );
- }
-}
diff --git a/lib/app/modules/settings/widgets/setting_card.dart b/lib/app/modules/settings/widgets/setting_card.dart
deleted file mode 100644
index 4bdccca..0000000
--- a/lib/app/modules/settings/widgets/setting_card.dart
+++ /dev/null
@@ -1,94 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-import 'package:iconsax/iconsax.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(16),
- ),
- 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(
- 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(
- Iconsax.arrow_right_3,
- size: 18,
- ),
- ],
- )
- : infoWidget!
- : const Icon(
- Iconsax.arrow_right_3,
- size: 18,
- ),
- ),
- );
- }
-}
diff --git a/lib/app/services/notification.dart b/lib/app/services/notification.dart
deleted file mode 100644
index ba07067..0000000
--- a/lib/app/services/notification.dart
+++ /dev/null
@@ -1,42 +0,0 @@
-import 'package:flutter_local_notifications/flutter_local_notifications.dart';
-import 'package:rain/app/controller/controller.dart';
-import 'package:rain/main.dart';
-import 'package:timezone/timezone.dart' as tz;
-
-class NotificationShow {
- Future showNotification(
- int id,
- String title,
- String body,
- DateTime date,
- String icon,
- ) async {
- final imagePath = await WeatherController().getLocalImagePath(icon);
-
- AndroidNotificationDetails androidNotificationDetails =
- AndroidNotificationDetails(
- 'Rain',
- 'DARK NIGHT',
- priority: Priority.high,
- importance: Importance.max,
- playSound: false,
- enableVibration: false,
- largeIcon: FilePathAndroidBitmap(imagePath),
- );
- NotificationDetails notificationDetails =
- NotificationDetails(android: androidNotificationDetails);
-
- var scheduledTime = tz.TZDateTime.from(date, tz.local);
- flutterLocalNotificationsPlugin.zonedSchedule(
- id,
- title,
- body,
- scheduledTime,
- notificationDetails,
- uiLocalNotificationDateInterpretation:
- UILocalNotificationDateInterpretation.absoluteTime,
- androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
- payload: imagePath,
- );
- }
-}
diff --git a/lib/app/services/utils.dart b/lib/app/services/utils.dart
deleted file mode 100644
index a7dd8a1..0000000
--- a/lib/app/services/utils.dart
+++ /dev/null
@@ -1,20 +0,0 @@
-import 'package:flutter/material.dart';
-import 'package:get/get.dart';
-
-final globalKey = GlobalKey();
-
-void showSnackBar({required String content, Function? onPressed}) {
- globalKey.currentState?.showSnackBar(
- SnackBar(
- content: Text(content),
- action: onPressed != null
- ? SnackBarAction(
- label: 'settings'.tr,
- onPressed: () {
- onPressed();
- },
- )
- : null,
- ),
- );
-}
diff --git a/lib/app/ui/geolocation.dart b/lib/app/ui/geolocation.dart
new file mode 100755
index 0000000..2b6165c
--- /dev/null
+++ b/lib/app/ui/geolocation.dart
@@ -0,0 +1,479 @@
+import 'dart:ui';
+import 'package:flutter/material.dart';
+import 'package:flutter_map/flutter_map.dart';
+import 'package:gap/gap.dart';
+import 'package:geolocator/geolocator.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:latlong2/latlong.dart';
+import 'package:rain/app/api/api.dart';
+import 'package:rain/app/api/city_api.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/ui/home.dart';
+import 'package:rain/app/ui/widgets/button.dart';
+import 'package:rain/app/ui/widgets/text_form.dart';
+import 'package:rain/main.dart';
+
+class SelectGeolocation extends StatefulWidget {
+ const SelectGeolocation({super.key, required this.isStart});
+ final bool isStart;
+
+ @override
+ State createState() => _SelectGeolocationState();
+}
+
+class _SelectGeolocationState extends State {
+ bool isLoading = false;
+ final formKeySearch = GlobalKey();
+ final _focusNode = FocusNode();
+ final weatherController = Get.put(WeatherController());
+ final _controller = TextEditingController();
+ final _controllerLat = TextEditingController();
+ final _controllerLon = TextEditingController();
+ final _controllerCity = TextEditingController();
+ final _controllerDistrict = TextEditingController();
+
+ static const kTextFieldElevation = 4.0;
+
+ static const colorFilter = ColorFilter.matrix([
+ -0.2, -0.7, -0.08, 0, 255, // Red channel
+ -0.2, -0.7, -0.08, 0, 255, // Green channel
+ -0.2, -0.7, -0.08, 0, 255, // Blue channel
+ 0, 0, 0, 1, 0, // Alpha channel
+ ]);
+
+ final bool _isDarkMode = Get.theme.brightness == Brightness.dark;
+ final mapController = MapController();
+
+ void textTrim(TextEditingController value) {
+ value.text = value.text.trim();
+ while (value.text.contains(' ')) {
+ value.text = value.text.replaceAll(' ', ' ');
+ }
+ }
+
+ void fillController(Result selection) {
+ _controllerLat.text = '${selection.latitude}';
+ _controllerLon.text = '${selection.longitude}';
+ _controllerCity.text = selection.name;
+ _controllerDistrict.text = selection.admin1;
+ _controller.clear();
+ _focusNode.unfocus();
+ setState(() {});
+ }
+
+ void fillControllerGeo(Map location) {
+ _controllerLat.text = '${location['lat']}';
+ _controllerLon.text = '${location['lon']}';
+ _controllerCity.text = location['district'];
+ _controllerDistrict.text = location['city'];
+ setState(() {});
+ }
+
+ void fillMap(double latitude, double longitude) {
+ _controllerLat.text = '$latitude';
+ _controllerLon.text = '$longitude';
+ setState(() {});
+ }
+
+ Widget _buildMapTileLayer() {
+ return TileLayer(
+ urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
+ userAgentPackageName: 'com.darkmoonight.rain',
+ );
+ }
+
+ Widget _buildMap() {
+ return ClipRRect(
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ child: SizedBox(
+ height: Get.size.height * 0.3,
+ child: FlutterMap(
+ mapController: mapController,
+ options: MapOptions(
+ backgroundColor: context.theme.colorScheme.surface,
+ initialCenter: const LatLng(55.7522, 37.6156),
+ initialZoom: 3,
+ interactionOptions: const InteractionOptions(
+ flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
+ ),
+ cameraConstraint: CameraConstraint.contain(
+ bounds: LatLngBounds(
+ const LatLng(-90, -180),
+ const LatLng(90, 180),
+ ),
+ ),
+ onLongPress: (tapPosition, point) =>
+ fillMap(point.latitude, point.longitude),
+ ),
+ children: [
+ if (_isDarkMode)
+ ColorFiltered(
+ colorFilter: colorFilter,
+ child: _buildMapTileLayer(),
+ )
+ else
+ _buildMapTileLayer(),
+ RichAttributionWidget(
+ animationConfig: const ScaleRAWA(),
+ attributions: [
+ TextSourceAttribution(
+ 'OpenStreetMap contributors',
+ onTap: () => weatherController.urlLauncher(
+ 'https://openstreetmap.org/copyright',
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSearchField() {
+ return RawAutocomplete(
+ focusNode: _focusNode,
+ textEditingController: _controller,
+ fieldViewBuilder:
+ (
+ BuildContext context,
+ TextEditingController fieldTextEditingController,
+ FocusNode fieldFocusNode,
+ VoidCallback onFieldSubmitted,
+ ) {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ labelText: 'search'.tr,
+ type: TextInputType.text,
+ icon: const Icon(IconsaxPlusLinear.global_search),
+ controller: _controller,
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ focusNode: _focusNode,
+ );
+ },
+ optionsBuilder: (TextEditingValue textEditingValue) {
+ if (textEditingValue.text.isEmpty) {
+ return const Iterable.empty();
+ }
+ return WeatherAPI().getCity(textEditingValue.text, locale);
+ },
+ onSelected: (Result selection) => fillController(selection),
+ displayStringForOption: (Result option) =>
+ '${option.name}, ${option.admin1}',
+ optionsViewBuilder:
+ (
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ ) {
+ return _buildOptionsView(context, onSelected, options);
+ },
+ );
+ }
+
+ Widget _buildOptionsView(
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ ) {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ child: Align(
+ alignment: Alignment.topCenter,
+ child: Material(
+ borderRadius: BorderRadius.circular(20),
+ elevation: 4.0,
+ child: ListView.builder(
+ padding: EdgeInsets.zero,
+ shrinkWrap: true,
+ itemCount: options.length,
+ itemBuilder: (BuildContext context, int index) {
+ final Result option = options.elementAt(index);
+ return InkWell(
+ onTap: () => onSelected(option),
+ child: ListTile(
+ title: Text(
+ '${option.name}, ${option.admin1}',
+ style: context.textTheme.labelLarge,
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildLocationButton() {
+ return Card(
+ elevation: kTextFieldElevation,
+ margin: const EdgeInsets.only(top: 10, right: 10),
+ child: Container(
+ margin: const EdgeInsets.all(2.5),
+ child: IconButton(
+ onPressed: _handleLocationButtonPress,
+ icon: const Icon(IconsaxPlusLinear.location),
+ ),
+ ),
+ );
+ }
+
+ Future _handleLocationButtonPress() async {
+ bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
+ if (!serviceEnabled) {
+ if (!context.mounted) return;
+ await _showLocationDialog();
+ return;
+ }
+ setState(() => isLoading = true);
+ final location = await weatherController.getCurrentLocationSearch();
+ fillControllerGeo(location);
+ setState(() => isLoading = false);
+ }
+
+ Future _showLocationDialog() async {
+ await showAdaptiveDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AlertDialog.adaptive(
+ title: Text('location'.tr, style: context.textTheme.titleLarge),
+ content: Text('no_location'.tr, style: context.textTheme.titleMedium),
+ actions: [
+ TextButton(
+ onPressed: () => Get.back(result: false),
+ child: Text(
+ 'cancel'.tr,
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.blueAccent,
+ ),
+ ),
+ ),
+ TextButton(
+ onPressed: () {
+ Geolocator.openLocationSettings();
+ Get.back(result: true);
+ },
+ child: Text(
+ 'settings'.tr,
+ style: context.textTheme.titleMedium?.copyWith(
+ color: Colors.green,
+ ),
+ ),
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ Widget _buildLatitudeField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerLat,
+ labelText: 'lat'.tr,
+ type: TextInputType.number,
+ icon: const Icon(IconsaxPlusLinear.location),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ validator: (value) => _validateLatitude(value),
+ );
+ }
+
+ Widget _buildLongitudeField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerLon,
+ labelText: 'lon'.tr,
+ type: TextInputType.number,
+ icon: const Icon(IconsaxPlusLinear.location),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ validator: (value) => _validateLongitude(value),
+ );
+ }
+
+ Widget _buildCityField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerCity,
+ labelText: 'city'.tr,
+ type: TextInputType.name,
+ icon: const Icon(IconsaxPlusLinear.building_3),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ validator: (value) => _validateCity(value),
+ );
+ }
+
+ Widget _buildDistrictField() {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ controller: _controllerDistrict,
+ labelText: 'district'.tr,
+ type: TextInputType.streetAddress,
+ icon: const Icon(IconsaxPlusLinear.global),
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ );
+ }
+
+ Widget _buildSubmitButton() {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
+ child: MyTextButton(buttonName: 'done'.tr, onPressed: _handleSubmit),
+ );
+ }
+
+ String? _validateLatitude(String? value) {
+ if (value == null || value.isEmpty) {
+ return 'validateValue'.tr;
+ }
+ double? numericValue = double.tryParse(value);
+ if (numericValue == null) {
+ return 'validateNumber'.tr;
+ }
+ if (numericValue < -90 || numericValue > 90) {
+ return 'validate90'.tr;
+ }
+ return null;
+ }
+
+ String? _validateLongitude(String? value) {
+ if (value == null || value.isEmpty) {
+ return 'validateValue'.tr;
+ }
+ double? numericValue = double.tryParse(value);
+ if (numericValue == null) {
+ return 'validateNumber'.tr;
+ }
+ if (numericValue < -180 || numericValue > 180) {
+ return 'validate180'.tr;
+ }
+ return null;
+ }
+
+ String? _validateCity(String? value) {
+ if (value == null || value.isEmpty) {
+ return 'validateName'.tr;
+ }
+ return null;
+ }
+
+ Future _handleSubmit() async {
+ if (formKeySearch.currentState!.validate()) {
+ textTrim(_controllerLat);
+ textTrim(_controllerLon);
+ textTrim(_controllerCity);
+ textTrim(_controllerDistrict);
+ try {
+ await weatherController.deleteAll(true);
+ await weatherController.getLocation(
+ double.parse(_controllerLat.text),
+ double.parse(_controllerLon.text),
+ _controllerDistrict.text,
+ _controllerCity.text,
+ );
+ widget.isStart
+ ? Get.off(() => const HomePage(), transition: Transition.downToUp)
+ : Get.back();
+ } catch (error) {
+ Future.error(error);
+ }
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Form(
+ key: formKeySearch,
+ child: Scaffold(
+ resizeToAvoidBottomInset: true,
+ appBar: _buildAppBar(),
+ body: SafeArea(
+ child: Stack(
+ children: [
+ Column(
+ children: [
+ Flexible(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ children: [
+ Flexible(
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 10,
+ ),
+ child: _buildMap(),
+ ),
+ Padding(
+ padding: const EdgeInsets.fromLTRB(
+ 10,
+ 15,
+ 10,
+ 5,
+ ),
+ child: Text(
+ 'searchMethod'.tr,
+ style: context.theme.textTheme.bodyLarge
+ ?.copyWith(fontWeight: FontWeight.bold),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ Row(
+ children: [
+ Flexible(child: _buildSearchField()),
+ _buildLocationButton(),
+ ],
+ ),
+ _buildLatitudeField(),
+ _buildLongitudeField(),
+ _buildCityField(),
+ _buildDistrictField(),
+ const Gap(20),
+ ],
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ _buildSubmitButton(),
+ ],
+ ),
+ if (isLoading)
+ BackdropFilter(
+ filter: ImageFilter.blur(sigmaY: 3, sigmaX: 3),
+ child: const Center(child: CircularProgressIndicator()),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ AppBar _buildAppBar() {
+ return AppBar(
+ centerTitle: true,
+ leading: widget.isStart
+ ? null
+ : IconButton(
+ onPressed: () {
+ Get.back();
+ },
+ icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
+ splashColor: Colors.transparent,
+ highlightColor: Colors.transparent,
+ ),
+ automaticallyImplyLeading: false,
+ title: Text(
+ 'searchCity'.tr,
+ style: context.textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/app/ui/home.dart b/lib/app/ui/home.dart
new file mode 100755
index 0000000..655d2ba
--- /dev/null
+++ b/lib/app/ui/home.dart
@@ -0,0 +1,311 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:isar/isar.dart';
+import 'package:rain/app/api/api.dart';
+import 'package:rain/app/api/city_api.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/places/view/place_list.dart';
+import 'package:rain/app/ui/places/widgets/create_place.dart';
+import 'package:rain/app/ui/geolocation.dart';
+import 'package:rain/app/ui/main/view/main.dart';
+import 'package:rain/app/ui/map/view/map.dart';
+import 'package:rain/app/ui/settings/view/settings.dart';
+import 'package:rain/app/utils/show_snack_bar.dart';
+import 'package:rain/main.dart';
+
+class HomePage extends StatefulWidget {
+ const HomePage({super.key});
+
+ @override
+ State createState() => _HomePageState();
+}
+
+class _HomePageState extends State with TickerProviderStateMixin {
+ int tabIndex = 0;
+ bool visible = false;
+ final _focusNode = FocusNode();
+ late TabController tabController;
+ final weatherController = Get.put(WeatherController());
+ final _controller = TextEditingController();
+
+ final List pages = [
+ const MainPage(),
+ const PlaceList(),
+ if (!settings.hideMap) const MapPage(),
+ const SettingsPage(),
+ ];
+
+ @override
+ void initState() {
+ super.initState();
+ getData();
+ setupTabController();
+ }
+
+ @override
+ void dispose() {
+ tabController.dispose();
+ super.dispose();
+ }
+
+ void setupTabController() {
+ tabController = TabController(
+ initialIndex: tabIndex,
+ length: pages.length,
+ vsync: this,
+ );
+
+ tabController.animation?.addListener(() {
+ int value = (tabController.animation!.value).round();
+ if (value != tabIndex) setState(() => tabIndex = value);
+ });
+
+ tabController.addListener(() {
+ setState(() {
+ tabIndex = tabController.index;
+ });
+ });
+ }
+
+ void getData() async {
+ await weatherController.deleteCache();
+ await weatherController.updateCacheCard(false);
+ await weatherController.setLocation();
+ }
+
+ void changeTabIndex(int index) {
+ setState(() {
+ tabIndex = index;
+ });
+ tabController.animateTo(tabIndex);
+ }
+
+ Widget _buildAppBarTitle(
+ int tabIndex,
+ TextStyle? textStyle,
+ TextStyle? labelLarge,
+ ) {
+ switch (tabIndex) {
+ case 0:
+ return visible
+ ? _buildSearchField(labelLarge)
+ : Obx(() {
+ final location = weatherController.location;
+ final city = location.city;
+ final district = location.district;
+ return Text(
+ weatherController.isLoading.isFalse
+ ? district!.isEmpty
+ ? '$city'
+ : city!.isEmpty
+ ? district
+ : city == district
+ ? city
+ : '$city, $district'
+ : settings.location
+ ? 'search'.tr
+ : (isar.locationCaches.where().findAllSync()).isNotEmpty
+ ? 'loading'.tr
+ : 'searchCity'.tr,
+ style: textStyle,
+ );
+ });
+ case 1:
+ return Text('cities'.tr, style: textStyle);
+ case 2:
+ return settings.hideMap
+ ? Text('settings_full'.tr, style: textStyle)
+ : Text('map'.tr, style: textStyle);
+ case 3:
+ return Text('settings_full'.tr, style: textStyle);
+ default:
+ return const SizedBox.shrink();
+ }
+ }
+
+ Widget _buildSearchField(TextStyle? labelLarge) {
+ return RawAutocomplete(
+ focusNode: _focusNode,
+ textEditingController: _controller,
+ fieldViewBuilder: (_, __, ___, ____) {
+ return TextField(
+ controller: _controller,
+ focusNode: _focusNode,
+ style: labelLarge?.copyWith(fontSize: 16),
+ decoration: InputDecoration(hintText: 'search'.tr),
+ );
+ },
+ optionsBuilder: (TextEditingValue textEditingValue) {
+ if (textEditingValue.text.isEmpty) {
+ return const Iterable.empty();
+ }
+ return WeatherAPI().getCity(textEditingValue.text, locale);
+ },
+ onSelected: (Result selection) async {
+ await weatherController.deleteAll(true);
+ await weatherController.getLocation(
+ double.parse('${selection.latitude}'),
+ double.parse('${selection.longitude}'),
+ selection.admin1,
+ selection.name,
+ );
+ visible = false;
+ _controller.clear();
+ _focusNode.unfocus();
+ setState(() {});
+ },
+ displayStringForOption:
+ (Result option) => '${option.name}, ${option.admin1}',
+ optionsViewBuilder: (
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ ) {
+ return _buildOptionsView(context, onSelected, options, labelLarge);
+ },
+ );
+ }
+
+ Widget _buildOptionsView(
+ BuildContext context,
+ AutocompleteOnSelected onSelected,
+ Iterable options,
+ TextStyle? labelLarge,
+ ) {
+ return Align(
+ alignment: Alignment.topLeft,
+ child: Material(
+ borderRadius: BorderRadius.circular(20),
+ elevation: 4.0,
+ child: SizedBox(
+ width: 250,
+ child: ListView.builder(
+ padding: EdgeInsets.zero,
+ shrinkWrap: true,
+ itemCount: options.length,
+ itemBuilder: (BuildContext context, int index) {
+ final Result option = options.elementAt(index);
+ return InkWell(
+ onTap: () => onSelected(option),
+ child: ListTile(
+ title: Text(
+ '${option.name}, ${option.admin1}',
+ style: labelLarge,
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSearchIconButton() {
+ return IconButton(
+ onPressed: () {
+ if (visible) {
+ _controller.clear();
+ _focusNode.unfocus();
+ visible = false;
+ } else {
+ visible = true;
+ }
+ setState(() {});
+ },
+ icon: Icon(
+ visible
+ ? IconsaxPlusLinear.close_circle
+ : IconsaxPlusLinear.search_normal_1,
+ size: 18,
+ ),
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final textTheme = context.textTheme;
+ final labelLarge = textTheme.labelLarge;
+
+ final textStyle = textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ );
+
+ return DefaultTabController(
+ length: pages.length,
+ child: ScaffoldMessenger(
+ key: globalKey,
+ child: Scaffold(
+ appBar: AppBar(
+ centerTitle: true,
+ automaticallyImplyLeading: false,
+ leading:
+ tabIndex == 0
+ ? IconButton(
+ onPressed: () {
+ Get.to(
+ () => const SelectGeolocation(isStart: false),
+ transition: Transition.downToUp,
+ );
+ },
+ icon: const Icon(
+ IconsaxPlusLinear.global_search,
+ size: 18,
+ ),
+ )
+ : null,
+ title: _buildAppBarTitle(tabIndex, textStyle, labelLarge),
+ actions: tabIndex == 0 ? [_buildSearchIconButton()] : null,
+ ),
+ body: SafeArea(
+ child: TabBarView(controller: tabController, children: pages),
+ ),
+ bottomNavigationBar: NavigationBar(
+ onDestinationSelected: (int index) => changeTabIndex(index),
+ selectedIndex: tabIndex,
+ destinations: [
+ NavigationDestination(
+ icon: const Icon(IconsaxPlusLinear.cloud_sunny),
+ selectedIcon: const Icon(IconsaxPlusBold.cloud_sunny),
+ label: 'name'.tr,
+ ),
+ NavigationDestination(
+ icon: const Icon(IconsaxPlusLinear.buildings),
+ selectedIcon: const Icon(IconsaxPlusBold.buildings),
+ label: 'cities'.tr,
+ ),
+ if (!settings.hideMap)
+ NavigationDestination(
+ icon: const Icon(IconsaxPlusLinear.map),
+ selectedIcon: const Icon(IconsaxPlusBold.map),
+ label: 'map'.tr,
+ ),
+ NavigationDestination(
+ icon: const Icon(IconsaxPlusLinear.category),
+ selectedIcon: const Icon(IconsaxPlusBold.category),
+ label: 'settings_full'.tr,
+ ),
+ ],
+ ),
+ floatingActionButton:
+ tabIndex == 1
+ ? FloatingActionButton(
+ onPressed:
+ () => showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ enableDrag: false,
+ builder:
+ (BuildContext context) => const CreatePlace(),
+ ),
+ child: const Icon(IconsaxPlusLinear.add),
+ )
+ : null,
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/app/ui/main/view/main.dart b/lib/app/ui/main/view/main.dart
new file mode 100755
index 0000000..fe0987e
--- /dev/null
+++ b/lib/app/ui/main/view/main.dart
@@ -0,0 +1,242 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_card_list.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_container.dart';
+import 'package:rain/app/ui/widgets/weather/desc/desc_container.dart';
+import 'package:rain/app/ui/widgets/weather/hourly.dart';
+import 'package:rain/app/ui/widgets/weather/now.dart';
+import 'package:rain/app/ui/widgets/shimmer.dart';
+import 'package:rain/app/ui/widgets/weather/sunset_sunrise.dart';
+import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
+
+class MainPage extends StatefulWidget {
+ const MainPage({super.key});
+
+ @override
+ State createState() => _MainPageState();
+}
+
+class _MainPageState extends State {
+ final weatherController = Get.put(WeatherController());
+
+ @override
+ Widget build(BuildContext context) {
+ return RefreshIndicator(
+ onRefresh: _handleRefresh,
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10),
+ child: Obx(() {
+ if (weatherController.isLoading.isTrue) {
+ return _buildLoadingView();
+ }
+
+ final mainWeather = weatherController.mainWeather;
+ final weatherCard = WeatherCard.fromJson(mainWeather.toJson());
+ final hourOfDay = weatherController.hourOfDay.value;
+ final dayOfNow = weatherController.dayOfNow.value;
+ final sunrise = mainWeather.sunrise![dayOfNow];
+ final sunset = mainWeather.sunset![dayOfNow];
+ final tempMax = mainWeather.temperature2MMax![dayOfNow];
+ final tempMin = mainWeather.temperature2MMin![dayOfNow];
+
+ return _buildMainView(
+ context,
+ mainWeather,
+ weatherCard,
+ hourOfDay,
+ dayOfNow,
+ sunrise,
+ sunset,
+ tempMax!,
+ tempMin!,
+ );
+ }),
+ ),
+ );
+ }
+
+ Future _handleRefresh() async {
+ await weatherController.deleteAll(false);
+ await weatherController.setLocation();
+ setState(() {});
+ }
+
+ Widget _buildLoadingView() {
+ return ListView(
+ children: const [
+ MyShimmer(height: 200),
+ MyShimmer(height: 130, margin: EdgeInsets.symmetric(vertical: 15)),
+ MyShimmer(height: 90, margin: EdgeInsets.only(bottom: 15)),
+ MyShimmer(height: 400, margin: EdgeInsets.only(bottom: 15)),
+ MyShimmer(height: 450, margin: EdgeInsets.only(bottom: 15)),
+ ],
+ );
+ }
+
+ Widget _buildMainView(
+ BuildContext context,
+ MainWeatherCache mainWeather,
+ WeatherCard weatherCard,
+ int hourOfDay,
+ int dayOfNow,
+ String sunrise,
+ String sunset,
+ double tempMax,
+ double tempMin,
+ ) {
+ return ListView(
+ children: [
+ _buildNowWidget(
+ mainWeather,
+ hourOfDay,
+ dayOfNow,
+ sunrise,
+ sunset,
+ tempMax,
+ tempMin,
+ ),
+ _buildHourlyList(context, mainWeather, hourOfDay, dayOfNow),
+ _buildSunsetSunriseWidget(sunrise, sunset),
+ _buildHourlyDescContainer(mainWeather, hourOfDay),
+ _buildDailyContainer(weatherCard),
+ ],
+ );
+ }
+
+ Widget _buildNowWidget(
+ MainWeatherCache mainWeather,
+ int hourOfDay,
+ int dayOfNow,
+ String sunrise,
+ String sunset,
+ double tempMax,
+ double tempMin,
+ ) {
+ return Now(
+ time: mainWeather.time![hourOfDay],
+ weather: mainWeather.weathercode![hourOfDay],
+ degree: mainWeather.temperature2M![hourOfDay],
+ feels: mainWeather.apparentTemperature![hourOfDay]!,
+ timeDay: sunrise,
+ timeNight: sunset,
+ tempMax: tempMax,
+ tempMin: tempMin,
+ );
+ }
+
+ Widget _buildHourlyList(
+ BuildContext context,
+ MainWeatherCache mainWeather,
+ int hourOfDay,
+ int dayOfNow,
+ ) {
+ return Card(
+ margin: const EdgeInsets.only(bottom: 15),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ child: SizedBox(
+ height: 135,
+ child: ScrollablePositionedList.separated(
+ key: const PageStorageKey(0),
+ separatorBuilder: (BuildContext context, int index) {
+ return const VerticalDivider(
+ width: 10,
+ indent: 40,
+ endIndent: 40,
+ );
+ },
+ scrollDirection: Axis.horizontal,
+ itemScrollController: weatherController.itemScrollController,
+ itemCount: mainWeather.time!.length,
+ itemBuilder: (ctx, i) {
+ return _buildHourlyItem(
+ context,
+ mainWeather,
+ i,
+ hourOfDay,
+ dayOfNow,
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildHourlyItem(
+ BuildContext context,
+ MainWeatherCache mainWeather,
+ int i,
+ int hourOfDay,
+ int dayOfNow,
+ ) {
+ final i24 = (i / 24).floor();
+
+ return GestureDetector(
+ onTap: () {
+ weatherController.hourOfDay.value = i;
+ weatherController.dayOfNow.value = i24;
+ setState(() {});
+ },
+ child: Container(
+ margin: const EdgeInsets.symmetric(vertical: 5),
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
+ decoration: BoxDecoration(
+ color: i == hourOfDay
+ ? context.theme.colorScheme.secondaryContainer
+ : Colors.transparent,
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ ),
+ child: Hourly(
+ time: mainWeather.time![i],
+ weather: mainWeather.weathercode![i],
+ degree: mainWeather.temperature2M![i],
+ timeDay: mainWeather.sunrise![i24],
+ timeNight: mainWeather.sunset![i24],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSunsetSunriseWidget(String sunrise, String sunset) {
+ return SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset);
+ }
+
+ Widget _buildHourlyDescContainer(
+ MainWeatherCache mainWeather,
+ int hourOfDay,
+ ) {
+ return DescContainer(
+ humidity: mainWeather.relativehumidity2M?[hourOfDay],
+ wind: mainWeather.windspeed10M?[hourOfDay],
+ visibility: mainWeather.visibility?[hourOfDay],
+ feels: mainWeather.apparentTemperature?[hourOfDay],
+ evaporation: mainWeather.evapotranspiration?[hourOfDay],
+ precipitation: mainWeather.precipitation?[hourOfDay],
+ direction: mainWeather.winddirection10M?[hourOfDay],
+ pressure: mainWeather.surfacePressure?[hourOfDay],
+ rain: mainWeather.rain?[hourOfDay],
+ cloudcover: mainWeather.cloudcover?[hourOfDay],
+ windgusts: mainWeather.windgusts10M?[hourOfDay],
+ uvIndex: mainWeather.uvIndex?[hourOfDay],
+ dewpoint2M: mainWeather.dewpoint2M?[hourOfDay],
+ precipitationProbability:
+ mainWeather.precipitationProbability?[hourOfDay],
+ shortwaveRadiation: mainWeather.shortwaveRadiation?[hourOfDay],
+ initiallyExpanded: false,
+ title: 'hourlyVariables'.tr,
+ );
+ }
+
+ Widget _buildDailyContainer(WeatherCard weatherCard) {
+ return DailyContainer(
+ weatherData: weatherCard,
+ onTap: () => Get.to(
+ () => DailyCardList(weatherData: weatherCard),
+ transition: Transition.downToUp,
+ ),
+ );
+ }
+}
diff --git a/lib/app/ui/map/view/map.dart b/lib/app/ui/map/view/map.dart
new file mode 100755
index 0000000..7f26040
--- /dev/null
+++ b/lib/app/ui/map/view/map.dart
@@ -0,0 +1,477 @@
+import 'dart:io';
+import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_expandable_fab/flutter_expandable_fab.dart';
+import 'package:flutter_map/flutter_map.dart';
+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/main.dart';
+
+class MapPage extends StatefulWidget {
+ const MapPage({super.key});
+
+ @override
+ State createState() => _MapPageState();
+}
+
+class _MapPageState extends State with TickerProviderStateMixin {
+ late final AnimatedMapController _animatedMapController =
+ AnimatedMapController(vsync: this);
+ final weatherController = Get.put(WeatherController());
+ final statusWeather = StatusWeather();
+ 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();
+
+ static Future _getCacheStore() async {
+ final dir = await getTemporaryDirectory();
+ return FileCacheStore('${dir.path}${Platform.pathSeparator}MapTiles');
+ }
+
+ @override
+ void initState() {
+ _animationController = AnimationController(
+ duration: const Duration(milliseconds: 300),
+ vsync: this,
+ );
+
+ _offsetAnimation = Tween(
+ begin: const Offset(0.0, 1.0),
+ end: Offset.zero,
+ ).animate(
+ CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
+ );
+ super.initState();
+ }
+
+ @override
+ void dispose() {
+ _animatedMapController.dispose();
+ _controllerSearch.dispose();
+ _animationController.dispose();
+ super.dispose();
+ }
+
+ void _resetMapOrientation({LatLng? center, double? zoom}) {
+ _animatedMapController.animateTo(
+ customId: _useTransformer ? _useTransformerId : null,
+ dest: center,
+ zoom: zoom,
+ rotation: 0,
+ duration: const Duration(milliseconds: 500),
+ curve: Curves.easeInOut,
+ );
+ }
+
+ void _onMarkerTap(WeatherCard weatherCard) {
+ setState(() {
+ _selectedWeatherCard = weatherCard;
+ });
+ _animationController.forward();
+ _isCardVisible = true;
+
+ if (_fabKey.currentState?.isOpen == true) {
+ _fabKey.currentState?.toggle();
+ }
+ }
+
+ void _hideCard() {
+ _animationController.reverse().then((_) {
+ setState(() {
+ _isCardVisible = false;
+ _selectedWeatherCard = null;
+ });
+ });
+ _focusNode.unfocus();
+ }
+
+ Widget _buildStyleMarkers(
+ int weathercode,
+ String time,
+ String sunrise,
+ String sunset,
+ double temperature2M,
+ ) {
+ return Card(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Image.asset(
+ statusWeather.getImageNow(weathercode, time, sunrise, sunset),
+ scale: 18,
+ ),
+ const MaxGap(5),
+ Text(
+ statusData.getDegree(
+ roundDegree ? temperature2M.round() : temperature2M,
+ ),
+ style: context.textTheme.labelLarge?.copyWith(
+ fontWeight: FontWeight.bold,
+ fontSize: 16,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Marker _buildMainLocationMarker(
+ 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(
+ weatherCard.weathercode![hourOfDay],
+ weatherCard.time![hourOfDay],
+ weatherCard.sunrise![dayOfNow],
+ weatherCard.sunset![dayOfNow],
+ weatherCard.temperature2M![hourOfDay],
+ ),
+ ),
+ );
+ }
+
+ 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],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildMapTileLayer(CacheStore cacheStore) {
+ return TileLayer(
+ urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
+ userAgentPackageName: 'com.darkmoonight.rain',
+ tileProvider: CachedTileProvider(
+ store: cacheStore,
+ maxStale: const Duration(days: 30),
+ ),
+ );
+ }
+
+ 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,
+ ),
+ ),
+ );
+ },
+ ),
+ ),
+ ),
+ );
+ },
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final mainLocation = weatherController.location;
+ final mainWeather = weatherController.mainWeather;
+
+ final hourOfDay = weatherController.hourOfDay.value;
+ final dayOfNow = weatherController.dayOfNow.value;
+
+ return Scaffold(
+ body: FutureBuilder(
+ future: _cacheStoreFuture,
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.waiting) {
+ return const Center(child: CircularProgressIndicator());
+ }
+
+ if (snapshot.hasError) {
+ return Center(child: Text(snapshot.error.toString()));
+ }
+
+ final cacheStore = snapshot.data!;
+
+ return Stack(
+ children: [
+ FlutterMap(
+ mapController: _animatedMapController.mapController,
+ options: MapOptions(
+ backgroundColor: context.theme.colorScheme.surface,
+ initialCenter: LatLng(mainLocation.lat!, mainLocation.lon!),
+ initialZoom: 8,
+ interactionOptions: const InteractionOptions(
+ flags: InteractiveFlag.all & ~InteractiveFlag.rotate,
+ ),
+ cameraConstraint: CameraConstraint.contain(
+ bounds: LatLngBounds(
+ const LatLng(-90, -180),
+ const LatLng(90, 180),
+ ),
+ ),
+ onTap: (_, __) => _hideCard(),
+ onLongPress:
+ (tapPosition, point) => showModalBottomSheet(
+ context: context,
+ isScrollControlled: true,
+ enableDrag: false,
+ builder:
+ (BuildContext context) => CreatePlace(
+ latitude: '${point.latitude}',
+ longitude: '${point.longitude}',
+ ),
+ ),
+ ),
+ children: [
+ if (_isDarkMode)
+ ColorFiltered(
+ colorFilter: const 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
+ ]),
+ child: _buildMapTileLayer(cacheStore),
+ )
+ else
+ _buildMapTileLayer(cacheStore),
+ RichAttributionWidget(
+ animationConfig: const ScaleRAWA(),
+ alignment: AttributionAlignment.bottomLeft,
+ attributions: [
+ TextSourceAttribution(
+ 'OpenStreetMap contributors',
+ onTap:
+ () => weatherController.urlLauncher(
+ 'https://openstreetmap.org/copyright',
+ ),
+ ),
+ ],
+ ),
+ Obx(() {
+ final mainMarker = _buildMainLocationMarker(
+ WeatherCard.fromJson({
+ ...mainWeather.toJson(),
+ ...mainLocation.toJson(),
+ }),
+ hourOfDay,
+ dayOfNow,
+ );
+
+ final cardMarkers =
+ weatherController.weatherCards
+ .map(
+ (weatherCardList) =>
+ _buildCardMarker(weatherCardList),
+ )
+ .toList();
+
+ return MarkerLayer(markers: [mainMarker, ...cardMarkers]);
+ }),
+ ExpandableFab(
+ key: _fabKey,
+ pos: ExpandableFabPos.right,
+ type: ExpandableFabType.up,
+ distance: 70,
+ openButtonBuilder: RotateFloatingActionButtonBuilder(
+ child: const Icon(IconsaxPlusLinear.menu),
+ fabSize: ExpandableFabSize.regular,
+ ),
+ closeButtonBuilder: DefaultFloatingActionButtonBuilder(
+ child: const Icon(Icons.close),
+ fabSize: ExpandableFabSize.regular,
+ ),
+ children: [
+ FloatingActionButton(
+ heroTag: null,
+ child: const Icon(IconsaxPlusLinear.home_2),
+ onPressed:
+ () => _resetMapOrientation(
+ center: LatLng(
+ mainLocation.lat!,
+ mainLocation.lon!,
+ ),
+ zoom: 8,
+ ),
+ ),
+ 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,
+ ),
+ ),
+ ],
+ ),
+ Positioned(
+ left: 0,
+ right: 0,
+ bottom: 0,
+ child: _buildWeatherCard(),
+ ),
+ ],
+ ),
+ _buildSearchField(),
+ ],
+ );
+ },
+ ),
+ floatingActionButtonLocation: ExpandableFab.location,
+ );
+ }
+}
diff --git a/lib/app/ui/onboarding.dart b/lib/app/ui/onboarding.dart
new file mode 100755
index 0000000..29561c3
--- /dev/null
+++ b/lib/app/ui/onboarding.dart
@@ -0,0 +1,198 @@
+import 'package:flutter/material.dart';
+import 'package:gap/gap.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/geolocation.dart';
+import 'package:rain/app/ui/widgets/button.dart';
+import 'package:rain/main.dart';
+import 'package:get/get.dart';
+
+class OnBording extends StatefulWidget {
+ const OnBording({super.key});
+
+ @override
+ State createState() => _OnBordingState();
+}
+
+class _OnBordingState extends State {
+ late PageController pageController;
+ int pageIndex = 0;
+
+ @override
+ void initState() {
+ super.initState();
+ pageController = PageController(initialPage: 0);
+ }
+
+ @override
+ void dispose() {
+ pageController.dispose();
+ super.dispose();
+ }
+
+ void onBoardHome() {
+ settings.onboard = true;
+ isar.writeTxnSync(() => isar.settings.putSync(settings));
+ Get.off(
+ () => const SelectGeolocation(isStart: true),
+ transition: Transition.downToUp,
+ );
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(),
+ body: SafeArea(
+ child: Column(
+ children: [
+ _buildPageView(),
+ _buildDotIndicators(),
+ _buildActionButton(),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildPageView() {
+ return Expanded(
+ child: PageView.builder(
+ controller: pageController,
+ itemCount: data.length,
+ onPageChanged: (index) {
+ setState(() {
+ pageIndex = index;
+ });
+ },
+ itemBuilder: (context, index) => OnboardContent(
+ image: data[index].image,
+ title: data[index].title,
+ description: data[index].description,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildDotIndicators() {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: List.generate(
+ data.length,
+ (index) => Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 5),
+ child: DotIndicator(isActive: index == pageIndex),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildActionButton() {
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
+ child: MyTextButton(
+ buttonName: pageIndex == data.length - 1 ? 'start'.tr : 'next'.tr,
+ onPressed: () {
+ if (pageIndex == data.length - 1) {
+ onBoardHome();
+ } else {
+ pageController.nextPage(
+ duration: const Duration(milliseconds: 300),
+ curve: Curves.ease,
+ );
+ }
+ },
+ ),
+ );
+ }
+}
+
+class DotIndicator extends StatelessWidget {
+ const DotIndicator({super.key, this.isActive = false});
+
+ final bool isActive;
+
+ @override
+ Widget build(BuildContext context) {
+ return AnimatedContainer(
+ duration: const Duration(milliseconds: 300),
+ height: 8,
+ width: 8,
+ decoration: BoxDecoration(
+ color: isActive
+ ? context.theme.colorScheme.secondary
+ : context.theme.colorScheme.secondaryContainer,
+ shape: BoxShape.circle,
+ ),
+ );
+ }
+}
+
+class Onboard {
+ final String image, title, description;
+
+ Onboard({
+ required this.image,
+ required this.title,
+ required this.description,
+ });
+}
+
+final List data = [
+ Onboard(
+ image: 'assets/icons/Rain.png',
+ title: 'name'.tr,
+ description: 'description'.tr,
+ ),
+ Onboard(
+ image: 'assets/icons/Design.png',
+ title: 'name2'.tr,
+ description: 'description2'.tr,
+ ),
+ Onboard(
+ image: 'assets/icons/Team.png',
+ title: 'name3'.tr,
+ description: 'description3'.tr,
+ ),
+];
+
+class OnboardContent extends StatelessWidget {
+ const OnboardContent({
+ super.key,
+ required this.image,
+ required this.title,
+ required this.description,
+ });
+
+ final String image, title, description;
+
+ @override
+ Widget build(BuildContext context) {
+ return Column(
+ children: [
+ Flexible(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Image.asset(image, scale: 5),
+ Text(
+ title,
+ style: context.textTheme.titleLarge?.copyWith(
+ fontWeight: FontWeight.w600,
+ ),
+ ),
+ const Gap(10),
+ SizedBox(
+ width: 300,
+ child: Text(
+ description,
+ style: context.textTheme.labelLarge?.copyWith(fontSize: 14),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ],
+ );
+ }
+}
diff --git a/lib/app/ui/places/view/place_info.dart b/lib/app/ui/places/view/place_info.dart
new file mode 100755
index 0000000..f56f709
--- /dev/null
+++ b/lib/app/ui/places/view/place_info.dart
@@ -0,0 +1,212 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/data/db.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_card_list.dart';
+import 'package:rain/app/ui/widgets/weather/daily/daily_container.dart';
+import 'package:rain/app/ui/widgets/weather/desc/desc_container.dart';
+import 'package:rain/app/ui/widgets/weather/hourly.dart';
+import 'package:rain/app/ui/widgets/weather/now.dart';
+import 'package:rain/app/ui/widgets/weather/sunset_sunrise.dart';
+import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
+
+class PlaceInfo extends StatefulWidget {
+ const PlaceInfo({super.key, required this.weatherCard});
+ final WeatherCard weatherCard;
+
+ @override
+ State createState() => _PlaceInfoState();
+}
+
+class _PlaceInfoState extends State {
+ int timeNow = 0;
+ int dayNow = 0;
+ final weatherController = Get.put(WeatherController());
+ final itemScrollController = ItemScrollController();
+
+ @override
+ void initState() {
+ getTime();
+ super.initState();
+ }
+
+ void getTime() {
+ final weatherCard = widget.weatherCard;
+
+ timeNow = weatherController.getTime(
+ weatherCard.time!,
+ weatherCard.timezone!,
+ );
+ dayNow = weatherController.getDay(
+ weatherCard.timeDaily!,
+ weatherCard.timezone!,
+ );
+ Future.delayed(const Duration(milliseconds: 30), () {
+ itemScrollController.scrollTo(
+ index: timeNow,
+ duration: const Duration(seconds: 2),
+ curve: Curves.easeInOutCubic,
+ );
+ });
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final weatherCard = widget.weatherCard;
+
+ return RefreshIndicator(
+ onRefresh: _handleRefresh,
+ child: Scaffold(
+ appBar: _buildAppBar(context, weatherCard),
+ body: SafeArea(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10),
+ child: ListView(
+ children: [
+ _buildNowWidget(weatherCard),
+ _buildHourlyList(weatherCard),
+ _buildSunsetSunriseWidget(weatherCard),
+ _buildHourlyDescContainer(weatherCard),
+ _buildDailyContainer(weatherCard),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Future _handleRefresh() async {
+ await weatherController.updateCard(widget.weatherCard);
+ getTime();
+ setState(() {});
+ }
+
+ AppBar _buildAppBar(BuildContext context, WeatherCard weatherCard) {
+ return AppBar(
+ centerTitle: true,
+ automaticallyImplyLeading: false,
+ leading: IconButton(
+ onPressed: () => Get.back(),
+ icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
+ ),
+ title: Text(
+ weatherCard.district!.isNotEmpty
+ ? '${weatherCard.city}, ${weatherCard.district}'
+ : '${weatherCard.city}',
+ style: context.textTheme.titleMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ ),
+ ),
+ );
+ }
+
+ Widget _buildNowWidget(WeatherCard weatherCard) {
+ return Now(
+ time: weatherCard.time![timeNow],
+ weather: weatherCard.weathercode![timeNow],
+ degree: weatherCard.temperature2M![timeNow],
+ feels: weatherCard.apparentTemperature![timeNow]!,
+ timeDay: weatherCard.sunrise![dayNow],
+ timeNight: weatherCard.sunset![dayNow],
+ tempMax: weatherCard.temperature2MMax![dayNow]!,
+ tempMin: weatherCard.temperature2MMin![dayNow]!,
+ );
+ }
+
+ Widget _buildHourlyList(WeatherCard weatherCard) {
+ return Card(
+ margin: const EdgeInsets.only(bottom: 15),
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ child: SizedBox(
+ height: 135,
+ child: ScrollablePositionedList.separated(
+ key: const PageStorageKey(1),
+ separatorBuilder: (BuildContext context, int index) {
+ return const VerticalDivider(
+ width: 10,
+ indent: 40,
+ endIndent: 40,
+ );
+ },
+ scrollDirection: Axis.horizontal,
+ itemScrollController: itemScrollController,
+ itemCount: weatherCard.time!.length,
+ itemBuilder: (ctx, i) => _buildHourlyItem(weatherCard, i),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildHourlyItem(WeatherCard weatherCard, int i) {
+ return GestureDetector(
+ onTap: () {
+ timeNow = i;
+ dayNow = (i / 24).floor();
+ setState(() {});
+ },
+ child: Container(
+ margin: const EdgeInsets.symmetric(vertical: 5),
+ padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 5),
+ decoration: BoxDecoration(
+ color:
+ i == timeNow
+ ? context.theme.colorScheme.secondaryContainer
+ : Colors.transparent,
+ borderRadius: const BorderRadius.all(Radius.circular(20)),
+ ),
+ child: Hourly(
+ time: weatherCard.time![i],
+ weather: weatherCard.weathercode![i],
+ degree: weatherCard.temperature2M![i],
+ timeDay: weatherCard.sunrise![(i / 24).floor()],
+ timeNight: weatherCard.sunset![(i / 24).floor()],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSunsetSunriseWidget(WeatherCard weatherCard) {
+ return SunsetSunrise(
+ timeSunrise: weatherCard.sunrise![dayNow],
+ timeSunset: weatherCard.sunset![dayNow],
+ );
+ }
+
+ Widget _buildHourlyDescContainer(WeatherCard weatherCard) {
+ return DescContainer(
+ humidity: weatherCard.relativehumidity2M?[timeNow],
+ wind: weatherCard.windspeed10M?[timeNow],
+ visibility: weatherCard.visibility?[timeNow],
+ feels: weatherCard.apparentTemperature?[timeNow],
+ evaporation: weatherCard.evapotranspiration?[timeNow],
+ precipitation: weatherCard.precipitation?[timeNow],
+ direction: weatherCard.winddirection10M?[timeNow],
+ pressure: weatherCard.surfacePressure?[timeNow],
+ rain: weatherCard.rain?[timeNow],
+ cloudcover: weatherCard.cloudcover?[timeNow],
+ windgusts: weatherCard.windgusts10M?[timeNow],
+ uvIndex: weatherCard.uvIndex?[timeNow],
+ dewpoint2M: weatherCard.dewpoint2M?[timeNow],
+ precipitationProbability: weatherCard.precipitationProbability?[timeNow],
+ shortwaveRadiation: weatherCard.shortwaveRadiation?[timeNow],
+ initiallyExpanded: false,
+ title: 'hourlyVariables'.tr,
+ );
+ }
+
+ Widget _buildDailyContainer(WeatherCard weatherCard) {
+ return DailyContainer(
+ weatherData: weatherCard,
+ onTap:
+ () => Get.to(
+ () => DailyCardList(weatherData: weatherCard),
+ transition: Transition.downToUp,
+ ),
+ );
+ }
+}
diff --git a/lib/app/ui/places/view/place_list.dart b/lib/app/ui/places/view/place_list.dart
new file mode 100755
index 0000000..7fafd5a
--- /dev/null
+++ b/lib/app/ui/places/view/place_list.dart
@@ -0,0 +1,116 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/ui/places/widgets/place_card_list.dart';
+import 'package:rain/app/ui/widgets/text_form.dart';
+
+class PlaceList extends StatefulWidget {
+ const PlaceList({super.key});
+
+ @override
+ State createState() => _PlaceListState();
+}
+
+class _PlaceListState extends State {
+ final weatherController = Get.put(WeatherController());
+ final TextEditingController searchTasks = TextEditingController();
+ String filter = '';
+
+ @override
+ void initState() {
+ super.initState();
+ applyFilter('');
+ }
+
+ void applyFilter(String value) {
+ filter = value.toLowerCase();
+ setState(() {});
+ }
+
+ void clearSearch() {
+ searchTasks.clear();
+ applyFilter('');
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final textTheme = context.textTheme;
+ final titleMedium = textTheme.titleMedium;
+
+ return Obx(() => _buildContent(context, titleMedium));
+ }
+
+ Widget _buildContent(BuildContext context, TextStyle? titleMedium) {
+ if (weatherController.weatherCards.isEmpty) {
+ return _buildEmptyState(context, titleMedium);
+ } else {
+ return _buildListView(context);
+ }
+ }
+
+ Widget _buildEmptyState(BuildContext context, TextStyle? titleMedium) {
+ return Center(
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ Image.asset('assets/icons/City.png', scale: 6),
+ SizedBox(
+ width: Get.size.width * 0.8,
+ child: Text(
+ 'noWeatherCard'.tr,
+ textAlign: TextAlign.center,
+ style: titleMedium?.copyWith(
+ fontWeight: FontWeight.w600,
+ fontSize: 18,
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildListView(BuildContext context) {
+ return NestedScrollView(
+ physics: const NeverScrollableScrollPhysics(),
+ headerSliverBuilder: (context, innerBoxIsScrolled) {
+ return [_buildSearchField(context)];
+ },
+ body: RefreshIndicator(
+ onRefresh: _handleRefresh,
+ child: PlaceCardList(searchCity: filter),
+ ),
+ );
+ }
+
+ Widget _buildSearchField(BuildContext context) {
+ return SliverToBoxAdapter(
+ child: MyTextForm(
+ labelText: 'search'.tr,
+ type: TextInputType.text,
+ icon: const Icon(IconsaxPlusLinear.search_normal_1, size: 20),
+ controller: searchTasks,
+ margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
+ onChanged: applyFilter,
+ iconButton:
+ searchTasks.text.isNotEmpty
+ ? IconButton(
+ onPressed: clearSearch,
+ icon: const Icon(
+ IconsaxPlusLinear.close_circle,
+ color: Colors.grey,
+ size: 20,
+ ),
+ )
+ : null,
+ ),
+ );
+ }
+
+ Future _handleRefresh() async {
+ await weatherController.updateCacheCard(true);
+ setState(() {});
+ }
+}
diff --git a/lib/app/ui/places/widgets/create_place.dart b/lib/app/ui/places/widgets/create_place.dart
new file mode 100755
index 0000000..0ef872a
--- /dev/null
+++ b/lib/app/ui/places/widgets/create_place.dart
@@ -0,0 +1,346 @@
+import 'package:flutter/material.dart';
+import 'package:get/get.dart';
+import 'package:iconsax_plus/iconsax_plus.dart';
+import 'package:rain/app/api/api.dart';
+import 'package:rain/app/api/city_api.dart';
+import 'package:rain/app/controller/controller.dart';
+import 'package:rain/app/ui/widgets/button.dart';
+import 'package:rain/app/ui/widgets/text_form.dart';
+import 'package:rain/main.dart';
+
+class CreatePlace extends StatefulWidget {
+ const CreatePlace({super.key, this.latitude, this.longitude});
+ final String? latitude;
+ final String? longitude;
+
+ @override
+ State createState() => _CreatePlaceState();
+}
+
+class _CreatePlaceState extends State
+ with SingleTickerProviderStateMixin {
+ bool isLoading = false;
+ final formKey = GlobalKey();
+ final _focusNode = FocusNode();
+ final weatherController = Get.put(WeatherController());
+
+ static const kTextFieldElevation = 4.0;
+
+ late TextEditingController _controller;
+ late TextEditingController _controllerLat;
+ late TextEditingController _controllerLon;
+ late TextEditingController _controllerCity;
+ late TextEditingController _controllerDistrict;
+
+ late AnimationController _animationController;
+ late Animation _animation;
+
+ @override
+ void initState() {
+ super.initState();
+ _controller = TextEditingController();
+ _controllerLat = TextEditingController(text: widget.latitude);
+ _controllerLon = TextEditingController(text: widget.longitude);
+ _controllerCity = TextEditingController();
+ _controllerDistrict = TextEditingController();
+
+ _animationController = AnimationController(
+ duration: const Duration(milliseconds: 300),
+ vsync: this,
+ );
+ _animation = CurvedAnimation(
+ parent: _animationController,
+ curve: Curves.easeInOut,
+ );
+ }
+
+ @override
+ void dispose() {
+ _animationController.dispose();
+ _controller.dispose();
+ _controllerLat.dispose();
+ _controllerLon.dispose();
+ _controllerCity.dispose();
+ _controllerDistrict.dispose();
+ super.dispose();
+ }
+
+ void textTrim(TextEditingController value) {
+ value.text = value.text.trim();
+ while (value.text.contains(' ')) {
+ value.text = value.text.replaceAll(' ', ' ');
+ }
+ }
+
+ void fillController(Result selection) {
+ _controllerLat.text = '${selection.latitude}';
+ _controllerLon.text = '${selection.longitude}';
+ _controllerCity.text = selection.name;
+ _controllerDistrict.text = selection.admin1;
+ _controller.clear();
+ _focusNode.unfocus();
+ setState(() {});
+ }
+
+ bool get showButton {
+ return _controllerLon.text.isNotEmpty &&
+ _controllerLat.text.isNotEmpty &&
+ _controllerCity.text.isNotEmpty &&
+ _controllerDistrict.text.isNotEmpty;
+ }
+
+ void updateButtonVisibility() {
+ if (showButton) {
+ _animationController.forward();
+ } else {
+ _animationController.reverse();
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ updateButtonVisibility();
+
+ return Padding(
+ padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
+ child: Form(
+ key: formKey,
+ child: SingleChildScrollView(
+ child: Stack(
+ children: [
+ Padding(
+ padding: EdgeInsets.only(
+ bottom: MediaQuery.of(context).viewInsets.bottom,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ _buildTitleText(),
+ _buildSearchField(),
+ _buildLatitudeField(),
+ _buildLongitudeField(),
+ _buildCityField(),
+ _buildDistrictField(),
+ _buildSubmitButton(),
+ ],
+ ),
+ ),
+ if (isLoading) const Center(child: CircularProgressIndicator()),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildTitleText() {
+ return Padding(
+ padding: const EdgeInsets.only(top: 14, bottom: 7),
+ child: Text(
+ 'create'.tr,
+ style: context.textTheme.titleLarge?.copyWith(
+ fontWeight: FontWeight.bold,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ );
+ }
+
+ Widget _buildSearchField() {
+ return RawAutocomplete(
+ focusNode: _focusNode,
+ textEditingController: _controller,
+ fieldViewBuilder: (
+ BuildContext context,
+ TextEditingController fieldTextEditingController,
+ FocusNode fieldFocusNode,
+ VoidCallback onFieldSubmitted,
+ ) {
+ return MyTextForm(
+ elevation: kTextFieldElevation,
+ labelText: 'search'.tr,
+ type: TextInputType.text,
+ icon: const Icon(IconsaxPlusLinear.global_search),
+ controller: _controller,
+ margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
+ focusNode: _focusNode,
+ );
+ },
+ optionsBuilder: (TextEditingValue textEditingValue) {
+ if (textEditingValue.text.isEmpty) {
+ return const Iterable.empty();
+ }
+ return WeatherAPI().getCity(textEditingValue.text, locale);
+ },
+ onSelected: (Result selection) => fillController(selection),
+ displayStringForOption:
+ (Result option) => '${option.name}, ${option.admin1}',
+ optionsViewBuilder: (
+ BuildContext context,
+ AutocompleteOnSelected