PRODUCTION - READY NOTIFICATION SERVICE for weather app,
 
I want to build a minimal Notification Manager for my app in efficient, optimized, cleaner, robust and performant way without affecting battery and performance. Use Kotlin Coroutines, Workers, Koin, Jetpack Compose and material3 library, Flows, Jetpack datstore preferences, KotlinX date time, KotlinX serialization, and my Arrow for better error handling. Follow Clean Android Architecture guidelines, KISS, SOLID principles and DON'T OVER ENGINEER IT. Just create a simple implementation to test it then i will scale it slowly just create the foundation part first.

## ON-GOING NOTIFICATION

- Create a persistent notification showing location name, temp, condition, and weather icon with status bar icon showing temp or icon. the notification and statusBar icon should be consistent. and add a toggle in settings to turnerd it on/off.
- If notification permission is not granted ask permission first, if denied multiple times go to noti-permission settings.
- Add option to expand to show more weather details like feels like, uv, humidity,wind, AQI. and update interval[30 min, 1hour, 3hours, 6hours] for updating weather by API calls in background.
- Add a test button below to test full real time preview of notification .so when users clicked , and it instantly shoes what real notification will look like. Add a option to select a notification for which location to show.


## DAILY NOTIFICATION

- List all the weather for location to show notification.
- Add a time interval [ Hurly, Every 3/6 hours, Daily with Time, user can select ]
- Add a test button below to test full real time preview of notification .so when users clicked , and it instantly shoes what real notification will look like.


## SEVERE WEATHER ALERTS

- Add a separate toggle in settings for both ongoing and daily notification for showing weather alerts. and add the condition for showing it like Precipitaion chances, heavy thunder storm, very cloudy.

Also, consider my domain models: 

 data class Weather(
    val id: Long = 0,
    val condition: Condition,
    val location: Location,
    val feelslike_c: Double,
    val feelslike_f: Double,
    val humidity: Int,
    val is_day: Int,
    val precip_in: Double,
    val precip_mm: Double,
    val pressure_in: Double,
    val pressure_mb: Double,
    val temp_c: Double,
    val temp_f: Double,
    val wind_kph: Double,
    val wind_mph: Double,
    val wind_dir: String,
    val wind_degree: Int,
    val vis_km: Double,
    val vis_miles: Double,
    val gust_kph: Double,
    val gust_mph: Double,
    val cloud: Int,
    val uv: Double,
    val lat: Double,
    val lon: Double,
    val located: Boolean,
    val air_quality: AirQuality,
    val dewpoint_c: Double,
    val dewpoint_f: Double,
)

data class Condition(
    val code: Int,
    val icon: String,
    val text: String,
)

data class Location(
    val country: String,
    val name: String,
    val region: String,
    val localtime: String,
    val localtimeEpoch: Int,
    val timezoneId: String,

    ) data class Alerts(
    val alert: List<Alert>
)

data class Alert(
    val areas: String,
    val category: String,
    val certainty: String,
    val desc: String,
    val effective: String,
    val event: String,
    val expires: String,
    val headline: String,
    val instruction: String,
    val msgtype: String,
    val note: String,
    val severity: String,
    val urgency: String
)

// usecases for domain layer is

class GetWeatherDBbyCityIdUseCase @Inject constructor(
    private val repository: WeatherRepository
) {
    suspend operator fun invoke(cityId: Long): WeatherResult<Weather?> {
        return repository.getWeatherByCityId(cityId)
            .map { entity -> entity?.mapEntityToWeather() }
    }
}

class UpdateWeatherByIdUseCase(
    private val savedLocationRepository: WeatherRepository,
) {
    operator fun invoke(cityId: Long): Flow<WeatherResult<Weather>> = flow {
        val result = savedLocationRepository
            .updateWeatherDataById(cityId)
            .map { entity -> entity.mapEntityToWeather() }
        emit(result)
    }.flowOn(Dispatchers.IO)
}



// on both current and forecast
data class AirQuality(
    val co: Double,
    val ukDefraIndex: Int,
    val no2: Double,
    val o3: Double,
    val pm10: Double,
    val pm25: Double,
    val so2: Double,
    val usEPAStandard: Int
) 

and Domain error
// domain/error/WeatherDomainError.kt

sealed interface WeatherDomainError {
    data object Connectivity : WeatherDomainError
    data class LocationNotFound(val id: Long) : WeatherDomainError
    data class InvalidCoordinates(val lat: Double, val lon: Double) : WeatherDomainError
    data object Unauthorized : WeatherDomainError
    data class Unexpected(val debug: String? = null) : WeatherDomainError
}

// Type alias for cleaner signatures
typealias WeatherResult<T> = Either<WeatherDomainError, T>

sealed interface WeatherUiError {
    data object Offline : WeatherUiError
    data object LocationNotFound : WeatherUiError
    data object InvalidLocation : WeatherUiError
    data object AuthorizationRequired : WeatherUiError
    data object Unknown : WeatherUiError
    data object LocationServicesDisabled : WeatherUiError
    data object NoSavedLocations : WeatherUiError


    // For backup/restore errors
    data object NoLocationsToBackup : WeatherUiError
    data object EmptyBackupFile : WeatherUiError
    data object InvalidBackupData : WeatherUiError
    data object BackupFailed : WeatherUiError
    data object RestoreFailed : WeatherUiError

    companion object {
        fun fromDomainError(error: WeatherDomainError): WeatherUiError = when (error) {
            is WeatherDomainError.Connectivity -> Offline
            is WeatherDomainError.LocationNotFound -> LocationNotFound
            is WeatherDomainError.InvalidCoordinates -> InvalidLocation
            is WeatherDomainError.Unauthorized -> AuthorizationRequired
            is WeatherDomainError.Unexpected -> Unknown
        }

        fun fromBackupError(error: BackupError): WeatherUiError = when (error) {
            is BackupError.NoLocationsToBackup -> NoLocationsToBackup
            is BackupError.EmptyBackupFile -> EmptyBackupFile
            is BackupError.InvalidBackupData -> InvalidBackupData
            is BackupError.IOError,
            is BackupError.DatabaseError,
            is BackupError.SerializationError -> BackupFailed
        }
    }

    fun getErrorMessage(): String = when (this) {
        is Offline -> "No internet connection available"
        is LocationNotFound -> "Location could not be found"
        is InvalidLocation -> "Invalid location coordinates"
        is AuthorizationRequired -> "Authorization required"
        is Unknown -> "An unexpected error occurred"
        is NoLocationsToBackup -> "No locations to backup"
        is EmptyBackupFile -> "Backup file is empty"
        is InvalidBackupData -> "Invalid backup file format"
        is BackupFailed -> "Failed to create backup"
        is RestoreFailed -> "Failed to restore data"
        is LocationServicesDisabled -> "Please enable location services"
        is NoSavedLocations -> "No saved locations found"
    }
}