adding first working widget

This commit is contained in:
Pranshul committed 2025-08-06 12:29:39 +05:30
1 parent eeda3fcea9
commit 06c76cfd12
24 files changed
+557 -246

No files matched your search

+2 -1
View File
@@ -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")
}
+28
View File
@@ -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
}
}
@@ -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" />
+23
View File
@@ -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
View File
@@ -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: () {
-20
View File
@@ -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 = {
+76
View File
@@ -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);
});
}
-203
View File
@@ -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
View File
@@ -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
View File
@@ -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
@@ -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
@@ -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