mirror of
https://github.com/hmalik144/Weather-apps.git
synced 2025-12-10 02:05:20 +00:00
- new weather api added
This commit is contained in:
10633
app/src/androidTest/assets/new_response.json
Normal file
10633
app/src/androidTest/assets/new_response.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
const val baseUrl = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/"
|
||||
enum class Stubs(
|
||||
val id: String
|
||||
) {
|
||||
@@ -7,5 +8,6 @@ enum class Stubs(
|
||||
Imperial("valid_response_imperial"),
|
||||
WrongLocation("wrong_location_response"),
|
||||
InvalidKey("invalid_api_key_response"),
|
||||
Sydney("valid_response_metric_sydney")
|
||||
Sydney("valid_response_metric_sydney"),
|
||||
New("new_response")
|
||||
}
|
||||
@@ -24,10 +24,7 @@ class SettingsScreen : BaseTestRobot() {
|
||||
RecyclerViewActions.actionOnItem<ViewHolder>(
|
||||
ViewMatchers.hasDescendant(withText(R.string.weather_units)),
|
||||
click()))
|
||||
val label = when (unitType) {
|
||||
UnitType.METRIC -> "Metric"
|
||||
UnitType.IMPERIAL -> "Imperial"
|
||||
}
|
||||
val label = unitType.getLabel()
|
||||
|
||||
onView(withText(label))
|
||||
.inRoot(isDialog())
|
||||
|
||||
@@ -7,7 +7,7 @@ import androidx.test.platform.app.InstrumentationRegistry
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.location.MockLocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NetworkModule
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NewWeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.MockingNetworkInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInterceptor
|
||||
@@ -32,13 +32,13 @@ class TestAppClass : AppClass() {
|
||||
IdlingRegistry.getInstance().register(idlingResources)
|
||||
}
|
||||
|
||||
override fun createNetworkModule(): WeatherApi {
|
||||
return NetworkModule().invoke<WeatherApi>(
|
||||
override fun createNetworkModule(): NewWeatherApi {
|
||||
return NetworkModule().invoke<NewWeatherApi>(
|
||||
mockingNetworkInterceptor,
|
||||
NetworkConnectionInterceptor(this),
|
||||
QueryParamsInterceptor(),
|
||||
loggingInterceptor
|
||||
) as WeatherApi
|
||||
) as NewWeatherApi
|
||||
}
|
||||
|
||||
override fun createLocationModule(): LocationProvider {
|
||||
|
||||
@@ -13,6 +13,7 @@ import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
||||
import com.appttude.h_mal.atlas_weather.R
|
||||
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
|
||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType.Companion.getLabel
|
||||
|
||||
|
||||
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }
|
||||
@@ -24,10 +25,7 @@ class SettingsScreen : BaseTestRobot() {
|
||||
RecyclerViewActions.actionOnItem<ViewHolder>(
|
||||
ViewMatchers.hasDescendant(withText(R.string.weather_units)),
|
||||
click()))
|
||||
val label = when (unitType) {
|
||||
UnitType.METRIC -> "Metric"
|
||||
UnitType.IMPERIAL -> "Imperial"
|
||||
}
|
||||
val label = unitType.getLabel()
|
||||
|
||||
onView(withText(label))
|
||||
.inRoot(isDialog())
|
||||
|
||||
@@ -5,17 +5,16 @@ import com.appttude.h_mal.atlas_weather.BaseTest
|
||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
||||
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||
import com.appttude.h_mal.monoWeather.robot.furtherInfoScreen
|
||||
import com.appttude.h_mal.monoWeather.robot.settingsScreen
|
||||
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||
import okio.IOException
|
||||
import org.junit.Test
|
||||
import tools.fastlane.screengrab.Screengrab
|
||||
|
||||
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||
|
||||
override fun beforeLaunch() {
|
||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
||||
stubEndpoint(baseUrl, Stubs.New)
|
||||
clearPrefs()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.appttude.h_mal.atlas_weather.application
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NetworkModule
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NewWeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkConnectionInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.QueryParamsInterceptor
|
||||
import com.appttude.h_mal.atlas_weather.data.network.networkUtils.loggingInterceptor
|
||||
@@ -11,12 +11,12 @@ import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||
|
||||
open class AppClass : BaseAppClass() {
|
||||
|
||||
override fun createNetworkModule(): WeatherApi {
|
||||
return NetworkModule().invoke<WeatherApi>(
|
||||
override fun createNetworkModule(): NewWeatherApi {
|
||||
return NetworkModule().invoke<NewWeatherApi>(
|
||||
NetworkConnectionInterceptor(this),
|
||||
QueryParamsInterceptor(),
|
||||
loggingInterceptor
|
||||
) as WeatherApi
|
||||
) as NewWeatherApi
|
||||
}
|
||||
|
||||
override fun createLocationModule(): LocationProvider = LocationProviderImpl(this)
|
||||
|
||||
@@ -3,7 +3,7 @@ package com.appttude.h_mal.atlas_weather.application
|
||||
import android.app.Application
|
||||
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.Api
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepositoryImpl
|
||||
@@ -12,7 +12,6 @@ import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
||||
import com.google.gson.Gson
|
||||
import org.kodein.di.Kodein
|
||||
import org.kodein.di.KodeinAware
|
||||
import org.kodein.di.KodeinContainer
|
||||
import org.kodein.di.android.x.androidXModule
|
||||
import org.kodein.di.generic.bind
|
||||
import org.kodein.di.generic.instance
|
||||
@@ -41,7 +40,7 @@ abstract class BaseAppClass : Application(), KodeinAware {
|
||||
bind() from singleton { WeatherSource(instance(), instance()) }
|
||||
}
|
||||
|
||||
abstract fun createNetworkModule(): WeatherApi
|
||||
abstract fun createNetworkModule(): Api
|
||||
abstract fun createLocationModule(): LocationProvider
|
||||
abstract fun createRoomDatabase(): AppDatabase
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||
import com.appttude.h_mal.atlas_weather.utils.getSymbol
|
||||
import java.io.IOException
|
||||
|
||||
class WeatherSource(
|
||||
@@ -38,7 +39,7 @@ class WeatherSource(
|
||||
// get data from database
|
||||
val weatherEntity = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
|
||||
// check unit type - if same do nothing
|
||||
val units = if (repository.getUnitType() == UnitType.METRIC) "°C" else "°F"
|
||||
val units = repository.getUnitType().getSymbol()
|
||||
if (weatherEntity.weather.temperatureUnit == units) return weatherEntity.weather
|
||||
// load data for forced
|
||||
return fetchWeather(
|
||||
@@ -55,11 +56,13 @@ class WeatherSource(
|
||||
// Get weather from api
|
||||
val weather = repository
|
||||
.getWeatherFromApi(latLon.first.toString(), latLon.second.toString())
|
||||
val lat = weather.latitude ?: latLon.first
|
||||
val long = weather.longitude ?: latLon.second
|
||||
val currentLocation =
|
||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon, locationType)
|
||||
val unit = repository.getUnitType()
|
||||
val fullWeather = FullWeather(weather).apply {
|
||||
temperatureUnit = if (unit == UnitType.METRIC) "°C" else "°F"
|
||||
locationProvider.getLocationNameFromLatLong(lat, long, locationType)
|
||||
val unit = repository.getUnitType().getSymbol()
|
||||
val fullWeather = weather.mapData().apply {
|
||||
temperatureUnit = unit
|
||||
locationString = currentLocation
|
||||
}
|
||||
val entityItem = EntityItem(locationName, fullWeather)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
class NetworkModule : BaseNetworkModule() {
|
||||
override fun baseUrl(): String = "https://api.openweathermap.org/data/2.5/"
|
||||
override fun baseUrl(): String = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/"
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
|
||||
interface NewWeatherApi : Api {
|
||||
|
||||
// Todo: change the location
|
||||
// Todo: add endpoint for lat/long
|
||||
@GET("{location}")
|
||||
suspend fun getFromApi(
|
||||
@Query("contentType") exclude: String = "json",
|
||||
@Query("unitGroup") units: String = "uk",
|
||||
@Path("location") location: String
|
||||
): Response<WeatherApiResponse>
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class QueryParamsInterceptor : Interceptor {
|
||||
val original = chain.request()
|
||||
|
||||
val url = original.url.newBuilder()
|
||||
.addQueryParameter("appid", id)
|
||||
.addQueryParameter("key", id)
|
||||
.build()
|
||||
|
||||
// Request customization: add request headers
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.network.networkUtils
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkInterceptor
|
||||
import com.google.gson.GsonBuilder
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||
@@ -34,6 +36,13 @@ fun buildOkHttpClient(
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
fun createGsonConverterFactory(): GsonConverterFactory {
|
||||
val gson = GsonBuilder()
|
||||
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
|
||||
.create()
|
||||
return GsonConverterFactory.create(gson)
|
||||
}
|
||||
|
||||
fun <T> createRetrofit(
|
||||
baseUrl: String,
|
||||
okHttpClient: OkHttpClient,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.repository
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||
|
||||
interface Repository {
|
||||
|
||||
suspend fun getWeatherFromApi(lat: String, long: String): WeatherResponse
|
||||
suspend fun getWeatherFromApi(lat: String, long: String): WeatherApiResponse
|
||||
suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem)
|
||||
suspend fun saveWeatherListToRoom(list: List<EntityItem>)
|
||||
fun loadRoomWeatherLiveData(): LiveData<List<EntityItem>>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.repository
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NewWeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.ResponseUnwrap
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||
@@ -12,7 +12,7 @@ import com.appttude.h_mal.atlas_weather.utils.FALLBACK_TIME
|
||||
|
||||
|
||||
class RepositoryImpl(
|
||||
private val api: WeatherApi,
|
||||
private val api: NewWeatherApi,
|
||||
private val db: AppDatabase,
|
||||
private val prefs: PreferenceProvider
|
||||
) : Repository, ResponseUnwrap() {
|
||||
@@ -20,8 +20,8 @@ class RepositoryImpl(
|
||||
override suspend fun getWeatherFromApi(
|
||||
lat: String,
|
||||
long: String
|
||||
): WeatherResponse {
|
||||
return responseUnwrap { api.getFromApi(lat, long, units = prefs.getUnitsType().name.lowercase()) }
|
||||
): WeatherApiResponse {
|
||||
return responseUnwrap { api.getFromApi(location = lat + long) }
|
||||
}
|
||||
|
||||
override suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem) {
|
||||
|
||||
@@ -11,14 +11,13 @@ import com.appttude.h_mal.atlas_weather.data.repository.Repository
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetCellData
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetData
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetData
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetError
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetState
|
||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetWeatherCollection
|
||||
import com.appttude.h_mal.atlas_weather.utils.getSymbol
|
||||
import com.appttude.h_mal.atlas_weather.utils.toSmallDayName
|
||||
import com.squareup.picasso.Picasso
|
||||
import com.squareup.picasso.Target
|
||||
@@ -45,9 +44,11 @@ class ServicesHelper(
|
||||
// Get weather from api
|
||||
val weather = repository
|
||||
.getWeatherFromApi(latLong.first.toString(), latLong.second.toString())
|
||||
val lat = weather.latitude ?: latLong.first
|
||||
val long = weather.longitude ?: latLong.second
|
||||
val currentLocation =
|
||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
||||
val fullWeather = FullWeather(weather).apply {
|
||||
locationProvider.getLocationNameFromLatLong(lat, long)
|
||||
val fullWeather = weather.mapData().apply {
|
||||
temperatureUnit = "°C"
|
||||
locationString = currentLocation
|
||||
}
|
||||
@@ -105,8 +106,11 @@ class ServicesHelper(
|
||||
return WidgetState.HasError(error)
|
||||
}
|
||||
|
||||
val lat = weather.latitude ?: latLong.first
|
||||
val long = weather.longitude ?: latLong.second
|
||||
|
||||
val currentLocation = try {
|
||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
||||
locationProvider.getLocationNameFromLatLong(lat, long)
|
||||
} catch (e: IOException) {
|
||||
val data = getWidgetWeatherCollection()
|
||||
data?.let {
|
||||
@@ -120,8 +124,8 @@ class ServicesHelper(
|
||||
return WidgetState.HasError(error)
|
||||
}
|
||||
|
||||
val fullWeather = FullWeather(weather).apply {
|
||||
temperatureUnit = if (repository.getUnitType() == UnitType.METRIC) "°C" else "°F"
|
||||
val fullWeather = weather.mapData().apply {
|
||||
temperatureUnit = repository.getUnitType().getSymbol()
|
||||
locationString = currentLocation
|
||||
}
|
||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
package com.appttude.h_mal.atlas_weather.model
|
||||
|
||||
interface DataMapper <T: Any> {
|
||||
fun mapData(): T
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.appttude.h_mal.atlas_weather.model
|
||||
|
||||
enum class IconMapper(val label: String) {
|
||||
snow("13d"),
|
||||
snow_showers_day("13d"),
|
||||
snow_showers_night("13n"),
|
||||
thunder_rain("11d"),
|
||||
thunder_showers_day("11d"),
|
||||
thunder_showers_night("11n"),
|
||||
rain("10d"),
|
||||
showers_day("10d"),
|
||||
showers_night("10n"),
|
||||
fog("50d"),
|
||||
wind("50d"),
|
||||
cloudy("04d"),
|
||||
partly_cloudy_day("03d"),
|
||||
partly_cloudy_night("03n"),
|
||||
clear_day("01d"),
|
||||
clear_night("01n");
|
||||
|
||||
companion object{
|
||||
fun findIconCode(iconId: String?): String? {
|
||||
val label = iconId?.replace("-", "_")
|
||||
val enumName = IconMapper.entries.find { it.name == label }
|
||||
return enumName?.label
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ data class WeatherDisplay(
|
||||
val averageTemp: Double?,
|
||||
var unit: String?,
|
||||
var location: String?,
|
||||
val iconURL: String?,
|
||||
var iconURL: String?,
|
||||
val description: String?,
|
||||
val hourly: List<Hour>?,
|
||||
val forecast: List<Forecast>?,
|
||||
|
||||
@@ -8,11 +8,15 @@ enum class UnitType {
|
||||
|
||||
companion object {
|
||||
fun getByName(name: String?): UnitType? {
|
||||
return values().firstOrNull {
|
||||
return entries.firstOrNull {
|
||||
it.name.lowercase(Locale.ROOT) == name?.lowercase(
|
||||
Locale.ROOT
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun UnitType.getLabel() = name.lowercase().replaceFirstChar {
|
||||
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.appttude.h_mal.atlas_weather.model.weather
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.Current
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.weather.CurrentConditions
|
||||
import com.appttude.h_mal.atlas_weather.model.IconMapper
|
||||
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
||||
|
||||
data class Current(
|
||||
@@ -42,4 +44,24 @@ data class Current(
|
||||
dailyItem.humidity,
|
||||
dailyItem.windSpeed
|
||||
)
|
||||
|
||||
constructor(currentConditions: CurrentConditions?): this(
|
||||
dt = currentConditions?.datetimeEpoch,
|
||||
sunrise = currentConditions?.sunriseEpoch,
|
||||
sunset = currentConditions?.sunsetEpoch,
|
||||
temp = currentConditions?.temp,
|
||||
visibility = currentConditions?.visibility?.toInt(),
|
||||
uvi = currentConditions?.uvindex?.toDouble(),
|
||||
pressure = currentConditions?.pressure?.toInt(),
|
||||
clouds = currentConditions?.cloudcover?.toInt(),
|
||||
feelsLike = currentConditions?.feelslike,
|
||||
windDeg = currentConditions?.winddir?.toInt(),
|
||||
dewPoint = currentConditions?.dew,
|
||||
icon = generateIconUrlString(IconMapper.findIconCode(currentConditions?.icon)),
|
||||
description = currentConditions?.conditions,
|
||||
main = currentConditions?.conditions,
|
||||
id = currentConditions?.datetimeEpoch,
|
||||
humidity = currentConditions?.humidity?.toInt(),
|
||||
windSpeed = currentConditions?.windspeed
|
||||
)
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.appttude.h_mal.atlas_weather.model.weather
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.DailyItem
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.weather.Days
|
||||
import com.appttude.h_mal.atlas_weather.model.IconMapper
|
||||
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
||||
|
||||
|
||||
@@ -50,5 +52,30 @@ data class DailyWeather(
|
||||
dailyItem.rain
|
||||
)
|
||||
|
||||
constructor(days: Days) : this(
|
||||
days.datetimeEpoch,
|
||||
days.sunriseEpoch,
|
||||
days.sunsetEpoch,
|
||||
days.tempmin,
|
||||
days.tempmax,
|
||||
days.temp?.toDouble(),
|
||||
days.feelslike,
|
||||
days.pressure?.toInt(),
|
||||
days.humidity?.toInt(),
|
||||
days.dew,
|
||||
days.windspeed,
|
||||
days.winddir?.toInt(),
|
||||
generateIconUrlString(
|
||||
IconMapper.findIconCode(days.icon)
|
||||
),
|
||||
days.description,
|
||||
days.conditions,
|
||||
days.datetimeEpoch,
|
||||
days.cloudcover?.toInt(),
|
||||
days.precipprob?.toDouble(),
|
||||
days.uvindex?.toDouble(),
|
||||
days.precip?.toDouble()
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
@@ -2,8 +2,10 @@ package com.appttude.h_mal.atlas_weather.model.weather
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.appttude.h_mal.atlas_weather.model.IconMapper
|
||||
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.Hour as ForecastHour
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.weather.Hours as WeatherHour
|
||||
|
||||
|
||||
data class Hour(
|
||||
@@ -24,6 +26,12 @@ data class Hour(
|
||||
hour.temp,
|
||||
generateIconUrlString(hour.weather?.getOrNull(0)?.icon)
|
||||
)
|
||||
|
||||
constructor(weatherHour: WeatherHour) : this(
|
||||
weatherHour.datetimeEpoch,
|
||||
weatherHour.temp,
|
||||
generateIconUrlString(IconMapper.findIconCode(weatherHour.icon))
|
||||
)
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeValue(dt)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.appttude.h_mal.atlas_weather.utils
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||
|
||||
|
||||
fun generateIconUrlString(icon: String?): String? {
|
||||
return icon?.let {
|
||||
@@ -9,4 +11,6 @@ fun generateIconUrlString(icon: String?): String? {
|
||||
.append("@2x.png")
|
||||
.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun UnitType.getSymbol(): String = if (this == UnitType.METRIC) "°C" else "°F"
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.appttude.h_mal.atlas_weather.data
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.RepositoryImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
||||
@@ -18,6 +18,7 @@ import kotlinx.coroutines.runBlocking
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.IOException
|
||||
import kotlin.properties.Delegates
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class WeatherSourceTest : BaseTest() {
|
||||
@@ -31,41 +32,43 @@ class WeatherSourceTest : BaseTest() {
|
||||
@MockK
|
||||
lateinit var locationProvider: LocationProviderImpl
|
||||
|
||||
private lateinit var weatherResponse: WeatherResponse
|
||||
private lateinit var weatherResponse: WeatherApiResponse
|
||||
private var lat by Delegates.notNull<Double>()
|
||||
private var long by Delegates.notNull<Double>()
|
||||
private lateinit var latlon: Pair<Double, Double>
|
||||
private lateinit var fullWeather: FullWeather
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
weatherResponse = getTestData("weather_sample.json", WeatherResponse::class.java)
|
||||
weatherResponse = getTestData("new_response.json", WeatherApiResponse::class.java)
|
||||
lat = weatherResponse.latitude!!
|
||||
long = weatherResponse.longitude!!
|
||||
latlon = Pair(lat, long)
|
||||
fullWeather = weatherResponse.mapData().apply {
|
||||
temperatureUnit = "°C"
|
||||
locationString = CURRENT_LOCATION
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fetchDataForSingleLocation_validLocation_validReturn() {
|
||||
// Arrange
|
||||
val latlon = Pair(
|
||||
weatherResponse.lat,
|
||||
weatherResponse.lon
|
||||
)
|
||||
val fullWeather = FullWeather(weatherResponse).apply {
|
||||
temperatureUnit = "°C"
|
||||
locationString = CURRENT_LOCATION
|
||||
}
|
||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||
|
||||
|
||||
// Act
|
||||
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
||||
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
|
||||
coEvery {
|
||||
repository.getWeatherFromApi(
|
||||
weatherResponse.lat.toString(),
|
||||
weatherResponse.lon.toString()
|
||||
lat.toString(),
|
||||
long.toString()
|
||||
)
|
||||
}.returns(weatherResponse)
|
||||
coEvery {
|
||||
locationProvider.getLocationNameFromLatLong(
|
||||
weatherResponse.lat,
|
||||
weatherResponse.lon,
|
||||
lat,
|
||||
long,
|
||||
LocationType.City
|
||||
)
|
||||
}.returns(CURRENT_LOCATION)
|
||||
@@ -82,17 +85,13 @@ class WeatherSourceTest : BaseTest() {
|
||||
@Test(expected = IOException::class)
|
||||
fun fetchDataForSingleLocation_failedWeatherApi_invalidReturn() {
|
||||
// Arrange
|
||||
val latlon = Pair(
|
||||
weatherResponse.lat,
|
||||
weatherResponse.lon
|
||||
)
|
||||
|
||||
// Act
|
||||
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
||||
coEvery {
|
||||
repository.getWeatherFromApi(
|
||||
weatherResponse.lat.toString(),
|
||||
weatherResponse.lon.toString()
|
||||
lat.toString(),
|
||||
long.toString()
|
||||
)
|
||||
} throws IOException("Unable fetch data")
|
||||
|
||||
@@ -103,23 +102,19 @@ class WeatherSourceTest : BaseTest() {
|
||||
@Test(expected = IOException::class)
|
||||
fun fetchDataForSingleLocation_failedLocation_invalidReturn() {
|
||||
// Arrange
|
||||
val latlon = Pair(
|
||||
weatherResponse.lat,
|
||||
weatherResponse.lon
|
||||
)
|
||||
|
||||
// Act
|
||||
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
||||
coEvery {
|
||||
repository.getWeatherFromApi(
|
||||
weatherResponse.lat.toString(),
|
||||
weatherResponse.lon.toString()
|
||||
lat.toString(),
|
||||
long.toString()
|
||||
)
|
||||
} returns weatherResponse
|
||||
coEvery {
|
||||
locationProvider.getLocationNameFromLatLong(
|
||||
weatherResponse.lat,
|
||||
weatherResponse.lon
|
||||
lat,
|
||||
long
|
||||
)
|
||||
}.throws(IOException())
|
||||
|
||||
@@ -130,14 +125,6 @@ class WeatherSourceTest : BaseTest() {
|
||||
@Test
|
||||
fun searchAboveFallbackTime_validLocation_validReturn() {
|
||||
// Arrange
|
||||
val latlon = Pair(
|
||||
weatherResponse.lat,
|
||||
weatherResponse.lon
|
||||
)
|
||||
val fullWeather = FullWeather(weatherResponse).apply {
|
||||
temperatureUnit = "°C"
|
||||
locationString = CURRENT_LOCATION
|
||||
}
|
||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||
|
||||
// Act
|
||||
@@ -153,14 +140,6 @@ class WeatherSourceTest : BaseTest() {
|
||||
@Test
|
||||
fun forceFetchDataForSingleLocation_validLocation_validReturn() {
|
||||
// Arrange
|
||||
val latlon = Pair(
|
||||
weatherResponse.lat,
|
||||
weatherResponse.lon
|
||||
)
|
||||
val fullWeather = FullWeather(weatherResponse).apply {
|
||||
temperatureUnit = "°C"
|
||||
locationString = CURRENT_LOCATION
|
||||
}
|
||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||
|
||||
// Act
|
||||
@@ -169,14 +148,14 @@ class WeatherSourceTest : BaseTest() {
|
||||
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
|
||||
coEvery {
|
||||
repository.getWeatherFromApi(
|
||||
weatherResponse.lat.toString(),
|
||||
weatherResponse.lon.toString()
|
||||
weatherResponse.latitude.toString(),
|
||||
weatherResponse.longitude.toString()
|
||||
)
|
||||
}.returns(weatherResponse)
|
||||
coEvery {
|
||||
locationProvider.getLocationNameFromLatLong(
|
||||
weatherResponse.lat,
|
||||
weatherResponse.lon,
|
||||
lat,
|
||||
long,
|
||||
LocationType.City
|
||||
)
|
||||
}.returns(CURRENT_LOCATION)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.appttude.h_mal.atlas_weather.data.repository
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.network.NewWeatherApi
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.LOCATION_CONST
|
||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||
@@ -29,8 +29,7 @@ class RepositoryImplTest : BaseTest() {
|
||||
|
||||
lateinit var repository: RepositoryImpl
|
||||
|
||||
@MockK
|
||||
lateinit var api: WeatherApi
|
||||
@MockK lateinit var api: NewWeatherApi
|
||||
|
||||
@MockK
|
||||
lateinit var db: AppDatabase
|
||||
@@ -86,29 +85,29 @@ class RepositoryImplTest : BaseTest() {
|
||||
@Test
|
||||
fun getWeatherFromApi_validLatLong_validSearch() {
|
||||
//Arrange
|
||||
val mockResponse = createSuccessfulRetrofitMock<WeatherResponse>()
|
||||
val mockResponse = createSuccessfulRetrofitMock<WeatherApiResponse>()
|
||||
|
||||
//Act
|
||||
//create a successful retrofit response
|
||||
every { prefs.getUnitsType() } returns (UnitType.METRIC)
|
||||
coEvery { api.getFromApi("", "") }.returns(mockResponse)
|
||||
coEvery { api.getFromApi(location = "") }.returns(mockResponse)
|
||||
|
||||
// Assert
|
||||
runBlocking {
|
||||
val result = repository.getWeatherFromApi("", "")
|
||||
assertIs<WeatherResponse>(result)
|
||||
assertIs<WeatherApiResponse>(result)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun getWeatherFromApi_validLatLong_invalidResponse() {
|
||||
//Arrange
|
||||
val mockResponse = createErrorRetrofitMock<WeatherResponse>()
|
||||
val mockResponse = createErrorRetrofitMock<WeatherApiResponse>()
|
||||
|
||||
//Act
|
||||
//create a successful retrofit response
|
||||
every { prefs.getUnitsType() } returns (UnitType.METRIC)
|
||||
coEvery { api.getFromApi(any(), any()) } returns (mockResponse)
|
||||
coEvery { api.getFromApi(location = any()) } returns (mockResponse)
|
||||
|
||||
// Assert
|
||||
val ioExceptionReturned = assertFailsWith<IOException> {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.appttude.h_mal.atlas_weather.helper
|
||||
|
||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.network.response.weather.WeatherApiResponse
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.Repository
|
||||
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
|
||||
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
||||
@@ -17,6 +17,7 @@ import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.io.IOException
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class ServicesHelperTest : BaseTest() {
|
||||
|
||||
@@ -31,40 +32,45 @@ class ServicesHelperTest : BaseTest() {
|
||||
@MockK
|
||||
lateinit var locationProvider: LocationProviderImpl
|
||||
|
||||
lateinit var weatherResponse: WeatherResponse
|
||||
lateinit var weatherResponse: WeatherApiResponse
|
||||
private var lat by Delegates.notNull<Double>()
|
||||
private var long by Delegates.notNull<Double>()
|
||||
private lateinit var latlon: Pair<Double, Double>
|
||||
private lateinit var fullWeather: FullWeather
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockKAnnotations.init(this)
|
||||
helper = ServicesHelper(repository, settingsRepository, locationProvider)
|
||||
|
||||
weatherResponse = getTestData("weather_sample.json", WeatherResponse::class.java)
|
||||
weatherResponse = getTestData("new_response.json", WeatherApiResponse::class.java)
|
||||
lat = weatherResponse.latitude!!
|
||||
long = weatherResponse.longitude!!
|
||||
latlon = Pair(lat, long)
|
||||
fullWeather = weatherResponse.mapData().apply {
|
||||
temperatureUnit = "°C"
|
||||
locationString = CURRENT_LOCATION
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testWidgetDataAsync_successfulResponse() = runBlocking {
|
||||
// Arrange
|
||||
val entityItem = EntityItem(CURRENT_LOCATION, FullWeather(weatherResponse).apply {
|
||||
temperatureUnit = "°C"
|
||||
locationString = CURRENT_LOCATION
|
||||
})
|
||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||
|
||||
// Act
|
||||
coEvery { locationProvider.getCurrentLatLong() } returns Pair(
|
||||
weatherResponse.lat,
|
||||
weatherResponse.lon
|
||||
)
|
||||
coEvery { locationProvider.getCurrentLatLong() } returns Pair(lat, long)
|
||||
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
||||
coEvery {
|
||||
repository.getWeatherFromApi(
|
||||
weatherResponse.lat.toString(),
|
||||
weatherResponse.lon.toString()
|
||||
lat.toString(),
|
||||
long.toString()
|
||||
)
|
||||
}.returns(weatherResponse)
|
||||
coEvery {
|
||||
locationProvider.getLocationNameFromLatLong(
|
||||
weatherResponse.lat,
|
||||
weatherResponse.lon
|
||||
lat,
|
||||
long
|
||||
)
|
||||
}.returns(CURRENT_LOCATION)
|
||||
every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit
|
||||
|
||||
10633
app/src/test/resources/new_response.json
Normal file
10633
app/src/test/resources/new_response.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user