import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_timezone/flutter_timezone.dart'; import 'package:geocoding/geocoding.dart'; import 'package:geolocator/geolocator.dart'; import 'package:get/get.dart'; import 'package:home_widget/home_widget.dart'; import 'package:isar/isar.dart'; import 'package:lat_lng_to_timezone/lat_lng_to_timezone.dart' as tzmap; import 'package:path_provider/path_provider.dart'; import 'package:rain/app/api/api.dart'; import 'package:rain/app/data/db.dart'; import 'package:rain/app/utils/notification.dart'; import 'package:rain/app/utils/show_snack_bar.dart'; import 'package:rain/app/ui/widgets/weather/status/status_data.dart'; import 'package:rain/app/ui/widgets/weather/status/status_weather.dart'; import 'package:rain/main.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:timezone/data/latest_all.dart' as tz; import 'package:timezone/standalone.dart' as tz; import 'package:timezone/timezone.dart' as tz; import 'package:url_launcher/url_launcher.dart'; import 'package:workmanager/workmanager.dart'; class WeatherController extends GetxController { final isLoading = true.obs; final _district = ''.obs; final _city = ''.obs; final _latitude = 0.0.obs; final _longitude = 0.0.obs; String get district => _district.value; String get city => _city.value; double get latitude => _latitude.value; double get longitude => _longitude.value; final _mainWeather = MainWeatherCache().obs; final _location = LocationCache().obs; final _weatherCard = WeatherCard().obs; final weatherCards = [].obs; MainWeatherCache get mainWeather => _mainWeather.value; LocationCache get location => _location.value; WeatherCard get weatherCard => _weatherCard.value; final hourOfDay = 0.obs; final dayOfNow = 0.obs; final itemScrollController = ItemScrollController(); final cacheExpiry = DateTime.now().subtract(const Duration(hours: 12)); @override void onInit() { weatherCards .assignAll(isar.weatherCards.where().sortByIndex().findAllSync()); super.onInit(); } Future determinePosition() async { LocationPermission permission; permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { return Future.error('Location permissions are denied'); } } if (permission == LocationPermission.deniedForever) { return Future.error( 'Location permissions are permanently denied, we cannot request permissions.'); } return await Geolocator.getCurrentPosition(); } Future setLocation() async { if (settings.location) { await getCurrentLocation(); } else { if ((isar.locationCaches.where().findAllSync()).isNotEmpty) { LocationCache locationCity = (isar.locationCaches.where().findFirstSync())!; await getLocation(locationCity.lat!, locationCity.lon!, locationCity.district!, locationCity.city!); } } } Future getCurrentLocation() async { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!(await isOnline.value)) { showSnackBar(content: 'no_inter'.tr); await readCache(); return; } if (!serviceEnabled) { showSnackBar( content: 'no_location'.tr, onPressed: () => Geolocator.openLocationSettings(), ); await readCache(); return; } if ((isar.mainWeatherCaches.where().findAllSync()).isNotEmpty) { await readCache(); return; } Position position = await determinePosition(); List placemarks = await placemarkFromCoordinates(position.latitude, position.longitude); Placemark place = placemarks[0]; _latitude.value = position.latitude; _longitude.value = position.longitude; _district.value = '${place.administrativeArea}'; _city.value = '${place.locality}'; _mainWeather.value = await WeatherAPI().getWeatherData(_latitude.value, _longitude.value); notificationCheck(); await writeCache(); await readCache(); } Future getCurrentLocationSearch() async { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); double lat, lon; String city, district; if (!(await isOnline.value)) { showSnackBar(content: 'no_inter'.tr); } if (!serviceEnabled) { showSnackBar( content: 'no_location'.tr, onPressed: () => Geolocator.openLocationSettings(), ); } Position position = await determinePosition(); List placemarks = await placemarkFromCoordinates(position.latitude, position.longitude); Placemark place = placemarks[0]; lat = position.latitude; lon = position.longitude; city = '${place.administrativeArea}'; district = '${place.locality}'; Map location = {'lat': lat, 'lon': lon, 'city': city, 'district': district}; return location; } Future getLocation(double latitude, double longitude, String district, String locality) async { if (!(await isOnline.value)) { showSnackBar(content: 'no_inter'.tr); await readCache(); return; } if ((isar.mainWeatherCaches.where().findAllSync()).isNotEmpty) { await readCache(); return; } _latitude.value = latitude; _longitude.value = longitude; _district.value = district; _city.value = locality; _mainWeather.value = await WeatherAPI().getWeatherData(_latitude.value, _longitude.value); notificationCheck(); await writeCache(); await readCache(); } Future readCache() async { MainWeatherCache? mainWeatherCache; LocationCache? locationCache; while (mainWeatherCache == null || locationCache == null) { mainWeatherCache = isar.mainWeatherCaches.where().findFirstSync(); locationCache = isar.locationCaches.where().findFirstSync(); } _mainWeather.value = mainWeatherCache; _location.value = locationCache; hourOfDay.value = getTime(_mainWeather.value.time!, _mainWeather.value.timezone!); dayOfNow.value = getDay(_mainWeather.value.timeDaily!, _mainWeather.value.timezone!); if (Platform.isAndroid) { Workmanager().registerPeriodicTask( 'widgetUpdate', 'widgetBackgroundUpdate', frequency: const Duration(minutes: 15), existingWorkPolicy: ExistingWorkPolicy.update, ); } isLoading.value = false; Future.delayed(const Duration(milliseconds: 30), () { itemScrollController.scrollTo( index: hourOfDay.value, duration: const Duration(seconds: 2), curve: Curves.easeInOutCubic, ); }); } Future writeCache() async { final locationCaches = LocationCache( lat: _latitude.value, lon: _longitude.value, city: _city.value, district: _district.value, ); isar.writeTxnSync(() { final mainWeatherCachesIsEmpty = (isar.mainWeatherCaches.where().findAllSync()).isEmpty; final locationCachesIsEmpty = (isar.locationCaches.where().findAllSync()).isEmpty; if (mainWeatherCachesIsEmpty) { isar.mainWeatherCaches.putSync(_mainWeather.value); } if (locationCachesIsEmpty) { isar.locationCaches.putSync(locationCaches); } }); } Future deleteCache() async { if (!(await isOnline.value)) { return; } isar.writeTxnSync(() { isar.mainWeatherCaches .filter() .timestampLessThan(cacheExpiry) .deleteAllSync(); }); if ((isar.mainWeatherCaches.where().findAllSync()).isEmpty) { await flutterLocalNotificationsPlugin.cancelAll(); } } Future deleteAll(bool changeCity) async { if (!(await isOnline.value)) { return; } bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); await flutterLocalNotificationsPlugin.cancelAll(); isar.writeTxnSync(() { if (!settings.location) { isar.mainWeatherCaches.where().deleteAllSync(); } if ((settings.location && serviceEnabled) || changeCity) { isar.mainWeatherCaches.where().deleteAllSync(); isar.locationCaches.where().deleteAllSync(); } }); } // Card Weather Future addCardWeather( double latitude, double longitude, String city, String district) async { if (!(await isOnline.value)) { showSnackBar(content: 'no_inter'.tr); return; } String tz = tzmap.latLngToTimezoneString(latitude, longitude); _weatherCard.value = await WeatherAPI() .getWeatherCard(latitude, longitude, city, district, tz); isar.writeTxnSync(() { weatherCards.add(_weatherCard.value); isar.weatherCards.putSync(_weatherCard.value); }); } Future updateCacheCard(bool refresh) async { List weatherCard = refresh ? isar.weatherCards.where().sortByIndex().findAllSync() : isar.weatherCards .filter() .timestampLessThan(cacheExpiry) .sortByIndex() .findAllSync(); if ((!(await isOnline.value)) || weatherCard.isEmpty) { return; } for (var oldCard in weatherCard) { var updatedCard = await WeatherAPI().getWeatherCard(oldCard.lat!, oldCard.lon!, oldCard.city!, oldCard.district!, oldCard.timezone!); isar.writeTxnSync(() { oldCard ..time = updatedCard.time ..weathercode = updatedCard.weathercode ..temperature2M = updatedCard.temperature2M ..apparentTemperature = updatedCard.apparentTemperature ..relativehumidity2M = updatedCard.relativehumidity2M ..precipitation = updatedCard.precipitation ..rain = updatedCard.rain ..surfacePressure = updatedCard.surfacePressure ..visibility = updatedCard.visibility ..evapotranspiration = updatedCard.evapotranspiration ..windspeed10M = updatedCard.windspeed10M ..winddirection10M = updatedCard.winddirection10M ..windgusts10M = updatedCard.windgusts10M ..cloudcover = updatedCard.cloudcover ..uvIndex = updatedCard.uvIndex ..dewpoint2M = updatedCard.dewpoint2M ..precipitationProbability = updatedCard.precipitationProbability ..shortwaveRadiation = updatedCard.shortwaveRadiation ..timeDaily = updatedCard.timeDaily ..weathercodeDaily = updatedCard.weathercodeDaily ..temperature2MMax = updatedCard.temperature2MMax ..temperature2MMin = updatedCard.temperature2MMin ..apparentTemperatureMax = updatedCard.apparentTemperatureMax ..apparentTemperatureMin = updatedCard.apparentTemperatureMin ..sunrise = updatedCard.sunrise ..sunset = updatedCard.sunset ..precipitationSum = updatedCard.precipitationSum ..precipitationProbabilityMax = updatedCard.precipitationProbabilityMax ..windspeed10MMax = updatedCard.windspeed10MMax ..windgusts10MMax = updatedCard.windgusts10MMax ..uvIndexMax = updatedCard.uvIndexMax ..rainSum = updatedCard.rainSum ..winddirection10MDominant = updatedCard.winddirection10MDominant ..timestamp = DateTime.now(); isar.weatherCards.putSync(oldCard); var newCard = oldCard; int oldIdx = weatherCard.indexOf(oldCard); weatherCards[oldIdx] = newCard; weatherCards.refresh(); }); } } Future updateCard(WeatherCard weatherCard) async { if (!(await isOnline.value)) { return; } final updatedCard = await WeatherAPI().getWeatherCard( weatherCard.lat!, weatherCard.lon!, weatherCard.city!, weatherCard.district!, weatherCard.timezone!, ); isar.writeTxnSync(() { weatherCard ..time = updatedCard.time ..weathercode = updatedCard.weathercode ..temperature2M = updatedCard.temperature2M ..apparentTemperature = updatedCard.apparentTemperature ..relativehumidity2M = updatedCard.relativehumidity2M ..precipitation = updatedCard.precipitation ..rain = updatedCard.rain ..surfacePressure = updatedCard.surfacePressure ..visibility = updatedCard.visibility ..evapotranspiration = updatedCard.evapotranspiration ..windspeed10M = updatedCard.windspeed10M ..winddirection10M = updatedCard.winddirection10M ..windgusts10M = updatedCard.windgusts10M ..cloudcover = updatedCard.cloudcover ..uvIndex = updatedCard.uvIndex ..dewpoint2M = updatedCard.dewpoint2M ..precipitationProbability = updatedCard.precipitationProbability ..shortwaveRadiation = updatedCard.shortwaveRadiation ..timeDaily = updatedCard.timeDaily ..weathercodeDaily = updatedCard.weathercodeDaily ..temperature2MMax = updatedCard.temperature2MMax ..temperature2MMin = updatedCard.temperature2MMin ..apparentTemperatureMax = updatedCard.apparentTemperatureMax ..apparentTemperatureMin = updatedCard.apparentTemperatureMin ..sunrise = updatedCard.sunrise ..sunset = updatedCard.sunset ..precipitationSum = updatedCard.precipitationSum ..precipitationProbabilityMax = updatedCard.precipitationProbabilityMax ..windspeed10MMax = updatedCard.windspeed10MMax ..windgusts10MMax = updatedCard.windgusts10MMax ..uvIndexMax = updatedCard.uvIndexMax ..rainSum = updatedCard.rainSum ..winddirection10MDominant = updatedCard.winddirection10MDominant ..timestamp = DateTime.now(); isar.weatherCards.putSync(weatherCard); }); } Future deleteCardWeather(WeatherCard weatherCard) async { isar.writeTxnSync(() { weatherCards.remove(weatherCard); isar.weatherCards.deleteSync(weatherCard.id); }); } int getTime(List time, String timezone) { int getTime = 0; for (var i = 0; i < time.length; i++) { if (tz.TZDateTime.now(tz.getLocation(timezone)).hour == DateTime.parse(time[i]).hour && tz.TZDateTime.now(tz.getLocation(timezone)).day == DateTime.parse(time[i]).day) { getTime = i; } } return getTime; } int getDay(List time, String timezone) { int getDay = 0; for (var i = 0; i < time.length; i++) { if (tz.TZDateTime.now(tz.getLocation(timezone)).day == time[i].day) { getDay = i; } } return getDay; } TimeOfDay timeConvert(String normTime) { int hh = 0; if (normTime.endsWith('PM')) hh = 12; normTime = normTime.split(' ')[0]; return TimeOfDay( hour: hh + int.parse(normTime.split(':')[0]) % 24, minute: int.parse(normTime.split(':')[1]) % 60, ); } Future getLocalImagePath(String icon) async { final directory = await getTemporaryDirectory(); final imagePath = '${directory.path}/$icon'; final ByteData data = await rootBundle.load('assets/images/$icon'); final List bytes = data.buffer.asUint8List(); await File(imagePath).writeAsBytes(bytes); return imagePath; } void notification(MainWeatherCache mainWeatherCache) async { DateTime now = DateTime.now(); int startHour = timeConvert(timeStart).hour; int endHour = timeConvert(timeEnd).hour; for (var i = 0; i < mainWeatherCache.time!.length; i += timeRange) { DateTime notificationTime = DateTime.parse(mainWeatherCache.time![i]); if (notificationTime.isAfter(now) && notificationTime.hour >= startHour && notificationTime.hour <= endHour) { for (var j = 0; j < mainWeatherCache.timeDaily!.length; j++) { if (mainWeatherCache.timeDaily![j].day == notificationTime.day) { NotificationShow().showNotification( UniqueKey().hashCode, '$city: ${mainWeatherCache.temperature2M![i]}°', '${StatusWeather().getText(mainWeatherCache.weathercode![i])} · ${StatusData().getTimeFormat(mainWeatherCache.time![i])}', notificationTime, StatusWeather().getImageNotification( mainWeatherCache.weathercode![i], mainWeatherCache.time![i], mainWeatherCache.sunrise![j], mainWeatherCache.sunset![j], ), ); } } } } } void notificationCheck() async { if (settings.notifications) { final List pendingNotificationRequests = await flutterLocalNotificationsPlugin.pendingNotificationRequests(); if (pendingNotificationRequests.isEmpty) { notification(_mainWeather.value); } } } void reorder(oldIndex, newIndex) { if (newIndex > oldIndex) { newIndex -= 1; } final element = weatherCards.removeAt(oldIndex); weatherCards.insert(newIndex, element); for (int i = 0; i < weatherCards.length; i++) { final item = weatherCards[i]; item.index = i; isar.writeTxnSync(() => isar.weatherCards.putSync(item)); } } Future updateWidgetBackgroundColor(String color) async { settings.widgetBackgroundColor = color; isar.writeTxnSync(() { isar.settings.putSync(settings); }); return Future.wait([ HomeWidget.saveWidgetData( 'background_color', color, ), HomeWidget.updateWidget(androidName: androidWidgetName), ]).then((value) { return !value.contains(false); }); } Future updateWidgetTextColor(String color) async { settings.widgetTextColor = color; isar.writeTxnSync(() { isar.settings.putSync(settings); }); return Future.wait([ HomeWidget.saveWidgetData( 'text_color', color, ), HomeWidget.updateWidget(androidName: androidWidgetName), ]).then((value) { return !value.contains(false); }); } Future updateWidget() async { final timeZoneName = await FlutterTimezone.getLocalTimezone(); tz.initializeTimeZones(); tz.setLocalLocation(tz.getLocation(timeZoneName)); final isarWidget = await Isar.open([ SettingsSchema, MainWeatherCacheSchema, LocationCacheSchema, WeatherCardSchema, ], directory: (await getApplicationSupportDirectory()).path); MainWeatherCache? mainWeatherCache; mainWeatherCache = isarWidget.mainWeatherCaches.where().findFirstSync(); if (mainWeatherCache == null) return false; int hour = getTime(mainWeatherCache.time!, mainWeatherCache.timezone!); int day = getDay(mainWeatherCache.timeDaily!, mainWeatherCache.timezone!); return Future.wait([ HomeWidget.saveWidgetData( 'weather_icon', await getLocalImagePath(StatusWeather().getImageNotification( mainWeatherCache.weathercode![hour], mainWeatherCache.time![hour], mainWeatherCache.sunrise![day], mainWeatherCache.sunset![day], ))), HomeWidget.saveWidgetData( 'weather_degree', '${mainWeatherCache.temperature2M?[hour].round()}°', ), HomeWidget.updateWidget(androidName: androidWidgetName), ]).then((value) { return !value.contains(false); }); } void urlLauncher(String uri) async { final Uri url = Uri.parse(uri); if (!await launchUrl(url, mode: LaunchMode.externalApplication)) { throw Exception('Could not launch $url'); } } }