This commit is contained in:
Yoshi 2025-03-15 23:40:48 +03:00
parent d169f6237f
commit fb150421a6
62 changed files with 5006 additions and 4901 deletions

View file

@ -7,8 +7,8 @@ import 'package:rain/app/data/db.dart';
import 'package:rain/main.dart'; import 'package:rain/main.dart';
class WeatherAPI { class WeatherAPI {
final Dio dio = Dio() final Dio dio =
..options.baseUrl = 'https://api.open-meteo.com/v1/forecast?'; Dio()..options.baseUrl = 'https://api.open-meteo.com/v1/forecast?';
final Dio dioLocation = Dio(); final Dio dioLocation = Dio();
static const String _weatherParams = static const String _weatherParams =
@ -41,14 +41,25 @@ class WeatherAPI {
} }
} }
Future<WeatherCard> getWeatherCard(double lat, double lon, String city, Future<WeatherCard> getWeatherCard(
String district, String timezone) async { double lat,
double lon,
String city,
String district,
String timezone,
) async {
final String urlWeather = _buildWeatherUrl(lat, lon); final String urlWeather = _buildWeatherUrl(lat, lon);
try { try {
Response response = await dio.get(urlWeather); Response response = await dio.get(urlWeather);
WeatherDataApi weatherData = WeatherDataApi.fromJson(response.data); WeatherDataApi weatherData = WeatherDataApi.fromJson(response.data);
return _mapWeatherDataToCard( return _mapWeatherDataToCard(
weatherData, lat, lon, city, district, timezone); weatherData,
lat,
lon,
city,
district,
timezone,
);
} on DioException catch (e) { } on DioException catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
@ -124,8 +135,14 @@ class WeatherAPI {
); );
} }
WeatherCard _mapWeatherDataToCard(WeatherDataApi weatherData, double lat, WeatherCard _mapWeatherDataToCard(
double lon, String city, String district, String timezone) { WeatherDataApi weatherData,
double lat,
double lon,
String city,
String district,
String timezone,
) {
return WeatherCard( return WeatherCard(
time: weatherData.hourly.time, time: weatherData.hourly.time,
temperature2M: weatherData.hourly.temperature2M, temperature2M: weatherData.hourly.temperature2M,

View file

@ -1,12 +1,11 @@
class CityApi { class CityApi {
CityApi({ CityApi({required this.results});
required this.results,
});
List<Result> results; List<Result> results;
factory CityApi.fromJson(Map<String, dynamic> json) => CityApi( factory CityApi.fromJson(Map<String, dynamic> json) => CityApi(
results: json['results'] == null results:
json['results'] == null
? List<Result>.empty() ? List<Result>.empty()
: List<Result>.from(json['results'].map((x) => Result.fromJson(x))), : List<Result>.from(json['results'].map((x) => Result.fromJson(x))),
); );

View file

@ -1,7 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_timezone/flutter_timezone.dart'; import 'package:flutter_timezone/flutter_timezone.dart';
import 'package:geocoding/geocoding.dart'; import 'package:geocoding/geocoding.dart';
import 'package:geolocator/geolocator.dart'; import 'package:geolocator/geolocator.dart';
@ -53,15 +52,14 @@ class WeatherController extends GetxController {
@override @override
void onInit() { void onInit() {
weatherCards weatherCards.assignAll(
.assignAll(isar.weatherCards.where().sortByIndex().findAllSync()); isar.weatherCards.where().sortByIndex().findAllSync(),
);
super.onInit(); super.onInit();
} }
Future<Position> determinePosition() async { Future<Position> _determinePosition() async {
LocationPermission permission; LocationPermission permission = await Geolocator.checkPermission();
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) { if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission(); permission = await Geolocator.requestPermission();
if (permission == LocationPermission.denied) { if (permission == LocationPermission.denied) {
@ -71,7 +69,8 @@ class WeatherController extends GetxController {
if (permission == LocationPermission.deniedForever) { if (permission == LocationPermission.deniedForever) {
return Future.error( return Future.error(
'Location permissions are permanently denied, we cannot request permissions.'); 'Location permissions are permanently denied, we cannot request permissions.',
);
} }
return await Geolocator.getCurrentPosition(); return await Geolocator.getCurrentPosition();
} }
@ -80,25 +79,26 @@ class WeatherController extends GetxController {
if (settings.location) { if (settings.location) {
await getCurrentLocation(); await getCurrentLocation();
} else { } else {
if ((isar.locationCaches.where().findAllSync()).isNotEmpty) { final locationCity = isar.locationCaches.where().findFirstSync();
LocationCache locationCity = if (locationCity != null) {
(isar.locationCaches.where().findFirstSync())!; await getLocation(
await getLocation(locationCity.lat!, locationCity.lon!, locationCity.lat!,
locationCity.district!, locationCity.city!); locationCity.lon!,
locationCity.district!,
locationCity.city!,
);
} }
} }
} }
Future<void> getCurrentLocation() async { Future<void> getCurrentLocation() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!(await isOnline.value)) { if (!(await isOnline.value)) {
showSnackBar(content: 'no_inter'.tr); showSnackBar(content: 'no_inter'.tr);
await readCache(); await readCache();
return; return;
} }
if (!serviceEnabled) { if (!await Geolocator.isLocationServiceEnabled()) {
showSnackBar( showSnackBar(
content: 'no_location'.tr, content: 'no_location'.tr,
onPressed: () => Geolocator.openLocationSettings(), onPressed: () => Geolocator.openLocationSettings(),
@ -107,70 +107,73 @@ class WeatherController extends GetxController {
return; return;
} }
if ((isar.mainWeatherCaches.where().findAllSync()).isNotEmpty) { if (isar.mainWeatherCaches.where().findAllSync().isNotEmpty) {
await readCache(); await readCache();
return; return;
} }
Position position = await determinePosition(); final position = await _determinePosition();
List<Placemark> placemarks = final placemarks = await placemarkFromCoordinates(
await placemarkFromCoordinates(position.latitude, position.longitude); position.latitude,
Placemark place = placemarks[0]; position.longitude,
);
final place = placemarks[0];
_latitude.value = position.latitude; _latitude.value = position.latitude;
_longitude.value = position.longitude; _longitude.value = position.longitude;
_district.value = '${place.administrativeArea}'; _district.value = place.administrativeArea ?? '';
_city.value = '${place.locality}'; _city.value = place.locality ?? '';
_mainWeather.value = _mainWeather.value = await WeatherAPI().getWeatherData(
await WeatherAPI().getWeatherData(_latitude.value, _longitude.value); _latitude.value,
_longitude.value,
);
notificationCheck(); notificationCheck();
await writeCache(); await writeCache();
await readCache(); await readCache();
} }
Future<Map> getCurrentLocationSearch() async { Future<Map> getCurrentLocationSearch() async {
bool serviceEnabled = await Geolocator.isLocationServiceEnabled();
double lat, lon;
String city, district;
if (!(await isOnline.value)) { if (!(await isOnline.value)) {
showSnackBar(content: 'no_inter'.tr); showSnackBar(content: 'no_inter'.tr);
} }
if (!serviceEnabled) { if (!await Geolocator.isLocationServiceEnabled()) {
showSnackBar( showSnackBar(
content: 'no_location'.tr, content: 'no_location'.tr,
onPressed: () => Geolocator.openLocationSettings(), onPressed: () => Geolocator.openLocationSettings(),
); );
} }
Position position = await determinePosition(); final position = await _determinePosition();
List<Placemark> placemarks = final placemarks = await placemarkFromCoordinates(
await placemarkFromCoordinates(position.latitude, position.longitude); position.latitude,
Placemark place = placemarks[0]; position.longitude,
);
final place = placemarks[0];
lat = position.latitude; return {
lon = position.longitude; 'lat': position.latitude,
city = '${place.administrativeArea}'; 'lon': position.longitude,
district = '${place.locality}'; 'city': place.administrativeArea ?? '',
'district': place.locality ?? '',
Map location = {'lat': lat, 'lon': lon, 'city': city, 'district': district}; };
return location;
} }
Future<void> getLocation(double latitude, double longitude, String district, Future<void> getLocation(
String locality) async { double latitude,
double longitude,
String district,
String locality,
) async {
if (!(await isOnline.value)) { if (!(await isOnline.value)) {
showSnackBar(content: 'no_inter'.tr); showSnackBar(content: 'no_inter'.tr);
await readCache(); await readCache();
return; return;
} }
if ((isar.mainWeatherCaches.where().findAllSync()).isNotEmpty) { if (isar.mainWeatherCaches.where().findAllSync().isNotEmpty) {
await readCache(); await readCache();
return; return;
} }
@ -180,11 +183,12 @@ class WeatherController extends GetxController {
_district.value = district; _district.value = district;
_city.value = locality; _city.value = locality;
_mainWeather.value = _mainWeather.value = await WeatherAPI().getWeatherData(
await WeatherAPI().getWeatherData(_latitude.value, _longitude.value); _latitude.value,
_longitude.value,
);
notificationCheck(); notificationCheck();
await writeCache(); await writeCache();
await readCache(); await readCache();
} }
@ -201,10 +205,14 @@ class WeatherController extends GetxController {
_mainWeather.value = mainWeatherCache; _mainWeather.value = mainWeatherCache;
_location.value = locationCache; _location.value = locationCache;
hourOfDay.value = hourOfDay.value = getTime(
getTime(_mainWeather.value.time!, _mainWeather.value.timezone!); _mainWeather.value.time!,
dayOfNow.value = _mainWeather.value.timezone!,
getDay(_mainWeather.value.timeDaily!, _mainWeather.value.timezone!); );
dayOfNow.value = getDay(
_mainWeather.value.timeDaily!,
_mainWeather.value.timezone!,
);
if (Platform.isAndroid) { if (Platform.isAndroid) {
Workmanager().registerPeriodicTask( Workmanager().registerPeriodicTask(
@ -235,16 +243,10 @@ class WeatherController extends GetxController {
); );
isar.writeTxnSync(() { isar.writeTxnSync(() {
final mainWeatherCachesIsEmpty = if (isar.mainWeatherCaches.where().findAllSync().isEmpty) {
(isar.mainWeatherCaches.where().findAllSync()).isEmpty;
final locationCachesIsEmpty =
(isar.locationCaches.where().findAllSync()).isEmpty;
if (mainWeatherCachesIsEmpty) {
isar.mainWeatherCaches.putSync(_mainWeather.value); isar.mainWeatherCaches.putSync(_mainWeather.value);
} }
if (isar.locationCaches.where().findAllSync().isEmpty) {
if (locationCachesIsEmpty) {
isar.locationCaches.putSync(locationCaches); isar.locationCaches.putSync(locationCaches);
} }
}); });
@ -261,7 +263,7 @@ class WeatherController extends GetxController {
.timestampLessThan(cacheExpiry) .timestampLessThan(cacheExpiry)
.deleteAllSync(); .deleteAllSync();
}); });
if ((isar.mainWeatherCaches.where().findAllSync()).isEmpty) { if (isar.mainWeatherCaches.where().findAllSync().isEmpty) {
await flutterLocalNotificationsPlugin.cancelAll(); await flutterLocalNotificationsPlugin.cancelAll();
} }
} }
@ -271,31 +273,39 @@ class WeatherController extends GetxController {
return; return;
} }
bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); final serviceEnabled = await Geolocator.isLocationServiceEnabled();
await flutterLocalNotificationsPlugin.cancelAll(); await flutterLocalNotificationsPlugin.cancelAll();
isar.writeTxnSync(() { isar.writeTxnSync(() {
if (!settings.location) { if (!settings.location) {
isar.mainWeatherCaches.where().deleteAllSync(); isar.mainWeatherCaches.where().deleteAllSync();
} }
if ((settings.location && serviceEnabled) || changeCity) { if (settings.location && serviceEnabled || changeCity) {
isar.mainWeatherCaches.where().deleteAllSync(); isar.mainWeatherCaches.where().deleteAllSync();
isar.locationCaches.where().deleteAllSync(); isar.locationCaches.where().deleteAllSync();
} }
}); });
} }
// Card Weather
Future<void> addCardWeather( Future<void> addCardWeather(
double latitude, double longitude, String city, String district) async { double latitude,
double longitude,
String city,
String district,
) async {
if (!(await isOnline.value)) { if (!(await isOnline.value)) {
showSnackBar(content: 'no_inter'.tr); showSnackBar(content: 'no_inter'.tr);
return; return;
} }
String tz = tzmap.latLngToTimezoneString(latitude, longitude); final tz = tzmap.latLngToTimezoneString(latitude, longitude);
_weatherCard.value = await WeatherAPI() _weatherCard.value = await WeatherAPI().getWeatherCard(
.getWeatherCard(latitude, longitude, city, district, tz); latitude,
longitude,
city,
district,
tz,
);
isar.writeTxnSync(() { isar.writeTxnSync(() {
weatherCards.add(_weatherCard.value); weatherCards.add(_weatherCard.value);
isar.weatherCards.putSync(_weatherCard.value); isar.weatherCards.putSync(_weatherCard.value);
@ -303,7 +313,8 @@ class WeatherController extends GetxController {
} }
Future<void> updateCacheCard(bool refresh) async { Future<void> updateCacheCard(bool refresh) async {
List<WeatherCard> weatherCard = refresh final weatherCard =
refresh
? isar.weatherCards.where().sortByIndex().findAllSync() ? isar.weatherCards.where().sortByIndex().findAllSync()
: isar.weatherCards : isar.weatherCards
.filter() .filter()
@ -311,13 +322,18 @@ class WeatherController extends GetxController {
.sortByIndex() .sortByIndex()
.findAllSync(); .findAllSync();
if ((!(await isOnline.value)) || weatherCard.isEmpty) { if (!(await isOnline.value) || weatherCard.isEmpty) {
return; return;
} }
for (var oldCard in weatherCard) { for (var oldCard in weatherCard) {
var updatedCard = await WeatherAPI().getWeatherCard(oldCard.lat!, final updatedCard = await WeatherAPI().getWeatherCard(
oldCard.lon!, oldCard.city!, oldCard.district!, oldCard.timezone!); oldCard.lat!,
oldCard.lon!,
oldCard.city!,
oldCard.district!,
oldCard.timezone!,
);
isar.writeTxnSync(() { isar.writeTxnSync(() {
oldCard oldCard
..time = updatedCard.time ..time = updatedCard.time
@ -358,8 +374,8 @@ class WeatherController extends GetxController {
isar.weatherCards.putSync(oldCard); isar.weatherCards.putSync(oldCard);
var newCard = oldCard; final newCard = oldCard;
int oldIdx = weatherCard.indexOf(oldCard); final oldIdx = weatherCard.indexOf(oldCard);
weatherCards[oldIdx] = newCard; weatherCards[oldIdx] = newCard;
weatherCards.refresh(); weatherCards.refresh();
}); });
@ -428,35 +444,26 @@ class WeatherController extends GetxController {
} }
int getTime(List<String> time, String timezone) { int getTime(List<String> time, String timezone) {
int getTime = 0; return time.indexWhere((t) {
for (var i = 0; i < time.length; i++) { final dateTime = DateTime.parse(t);
if (tz.TZDateTime.now(tz.getLocation(timezone)).hour == return tz.TZDateTime.now(tz.getLocation(timezone)).hour ==
DateTime.parse(time[i]).hour && dateTime.hour &&
tz.TZDateTime.now(tz.getLocation(timezone)).day == tz.TZDateTime.now(tz.getLocation(timezone)).day == dateTime.day;
DateTime.parse(time[i]).day) { });
getTime = i;
}
}
return getTime;
} }
int getDay(List<DateTime> time, String timezone) { int getDay(List<DateTime> time, String timezone) {
int getDay = 0; return time.indexWhere(
for (var i = 0; i < time.length; i++) { (t) => tz.TZDateTime.now(tz.getLocation(timezone)).day == t.day,
if (tz.TZDateTime.now(tz.getLocation(timezone)).day == time[i].day) { );
getDay = i;
}
}
return getDay;
} }
TimeOfDay timeConvert(String normTime) { TimeOfDay timeConvert(String normTime) {
int hh = 0; final hh = normTime.endsWith('PM') ? 12 : 0;
if (normTime.endsWith('PM')) hh = 12; final timeParts = normTime.split(' ')[0].split(':');
normTime = normTime.split(' ')[0];
return TimeOfDay( return TimeOfDay(
hour: hh + int.parse(normTime.split(':')[0]) % 24, hour: hh + int.parse(timeParts[0]) % 24,
minute: int.parse(normTime.split(':')[1]) % 60, minute: int.parse(timeParts[1]) % 60,
); );
} }
@ -464,8 +471,8 @@ class WeatherController extends GetxController {
final directory = await getTemporaryDirectory(); final directory = await getTemporaryDirectory();
final imagePath = '${directory.path}/$icon'; final imagePath = '${directory.path}/$icon';
final ByteData data = await rootBundle.load('assets/images/$icon'); final data = await rootBundle.load('assets/images/$icon');
final List<int> bytes = data.buffer.asUint8List(); final bytes = data.buffer.asUint8List();
await File(imagePath).writeAsBytes(bytes); await File(imagePath).writeAsBytes(bytes);
@ -473,12 +480,12 @@ class WeatherController extends GetxController {
} }
void notification(MainWeatherCache mainWeatherCache) async { void notification(MainWeatherCache mainWeatherCache) async {
DateTime now = DateTime.now(); final now = DateTime.now();
int startHour = timeConvert(timeStart).hour; final startHour = timeConvert(timeStart).hour;
int endHour = timeConvert(timeEnd).hour; final endHour = timeConvert(timeEnd).hour;
for (var i = 0; i < mainWeatherCache.time!.length; i += timeRange) { for (var i = 0; i < mainWeatherCache.time!.length; i += timeRange) {
DateTime notificationTime = DateTime.parse(mainWeatherCache.time![i]); final notificationTime = DateTime.parse(mainWeatherCache.time![i]);
if (notificationTime.isAfter(now) && if (notificationTime.isAfter(now) &&
notificationTime.hour >= startHour && notificationTime.hour >= startHour &&
@ -505,7 +512,7 @@ class WeatherController extends GetxController {
void notificationCheck() async { void notificationCheck() async {
if (settings.notifications) { if (settings.notifications) {
final List<PendingNotificationRequest> pendingNotificationRequests = final pendingNotificationRequests =
await flutterLocalNotificationsPlugin.pendingNotificationRequests(); await flutterLocalNotificationsPlugin.pendingNotificationRequests();
if (pendingNotificationRequests.isEmpty) { if (pendingNotificationRequests.isEmpty) {
notification(_mainWeather.value); notification(_mainWeather.value);
@ -513,7 +520,7 @@ class WeatherController extends GetxController {
} }
} }
void reorder(oldIndex, newIndex) { void reorder(int oldIndex, int newIndex) {
if (newIndex > oldIndex) { if (newIndex > oldIndex) {
newIndex -= 1; newIndex -= 1;
} }
@ -533,15 +540,11 @@ class WeatherController extends GetxController {
isar.settings.putSync(settings); isar.settings.putSync(settings);
}); });
return Future.wait<bool?>([ final results = await Future.wait<bool?>([
HomeWidget.saveWidgetData( HomeWidget.saveWidgetData('background_color', color),
'background_color',
color,
),
HomeWidget.updateWidget(androidName: androidWidgetName), HomeWidget.updateWidget(androidName: androidWidgetName),
]).then((value) { ]);
return !value.contains(false); return !results.contains(false);
});
} }
Future<bool> updateWidgetTextColor(String color) async { Future<bool> updateWidgetTextColor(String color) async {
@ -550,15 +553,11 @@ class WeatherController extends GetxController {
isar.settings.putSync(settings); isar.settings.putSync(settings);
}); });
return Future.wait<bool?>([ final results = await Future.wait<bool?>([
HomeWidget.saveWidgetData( HomeWidget.saveWidgetData('text_color', color),
'text_color',
color,
),
HomeWidget.updateWidget(androidName: androidWidgetName), HomeWidget.updateWidget(androidName: androidWidgetName),
]).then((value) { ]);
return !value.contains(false); return !results.contains(false);
});
} }
Future<bool> updateWidget() async { Future<bool> updateWidget() async {
@ -573,34 +572,36 @@ class WeatherController extends GetxController {
WeatherCardSchema, WeatherCardSchema,
], directory: (await getApplicationSupportDirectory()).path); ], directory: (await getApplicationSupportDirectory()).path);
MainWeatherCache? mainWeatherCache; final mainWeatherCache =
mainWeatherCache = isarWidget.mainWeatherCaches.where().findFirstSync(); isarWidget.mainWeatherCaches.where().findFirstSync();
if (mainWeatherCache == null) return false; if (mainWeatherCache == null) return false;
int hour = getTime(mainWeatherCache.time!, mainWeatherCache.timezone!); final hour = getTime(mainWeatherCache.time!, mainWeatherCache.timezone!);
int day = getDay(mainWeatherCache.timeDaily!, mainWeatherCache.timezone!); final day = getDay(mainWeatherCache.timeDaily!, mainWeatherCache.timezone!);
return Future.wait<bool?>([ final results = await Future.wait<bool?>([
HomeWidget.saveWidgetData( HomeWidget.saveWidgetData(
'weather_icon', 'weather_icon',
await getLocalImagePath(StatusWeather().getImageNotification( await getLocalImagePath(
StatusWeather().getImageNotification(
mainWeatherCache.weathercode![hour], mainWeatherCache.weathercode![hour],
mainWeatherCache.time![hour], mainWeatherCache.time![hour],
mainWeatherCache.sunrise![day], mainWeatherCache.sunrise![day],
mainWeatherCache.sunset![day], mainWeatherCache.sunset![day],
))), ),
),
),
HomeWidget.saveWidgetData( HomeWidget.saveWidgetData(
'weather_degree', 'weather_degree',
'${mainWeatherCache.temperature2M?[hour].round()}°', '${mainWeatherCache.temperature2M?[hour].round()}°',
), ),
HomeWidget.updateWidget(androidName: androidWidgetName), HomeWidget.updateWidget(androidName: androidWidgetName),
]).then((value) { ]);
return !value.contains(false); return !results.contains(false);
});
} }
void urlLauncher(String uri) async { void urlLauncher(String uri) async {
final Uri url = Uri.parse(uri); final url = Uri.parse(uri);
if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { if (!await launchUrl(url, mode: LaunchMode.externalApplication)) {
throw Exception('Could not launch $url'); throw Exception('Could not launch $url');
} }

View file

@ -152,12 +152,7 @@ class LocationCache {
String? city; String? city;
String? district; String? district;
LocationCache({ LocationCache({this.lat, this.lon, this.city, this.district});
this.lat,
this.lon,
this.city,
this.district,
});
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
@ -304,8 +299,9 @@ class WeatherCard {
time: List<String>.from(json['time'] ?? []), time: List<String>.from(json['time'] ?? []),
weathercode: List<int>.from(json['weathercode'] ?? []), weathercode: List<int>.from(json['weathercode'] ?? []),
temperature2M: List<double>.from(json['temperature2M'] ?? []), temperature2M: List<double>.from(json['temperature2M'] ?? []),
apparentTemperature: apparentTemperature: List<double?>.from(
List<double?>.from(json['apparentTemperature'] ?? []), json['apparentTemperature'] ?? [],
),
relativehumidity2M: List<int?>.from(json['relativehumidity2M'] ?? []), relativehumidity2M: List<int?>.from(json['relativehumidity2M'] ?? []),
precipitation: List<double>.from(json['precipitation'] ?? []), precipitation: List<double>.from(json['precipitation'] ?? []),
rain: List<double?>.from(json['rain'] ?? []), rain: List<double?>.from(json['rain'] ?? []),
@ -318,26 +314,31 @@ class WeatherCard {
cloudcover: List<int?>.from(json['cloudcover'] ?? []), cloudcover: List<int?>.from(json['cloudcover'] ?? []),
uvIndex: List<double?>.from(json['uvIndex'] ?? []), uvIndex: List<double?>.from(json['uvIndex'] ?? []),
dewpoint2M: List<double?>.from(json['dewpoint2M'] ?? []), dewpoint2M: List<double?>.from(json['dewpoint2M'] ?? []),
precipitationProbability: precipitationProbability: List<int?>.from(
List<int?>.from(json['precipitationProbability'] ?? []), json['precipitationProbability'] ?? [],
),
shortwaveRadiation: List<double?>.from(json['shortwaveRadiation'] ?? []), shortwaveRadiation: List<double?>.from(json['shortwaveRadiation'] ?? []),
timeDaily: List<DateTime>.from(json['timeDaily'] ?? []), timeDaily: List<DateTime>.from(json['timeDaily'] ?? []),
weathercodeDaily: List<int?>.from(json['weathercodeDaily'] ?? []), weathercodeDaily: List<int?>.from(json['weathercodeDaily'] ?? []),
temperature2MMax: List<double?>.from(json['temperature2MMax'] ?? []), temperature2MMax: List<double?>.from(json['temperature2MMax'] ?? []),
temperature2MMin: List<double?>.from(json['temperature2MMin'] ?? []), temperature2MMin: List<double?>.from(json['temperature2MMin'] ?? []),
apparentTemperatureMax: apparentTemperatureMax: List<double?>.from(
List<double?>.from(json['apparentTemperatureMax'] ?? []), json['apparentTemperatureMax'] ?? [],
apparentTemperatureMin: ),
List<double?>.from(json['apparentTemperatureMin'] ?? []), apparentTemperatureMin: List<double?>.from(
json['apparentTemperatureMin'] ?? [],
),
windspeed10MMax: List<double?>.from(json['windspeed10MMax'] ?? []), windspeed10MMax: List<double?>.from(json['windspeed10MMax'] ?? []),
windgusts10MMax: List<double?>.from(json['windgusts10MMax'] ?? []), windgusts10MMax: List<double?>.from(json['windgusts10MMax'] ?? []),
uvIndexMax: List<double?>.from(json['uvIndexMax'] ?? []), uvIndexMax: List<double?>.from(json['uvIndexMax'] ?? []),
rainSum: List<double?>.from(json['rainSum'] ?? []), rainSum: List<double?>.from(json['rainSum'] ?? []),
winddirection10MDominant: winddirection10MDominant: List<int?>.from(
List<int?>.from(json['winddirection10MDominant'] ?? []), json['winddirection10MDominant'] ?? [],
),
precipitationSum: List<double?>.from(json['precipitationSum'] ?? []), precipitationSum: List<double?>.from(json['precipitationSum'] ?? []),
precipitationProbabilityMax: precipitationProbabilityMax: List<int?>.from(
List<int?>.from(json['precipitationProbabilityMax'] ?? []), json['precipitationProbabilityMax'] ?? [],
),
sunrise: List<String>.from(json['sunrise'] ?? []), sunrise: List<String>.from(json['sunrise'] ?? []),
sunset: List<String>.from(json['sunset'] ?? []), sunset: List<String>.from(json['sunset'] ?? []),
lat: json['lat'], lat: json['lat'],

View file

@ -15,10 +15,7 @@ import 'package:rain/app/ui/widgets/text_form.dart';
import 'package:rain/main.dart'; import 'package:rain/main.dart';
class SelectGeolocation extends StatefulWidget { class SelectGeolocation extends StatefulWidget {
const SelectGeolocation({ const SelectGeolocation({super.key, required this.isStart});
super.key,
required this.isStart,
});
final bool isStart; final bool isStart;
@override @override
@ -94,16 +91,14 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
leading: widget.isStart leading:
widget.isStart
? null ? null
: IconButton( : IconButton(
onPressed: () { onPressed: () {
Get.back(); Get.back();
}, },
icon: const Icon( icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
IconsaxPlusLinear.arrow_left_3,
size: 20,
),
splashColor: Colors.transparent, splashColor: Colors.transparent,
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
), ),
@ -131,7 +126,8 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 10), horizontal: 10,
),
child: ClipRRect( child: ClipRRect(
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular(20), Radius.circular(20),
@ -150,7 +146,8 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
initialZoom: 3, initialZoom: 3,
interactionOptions: interactionOptions:
const InteractionOptions( const InteractionOptions(
flags: InteractiveFlag.all & flags:
InteractiveFlag.all &
~InteractiveFlag.rotate, ~InteractiveFlag.rotate,
), ),
cameraConstraint: cameraConstraint:
@ -160,9 +157,11 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
const LatLng(90, 180), const LatLng(90, 180),
), ),
), ),
onLongPress: (tapPosition, point) => onLongPress:
fillMap(point.latitude, (tapPosition, point) => fillMap(
point.longitude), point.latitude,
point.longitude,
),
), ),
children: [ children: [
if (_isDarkMode) if (_isDarkMode)
@ -177,9 +176,11 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
attributions: [ attributions: [
TextSourceAttribution( TextSourceAttribution(
'OpenStreetMap contributors', 'OpenStreetMap contributors',
onTap: () => weatherController onTap:
() => weatherController
.urlLauncher( .urlLauncher(
'https://openstreetmap.org/copyright'), 'https://openstreetmap.org/copyright',
),
), ),
], ],
), ),
@ -189,8 +190,12 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
), ),
), ),
Padding( Padding(
padding: padding: const EdgeInsets.fromLTRB(
const EdgeInsets.fromLTRB(10, 15, 10, 5), 10,
15,
10,
5,
),
child: Text( child: Text(
'searchMethod'.tr, 'searchMethod'.tr,
style: context.theme.textTheme.bodyLarge style: context.theme.textTheme.bodyLarge
@ -204,42 +209,54 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
child: RawAutocomplete<Result>( child: RawAutocomplete<Result>(
focusNode: _focusNode, focusNode: _focusNode,
textEditingController: _controller, textEditingController: _controller,
fieldViewBuilder: (BuildContext context, fieldViewBuilder: (
BuildContext context,
TextEditingController TextEditingController
fieldTextEditingController, fieldTextEditingController,
FocusNode fieldFocusNode, FocusNode fieldFocusNode,
VoidCallback onFieldSubmitted) { VoidCallback onFieldSubmitted,
) {
return MyTextForm( return MyTextForm(
elevation: kTextFieldElevation, elevation: kTextFieldElevation,
labelText: 'search'.tr, labelText: 'search'.tr,
type: TextInputType.text, type: TextInputType.text,
icon: const Icon(IconsaxPlusLinear icon: const Icon(
.global_search), IconsaxPlusLinear.global_search,
),
controller: _controller, controller: _controller,
margin: const EdgeInsets.only( margin: const EdgeInsets.only(
left: 10, right: 10, top: 10), left: 10,
right: 10,
top: 10,
),
focusNode: _focusNode, focusNode: _focusNode,
); );
}, },
optionsBuilder: (TextEditingValue optionsBuilder: (
textEditingValue) { TextEditingValue textEditingValue,
) {
if (textEditingValue.text.isEmpty) { if (textEditingValue.text.isEmpty) {
return const Iterable< return const Iterable<
Result>.empty(); Result
>.empty();
} }
return WeatherAPI().getCity( return WeatherAPI().getCity(
textEditingValue.text, locale); textEditingValue.text,
locale,
);
}, },
onSelected: (Result selection) => onSelected:
(Result selection) =>
fillController(selection), fillController(selection),
displayStringForOption: (Result displayStringForOption:
option) => (Result option) =>
'${option.name}, ${option.admin1}', '${option.name}, ${option.admin1}',
optionsViewBuilder: optionsViewBuilder: (
(BuildContext context, BuildContext context,
AutocompleteOnSelected<Result> AutocompleteOnSelected<Result>
onSelected, onSelected,
Iterable<Result> options) { Iterable<Result> options,
) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 10, horizontal: 10,
@ -255,19 +272,24 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
shrinkWrap: true, shrinkWrap: true,
itemCount: options.length, itemCount: options.length,
itemBuilder: itemBuilder: (
(BuildContext context, BuildContext context,
int index) { int index,
) {
final Result option = final Result option =
options options.elementAt(
.elementAt(index); index,
);
return InkWell( return InkWell(
onTap: () => onTap:
onSelected(option), () => onSelected(
option,
),
child: ListTile( child: ListTile(
title: Text( title: Text(
'${option.name}, ${option.admin1}', '${option.name}, ${option.admin1}',
style: context style:
context
.textTheme .textTheme
.labelLarge, .labelLarge,
), ),
@ -292,47 +314,53 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
child: IconButton( child: IconButton(
onPressed: () async { onPressed: () async {
bool serviceEnabled = bool serviceEnabled =
await Geolocator await Geolocator.isLocationServiceEnabled();
.isLocationServiceEnabled();
if (!serviceEnabled) { if (!serviceEnabled) {
if (!context.mounted) return; if (!context.mounted) return;
await showAdaptiveDialog( await showAdaptiveDialog(
context: context, context: context,
builder: builder: (
(BuildContext context) { BuildContext context,
) {
return AlertDialog.adaptive( return AlertDialog.adaptive(
title: Text( title: Text(
'location'.tr, 'location'.tr,
style: context style:
.textTheme.titleLarge, context
.textTheme
.titleLarge,
), ),
content: Text( content: Text(
'no_location'.tr, 'no_location'.tr,
style: context.textTheme style:
context
.textTheme
.titleMedium, .titleMedium,
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => onPressed:
Get.back( () => Get.back(
result: false), result: false,
),
child: Text( child: Text(
'cancel'.tr, 'cancel'.tr,
style: context style: context
.textTheme .textTheme
.titleMedium .titleMedium
?.copyWith( ?.copyWith(
color: Colors color:
Colors
.blueAccent, .blueAccent,
), ),
), ),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
Geolocator Geolocator.openLocationSettings();
.openLocationSettings();
Get.back( Get.back(
result: true); result: true,
);
}, },
child: Text( child: Text(
'settings'.tr, 'settings'.tr,
@ -340,8 +368,10 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
.textTheme .textTheme
.titleMedium .titleMedium
?.copyWith( ?.copyWith(
color: Colors color:
.green), Colors
.green,
),
), ),
), ),
], ],
@ -380,8 +410,9 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'validateValue'.tr; return 'validateValue'.tr;
} }
double? numericValue = double? numericValue = double.tryParse(
double.tryParse(value); value,
);
if (numericValue == null) { if (numericValue == null) {
return 'validateNumber'.tr; return 'validateNumber'.tr;
} }
@ -407,8 +438,9 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'validateValue'.tr; return 'validateValue'.tr;
} }
double? numericValue = double? numericValue = double.tryParse(
double.tryParse(value); value,
);
if (numericValue == null) { if (numericValue == null) {
return 'validateNumber'.tr; return 'validateNumber'.tr;
} }
@ -424,10 +456,14 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
controller: _controllerCity, controller: _controllerCity,
labelText: 'city'.tr, labelText: 'city'.tr,
type: TextInputType.name, type: TextInputType.name,
icon: icon: const Icon(
const Icon(IconsaxPlusLinear.building_3), IconsaxPlusLinear.building_3,
),
margin: const EdgeInsets.only( margin: const EdgeInsets.only(
left: 10, right: 10, top: 10), left: 10,
right: 10,
top: 10,
),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'validateName'.tr; return 'validateName'.tr;
@ -442,7 +478,10 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
type: TextInputType.streetAddress, type: TextInputType.streetAddress,
icon: const Icon(IconsaxPlusLinear.global), icon: const Icon(IconsaxPlusLinear.global),
margin: const EdgeInsets.only( margin: const EdgeInsets.only(
left: 10, right: 10, top: 10), left: 10,
right: 10,
top: 10,
),
), ),
const Gap(20), const Gap(20),
], ],
@ -453,8 +492,10 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
), ),
), ),
Padding( Padding(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 10, vertical: 8), horizontal: 10,
vertical: 8,
),
child: MyTextButton( child: MyTextButton(
buttonName: 'done'.tr, buttonName: 'done'.tr,
onPressed: () async { onPressed: () async {
@ -472,8 +513,10 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
_controllerCity.text, _controllerCity.text,
); );
widget.isStart widget.isStart
? Get.off(() => const HomePage(), ? Get.off(
transition: Transition.downToUp) () => const HomePage(),
transition: Transition.downToUp,
)
: Get.back(); : Get.back();
} catch (error) { } catch (error) {
Future.error(error); Future.error(error);
@ -487,9 +530,7 @@ class _SelectGeolocationState extends State<SelectGeolocation> {
if (isLoading) if (isLoading)
BackdropFilter( BackdropFilter(
filter: ImageFilter.blur(sigmaY: 3, sigmaX: 3), filter: ImageFilter.blur(sigmaY: 3, sigmaX: 3),
child: const Center( child: const Center(child: CircularProgressIndicator()),
child: CircularProgressIndicator(),
),
), ),
], ],
), ),

View file

@ -103,18 +103,18 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
leading: switch (tabIndex) { leading: switch (tabIndex) {
0 => IconButton( 0 => IconButton(
onPressed: () { onPressed: () {
Get.to(() => const SelectGeolocation(isStart: false), Get.to(
transition: Transition.downToUp); () => const SelectGeolocation(isStart: false),
transition: Transition.downToUp,
);
}, },
icon: const Icon( icon: const Icon(IconsaxPlusLinear.global_search, size: 18),
IconsaxPlusLinear.global_search,
size: 18,
),
), ),
int() => null, int() => null,
}, },
title: switch (tabIndex) { title: switch (tabIndex) {
0 => visible 0 =>
visible
? RawAutocomplete<Result>( ? RawAutocomplete<Result>(
focusNode: _focusNode, focusNode: _focusNode,
textEditingController: _controller, textEditingController: _controller,
@ -123,17 +123,17 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
controller: _controller, controller: _controller,
focusNode: _focusNode, focusNode: _focusNode,
style: labelLarge?.copyWith(fontSize: 16), style: labelLarge?.copyWith(fontSize: 16),
decoration: InputDecoration( decoration: InputDecoration(hintText: 'search'.tr),
hintText: 'search'.tr,
),
); );
}, },
optionsBuilder: (TextEditingValue textEditingValue) { optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text.isEmpty) { if (textEditingValue.text.isEmpty) {
return const Iterable<Result>.empty(); return const Iterable<Result>.empty();
} }
return WeatherAPI() return WeatherAPI().getCity(
.getCity(textEditingValue.text, locale); textEditingValue.text,
locale,
);
}, },
onSelected: (Result selection) async { onSelected: (Result selection) async {
await weatherController.deleteAll(true); await weatherController.deleteAll(true);
@ -148,11 +148,13 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
_focusNode.unfocus(); _focusNode.unfocus();
setState(() {}); setState(() {});
}, },
displayStringForOption: (Result option) => displayStringForOption:
'${option.name}, ${option.admin1}', (Result option) => '${option.name}, ${option.admin1}',
optionsViewBuilder: (BuildContext context, optionsViewBuilder: (
BuildContext context,
AutocompleteOnSelected<Result> onSelected, AutocompleteOnSelected<Result> onSelected,
Iterable<Result> options) { Iterable<Result> options,
) {
return Align( return Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: Material( child: Material(
@ -165,8 +167,9 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
shrinkWrap: true, shrinkWrap: true,
itemCount: options.length, itemCount: options.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final Result option = final Result option = options.elementAt(
options.elementAt(index); index,
);
return InkWell( return InkWell(
onTap: () => onSelected(option), onTap: () => onSelected(option),
child: ListTile( child: ListTile(
@ -183,8 +186,7 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
); );
}, },
) )
: Obx( : Obx(() {
() {
final location = weatherController.location; final location = weatherController.location;
final city = location.city; final city = location.city;
final district = location.district; final district = location.district;
@ -196,7 +198,8 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
? district ? district
: city == district : city == district
? city ? city
: '$city' ', $district' : '$city'
', $district'
: settings.location : settings.location
? 'search'.tr ? 'search'.tr
: (isar.locationCaches.where().findAllSync()) : (isar.locationCaches.where().findAllSync())
@ -205,25 +208,13 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
: 'searchCity'.tr, : 'searchCity'.tr,
style: textStyle, style: textStyle,
); );
}, }),
), 1 => Text('cities'.tr, style: textStyle),
1 => Text( 2 =>
'cities'.tr, settings.hideMap
style: textStyle, ? Text('settings_full'.tr, style: textStyle)
), : Text('map'.tr, style: textStyle),
2 => settings.hideMap 3 => Text('settings_full'.tr, style: textStyle),
? Text(
'settings_full'.tr,
style: textStyle,
)
: Text(
'map'.tr,
style: textStyle,
),
3 => Text(
'settings_full'.tr,
style: textStyle,
),
int() => null, int() => null,
}, },
actions: switch (tabIndex) { actions: switch (tabIndex) {
@ -245,16 +236,13 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
: IconsaxPlusLinear.search_normal_1, : IconsaxPlusLinear.search_normal_1,
size: 18, size: 18,
), ),
) ),
], ],
int() => null, int() => null,
}, },
), ),
body: SafeArea( body: SafeArea(
child: TabBarView( child: TabBarView(controller: tabController, children: pages),
controller: tabController,
children: pages,
),
), ),
bottomNavigationBar: NavigationBar( bottomNavigationBar: NavigationBar(
onDestinationSelected: (int index) => changeTabIndex(index), onDestinationSelected: (int index) => changeTabIndex(index),
@ -283,17 +271,18 @@ class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
), ),
], ],
), ),
floatingActionButton: tabIndex == 1 floatingActionButton:
tabIndex == 1
? FloatingActionButton( ? FloatingActionButton(
onPressed: () => showModalBottomSheet( onPressed:
() => showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
enableDrag: false, enableDrag: false,
builder: (BuildContext context) => const CreatePlace(), builder:
), (BuildContext context) => const CreatePlace(),
child: const Icon(
IconsaxPlusLinear.add,
), ),
child: const Icon(IconsaxPlusLinear.add),
) )
: null, : null,
), ),

View file

@ -35,9 +35,7 @@ class _MainPageState extends State<MainPage> {
if (weatherController.isLoading.isTrue) { if (weatherController.isLoading.isTrue) {
return ListView( return ListView(
children: const [ children: const [
MyShimmer( MyShimmer(hight: 200),
hight: 200,
),
MyShimmer( MyShimmer(
hight: 130, hight: 130,
edgeInsetsMargin: EdgeInsets.symmetric(vertical: 15), edgeInsetsMargin: EdgeInsets.symmetric(vertical: 15),
@ -53,7 +51,7 @@ class _MainPageState extends State<MainPage> {
MyShimmer( MyShimmer(
hight: 450, hight: 450,
edgeInsetsMargin: EdgeInsets.only(bottom: 15), edgeInsetsMargin: EdgeInsets.only(bottom: 15),
) ),
], ],
); );
} }
@ -82,8 +80,10 @@ class _MainPageState extends State<MainPage> {
Card( Card(
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 10, vertical: 5), horizontal: 10,
vertical: 5,
),
child: SizedBox( child: SizedBox(
height: 135, height: 135,
child: ScrollablePositionedList.separated( child: ScrollablePositionedList.separated(
@ -115,8 +115,12 @@ class _MainPageState extends State<MainPage> {
vertical: 5, vertical: 5,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: i == hourOfDay color:
? context.theme.colorScheme.secondaryContainer i == hourOfDay
? context
.theme
.colorScheme
.secondaryContainer
: Colors.transparent, : Colors.transparent,
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular(20), Radius.circular(20),
@ -136,10 +140,7 @@ class _MainPageState extends State<MainPage> {
), ),
), ),
), ),
SunsetSunrise( SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset),
timeSunrise: sunrise,
timeSunset: sunset,
),
DescContainer( DescContainer(
humidity: mainWeather.relativehumidity2M?[hourOfDay], humidity: mainWeather.relativehumidity2M?[hourOfDay],
wind: mainWeather.windspeed10M?[hourOfDay], wind: mainWeather.windspeed10M?[hourOfDay],
@ -162,13 +163,12 @@ class _MainPageState extends State<MainPage> {
), ),
DailyContainer( DailyContainer(
weatherData: weatherCard, weatherData: weatherCard,
onTap: () => Get.to( onTap:
() => DailyCardList( () => Get.to(
weatherData: weatherCard, () => DailyCardList(weatherData: weatherCard),
),
transition: Transition.downToUp, transition: Transition.downToUp,
), ),
) ),
], ],
); );
}), }),

View file

@ -66,10 +66,9 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
_offsetAnimation = Tween<Offset>( _offsetAnimation = Tween<Offset>(
begin: const Offset(0.0, 1.0), begin: const Offset(0.0, 1.0),
end: Offset.zero, end: Offset.zero,
).animate(CurvedAnimation( ).animate(
parent: _animationController, CurvedAnimation(parent: _animationController, curve: Curves.easeInOut),
curve: Curves.easeInOut, );
));
super.initState(); super.initState();
} }
@ -114,25 +113,26 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
_focusNode.unfocus(); _focusNode.unfocus();
} }
Widget _buidStyleMarkers(int weathercode, String time, String sunrise, Widget _buidStyleMarkers(
String sunset, double temperature2M) { int weathercode,
String time,
String sunrise,
String sunset,
double temperature2M,
) {
return Card( return Card(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Image.asset( Image.asset(
statusWeather.getImageNow( statusWeather.getImageNow(weathercode, time, sunrise, sunset),
weathercode,
time,
sunrise,
sunset,
),
scale: 18, scale: 18,
), ),
const MaxGap(5), const MaxGap(5),
Text( Text(
statusData statusData.getDegree(
.getDegree(roundDegree ? temperature2M.round() : temperature2M), roundDegree ? temperature2M.round() : temperature2M,
),
style: context.textTheme.labelLarge?.copyWith( style: context.textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
fontSize: 16, fontSize: 16,
@ -144,7 +144,10 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
} }
Marker _buildMainLocationMarker( Marker _buildMainLocationMarker(
WeatherCard weatherCard, int hourOfDay, int dayOfNow) { WeatherCard weatherCard,
int hourOfDay,
int dayOfNow,
) {
return Marker( return Marker(
height: 50, height: 50,
width: 100, width: 100,
@ -171,15 +174,25 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
onTap: () => _onMarkerTap(weatherCardList), onTap: () => _onMarkerTap(weatherCardList),
child: _buidStyleMarkers( child: _buidStyleMarkers(
weatherCardList.weathercode![weatherController.getTime( weatherCardList.weathercode![weatherController.getTime(
weatherCardList.time!, weatherCardList.timezone!)], weatherCardList.time!,
weatherCardList.timezone!,
)],
weatherCardList.time![weatherController.getTime( weatherCardList.time![weatherController.getTime(
weatherCardList.time!, weatherCardList.timezone!)], weatherCardList.time!,
weatherCardList.timezone!,
)],
weatherCardList.sunrise![weatherController.getDay( weatherCardList.sunrise![weatherController.getDay(
weatherCardList.timeDaily!, weatherCardList.timezone!)], weatherCardList.timeDaily!,
weatherCardList.timezone!,
)],
weatherCardList.sunset![weatherController.getDay( weatherCardList.sunset![weatherController.getDay(
weatherCardList.timeDaily!, weatherCardList.timezone!)], weatherCardList.timeDaily!,
weatherCardList.timezone!,
)],
weatherCardList.temperature2M![weatherController.getTime( weatherCardList.temperature2M![weatherController.getTime(
weatherCardList.time!, weatherCardList.timezone!)], weatherCardList.time!,
weatherCardList.timezone!,
)],
), ),
), ),
); );
@ -201,7 +214,8 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
? SlideTransition( ? SlideTransition(
position: _offsetAnimation, position: _offsetAnimation,
child: GestureDetector( child: GestureDetector(
onTap: () => Get.to( onTap:
() => Get.to(
() => PlaceInfo(weatherCard: _selectedWeatherCard!), () => PlaceInfo(weatherCard: _selectedWeatherCard!),
transition: Transition.downToUp, transition: Transition.downToUp,
), ),
@ -261,11 +275,13 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
), ),
), ),
onTap: (_, __) => _hideCard(), onTap: (_, __) => _hideCard(),
onLongPress: (tapPosition, point) => showModalBottomSheet( onLongPress:
(tapPosition, point) => showModalBottomSheet(
context: context, context: context,
isScrollControlled: true, isScrollControlled: true,
enableDrag: false, enableDrag: false,
builder: (BuildContext context) => CreatePlace( builder:
(BuildContext context) => CreatePlace(
latitude: '${point.latitude}', latitude: '${point.latitude}',
longitude: '${point.longitude}', longitude: '${point.longitude}',
), ),
@ -290,8 +306,10 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
attributions: [ attributions: [
TextSourceAttribution( TextSourceAttribution(
'OpenStreetMap contributors', 'OpenStreetMap contributors',
onTap: () => weatherController onTap:
.urlLauncher('https://openstreetmap.org/copyright'), () => weatherController.urlLauncher(
'https://openstreetmap.org/copyright',
),
), ),
], ],
), ),
@ -305,14 +323,15 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
dayOfNow, dayOfNow,
); );
final cardMarkers = weatherController.weatherCards final cardMarkers =
.map((weatherCardList) => weatherController.weatherCards
_buildCardMarker(weatherCardList)) .map(
(weatherCardList) =>
_buildCardMarker(weatherCardList),
)
.toList(); .toList();
return MarkerLayer( return MarkerLayer(markers: [mainMarker, ...cardMarkers]);
markers: [mainMarker, ...cardMarkers],
);
}), }),
ExpandableFab( ExpandableFab(
key: _fabKey, key: _fabKey,
@ -331,23 +350,31 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
FloatingActionButton( FloatingActionButton(
heroTag: null, heroTag: null,
child: const Icon(IconsaxPlusLinear.home_2), child: const Icon(IconsaxPlusLinear.home_2),
onPressed: () => _resetMapOrientation( onPressed:
center: () => _resetMapOrientation(
LatLng(mainLocation.lat!, mainLocation.lon!), center: LatLng(
zoom: 8), mainLocation.lat!,
mainLocation.lon!,
),
zoom: 8,
),
), ),
FloatingActionButton( FloatingActionButton(
heroTag: null, heroTag: null,
child: const Icon(IconsaxPlusLinear.search_zoom_out_1), child: const Icon(IconsaxPlusLinear.search_zoom_out_1),
onPressed: () => _animatedMapController.animatedZoomOut( onPressed:
customId: _useTransformer ? _useTransformerId : null, () => _animatedMapController.animatedZoomOut(
customId:
_useTransformer ? _useTransformerId : null,
), ),
), ),
FloatingActionButton( FloatingActionButton(
heroTag: null, heroTag: null,
child: const Icon(IconsaxPlusLinear.search_zoom_in), child: const Icon(IconsaxPlusLinear.search_zoom_in),
onPressed: () => _animatedMapController.animatedZoomIn( onPressed:
customId: _useTransformer ? _useTransformerId : null, () => _animatedMapController.animatedZoomIn(
customId:
_useTransformer ? _useTransformerId : null,
), ),
), ),
], ],
@ -363,10 +390,12 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
RawAutocomplete<Result>( RawAutocomplete<Result>(
focusNode: _focusNode, focusNode: _focusNode,
textEditingController: _controllerSearch, textEditingController: _controllerSearch,
fieldViewBuilder: (BuildContext context, fieldViewBuilder: (
BuildContext context,
TextEditingController fieldTextEditingController, TextEditingController fieldTextEditingController,
FocusNode fieldFocusNode, FocusNode fieldFocusNode,
VoidCallback onFieldSubmitted) { VoidCallback onFieldSubmitted,
) {
return MyTextForm( return MyTextForm(
labelText: 'search'.tr, labelText: 'search'.tr,
type: TextInputType.text, type: TextInputType.text,
@ -375,7 +404,8 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
margin: const EdgeInsets.only(left: 10, right: 10, top: 10), margin: const EdgeInsets.only(left: 10, right: 10, top: 10),
focusNode: _focusNode, focusNode: _focusNode,
onChanged: (value) => setState(() {}), onChanged: (value) => setState(() {}),
iconButton: _controllerSearch.text.isNotEmpty iconButton:
_controllerSearch.text.isNotEmpty
? IconButton( ? IconButton(
onPressed: () { onPressed: () {
_controllerSearch.clear(); _controllerSearch.clear();
@ -397,18 +427,24 @@ class _MapPageState extends State<MapPage> with TickerProviderStateMixin {
}, },
onSelected: (Result selection) { onSelected: (Result selection) {
_animatedMapController.mapController.move( _animatedMapController.mapController.move(
LatLng(selection.latitude, selection.longitude), 14); LatLng(selection.latitude, selection.longitude),
14,
);
_controllerSearch.clear(); _controllerSearch.clear();
_focusNode.unfocus(); _focusNode.unfocus();
}, },
displayStringForOption: (Result option) => displayStringForOption:
'${option.name}, ${option.admin1}', (Result option) => '${option.name}, ${option.admin1}',
optionsViewBuilder: (BuildContext context, optionsViewBuilder: (
BuildContext context,
AutocompleteOnSelected<Result> onSelected, AutocompleteOnSelected<Result> onSelected,
Iterable<Result> options) { Iterable<Result> options,
) {
return Padding( return Padding(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 10, vertical: 5), horizontal: 10,
vertical: 5,
),
child: Align( child: Align(
alignment: Alignment.topCenter, alignment: Alignment.topCenter,
child: Material( child: Material(

View file

@ -32,8 +32,10 @@ class _OnBordingState extends State<OnBording> {
void onBoardHome() { void onBoardHome() {
settings.onboard = true; settings.onboard = true;
isar.writeTxnSync(() => isar.settings.putSync(settings)); isar.writeTxnSync(() => isar.settings.putSync(settings));
Get.off(() => const SelectGeolocation(isStart: true), Get.off(
transition: Transition.downToUp); () => const SelectGeolocation(isStart: true),
transition: Transition.downToUp,
);
} }
@override @override
@ -52,7 +54,8 @@ class _OnBordingState extends State<OnBording> {
pageIndex = index; pageIndex = index;
}); });
}, },
itemBuilder: (context, index) => OnboardContent( itemBuilder:
(context, index) => OnboardContent(
image: data[index].image, image: data[index].image,
title: data[index].title, title: data[index].title,
description: data[index].description, description: data[index].description,
@ -67,7 +70,8 @@ class _OnBordingState extends State<OnBording> {
(index) => Padding( (index) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 5), padding: const EdgeInsets.symmetric(horizontal: 5),
child: DotIndicator(isActive: index == pageIndex), child: DotIndicator(isActive: index == pageIndex),
)), ),
),
], ],
), ),
Padding( Padding(
@ -84,7 +88,7 @@ class _OnBordingState extends State<OnBording> {
); );
}, },
), ),
) ),
], ],
), ),
), ),
@ -93,10 +97,7 @@ class _OnBordingState extends State<OnBording> {
} }
class DotIndicator extends StatelessWidget { class DotIndicator extends StatelessWidget {
const DotIndicator({ const DotIndicator({super.key, this.isActive = false});
super.key,
this.isActive = false,
});
final bool isActive; final bool isActive;
@ -107,7 +108,8 @@ class DotIndicator extends StatelessWidget {
height: 8, height: 8,
width: 8, width: 8,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isActive color:
isActive
? context.theme.colorScheme.secondary ? context.theme.colorScheme.secondary
: context.theme.colorScheme.secondaryContainer, : context.theme.colorScheme.secondaryContainer,
shape: BoxShape.circle, shape: BoxShape.circle,
@ -130,15 +132,18 @@ final List<Onboard> data = [
Onboard( Onboard(
image: 'assets/icons/Rain.png', image: 'assets/icons/Rain.png',
title: 'name'.tr, title: 'name'.tr,
description: 'description'.tr), description: 'description'.tr,
),
Onboard( Onboard(
image: 'assets/icons/Design.png', image: 'assets/icons/Design.png',
title: 'name2'.tr, title: 'name2'.tr,
description: 'description2'.tr), description: 'description2'.tr,
),
Onboard( Onboard(
image: 'assets/icons/Team.png', image: 'assets/icons/Team.png',
title: 'name3'.tr, title: 'name3'.tr,
description: 'description3'.tr), description: 'description3'.tr,
),
]; ];
class OnboardContent extends StatelessWidget { class OnboardContent extends StatelessWidget {
@ -158,14 +163,12 @@ class OnboardContent extends StatelessWidget {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Image.asset( Image.asset(image, scale: 5),
image,
scale: 5,
),
Text( Text(
title, title,
style: context.textTheme.titleLarge style: context.textTheme.titleLarge?.copyWith(
?.copyWith(fontWeight: FontWeight.w600), fontWeight: FontWeight.w600,
),
), ),
const Gap(10), const Gap(10),
SizedBox( SizedBox(

View file

@ -12,10 +12,7 @@ import 'package:rain/app/ui/widgets/weather/sunset_sunrise.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
class PlaceInfo extends StatefulWidget { class PlaceInfo extends StatefulWidget {
const PlaceInfo({ const PlaceInfo({super.key, required this.weatherCard});
super.key,
required this.weatherCard,
});
final WeatherCard weatherCard; final WeatherCard weatherCard;
@override @override
@ -37,10 +34,14 @@ class _PlaceInfoState extends State<PlaceInfo> {
void getTime() { void getTime() {
final weatherCard = widget.weatherCard; final weatherCard = widget.weatherCard;
timeNow = timeNow = weatherController.getTime(
weatherController.getTime(weatherCard.time!, weatherCard.timezone!); weatherCard.time!,
dayNow = weatherCard.timezone!,
weatherController.getDay(weatherCard.timeDaily!, weatherCard.timezone!); );
dayNow = weatherController.getDay(
weatherCard.timeDaily!,
weatherCard.timezone!,
);
Future.delayed(const Duration(milliseconds: 30), () { Future.delayed(const Duration(milliseconds: 30), () {
itemScrollController.scrollTo( itemScrollController.scrollTo(
index: timeNow, index: timeNow,
@ -66,10 +67,7 @@ class _PlaceInfoState extends State<PlaceInfo> {
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
leading: IconButton( leading: IconButton(
onPressed: () => Get.back(), onPressed: () => Get.back(),
icon: const Icon( icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
IconsaxPlusLinear.arrow_left_3,
size: 20,
),
), ),
title: Text( title: Text(
weatherCard.district!.isNotEmpty weatherCard.district!.isNotEmpty
@ -100,8 +98,10 @@ class _PlaceInfoState extends State<PlaceInfo> {
Card( Card(
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
child: Padding( child: Padding(
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 10, vertical: 5), horizontal: 10,
vertical: 5,
),
child: SizedBox( child: SizedBox(
height: 135, height: 135,
child: ScrollablePositionedList.separated( child: ScrollablePositionedList.separated(
@ -116,7 +116,8 @@ class _PlaceInfoState extends State<PlaceInfo> {
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemScrollController: itemScrollController, itemScrollController: itemScrollController,
itemCount: weatherCard.time!.length, itemCount: weatherCard.time!.length,
itemBuilder: (ctx, i) => GestureDetector( itemBuilder:
(ctx, i) => GestureDetector(
onTap: () { onTap: () {
timeNow = i; timeNow = i;
dayNow = (i / 24).floor(); dayNow = (i / 24).floor();
@ -129,8 +130,12 @@ class _PlaceInfoState extends State<PlaceInfo> {
vertical: 5, vertical: 5,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: i == timeNow color:
? context.theme.colorScheme.secondaryContainer i == timeNow
? context
.theme
.colorScheme
.secondaryContainer
: Colors.transparent, : Colors.transparent,
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
Radius.circular(20), Radius.circular(20),
@ -140,8 +145,10 @@ class _PlaceInfoState extends State<PlaceInfo> {
time: weatherCard.time![i], time: weatherCard.time![i],
weather: weatherCard.weathercode![i], weather: weatherCard.weathercode![i],
degree: weatherCard.temperature2M![i], degree: weatherCard.temperature2M![i],
timeDay: weatherCard.sunrise![(i / 24).floor()], timeDay:
timeNight: weatherCard.sunset![(i / 24).floor()], weatherCard.sunrise![(i / 24).floor()],
timeNight:
weatherCard.sunset![(i / 24).floor()],
), ),
), ),
), ),
@ -175,10 +182,9 @@ class _PlaceInfoState extends State<PlaceInfo> {
), ),
DailyContainer( DailyContainer(
weatherData: weatherCard, weatherData: weatherCard,
onTap: () => Get.to( onTap:
() => DailyCardList( () => Get.to(
weatherData: weatherCard, () => DailyCardList(weatherData: weatherCard),
),
transition: Transition.downToUp, transition: Transition.downToUp,
), ),
), ),

View file

@ -33,15 +33,13 @@ class _PlaceListState extends State<PlaceList> {
final textTheme = context.textTheme; final textTheme = context.textTheme;
final titleMedium = textTheme.titleMedium; final titleMedium = textTheme.titleMedium;
return Obx( return Obx(
() => weatherController.weatherCards.isEmpty () =>
weatherController.weatherCards.isEmpty
? Center( ? Center(
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
Image.asset( Image.asset('assets/icons/City.png', scale: 6),
'assets/icons/City.png',
scale: 6,
),
SizedBox( SizedBox(
width: Get.size.width * 0.8, width: Get.size.width * 0.8,
child: Text( child: Text(
@ -71,9 +69,12 @@ class _PlaceListState extends State<PlaceList> {
), ),
controller: searchTasks, controller: searchTasks,
margin: const EdgeInsets.symmetric( margin: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5), horizontal: 10,
vertical: 5,
),
onChanged: applyFilter, onChanged: applyFilter,
iconButton: searchTasks.text.isNotEmpty iconButton:
searchTasks.text.isNotEmpty
? IconButton( ? IconButton(
onPressed: () { onPressed: () {
searchTasks.clear(); searchTasks.clear();

View file

@ -9,11 +9,7 @@ import 'package:rain/app/ui/widgets/text_form.dart';
import 'package:rain/main.dart'; import 'package:rain/main.dart';
class CreatePlace extends StatefulWidget { class CreatePlace extends StatefulWidget {
const CreatePlace({ const CreatePlace({super.key, this.latitude, this.longitude});
super.key,
this.latitude,
this.longitude,
});
final String? latitude; final String? latitude;
final String? longitude; final String? longitude;
@ -85,7 +81,8 @@ class _CreatePlaceState extends State<CreatePlace>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const kTextFieldElevation = 4.0; const kTextFieldElevation = 4.0;
bool showButton = _controllerLon.text.isNotEmpty && bool showButton =
_controllerLon.text.isNotEmpty &&
_controllerLat.text.isNotEmpty && _controllerLat.text.isNotEmpty &&
_controllerCity.text.isNotEmpty && _controllerCity.text.isNotEmpty &&
_controllerDistrict.text.isNotEmpty; _controllerDistrict.text.isNotEmpty;
@ -115,18 +112,21 @@ class _CreatePlaceState extends State<CreatePlace>
padding: const EdgeInsets.only(top: 14, bottom: 7), padding: const EdgeInsets.only(top: 14, bottom: 7),
child: Text( child: Text(
'create'.tr, 'create'.tr,
style: context.textTheme.titleLarge style: context.textTheme.titleLarge?.copyWith(
?.copyWith(fontWeight: FontWeight.bold), fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
RawAutocomplete<Result>( RawAutocomplete<Result>(
focusNode: _focusNode, focusNode: _focusNode,
textEditingController: _controller, textEditingController: _controller,
fieldViewBuilder: (BuildContext context, fieldViewBuilder: (
BuildContext context,
TextEditingController fieldTextEditingController, TextEditingController fieldTextEditingController,
FocusNode fieldFocusNode, FocusNode fieldFocusNode,
VoidCallback onFieldSubmitted) { VoidCallback onFieldSubmitted,
) {
return MyTextForm( return MyTextForm(
elevation: kTextFieldElevation, elevation: kTextFieldElevation,
labelText: 'search'.tr, labelText: 'search'.tr,
@ -134,7 +134,10 @@ class _CreatePlaceState extends State<CreatePlace>
icon: const Icon(IconsaxPlusLinear.global_search), icon: const Icon(IconsaxPlusLinear.global_search),
controller: _controller, controller: _controller,
margin: const EdgeInsets.only( margin: const EdgeInsets.only(
left: 10, right: 10, top: 10), left: 10,
right: 10,
top: 10,
),
focusNode: _focusNode, focusNode: _focusNode,
); );
}, },
@ -142,16 +145,20 @@ class _CreatePlaceState extends State<CreatePlace>
if (textEditingValue.text.isEmpty) { if (textEditingValue.text.isEmpty) {
return const Iterable<Result>.empty(); return const Iterable<Result>.empty();
} }
return WeatherAPI() return WeatherAPI().getCity(
.getCity(textEditingValue.text, locale); textEditingValue.text,
locale,
);
}, },
onSelected: (Result selection) => onSelected:
fillController(selection), (Result selection) => fillController(selection),
displayStringForOption: (Result option) => displayStringForOption:
'${option.name}, ${option.admin1}', (Result option) => '${option.name}, ${option.admin1}',
optionsViewBuilder: (BuildContext context, optionsViewBuilder: (
BuildContext context,
AutocompleteOnSelected<Result> onSelected, AutocompleteOnSelected<Result> onSelected,
Iterable<Result> options) { Iterable<Result> options,
) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
child: Align( child: Align(
@ -164,8 +171,9 @@ class _CreatePlaceState extends State<CreatePlace>
shrinkWrap: true, shrinkWrap: true,
itemCount: options.length, itemCount: options.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final Result option = final Result option = options.elementAt(
options.elementAt(index); index,
);
return InkWell( return InkWell(
onTap: () => onSelected(option), onTap: () => onSelected(option),
child: ListTile( child: ListTile(
@ -189,8 +197,11 @@ class _CreatePlaceState extends State<CreatePlace>
type: TextInputType.number, type: TextInputType.number,
icon: const Icon(IconsaxPlusLinear.location), icon: const Icon(IconsaxPlusLinear.location),
onChanged: (value) => setState(() {}), onChanged: (value) => setState(() {}),
margin: margin: const EdgeInsets.only(
const EdgeInsets.only(left: 10, right: 10, top: 10), left: 10,
right: 10,
top: 10,
),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'validateValue'.tr; return 'validateValue'.tr;
@ -212,8 +223,11 @@ class _CreatePlaceState extends State<CreatePlace>
type: TextInputType.number, type: TextInputType.number,
icon: const Icon(IconsaxPlusLinear.location), icon: const Icon(IconsaxPlusLinear.location),
onChanged: (value) => setState(() {}), onChanged: (value) => setState(() {}),
margin: margin: const EdgeInsets.only(
const EdgeInsets.only(left: 10, right: 10, top: 10), left: 10,
right: 10,
top: 10,
),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'validateValue'.tr; return 'validateValue'.tr;
@ -235,8 +249,11 @@ class _CreatePlaceState extends State<CreatePlace>
type: TextInputType.name, type: TextInputType.name,
icon: const Icon(IconsaxPlusLinear.building_3), icon: const Icon(IconsaxPlusLinear.building_3),
onChanged: (value) => setState(() {}), onChanged: (value) => setState(() {}),
margin: margin: const EdgeInsets.only(
const EdgeInsets.only(left: 10, right: 10, top: 10), left: 10,
right: 10,
top: 10,
),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'validateName'.tr; return 'validateName'.tr;
@ -251,8 +268,11 @@ class _CreatePlaceState extends State<CreatePlace>
type: TextInputType.streetAddress, type: TextInputType.streetAddress,
icon: const Icon(IconsaxPlusLinear.global), icon: const Icon(IconsaxPlusLinear.global),
onChanged: (value) => setState(() {}), onChanged: (value) => setState(() {}),
margin: margin: const EdgeInsets.only(
const EdgeInsets.only(left: 10, right: 10, top: 10), left: 10,
right: 10,
top: 10,
),
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return 'validateName'.tr; return 'validateName'.tr;
@ -262,7 +282,9 @@ class _CreatePlaceState extends State<CreatePlace>
), ),
Padding( Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 10), horizontal: 10,
vertical: 10,
),
child: SizeTransition( child: SizeTransition(
sizeFactor: _animation, sizeFactor: _animation,
axisAlignment: -1.0, axisAlignment: -1.0,
@ -291,10 +313,7 @@ class _CreatePlaceState extends State<CreatePlace>
], ],
), ),
), ),
if (isLoading) if (isLoading) const Center(child: CircularProgressIndicator()),
const Center(
child: CircularProgressIndicator(),
),
], ],
), ),
), ),

View file

@ -54,10 +54,15 @@ class _PlaceCardState extends State<PlaceCard> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text( Text(
statusData.getDegree(widget.degree[weatherController statusData.getDegree(
.getTime(widget.time, widget.timezone)] widget
.degree[weatherController.getTime(
widget.time,
widget.timezone,
)]
.round() .round()
.toInt()), .toInt(),
),
style: context.textTheme.titleLarge?.copyWith( style: context.textTheme.titleLarge?.copyWith(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -65,8 +70,12 @@ class _PlaceCardState extends State<PlaceCard> {
), ),
const Gap(7), const Gap(7),
Text( Text(
statusWeather.getText(widget.weather[weatherController statusWeather.getText(
.getTime(widget.time, widget.timezone)]), widget.weather[weatherController.getTime(
widget.time,
widget.timezone,
)],
),
style: context.textTheme.titleMedium?.copyWith( style: context.textTheme.titleMedium?.copyWith(
color: Colors.grey, color: Colors.grey,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -107,14 +116,23 @@ class _PlaceCardState extends State<PlaceCard> {
const Gap(5), const Gap(5),
Image.asset( Image.asset(
statusWeather.getImageNow( statusWeather.getImageNow(
widget.weather[ widget.weather[weatherController.getTime(
weatherController.getTime(widget.time, widget.timezone)], widget.time,
widget.time[ widget.timezone,
weatherController.getTime(widget.time, widget.timezone)], )],
widget.time[weatherController.getTime(
widget.time,
widget.timezone,
)],
widget.timeDay[weatherController.getDay( widget.timeDay[weatherController.getDay(
widget.timeDaily, widget.timezone)], widget.timeDaily,
widget.timezone,
)],
widget.timeNight[weatherController.getDay( widget.timeNight[weatherController.getDay(
widget.timeDaily, widget.timezone)]), widget.timeDaily,
widget.timezone,
)],
),
scale: 6.5, scale: 6.5,
), ),
], ],

View file

@ -6,10 +6,7 @@ import 'package:rain/app/ui/places/view/place_info.dart';
import 'package:rain/app/ui/places/widgets/place_card.dart'; import 'package:rain/app/ui/places/widgets/place_card.dart';
class PlaceCardList extends StatefulWidget { class PlaceCardList extends StatefulWidget {
const PlaceCardList({ const PlaceCardList({super.key, required this.searchCity});
super.key,
required this.searchCity,
});
final String searchCity; final String searchCity;
@override @override
@ -24,15 +21,21 @@ class _PlaceCardListState extends State<PlaceCardList> {
final textTheme = context.textTheme; final textTheme = context.textTheme;
final titleMedium = textTheme.titleMedium; final titleMedium = textTheme.titleMedium;
var weatherCards = weatherController.weatherCards var weatherCards =
.where((weatherCard) => (widget.searchCity.isEmpty || weatherController.weatherCards
weatherCard.city!.toLowerCase().contains(widget.searchCity))) .where(
(weatherCard) =>
(widget.searchCity.isEmpty ||
weatherCard.city!.toLowerCase().contains(
widget.searchCity,
)),
)
.toList() .toList()
.obs; .obs;
return ReorderableListView( return ReorderableListView(
onReorder: (oldIndex, newIndex) => onReorder:
weatherController.reorder(oldIndex, newIndex), (oldIndex, newIndex) => weatherController.reorder(oldIndex, newIndex),
children: [ children: [
...weatherCards.map( ...weatherCards.map(
(weatherCardList) => Dismissible( (weatherCardList) => Dismissible(
@ -42,10 +45,7 @@ class _PlaceCardListState extends State<PlaceCardList> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: const Padding( child: const Padding(
padding: EdgeInsets.only(right: 15), padding: EdgeInsets.only(right: 15),
child: Icon( child: Icon(IconsaxPlusLinear.trash_square, color: Colors.red),
IconsaxPlusLinear.trash_square,
color: Colors.red,
),
), ),
), ),
confirmDismiss: (DismissDirection direction) async { confirmDismiss: (DismissDirection direction) async {
@ -75,9 +75,7 @@ class _PlaceCardListState extends State<PlaceCardList> {
onPressed: () => Get.back(result: true), onPressed: () => Get.back(result: true),
child: Text( child: Text(
'delete'.tr, 'delete'.tr,
style: titleMedium?.copyWith( style: titleMedium?.copyWith(color: Colors.red),
color: Colors.red,
),
), ),
), ),
], ],
@ -89,10 +87,9 @@ class _PlaceCardListState extends State<PlaceCardList> {
await weatherController.deleteCardWeather(weatherCardList); await weatherController.deleteCardWeather(weatherCardList);
}, },
child: GestureDetector( child: GestureDetector(
onTap: () => Get.to( onTap:
() => PlaceInfo( () => Get.to(
weatherCard: weatherCardList, () => PlaceInfo(weatherCard: weatherCardList),
),
transition: Transition.downToUp, transition: Transition.downToUp,
), ),
child: PlaceCard( child: PlaceCard(

View file

@ -41,9 +41,7 @@ class SettingCard extends StatelessWidget {
elevation: elevation ?? 1, elevation: elevation ?? 1,
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 5),
child: ListTile( child: ListTile(
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
borderRadius: BorderRadius.circular(15),
),
onTap: onPressed, onTap: onPressed,
leading: icon, leading: icon,
title: Text( title: Text(
@ -51,13 +49,11 @@ class SettingCard extends StatelessWidget {
style: context.textTheme.titleMedium, style: context.textTheme.titleMedium,
overflow: TextOverflow.visible, overflow: TextOverflow.visible,
), ),
trailing: switcher trailing:
switcher
? Transform.scale( ? Transform.scale(
scale: 0.8, scale: 0.8,
child: Switch( child: Switch(value: value!, onChanged: onChange),
value: value!,
onChanged: onChange,
),
) )
: dropdown : dropdown
? DropdownButton<String>( ? DropdownButton<String>(
@ -70,8 +66,10 @@ class SettingCard extends StatelessWidget {
borderRadius: const BorderRadius.all(Radius.circular(15)), borderRadius: const BorderRadius.all(Radius.circular(15)),
underline: Container(), underline: Container(),
value: dropdownName, value: dropdownName,
items: dropdownList! items:
.map<DropdownMenuItem<String>>((String value) { dropdownList!.map<DropdownMenuItem<String>>((
String value,
) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: value, value: value,
child: Text(value), child: Text(value),
@ -84,17 +82,11 @@ class SettingCard extends StatelessWidget {
? Wrap( ? Wrap(
children: [ children: [
infoWidget!, infoWidget!,
const Icon( const Icon(IconsaxPlusLinear.arrow_right_3, size: 18),
IconsaxPlusLinear.arrow_right_3,
size: 18,
),
], ],
) )
: infoWidget! : infoWidget!
: const Icon( : const Icon(IconsaxPlusLinear.arrow_right_3, size: 18),
IconsaxPlusLinear.arrow_right_3,
size: 18,
),
), ),
); );
} }

View file

@ -19,13 +19,11 @@ class MyTextButton extends StatelessWidget {
style: ButtonStyle( style: ButtonStyle(
shadowColor: const WidgetStatePropertyAll(Colors.transparent), shadowColor: const WidgetStatePropertyAll(Colors.transparent),
backgroundColor: WidgetStatePropertyAll( backgroundColor: WidgetStatePropertyAll(
context.theme.colorScheme.secondaryContainer.withAlpha(80)), context.theme.colorScheme.secondaryContainer.withAlpha(80),
),
), ),
onPressed: onPressed, onPressed: onPressed,
child: Text( child: Text(buttonName, style: context.textTheme.titleMedium),
buttonName,
style: context.textTheme.titleMedium,
),
), ),
); );
} }

View file

@ -3,11 +3,7 @@ import 'package:get/get.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
class MyShimmer extends StatelessWidget { class MyShimmer extends StatelessWidget {
const MyShimmer({ const MyShimmer({super.key, required this.hight, this.edgeInsetsMargin});
super.key,
required this.hight,
this.edgeInsetsMargin,
});
final double hight; final double hight;
final EdgeInsets? edgeInsetsMargin; final EdgeInsets? edgeInsetsMargin;
@ -16,12 +12,7 @@ class MyShimmer extends StatelessWidget {
return Shimmer.fromColors( return Shimmer.fromColors(
baseColor: context.theme.cardColor, baseColor: context.theme.cardColor,
highlightColor: context.theme.primaryColor, highlightColor: context.theme.primaryColor,
child: Card( child: Card(margin: edgeInsetsMargin, child: SizedBox(height: hight)),
margin: edgeInsetsMargin,
child: SizedBox(
height: hight,
),
),
); );
} }
} }

View file

@ -50,8 +50,9 @@ class _DailyCardState extends State<DailyCard> {
), ),
const Gap(5), const Gap(5),
Text( Text(
DateFormat.MMMMEEEEd(locale.languageCode) DateFormat.MMMMEEEEd(
.format(widget.timeDaily), locale.languageCode,
).format(widget.timeDaily),
style: context.textTheme.titleMedium?.copyWith( style: context.textTheme.titleMedium?.copyWith(
color: Colors.grey, color: Colors.grey,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,

View file

@ -64,16 +64,14 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
onPressed: () { onPressed: () {
Get.back(); Get.back();
}, },
icon: const Icon( icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
IconsaxPlusLinear.arrow_left_3,
size: 20,
),
splashColor: Colors.transparent, splashColor: Colors.transparent,
highlightColor: Colors.transparent, highlightColor: Colors.transparent,
), ),
title: Text( title: Text(
DateFormat.MMMMEEEEd(locale.languageCode) DateFormat.MMMMEEEEd(
.format(timeDaily[pageIndex]), locale.languageCode,
).format(timeDaily[pageIndex]),
style: textTheme.titleMedium?.copyWith( style: textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
fontSize: 18, fontSize: 18,
@ -121,10 +119,11 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
Now( Now(
weather: weather:
weatherData.weathercode![startIndex + hourOfDay], weatherData.weathercode![startIndex + hourOfDay],
degree: weatherData degree:
.temperature2M![startIndex + hourOfDay], weatherData.temperature2M![startIndex + hourOfDay],
feels: weatherData feels:
.apparentTemperature![startIndex + hourOfDay]!, weatherData.apparentTemperature![startIndex +
hourOfDay]!,
time: weatherData.time![startIndex + hourOfDay], time: weatherData.time![startIndex + hourOfDay],
timeDay: sunrise, timeDay: sunrise,
timeNight: sunset, timeNight: sunset,
@ -135,12 +134,16 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 5), horizontal: 10,
vertical: 5,
),
child: SizedBox( child: SizedBox(
height: 135, height: 135,
child: ScrollablePositionedList.separated( child: ScrollablePositionedList.separated(
separatorBuilder: separatorBuilder: (
(BuildContext context, int index) { BuildContext context,
int index,
) {
return const VerticalDivider( return const VerticalDivider(
width: 10, width: 10,
indent: 40, indent: 40,
@ -158,14 +161,18 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
}, },
child: Container( child: Container(
margin: const EdgeInsets.symmetric( margin: const EdgeInsets.symmetric(
vertical: 5), vertical: 5,
),
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 20, horizontal: 20,
vertical: 5, vertical: 5,
), ),
decoration: BoxDecoration( decoration: BoxDecoration(
color: i == hourOfDay color:
? context.theme.colorScheme i == hourOfDay
? context
.theme
.colorScheme
.secondaryContainer .secondaryContainer
: Colors.transparent, : Colors.transparent,
borderRadius: const BorderRadius.all( borderRadius: const BorderRadius.all(
@ -174,9 +181,10 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
), ),
child: Hourly( child: Hourly(
time: weatherData.time![hourlyIndex], time: weatherData.time![hourlyIndex],
weather: weatherData weather:
.weathercode![hourlyIndex], weatherData.weathercode![hourlyIndex],
degree: weatherData degree:
weatherData
.temperature2M![hourlyIndex], .temperature2M![hourlyIndex],
timeDay: sunrise, timeDay: sunrise,
timeNight: sunset, timeNight: sunset,
@ -188,27 +196,28 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
), ),
), ),
), ),
SunsetSunrise( SunsetSunrise(timeSunrise: sunrise, timeSunset: sunset),
timeSunrise: sunrise,
timeSunset: sunset,
),
DescContainer( DescContainer(
humidity: weatherData humidity:
.relativehumidity2M?[startIndex + hourOfDay], weatherData.relativehumidity2M?[startIndex +
wind: hourOfDay],
weatherData.windspeed10M?[startIndex + hourOfDay], wind: weatherData.windspeed10M?[startIndex + hourOfDay],
visibility: visibility:
weatherData.visibility?[startIndex + hourOfDay], weatherData.visibility?[startIndex + hourOfDay],
feels: weatherData feels:
.apparentTemperature?[startIndex + hourOfDay], weatherData.apparentTemperature?[startIndex +
evaporation: weatherData hourOfDay],
.evapotranspiration?[startIndex + hourOfDay], evaporation:
precipitation: weatherData weatherData.evapotranspiration?[startIndex +
.precipitation?[startIndex + hourOfDay], hourOfDay],
direction: weatherData precipitation:
.winddirection10M?[startIndex + hourOfDay], weatherData.precipitation?[startIndex + hourOfDay],
pressure: weatherData direction:
.surfacePressure?[startIndex + hourOfDay], weatherData.winddirection10M?[startIndex +
hourOfDay],
pressure:
weatherData.surfacePressure?[startIndex +
hourOfDay],
rain: weatherData.rain?[startIndex + hourOfDay], rain: weatherData.rain?[startIndex + hourOfDay],
cloudcover: cloudcover:
weatherData.cloudcover?[startIndex + hourOfDay], weatherData.cloudcover?[startIndex + hourOfDay],
@ -218,10 +227,11 @@ class _DailyCardInfoState extends State<DailyCardInfo> {
dewpoint2M: dewpoint2M:
weatherData.dewpoint2M?[startIndex + hourOfDay], weatherData.dewpoint2M?[startIndex + hourOfDay],
precipitationProbability: precipitationProbability:
weatherData.precipitationProbability?[ weatherData.precipitationProbability?[startIndex +
startIndex + hourOfDay], hourOfDay],
shortwaveRadiation: weatherData shortwaveRadiation:
.shortwaveRadiation?[startIndex + hourOfDay], weatherData.shortwaveRadiation?[startIndex +
hourOfDay],
initiallyExpanded: true, initiallyExpanded: true,
title: 'hourlyVariables'.tr, title: 'hourlyVariables'.tr,
), ),

View file

@ -6,10 +6,7 @@ import 'package:rain/app/ui/widgets/weather/daily/daily_card_info.dart';
import 'package:rain/app/ui/widgets/weather/daily/daily_card.dart'; import 'package:rain/app/ui/widgets/weather/daily/daily_card.dart';
class DailyCardList extends StatefulWidget { class DailyCardList extends StatefulWidget {
const DailyCardList({ const DailyCardList({super.key, required this.weatherData});
super.key,
required this.weatherData,
});
final WeatherCard weatherData; final WeatherCard weatherData;
@override @override
@ -31,10 +28,7 @@ class _DailyCardListState extends State<DailyCardList> {
onPressed: () { onPressed: () {
Get.back(); Get.back();
}, },
icon: const Icon( icon: const Icon(IconsaxPlusLinear.arrow_left_3, size: 20),
IconsaxPlusLinear.arrow_left_3,
size: 20,
),
splashColor: transparent, splashColor: transparent,
highlightColor: transparent, highlightColor: transparent,
), ),
@ -49,12 +43,12 @@ class _DailyCardListState extends State<DailyCardList> {
body: SafeArea( body: SafeArea(
child: ListView.builder( child: ListView.builder(
itemCount: timeDaily.length, itemCount: timeDaily.length,
itemBuilder: (context, index) => GestureDetector( itemBuilder:
onTap: () => Get.to( (context, index) => GestureDetector(
() => DailyCardInfo( onTap:
weatherData: weatherData, () => Get.to(
index: index, () =>
), DailyCardInfo(weatherData: weatherData, index: index),
transition: Transition.downToUp, transition: Transition.downToUp,
), ),
child: DailyCard( child: DailyCard(

View file

@ -29,9 +29,7 @@ class _DailyContainerState extends State<DailyContainer> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final splashColor = context.theme.colorScheme.primary.withOpacity(0.4); final splashColor = context.theme.colorScheme.primary.withOpacity(0.4);
const inkWellBorderRadius = BorderRadius.all( const inkWellBorderRadius = BorderRadius.all(Radius.circular(16));
Radius.circular(16),
);
final weatherData = widget.weatherData; final weatherData = widget.weatherData;
final weatherCodeDaily = weatherData.weathercodeDaily ?? []; final weatherCodeDaily = weatherData.weathercodeDaily ?? [];
@ -52,7 +50,8 @@ class _DailyContainerState extends State<DailyContainer> {
return InkWell( return InkWell(
splashColor: splashColor, splashColor: splashColor,
borderRadius: inkWellBorderRadius, borderRadius: inkWellBorderRadius,
onTap: () => Get.to( onTap:
() => Get.to(
() => DailyCardInfo( () => DailyCardInfo(
weatherData: weatherData, weatherData: weatherData,
index: index, index: index,
@ -66,8 +65,9 @@ class _DailyContainerState extends State<DailyContainer> {
children: [ children: [
Expanded( Expanded(
child: Text( child: Text(
DateFormat.EEEE(locale.languageCode) DateFormat.EEEE(
.format((weatherData.timeDaily ?? [])[index]), locale.languageCode,
).format((weatherData.timeDaily ?? [])[index]),
style: labelLarge, style: labelLarge,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -77,15 +77,17 @@ class _DailyContainerState extends State<DailyContainer> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Image.asset( Image.asset(
statusWeather statusWeather.getImage7Day(
.getImage7Day(weatherCodeDaily[index]), weatherCodeDaily[index],
),
scale: 3, scale: 3,
), ),
const Gap(5), const Gap(5),
Expanded( Expanded(
child: Text( child: Text(
statusWeather statusWeather.getText(
.getText(weatherCodeDaily[index]), weatherCodeDaily[index],
),
style: labelLarge, style: labelLarge,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -100,17 +102,16 @@ class _DailyContainerState extends State<DailyContainer> {
Text( Text(
statusData.getDegree( statusData.getDegree(
(weatherData.temperature2MMin ?? [])[index] (weatherData.temperature2MMin ?? [])[index]
?.round()), ?.round(),
style: labelLarge, ),
),
Text(
' / ',
style: labelLarge, style: labelLarge,
), ),
Text(' / ', style: labelLarge),
Text( Text(
statusData.getDegree( statusData.getDegree(
(weatherData.temperature2MMax ?? [])[index] (weatherData.temperature2MMax ?? [])[index]
?.round()), ?.round(),
),
style: labelLarge, style: labelLarge,
), ),
], ],

View file

@ -35,14 +35,12 @@ class _DescWeatherState extends State<DescWeather> {
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Image.asset( Image.asset(widget.imageName, scale: 20),
widget.imageName,
scale: 20,
),
const Gap(5), const Gap(5),
Text( Text(
widget.value, widget.value,
style: textTheme.labelLarge, style: textTheme.labelLarge,
overflow: TextOverflow.ellipsis,
), ),
Expanded( Expanded(
child: Text( child: Text(

View file

@ -107,10 +107,7 @@ class _DescContainerState extends State<DescContainer> {
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
child: ExpansionTile( child: ExpansionTile(
shape: const Border(), shape: const Border(),
title: Text( title: Text(title, style: context.textTheme.labelLarge),
title,
style: context.textTheme.labelLarge,
),
initiallyExpanded: initiallyExpanded, initiallyExpanded: initiallyExpanded,
children: [ children: [
Padding( Padding(
@ -123,16 +120,18 @@ class _DescContainerState extends State<DescContainer> {
? Container() ? Container()
: DescWeather( : DescWeather(
imageName: 'assets/images/cold.png', imageName: 'assets/images/cold.png',
value: statusData value: statusData.getDegree(
.getDegree(apparentTemperatureMin.round()), apparentTemperatureMin.round(),
),
desc: 'apparentTemperatureMin'.tr, desc: 'apparentTemperatureMin'.tr,
), ),
apparentTemperatureMax == null apparentTemperatureMax == null
? Container() ? Container()
: DescWeather( : DescWeather(
imageName: 'assets/images/hot.png', imageName: 'assets/images/hot.png',
value: statusData value: statusData.getDegree(
.getDegree(apparentTemperatureMax.round()), apparentTemperatureMax.round(),
),
desc: 'apparentTemperatureMax'.tr, desc: 'apparentTemperatureMax'.tr,
), ),
uvIndexMax == null uvIndexMax == null
@ -168,8 +167,7 @@ class _DescContainerState extends State<DescContainer> {
precipitationProbabilityMax == null precipitationProbabilityMax == null
? Container() ? Container()
: DescWeather( : DescWeather(
imageName: imageName: 'assets/images/precipitation_probability.png',
'assets/images/precipitation_probability.png',
value: '$precipitationProbabilityMax%', value: '$precipitationProbabilityMax%',
desc: 'precipitationProbability'.tr, desc: 'precipitationProbability'.tr,
), ),
@ -254,8 +252,7 @@ class _DescContainerState extends State<DescContainer> {
precipitationProbability == null precipitationProbability == null
? Container() ? Container()
: DescWeather( : DescWeather(
imageName: imageName: 'assets/images/precipitation_probability.png',
'assets/images/precipitation_probability.png',
value: '$precipitationProbability%', value: '$precipitationProbability%',
desc: 'precipitationProbability'.tr, desc: 'precipitationProbability'.tr,
), ),

View file

@ -37,16 +37,13 @@ class _HourlyState extends State<Hourly> {
children: [ children: [
Column( Column(
children: [ children: [
Text(statusData.getTimeFormat(time), style: textTheme.labelLarge),
Text( Text(
statusData.getTimeFormat(time), DateFormat(
style: textTheme.labelLarge, 'E',
), locale.languageCode,
Text( ).format(DateTime.tryParse(time)!),
DateFormat('E', locale.languageCode) style: textTheme.labelLarge?.copyWith(color: Colors.grey),
.format(DateTime.tryParse(time)!),
style: textTheme.labelLarge?.copyWith(
color: Colors.grey,
),
), ),
], ],
), ),
@ -61,9 +58,7 @@ class _HourlyState extends State<Hourly> {
), ),
Text( Text(
statusData.getDegree(widget.degree.round()), statusData.getDegree(widget.degree.round()),
style: textTheme.titleMedium?.copyWith( style: textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w600),
fontWeight: FontWeight.w600,
),
), ),
], ],
); );

View file

@ -45,8 +45,14 @@ class _NowState extends State<Now> {
children: [ children: [
const Gap(15), const Gap(15),
Image( Image(
image: AssetImage(statusWeather.getImageNow(widget.weather, image: AssetImage(
widget.time, widget.timeDay, widget.timeNight)), statusWeather.getImageNow(
widget.weather,
widget.time,
widget.timeDay,
widget.timeNight,
),
),
fit: BoxFit.fill, fit: BoxFit.fill,
height: 200, height: 200,
), ),
@ -55,12 +61,8 @@ class _NowState extends State<Now> {
style: context.textTheme.displayLarge?.copyWith( style: context.textTheme.displayLarge?.copyWith(
fontSize: 90, fontSize: 90,
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
shadows: const [ shadows: const [Shadow(blurRadius: 15, offset: Offset(5, 5))],
Shadow( ),
blurRadius: 15,
offset: Offset(5, 5),
)
]),
), ),
Text( Text(
statusWeather.getText(widget.weather), statusWeather.getText(widget.weather),
@ -68,9 +70,9 @@ class _NowState extends State<Now> {
), ),
const Gap(5), const Gap(5),
Text( Text(
DateFormat.MMMMEEEEd(locale.languageCode).format( DateFormat.MMMMEEEEd(
DateTime.parse(widget.time), locale.languageCode,
), ).format(DateTime.parse(widget.time)),
style: context.textTheme.labelLarge?.copyWith( style: context.textTheme.labelLarge?.copyWith(
color: Colors.grey, color: Colors.grey,
), ),
@ -82,7 +84,11 @@ class _NowState extends State<Now> {
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 18, bottom: 18, left: 25, right: 15), top: 18,
bottom: 18,
left: 25,
right: 15,
),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -91,9 +97,9 @@ class _NowState extends State<Now> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
DateFormat.MMMMEEEEd(locale.languageCode).format( DateFormat.MMMMEEEEd(
DateTime.parse(widget.time), locale.languageCode,
), ).format(DateTime.parse(widget.time)),
style: context.textTheme.labelLarge?.copyWith( style: context.textTheme.labelLarge?.copyWith(
color: Colors.grey, color: Colors.grey,
), ),
@ -101,24 +107,26 @@ class _NowState extends State<Now> {
const Gap(5), const Gap(5),
Text( Text(
statusWeather.getText(widget.weather), statusWeather.getText(widget.weather),
style: context.textTheme.titleLarge style: context.textTheme.titleLarge?.copyWith(
?.copyWith(fontSize: 20), fontSize: 20,
),
), ),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text('feels'.tr, Text('feels'.tr, style: context.textTheme.bodyMedium),
style: context.textTheme.bodyMedium),
Text('', style: context.textTheme.bodyMedium), Text('', style: context.textTheme.bodyMedium),
Text(statusData.getDegree(widget.feels.round()), Text(
style: context.textTheme.bodyMedium), statusData.getDegree(widget.feels.round()),
style: context.textTheme.bodyMedium,
),
], ],
), ),
const Gap(30), const Gap(30),
Text( Text(
statusData.getDegree(roundDegree statusData.getDegree(
? widget.degree.round() roundDegree ? widget.degree.round() : widget.degree,
: widget.degree), ),
style: context.textTheme.displayMedium?.copyWith( style: context.textTheme.displayMedium?.copyWith(
fontWeight: FontWeight.w800, fontWeight: FontWeight.w800,
), ),
@ -127,19 +135,29 @@ class _NowState extends State<Now> {
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text(statusData.getDegree((widget.tempMin.round())), Text(
style: context.textTheme.labelLarge), statusData.getDegree((widget.tempMin.round())),
style: context.textTheme.labelLarge,
),
Text(' / ', style: context.textTheme.labelLarge), Text(' / ', style: context.textTheme.labelLarge),
Text(statusData.getDegree((widget.tempMax.round())), Text(
style: context.textTheme.labelLarge), statusData.getDegree((widget.tempMax.round())),
style: context.textTheme.labelLarge,
),
], ],
), ),
], ],
), ),
), ),
Image( Image(
image: AssetImage(statusWeather.getImageNow(widget.weather, image: AssetImage(
widget.time, widget.timeDay, widget.timeNight)), statusWeather.getImageNow(
widget.weather,
widget.time,
widget.timeDay,
widget.timeNight,
),
),
fit: BoxFit.fill, fit: BoxFit.fill,
height: 140, height: 140,
), ),

View file

@ -63,14 +63,17 @@ class StatusData {
String getTimeFormat(String time) { String getTimeFormat(String time) {
switch (settings.timeformat) { switch (settings.timeformat) {
case '12': case '12':
return DateFormat.jm(locale.languageCode) return DateFormat.jm(
.format(DateTime.tryParse(time)!); locale.languageCode,
).format(DateTime.tryParse(time)!);
case '24': case '24':
return DateFormat.Hm(locale.languageCode) return DateFormat.Hm(
.format(DateTime.tryParse(time)!); locale.languageCode,
).format(DateTime.tryParse(time)!);
default: default:
return DateFormat.Hm(locale.languageCode) return DateFormat.Hm(
.format(DateTime.tryParse(time)!); locale.languageCode,
).format(DateTime.tryParse(time)!);
} }
} }

View file

@ -4,15 +4,29 @@ const assetImageRoot = 'assets/images/';
class StatusWeather { class StatusWeather {
String getImageNow( String getImageNow(
int weather, String time, String timeDay, String timeNight) { int weather,
String time,
String timeDay,
String timeNight,
) {
final currentTime = DateTime.parse(time); final currentTime = DateTime.parse(time);
final day = DateTime.parse(timeDay); final day = DateTime.parse(timeDay);
final night = DateTime.parse(timeNight); final night = DateTime.parse(timeNight);
final dayTime = final dayTime = DateTime(
DateTime(day.year, day.month, day.day, day.hour, day.minute); day.year,
final nightTime = day.month,
DateTime(night.year, night.month, night.day, night.hour, night.minute); day.day,
day.hour,
day.minute,
);
final nightTime = DateTime(
night.year,
night.month,
night.day,
night.hour,
night.minute,
);
switch (weather) { switch (weather) {
case 0: case 0:
@ -112,15 +126,29 @@ class StatusWeather {
} }
String getImageToday( String getImageToday(
int weather, String time, String timeDay, String timeNight) { int weather,
String time,
String timeDay,
String timeNight,
) {
final currentTime = DateTime.parse(time); final currentTime = DateTime.parse(time);
final day = DateTime.parse(timeDay); final day = DateTime.parse(timeDay);
final night = DateTime.parse(timeNight); final night = DateTime.parse(timeNight);
final dayTime = final dayTime = DateTime(
DateTime(day.year, day.month, day.day, day.hour, day.minute); day.year,
final nightTime = day.month,
DateTime(night.year, night.month, night.day, night.hour, night.minute); day.day,
day.hour,
day.minute,
);
final nightTime = DateTime(
night.year,
night.month,
night.day,
night.hour,
night.minute,
);
switch (weather) { switch (weather) {
case 0: case 0:
@ -274,15 +302,29 @@ class StatusWeather {
} }
String getImageNotification( String getImageNotification(
int weather, String time, String timeDay, String timeNight) { int weather,
String time,
String timeDay,
String timeNight,
) {
final currentTime = DateTime.parse(time); final currentTime = DateTime.parse(time);
final day = DateTime.parse(timeDay); final day = DateTime.parse(timeDay);
final night = DateTime.parse(timeNight); final night = DateTime.parse(timeNight);
final dayTime = final dayTime = DateTime(
DateTime(day.year, day.month, day.day, day.hour, day.minute); day.year,
final nightTime = day.month,
DateTime(night.year, night.month, night.day, night.hour, night.minute); day.day,
day.hour,
day.minute,
);
final nightTime = DateTime(
night.year,
night.month,
night.day,
night.hour,
night.minute,
);
switch (weather) { switch (weather) {
case 0: case 0:

View file

@ -55,10 +55,7 @@ class _SunsetSunriseState extends State<SunsetSunrise> {
), ),
const Gap(5), const Gap(5),
Flexible( Flexible(
child: Image.asset( child: Image.asset('assets/images/sunrise.png', scale: 10),
'assets/images/sunrise.png',
scale: 10,
),
), ),
], ],
), ),
@ -86,10 +83,7 @@ class _SunsetSunriseState extends State<SunsetSunrise> {
), ),
const Gap(5), const Gap(5),
Flexible( Flexible(
child: Image.asset( child: Image.asset('assets/images/sunset.png', scale: 10),
'assets/images/sunset.png',
scale: 10,
),
), ),
], ],
), ),

View file

@ -10,9 +10,17 @@ extension HexColor on Color {
} }
/// Prefixes a hash sign if [leadingHashSign] is set to `true` (default is `true`). /// Prefixes a hash sign if [leadingHashSign] is set to `true` (default is `true`).
String toHex({bool leadingHashSign = true}) => '${leadingHashSign ? '#' : ''}' String toHex({bool leadingHashSign = true}) {
'${alpha.toRadixString(16).padLeft(2, '0')}' final argb = toARGB32(); // Get 32-bit integer representation
'${red.toRadixString(16).padLeft(2, '0')}' final a = (argb >> 24) & 0xFF;
'${green.toRadixString(16).padLeft(2, '0')}' final r = (argb >> 16) & 0xFF;
'${blue.toRadixString(16).padLeft(2, '0')}'; final g = (argb >> 8) & 0xFF;
final b = argb & 0xFF;
return '${leadingHashSign ? '#' : ''}'
'${a.toRadixString(16).padLeft(2, '0')}'
'${r.toRadixString(16).padLeft(2, '0')}'
'${g.toRadixString(16).padLeft(2, '0')}'
'${b.toRadixString(16).padLeft(2, '0')}';
}
} }

View file

@ -23,8 +23,9 @@ class NotificationShow {
enableVibration: false, enableVibration: false,
largeIcon: FilePathAndroidBitmap(imagePath), largeIcon: FilePathAndroidBitmap(imagePath),
); );
NotificationDetails notificationDetails = NotificationDetails notificationDetails = NotificationDetails(
NotificationDetails(android: androidNotificationDetails); android: androidNotificationDetails,
);
var scheduledTime = tz.TZDateTime.from(date, tz.local); var scheduledTime = tz.TZDateTime.from(date, tz.local);
flutterLocalNotificationsPlugin.zonedSchedule( flutterLocalNotificationsPlugin.zonedSchedule(

View file

@ -7,7 +7,8 @@ void showSnackBar({required String content, Function? onPressed}) {
globalKey.currentState?.showSnackBar( globalKey.currentState?.showSnackBar(
SnackBar( SnackBar(
content: Text(content), content: Text(content),
action: onPressed != null action:
onPressed != null
? SnackBarAction( ? SnackBarAction(
label: 'settings'.tr, label: 'settings'.tr,
onPressed: () { onPressed: () {

View file

@ -33,7 +33,7 @@ final ValueNotifier<Future<bool>> isOnline = ValueNotifier(
InternetConnection().hasInternetAccess, InternetConnection().hasInternetAccess,
); );
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin(); FlutterLocalNotificationsPlugin();
bool amoledTheme = false; bool amoledTheme = false;
@ -47,36 +47,36 @@ String timeEnd = '21:00';
String widgetBackgroundColor = ''; String widgetBackgroundColor = '';
String widgetTextColor = ''; String widgetTextColor = '';
final List appLanguages = [
{'name': 'বাংলা', 'locale': const Locale('bn', 'IN')},
{'name': 'Čeština', 'locale': const Locale('cs', 'CZ')},
{'name': 'Dansk', 'locale': const Locale('da', 'DK')},
{'name': 'Deutsch', 'locale': const Locale('de', 'DE')},
{'name': 'English', 'locale': const Locale('en', 'US')},
{'name': 'Español', 'locale': const Locale('es', 'ES')},
{'name': 'Français', 'locale': const Locale('fr', 'FR')},
// {'name': 'Gaeilge', 'locale': const Locale('ga', 'IE')},
{'name': 'हिन्दी', 'locale': const Locale('hi', 'IN')},
{'name': 'Magyar', 'locale': const Locale('hu', 'HU')},
{'name': 'Italiano', 'locale': const Locale('it', 'IT')},
{'name': '한국어', 'locale': const Locale('ko', 'KR')},
{'name': 'فارسی', 'locale': const Locale('fa', 'IR')},
{'name': 'ქართული', 'locale': const Locale('ka', 'GE')},
{'name': 'Nederlands', 'locale': const Locale('nl', 'NL')},
{'name': 'Polski', 'locale': const Locale('pl', 'PL')},
{'name': 'Português (Brasil)', 'locale': const Locale('pt', 'BR')},
{'name': 'Română', 'locale': const Locale('ro', 'RO')},
{'name': 'Русский', 'locale': const Locale('ru', 'RU')},
{'name': 'Slovenčina', 'locale': const Locale('sk', 'SK')},
{'name': 'Türkçe', 'locale': const Locale('tr', 'TR')},
{'name': 'اردو', 'locale': const Locale('ur', 'PK')},
{'name': '中文(简体)', 'locale': const Locale('zh', 'CN')},
{'name': '中文(繁體)', 'locale': const Locale('zh', 'TW')},
];
const String appGroupId = 'DARK NIGHT'; const String appGroupId = 'DARK NIGHT';
const String androidWidgetName = 'OreoWidget'; const String androidWidgetName = 'OreoWidget';
const List<Map<String, dynamic>> appLanguages = [
{'name': 'বাংলা', 'locale': Locale('bn', 'IN')},
{'name': 'Čeština', 'locale': Locale('cs', 'CZ')},
{'name': 'Dansk', 'locale': Locale('da', 'DK')},
{'name': 'Deutsch', 'locale': Locale('de', 'DE')},
{'name': 'English', 'locale': Locale('en', 'US')},
{'name': 'Español', 'locale': Locale('es', 'ES')},
{'name': 'Français', 'locale': Locale('fr', 'FR')},
// {'name': 'Gaeilge', 'locale': Locale('ga', 'IE')},
{'name': 'हिन्दी', 'locale': Locale('hi', 'IN')},
{'name': 'Magyar', 'locale': Locale('hu', 'HU')},
{'name': 'Italiano', 'locale': Locale('it', 'IT')},
{'name': '한국어', 'locale': Locale('ko', 'KR')},
{'name': 'فارسی', 'locale': Locale('fa', 'IR')},
{'name': 'ქართული', 'locale': Locale('ka', 'GE')},
{'name': 'Nederlands', 'locale': Locale('nl', 'NL')},
{'name': 'Polski', 'locale': Locale('pl', 'PL')},
{'name': 'Português (Brasil)', 'locale': Locale('pt', 'BR')},
{'name': 'Română', 'locale': Locale('ro', 'RO')},
{'name': 'Русский', 'locale': Locale('ru', 'RU')},
{'name': 'Slovenčina', 'locale': Locale('sk', 'SK')},
{'name': 'Türkçe', 'locale': Locale('tr', 'TR')},
{'name': 'اردو', 'locale': Locale('ur', 'PK')},
{'name': '中文(简体)', 'locale': Locale('zh', 'CN')},
{'name': '中文(繁體)', 'locale': Locale('zh', 'TW')},
];
@pragma('vm:entry-point') @pragma('vm:entry-point')
void callbackDispatcher() { void callbackDispatcher() {
Workmanager().executeTask((task, inputData) { Workmanager().executeTask((task, inputData) {
@ -85,59 +85,40 @@ void callbackDispatcher() {
} }
void main() async { void main() async {
final String timeZoneName;
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
Connectivity().onConnectivityChanged.listen(( await _initializeApp();
List<ConnectivityResult> result,
) {
result.contains(ConnectivityResult.none)
? isOnline.value = Future(() => false)
: isOnline.value = InternetConnection().hasInternetAccess;
});
DeviceFeature().init();
if (Platform.isAndroid) {
Workmanager().initialize(callbackDispatcher, isInDebugMode: kDebugMode);
await setOptimalDisplayMode();
}
timeZoneName = await FlutterTimezone.getLocalTimezone();
tz.initializeTimeZones();
tz.setLocalLocation(tz.getLocation(timeZoneName));
await isarInit();
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
const DarwinInitializationSettings initializationSettingsDarwin =
DarwinInitializationSettings();
const LinuxInitializationSettings initializationSettingsLinux =
LinuxInitializationSettings(defaultActionName: 'Rain');
const InitializationSettings initializationSettings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin,
linux: initializationSettingsLinux,
);
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
runApp(const MyApp()); runApp(const MyApp());
} }
Future<void> setOptimalDisplayMode() async { Future<void> _initializeApp() async {
final List<DisplayMode> supported = await FlutterDisplayMode.supported; _setupConnectivityListener();
final DisplayMode active = await FlutterDisplayMode.active; await _initializeTimeZone();
final List<DisplayMode> sameResolution = await _initializeIsar();
supported await _initializeNotifications();
.where( if (Platform.isAndroid) {
(DisplayMode m) => await _setOptimalDisplayMode();
m.width == active.width && m.height == active.height, Workmanager().initialize(callbackDispatcher, isInDebugMode: kDebugMode);
) HomeWidget.setAppGroupId(appGroupId);
.toList() }
..sort( DeviceFeature().init();
(DisplayMode a, DisplayMode b) =>
b.refreshRate.compareTo(a.refreshRate),
);
final DisplayMode mostOptimalMode =
sameResolution.isNotEmpty ? sameResolution.first : active;
await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
} }
Future<void> isarInit() async { void _setupConnectivityListener() {
Connectivity().onConnectivityChanged.listen((result) {
isOnline.value =
result.contains(ConnectivityResult.none)
? Future.value(false)
: InternetConnection().hasInternetAccess;
});
}
Future<void> _initializeTimeZone() async {
final timeZoneName = await FlutterTimezone.getLocalTimezone();
tz.initializeTimeZones();
tz.setLocalLocation(tz.getLocation(timeZoneName));
}
Future<void> _initializeIsar() async {
isar = await Isar.open([ isar = await Isar.open([
SettingsSchema, SettingsSchema,
MainWeatherCacheSchema, MainWeatherCacheSchema,
@ -159,6 +140,28 @@ Future<void> isarInit() async {
} }
} }
Future<void> _initializeNotifications() async {
const initializationSettings = InitializationSettings(
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
iOS: DarwinInitializationSettings(),
linux: LinuxInitializationSettings(defaultActionName: 'Rain'),
);
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
}
Future<void> _setOptimalDisplayMode() async {
final supported = await FlutterDisplayMode.supported;
final active = await FlutterDisplayMode.active;
final sameResolution =
supported
.where((m) => m.width == active.width && m.height == active.height)
.toList()
..sort((a, b) => b.refreshRate.compareTo(a.refreshRate));
final mostOptimalMode =
sameResolution.isNotEmpty ? sameResolution.first : active;
await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
}
class MyApp extends StatefulWidget { class MyApp extends StatefulWidget {
const MyApp({super.key}); const MyApp({super.key});
@ -177,30 +180,14 @@ class MyApp extends StatefulWidget {
}) async { }) async {
final state = context.findAncestorStateOfType<_MyAppState>()!; final state = context.findAncestorStateOfType<_MyAppState>()!;
if (newAmoledTheme != null) { if (newAmoledTheme != null) state.changeAmoledTheme(newAmoledTheme);
state.changeAmoledTheme(newAmoledTheme); if (newMaterialColor != null) state.changeMarerialTheme(newMaterialColor);
} if (newRoundDegree != null) state.changeRoundDegree(newRoundDegree);
if (newMaterialColor != null) { if (newLargeElement != null) state.changeLargeElement(newLargeElement);
state.changeMarerialTheme(newMaterialColor); if (newLocale != null) state.changeLocale(newLocale);
} if (newTimeRange != null) state.changeTimeRange(newTimeRange);
if (newRoundDegree != null) { if (newTimeStart != null) state.changeTimeStart(newTimeStart);
state.changeRoundDegree(newRoundDegree); if (newTimeEnd != null) state.changeTimeEnd(newTimeEnd);
}
if (newLargeElement != null) {
state.changeLargeElement(newLargeElement);
}
if (newLocale != null) {
state.changeLocale(newLocale);
}
if (newTimeRange != null) {
state.changeTimeRange(newTimeRange);
}
if (newTimeStart != null) {
state.changeTimeStart(newTimeStart);
}
if (newTimeEnd != null) {
state.changeTimeEnd(newTimeEnd);
}
if (newWidgetBackgroundColor != null) { if (newWidgetBackgroundColor != null) {
state.changeWidgetBackgroundColor(newWidgetBackgroundColor); state.changeWidgetBackgroundColor(newWidgetBackgroundColor);
} }
@ -216,68 +203,28 @@ class MyApp extends StatefulWidget {
class _MyAppState extends State<MyApp> { class _MyAppState extends State<MyApp> {
final themeController = Get.put(ThemeController()); final themeController = Get.put(ThemeController());
void changeAmoledTheme(bool newAmoledTheme) { void changeAmoledTheme(bool newAmoledTheme) =>
setState(() { setState(() => amoledTheme = newAmoledTheme);
amoledTheme = newAmoledTheme; void changeMarerialTheme(bool newMaterialColor) =>
}); setState(() => materialColor = newMaterialColor);
} void changeRoundDegree(bool newRoundDegree) =>
setState(() => roundDegree = newRoundDegree);
void changeMarerialTheme(bool newMaterialColor) { void changeLargeElement(bool newLargeElement) =>
setState(() { setState(() => largeElement = newLargeElement);
materialColor = newMaterialColor; void changeTimeRange(int newTimeRange) =>
}); setState(() => timeRange = newTimeRange);
} void changeTimeStart(String newTimeStart) =>
setState(() => timeStart = newTimeStart);
void changeRoundDegree(bool newRoundDegree) { void changeTimeEnd(String newTimeEnd) => setState(() => timeEnd = newTimeEnd);
setState(() { void changeLocale(Locale newLocale) => setState(() => locale = newLocale);
roundDegree = newRoundDegree; void changeWidgetBackgroundColor(String newWidgetBackgroundColor) =>
}); setState(() => widgetBackgroundColor = newWidgetBackgroundColor);
} void changeWidgetTextColor(String newWidgetTextColor) =>
setState(() => widgetTextColor = newWidgetTextColor);
void changeLargeElement(bool newLargeElement) {
setState(() {
largeElement = newLargeElement;
});
}
void changeTimeRange(int newTimeRange) {
setState(() {
timeRange = newTimeRange;
});
}
void changeTimeStart(String newTimeStart) {
setState(() {
timeStart = newTimeStart;
});
}
void changeTimeEnd(String newTimeEnd) {
setState(() {
timeEnd = newTimeEnd;
});
}
void changeLocale(Locale newLocale) {
setState(() {
locale = newLocale;
});
}
void changeWidgetBackgroundColor(String newWidgetBackgroundColor) {
setState(() {
widgetBackgroundColor = newWidgetBackgroundColor;
});
}
void changeWidgetTextColor(String newWidgetTextColor) {
setState(() {
widgetTextColor = newWidgetTextColor;
});
}
@override @override
void initState() { void initState() {
super.initState();
amoledTheme = settings.amoledTheme; amoledTheme = settings.amoledTheme;
materialColor = settings.materialColor; materialColor = settings.materialColor;
roundDegree = settings.roundDegree; roundDegree = settings.roundDegree;
@ -291,10 +238,6 @@ class _MyAppState extends State<MyApp> {
timeEnd = settings.timeEnd ?? '21:00'; timeEnd = settings.timeEnd ?? '21:00';
widgetBackgroundColor = settings.widgetBackgroundColor ?? ''; widgetBackgroundColor = settings.widgetBackgroundColor ?? '';
widgetTextColor = settings.widgetTextColor ?? ''; widgetTextColor = settings.widgetTextColor ?? '';
if (Platform.isAndroid) {
HomeWidget.setAppGroupId(appGroupId);
}
super.initState();
} }
@override @override
@ -379,10 +322,10 @@ class _MyAppState extends State<MyApp> {
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
home: home:
settings.onboard settings.onboard
? (locationCache.city == null) || ? (locationCache.city == null ||
(locationCache.district == null) || locationCache.district == null ||
(locationCache.lat == null) || locationCache.lat == null ||
(locationCache.lon == null) locationCache.lon == null)
? const SelectGeolocation(isStart: true) ? const SelectGeolocation(isStart: true)
: const HomePage() : const HomePage()
: const OnBording(), : const OnBording(),

View file

@ -20,10 +20,14 @@ ColorScheme colorSchemeDark = ColorScheme.fromSeed(
); );
ThemeData lightTheme( ThemeData lightTheme(
Color? color, ColorScheme? colorScheme, bool edgeToEdgeAvailable) { Color? color,
ColorScheme? colorScheme,
bool edgeToEdgeAvailable,
) {
return baseLigth.copyWith( return baseLigth.copyWith(
brightness: Brightness.light, brightness: Brightness.light,
colorScheme: colorScheme colorScheme:
colorScheme
?.copyWith( ?.copyWith(
brightness: Brightness.light, brightness: Brightness.light,
surface: baseLigth.colorScheme.surface, surface: baseLigth.colorScheme.surface,
@ -54,9 +58,7 @@ ThemeData lightTheme(
color: color, color: color,
surfaceTintColor: surfaceTintColor:
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint, color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
borderRadius: BorderRadius.circular(20),
),
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
), ),
bottomSheetTheme: baseLigth.bottomSheetTheme.copyWith( bottomSheetTheme: baseLigth.bottomSheetTheme.copyWith(
@ -73,11 +75,9 @@ ThemeData lightTheme(
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint, color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
), ),
inputDecorationTheme: baseLigth.inputDecorationTheme.copyWith( inputDecorationTheme: baseLigth.inputDecorationTheme.copyWith(
labelStyle: WidgetStateTextStyle.resolveWith( labelStyle: WidgetStateTextStyle.resolveWith((Set<WidgetState> states) {
(Set<WidgetState> states) {
return const TextStyle(fontSize: 14); return const TextStyle(fontSize: 14);
}, }),
),
border: InputBorder.none, border: InputBorder.none,
focusedBorder: InputBorder.none, focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none, enabledBorder: InputBorder.none,
@ -87,10 +87,14 @@ ThemeData lightTheme(
} }
ThemeData darkTheme( ThemeData darkTheme(
Color? color, ColorScheme? colorScheme, bool edgeToEdgeAvailable) { Color? color,
ColorScheme? colorScheme,
bool edgeToEdgeAvailable,
) {
return baseDark.copyWith( return baseDark.copyWith(
brightness: Brightness.dark, brightness: Brightness.dark,
colorScheme: colorScheme colorScheme:
colorScheme
?.copyWith( ?.copyWith(
brightness: Brightness.dark, brightness: Brightness.dark,
surface: baseDark.colorScheme.surface, surface: baseDark.colorScheme.surface,
@ -121,9 +125,7 @@ ThemeData darkTheme(
color: color, color: color,
surfaceTintColor: surfaceTintColor:
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint, color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
borderRadius: BorderRadius.circular(20),
),
shadowColor: Colors.transparent, shadowColor: Colors.transparent,
), ),
bottomSheetTheme: baseDark.bottomSheetTheme.copyWith( bottomSheetTheme: baseDark.bottomSheetTheme.copyWith(
@ -140,11 +142,9 @@ ThemeData darkTheme(
color == oledColor ? Colors.transparent : colorScheme?.surfaceTint, color == oledColor ? Colors.transparent : colorScheme?.surfaceTint,
), ),
inputDecorationTheme: baseDark.inputDecorationTheme.copyWith( inputDecorationTheme: baseDark.inputDecorationTheme.copyWith(
labelStyle: WidgetStateTextStyle.resolveWith( labelStyle: WidgetStateTextStyle.resolveWith((Set<WidgetState> states) {
(Set<WidgetState> states) {
return const TextStyle(fontSize: 14); return const TextStyle(fontSize: 14);
}, }),
),
border: InputBorder.none, border: InputBorder.none,
focusedBorder: InputBorder.none, focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none, enabledBorder: InputBorder.none,

View file

@ -4,7 +4,8 @@ import 'package:rain/app/data/db.dart';
import 'package:rain/main.dart'; import 'package:rain/main.dart';
class ThemeController extends GetxController { class ThemeController extends GetxController {
ThemeMode get theme => settings.theme == 'system' ThemeMode get theme =>
settings.theme == 'system'
? ThemeMode.system ? ThemeMode.system
: settings.theme == 'dark' : settings.theme == 'dark'
? ThemeMode.dark ? ThemeMode.dark

View file

@ -44,8 +44,7 @@ class EsEs {
'hPa': 'hPa', 'hPa': 'hPa',
'settings': 'Ajustes', 'settings': 'Ajustes',
'no_inter': 'Sin conexión a Internet', 'no_inter': 'Sin conexión a Internet',
'on_inter': 'on_inter': 'Conéctate a Internet para obtener información meteorológica.',
'Conéctate a Internet para obtener información meteorológica.',
'location': 'Ubicación', 'location': 'Ubicación',
'no_location': 'no_location':
'Activa la localización para obtener información meteorológica para tu ubicación actual.', 'Activa la localización para obtener información meteorológica para tu ubicación actual.',
@ -136,8 +135,7 @@ class EsEs {
'map': 'Mapa', 'map': 'Mapa',
'clearCacheStore': 'Borrar caché', 'clearCacheStore': 'Borrar caché',
'deletedCacheStore': 'Borrando caché', 'deletedCacheStore': 'Borrando caché',
'deletedCacheStoreQuery': 'deletedCacheStoreQuery': '¿Estás seguro de que quieres borrar el caché?',
'¿Estás seguro de que quieres borrar el caché?',
'addWidget': 'Agregar widget', 'addWidget': 'Agregar widget',
'hideMap': 'Ocultar mapa', 'hideMap': 'Ocultar mapa',
}; };

View file

@ -60,8 +60,7 @@ class FrFr {
'district': 'District', 'district': 'District',
'noWeatherCard': 'Ajouter une ville', 'noWeatherCard': 'Ajouter une ville',
'deletedCardWeather': 'Supprimer une ville', 'deletedCardWeather': 'Supprimer une ville',
'deletedCardWeatherQuery': 'deletedCardWeatherQuery': 'Êtes-vous sûr de vouloir supprimer la ville ?',
'Êtes-vous sûr de vouloir supprimer la ville ?',
'delete': 'Supprimer', 'delete': 'Supprimer',
'cancel': 'Annuler', 'cancel': 'Annuler',
'time': 'Heure locale', 'time': 'Heure locale',

View file

@ -44,8 +44,7 @@ class HuHu {
'hPa': 'hPa', 'hPa': 'hPa',
'settings': 'Beállítások', 'settings': 'Beállítások',
'no_inter': 'Nincs internet', 'no_inter': 'Nincs internet',
'on_inter': 'on_inter': 'Kapcsolja be az internetet az időjárási adatok lekéréséhez.',
'Kapcsolja be az internetet az időjárási adatok lekéréséhez.',
'location': 'Hely', 'location': 'Hely',
'no_location': 'no_location':
'Engedélyezze a helyszolgáltatást az aktuális hely időjárásadatainak megszerzéséhez.', 'Engedélyezze a helyszolgáltatást az aktuális hely időjárásadatainak megszerzéséhez.',

View file

@ -44,8 +44,7 @@ class ItIt {
'hPa': 'hPa', 'hPa': 'hPa',
'settings': 'Imposta.', 'settings': 'Imposta.',
'no_inter': 'Non c\'è connessione Internet', 'no_inter': 'Non c\'è connessione Internet',
'on_inter': 'on_inter': 'Attiva la connessione Internet per avere dati meteorologici.',
'Attiva la connessione Internet per avere dati meteorologici.',
'location': 'Posizione', 'location': 'Posizione',
'no_location': 'no_location':
'Abilita il servizio di localizzazione per ottenere i dati meteo per la posizione corrente.', 'Abilita il servizio di localizzazione per ottenere i dati meteo per la posizione corrente.',
@ -60,8 +59,7 @@ class ItIt {
'district': 'Regione', 'district': 'Regione',
'noWeatherCard': 'Aggiungi una città', 'noWeatherCard': 'Aggiungi una città',
'deletedCardWeather': 'Rimozione della città', 'deletedCardWeather': 'Rimozione della città',
'deletedCardWeatherQuery': 'deletedCardWeatherQuery': 'Sei sicuro di voler rimuovere questa città?',
'Sei sicuro di voler rimuovere questa città?',
'delete': 'Elimina', 'delete': 'Elimina',
'cancel': 'Annulla', 'cancel': 'Annulla',
'time': 'Orario locale', 'time': 'Orario locale',

View file

@ -59,8 +59,7 @@ class KaGe {
'district': 'რაიონი', 'district': 'რაიონი',
'noWeatherCard': 'დაამატეთ ქალაქი', 'noWeatherCard': 'დაამატეთ ქალაქი',
'deletedCardWeather': 'ქალაქის წაშლა', 'deletedCardWeather': 'ქალაქის წაშლა',
'deletedCardWeatherQuery': 'deletedCardWeatherQuery': 'დარწმუნებული ხართ, რომ გსურთ ქალაქის წაშლა?',
'დარწმუნებული ხართ, რომ გსურთ ქალაქის წაშლა?',
'delete': 'ამოღება', 'delete': 'ამოღება',
'cancel': 'გაუქმება', 'cancel': 'გაუქმება',
'time': 'დრო ქალაქში', 'time': 'დრო ქალაქში',
@ -135,8 +134,7 @@ class KaGe {
'map': 'რუკა', 'map': 'რუკა',
'clearCacheStore': 'ქეშის გასუფთავება', 'clearCacheStore': 'ქეშის გასუფთავება',
'deletedCacheStore': 'ქეშის გასუფთავება მიმდინარეობს', 'deletedCacheStore': 'ქეშის გასუფთავება მიმდინარეობს',
'deletedCacheStoreQuery': 'deletedCacheStoreQuery': 'დარწმუნებული ხართ, რომ გსურთ ქეშის გასუფთავება?',
'დარწმუნებული ხართ, რომ გსურთ ქეშის გასუფთავება?',
'addWidget': 'ვიდჯეტის დამატება', 'addWidget': 'ვიდჯეტის დამატება',
'hideMap': 'რუკის დამალვა', 'hideMap': 'რუკის დამალვა',
}; };

View file

@ -44,8 +44,7 @@ class NlNl {
'hPa': 'hPa', 'hPa': 'hPa',
'settings': 'Instellingen.', 'settings': 'Instellingen.',
'no_inter': 'Geen Internet', 'no_inter': 'Geen Internet',
'on_inter': 'on_inter': 'Schakel Internet in om meteorologische gegevens te ontvangen.',
'Schakel Internet in om meteorologische gegevens te ontvangen.',
'location': 'Locatie', 'location': 'Locatie',
'no_location': 'no_location':
'Schakel de locatiedienst in om weer gegevens voor de huidige locatie te ontvangen.', 'Schakel de locatiedienst in om weer gegevens voor de huidige locatie te ontvangen.',
@ -60,8 +59,7 @@ class NlNl {
'district': 'District', 'district': 'District',
'noWeatherCard': 'Voeg een stad toe', 'noWeatherCard': 'Voeg een stad toe',
'deletedCardWeather': 'Verwijder een city', 'deletedCardWeather': 'Verwijder een city',
'deletedCardWeatherQuery': 'deletedCardWeatherQuery': 'Weet je zeker dat je de stad wilt verwijderen?',
'Weet je zeker dat je de stad wilt verwijderen?',
'delete': 'Verwijder', 'delete': 'Verwijder',
'cancel': 'Annuleer', 'cancel': 'Annuleer',
'time': 'Tijd in de stad', 'time': 'Tijd in de stad',

View file

@ -134,8 +134,7 @@ class PlPl {
'map': 'Mapa', 'map': 'Mapa',
'clearCacheStore': 'Wyczyść pamięć podręczną', 'clearCacheStore': 'Wyczyść pamięć podręczną',
'deletedCacheStore': 'Czyszczenie pamięci podręcznej', 'deletedCacheStore': 'Czyszczenie pamięci podręcznej',
'deletedCacheStoreQuery': 'deletedCacheStoreQuery': 'Czy na pewno chcesz wyczyścić pamięć podręczną?',
'Czy na pewno chcesz wyczyścić pamięć podręczną?',
'addWidget': 'Dodaj widget', 'addWidget': 'Dodaj widget',
'hideMap': 'Ukryj mapę', 'hideMap': 'Ukryj mapę',
}; };

View file

@ -1385,5 +1385,5 @@ packages:
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
sdks: sdks:
dart: ">=3.7.0 <4.0.0" dart: ">=3.7.2 <4.0.0"
flutter: ">=3.27.0" flutter: ">=3.27.0"

View file

@ -6,7 +6,7 @@ publish_to: "none"
version: 1.3.8+41 version: 1.3.8+41
environment: environment:
sdk: ">=3.7.0 <4.0.0" sdk: ">=3.7.2 <4.0.0"
dependencies: dependencies:
flutter: flutter: