mirror of
https://github.com/PranshulGG/WeatherMaster.git
synced 2026-10-21 18:43:03 -05:00
adding first working widget
This commit is contained in:
1 parent
eeda3fcea9
commit
06c76cfd12
24 files changed
+557
-246
No files matched your search
@@ -72,5 +72,6 @@ flutter {
|
||||
dependencies {
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||
implementation("androidx.work:work-runtime-ktx:2.9.0")
|
||||
|
||||
implementation("androidx.glance:glance-appwidget:1.0.0-alpha05")
|
||||
implementation("com.google.android.material:material:1.12.0")
|
||||
}
|
||||
@@ -40,6 +40,34 @@
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<!-- WIDGETS -->
|
||||
|
||||
<receiver
|
||||
android:name="dev.fluttercommunity.plus.homewidget.HomeWidgetBackgroundReceiver"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
|
||||
<receiver android:name=".WeatherWidgetProvider" android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/weather_widget_info" />
|
||||
</receiver>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- WIDGETS END -->
|
||||
|
||||
|
||||
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.pranshulgg.weather_master_app.util
|
||||
|
||||
import com.pranshulgg.weather_master_app.R
|
||||
|
||||
object WeatherIconMapper {
|
||||
|
||||
private val iconMap = mapOf(
|
||||
// 0: Clear sky
|
||||
"0_1" to R.drawable.sunny,
|
||||
"0_0" to R.drawable.clear_night,
|
||||
|
||||
// 1,2,3: Mainly clear, partly cloudy, overcast
|
||||
"1_1" to R.drawable.mostly_sunny,
|
||||
"1_0" to R.drawable.mostly_clear_night,
|
||||
"2_1" to R.drawable.partly_cloudy,
|
||||
"2_0" to R.drawable.partly_cloudy_night,
|
||||
"3_1" to R.drawable.cloudy,
|
||||
"3_0" to R.drawable.cloudy,
|
||||
|
||||
// 45,48: Fog
|
||||
"45_1" to R.drawable.haze_fog_dust_smoke,
|
||||
"45_0" to R.drawable.haze_fog_dust_smoke,
|
||||
"48_1" to R.drawable.haze_fog_dust_smoke,
|
||||
"48_0" to R.drawable.haze_fog_dust_smoke,
|
||||
|
||||
// 51,53,55: Drizzle
|
||||
"51_1" to R.drawable.drizzle,
|
||||
"51_0" to R.drawable.drizzle,
|
||||
"53_1" to R.drawable.drizzle,
|
||||
"53_0" to R.drawable.drizzle,
|
||||
"55_1" to R.drawable.drizzle,
|
||||
"55_0" to R.drawable.drizzle,
|
||||
|
||||
// 56,57: Freezing Drizzle
|
||||
"56_1" to R.drawable.wintry_mix_rain_snow,
|
||||
"56_0" to R.drawable.wintry_mix_rain_snow,
|
||||
"57_1" to R.drawable.wintry_mix_rain_snow,
|
||||
"57_0" to R.drawable.wintry_mix_rain_snow,
|
||||
|
||||
// 61,63,65: Rain
|
||||
"61_1" to R.drawable.scattered_showers_day,
|
||||
"61_0" to R.drawable.scattered_showers_night,
|
||||
"63_1" to R.drawable.showers_rain,
|
||||
"63_0" to R.drawable.showers_rain,
|
||||
"65_1" to R.drawable.heavy_rain,
|
||||
"65_0" to R.drawable.heavy_rain,
|
||||
|
||||
// 66,67: Freezing Rain
|
||||
"66_1" to R.drawable.wintry_mix_rain_snow,
|
||||
"66_0" to R.drawable.wintry_mix_rain_snow,
|
||||
"67_1" to R.drawable.wintry_mix_rain_snow,
|
||||
"67_0" to R.drawable.wintry_mix_rain_snow,
|
||||
|
||||
// 71,73,75: Snowfall
|
||||
"71_1" to R.drawable.snow_showers_snow,
|
||||
"71_0" to R.drawable.heavy_snow,
|
||||
"73_1" to R.drawable.heavy_snow,
|
||||
"73_0" to R.drawable.heavy_snow,
|
||||
"75_1" to R.drawable.heavy_snow,
|
||||
"75_0" to R.drawable.heavy_snow,
|
||||
|
||||
// 77: Snow grains
|
||||
"77_1" to R.drawable.flurries,
|
||||
"77_0" to R.drawable.flurries,
|
||||
|
||||
// 80,81,82: Rain showers
|
||||
"80_1" to R.drawable.scattered_showers_day,
|
||||
"80_0" to R.drawable.scattered_showers_night,
|
||||
"81_1" to R.drawable.showers_rain,
|
||||
"81_0" to R.drawable.showers_rain,
|
||||
"82_1" to R.drawable.heavy_rain,
|
||||
"82_0" to R.drawable.heavy_rain,
|
||||
|
||||
// 85,86: Snow showers
|
||||
"85_1" to R.drawable.flurries,
|
||||
"85_0" to R.drawable.flurries,
|
||||
"86_1" to R.drawable.heavy_snow,
|
||||
"86_0" to R.drawable.heavy_snow,
|
||||
|
||||
// 95: Thunderstorm (no hail)
|
||||
"95_1" to R.drawable.isolated_scattered_tstorms_day,
|
||||
"95_0" to R.drawable.isolated_scattered_tstorms_night,
|
||||
|
||||
// 96,99: Thunderstorm with hail
|
||||
"96_1" to R.drawable.strong_tstorms,
|
||||
"96_0" to R.drawable.strong_tstorms,
|
||||
"99_1" to R.drawable.strong_tstorms,
|
||||
"99_0" to R.drawable.strong_tstorms
|
||||
)
|
||||
|
||||
|
||||
fun getIconResource(code: String?, isDay: String?): Int {
|
||||
val key = "${code ?: "-1"}_${isDay ?: "-1"}"
|
||||
return iconMap[key] ?: R.drawable.showers_rain // fallback
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package com.pranshulgg.weather_master_app
|
||||
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.widget.RemoteViews
|
||||
import com.pranshulgg.weather_master_app.util.WeatherIconMapper
|
||||
class WeatherWidgetProvider : AppWidgetProvider() {
|
||||
override fun onUpdate(context: Context, manager: AppWidgetManager, ids: IntArray) {
|
||||
for (id in ids) {
|
||||
val views = RemoteViews(context.packageName, R.layout.weather_widget)
|
||||
val prefs = context.getSharedPreferences("HomeWidgetPreferences", Context.MODE_PRIVATE)
|
||||
val temp = prefs.getString("temperatureCurrentPill", "--")
|
||||
val code = prefs.getString("weather_codeCurrentPill", "--")
|
||||
|
||||
|
||||
// Prevents crash when isDayWidget was stored as Boolean instead of String
|
||||
val rawIsDay = prefs.all["isDayWidget"]
|
||||
val isDay = when (rawIsDay) {
|
||||
is String -> rawIsDay
|
||||
is Boolean -> if (rawIsDay) "1" else "0"
|
||||
else -> "1"
|
||||
}
|
||||
|
||||
val max = prefs.getString("todayMax", "--")
|
||||
val min = prefs.getString("todayMin", "--")
|
||||
|
||||
views.setTextViewText(R.id.current_widget_temp_pill, "$temp°")
|
||||
views.setTextViewText(R.id.temp_high, "$max° •")
|
||||
views.setTextViewText(R.id.temp_low, " $min°")
|
||||
|
||||
val iconRes = WeatherIconMapper.getIconResource(code, isDay)
|
||||
views.setImageViewResource(R.id.widget_icon_current_pill, iconRes)
|
||||
|
||||
views.setImageViewResource(R.id.widget_icon_current_pill, iconRes)
|
||||
|
||||
manager.updateAppWidget(id, views)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<corners android:radius="999dp" />
|
||||
<solid android:color="@color/colorSurfaceContainer" />
|
||||
</shape>
|
||||
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/widget_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="120dp"
|
||||
android:background="@drawable/rounded_widget_bg"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_icon_current_pill"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginLeft="26dp"
|
||||
android:src="@drawable/clear_night" />
|
||||
|
||||
<!-- Temperature Text -->
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
|
||||
android:paddingRight="30dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_gravity="end|center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/current_widget_temp_pill"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="28°"
|
||||
android:textColor="@color/colorPrimary"
|
||||
android:textSize="50sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<!-- High and Low Temperature -->
|
||||
<LinearLayout
|
||||
android:id="@+id/temp_range"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/temp_high"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="30°"
|
||||
android:textColor="@color/colorSecondary"
|
||||
android:textSize="20sp"
|
||||
/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/temp_low"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text=" 21°"
|
||||
android:textColor="@color/colorOnSurface"
|
||||
android:textSize="20sp" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Primary Colors -->
|
||||
<color name="colorPrimary">@android:color/system_accent1_100</color>
|
||||
<color name="colorOnPrimary">@android:color/system_accent1_900</color>
|
||||
<color name="colorPrimaryContainer">@android:color/system_accent1_700</color>
|
||||
<color name="colorOnPrimaryContainer">@android:color/system_accent1_100</color>
|
||||
|
||||
<!-- Secondary Colors -->
|
||||
<color name="colorSecondary">@android:color/system_accent2_300</color>
|
||||
<color name="colorOnSecondary">@android:color/system_accent2_900</color>
|
||||
<color name="colorSecondaryContainer">@android:color/system_accent2_700</color>
|
||||
<color name="colorOnSecondaryContainer">@android:color/system_accent2_100</color>
|
||||
|
||||
<!-- Tertiary Colors -->
|
||||
<color name="colorTertiary">@android:color/system_accent3_300</color>
|
||||
<color name="colorOnTertiary">@android:color/system_accent3_900</color>
|
||||
<color name="colorTertiaryContainer">@android:color/system_accent3_700</color>
|
||||
<color name="colorOnTertiaryContainer">@android:color/system_accent3_100</color>
|
||||
|
||||
<!-- Neutral Colors -->
|
||||
<color name="colorBackground">@android:color/system_neutral1_900</color>
|
||||
<color name="colorOnBackground">@android:color/system_neutral1_100</color>
|
||||
<color name="colorSurface">@android:color/system_neutral1_700</color>
|
||||
<color name="colorOnSurface">@android:color/system_neutral1_100</color>
|
||||
|
||||
<!-- Surface Container -->
|
||||
<color name="colorSurfaceContainer">@android:color/system_accent2_800</color>
|
||||
<color name="colorSurfaceContainerLowest">@color/black</color>
|
||||
<color name="colorSurfaceContainerHigh">@color/material_dynamic_neutral40</color>
|
||||
|
||||
<!-- Surface Variant -->
|
||||
<color name="colorSurfaceVariant">@android:color/system_neutral2_700</color>
|
||||
<color name="colorOnSurfaceVariant">@android:color/system_neutral2_100</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,29 @@
|
||||
<resources>
|
||||
<color name="colorPrimary">#aac7ff</color>
|
||||
<color name="colorOnPrimary">#0a305f</color>
|
||||
<color name="colorPrimaryContainer">#284777</color>
|
||||
<color name="colorOnPrimaryContainer">#d6e3ff</color>
|
||||
|
||||
<color name="colorSecondary">#bec6dc</color>
|
||||
<color name="colorOnSecondary">#283141</color>
|
||||
<color name="colorSecondaryContainer">#3e4759</color>
|
||||
<color name="colorOnSecondaryContainer">#dae2f9</color>
|
||||
|
||||
<color name="colorTertiary">#ddbce0</color>
|
||||
<color name="colorOnTertiary">#3f2844</color>
|
||||
<color name="colorTertiaryContainer">#573e5c</color>
|
||||
<color name="colorOnTertiaryContainer">#fad8fd</color>
|
||||
|
||||
<color name="colorBackground">#111318</color>
|
||||
<color name="colorOnBackground">#e2e2e9</color>
|
||||
<color name="colorSurface">#111318</color>
|
||||
<color name="colorOnSurface">#e2e2e9</color>
|
||||
|
||||
<color name="colorSurfaceContainer">#1d2024</color>
|
||||
|
||||
<color name="colorSurfaceVariant">#111318</color>
|
||||
<color name="colorOnSurfaceVariant">#c4c6d0</color>
|
||||
|
||||
<color name="colorError">#ffb4ab</color>
|
||||
<color name="colorOnError">#690005</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Primary Colors -->
|
||||
<color name="colorPrimary">@android:color/system_accent1_600</color>
|
||||
<color name="colorOnPrimary">@android:color/system_accent1_0</color>
|
||||
<color name="colorPrimaryContainer">@android:color/system_accent1_100</color>
|
||||
<color name="colorOnPrimaryContainer">@android:color/system_accent1_900</color>
|
||||
|
||||
<!-- Secondary Colors -->
|
||||
<color name="colorSecondary">@android:color/system_accent2_600</color>
|
||||
<color name="colorOnSecondary">@android:color/system_accent2_0</color>
|
||||
<color name="colorSecondaryContainer">@android:color/system_accent2_100</color>
|
||||
<color name="colorOnSecondaryContainer">@android:color/system_accent2_900</color>
|
||||
|
||||
<!-- Tertiary Colors -->
|
||||
<color name="colorTertiary">@android:color/system_accent3_600</color>
|
||||
<color name="colorOnTertiary">@android:color/system_accent3_0</color>
|
||||
<color name="colorTertiaryContainer">@android:color/system_accent3_100</color>
|
||||
<color name="colorOnTertiaryContainer">@android:color/system_accent3_900</color>
|
||||
|
||||
<!-- Neutral Colors -->
|
||||
<color name="colorBackground">@android:color/system_neutral1_100</color>
|
||||
<color name="colorOnBackground">@android:color/system_neutral1_900</color>
|
||||
<color name="colorSurface">@android:color/system_neutral1_500</color>
|
||||
<color name="colorOnSurface">@android:color/system_neutral1_100</color>
|
||||
|
||||
<!-- Surface Container -->
|
||||
<color name="colorSurfaceContainer">@android:color/system_accent2_50</color>
|
||||
<color name="colorSurfaceContainerLowest">@color/white</color>
|
||||
<color name="colorSurfaceContainerHigh">@color/material_dynamic_neutral90</color>
|
||||
|
||||
<!-- Surface Variant -->
|
||||
<color name="colorSurfaceVariant">@android:color/system_neutral2_500</color>
|
||||
<color name="colorOnSurfaceVariant">@android:color/system_neutral1_500</color>
|
||||
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,35 @@
|
||||
<resources>
|
||||
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
|
||||
|
||||
<!-- default fallback -->
|
||||
<color name="colorPrimary">#415f91</color>
|
||||
<color name="colorOnPrimary">#ffffff</color>
|
||||
<color name="colorPrimaryContainer">#d6e3ff</color>
|
||||
<color name="colorOnPrimaryContainer">#001b3e</color>
|
||||
|
||||
<color name="colorSecondary">#565f71</color>
|
||||
<color name="colorOnSecondary">#ffffff</color>
|
||||
<color name="colorSecondaryContainer">#dae2f9</color>
|
||||
<color name="colorOnSecondaryContainer">#131c2b</color>
|
||||
|
||||
<color name="colorTertiary">#705575</color>
|
||||
<color name="colorOnTertiary">#ffffff</color>
|
||||
<color name="colorTertiaryContainer">#fad8fd</color>
|
||||
<color name="colorOnTertiaryContainer">#28132e</color>
|
||||
|
||||
<color name="colorBackground">#f9f9ff</color>
|
||||
<color name="colorOnBackground">#191c20</color>
|
||||
<color name="colorSurface">#f9f9ff</color>
|
||||
<color name="colorOnSurface">#191c20</color>
|
||||
|
||||
<color name="colorSurfaceContainer">#ededf4</color>
|
||||
|
||||
<color name="colorSurfaceVariant">#d9d9e0</color>
|
||||
<color name="colorOnSurfaceVariant">#44474e</color>
|
||||
|
||||
<color name="colorError">#ba1a1a</color>
|
||||
<color name="colorOnError">#ffffff</color>
|
||||
</resources>
|
||||
@@ -11,8 +11,38 @@
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
|
||||
<!-- <style name="Theme.widgetsTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorOnPrimary">@color/colorOnPrimary</item>
|
||||
<item name="colorPrimaryContainer">@color/colorPrimaryContainer</item>
|
||||
<item name="colorOnPrimaryContainer">@color/colorOnPrimaryContainer</item>
|
||||
|
||||
<item name="colorSecondary">@color/colorSecondary</item>
|
||||
<item name="colorOnSecondary">@color/colorOnSecondary</item>
|
||||
<item name="colorSecondaryContainer">@color/colorSecondaryContainer</item>
|
||||
<item name="colorOnSecondaryContainer">@color/colorOnSecondaryContainer</item>
|
||||
|
||||
<item name="colorTertiary">@color/colorTertiary</item>
|
||||
<item name="colorOnTertiary">@color/colorOnTertiary</item>
|
||||
<item name="colorTertiaryContainer">@color/colorTertiaryContainer</item>
|
||||
<item name="colorOnTertiaryContainer">@color/colorOnTertiaryContainer</item>
|
||||
|
||||
<item name="colorOnBackground">@color/colorOnBackground</item>
|
||||
<item name="colorSurface">@color/colorSurface</item>
|
||||
<item name="colorOnSurface">@color/colorOnSurface</item>
|
||||
|
||||
<item name="colorSurfaceVariant">@color/colorSurfaceVariant</item>
|
||||
<item name="colorOnSurfaceVariant">@color/colorOnSurfaceVariant</item>
|
||||
|
||||
<item name="colorSurfaceContainer">@color/colorSurfaceContainer</item>
|
||||
</style> -->
|
||||
|
||||
|
||||
</resources>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:minWidth="250dp"
|
||||
android:minHeight="120dp"
|
||||
android:updatePeriodMillis="0"
|
||||
android:initialLayout="@layout/weather_widget"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:widgetCategory="home_screen" />
|
||||
@@ -22,6 +22,8 @@ import '../services/data_backup_service.dart';
|
||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
import 'widget_background.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
|
||||
final CorePalette paletteStartScreen = CorePalette.of(const Color.fromARGB(255, 255, 196, 0).toARGB32());
|
||||
|
||||
@@ -69,6 +71,7 @@ final flutterSupportedLocales = easySupportedLocales
|
||||
.toList();
|
||||
|
||||
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await EasyLocalization.ensureInitialized();
|
||||
@@ -88,6 +91,26 @@ void main() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
Hive.init(dir.path);
|
||||
|
||||
// widget------
|
||||
|
||||
Workmanager().initialize(
|
||||
callbackDispatcherBG,
|
||||
);
|
||||
|
||||
Workmanager().registerPeriodicTask(
|
||||
"weatherAutoUpdateTask",
|
||||
"weatherUpdate",
|
||||
frequency: Duration(minutes: 15),
|
||||
constraints: Constraints(
|
||||
networkType: NetworkType.connected,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
|
||||
// widget ------
|
||||
|
||||
|
||||
final themeController = ThemeController();
|
||||
await themeController.initialize();
|
||||
await themeController.checkDynamicColorSupport();
|
||||
|
||||
+16
-13
@@ -60,7 +60,11 @@ import '../widgets/pollen_card.dart';
|
||||
import '../widgets/rain_block.dart';
|
||||
import '../widgets/top_summary_block.dart';
|
||||
import '../widgets/top_weather_card.dart';
|
||||
import '../widgets/alert_block.dart';
|
||||
|
||||
|
||||
// home widget
|
||||
import '../widget_background.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
|
||||
|
||||
class WeatherHome extends StatefulWidget {
|
||||
@@ -270,7 +274,7 @@ Future<void> saveLayoutConfig() async {
|
||||
} else if (isHomeLocation && lastUpdated != null) {
|
||||
final lastUpdateTime = DateTime.tryParse(lastUpdated);
|
||||
final now = DateTime.now();
|
||||
if (lastUpdateTime != null && now.difference(lastUpdateTime).inMinutes < 450) {
|
||||
if (lastUpdateTime != null && now.difference(lastUpdateTime).inMinutes < 4500) {
|
||||
_isAppFullyLoaded = true;
|
||||
} else{
|
||||
checkAndUpdateHomeLocation();
|
||||
@@ -811,6 +815,9 @@ Widget _buildWeatherContent() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
final raw = snapshot.data!;
|
||||
final weather = raw['data'];
|
||||
final current = weather['current'];
|
||||
@@ -881,7 +888,7 @@ void maybeUpdateWeatherAnimation(Map<String, dynamic> current) {
|
||||
|
||||
final useFullMaterialScheme = PreferencesHelper.getBool("OnlyMaterialScheme") ?? false;
|
||||
|
||||
|
||||
// updateHomeWidget();
|
||||
|
||||
|
||||
String formattedTime = lastUpdated != null
|
||||
@@ -969,6 +976,10 @@ for (int i = 0; i < allTimeStrings.length; i++) {
|
||||
}
|
||||
}
|
||||
|
||||
// updateHomeWidget();
|
||||
|
||||
|
||||
|
||||
final List<double> next2hPrecip = [];
|
||||
|
||||
for (int i = 0; i < timeNext12h.length; i++) {
|
||||
@@ -1009,10 +1020,6 @@ if (rainStart != null) {
|
||||
|
||||
final bool shouldShowRainBlock = bestStart != null && bestEnd != null;
|
||||
|
||||
final List<dynamic> alerts = (weather['alerts']?['alert'] as List?) ?? []; // Get alerts list or empty list if null
|
||||
final bool showAlerts = alerts.isNotEmpty; // True if there are any alerts
|
||||
|
||||
|
||||
|
||||
Widget buildLayoutBlock(LayoutBlockType type) {
|
||||
switch (type) {
|
||||
@@ -1145,6 +1152,7 @@ else
|
||||
|
||||
GestureDetector(
|
||||
onTap: () async {
|
||||
await Workmanager().cancelAll();
|
||||
|
||||
final result =
|
||||
await Navigator.of(context).push<Map<String, dynamic>>(
|
||||
@@ -1318,12 +1326,7 @@ else
|
||||
),
|
||||
WeatherFrogIconWidget(iconUrl: _iconUrlFroggy),
|
||||
const SizedBox(height: 12),
|
||||
|
||||
if(showAlerts && showAlertsPref)
|
||||
alertCard( weather['alerts']!, context),
|
||||
|
||||
shouldShowRainBlock ? SizedBox(height: 8.5,) : showInsightsRandomly ? SizedBox(height: 8.5,) : SizedBox.shrink(),
|
||||
|
||||
|
||||
|
||||
Column(
|
||||
children: () {
|
||||
|
||||
@@ -46,26 +46,12 @@ class WeatherService {
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
bool showAlerts = PreferencesHelper.getBool("showAlerts") ?? true;
|
||||
|
||||
// Prepare list of HTTP requests (always include weather and air quality)
|
||||
final requests = <Future<http.Response>>[
|
||||
http.get(uri),
|
||||
http.get(airQualityUri),
|
||||
];
|
||||
|
||||
// Conditionally add alerts request only if user wants them
|
||||
if (showAlerts && !isBackground) {
|
||||
final alertUri = Uri.parse('https://api.weatherapi.com/v1/alerts.json').replace(queryParameters: {
|
||||
'key': dotenv.env['API_KEY_WEATHERAPI']!.toString(),
|
||||
'q': '$lat,$lon',
|
||||
});
|
||||
requests.add(http.get(alertUri));
|
||||
}
|
||||
|
||||
// Execute all HTTP requests in parallel
|
||||
final responses = await Future.wait(requests);
|
||||
|
||||
|
||||
@@ -73,10 +59,6 @@ class WeatherService {
|
||||
final weatherData = json.decode(responses[0].body) as Map<String, dynamic>;
|
||||
final airQualityData = json.decode(responses[1].body) as Map<String, dynamic>;
|
||||
|
||||
// Conditionally decode alert data if it was fetched
|
||||
final alertData = showAlerts && responses.length > 2
|
||||
? json.decode(responses[2].body) as Map<String, dynamic>
|
||||
: {};
|
||||
|
||||
// Check if we need fallback data for missing fields
|
||||
Map<String, dynamic> finalWeatherData = weatherData;
|
||||
@@ -117,7 +99,6 @@ class WeatherService {
|
||||
final combinedDataForView = {
|
||||
...finalWeatherData,
|
||||
'air_quality': airQualityData,
|
||||
'alerts': alertData['alerts'] ?? [],
|
||||
};
|
||||
|
||||
final nowForView = DateTime.now().toIso8601String();
|
||||
@@ -155,7 +136,6 @@ class WeatherService {
|
||||
final combinedData = {
|
||||
...finalWeatherData,
|
||||
'air_quality': airQualityData,
|
||||
'alerts': alertData['alerts'] ?? [],
|
||||
};
|
||||
|
||||
final wrappedData = {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import 'services/fetch_data.dart';
|
||||
import 'utils/preferences_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
Future<void> updateHomeWidget() async {
|
||||
try {
|
||||
print('[WidgetUpdate] Starting updateHomeWidget');
|
||||
|
||||
await HomeWidget.setAppGroupId('com.pranshulgg.weather_master_app');
|
||||
print('[WidgetUpdate] Set AppGroupId');
|
||||
|
||||
|
||||
final weatherService = WeatherService();
|
||||
final homeLocation = PreferencesHelper.getJson('homeLocation');
|
||||
|
||||
final result = await weatherService.fetchWeather(
|
||||
homeLocation?['lat']!,
|
||||
homeLocation?['lon']!,
|
||||
locationName: homeLocation?['cacheKey'],
|
||||
context: null,
|
||||
isBackground: true
|
||||
);
|
||||
|
||||
final current = result!['data']['current'];
|
||||
final temp = current['temperature_2m'].toInt();
|
||||
final code = current['weather_code'];
|
||||
final daily = result!['data']['daily'];
|
||||
final maxTemp = daily['temperature_2m_max'][0];
|
||||
final minTemp = daily['temperature_2m_min'][0];
|
||||
final isDay = current['is_day'];
|
||||
|
||||
|
||||
// final temp = 200;
|
||||
// final code = 80;
|
||||
// final maxTemp = 33;
|
||||
// final minTemp = 40;
|
||||
// final isDay = 0;
|
||||
|
||||
await HomeWidget.saveWidgetData<String>('temperatureCurrentPill', temp.round().toString());
|
||||
await HomeWidget.saveWidgetData<String>('weather_codeCurrentPill', code.toString());
|
||||
await HomeWidget.saveWidgetData<String>('todayMax', maxTemp.round().toString());
|
||||
await HomeWidget.saveWidgetData<String>('todayMin', minTemp.round().toString());
|
||||
|
||||
await HomeWidget.saveWidgetData<String>('isDayWidget', isDay.toString());
|
||||
|
||||
await HomeWidget.updateWidget(name: 'WeatherWidgetProvider', iOSName: null);
|
||||
print('[WidgetUpdate] Called updateWidget');
|
||||
} catch (e, stack) {
|
||||
print('[WidgetUpdate][ERROR] $e');
|
||||
print(stack);
|
||||
}
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void callbackDispatcherBG() {
|
||||
Workmanager().executeTask((task, inputData) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
Hive.init(dir.path);
|
||||
|
||||
|
||||
await PreferencesHelper.init();
|
||||
await updateHomeWidget();
|
||||
|
||||
|
||||
return Future.value(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
Widget alertCard(Map<String, dynamic> data, BuildContext context) {
|
||||
List alerts = data['alert'] ?? [];
|
||||
|
||||
if (alerts.isEmpty) {
|
||||
return const Text("No alerts available.");
|
||||
}
|
||||
|
||||
final alert = alerts.first;
|
||||
|
||||
return GestureDetector(
|
||||
|
||||
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.fromLTRB(12, 0, 12, 0),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.errorContainer,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
constraints: BoxConstraints(
|
||||
minHeight: 65
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Container(
|
||||
width: 46,
|
||||
height: 46,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
borderRadius: BorderRadius.circular(50),
|
||||
|
||||
),
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.warning_amber,
|
||||
color: Theme.of(context).colorScheme.onError,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Expanded(
|
||||
child: Text(
|
||||
alert['headline'] ?? 'No headline',
|
||||
style: TextStyle(
|
||||
fontSize: 14.5,
|
||||
color: Theme.of(context).colorScheme.onErrorContainer,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => AlertsPage(data: data,)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
class AlertsPage extends StatelessWidget {
|
||||
final Map<String, dynamic> data;
|
||||
|
||||
const AlertsPage({super.key, required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
List alerts = data['alert'] ?? [];
|
||||
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar.large(
|
||||
titleSpacing: 0,
|
||||
automaticallyImplyLeading: false,
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
title: Text("weather_alerts".tr()),
|
||||
expandedTitleScale: 1.3,
|
||||
titlePadding: EdgeInsets.all(16),
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
expandedHeight: 100,
|
||||
scrolledUnderElevation: 1,
|
||||
actions: [
|
||||
IconButton(onPressed: () {Navigator.pop(context);}, icon: Icon(Icons.close, weight: 600,)),
|
||||
SizedBox(width: 5)
|
||||
],
|
||||
),
|
||||
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 0.7,
|
||||
child: Padding(padding: EdgeInsets.only(bottom: 10, left: 16, right: 16),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: 2),
|
||||
child: Icon(Icons.info_outline, color: Theme.of(context).colorScheme.onSurfaceVariant, size: 17,),
|
||||
),
|
||||
SizedBox(width: 5,),
|
||||
Expanded(
|
||||
child: Text(
|
||||
"Due to the API being in beta, alerts from other regions may occasionally appear",
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (alerts.isNotEmpty)
|
||||
...alerts.map<Widget>((alert) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(vertical: 6.0, horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerLow,
|
||||
borderRadius: BorderRadius.circular(18),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
alert['headline'] ?? 'No headline',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
Divider(),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
alert['desc']?.replaceAll('\n', '\n') ?? '',
|
||||
style: const TextStyle(fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
alert['instruction']?.replaceAll('\n', '\n') ?? '',
|
||||
style: const TextStyle(
|
||||
fontSize: 14,
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Divider(),
|
||||
Text(
|
||||
'Effective: ${formatDate(alert['effective'])}',
|
||||
style: TextStyle(fontSize: 15, color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
Text(
|
||||
'Expires: ${formatDate(alert['expires'])}',
|
||||
style: TextStyle(fontSize: 15, color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
|
||||
SizedBox(height: MediaQuery.of(context).padding.bottom + 10,)
|
||||
],
|
||||
),
|
||||
),
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
DateFormat dateFormat = DateFormat('EEEE, MMM d • h:mm a');
|
||||
|
||||
String formatDate(String dateStr) {
|
||||
try {
|
||||
DateTime dt = DateTime.parse(dateStr).toLocal();
|
||||
return dateFormat.format(dt);
|
||||
} catch (_) {
|
||||
return dateStr;
|
||||
}
|
||||
}
|
||||
+8
-8
@@ -1,14 +1,6 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
android_intent_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: android_intent_plus
|
||||
sha256: dfc1fd3a577205ae8f11e990fb4ece8c90cceabbee56fcf48e463ecf0bd6aae3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.3.0"
|
||||
animations:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -438,6 +430,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
home_widget:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: home_widget
|
||||
sha256: ad9634ef5894f3bac73f04d59e2e5151a39798f49985399fd928dadc828d974a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
+3
-1
@@ -64,7 +64,9 @@ dependencies:
|
||||
file_picker: ^10.2.0
|
||||
flutter_dotenv: ^5.2.1
|
||||
workmanager: ^0.9.0
|
||||
android_intent_plus: ^5.3.0
|
||||
home_widget: ^0.8.0
|
||||
|
||||
|
||||
|
||||
settings_tiles:
|
||||
path: ./settings_tiles_custom
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+8
@@ -0,0 +1,8 @@
|
||||
# C/C++ build system timings
|
||||
create_cxx_tasks
|
||||
create-initial-cxx-model
|
||||
create-module-model 10ms
|
||||
[gap of 101ms]
|
||||
create-initial-cxx-model completed in 111ms
|
||||
create_cxx_tasks completed in 116ms
|
||||
|
||||
+11
@@ -114,3 +114,14 @@ create_cxx_tasks
|
||||
[gap of 20ms]
|
||||
create_cxx_tasks completed in 209ms
|
||||
|
||||
# C/C++ build system timings
|
||||
create_cxx_tasks
|
||||
create-initial-cxx-model
|
||||
create-module-model 31ms
|
||||
create-variant-model 13ms
|
||||
create-ARMEABI_V7A-model 35ms
|
||||
[gap of 80ms]
|
||||
create-initial-cxx-model completed in 165ms
|
||||
[gap of 23ms]
|
||||
create_cxx_tasks completed in 195ms
|
||||
|
||||
Reference in new issue
Block a user