Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6fcbce1
Refactor: Remove unused phone call permission handling and clean up n…
bosankus Jul 18, 2025
5844ed8
Refactor: Make weather condition properties nullable and provide defa…
bosankus Jul 18, 2025
57c2d0a
Refactor: Enhance error handling and loading state in error component
bosankus Jul 19, 2025
a5c8e2d
Refactor: Update AirQuality and WeatherForecast models for new API st…
bosankus Jul 20, 2025
0271014
Feat: Introduce weather alert display and enhance network models
bosankus Jul 27, 2025
8a4698a
Feat: Introduce authentication and enhance network, UI, and storage m…
bosankus Aug 17, 2025
b1164bd
Docs: Update README to correct 5-day forecast description
bosankus Aug 17, 2025
0d8d2fc
Feat: Enhance openAppLocaleSettings method with fallbacks for languag…
bosankus Aug 18, 2025
5a62525
Feat: Add Firebase Cloud Messaging token to registration and enhance …
bosankus Aug 23, 2025
7119d83
Chore: Remove Lottie dependency version from Versions.kt
bosankus Aug 23, 2025
8808241
Feat: Enhance authentication resilience and logging
bosankus Aug 23, 2025
6c33bf0
Refactor: Integrate payment module into app and enhance payment flow
bosankus Aug 25, 2025
0741c71
Refactor: Overhaul Settings UI, add Feedback API, and enhance stability
bosankus Oct 12, 2025
70df26a
Refactor: Simplify air quality card layout and enhance UI components
bosankus Oct 16, 2025
bc0c6d9
Refactor: Enhance BriefAirQualityReportCard layout with expandable de…
bosankus Oct 16, 2025
b305637
Migration: Replace kapt with KSP for annotation processing
bosankus Mar 14, 2026
7b89690
Fix: Update Firebase dependencies for BOM 34.10.0 KTX API changes
bosankus Mar 14, 2026
502bc64
Enhance Settings screen UI, add localized strings, and implement logg…
bosankus Mar 14, 2026
63a0113
Handle nullable weather and air quality entities in WeatherDao and re…
bosankus Mar 14, 2026
45631bd
Refactor MainViewModel state management and reduce network retry count
bosankus Mar 18, 2026
f22b860
feat: integrate Firebase Cloud Messaging and implement in-app WebView
bosankus Mar 17, 2026
51edc6a
Refactor authentication and token management logic
bosankus Mar 19, 2026
dd90551
Remove payment success toast from SettingsScreen and update AuthModel…
bosankus Mar 23, 2026
be203f3
Refactor permission handling and migrate UI components to a new commo…
bosankus Mar 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: CI

on:
pull_request:
branches: [ "**" ]
push:
branches: [ "main" ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'

- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4

Comment on lines +16 to +24
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Android builds typically require SDK installation; add setup-android step if AGP modules are present.

The project appears to include Android modules (Compose app). Without an SDK, ./gradlew build often fails on CI.

First, confirm AGP usage:

If present, add before Build:

      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '21'

+     - name: Set up Android SDK
+       uses: android-actions/setup-android@v3
+       with:
+         api-level: 34
+         ndk: 26.1.10909125

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v4

🏁 Script executed:

#!/bin/bash
# Detect Android Gradle Plugin usage and required compileSdk.
rg -nP -C2 'com\.android\.(application|library)|compileSdk|namespace' --type=gradle.kts

Length of output: 124


🏁 Script executed:

#!/bin/bash
# Search for Android Gradle Plugin usage in all Gradle scripts
rg -nP 'com\.android\.(application|library)|compileSdk|namespace' -g '*.gradle*' -n -C2

Length of output: 3348


🏁 Script executed:

#!/bin/bash
# Locate ConfigData declaration
rg -n 'object ConfigData' -g '*.kt' -n -C2

# Find compileSdkVersion definition
rg -n 'compileSdkVersion' -g '*.kt' -n -C2

Length of output: 656


Add Android SDK setup to CI workflow

This repository defines multiple Android modules (application and library plugins detected in build.gradle.kts) all targeting compileSdkVersion = 36 (see buildSrc/src/main/java/ConfigData.kt) and will fail to build on CI without the Android SDK installed. Insert an Android SDK setup step before the Gradle setup:

      - name: Set up JDK
        uses: actions/setup-java@v4
        with:
          distribution: temurin
          java-version: '21'

+     - name: Set up Android SDK
+       uses: android-actions/setup-android@v3
+       with:
+         api-level: 36           # matches ConfigData.compileSdkVersion
+         components:             # install required SDK components
+           - build-tools;36.0.0
+           - platform-tools
+         # ndk: <version>        # add if you have native code

      - name: Setup Gradle
        uses: gradle/actions/setup-gradle@v4
  • api-level must match ConfigData.compileSdkVersion (36) to satisfy the AGP modules.
  • Include any additional components (e.g., NDK) only if your modules require native builds.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Set up Android SDK
uses: android-actions/setup-android@v3
with:
api-level: 36 # matches ConfigData.compileSdkVersion
components: # install required SDK components
- build-tools;36.0.0
- platform-tools
# ndk: <version> # add if you have native code
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
🤖 Prompt for AI Agents
.github/workflows/ci.yml around lines 16 to 24: the CI workflow currently sets
up JDK and then Gradle but does not install the Android SDK, causing Android
modules targeting compileSdkVersion 36 to fail; add a step before the "Setup
Gradle" step that installs the Android SDK platform 36 and required components
(platform-tools, build-tools matching the platform, and any optional components
like NDK only if native builds are needed), accept licenses, and export
ANDROID_SDK_ROOT/ANDROID_HOME so Gradle/AGP can find the SDK; ensure the
api-level installed matches ConfigData.compileSdkVersion (36) and that the step
runs on the same runner environment as the rest of the job.

- name: Deep clean (project-local only)
run: ./gradlew --no-daemon deepClean

- name: Build
run: ./gradlew --no-daemon build

- name: Spotless check (soft enforcement for initial adoption)
run: ./gradlew --no-daemon spotlessCheckAll
continue-on-error: true

- name: Detekt (soft enforcement for initial adoption)
run: ./gradlew --no-daemon detektAll
continue-on-error: true

# Notes:
# - Spotless and Detekt steps are set to soft-fail initially to avoid breaking CI before the first formatting commit.
# After running `./gradlew applyCodeCleanup` and committing the formatting changes, set `continue-on-error: false`
# (or remove those lines) to enforce style and static analysis strictly on PRs.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

A modern weather application built with Jetpack Compose that provides current weather conditions, forecasts, and air quality information.

+[![Download APK](https://img.shields.io/badge/download-APK-22272E.svg?style=for-the-badge&logo=android&logoColor=47954A)](https://github.com/bosankus/Compose-Weatherify/releases/latest)
[![Download APK](https://img.shields.io/badge/download-APK-22272E.svg?style=for-the-badge&logo=android&logoColor=47954A)](https://github.com/bosankus/Compose-Weatherify/releases/latest)

## 📱 Features

- **Current Weather**: View today's temperature and weather conditions
- **5-Day Forecast**: See weather predictions for the next 4 days
- **5-Day Forecast**: See weather predictions for the next 5 days
- **Air Quality Index**: Monitor air pollution levels
- **Multiple Cities**: Search and save your favorite locations
- **Multi-language Support**: Available in English, Hindi, and Hebrew
Expand Down
62 changes: 35 additions & 27 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import org.jetbrains.kotlin.compose.compiler.gradle.ComposeFeatureFlag
import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
id("com.google.devtools.ksp")
id("com.google.gms.google-services")
id("dagger.hilt.android.plugin")
id("kotlin-parcelize")
Expand All @@ -23,13 +23,11 @@ android {
versionName = ConfigData.versionName
multiDexEnabled = ConfigData.multiDexEnabled
testInstrumentationRunner = "bose.ankush.weatherify.helper.HiltTestRunner"
resourceConfigurations.addAll(listOf("en", "hi", "iw"))
}

kapt {
arguments {
arg("room.schemaLocation", "$projectDir/schemas")
}
@Suppress("UnstableApiUsage")
androidResources {
localeFilters.addAll(listOf("en", "hi", "he"))
}

packaging {
Expand Down Expand Up @@ -58,17 +56,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}

kotlin {
sourceSets.all {
languageSettings {
languageVersion = Versions.kotlinCompiler
}
}
}

lint {
abortOnError = false
Expand All @@ -77,14 +64,14 @@ android {
namespace = "bose.ankush.weatherify"
}

composeCompiler {
featureFlags = setOf(
ComposeFeatureFlag.StrongSkipping.disabled()
)

ksp {
arg("room.schemaLocation", "$projectDir/schemas")
}

dependencies {

api(project(":common-ui"))
api(project(":language"))
api(project(":storage"))
api(project(":network"))
Expand All @@ -111,6 +98,7 @@ dependencies {
debugImplementation(Deps.composeUiTooling)
implementation(Deps.composeUiToolingPreview)
implementation(Deps.composeMaterial3)
implementation(Deps.composeIconsExtended)

// Unit Testing
testImplementation(Deps.junit)
Expand All @@ -128,16 +116,21 @@ dependencies {
androidTestImplementation(Deps.espressoCore)
androidTestImplementation(Deps.espressoContrib)
androidTestImplementation(Deps.hiltTesting)
kaptAndroidTest(Deps.hiltDaggerAndroidCompiler)
kspAndroidTest(Deps.hiltDaggerAndroidCompiler)

// Networking
implementation(Deps.gson)

// Room runtime for providing WeatherDatabase from app DI
implementation(Deps.room)
implementation(Deps.roomKtx)

Comment on lines +125 to 128
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Room compiler missing — annotation processing will fail at build time

You’ve added Room runtime but not the processor. Without room-compiler, DAOs/entities won’t generate. Add the kapt (or KSP) dependency.

 dependencies {
@@
-    // Room runtime for providing WeatherDatabase from app DI
-    implementation(Deps.room)
-    implementation(Deps.roomKtx)
+    // Room
+    implementation(Deps.room)
+    implementation(Deps.roomKtx)
+    kapt(Deps.roomCompiler)
 }

If you’re migrating to KSP, replace kapt with ksp(Deps.roomCompiler) and apply the KSP plugin instead of kapt.

🤖 Prompt for AI Agents
In app/build.gradle.kts around lines 127 to 130, you added Room runtime
(implementation(Deps.room) and implementation(Deps.roomKtx)) but omitted the
Room annotation processor; this will cause DAO/entity generation to fail at
build time. Add the Room compiler as an annotation processor (either
kapt(Deps.roomCompiler) and apply the kapt plugin in the Gradle file, or if
using KSP use ksp(Deps.roomCompiler) and apply the KSP plugin), ensuring the
chosen processor configuration matches the rest of the project (kapt vs ksp) and
remove any conflicting processor config.

// Firebase
implementation(platform(Deps.firebaseBom))
implementation(Deps.firebaseConfig)
implementation(Deps.firebaseAnalytics)
implementation("com.google.firebase:firebase-config")
implementation("com.google.firebase:firebase-analytics")
implementation(Deps.firebasePerformanceMonitoring)
implementation("com.google.firebase:firebase-messaging")

// Coroutines
implementation(Deps.coroutinesCore)
Expand All @@ -146,13 +139,28 @@ dependencies {
// Dependency Injection
implementation(Deps.hilt)
implementation(Deps.hiltNavigationCompose)
kapt(Deps.hiltDaggerAndroidCompiler)
ksp(Deps.hiltDaggerAndroidCompiler)
ksp(Deps.hiltAndroidXCompiler)

// Miscellaneous
implementation(Deps.timber)
implementation(Deps.lottieCompose)
// Removed Lottie dependency as per requirements
implementation(Deps.coilCompose)

// Memory leak
debugImplementation(Deps.leakCanary)

// Payment SDK moved to app module
implementation(Deps.razorPay)
}


kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
freeCompilerArgs.addAll(
"-Xopt-in=kotlin.RequiresOptIn",
"-Xopt-in=androidx.compose.animation.ExperimentalAnimationApi"
)
}
}
26 changes: 24 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
tools:ignore="ForegroundServicesPolicy" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="android.permission.VIBRATE" />

<application
android:name=".WeatherifyApplication"
Expand All @@ -22,6 +26,7 @@
android:localeConfig="@xml/locales_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:usesCleartextTraffic="false"
android:theme="@style/Theme.Weatherify.Starting"
tools:ignore="AllowBackup,UnusedAttribute">
<activity
Expand All @@ -46,6 +51,23 @@
android:name="autoStoreLocales"
android:value="true" />
</service>

<!-- FCM Service -->
<service
android:name=".base.notification.WeatherifyMessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>

<!-- Firebase Messaging Service -->
<meta-data
android:name="com.google.firebase.messaging.default_notification_icon"
android:resource="@mipmap/ic_launcher" />
<meta-data
android:name="com.google.firebase.messaging.default_notification_color"
android:resource="@color/wallet_bright_foreground_disabled_holo_light" />
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,22 @@ package bose.ankush.storage.di

import android.content.Context
import androidx.room.Room
import bose.ankush.network.repository.WeatherRepository as NetworkWeatherRepository
import bose.ankush.network.auth.storage.TokenStorage
import bose.ankush.storage.api.WeatherStorage
import bose.ankush.storage.common.WEATHER_DATABASE_NAME
import bose.ankush.storage.impl.TokenStorageImpl
import bose.ankush.storage.impl.WeatherStorageImpl
import bose.ankush.storage.room.JsonParser
import bose.ankush.storage.room.WeatherDatabase
import bose.ankush.storage.room.WeatherDataModelConverters
import bose.ankush.storage.room.WeatherDatabase
import com.google.gson.Gson
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import bose.ankush.network.repository.WeatherRepository as NetworkWeatherRepository

@Module
@InstallIn(SingletonComponent::class)
Expand All @@ -24,7 +26,6 @@ object StorageModule {
@Provides
@Singleton
fun provideGson(): Gson = Gson()

@Provides
@Singleton
fun provideJsonParser(gson: Gson): JsonParser = JsonParser(gson)
Expand All @@ -47,7 +48,7 @@ object StorageModule {
WEATHER_DATABASE_NAME
)
.addTypeConverter(converters)
.fallbackToDestructiveMigration()
.fallbackToDestructiveMigration(false)
.build()
}

Expand All @@ -59,4 +60,12 @@ object StorageModule {
): WeatherStorage {
return WeatherStorageImpl(networkRepository, weatherDatabase)
}
}

@Provides
@Singleton
fun provideTokenStorage(
weatherDatabase: WeatherDatabase
): TokenStorage {
return TokenStorageImpl(weatherDatabase)
}
}
41 changes: 37 additions & 4 deletions app/src/main/java/bose/ankush/weatherify/WeatherifyApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package bose.ankush.weatherify

import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import bose.ankush.weatherify.base.location.LocationService.Companion.NOTIFICATION_CHANNEL_ID
import bose.ankush.weatherify.base.location.LocationService.Companion.NOTIFICATION_NAME
import bose.ankush.weatherify.domain.remote_config.RemoteConfigService
import com.google.firebase.FirebaseApp
import com.google.firebase.messaging.FirebaseMessaging
import dagger.hilt.android.HiltAndroidApp
import timber.log.Timber
import javax.inject.Inject
Expand All @@ -24,26 +25,58 @@ class WeatherifyApplication : WeatherifyApplicationCore() {
override fun onCreate() {
super.onCreate()
enableTimber()
initializeFirebase()
createNotificationChannel()
initializeRemoteConfig()
subscribeToTopics()
}

private fun initializeFirebase() {
FirebaseApp.initializeApp(this)
}

private fun subscribeToTopics() {
FirebaseMessaging.getInstance().subscribeToTopic("weather_alerts")
.addOnCompleteListener { task ->
if (!task.isSuccessful) {
Timber.e(task.exception, "Failed to subscribe to weather_alerts topic")
} else {
Timber.d("Successfully subscribed to weather_alerts topic")
}
}
}

private fun initializeRemoteConfig() {
remoteConfigService.initialize()
}

private fun enableTimber() {
Timber.plant(Timber.DebugTree())
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
} else {
Timber.plant(object : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
// Only log WARN, ERROR, and WTF in release; avoid verbose/debug/info
if (priority == android.util.Log.VERBOSE || priority == android.util.Log.DEBUG || priority == android.util.Log.INFO) return
android.util.Log.println(priority, tag, message)
}
})
}
}

private fun createNotificationChannel() {
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_NAME,
NotificationManager.IMPORTANCE_HIGH
)
).apply {
description = "Channel for weather alerts and updates"
enableVibration(true)
}

val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
Timber.d("Notification channel created: $NOTIFICATION_CHANNEL_ID")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ import android.annotation.SuppressLint
Author: Ankush Bose
Date: 05,May,2021
**/

/*General constants*/
const val WEATHER_BASE_URL = "https://data.androidplay.in/"
const val WEATHER_IMG_URL = "https://openweathermap.org/img/wn/"

const val APP_UPDATE_REQ_CODE = 111
Expand All @@ -22,7 +19,6 @@ const val DEFAULT_CITY_NAME = "New Delhi"
/* Permission constants */
const val ACCESS_FINE_LOCATION = android.Manifest.permission.ACCESS_FINE_LOCATION
const val ACCESS_COARSE_LOCATION = android.Manifest.permission.ACCESS_COARSE_LOCATION
const val ACCESS_PHONE_CALL = android.Manifest.permission.CALL_PHONE
@SuppressLint("InlinedApi")
const val ACCESS_NOTIFICATION = android.Manifest.permission.POST_NOTIFICATIONS

Expand All @@ -31,10 +27,5 @@ val PERMISSIONS_TO_REQUEST = arrayOf(
ACCESS_COARSE_LOCATION
)

/*Room central db name*/
const val WEATHER_DATABASE_NAME = "central_weather_table"
const val AQ_DATABASE_NAME = "central_aq_table"
const val PHONE_NUMBER = "tel:+91XXXXXXXXX"

/*Remote keys*/
const val ENABLE_NOTIFICATION = "enable_notification"
Loading
Loading