diff --git a/CLAUDE.md b/CLAUDE.md index 4270b6e..7970825 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -38,7 +38,7 @@ export JAVA_HOME=~/jdk21 # Gradle 8.13, compileSdk/targetSdk 36, minSdk 2 ## Release flow (see git history for the v0.3.x pattern) 1. Bump `versionCode`/`versionName` in `app/build.gradle.kts` - (currently 37 / 0.3.7). + (currently 38 / 0.3.8). 2. Build both flavors, then sign each: ```bash @@ -46,9 +46,9 @@ PASS=$(cat ~/android-keystores/easylauncher-release.pass) ~/android-sdk/build-tools/36.0.0/apksigner sign --v4-signing-enabled true \ --ks ~/android-keystores/easylauncher-release.jks --ks-key-alias easylauncher \ --ks-pass "pass:$PASS" --key-pass "pass:$PASS" \ - --out dist/EasyLauncher-Internet-v0.3.7-Signed.apk \ + --out dist/EasyLauncher-Internet-v0.3.8-Signed.apk \ app/build/outputs/apk/withInternet/release/app.easy.launcher_v0.3.7-Release.apk -# repeat for withoutInternet -> dist/EasyLauncher-v0.3.7-Signed.apk +# repeat for withoutInternet -> dist/EasyLauncher-v0.3.8-Signed.apk ``` 3. `apksigner verify --print-certs` should show the keystore SHA-256. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2a27e09..855f745 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -19,8 +19,8 @@ android { applicationId = "app.easy.launcher" minSdk = 24 targetSdk = 36 - versionCode = 37 - versionName = "0.3.7" + versionCode = 38 + versionName = "0.3.8" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" manifestPlaceholders["internetPermission"] = "android.permission.INTERNET" diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index ebb6b91..c50478c 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -26,5 +26,8 @@ -keep class com.github.droidworksstudio.launcher.helper.weather.Main { *; } -keep class com.github.droidworksstudio.launcher.helper.weather.Weather { *; } +# Keep the shared met-weather library classes (Gson DTOs must keep field names) +-keep class dk.haugesenspil.metweather.** { *; } + # Keep all model classes and their fields that Gson might use for serialization/deserialization -keep class com.github.droidworksstudio.launcher.helper.weather.** { *; } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt b/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt index a0f6ede..33723bc 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/helper/AppHelper.kt @@ -59,6 +59,7 @@ class AppHelper @Inject constructor() { private companion object { /** MET requires a descriptive User-Agent identifying the app + contact. */ const val MET_USER_AGENT = "app.easy.launcher (https://gitea.haugesenspil.dk/jonas/EasyLauncher)" + private const val FOUR_HOURS_MILLIS = 4L * 60 * 60 * 1000 } @SuppressLint("WrongConstant", "PrivateApi") @@ -562,7 +563,13 @@ class AppHelper @Inject constructor() { data class CachedWeatherData(val timestamp: Long, val weatherResponse: WeatherResponse) sealed class MetWeatherResult { - data class Success(val temperature: Int, val symbolCode: String) : MetWeatherResult() + data class Success( + val temperature: Int, + val symbolCode: String, + val temperature4h: Int? = null, + val symbol4h: String? = null, + ) : MetWeatherResult() + data class Failure(val errorMessage: String) : MetWeatherResult() } @@ -592,9 +599,15 @@ class AppHelper @Inject constructor() { return try { val weather = MetWeatherClient(MET_USER_AGENT) .fetch(latitude.toDouble(), longitude.toDouble()) + val now = System.currentTimeMillis() + val plus4 = weather.hourly.minByOrNull { + kotlin.math.abs(it.epochMillis - (now + FOUR_HOURS_MILLIS)) + } val result = MetWeatherResult.Success( - weather.temperatureC.roundToInt(), - weather.symbolCode, + temperature = weather.temperatureC.roundToInt(), + symbolCode = weather.symbolCode, + temperature4h = plus4?.temperatureC?.roundToInt(), + symbol4h = plus4?.symbolCode, ) context.cacheMetWeather(result) result @@ -605,11 +618,21 @@ class AppHelper @Inject constructor() { } /** - * Builds the home-screen weather text: nerd-font weather glyph followed - * by the temperature in Celsius, e.g. "\uE312 16°". + * Builds the home-screen weather text: the current nerd-font weather glyph + * and Celsius value, plus the reading ~4 hours out when available, e.g. + * "\uE302 16° → \uE31D 18°". */ - fun buildCurrentWeatherText(temperature: Int, symbolCode: String): String { - return "${MetSymbolMapper.glyphFor(symbolCode)} $temperature°" + fun buildCurrentWeatherText( + temperature: Int, + symbolCode: String, + temperature4h: Int? = null, + symbol4h: String? = null, + ): String { + val current = "${MetSymbolMapper.glyphFor(symbolCode)} $temperature°" + if (temperature4h != null && symbol4h != null) { + return "$current → ${MetSymbolMapper.glyphFor(symbol4h)} $temperature4h°" + } + return current } private fun Context.cacheMetWeather(result: MetWeatherResult.Success) { @@ -618,6 +641,8 @@ class AppHelper @Inject constructor() { .putLong(Constants.MET_WEATHER_TIMESTAMP, System.currentTimeMillis()) .putInt(Constants.MET_WEATHER_TEMPERATURE, result.temperature) .putString(Constants.MET_WEATHER_SYMBOL, result.symbolCode) + .putInt(Constants.MET_WEATHER_TEMPERATURE_4H, result.temperature4h ?: Int.MIN_VALUE) + .putString(Constants.MET_WEATHER_SYMBOL_4H, result.symbol4h ?: "") .apply() } @@ -627,6 +652,14 @@ class AppHelper @Inject constructor() { if (timestamp == -1L) return null val temperature = sharedPreferences.getInt(Constants.MET_WEATHER_TEMPERATURE, 0) val symbol = sharedPreferences.getString(Constants.MET_WEATHER_SYMBOL, "cloudy") ?: "cloudy" - return timestamp to MetWeatherResult.Success(temperature, symbol) + val temp4h = sharedPreferences.getInt(Constants.MET_WEATHER_TEMPERATURE_4H, Int.MIN_VALUE) + val symbol4h = sharedPreferences.getString(Constants.MET_WEATHER_SYMBOL_4H, "") ?: "" + val result = MetWeatherResult.Success( + temperature = temperature, + symbolCode = symbol, + temperature4h = temp4h.takeIf { it != Int.MIN_VALUE }, + symbol4h = symbol4h.takeIf { it.isNotEmpty() }, + ) + return timestamp to result } } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt b/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt index 92acc7b..499aa2c 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/ui/home/HomeFragment.kt @@ -393,7 +393,9 @@ class HomeFragment : Fragment(), is AppHelper.MetWeatherResult.Success -> { binding.currentWeather.text = appHelper.buildCurrentWeatherText( result.temperature, - result.symbolCode + result.symbolCode, + result.temperature4h, + result.symbol4h, ) binding.currentWeather.visibility = View.VISIBLE } diff --git a/app/src/main/java/com/github/droidworksstudio/launcher/utils/Constants.kt b/app/src/main/java/com/github/droidworksstudio/launcher/utils/Constants.kt index d252019..1c05842 100644 --- a/app/src/main/java/com/github/droidworksstudio/launcher/utils/Constants.kt +++ b/app/src/main/java/com/github/droidworksstudio/launcher/utils/Constants.kt @@ -24,6 +24,8 @@ object Constants { const val MET_WEATHER_TIMESTAMP = "MET_WEATHER_TIMESTAMP" const val MET_WEATHER_TEMPERATURE = "MET_WEATHER_TEMPERATURE" const val MET_WEATHER_SYMBOL = "MET_WEATHER_SYMBOL" + const val MET_WEATHER_TEMPERATURE_4H = "MET_WEATHER_TEMPERATURE_4H" + const val MET_WEATHER_SYMBOL_4H = "MET_WEATHER_SYMBOL_4H" const val WEATHER_RESPONSE = "WEATHER_RESPONSE" const val WEATHER_UNITS = "WEATHER_UNITS" const val LATITUDE = "LATITUDE" diff --git a/dist/EasyLauncher-Internet-v0.3.8-Signed.apk b/dist/EasyLauncher-Internet-v0.3.8-Signed.apk new file mode 100644 index 0000000..b96a551 Binary files /dev/null and b/dist/EasyLauncher-Internet-v0.3.8-Signed.apk differ diff --git a/dist/EasyLauncher-Internet-v0.3.8-Signed.apk.idsig b/dist/EasyLauncher-Internet-v0.3.8-Signed.apk.idsig new file mode 100644 index 0000000..ee0ff65 Binary files /dev/null and b/dist/EasyLauncher-Internet-v0.3.8-Signed.apk.idsig differ diff --git a/dist/EasyLauncher-v0.3.8-Signed.apk b/dist/EasyLauncher-v0.3.8-Signed.apk new file mode 100644 index 0000000..741f545 Binary files /dev/null and b/dist/EasyLauncher-v0.3.8-Signed.apk differ diff --git a/dist/EasyLauncher-v0.3.8-Signed.apk.idsig b/dist/EasyLauncher-v0.3.8-Signed.apk.idsig new file mode 100644 index 0000000..87b2f29 Binary files /dev/null and b/dist/EasyLauncher-v0.3.8-Signed.apk.idsig differ