diff --git a/README.md b/README.md index 4a0b0bb..6b2b849 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,15 @@ -![app_screens](Screenshots/Overmorrow_white_circle_mini.png) - # Overmorrow weather -![app_gallery](Screenshots/new_preview.png) +![app_gallery](Screenshots/new_feature_graphic_yellow.jpg) + +## Minimalist colorful weather app + +![app_gallery](Screenshots/app_gallery4_tranparent.png) -### Beautiful minimalist weather app. -![app_screens](Screenshots/app_gallery3.png) | [![Download on Google Play](/Screenshots/play_badge4.png 'Download')](https://play.google.com/store/apps/details?id=com.marotidev.Overmorrow) | [![Download on IzzyOnDroid](/Screenshots/IzzyOnDroid_c.png 'Download')](https://apt.izzysoft.de/fdroid/index/apk/com.marotidev.Overmorrow/) | |---|---| - ## Weather providers 🌨️ - [open-meteo](https://open-meteo.com) - [weatherapi.com](https://www.weatherapi.com) @@ -59,6 +58,8 @@ So instead here is my take on the weather app ui (but i did kep it free and ad f - ✅ Settings/Info/Donate pages - ✅ Tablet mode - ✅ more than one weather provider +- ✅ add network images +- ✅ material you #### hope to add in the near-future: diff --git a/Screenshots/Overmorrow_white_circle_mini.png b/Screenshots/Overmorrow_white_circle_mini.png deleted file mode 100644 index 0ce8b46..0000000 Binary files a/Screenshots/Overmorrow_white_circle_mini.png and /dev/null differ diff --git a/Screenshots/app_gallery4_tranparent.png b/Screenshots/app_gallery4_tranparent.png new file mode 100644 index 0000000..4744e39 Binary files /dev/null and b/Screenshots/app_gallery4_tranparent.png differ diff --git a/Screenshots/new_feature_graphic_yellow.jpg b/Screenshots/new_feature_graphic_yellow.jpg new file mode 100644 index 0000000..f143a51 Binary files /dev/null and b/Screenshots/new_feature_graphic_yellow.jpg differ diff --git a/Screenshots/new_preview.png b/Screenshots/new_preview.png deleted file mode 100644 index 72a8a44..0000000 Binary files a/Screenshots/new_preview.png and /dev/null differ diff --git a/android/app/build.gradle b/android/app/build.gradle index 431f645..f2015cd 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -63,8 +63,8 @@ android { // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. minSdkVersion flutter.minSdkVersion targetSdkVersion flutter.targetSdkVersion - versionCode 41 - versionName "2.4.1" + versionCode 42 + versionName "2.4.2" } buildTypes { diff --git a/lib/decoders/decode_OM.dart b/lib/decoders/decode_OM.dart index af33ed1..453642d 100644 --- a/lib/decoders/decode_OM.dart +++ b/lib/decoders/decode_OM.dart @@ -583,7 +583,9 @@ class OMHour { sunstatus, item["hourly"]["time"][index])), time: settings["Time mode"] == '12 hour'? oMamPmTime(item["hourly"]["time"][index]) : oM24hour(item["hourly"]["time"][index]), - precip: unit_coversion(item["hourly"]["precipitation"][index], settings["Precipitation"]), + precip: double.parse( + unit_coversion(item["hourly"]["precipitation"][index], settings["Precipitation"]).toStringAsFixed(1)), + precip_prob: item["hourly"]["precipitation_probability"][index], wind: double.parse( unit_coversion(item["hourly"]["wind_speed_10m"][index], settings["Wind"]).toStringAsFixed(1)), @@ -614,7 +616,7 @@ class OMSunstatus { ? OMConvertTime(item["daily"]["sunrise"][0]) : OMamPmTime(item["daily"]["sunrise"][0]), sunset: settings["Time mode"] == "24 hour" - ? OMConvertTime(item["daily"]["sunrise"][0]) + ? OMConvertTime(item["daily"]["sunset"][0]) : OMamPmTime(item["daily"]["sunset"][0]), absoluteSunriseSunset: "${OMConvertTime(item["daily"]["sunrise"][0])}/" "${OMConvertTime(item["daily"]["sunset"][0])}", diff --git a/lib/decoders/decode_mn.dart b/lib/decoders/decode_mn.dart index 3c7e713..9e6466d 100644 --- a/lib/decoders/decode_mn.dart +++ b/lib/decoders/decode_mn.dart @@ -16,17 +16,20 @@ along with this program. If not, see . */ +import 'dart:convert'; import 'dart:math'; -import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:overmorrow/Icons/overmorrow_weather_icons_icons.dart'; +import 'package:overmorrow/decoders/decode_OM.dart'; +import 'package:worldtime/worldtime.dart'; -import 'package:overmorrow/decoders/decode_wapi.dart'; - +import '../caching.dart'; import '../settings_page.dart'; import '../ui_helper.dart'; import '../weather_refact.dart'; +import 'decode_wapi.dart'; +import 'extra_info.dart'; String metNTextCorrection(String text, {language = 'English'}) { String p = metNWeatherToText[text] ?? 'Clear Sky'; @@ -34,6 +37,55 @@ String metNTextCorrection(String text, {language = 'English'}) { return t; } +int metNCalculateHourDif(DateTime timeThere) { + DateTime now = DateTime.now().toUtc(); + + return now.hour - timeThere.hour; +} + +int metNcalculateFeelsLike(double t, double r, double v) { + //unfortunately met norway has no feels like temperatures, so i have to calculate it myself based on: + //temperature, relative humidity, and wind speed + // https://meteor.geol.iastate.edu/~ckarsten/bufkit/apparent_temperature.html + + if (t >= 24) { + t = (t * 1.8) + 32; + + double heat_index = -42.379 + (2.04901523 * t) + (10.14333127 * r) + - (0.22475541 * t * r) - (0.00683783 * t * t) + - (0.05481717 * r * r) + (0.00122874 * t * t * r) + + (0.00085282 * t * r * r) - (0.00000199 * t * t * r * r); + + return ((heat_index - 32) / 1.8).round(); + } + + else if (t <= 13) { + t = (t * 1.8) + 32; + + double wind_chill = 35.74 + (0.6215 * t) - (35.75 * pow(v, 0.16)) + (0.4275 * t * pow(v, 0.16)); + + return ((wind_chill - 32) / 1.8).round(); + } + + else { + return t.round(); + } + +} + +String metNGetName(index, settings, item, start) { + if (index < 3) { + const names = ['Today', 'Tomorrow', 'Overmorrow']; + return translation(names[index], settings["Language"]); + } + String x = item["properties"]["timeseries"][start]["time"].split("T")[0]; + List z = x.split("-"); + DateTime time = DateTime(int.parse(z[0]), int.parse(z[1]), int.parse(z[2])); + const weeks = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + String weekname = translation(weeks[time.weekday - 1], settings["Language"]); + return "$weekname, ${time.month}/${time.day}"; +} + String metNBackdropCorrection(String text) { return textBackground[text] ?? 'clear_sky3.jpg'; } @@ -54,10 +106,10 @@ IconData metNIconCorrection(String text) { return textMaterialIcon[text] ?? OvermorrowWeatherIcons.sun2; } -String metNTimeCorrect(String date) { +String metNTimeCorrect(String date, int hourDif) { final realtime = date.split('T')[1]; final realhour = realtime.split(':')[0]; - final num = int.parse(realhour); + final num = (int.parse(realhour) - hourDif) % 24; if (num == 0) { return '12am'; } @@ -66,7 +118,7 @@ String metNTimeCorrect(String date) { return '${minusHour}am'; } else if (num < 12) { - return realhour + 'am'; + return '${realhour}am'; } else if (num == 12) { return '12pm'; @@ -74,58 +126,207 @@ String metNTimeCorrect(String date) { return '${num - 12}pm'; } +String metN24HourTime(String date, int hourDif) { + final realtime = date.split('T')[1]; + final realhour = realtime.split(':')[0]; + final num = (int.parse(realhour) - hourDif) % 24; + final hour = num.toString().padLeft(2, "0"); + final minute = realtime.split(':')[1].padLeft(2, "0"); + return "$hour:$minute"; +} + +Future MetNGetLocalTime(lat, lng) async { + return await Worldtime().timeByLocation( + latitude: lat, + longitude: lng, + ); +} + +Future> MetNMakeRequest(double lat, double lng, String real_loc) async { + + final MnParams = { + "lat" : lat.toString(), + "lon" : lng.toString(), + "altitude" : "100", + }; + final headers = { + "User-Agent": "Overmorrow weather (com.marotidev.overmorrow)" + }; + final MnUrl = Uri.https("api.met.no", 'weatherapi/locationforecast/2.0/complete', MnParams); + + var MnFile = await cacheManager2.getSingleFile(MnUrl.toString(), key: "$real_loc, met.no", headers: headers).timeout(const Duration(seconds: 6)); + var MnResponse = await MnFile.readAsString(); + final MnData = jsonDecode(MnResponse); + + DateTime fetch_datetime = await MnFile.lastModified(); + return [MnData, fetch_datetime]; + +} + class MetNCurrent { final String text; - final String backdrop; final int temp; - final List contentColor; final int humidity; + final int feels_like; final int uv; final double precip; + final int wind; - final Color backcolor; - final Color accentcolor; + final int wind_dir; + + final Color surface; + final Color primary; + final Color primaryLight; + final Color primaryLighter; + final Color onSurface; + final Color outline; + final Color containerLow; + final Color container; + final Color containerHigh; + final Color colorPop; + final Color descColor; + final Color surfaceVariant; + final Color onPrimaryLight; + final Color primarySecond; + + final Color backup_primary; + final Color backup_backcolor; + + final Image image; + + final String photographerName; + final String photographerUrl; + final String photoUrl; + + final List imageDebugColors; const MetNCurrent({ required this.precip, - required this.accentcolor, - required this.backcolor, - required this.backdrop, - required this.contentColor, required this.humidity, + required this.feels_like, required this.temp, required this.text, required this.uv, required this.wind, + required this.backup_backcolor, + required this.backup_primary, + required this.wind_dir, + + required this.surface, + required this.primary, + required this.primaryLight, + required this.primaryLighter, + required this.onSurface, + required this.outline, + required this.containerLow, + required this.container, + required this.containerHigh, + required this.colorPop, + required this.descColor, + required this.surfaceVariant, + required this.onPrimaryLight, + required this.primarySecond, + + required this.image, + required this.photographerName, + required this.photographerUrl, + required this.photoUrl, + required this.imageDebugColors, }); - static MetNCurrent fromJson(item, settings) => MetNCurrent( - text: metNTextCorrection(item["timeseries"][0]["data"]["next_1_hours"]["summary"]["symbol_code"], language: settings["Language"]), - - precip: unit_coversion(item["timeseries"][0]["data"]["next_1_hours"]["details"]["precipitation_amount"], settings["Precipitation"]), - temp: unit_coversion(item["timeseries"][0]["data"]["instant"]["details"]["air_temperature"], settings["Temperature"]).round(), - humidity: item["timeseries"][0]["data"]["instant"]["details"]["relative_humidity"], - wind: unit_coversion(item["timeseries"][0]["data"]["instant"]["details"]["wind_speed"] * 3.6, settings["Wind"]).round(), - uv: item["timeseries"][0]["data"]["instant"]["details"]["ultraviolet_index_clear_sky"], - - backdrop: metNBackdropCorrection( - metNTextCorrection(item["timeseries"][0]["data"]["next_1_hours"]["summary"]["symbol_code"]), - ), - backcolor: metNBackColorCorrection( - metNTextCorrection(item["timeseries"][0]["data"]["next_1_hours"]["summary"]["symbol_code"]), - ), - accentcolor: metNAccentColorCorrection( - metNTextCorrection(item["timeseries"][0]["data"]["next_1_hours"]["summary"]["symbol_code"]), - ), - contentColor: metNContentColorCorrection( - metNTextCorrection(item["timeseries"][0]["data"]["next_1_hours"]["summary"]["symbol_code"]), - ), - ); + static Future fromJson(item, settings, real_loc, lat, lng) async { + + Image Uimage; + + String photographerName = ""; + String photorgaperUrl = ""; + String photoLink = ""; + + if (settings["Image source"] == "network") { + final text = metNTextCorrection( + item["properties"]["timeseries"][0]["data"]["next_1_hours"]["summary"]["symbol_code"], + language: "English"); + final ImageData = await getUnsplashImage(text, real_loc, lat, lng); + Uimage = ImageData[0]; + photographerName = ImageData[1]; + photorgaperUrl = ImageData[2]; + photoLink = ImageData[3]; + } + else { + String imagePath = metNBackdropCorrection( + metNTextCorrection(item["properties"]["timeseries"][0]["data"]["next_1_hours"]["summary"]["symbol_code"]), + ); + Uimage = Image.asset("assets/backdrops/$imagePath", fit: BoxFit.cover, width: double.infinity, height: double.infinity,); + } + + Color back = metNAccentColorCorrection( + metNTextCorrection(item["properties"]["timeseries"][0]["data"]["next_1_hours"]["summary"]["symbol_code"]), + ); + + Color primary = metNBackColorCorrection( + metNTextCorrection(item["properties"]["timeseries"][0]["data"]["next_1_hours"]["summary"]["symbol_code"]), + ); + + List x = await getMainColor(settings, primary, back, Uimage); + List colors = x[0]; + List imageDebugColors = x[1]; + + var it = item["properties"]["timeseries"][0]["data"]; + + return MetNCurrent( + image: Uimage, + photographerName: photographerName, + photographerUrl: photorgaperUrl, + photoUrl: photoLink, + + text: metNTextCorrection( + it["next_1_hours"]["summary"]["symbol_code"], + language: settings["Language"]), + + precip: unit_coversion( + it["next_1_hours"]["details"]["precipitation_amount"], + settings["Precipitation"]), + temp: unit_coversion( + it["instant"]["details"]["air_temperature"], + settings["Temperature"]).round(), + humidity: it["instant"]["details"]["relative_humidity"].round(), + wind: unit_coversion( + it["instant"]["details"]["wind_speed"] * 3.6, + settings["Wind"]).round(), + uv: it["instant"]["details"]["ultraviolet_index_clear_sky"].round(), + feels_like: metNcalculateFeelsLike(it["instant"]["details"]["air_temperature"], + it["instant"]["details"]["relative_humidity"], it["instant"]["details"]["wind_speed"] * 3.6), + imageDebugColors: imageDebugColors, + wind_dir: it["instant"]["details"]["wind_from_direction"].round(), + + surface: colors[0], + primary: colors[1], + primaryLight: colors[2], + primaryLighter: colors[3], + onSurface: colors[4], + outline: colors[5], + containerLow: colors[6], + container: colors[7], + containerHigh: colors[8], + surfaceVariant: colors[9], + onPrimaryLight: colors[10], + primarySecond: colors[11], + + colorPop: colors[12], + descColor: colors[13], + + backup_backcolor: back, + backup_primary: primary, + ); + } } class MetNDay { final String text; + final IconData icon; + final double iconSize; + final String name; final String minmaxtemp; final List hourly; @@ -133,62 +334,42 @@ class MetNDay { final int precip_prob; final double total_precip; + final int windspeed; - final int avg_temp; + final int wind_dir; + final double mm_precip; + final int uv; const MetNDay({ required this.text, + required this.icon, + required this.iconSize, + required this.name, required this.minmaxtemp, required this.hourly, required this.precip_prob, - required this.avg_temp, required this.total_precip, required this.windspeed, required this.hourly_for_precip, required this.mm_precip, + required this.uv, + required this.wind_dir, }); - static Build(item, settings, index) { - - //finds the beggining of the day in question - int days_found = 0; - int index = 0; - while (days_found < index) { - String date = item[index]["time"]; - final realtime = date.split('T')[1]; - final realhour = realtime.split(':')[0]; - final num = int.parse(realhour); - if (num == 0) { - days_found += 1; - } - index += 1; - } - - int begin = index.toInt(); - int end = 0; - - while (end == 0) { - String date = item[index]["time"]; - final realtime = date.split('T')[1]; - final realhour = realtime.split(':')[0]; - final num = int.parse(realhour); - if (num == 0) { - end = index.toInt(); - } - index += 1; - } - - //now we know the timestamps for the beginning and the end of the day + static MetNDay fromJson(item, settings, start, end, index, hourDif) { List temperatures = []; - List windspeeds = []; + List windspeeds = []; + List winddirs = []; List precip_mm = []; + List precip = []; + List uvs = []; - int precipProb = 0; + int precipProb = -10; List oneSummary = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; const weather_names = ['Clear Night', 'Partly Cloudy', 'Clear Sky', 'Overcast', @@ -197,14 +378,18 @@ class MetNDay { List hours = []; - for (int n = begin; n < end; n++) { - MetNHour hour = MetNHour.fromJson(item[n], settings); + for (int n = start; n < end; n++) { + MetNHour hour = MetNHour.fromJson(item["properties"]["timeseries"][n], settings, hourDif); temperatures.add(hour.temp); windspeeds.add(hour.wind); - precip_mm.add(hour.precip); + winddirs.add(hour.wind_dir); + uvs.add(hour.uv); - int index = weather_names.indexOf(hour.text); - int value = weatherConditionBiassTable[hour.text] ?? 0; + precip_mm.add(hour.raw_precip); + precip.add(hour.precip); + + int index = weather_names.indexOf(hour.rawText); + int value = weatherConditionBiassTable[hour.rawText] ?? 0; oneSummary[index] += value; if (hour.precip_prob > precipProb) { @@ -219,27 +404,40 @@ class MetNDay { return MetNDay( mm_precip: precip_mm.reduce((a, b) => a + b), precip_prob: precipProb, - avg_temp: (precip_mm.reduce((a, b) => a + b) / temperatures.length).round(), - minmaxtemp: "${temperatures.reduce(max)}˚/${temperatures.reduce(min)}°", + minmaxtemp: "${temperatures.reduce(min)}°/${temperatures.reduce(max)}°", hourly: hours, hourly_for_precip: hours, - total_precip: unit_coversion(precip_mm.reduce((a, b) => a + b), settings["Precipitation"]), + total_precip: double.parse(precip.reduce((a, b) => a + b).toStringAsFixed(1)), windspeed: (windspeeds.reduce((a, b) => a + b) / windspeeds.length).round(), - name: getName(index, settings), - text: metNTextCorrection(weather_names[BIndex]), + name: metNGetName(index, settings, item, start), + text: translation(weather_names[BIndex], settings["Language"]), icon: metNIconCorrection(weather_names[BIndex]), + iconSize: oMIconSizeCorrection(weather_names[BIndex]), + wind_dir: (windspeeds.reduce((a, b) => a + b) / windspeeds.length).round(), + uv: uvs.reduce(max) ); } } class MetNHour { final int temp; + final IconData icon; + final double iconSize; + final String time; final String text; final double precip; - final int wind; final int precip_prob; + final double wind; + final int wind_dir; + final int uv; + + final double raw_temp; + final double raw_precip; + final double raw_wind; + + final rawText; const MetNHour( { @@ -249,18 +447,158 @@ class MetNHour { required this.text, required this.precip, required this.wind, + required this.iconSize, + required this.raw_precip, + required this.raw_temp, + required this.raw_wind, + required this.wind_dir, + required this.uv, required this.precip_prob, + required this.rawText, }); - static MetNHour fromJson(item, settings) => MetNHour( - text: metNTextCorrection(item["data"]["next_1_hours"]["summary"]["symbol_code"], language: settings["Language"]), - temp: unit_coversion(item["data"]["instant"]["details"]["air_temperature"], settings["Temperature"]).round(), - precip: item["data"]["next_1_hours"]["details"]["precipitation_amount"], - precip_prob : item["data"]["next_1_hours"]["details"]["probability_of_precipitation"].round(), - icon: metNIconCorrection( - metNTextCorrection(item["timeseries"][0]["data"]["next_1_hours"]["summary"]["symbol_code"]), - ), - time: metNTimeCorrect(item["time"]), - wind: unit_coversion(item["data"]["instant"]["details"]["wind_speed"] * 3.6, settings["Wind"]).round(), + static MetNHour fromJson(item, settings, hourDif) { + var nextHours = item["data"]["next_1_hours"] ?? item["data"]["next_6_hours"]; + return MetNHour( + rawText: metNTextCorrection( + nextHours["summary"]["symbol_code"]), + text: metNTextCorrection( + nextHours["summary"]["symbol_code"], + language: settings["Language"]), + temp: unit_coversion( + item["data"]["instant"]["details"]["air_temperature"], + settings["Temperature"]).round(), + precip: unit_coversion( + nextHours["details"]["precipitation_amount"], + settings["Precipitation"]), + precip_prob: (nextHours["details"]["probability_of_precipitation"] ?? + 0).round(), + icon: metNIconCorrection( + metNTextCorrection( + nextHours["summary"]["symbol_code"]), + ), + time: settings["Time mode"] == "24 hour" ? + metN24HourTime(item["time"], hourDif) : metNTimeCorrect(item["time"], hourDif), + wind: double.parse(unit_coversion( + item["data"]["instant"]["details"]["wind_speed"] * 3.6, + settings["Wind"]).toStringAsFixed(1)), + wind_dir: item["data"]["instant"]["details"]["wind_from_direction"] + .round(), + uv: (item["data"]["instant"]["details"]["ultraviolet_index_clear_sky"] ?? 0) + .round(), + + raw_wind: item["data"]["instant"]["details"]["wind_speed"] * 3.6, + raw_precip: nextHours["details"]["precipitation_amount"], + raw_temp: item["data"]["instant"]["details"]["air_temperature"], + iconSize: oMIconSizeCorrection(metNTextCorrection( + nextHours["summary"]["symbol_code"]),) + ); + } +} + + +class MetNSunstatus { + final String sunrise; + final String sunset; + final double sunstatus; + final String absoluteSunriseSunset; + + const MetNSunstatus({ + required this.sunrise, + required this.sunstatus, + required this.sunset, + required this.absoluteSunriseSunset, + }); + + static Future fromJson(item, settings, lat, lng, int dif, DateTime timeThere) async { + final MnParams = { + "lat" : lat.toString(), + "lon" : lng.toString(), + "date" : "${timeThere.year}-${timeThere.month.toString().padLeft(2, "0")}-${timeThere.day.toString().padLeft(2, "0")}", + }; + final headers = { + "User-Agent": "Overmorrow weather (com.marotidev.overmorrow)" + }; + final MnUrl = Uri.https("api.met.no", 'weatherapi/sunrise/3.0/sun', MnParams); + + var MnFile = await cacheManager2.getSingleFile(MnUrl.toString(), key: "$lat, $lng, sunstatus met.no", headers: headers).timeout(const Duration(seconds: 6)); + var MnResponse = await MnFile.readAsString(); + final item = jsonDecode(MnResponse); + + List sunriseString = item["properties"]["sunrise"]["time"].split("T")[1].split("+")[0].split(":"); + DateTime sunrise = timeThere.copyWith( + hour: (int.parse(sunriseString[0]) - dif) % 24, + minute: int.parse(sunriseString[1]), + ); + + List sunsetString = item["properties"]["sunset"]["time"].split("T")[1].split("+")[0].split(":"); + DateTime sunset = timeThere.copyWith( + hour: (int.parse(sunsetString[0]) - dif) % 24, + minute: int.parse(sunsetString[1]), + ); + + return MetNSunstatus( + sunrise: settings["Time mode"] == "24 hour" + ? "${sunrise.hour.toString().padLeft(2, "0")}:${sunrise.minute.toString().padLeft(2, "0")}" + : OMamPmTime("T${sunrise.hour}:${sunrise.minute}"), + sunset: settings["Time mode"] == "24 hour" + ? "${sunset.hour.toString().padLeft(2, "0")}:${sunset.minute.toString().padLeft(2, "0")}" + : OMamPmTime("T${sunset.hour}:${sunset.minute}"), + absoluteSunriseSunset: "${sunrise.hour}:${sunrise.minute}/${sunset.hour}:${sunset.minute}", + sunstatus: min(max( + timeThere.difference(sunrise).inMinutes / sunset.difference(sunrise).inMinutes, 0), 1), + ); + } +} + +Future MetNGetWeatherData(lat, lng, real_loc, settings, placeName) async { + + DateTime localTime = await MetNGetLocalTime(lat, lng); + int hourDif = metNCalculateHourDif(localTime); + + var Mn = await MetNMakeRequest(lat, lng, real_loc); + var MnBody = Mn[0]; + + DateTime fetch_datetime = Mn[1]; + + MetNSunstatus sunstatus = await MetNSunstatus.fromJson(MnBody, settings, lat, lng, hourDif, localTime); + + List days = []; + + int begin = 0; + int index = 0; + + int previous_hour = 0; + for (int n = 0; n < MnBody["properties"]["timeseries"].length; n++) { + int hour = (int.parse(MnBody["properties"]["timeseries"][n]["time"].split("T")[1].split(":")[0]) - hourDif) % 24; + if (n > 0 && hour - previous_hour < 1) { + MetNDay day = MetNDay.fromJson(MnBody, settings, begin, n, index, hourDif); + days.add(day); + index += 1; + begin = n; + } + previous_hour = hour; + } + + return WeatherData( + radar: await RainviewerRadar.getData(), + aqi: await OMAqi.fromJson(MnBody, lat, lng, settings), + sunstatus: sunstatus, + minutely_15_precip: const OM15MinutePrecip(t_minus: "", precip_sum: 0, precips: []), //because MetN has no 15 minute forecast + + current: await MetNCurrent.fromJson(MnBody, settings, real_loc, lat, lng), + days: days, + + lat: lat, + lng: lng, + + place: placeName, + settings: settings, + provider: "met norway", + real_loc: real_loc, + + fetch_datetime: fetch_datetime, + updatedTime: DateTime.now(), + localtime: "${localTime.hour}:${localTime.minute}" ); } \ No newline at end of file diff --git a/lib/decoders/decode_wapi.dart b/lib/decoders/decode_wapi.dart index cee1291..997d494 100644 --- a/lib/decoders/decode_wapi.dart +++ b/lib/decoders/decode_wapi.dart @@ -322,7 +322,7 @@ class WapiCurrent { if (settings["Image source"] == "network") { final text = textCorrection( item["current"]["condition"]["code"], item["current"]["is_day"], - language: settings["Language"] + language: "English" ); final ImageData = await getUnsplashImage(text, real_loc, lat, lng); Uimage = ImageData[0]; @@ -444,7 +444,7 @@ class WapiDay { item["day"]["condition"]["code"], 1 ), iconSize: oMIconSizeCorrection(textCorrection( - item["day"]["condition"]["code"], 1, language: settings["Language"] + item["day"]["condition"]["code"], 1, language: "English" ),), name: getName(index, settings), minmaxtemp: '${unit_coversion(item["day"]["maxtemp_c"], settings["Temperature"]).round()}°' @@ -522,7 +522,7 @@ class WapiHour { item["condition"]["code"], item["is_day"] ), iconSize: oMIconSizeCorrection(textCorrection( - item["condition"]["code"], item["is_day"], language: settings["Language"] + item["condition"]["code"], item["is_day"], language: "English", ),), temp: unit_coversion(item["temp_c"], settings["Temperature"]).round(), time: getTime(item["time"], settings["Time mode"] == '12 hour'), @@ -615,7 +615,6 @@ Future WapiGetWeatherData(lat, lng, real_loc, settings, placeName) var wapi_body = wapi[0]; DateTime fetch_datetime = wapi[1]; - //String real_time = wapi_body["location"]["localtime"]; int epoch = wapi_body["location"]["localtime_epoch"]; WapiSunstatus sunstatus = WapiSunstatus.fromJson(wapi_body, settings); diff --git a/lib/decoders/extra_info.dart b/lib/decoders/extra_info.dart index 7dfb41e..3005035 100644 --- a/lib/decoders/extra_info.dart +++ b/lib/decoders/extra_info.dart @@ -25,6 +25,7 @@ import 'package:dynamic_color/dynamic_color.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:overmorrow/decoders/decode_OM.dart'; +import 'package:overmorrow/decoders/decode_mn.dart'; import 'package:overmorrow/settings_page.dart'; import 'package:palette_generator/palette_generator.dart'; @@ -35,6 +36,18 @@ import '../ui_helper.dart'; import '../weather_refact.dart'; import 'decode_wapi.dart'; +class HexColor extends Color { + static int _getColorFromHex(String hexColor) { + hexColor = hexColor.toUpperCase().replaceAll("#", ""); + if (hexColor.length == 6) { + hexColor = "FF$hexColor"; + } + return int.parse(hexColor, radix: 16); + } + + HexColor(final String hexColor) : super(_getColorFromHex(hexColor)); +} + Future> getUnsplashImage(String _text, String real_loc, double lat, double lng) async { List keys1 = textFilter.keys.toList(); @@ -133,6 +146,10 @@ Future> getUnsplashImage(String _text, String real_loc, double lat final String username = unsplash_body[index]["user"]["name"] ?? ""; final String photoLink = unsplash_body[index]["links"]["html"] ?? ""; + //final Color color = HexColor(unsplash_body[index]["color"]); + + print(unsplash_body[index]["color"]); + //print((username, userLink)); return [Image(image: CachedNetworkImageProvider(image_path), fit: BoxFit.cover, @@ -300,36 +317,38 @@ Future> _generatorPalette(Image imageWidget) async { final int imageHeight = imageInfo.image.height; final int imageWidth = imageInfo.image.height; - final int desiredSquare = 400; //approximation because the top half image cropped is almost a square + const int desiredSquare = 400; //approximation because the top half image cropped is almost a square - final double crop_x = desiredSquare / imageWidth; - final double crop_y = desiredSquare / imageHeight; + final double cropX = desiredSquare / imageWidth; + final double cropY = desiredSquare / imageHeight; - final double crop_absolute = max(crop_y, crop_x); + final double cropAbsolute = max(cropY, cropX); - final double center_x = imageWidth / 2; - final double center_y = imageHeight / 2; + final double centerX = imageWidth / 2; + final double centerY = imageHeight / 2; - final new_left = center_x - ((desiredSquare / 2) / crop_absolute); - final new_top = center_y - ((desiredSquare / 2) / crop_absolute); + final newLeft = centerX - ((desiredSquare / 2) / cropAbsolute); + final newTop = centerY - ((desiredSquare / 2) / cropAbsolute); - final double regionWidth = 50; - final double regionHeight = 50; + const double regionWidth = 50; + const double regionHeight = 50; final Rect region = Rect.fromLTWH( - new_left + (50 / crop_absolute), - new_top + (300 / crop_absolute), - (regionWidth / crop_absolute), - (regionHeight / crop_absolute), + newLeft + (50 / cropAbsolute), + newTop + (300 / cropAbsolute), + (regionWidth / cropAbsolute), + (regionHeight / cropAbsolute), ); PaletteGenerator _paletteGenerator = await PaletteGenerator.fromImage( imageInfo.image, region: region, - maximumColorCount: 4 + maximumColorCount: 4, + filters: [], ); PaletteGenerator _paletteGenerator2 = await PaletteGenerator.fromImage( imageInfo.image, - maximumColorCount: 5 + maximumColorCount: 3, + filters: [], ); imageProvider.resolve(const ImageConfiguration()).removeListener(listener); @@ -355,7 +374,7 @@ Future _materialPalette(Image imageWidget, theme, color) async { return ColorScheme.fromSeed( seedColor: color, brightness: theme == 'light' ? Brightness.light : Brightness.dark, - dynamicSchemeVariant: DynamicSchemeVariant.tonalSpot + dynamicSchemeVariant: DynamicSchemeVariant.tonalSpot, ); } @@ -416,6 +435,9 @@ class WeatherData { if (provider == 'weatherapi.com') { return WapiGetWeatherData(lat, lng, real_loc, settings, placeName); } + else if (provider == "met norway"){ + return MetNGetWeatherData(lat, lng, real_loc, settings, placeName); + } else { return OMGetWeatherData(lat, lng, real_loc, settings, placeName); } @@ -445,8 +467,6 @@ class RainviewerRadar { final String host = data["host"]; - //int timenow = DateTime.now().toUtc().microsecond; - List images = []; List times = []; @@ -460,7 +480,6 @@ class RainviewerRadar { } for (var x in future) { - //int dif = x["time"] * 1000 - timenow; DateTime time = DateTime.fromMillisecondsSinceEpoch(x["time"] * 1000); images.add(host + x["path"]); times.add("${time.hour}h ${time.minute}m"); diff --git a/lib/donation_page.dart b/lib/donation_page.dart index 30b774e..894ee47 100644 --- a/lib/donation_page.dart +++ b/lib/donation_page.dart @@ -215,7 +215,7 @@ class _InfoPageState extends State { ), ), Padding( - padding: const EdgeInsets.only(top: 40, bottom: 10), + padding: const EdgeInsets.only(top: 50, bottom: 10), child: comfortatext( translation("weather data:", settings["Language"]), 16, settings, @@ -242,6 +242,29 @@ class _InfoPageState extends State { child: comfortatext("weatherapi", 16, settings, color: primary, decoration: TextDecoration.underline), ), + GestureDetector( + onTap: () { + HapticFeedback.selectionClick(); + _launchUrl("https://api.met.no/"); + }, + child: comfortatext("met-norway", 16, settings, color: primary, + decoration: TextDecoration.underline), + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only(top: 30, bottom: 10), + child: comfortatext( + "${translation("radar", settings["Language"])}:", 16, + settings, + color: onSurface), + ), + Padding( + padding: EdgeInsets.only(left: 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ GestureDetector( onTap: () { HapticFeedback.selectionClick(); @@ -250,11 +273,19 @@ class _InfoPageState extends State { child: comfortatext("rainviewer", 16, settings, color: primary, decoration: TextDecoration.underline), ), + GestureDetector( + onTap: () { + HapticFeedback.selectionClick(); + _launchUrl("https://carto.com/"); + }, + child: comfortatext("carto", 16, settings, color: primary, + decoration: TextDecoration.underline), + ), ], ), ), Padding( - padding: EdgeInsets.only(top: 40), + padding: EdgeInsets.only(top: 50), child: Wrap( spacing: 10, children: [ diff --git a/lib/languages.dart b/lib/languages.dart index 4f25d51..20b4e69 100644 --- a/lib/languages.dart +++ b/lib/languages.dart @@ -1458,49 +1458,6 @@ Map> mainTranslate = { 'godz.', 'ώ', ], - - "Overmorrow 2.4.0 introduces Network images!": [ - 'Overmorrow 2.4.0 introduces Network images!', - 'Az Overmorrow 2.4.0 hálózati képeket vezet be!', - '¡Overmorrow 2.4.0 introduce imágenes de red!', - 'Overmorrow 2.4.0 introduit des images réseau !', - 'Overmorrow 2.4.0 führt Netzwerkbilder ein!', - 'Overmorrow 2.4.0 introduce immagini di rete!', - 'Overmorrow 2.4.0 introduz imagens de rede!', - 'Overmorrow 2.4.0 представляет сетевые изображения!', - 'Overmorrow 2.4.0 引入了网络图像!', - 'Overmorrow 2.4.0 にネットワーク画像を導入!', - 'Overmorrow 2.4.0 wprowadza obrazy z sieci!', - 'Το Overmorrow 2.4.0 εισάγει εικόνες δικτύου!' - ], - "Would you like to enable network images?": [ - 'Would you like to enable network images?', - 'Szeretnéd engedélyezni a hálózati képeket?', - '¿Te gustaría habilitar imágenes de red?', - 'Souhaitez-vous activer les images réseau ?', - 'Möchten Sie Netzwerkbilder aktivieren?', - 'Vuoi abilitare le immagini di rete?', - 'Gostaria de ativar as imagens de rede?', - 'Хотите включить сетевые изображения?', - '您想启用网络图像吗?', - 'ネットワーク画像を有効にしますか?', - 'Czy chcesz włączyć obrazy z sieci?', - 'Θέλετε να ενεργοποιήσετε τις εικόνες δικτύου;' - ], - "note: you can always change later by going into settings > appearance > image source": [ - 'note: you can always change later by going into settings > appearance > image source', - 'megjegyzés: később bármikor módosíthatja a beállítások > megjelenés > kép forrása menüpontban', - 'nota: siempre puedes cambiarlo más tarde en configuración > apariencia > fuente de imagen', - 'remarque : vous pouvez toujours modifier plus tard en allant dans paramètres > apparence > source d\'image', - 'Hinweis: Sie können dies später jederzeit unter Einstellungen > Erscheinungsbild > Bildquelle ändern', - 'nota: puoi sempre modificarlo in seguito andando su impostazioni > aspetto > sorgente immagine', - 'nota: você pode sempre mudar depois indo para configurações > aparência > fonte de imagem', - 'примечание: вы всегда можете изменить это позже в настройках > внешний вид > источник изображения', - '注意:您可以随时通过进入设置 > 外观 > 图片来源来更改', - '注意:設定 > 外観 > 画像のソース で後からいつでも変更できます', - 'uwaga: zawsze możesz to zmienić później, przechodząc do ustawień > wygląd > źródło obrazu', - 'σημείωση: μπορείτε πάντα να αλλάξετε αργότερα πηγαίνοντας στις ρυθμίσεις > εμφάνιση > πηγή εικόνας' - ], "Disable": [ 'Disable', 'Letiltás', @@ -1529,6 +1486,48 @@ Map> mainTranslate = { 'Włącz', 'Ενεργοποίηση' ], + "Layout": [ + 'Layout', + 'Elrendezés', + 'Diseño', + 'Disposition', + 'Layout', + 'Layout', + 'Layout', + 'Макет', + '布局', + 'レイアウト', + 'Układ', + 'Διάταξη' + ], + "widget order, customization": [ + 'widget order, customization', + 'widget sorrend, személyreszabás', + 'orden de widgets, personalización', + 'ordre des widgets, personnalisation', + 'Widget-Reihenfolge, Anpassung', + 'ordine dei widget, personalizzazione', + 'ordem de widgets, personalização', + 'порядок виджетов, настройка', + '小部件顺序,自定义', + 'ウィジェットの順序、カスタマイズ', + 'kolejność widżetów, dostosowanie', + 'σειρά widget, προσαρμογή' + ], + "30m": [ + '30m', + '30p', + '30m', + '30m', + '30m', + '30m', + '30m', + '30м', + '30分', + '30分', + '30m', + '30λ', + ], 'Search translation': [ //used for getting the codes used for translation of city names //If none are available then en (english) is the default diff --git a/lib/main_screens.dart b/lib/main_screens.dart index 004a03a..245b0c1 100644 --- a/lib/main_screens.dart +++ b/lib/main_screens.dart @@ -26,7 +26,6 @@ import 'package:overmorrow/radar.dart'; import 'package:overmorrow/settings_page.dart'; import 'package:material_floating_search_bar_2/material_floating_search_bar_2.dart'; import 'package:stretchy_header/stretchy_header.dart'; -import 'main.dart'; import 'main_ui.dart'; import 'new_displays.dart'; import 'ui_helper.dart'; @@ -53,13 +52,17 @@ class _NewMainState extends State { @override void initState() { super.initState(); + /* + i'm keeping this in case i need another pop-up sometime if (data.settings['networkImageDialogShown'] == "false") { WidgetsBinding.instance.addPostFrameCallback((_) { _showFeatureDialog(context, data.settings); }); } + */ } + /* void _showFeatureDialog(BuildContext context, settings) { showDialog( context: context, @@ -116,14 +119,30 @@ class _NewMainState extends State { }, ); } + */ @override Widget build(BuildContext context) { + final FlutterView view = WidgetsBinding.instance.platformDispatcher.views.first; final Size size = view.physicalSize / view.devicePixelRatio; final FloatingSearchBarController controller = FloatingSearchBarController(); + final Map widgetsMap = { + 'sunstatus': NewSunriseSunset(data: data, key: Key(data.place), size: size,), + 'rain indicator': NewRain15MinuteIndicator(data), + 'air quality': NewAirQuality(data), + 'radar': RadarSmall(data: data, key: Key("${data.place}, ${data.current.surface}")), + 'forecast': buildNewDays(data), + 'daily': buildNewGlanceDay(data: data), + }; + + final List order = data.settings["Layout order"].split(","); + print(order); + + final List orderedWidgets = order.map((name) => widgetsMap[name]!).toList(); + String colorMode = data.settings["Color mode"]; if (colorMode == "auto") { var brightness = SchedulerBinding.instance.platformDispatcher.platformBrightness; @@ -213,6 +232,7 @@ class _NewMainState extends State { ), ], ), + /* Padding( padding: const EdgeInsets.only(left: 30), @@ -241,12 +261,11 @@ class _NewMainState extends State { ), */ - NewSunriseSunset(data: data, key: Key(data.place), size: size,), - NewRain15MinuteIndicator(data), - NewAirQuality(data), - RadarSmall(data: data, key: Key("${data.place}, ${data.current.surface}")), - buildNewDays(data), - buildNewGlanceDay(data: data), + Column( + children: orderedWidgets.map((widget) { + return widget; + }).toList(), + ), Padding( padding: const EdgeInsets.only(top: 10, bottom: 30), diff --git a/lib/main_ui.dart b/lib/main_ui.dart index 9ae3bdb..45d9bfe 100644 --- a/lib/main_ui.dart +++ b/lib/main_ui.dart @@ -88,7 +88,7 @@ Widget Circles(double width, var data, double bottom, color, {align = Alignment. child: SizedBox( width: width, child: Container( - padding: const EdgeInsets.only(top: 25, left: 4, right: 4), + padding: const EdgeInsets.only(top: 25, left: 4, right: 4, bottom: 13), child: Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ @@ -142,7 +142,7 @@ Widget Circles(double width, var data, double bottom, color, {align = Alignment. Widget providerSelector(settings, updateLocation, textcolor, highlight, primary, provider, latlng, real_loc) { return Padding( - padding: const EdgeInsets.only(left: 23, right: 23, top: 15, bottom: 30), + padding: const EdgeInsets.only(left: 23, right: 23, bottom: 30, top: 5), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -157,7 +157,7 @@ Widget providerSelector(settings, updateLocation, textcolor, highlight, primary, ), ), Padding( - padding: const EdgeInsets.only(top: 10), + padding: const EdgeInsets.only(top: 12), child: Container( decoration: BoxDecoration( color: highlight, @@ -178,7 +178,7 @@ Widget providerSelector(settings, updateLocation, textcolor, highlight, primary, ), //value: selected_temp_unit.isNotEmpty ? selected_temp_unit : null, // guard it with null if empty value: provider.toString(), - items: ['weatherapi.com', 'open-meteo'].map((item) { + items: ['weatherapi.com', 'open-meteo', 'met norway'].map((item) { return DropdownMenuItem( value: item, child: Text(item), diff --git a/lib/new_displays.dart b/lib/new_displays.dart index c0ab1b8..e2fd451 100644 --- a/lib/new_displays.dart +++ b/lib/new_displays.dart @@ -138,7 +138,7 @@ class _NewSunriseSunsetState extends State with SingleTickerPr String write = widget.data.settings["Time mode"] == "24 hour" ? OMConvertTime( - "j T${localTime.hour}:${localTime.minute}") //the j is just added so when splitting + "j T${localTime.hour.toString().padLeft(2, "0")}:${localTime.minute.toString().padLeft(2, "0")}") //the j is just added so when splitting : OMamPmTime( "j T${localTime.hour}:${localTime.minute}"); //it can grab the second item @@ -157,7 +157,7 @@ class _NewSunriseSunsetState extends State with SingleTickerPr final textWidth = textPainter.width; return Padding( - padding: const EdgeInsets.only(left: 25, right: 25, top: 13), + padding: const EdgeInsets.only(left: 25, right: 25, bottom: 23), child: Column( children: [ Padding( @@ -243,7 +243,7 @@ class _NewSunriseSunsetState extends State with SingleTickerPr Widget NewAirQuality(var data) { return Padding( - padding: const EdgeInsets.only(left: 20, right: 20, bottom: 19, top: 23), + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 59), child: Column( children: [ Align( @@ -316,7 +316,7 @@ Widget NewRain15MinuteIndicator(var data) { return Visibility( visible: data.minutely_15_precip.t_minus != "", child: Padding( - padding: const EdgeInsets.only(left: 21, right: 21, top: 23, bottom: 15), + padding: const EdgeInsets.only(left: 21, right: 21, bottom: 38), child: Container( decoration: BoxDecoration( color: data.current.containerLow, @@ -357,7 +357,7 @@ Widget NewRain15MinuteIndicator(var data) { ], ), Padding( - padding: const EdgeInsets.only(top: 14, bottom: 10), + padding: const EdgeInsets.only(top: 14, bottom: 8), child: SizedBox( height: 30, child: ListView.builder( @@ -382,7 +382,6 @@ Widget NewRain15MinuteIndicator(var data) { ), ), SizedBox( - height: 10, width: 11.0 * data.minutely_15_precip.precips.length + 11, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, diff --git a/lib/new_forecast.dart b/lib/new_forecast.dart index 237cccb..3cf5002 100644 --- a/lib/new_forecast.dart +++ b/lib/new_forecast.dart @@ -99,25 +99,26 @@ class _NewDayState extends State with AutomaticKeepAliveClientMixin { ), ), Padding( - padding: const EdgeInsets.only(top: 15, left: 23, right: 25), + padding: const EdgeInsets.only(top: 18, left: 23, right: 25), child: Row( children: [ SizedBox( width: 35, child: Icon(day.icon, size: 38.0 * day.iconSize, color: data.current.primary,)), - Padding( - padding: const EdgeInsets.only(left: 12.0, top: 3), - child: comfortatext(day.text, 20, data.settings, color: data.current.onSurface, - weight: FontWeight.w400), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 12.0, top: 3), + child: comfortatext(day.text, 20, data.settings, color: data.current.onSurface, + weight: FontWeight.w400), + ), ), - const Spacer(), Padding( padding: const EdgeInsets.only(top: 4), child: Row( children: [ comfortatext(day.minmaxtemp.split("/")[0], 20, data.settings, color: data.current.primary), Padding( - padding: const EdgeInsets.only(left: 5, right: 4), + padding: const EdgeInsets.only(left: 5, right: 7), child: comfortatext("/", 19, data.settings, color: data.current.onSurface), ), comfortatext(day.minmaxtemp.split("/")[1], 20, data.settings, color: data.current.primary), @@ -267,7 +268,7 @@ class _NewDayState extends State with AutomaticKeepAliveClientMixin { ), ), SizedBox( - height: state? 280 : 250, + height: state? 280 : 260, child: PageView( physics: const NeverScrollableScrollPhysics(), controller: _pageController, @@ -286,7 +287,7 @@ class _NewDayState extends State with AutomaticKeepAliveClientMixin { Widget buildNewDays(data) { return ListView.builder( - padding: const EdgeInsets.only(top: 50, left: 20, right: 20), + padding: const EdgeInsets.only(left: 20, right: 20, bottom: 10), physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: 3, @@ -707,7 +708,7 @@ class _buildNewGlanceDayState extends State with AutomaticKee super.build(context); if (data.days.length > 3) { return Padding( - padding: const EdgeInsets.only(left: 24, right: 24, bottom: 10, top: 10), + padding: const EdgeInsets.only(left: 24, right: 24, bottom: 25), child: Column( children: [ Padding( diff --git a/lib/radar.dart b/lib/radar.dart index be49719..53d0856 100644 --- a/lib/radar.dart +++ b/lib/radar.dart @@ -28,6 +28,8 @@ import 'package:overmorrow/settings_page.dart'; import 'package:overmorrow/ui_helper.dart'; import 'package:latlong2/latlong.dart'; +import 'decoders/decode_OM.dart'; + class RadarSmall extends StatefulWidget { final data; @@ -64,7 +66,13 @@ class _RadarSmallState extends State { for (int i = 0; i < data.radar.times.length; i++) { List split = data.radar.times[i].split("h"); String minute = split[1].replaceAll(RegExp(r"\D"), ""); - times.add("${int.parse(split[0]) + offset}:${minute == "0" ? "00" : minute}"); + int hour = (int.parse(split[0]) + offset) % 24; + if (data.settings["Time mode"] == "12 hour") { + times.add(OMamPmTime("jT$hour:${minute == "0" ? "00" : minute}")); + } + else { + times.add("${hour.toString().padLeft(2, "0")}:${minute == "0" ? "00" : minute}"); + } } timer = Timer.periodic(const Duration(milliseconds: 1000), (Timer t) { @@ -112,7 +120,7 @@ class _RadarSmallState extends State { return Column( children: [ Padding( - padding: const EdgeInsets.only(left: 25, top: 40), + padding: const EdgeInsets.only(left: 25), child: Align( alignment: Alignment.centerLeft, child: comfortatext( @@ -123,7 +131,7 @@ class _RadarSmallState extends State { ), Padding( padding: const EdgeInsets.only( - left: 25, right: 25, top: 12, bottom: 10), + left: 25, right: 25, top: 12, bottom: 10,), child: AspectRatio( aspectRatio: 1.57, child: Container( @@ -216,7 +224,7 @@ class _RadarSmallState extends State { ), ), Padding( - padding: const EdgeInsets.only(left: 38, right: 25, bottom: 0, top: 10), + padding: const EdgeInsets.only(left: 38, right: 25, bottom: 50, top: 10), child: Row( children: [ AnimatedSwitcher( @@ -300,26 +308,26 @@ class _RadarSmallState extends State { padding: const EdgeInsets.only(left: 16, right: 16, top: 9), child: Row( children: [ - comfortatext('-2hr', 13, data.settings, color: data.current.onSurface), + comfortatext('-2${translation('hr', data.settings['Language'])}', 13, data.settings, color: data.current.onSurface), Expanded( flex: 6, child: Align( alignment: Alignment.centerRight, - child: comfortatext('-1hr', 13, data.settings, color: data.current.onSurface) + child: comfortatext('-1${translation('hr', data.settings['Language'])}', 13, data.settings, color: data.current.onSurface) ), ), Expanded( flex: 6, child: Align( alignment: Alignment.centerRight, - child: comfortatext('now', 13, data.settings, color: data.current.onSurface) + child: comfortatext(translation('now', data.settings["Language"]), 13, data.settings, color: data.current.onSurface) ), ), Expanded( flex: 3, child: Align( alignment: Alignment.centerRight, - child: comfortatext('30m', 13, data.settings, color: data.current.onSurface) + child: comfortatext(translation('30m', data.settings["Language"]), 13, data.settings, color: data.current.onSurface) ), ), ], @@ -372,7 +380,13 @@ class _RadarBigState extends State { for (int i = 0; i < data.radar.times.length; i++) { List split = data.radar.times[i].split("h"); String minute = split[1].replaceAll(RegExp(r"\D"), ""); - times.add("${int.parse(split[0]) + offset}:${minute == "0" ? "00" : minute}"); + int hour = (int.parse(split[0]) + offset) % 24; + if (data.settings["Time mode"] == "12 hour") { + times.add(OMamPmTime("jT$hour:${minute == "0" ? "00" : minute}")); + } + else { + times.add("${hour.toString().padLeft(2, "0")}:${minute == "0" ? "00" : minute}"); + } } @@ -549,26 +563,26 @@ class _RadarBigState extends State { padding: const EdgeInsets.only(left: 16, right: 16, top: 9), child: Row( children: [ - comfortatext('-2hr', 13, data.settings, color: data.current.onSurface), + comfortatext('-2${translation('hr', data.settings['Language'])}', 13, data.settings, color: data.current.onSurface), Expanded( flex: 6, child: Align( alignment: Alignment.centerRight, - child: comfortatext('-1hr', 13, data.settings, color: data.current.onSurface) + child: comfortatext('-1${translation('hr', data.settings['Language'])}', 13, data.settings, color: data.current.onSurface) ), ), Expanded( flex: 6, child: Align( alignment: Alignment.centerRight, - child: comfortatext('now', 13, data.settings, color: data.current.onSurface) + child: comfortatext(translation('now', data.settings["Language"]), 13, data.settings, color: data.current.onSurface) ), ), Expanded( flex: 3, child: Align( alignment: Alignment.centerRight, - child: comfortatext('30m', 13, data.settings, color: data.current.onSurface) + child: comfortatext(translation('30m', data.settings["Language"]), 13, data.settings, color: data.current.onSurface) ), ), ], diff --git a/lib/search_screens.dart b/lib/search_screens.dart index 8976def..4cfa759 100644 --- a/lib/search_screens.dart +++ b/lib/search_screens.dart @@ -491,8 +491,8 @@ class dumbySearch extends StatelessWidget { return Scaffold( drawer: MyDrawer(backupprimary: primary, settings: settings, backupback: back, image: Image.asset("assets/backdrops/grayscale_snow2.jpg", - fit: BoxFit.cover, width: double.infinity, height: double.infinity, color: colors[6],), surface: colors[0], - onSurface: colors[4], primary: colors[1], hihglight: colors[6], + fit: BoxFit.cover, width: double.infinity, height: double.infinity), surface: colors[0], + onSurface: colors[4], primary: colors[1], hihglight: colors[6] ), backgroundColor: colors[0], body: StretchyHeader.singleChild( diff --git a/lib/settings_page.dart b/lib/settings_page.dart index 48401ab..e1f1afd 100644 --- a/lib/settings_page.dart +++ b/lib/settings_page.dart @@ -47,6 +47,8 @@ Map> settingSwitches = { 'Search provider' : ['weatherapi', 'open-meteo'], 'networkImageDialogShown' : ["false", "true"], + + 'Layout order' : ["sunstatus,rain indicator,air quality,radar,forecast,daily"], }; String translation(String text, String language) { @@ -363,7 +365,12 @@ Future> getSettingsUsed() async { final prefs = await SharedPreferences.getInstance(); final ifnot = v.value[0]; final used = prefs.getString('setting${v.key}') ?? ifnot; - settings[v.key] = v.value.contains(used) ? used: ifnot; + if (v.value.length > 1) { //this is so that ones like the layout don't have to include all possible options + settings[v.key] = v.value.contains(used) ? used: ifnot; + } + else { + settings[v.key] = used; + } } return settings; } diff --git a/lib/settings_screens.dart b/lib/settings_screens.dart index 19a8016..79352a3 100644 --- a/lib/settings_screens.dart +++ b/lib/settings_screens.dart @@ -94,6 +94,10 @@ Widget NewSettings(Map settings, Function updatePage, Image imag Icons.pie_chart_outline, settings, UnitsPage(colors: colors, settings: settings, image: image, updateMainPage: updatePage), context, updatePage), + mainSettingEntry("Layout", "widget order, customization", containerLow, primary, onSurface, surface, + Icons.pie_chart_outline, settings, + LayoutPage(colors: colors, settings: settings, image: image, updateMainPage: updatePage), + context, updatePage), ], ), ); @@ -623,4 +627,118 @@ class _LangaugePageState extends State { ), ); } +} + + +class LayoutPage extends StatefulWidget { + final settings; + final image; + final colors; + final updateMainPage; + + const LayoutPage({Key? key, required this.colors, required this.settings, + required this.image, required this.updateMainPage}) + : super(key: key); + + @override + _LayoutPageState createState() => + _LayoutPageState(image: image, settings: settings, colors: colors, + updateMainPage: updateMainPage); +} + +class _LayoutPageState extends State { + + final image; + final settings; + final colors; + final updateMainPage; + + _LayoutPageState({required this.image, required this.settings, required this.colors, required this.updateMainPage}); + + late List _items; + + @override + void initState() { + super.initState(); + _items = settings["Layout order"].split(","); + } + + void updatePage(String name, String to) { + setState(() { + updateMainPage(name, to); + }); + } + + void goBack() { + HapticFeedback.selectionClick(); + Navigator.pop(context); + } + + @override + Widget build(BuildContext context) { + + Color highlight = colors[7]; + Color primaryLight = colors[2]; + Color primary = colors[1]; + Color onSurface = colors[4]; + Color surface = colors[0]; + + return Material( + color: surface, + child: CustomScrollView( + slivers: [ + SliverAppBar.large( + leading: IconButton( + icon: Icon(Icons.arrow_back, color: surface), + onPressed: () { + updatePage('Layout order', _items.join(",")); + goBack(); + }, + ), + title: comfortatext("Layout", 30, settings, color: surface), + backgroundColor: primary, + pinned: false, + ), + SliverToBoxAdapter( + child: ReorderableListView( + shrinkWrap: true, + padding: const EdgeInsets.only(left: 30, right: 30, top: 30, bottom: 50), + children: [ + for (int index = 0; index < _items.length; index += 1) + Container( + key: Key("$index"), + color: surface, + padding: const EdgeInsets.all(4), + child: Container( + decoration: BoxDecoration( + color: highlight, + borderRadius: BorderRadius.circular(18), + ), + height: 70, + padding: const EdgeInsets.only(top: 6, bottom: 6, left: 20, right: 20), + child: Row( + children: [ + comfortatext(_items[index], 19, settings, color: onSurface), + const Spacer(), + Icon(Icons.reorder_rounded, color: primaryLight, size: 21,), + ], + ), + ), + ), + ], + onReorder: (int oldIndex, int newIndex) { + setState(() { + if (oldIndex < newIndex) { + newIndex -= 1; + } + final String item = _items.removeAt(oldIndex); + _items.insert(newIndex, item); + }); + }, + ), + ), + ], + ), + ); + } } \ No newline at end of file diff --git a/lib/ui_helper.dart b/lib/ui_helper.dart index ac213e5..8839553 100644 --- a/lib/ui_helper.dart +++ b/lib/ui_helper.dart @@ -415,6 +415,14 @@ Widget RainWidget(data, day, highlight) { List precip = []; + //this is done because sometimes the provider doesn't return the past hours + // of the day so i just extend it to 24 + if (hours.length < 24) { + for (int i = 0; i < 24 - hours.length; i++) { + precip.add(0); + } + } + for (var i = 0; i < hours.length; i++) { double x = min(hours[i].precip, 10); precip.add(x); diff --git a/lib/weather_refact.dart b/lib/weather_refact.dart index 6081dc8..e104143 100644 --- a/lib/weather_refact.dart +++ b/lib/weather_refact.dart @@ -408,14 +408,14 @@ Map weatherConditionBiassTable = { 'Partly Cloudy': 6, 'Clear Sky': 5, 'Overcast': 4, - 'Haze': 3, - 'Rain': 11, // you can't go wrong by choosing the less extreme one + 'Haze': 8, + 'Rain': 31, 'Sleet': 8, - 'Drizzle': 9, - 'Thunderstorm': 14, // super rare - 'Heavy Snow': 12, - 'Fog': 7, + 'Drizzle': 25, + 'Thunderstorm': 35, // super rare + 'Heavy Snow': 30, + 'Fog': 10, 'Snow': 13, // you can't go wrong by choosing the less extreme one - 'Heavy Rain': 10, + 'Heavy Rain': 30, 'Cloudy Night' : 1, // you don't want night in the daily summary }; \ No newline at end of file diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index a40de73..b957da5 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -9,6 +9,7 @@ #include #include #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) dynamic_color_registrar = @@ -20,4 +21,7 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); + g_autoptr(FlPluginRegistrar) worldtime_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "WorldtimePlugin"); + worldtime_plugin_register_with_registrar(worldtime_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 6a5fb62..a01f1fc 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -6,6 +6,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color handy_window url_launcher_linux + worldtime ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index f60ac80..05e1d39 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -11,6 +11,7 @@ import path_provider_foundation import shared_preferences_foundation import sqflite import url_launcher_macos +import worldtime func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) @@ -19,4 +20,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) + WorldtimePlugin.register(with: registry.registrar(forPlugin: "WorldtimePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 996b2ee..2707223 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -821,6 +821,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + worldtime: + dependency: "direct main" + description: + name: worldtime + sha256: "752cfcb7a0c8dd640546202070c9bf517d80dbffb378f96da13be6e4d1239c79" + url: "https://pub.dev" + source: hosted + version: "1.2.2" xdg_directories: dependency: transitive description: @@ -846,5 +854,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.4.0 <3.7.0" flutter: ">=3.22.0" diff --git a/pubspec.yaml b/pubspec.yaml index cea3f28..06b02c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: # geolocator: ^10.1.0 geolocator: ^7.7.1 geocoding: ^3.0.0 + worldtime: ^1.2.2 flutter_cache_manager: ^3.3.1 flutter_map: ^6.1.0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 5ec53c0..b7ed608 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,10 +8,13 @@ #include #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); + WorldtimePluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("WorldtimePlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a311f66..59077a5 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color url_launcher_windows + worldtime ) list(APPEND FLUTTER_FFI_PLUGIN_LIST