├── MODULE_LICENSE_APACHE2
├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── values
│ │ │ │ ├── themes.xml
│ │ │ │ ├── app_icon_background.xml
│ │ │ │ ├── attrs.xml
│ │ │ │ ├── styles.xml
│ │ │ │ ├── arrays.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── colors.xml
│ │ │ │ └── strings.xml
│ │ │ ├── values-night
│ │ │ │ ├── themes.xml
│ │ │ │ └── colors.xml
│ │ │ ├── .DS_Store
│ │ │ ├── drawable-hdpi
│ │ │ │ ├── ice.png
│ │ │ │ ├── fire.png
│ │ │ │ ├── flood.png
│ │ │ │ ├── wave.png
│ │ │ │ ├── windy.png
│ │ │ │ ├── blizzard.png
│ │ │ │ ├── nws_logo.png
│ │ │ │ ├── tornado.png
│ │ │ │ ├── winter.png
│ │ │ │ ├── blue_button.9.png
│ │ │ │ ├── grey_button.9.png
│ │ │ │ ├── red_button.9.png
│ │ │ │ ├── thunderstorm.png
│ │ │ │ ├── black_button.9.png
│ │ │ │ ├── orange_button.9.png
│ │ │ │ └── yellow_button.9.png
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── app_icon.png
│ │ │ │ ├── app_icon_round.png
│ │ │ │ └── app_icon_foreground.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── app_icon.png
│ │ │ │ ├── app_icon_round.png
│ │ │ │ └── app_icon_foreground.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── app_icon.png
│ │ │ │ ├── app_icon_round.png
│ │ │ │ └── app_icon_foreground.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── app_icon.png
│ │ │ │ ├── app_icon_round.png
│ │ │ │ └── app_icon_foreground.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── app_icon.png
│ │ │ │ ├── app_icon_round.png
│ │ │ │ └── app_icon_foreground.png
│ │ │ ├── xml
│ │ │ │ ├── backup_descriptor.xml
│ │ │ │ ├── network_security_config.xml
│ │ │ │ ├── data_extraction_rules.xml
│ │ │ │ └── alerts_widget_info.xml
│ │ │ ├── drawable
│ │ │ │ ├── semitransparent_background.xml
│ │ │ │ └── widget_frame.xml
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── app_icon.xml
│ │ │ │ └── app_icon_round.xml
│ │ │ ├── menu
│ │ │ │ ├── alerts_display_options.xml
│ │ │ │ └── detail.xml
│ │ │ ├── layout
│ │ │ │ ├── privacy_policy_fragment.xml
│ │ │ │ ├── alerts_widget_loading.xml
│ │ │ │ ├── spinner_layout.xml
│ │ │ │ ├── fragment_changelog.xml
│ │ │ │ ├── alerts_widget_list_item.xml
│ │ │ │ ├── main_activity.xml
│ │ │ │ ├── alert_types_fragment.xml
│ │ │ │ ├── alerts_display_fragment.xml
│ │ │ │ ├── about.xml
│ │ │ │ ├── alerts_widget_configure.xml
│ │ │ │ ├── alerts_widget.xml
│ │ │ │ ├── fragment_instructions.xml
│ │ │ │ ├── activity_alertdetail.xml
│ │ │ │ └── debug_fragment.xml
│ │ │ ├── layout-land
│ │ │ │ ├── alert_types_fragment.xml
│ │ │ │ ├── activity_alertdetail.xml
│ │ │ │ └── debug_fragment.xml
│ │ │ ├── raw
│ │ │ │ └── isrg_root_x1.crt
│ │ │ └── navigation
│ │ │ │ └── main_navigation.xml
│ │ ├── app_icon-playstore.png
│ │ ├── java
│ │ │ └── net
│ │ │ │ └── justdave
│ │ │ │ └── nwsweatheralertswidget
│ │ │ │ ├── objects
│ │ │ │ ├── NWSZone.kt
│ │ │ │ ├── NWSArea.kt
│ │ │ │ └── NWSAlert.kt
│ │ │ │ ├── JsonConfiguration.kt
│ │ │ │ ├── widget
│ │ │ │ ├── NWSWidgetService.kt
│ │ │ │ ├── DebugPrefs.kt
│ │ │ │ ├── NWSWidgetFactory.kt
│ │ │ │ ├── NWSWidgetPrefs.kt
│ │ │ │ └── NWSWidgetConfigureActivity.kt
│ │ │ │ ├── AlertsDisplayViewModel.kt
│ │ │ │ ├── PrivacyPolicyFragment.kt
│ │ │ │ ├── ChangelogFragment.kt
│ │ │ │ ├── DebugViewModel.kt
│ │ │ │ ├── AlertTypesFragment.kt
│ │ │ │ ├── MainActivity.kt
│ │ │ │ ├── Dialogs.kt
│ │ │ │ ├── BroadcastReceiver.kt
│ │ │ │ ├── AlertTypesAdapter.kt
│ │ │ │ ├── InstructionsFragment.kt
│ │ │ │ ├── DebugAlertsAdapter.kt
│ │ │ │ ├── AlertsDisplayFragment.kt
│ │ │ │ ├── AlertDetailActivity.kt
│ │ │ │ └── AlertsUpdateService.kt
│ │ ├── assets
│ │ │ ├── ABOUT.md
│ │ │ ├── PRIVACY.md
│ │ │ └── CHANGES.md
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── net
│ │ │ └── justdave
│ │ │ └── nwsweatheralertswidget
│ │ │ └── NWSAlertTest.kt
│ └── androidTest
│ │ └── java
│ │ └── net
│ │ └── justdave
│ │ └── nwsweatheralertswidget
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── settings.gradle
├── src-images
├── fire.xcf
├── wave.xcf
├── app_icon.xcf
├── blizzard.xcf
├── buttons.xcf
├── blowingsnow.png
├── app_icon_transparent.png
├── PlayStoreFeatureGraphic.xcf
├── SOURCES
├── zeimusu_Fire_Icon.svg
├── thundercloud.svg
├── icicles.svg
└── water1.svg
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── .gitignore
├── setup_hooks.sh
├── README.md
├── gradle.properties
├── .github
└── workflows
│ ├── checkin.yml
│ └── version_bump.yml
├── PRIVACY.md
├── gradlew.bat
├── gradlew
└── CHANGES.md
/MODULE_LICENSE_APACHE2:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name = "NWSWeatherAlertsWidget"
--------------------------------------------------------------------------------
/src-images/fire.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/src-images/fire.xcf
--------------------------------------------------------------------------------
/src-images/wave.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/src-images/wave.xcf
--------------------------------------------------------------------------------
/src-images/app_icon.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/src-images/app_icon.xcf
--------------------------------------------------------------------------------
/src-images/blizzard.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/src-images/blizzard.xcf
--------------------------------------------------------------------------------
/src-images/buttons.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/src-images/buttons.xcf
--------------------------------------------------------------------------------
/app/src/main/res/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/.DS_Store
--------------------------------------------------------------------------------
/src-images/blowingsnow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/src-images/blowingsnow.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/app_icon-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/app_icon-playstore.png
--------------------------------------------------------------------------------
/src-images/app_icon_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/src-images/app_icon_transparent.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ice.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/ice.png
--------------------------------------------------------------------------------
/src-images/PlayStoreFeatureGraphic.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/src-images/PlayStoreFeatureGraphic.xcf
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/fire.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/fire.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/flood.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/flood.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/wave.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/wave.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/windy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/windy.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/blizzard.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/blizzard.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/nws_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/nws_logo.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/tornado.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/tornado.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/winter.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/winter.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-hdpi/app_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-mdpi/app_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-xhdpi/app_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-xxhdpi/app_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/app_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-xxxhdpi/app_icon.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/blue_button.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/blue_button.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/grey_button.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/grey_button.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/red_button.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/red_button.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/thunderstorm.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/thunderstorm.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/app_icon_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-hdpi/app_icon_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/app_icon_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-mdpi/app_icon_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/app_icon_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-xhdpi/app_icon_round.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/black_button.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/black_button.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/orange_button.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/orange_button.9.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/yellow_button.9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/drawable-hdpi/yellow_button.9.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/app_icon_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-xxhdpi/app_icon_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/app_icon_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-xxxhdpi/app_icon_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/app_icon_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-hdpi/app_icon_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/app_icon_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-mdpi/app_icon_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/app_icon_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-xhdpi/app_icon_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/app_icon_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-xxhdpi/app_icon_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/app_icon_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/HEAD/app/src/main/res/mipmap-xxxhdpi/app_icon_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/values/app_icon_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #A1B8FE
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin
2 | /gen
3 | /.idea
4 | /app/app.iml
5 | /app/build
6 | /app/release
7 | /build
8 | /.gradle
9 | /README.md~
10 | /app/*.apk
11 | /local.properties
12 | /*.iml
13 | /.DS_Store
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_descriptor.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | @color/white
4 | @color/black
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/semitransparent_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Dec 02 21:05:20 EST 2025
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/app_icon.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/objects/NWSZone.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget.objects
2 |
3 | data class NWSZone(val id: String, val name: String) {
4 |
5 | //to display object as a string in spinner
6 | override fun toString(): String {
7 | return name
8 | }
9 | }
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/app_icon_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/objects/NWSArea.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget.objects
2 |
3 | data class NWSArea(val id: String, val name: String) {
4 |
5 | //to display object as a string in spinner
6 | override fun toString(): String {
7 | return name
8 | }
9 |
10 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/arrays.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | - @string/theme_semitransparent
5 | - @string/theme_light
6 | - @string/theme_dark
7 | - @string/theme_system
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/JsonConfiguration.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import kotlinx.serialization.json.Json
4 |
5 | /**
6 | * A shared, lenient JSON configuration for the entire app.
7 | * `ignoreUnknownKeys = true` makes the app resilient to new fields being added to the NWS API.
8 | */
9 | val lenientJson = Json { ignoreUnknownKeys = true }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/widget/NWSWidgetService.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget.widget
2 |
3 | import android.content.Intent
4 | import android.widget.RemoteViewsService
5 |
6 | class NWSWidgetService : RemoteViewsService() {
7 | override fun onGetViewFactory(intent: Intent): RemoteViewsFactory {
8 | return NWSWidgetFactory(applicationContext, intent)
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/widget_frame.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/alerts_display_options.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/privacy_policy_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/alerts_widget_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/detail.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/alerts_widget_info.xml:
--------------------------------------------------------------------------------
1 |
14 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/AlertsDisplayViewModel.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import androidx.lifecycle.ViewModel
6 |
7 | class AlertsDisplayViewModel : ViewModel() {
8 |
9 | private lateinit var nwsapi: NWSAPI
10 |
11 | init {
12 | Log.i("AlertsDisplayViewModel","Created!")
13 | }
14 |
15 | fun initializeContext(context: Context) {
16 | nwsapi = NWSAPI.getInstance(context)
17 | }
18 |
19 | override fun onCleared() {
20 | super.onCleared()
21 | Log.i("AlertsDisplayViewModel", "destroyed!")
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 3dp
5 | 3dp
6 | 4dp
7 | 4dp
8 | 4dp
9 | 4dp
10 |
11 |
15 | 0dp
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 | #000000
5 | #000000
6 | #FFFFFF
7 | #FFFFFF
8 |
9 |
10 | #FFFFFF
11 | #000000
12 | #55777777
13 |
14 |
15 |
16 |
17 | @color/black
18 | @color/white
19 |
20 |
--------------------------------------------------------------------------------
/app/src/test/java/net/justdave/nwsweatheralertswidget/NWSAlertTest.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import net.justdave.nwsweatheralertswidget.objects.NWSAlert
4 | import org.json.JSONObject
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 | import org.robolectric.RobolectricTestRunner
9 |
10 | @RunWith(RobolectricTestRunner::class)
11 | class NWSAlertTest {
12 | @Test
13 | fun parsing_isCorrect() {
14 | val fakeAlert =
15 | """{ "properties": {
16 | "headline": "Fake Headline",
17 | "description": "Fake Description"
18 | }}"""
19 | val alert = NWSAlert(JSONObject(fakeAlert))
20 | assertEquals("Fake Headline", alert.toString())
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/net/justdave/nwsweatheralertswidget/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("net.justdave.nwsweatheralertswidget", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/src-images/SOURCES:
--------------------------------------------------------------------------------
1 | Image sources:
2 |
3 | Ice: https://openclipart.org/detail/26214/icicles-by-anonymous
4 | Thunderstorm: https://openclipart.org/detail/167269/thundercloud-by-halattas
5 | Tornado: https://openclipart.org/detail/104887/tornado-by-laabadon
6 | Snow: https://openclipart.org/detail/65497/winter-by-laobc (trimmed by justdave)
7 | Flood: https://openclipart.org/detail/93091/water-by-photothailand
8 | Wind: https://openclipart.org/detail/170683/weather-icon---windy-by-gnokii-170683
9 | Blizzard: https://www.nws.noaa.gov/weather/images/fcicons/blowingsnow.png (upscaled and traced by justdave and combined with "Snow") - URL no longer works
10 | Fire: https://openclipart.org/detail/2242/fire-icon-by-zeimusu
11 | Wave: https://openclipart.org/detail/3166/crashing-wave-by-johnny_automatic
12 |
13 | Background buttons: https://openclipart.org/detail/75115/glossy-pill-buttons-by-inky2010
14 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/spinner_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/setup_hooks.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | HOOK_DIR=".git/hooks"
4 | PRE_COMMIT_HOOK="$HOOK_DIR/pre-commit"
5 |
6 | # Ensure the hooks directory exists
7 | mkdir -p "$HOOK_DIR"
8 |
9 | # Create the pre-commit hook script
10 | cat > "$PRE_COMMIT_HOOK" <
2 |
7 |
8 |
17 |
18 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/widget/DebugPrefs.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget.widget
2 |
3 | import android.content.Context
4 | import androidx.datastore.core.DataStore
5 | import androidx.datastore.preferences.core.Preferences
6 | import androidx.datastore.preferences.core.edit
7 | import androidx.datastore.preferences.core.stringPreferencesKey
8 | import androidx.datastore.preferences.preferencesDataStore
9 | import kotlinx.coroutines.flow.first
10 | import kotlinx.coroutines.flow.map
11 |
12 | private val Context.debugDataStore: DataStore by preferencesDataStore(name = "debug_settings")
13 |
14 | private val KEY_AREA = stringPreferencesKey("debug_area")
15 | private val KEY_ZONE = stringPreferencesKey("debug_zone")
16 |
17 | suspend fun saveDebugPrefs(context: Context, areaId: String, zoneId: String) {
18 | context.debugDataStore.edit {
19 | it[KEY_AREA] = areaId
20 | it[KEY_ZONE] = zoneId
21 | }
22 | }
23 |
24 | suspend fun loadDebugPrefs(context: Context): Pair {
25 | return context.debugDataStore.data.map {
26 | val area = it[KEY_AREA] ?: "us-all"
27 | val zone = it[KEY_ZONE] ?: "all"
28 | Pair(area, zone)
29 | }.first()
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/PrivacyPolicyFragment.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.TextView
8 | import androidx.fragment.app.Fragment
9 | import io.noties.markwon.Markwon
10 |
11 | class PrivacyPolicyFragment : Fragment() {
12 |
13 | override fun onCreateView(
14 | inflater: LayoutInflater,
15 | container: ViewGroup?,
16 | savedInstanceState: Bundle?
17 | ): View? {
18 | // Inflate the layout for this fragment
19 | return inflater.inflate(R.layout.privacy_policy_fragment, container, false)
20 | }
21 |
22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
23 | super.onViewCreated(view, savedInstanceState)
24 |
25 | val textView = view.findViewById(R.id.privacy_policy_text)
26 | val markdown = requireContext().assets.open("PRIVACY.md").bufferedReader().use { it.readText() }
27 |
28 | // get an instance of Markwon
29 | val markwon = Markwon.create(requireContext())
30 |
31 | // set markdown
32 | markwon.setMarkdown(textView, markdown)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | NWS Weather Alerts Widget
2 | =========================
3 |
4 | Android home screen widget to display current weather alerts from the US National Weather Service
5 |
6 | I created this because I wanted a tablet on the wall in my kitchen to display weather alerts on the screen, and for all the plethora of weather apps out there, I couldn't find one that showed anything more than a (!) icon on their widgets for alerts, and you had to click through to find out what they were. Some of them would put the alerts into the notification bar, but that wasn't much better. So this one displays a list of the current alerts right on the widget, and that is the only purpose of the widget. If there's more than fits, the list scrolls, and you can tap on an alert to open the full text of the alert in your web browser.
7 |
8 | There's also an app with a user interface to go with it, mostly for debugging, but also gets you instructions how to use it, and the About box and so forth. The app lets you look at the raw XML feed from the NWS, for example. That might eventually go away now that it's mostly working.
9 |
10 | If you feel like helping out or adding cool new features, I welcome pull requests. Feel free to file issues, too (and check the issue queue for bug reports and feature requests if you're looking for something to do).
11 |
--------------------------------------------------------------------------------
/app/src/main/res/layout-land/alert_types_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/alerts_widget_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
19 |
20 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/main_activity.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/assets/ABOUT.md:
--------------------------------------------------------------------------------
1 | Copyright 2014-2025 David D. Miller and [additional contributors](https://github.com/justdave/nwsweatheralertswidget/graphs/contributors).
2 |
3 | This app and its source code are licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0).
4 |
5 | This app is not affiliated with or endorsed by the National Weather Service. The weather data is provided by the [National Weather Service](https://www.weather.gov/), which is a public-domain source. The NWS logo is a trademark of the National Weather Service and its use does not imply endorsement.
6 |
7 | Special thanks to the developers of the following open-source libraries used in this app:
8 |
9 | * [Kotlin](https://kotlinlang.org/)
10 | * [AndroidX](https://developer.android.com/jetpack/androidx)
11 | * [Material Components for Android](https://github.com/material-components/material-components-android)
12 | * [Markwon](https://github.com/noties/Markwon)
13 |
14 | Useful links:
15 | * [Feature requests and bug reports](https://github.com/justdave/nwsweatheralertswidget/issues)
16 | * [Change log](https://github.com/justdave/nwsweatheralertswidget/releases)
17 | * [Image/icon credits](https://raw.githubusercontent.com/justdave/nwsweatheralertswidget/refs/heads/master/src-images/SOURCES)
18 | * ["NWS" name and logo usage guidelines](https://www.weather.gov/disclaimer)
19 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | android.nonTransitiveRClass=true
23 | android.nonFinalResIds=true
24 | org.gradle.warning.mode=all
25 | MAVEN_REPOSITORY=https://repo.maven.apache.org/maven2/
--------------------------------------------------------------------------------
/app/src/main/res/layout/alert_types_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/ChangelogFragment.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import android.os.Bundle
4 | import android.text.method.LinkMovementMethod
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.TextView
9 | import androidx.fragment.app.Fragment
10 | import io.noties.markwon.Markwon
11 |
12 | class ChangelogFragment : Fragment() {
13 |
14 | override fun onCreateView(
15 | inflater: LayoutInflater,
16 | container: ViewGroup?,
17 | savedInstanceState: Bundle?
18 | ): View? {
19 | return inflater.inflate(R.layout.fragment_changelog, container, false)
20 | }
21 |
22 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
23 | super.onViewCreated(view, savedInstanceState)
24 |
25 | val textView = view.findViewById(R.id.changelog_text)
26 | val markwon = Markwon.create(requireContext())
27 |
28 | try {
29 | val markdown = requireContext().assets.open("CHANGES.md").bufferedReader().use { it.readText() }
30 | markwon.setMarkdown(textView, markdown)
31 | textView.movementMethod = LinkMovementMethod.getInstance()
32 | } catch (e: Exception) {
33 | e.printStackTrace()
34 | textView.setText(R.string.changelog_error)
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.github/workflows/checkin.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: CI
4 |
5 | # Controls when the action will run.
6 | on:
7 | # Triggers the workflow on push or pull request events but only for the master branch
8 | push:
9 | branches: [ master ]
10 | pull_request:
11 | branches: [ master ]
12 |
13 | # Allows you to run this workflow manually from the Actions tab
14 | workflow_dispatch:
15 |
16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
17 | jobs:
18 | # This workflow contains a single job called "build"
19 | build:
20 | # The type of runner that the job will run on
21 | runs-on: ubuntu-latest
22 |
23 | # Steps represent a sequence of tasks that will be executed as part of the job
24 | steps:
25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
26 | - name: Checkout
27 | uses: actions/checkout@v4
28 |
29 | - name: Compare PRIVACY.md files
30 | run: diff PRIVACY.md app/src/main/assets/PRIVACY.md
31 |
32 | - name: set up JDK 17
33 | uses: actions/setup-java@v4
34 | with:
35 | java-version: 17
36 | distribution: 'temurin'
37 |
38 | - name: Build with Gradle
39 | id: build
40 | run: ./gradlew build
41 |
42 | - name: Run Unit Tests
43 | id: tests
44 | run: ./gradlew test
45 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/DebugViewModel.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import android.content.Context
4 | import android.util.Log
5 | import androidx.lifecycle.ViewModel
6 | import com.android.volley.Response
7 | import net.justdave.nwsweatheralertswidget.objects.NWSAlert
8 | import net.justdave.nwsweatheralertswidget.objects.NWSArea
9 | import net.justdave.nwsweatheralertswidget.objects.NWSZone
10 |
11 | class DebugViewModel : ViewModel() {
12 | private lateinit var nwsapi: NWSAPI
13 |
14 | init {
15 | Log.i("DebugViewModel","Created!")
16 | }
17 |
18 | fun initializeContext(context: Context) {
19 | nwsapi = NWSAPI.getInstance(context)
20 | }
21 |
22 | override fun onCleared() {
23 | super.onCleared()
24 | Log.i("DebugViewModel", "destroyed!")
25 | }
26 |
27 | fun getAreaPopupContent(listener: Response.Listener>) {
28 | return nwsapi.getAreas(listener)
29 | }
30 |
31 | fun getZonePopupContent(area: NWSArea, listener: Response.Listener>) {
32 | return nwsapi.getZones(area, listener)
33 | }
34 |
35 | fun getDebugContent(
36 | area: NWSArea,
37 | zone: NWSZone,
38 | listener: Response.Listener>,
39 | errorListener: Response.ErrorListener
40 | ) {
41 | return nwsapi.getActiveAlerts(area, zone, listener, errorListener)
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/PRIVACY.md:
--------------------------------------------------------------------------------
1 | # Privacy Policy for NWS Weather Alerts Widget
2 |
3 | **Last Updated: 2025-12-13**
4 |
5 | This Privacy Policy describes how your personal information is handled in the NWS Weather Alerts Widget for Android.
6 |
7 | **Information We Collect and Use**
8 |
9 | To provide weather alerts for your chosen location, the app sends your selected county or zone ID directly to the US National Weather Service (NWS) API. This is required to retrieve relevant alert information. Your device's IP address may also be visible to the NWS as part of this standard internet communication.
10 |
11 | We do **not** collect, log, store, or transmit any other personally identifiable information about you. All other data, including your widget configurations, is stored locally on your device and is never sent to our servers.
12 |
13 | **Permissions**
14 |
15 | The app requires the following permissions to function:
16 |
17 | * **Internet**: To download weather alert data from the NWS.
18 | * **Foreground Service**: To keep the alert checking service running in the background so your widgets stay up to date.
19 | * **Post Notifications**: To display the persistent notification required for the background service to run.
20 |
21 | **Changes to This Privacy Policy**
22 |
23 | We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.
24 |
25 | **Contact Us**
26 |
27 | If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us at playstoresupport@justdave.net.
28 |
--------------------------------------------------------------------------------
/app/src/main/assets/PRIVACY.md:
--------------------------------------------------------------------------------
1 | # Privacy Policy for NWS Weather Alerts Widget
2 |
3 | **Last Updated: 2025-12-13**
4 |
5 | This Privacy Policy describes how your personal information is handled in the NWS Weather Alerts Widget for Android.
6 |
7 | **Information We Collect and Use**
8 |
9 | To provide weather alerts for your chosen location, the app sends your selected county or zone ID directly to the US National Weather Service (NWS) API. This is required to retrieve relevant alert information. Your device's IP address may also be visible to the NWS as part of this standard internet communication.
10 |
11 | We do **not** collect, log, store, or transmit any other personally identifiable information about you. All other data, including your widget configurations, is stored locally on your device and is never sent to our servers.
12 |
13 | **Permissions**
14 |
15 | The app requires the following permissions to function:
16 |
17 | * **Internet**: To download weather alert data from the NWS.
18 | * **Foreground Service**: To keep the alert checking service running in the background so your widgets stay up to date.
19 | * **Post Notifications**: To display the persistent notification required for the background service to run.
20 |
21 | **Changes to This Privacy Policy**
22 |
23 | We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page.
24 |
25 | **Contact Us**
26 |
27 | If you have any questions or suggestions about our Privacy Policy, do not hesitate to contact us at playstoresupport@justdave.net.
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/alerts_display_fragment.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
15 |
16 |
22 |
23 |
24 |
25 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/AlertTypesFragment.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import android.os.Bundle
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import androidx.fragment.app.Fragment
8 | import androidx.recyclerview.widget.LinearLayoutManager
9 | import androidx.recyclerview.widget.RecyclerView
10 | import kotlinx.coroutines.CoroutineScope
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.launch
13 |
14 | class AlertTypesFragment : Fragment() {
15 |
16 | private lateinit var nwsapi: NWSAPI
17 | private lateinit var recyclerView: RecyclerView
18 |
19 | override fun onCreateView(
20 | inflater: LayoutInflater,
21 | container: ViewGroup?,
22 | savedInstanceState: Bundle?
23 | ): View? {
24 | // Inflate the layout for this fragment
25 | val view = inflater.inflate(R.layout.alert_types_fragment, container, false)
26 | recyclerView = view.findViewById(R.id.alert_types_recycler_view)
27 | recyclerView.layoutManager = LinearLayoutManager(context)
28 | return view
29 | }
30 |
31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
32 | super.onViewCreated(view, savedInstanceState)
33 |
34 | nwsapi = NWSAPI.getInstance(requireContext())
35 |
36 | CoroutineScope(Dispatchers.Main).launch {
37 | val alertTypes = nwsapi.getAlertTypes()
38 | recyclerView.adapter = AlertTypesAdapter(alertTypes)
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import android.content.Intent
4 | import android.os.Build
5 | import android.os.Bundle
6 | import androidx.appcompat.app.AppCompatActivity
7 | import androidx.navigation.NavController
8 | import androidx.navigation.findNavController
9 | import androidx.navigation.ui.NavigationUI
10 |
11 | class MainActivity : AppCompatActivity() {
12 |
13 | private lateinit var navController : NavController
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 | setContentView(R.layout.main_activity)
18 | setSupportActionBar(findViewById(R.id.my_toolbar))
19 | supportActionBar?.setDisplayHomeAsUpEnabled(true)
20 | //supportActionBar?.setLogo(R.mipmap.app_icon)
21 |
22 | // As a developer convenience, start the service if it isn't already running.
23 | if (!AlertsUpdateService.isRunning) {
24 | val serviceIntent = Intent(this, AlertsUpdateService::class.java).apply {
25 | addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
26 | }
27 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
28 | startForegroundService(serviceIntent)
29 | } else {
30 | startService(serviceIntent)
31 | }
32 | }
33 | }
34 |
35 | override fun onSupportNavigateUp(): Boolean {
36 | return navController.navigateUp()
37 | }
38 |
39 | override fun onPostCreate(
40 | savedInstanceState: Bundle?
41 | ) {
42 | super.onPostCreate(savedInstanceState)
43 | navController = this.findNavController(R.id.my_nav_host_fragment)
44 | NavigationUI.setupActionBarWithNavController(this, navController)
45 | //NavigationUI.setupWithNavController(navigationView, navController)
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/Dialogs.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import android.app.AlertDialog
4 | import android.content.Context
5 | import android.text.method.LinkMovementMethod
6 | import android.view.LayoutInflater
7 | import android.widget.TextView
8 | import androidx.core.content.pm.PackageInfoCompat
9 | import androidx.navigation.NavController
10 | import io.noties.markwon.Markwon
11 |
12 | fun showAboutDialog(context: Context, navController: NavController) {
13 | val builder = AlertDialog.Builder(context)
14 | val inflater = LayoutInflater.from(context)
15 | val view = inflater.inflate(R.layout.about, null)
16 |
17 | val versionView = view.findViewById(R.id.version_string)
18 | val aboutView = view.findViewById(R.id.info_text)
19 |
20 | // Set version string
21 | val info = context.packageManager.getPackageInfo(context.packageName, 0)
22 | versionView.text = context.getString(R.string.about_version, info.versionName, PackageInfoCompat.getLongVersionCode(info))
23 |
24 | // Set about text and make links clickable
25 | val markdown = context.assets.open("ABOUT.md").bufferedReader().use { it.readText() }
26 | val markwon = Markwon.create(context)
27 | markwon.setMarkdown(aboutView, markdown)
28 | aboutView.movementMethod = LinkMovementMethod.getInstance()
29 |
30 | builder.setTitle(R.string.action_about)
31 | builder.setView(view)
32 |
33 | builder.setPositiveButton(R.string.ok) { dialog, _ ->
34 | dialog.dismiss()
35 | }
36 |
37 | builder.setNeutralButton("Privacy Policy") { dialog, _ ->
38 | navController.navigate(R.id.privacyPolicyFragment)
39 | dialog.dismiss()
40 | }
41 |
42 | builder.setNegativeButton("Changelog") { dialog, _ ->
43 | navController.navigate(R.id.changelogFragment)
44 | dialog.dismiss()
45 | }
46 |
47 | builder.create().show()
48 | }
49 |
--------------------------------------------------------------------------------
/.github/workflows/version_bump.yml:
--------------------------------------------------------------------------------
1 | name: Bump Version Suffix
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | bump-version:
9 | # Only run on tags that look like a version number
10 | if: startsWith(github.ref, 'refs/tags/v')
11 | runs-on: ubuntu-latest
12 | steps:
13 | - name: Checkout code
14 | uses: actions/checkout@v4
15 |
16 | - name: Get version from tag
17 | id: get_version
18 | run: echo "VERSION=${{ github.event.release.tag_name }}" | sed 's/^v//' >> $GITHUB_ENV
19 |
20 | - name: Update CHANGES.md
21 | run: |
22 | # Insert a new "Unreleased Changes" section after the main title
23 | sed -i'.bak' '/^# NWS Weather Alerts Widget Change History/a\
24 | \
25 | ## Unreleased Changes\
26 | \
27 | * ' CHANGES.md
28 |
29 | - name: Update versionName in build.gradle
30 | # This script appends a '+' to the end of the versionName string
31 | run: |
32 | awk '/versionName/ {sub(/\x27$/, "+\x27")} 1' app/build.gradle > app/build.gradle.tmp && mv app/build.gradle.tmp app/build.gradle
33 |
34 | - name: Increment versionCode
35 | run: |
36 | awk '/versionCode/ {match($0, /[0-9]+/); num=substr($0, RSTART, RLENGTH); sub(num, num+1)} 1' app/build.gradle > app/build.gradle.tmp && mv app/build.gradle.tmp app/build.gradle
37 |
38 | - name: Configure Git
39 | run: |
40 | git config user.name "github-actions[bot]"
41 | git config user.email "github-actions[bot]@users.noreply.github.com"
42 |
43 | - name: Commit and push changes
44 | env:
45 | NEW_VERSION: ${{ env.VERSION }}+
46 | run: |
47 | git add app/build.gradle CHANGES.md
48 | git commit -m "updating version to $NEW_VERSION for development"
49 | # Pushing to the 'master' branch as used in other workflows
50 | git push origin HEAD:master
51 |
--------------------------------------------------------------------------------
/app/src/main/res/raw/isrg_root_x1.crt:
--------------------------------------------------------------------------------
1 | -----BEGIN CERTIFICATE-----
2 | MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw
3 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
4 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4
5 | WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu
6 | ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY
7 | MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc
8 | h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+
9 | 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U
10 | A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW
11 | T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH
12 | B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC
13 | B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv
14 | KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn
15 | OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn
16 | jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw
17 | qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI
18 | rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV
19 | HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq
20 | hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL
21 | ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ
22 | 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK
23 | NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5
24 | ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur
25 | TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC
26 | jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc
27 | oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq
28 | 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA
29 | mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d
30 | emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=
31 | -----END CERTIFICATE-----
32 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/BroadcastReceiver.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import android.app.AlarmManager
4 | import android.app.PendingIntent
5 | import android.content.BroadcastReceiver
6 | import android.content.Context
7 | import android.content.Intent
8 | import android.os.Build
9 | import android.os.SystemClock
10 | import android.util.Log
11 |
12 | class BroadcastReceiver : BroadcastReceiver() {
13 | override fun onReceive(context: Context, intent: Intent) {
14 | Log.i("BroadcastReceiver", "Intent received: ".plus(intent.action))
15 |
16 | // Schedule an immediate alarm to kick off the update chain.
17 | val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
18 | val serviceIntent = Intent(context, AlertsUpdateService::class.java).apply {
19 | addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
20 | }
21 |
22 | // Use getService instead of getForegroundService. This allows the service to start
23 | // as a background service (whitelisted by the exact alarm) without creating a
24 | // strict contract to become a foreground service immediately, avoiding crashes
25 | // if startForeground() is restricted (e.g. dataSync type at boot on Android 14+).
26 | val pendingIntent = PendingIntent.getService(context, 0, serviceIntent, PendingIntent.FLAG_IMMUTABLE)
27 |
28 | // How long to wait before triggering the initial update task
29 | val triggerTime = SystemClock.elapsedRealtime()
30 |
31 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || alarmManager.canScheduleExactAlarms()) {
32 | Log.i("BroadcastReceiver","Exactly scheduling initial update task in 0 seconds.")
33 | alarmManager.setExactAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, pendingIntent)
34 | } else {
35 | Log.i("BroadcastReceiver","Inexactly setting initial update task in 0 seconds.")
36 | alarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerTime, pendingIntent)
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/AlertTypesAdapter.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.ImageView
7 | import android.widget.TextView
8 | import androidx.recyclerview.widget.RecyclerView
9 | import net.justdave.nwsweatheralertswidget.objects.NWSAlert
10 |
11 | class AlertTypesAdapter(private val alertTypes: List) :
12 | RecyclerView.Adapter() {
13 |
14 | /**
15 | * Provide a reference to the type of views that you are using
16 | * (custom ViewHolder).
17 | */
18 | class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
19 | val textView: TextView
20 | val imageView: ImageView
21 |
22 | init {
23 | // Define click listener for the ViewHolder's View.
24 | textView = view.findViewById(R.id.alert_item_text)
25 | imageView = view.findViewById(R.id.alert_item_icon)
26 | }
27 | }
28 |
29 | // Create new views (invoked by the layout manager)
30 | override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
31 | // Create a new view, which defines the UI of the list item
32 | val view = LayoutInflater.from(viewGroup.context)
33 | .inflate(R.layout.alerts_widget_list_item, viewGroup, false)
34 |
35 | return ViewHolder(view)
36 | }
37 |
38 | // Replace the contents of a view (invoked by the layout manager)
39 | override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
40 |
41 | // Get element from your dataset at this position and replace the
42 | // contents of the view with that element
43 | val alert = NWSAlert().copy(event = alertTypes[position])
44 | viewHolder.textView.text = alert.event
45 | viewHolder.imageView.setImageResource(alert.getIcon())
46 | viewHolder.itemView.setBackgroundResource(alert.getBackground())
47 | }
48 |
49 | // Return the size of your dataset (invoked by the layout manager)
50 | override fun getItemCount() = alertTypes.size
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/net/justdave/nwsweatheralertswidget/InstructionsFragment.kt:
--------------------------------------------------------------------------------
1 | package net.justdave.nwsweatheralertswidget
2 |
3 | import android.app.AlarmManager
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Build
7 | import android.os.Bundle
8 | import android.provider.Settings
9 | import android.view.LayoutInflater
10 | import android.view.View
11 | import android.view.ViewGroup
12 | import android.widget.Button
13 | import androidx.cardview.widget.CardView
14 | import androidx.fragment.app.Fragment
15 | import androidx.navigation.fragment.findNavController
16 |
17 | class InstructionsFragment : Fragment() {
18 | override fun onCreateView(
19 | inflater: LayoutInflater,
20 | container: ViewGroup?,
21 | savedInstanceState: Bundle?
22 | ): View? {
23 | // Inflate the layout for this fragment
24 | return inflater.inflate(R.layout.fragment_instructions, container, false)
25 | }
26 |
27 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
28 | super.onViewCreated(view, savedInstanceState)
29 |
30 | view.findViewById