mirror of
https://github.com/hmalik144/Weather-apps.git
synced 2025-12-10 02:05:20 +00:00
Api fix (#38)
- minor code clean up - Remove old api and response - Tests passed - Weather API successfully replaces
This commit is contained in:
@@ -18,8 +18,8 @@ android {
|
|||||||
applicationId "com.appttude.h_mal.atlas_weather"
|
applicationId "com.appttude.h_mal.atlas_weather"
|
||||||
minSdkVersion MIN_SDK_VERSION
|
minSdkVersion MIN_SDK_VERSION
|
||||||
targetSdkVersion TARGET_SDK_VERSION
|
targetSdkVersion TARGET_SDK_VERSION
|
||||||
versionCode 5
|
versionCode 6
|
||||||
versionName "3.0"
|
versionName "3.1"
|
||||||
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
testInstrumentationRunner "com.appttude.h_mal.atlas_weather.application.TestRunner"
|
||||||
vectorDrawables.useSupportLibrary = true
|
vectorDrawables.useSupportLibrary = true
|
||||||
|
|
||||||
@@ -32,6 +32,10 @@ android {
|
|||||||
buildConfigField "String", "ParamOne", System.getenv('WEATHER_API')
|
buildConfigField "String", "ParamOne", System.getenv('WEATHER_API')
|
||||||
buildConfigField "String", "ParamTwo", System.getenv('SEARCH_API')
|
buildConfigField "String", "ParamTwo", System.getenv('SEARCH_API')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
packagingOptions {
|
||||||
|
resources.excludes.add("META-INF/*")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
android {
|
android {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"cod": 401,
|
|
||||||
"message": "Invalid API key. Please see http://openweathermap.org/faq#error401 for more info."
|
|
||||||
}
|
|
||||||
1
app/src/androidTest/assets/invalid_api_key_response.txt
Normal file
1
app/src/androidTest/assets/invalid_api_key_response.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
No account found with API key 'wrong api key'
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
10603
app/src/androidTest/assets/valid_response_with_alert.json
Normal file
10603
app/src/androidTest/assets/valid_response_with_alert.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -80,8 +80,8 @@ open class BaseTest<A : Activity>(
|
|||||||
afterLaunch()
|
afterLaunch()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stubEndpoint(url: String, stub: Stubs, code: Int = 200) {
|
fun stubEndpoint(url: String, stub: Stubs, code: Int = 200, extension: String = ".json") {
|
||||||
testApp.stubUrl(url, stub.id, code)
|
testApp.stubUrl(url, stub.id, code, extension)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unstubEndpoint(url: String) {
|
fun unstubEndpoint(url: String) {
|
||||||
@@ -125,7 +125,6 @@ open class BaseTest<A : Activity>(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
fun checkToastMessage(message: String) {
|
fun checkToastMessage(message: String) {
|
||||||
Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView)))
|
Espresso.onView(ViewMatchers.withText(message)).inRoot(withDecorView(Matchers.not(decorView)))
|
||||||
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
|
||||||
|
|||||||
@@ -17,7 +17,12 @@ import androidx.test.espresso.action.ViewActions.swipeDown
|
|||||||
import androidx.test.espresso.assertion.ViewAssertions.matches
|
import androidx.test.espresso.assertion.ViewAssertions.matches
|
||||||
import androidx.test.espresso.contrib.PickerActions
|
import androidx.test.espresso.contrib.PickerActions
|
||||||
import androidx.test.espresso.contrib.RecyclerViewActions
|
import androidx.test.espresso.contrib.RecyclerViewActions
|
||||||
import androidx.test.espresso.matcher.ViewMatchers.*
|
import androidx.test.espresso.matcher.ViewMatchers.hasDescendant
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.isRoot
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withClassName
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withId
|
||||||
|
import androidx.test.espresso.matcher.ViewMatchers.withText
|
||||||
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
|
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
|
||||||
import com.appttude.h_mal.atlas_weather.helpers.checkErrorMessage
|
import com.appttude.h_mal.atlas_weather.helpers.checkErrorMessage
|
||||||
import com.appttude.h_mal.atlas_weather.helpers.checkImage
|
import com.appttude.h_mal.atlas_weather.helpers.checkImage
|
||||||
@@ -60,7 +65,7 @@ open class BaseTestRobot {
|
|||||||
.atPosition(position).perform(click())
|
.atPosition(position).perform(click())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction? {
|
fun <VH : ViewHolder> scrollToRecyclerItem(recyclerId: Int, text: String): ViewInteraction {
|
||||||
return matchView(recyclerId)
|
return matchView(recyclerId)
|
||||||
.perform(
|
.perform(
|
||||||
// scrollTo will fail the test if no item matches.
|
// scrollTo will fail the test if no item matches.
|
||||||
@@ -73,7 +78,7 @@ open class BaseTestRobot {
|
|||||||
fun <VH : ViewHolder> scrollToRecyclerItem(
|
fun <VH : ViewHolder> scrollToRecyclerItem(
|
||||||
recyclerId: Int,
|
recyclerId: Int,
|
||||||
resIdForString: Int
|
resIdForString: Int
|
||||||
): ViewInteraction? {
|
): ViewInteraction {
|
||||||
return matchView(recyclerId)
|
return matchView(recyclerId)
|
||||||
.perform(
|
.perform(
|
||||||
// scrollTo will fail the test if no item matches.
|
// scrollTo will fail the test if no item matches.
|
||||||
@@ -86,7 +91,7 @@ open class BaseTestRobot {
|
|||||||
fun <VH : ViewHolder> scrollToRecyclerItemByPosition(
|
fun <VH : ViewHolder> scrollToRecyclerItemByPosition(
|
||||||
recyclerId: Int,
|
recyclerId: Int,
|
||||||
position: Int
|
position: Int
|
||||||
): ViewInteraction? {
|
): ViewInteraction {
|
||||||
return matchView(recyclerId)
|
return matchView(recyclerId)
|
||||||
.perform(
|
.perform(
|
||||||
// scrollTo will fail the test if no item matches.
|
// scrollTo will fail the test if no item matches.
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ class MockingNetworkInterceptor(
|
|||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
idlingResource.increment()
|
idlingResource.increment()
|
||||||
val original = chain.request()
|
val original = chain.request()
|
||||||
val originalHttpUrl = original.url.toString().split("?")[0]
|
val originalHttpUrl = original.url.toString()
|
||||||
|
|
||||||
feedMap[originalHttpUrl]?.let { responsePair ->
|
feedMap[feedMap.keys.first { originalHttpUrl.contains(it) }]?.let { responsePair ->
|
||||||
val code = responsePair.second
|
val code = responsePair.second
|
||||||
val jsonBody = responsePair.first
|
val jsonBody = responsePair.first
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.utils
|
package com.appttude.h_mal.atlas_weather.utils
|
||||||
|
|
||||||
|
const val baseUrl = "https://weather.visualcrossing.com/VisualCrossingWebServices/rest/services/timeline/"
|
||||||
enum class Stubs(
|
enum class Stubs(
|
||||||
val id: String
|
val id: String
|
||||||
) {
|
) {
|
||||||
@@ -7,5 +8,5 @@ enum class Stubs(
|
|||||||
Imperial("valid_response_imperial"),
|
Imperial("valid_response_imperial"),
|
||||||
WrongLocation("wrong_location_response"),
|
WrongLocation("wrong_location_response"),
|
||||||
InvalidKey("invalid_api_key_response"),
|
InvalidKey("invalid_api_key_response"),
|
||||||
Sydney("valid_response_metric_sydney")
|
Sydney("valid_response_metric_sydney"),
|
||||||
}
|
}
|
||||||
@@ -49,9 +49,9 @@ class TestAppClass : AppClass() {
|
|||||||
return database
|
return database
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stubUrl(url: String, rawPath: String, code: Int = 200) {
|
fun stubUrl(url: String, rawPath: String, code: Int = 200, extension: String = ".json") {
|
||||||
val iStream =
|
val iStream =
|
||||||
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath.json")
|
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath$extension")
|
||||||
val data = iStream.bufferedReader().use(BufferedReader::readText)
|
val data = iStream.bufferedReader().use(BufferedReader::readText)
|
||||||
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
|
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.R
|
||||||
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
|
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
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType.Companion.getLabel
|
||||||
|
|
||||||
|
|
||||||
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }
|
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }
|
||||||
@@ -24,10 +25,7 @@ class SettingsScreen : BaseTestRobot() {
|
|||||||
RecyclerViewActions.actionOnItem<ViewHolder>(
|
RecyclerViewActions.actionOnItem<ViewHolder>(
|
||||||
ViewMatchers.hasDescendant(withText(R.string.weather_units)),
|
ViewMatchers.hasDescendant(withText(R.string.weather_units)),
|
||||||
click()))
|
click()))
|
||||||
val label = when (unitType) {
|
val label = unitType.getLabel()
|
||||||
UnitType.METRIC -> "Metric"
|
|
||||||
UnitType.IMPERIAL -> "Imperial"
|
|
||||||
}
|
|
||||||
|
|
||||||
onView(withText(label))
|
onView(withText(label))
|
||||||
.inRoot(isDialog())
|
.inRoot(isDialog())
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import com.appttude.h_mal.atlas_weather.utils.Stubs
|
|||||||
import com.appttude.h_mal.atlas_weather.robot.furtherInfoScreen
|
import com.appttude.h_mal.atlas_weather.robot.furtherInfoScreen
|
||||||
import com.appttude.h_mal.atlas_weather.robot.settingsScreen
|
import com.appttude.h_mal.atlas_weather.robot.settingsScreen
|
||||||
import com.appttude.h_mal.atlas_weather.robot.weatherScreen
|
import com.appttude.h_mal.atlas_weather.robot.weatherScreen
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import tools.fastlane.screengrab.Screengrab
|
import tools.fastlane.screengrab.Screengrab
|
||||||
|
|
||||||
@@ -17,7 +18,7 @@ import tools.fastlane.screengrab.Screengrab
|
|||||||
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
stubLocation("London", 51.51, -0.13)
|
stubLocation("London", 51.51, -0.13)
|
||||||
clearPrefs()
|
clearPrefs()
|
||||||
}
|
}
|
||||||
@@ -50,7 +51,7 @@ class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
openMenuItem()
|
openMenuItem()
|
||||||
}
|
}
|
||||||
settingsScreen {
|
settingsScreen {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial)
|
stubEndpoint(baseUrl, Stubs.Imperial)
|
||||||
Screengrab.screenshot("SettingsScreen")
|
Screengrab.screenshot("SettingsScreen")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,27 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.tests
|
package com.appttude.h_mal.atlas_weather.tests
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.BaseTest
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
|
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
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.Stubs
|
||||||
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.InvalidKey, 400)
|
stubEndpoint(baseUrl, Stubs.InvalidKey, 400, ".txt")
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun loadApp_invalidKeyWeatherResponse_returnsEmptyViewPage() {
|
|
||||||
homeScreen {
|
|
||||||
waitFor(2000)
|
|
||||||
// verify empty
|
|
||||||
verifyUnableToRetrieve()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
|
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
|
||||||
homeScreen {
|
homeScreen {
|
||||||
waitFor(2000)
|
|
||||||
// verify empty
|
// verify empty
|
||||||
verifyUnableToRetrieve()
|
verifyUnableToRetrieve()
|
||||||
|
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
refresh()
|
refresh()
|
||||||
verifyCurrentTemperature(2)
|
verifyCurrentTemperature(13)
|
||||||
verifyCurrentLocation("Mock Location")
|
verifyCurrentLocation("Mock Location")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,19 +5,20 @@ import com.appttude.h_mal.atlas_weather.BaseTest
|
|||||||
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
import com.appttude.h_mal.atlas_weather.robot.homeScreen
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
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.Stubs
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun loadApp_validWeatherResponse_returnsValidPage() {
|
fun loadApp_validWeatherResponse_returnsValidPage() {
|
||||||
homeScreen {
|
homeScreen {
|
||||||
isDisplayed()
|
isDisplayed()
|
||||||
verifyCurrentTemperature(2)
|
verifyCurrentTemperature(13)
|
||||||
verifyCurrentLocation("Mock Location")
|
verifyCurrentLocation("Mock Location")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import org.kodein.di.LazyKodein
|
|||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
|
|
||||||
class TestAppClass : AppClass() {
|
class TestAppClass : AppClass() {
|
||||||
|
|
||||||
override val kodein: LazyKodein = super.kodein
|
override val kodein: LazyKodein = super.kodein
|
||||||
|
|
||||||
private val idlingResources = CountingIdlingResource("Data_loader")
|
private val idlingResources = CountingIdlingResource("Data_loader")
|
||||||
@@ -53,9 +52,9 @@ class TestAppClass : AppClass() {
|
|||||||
return database
|
return database
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stubUrl(url: String, rawPath: String, code: Int = 200) {
|
fun stubUrl(url: String, rawPath: String, code: Int = 200, extension: String = ".json") {
|
||||||
val iStream =
|
val iStream =
|
||||||
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath.json")
|
InstrumentationRegistry.getInstrumentation().context.assets.open("$rawPath$extension")
|
||||||
val data = iStream.bufferedReader().use(BufferedReader::readText)
|
val data = iStream.bufferedReader().use(BufferedReader::readText)
|
||||||
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
|
mockingNetworkInterceptor.addUrlStub(url = url, data = data, code = code)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ import androidx.test.filters.SmallTest
|
|||||||
import com.appttude.h_mal.atlas_weather.BaseTest
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
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.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.furtherInfoScreen
|
||||||
import com.appttude.h_mal.monoWeather.robot.settingsScreen
|
import com.appttude.h_mal.monoWeather.robot.settingsScreen
|
||||||
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||||
import org.junit.Ignore
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import tools.fastlane.screengrab.Screengrab
|
import tools.fastlane.screengrab.Screengrab
|
||||||
|
|
||||||
@@ -18,8 +18,8 @@ import tools.fastlane.screengrab.Screengrab
|
|||||||
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
stubLocation("London", 51.51, -0.13)
|
stubLocation("London", 51.5064, -0.12721)
|
||||||
clearPrefs()
|
clearPrefs()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +52,7 @@ class SnapshotCaptureTest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
openMenuItem()
|
openMenuItem()
|
||||||
}
|
}
|
||||||
settingsScreen {
|
settingsScreen {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial)
|
stubEndpoint(baseUrl, Stubs.Imperial)
|
||||||
Screengrab.screenshot("SettingsScreen")
|
Screengrab.screenshot("SettingsScreen")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.R
|
||||||
import com.appttude.h_mal.atlas_weather.helpers.EspressoHelper.waitForView
|
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
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType.Companion.getLabel
|
||||||
|
|
||||||
|
|
||||||
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }
|
fun settingsScreen(func: SettingsScreen.() -> Unit) = SettingsScreen().apply { func() }
|
||||||
@@ -24,10 +25,7 @@ class SettingsScreen : BaseTestRobot() {
|
|||||||
RecyclerViewActions.actionOnItem<ViewHolder>(
|
RecyclerViewActions.actionOnItem<ViewHolder>(
|
||||||
ViewMatchers.hasDescendant(withText(R.string.weather_units)),
|
ViewMatchers.hasDescendant(withText(R.string.weather_units)),
|
||||||
click()))
|
click()))
|
||||||
val label = when (unitType) {
|
val label = unitType.getLabel()
|
||||||
UnitType.METRIC -> "Metric"
|
|
||||||
UnitType.IMPERIAL -> "Imperial"
|
|
||||||
}
|
|
||||||
|
|
||||||
onView(withText(label))
|
onView(withText(label))
|
||||||
.inRoot(isDialog())
|
.inRoot(isDialog())
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.appttude.h_mal.monoWeather.robot
|
|||||||
import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
import com.appttude.h_mal.atlas_weather.BaseTestRobot
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
import com.appttude.h_mal.monoWeather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily
|
import com.appttude.h_mal.monoWeather.ui.home.adapter.forecastDaily.ViewHolderForecastDaily
|
||||||
import com.appttude.h_mal.monoWeather.ui.home.adapter.further.ViewHolderFurtherDetails
|
|
||||||
|
|
||||||
fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() }
|
fun weatherScreen(func: WeatherScreen.() -> Unit) = WeatherScreen().apply { func() }
|
||||||
class WeatherScreen : BaseTestRobot() {
|
class WeatherScreen : BaseTestRobot() {
|
||||||
|
|||||||
@@ -4,35 +4,30 @@ package com.appttude.h_mal.monoWeather.tests
|
|||||||
import com.appttude.h_mal.atlas_weather.BaseTest
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
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.Stubs
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||||
import org.junit.Ignore
|
import org.junit.FixMethodOrder
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class HomePageNoDataUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.InvalidKey, 400)
|
stubEndpoint(baseUrl, Stubs.InvalidKey, 400, ".txt")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun loadApp_invalidKeyWeatherResponse_returnsEmptyViewPage() {
|
|
||||||
weatherScreen {
|
|
||||||
// verify empty
|
|
||||||
verifyUnableToRetrieve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore("Test is flakey - must investigate")
|
|
||||||
@Test
|
@Test
|
||||||
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
|
fun invalidKeyWeatherResponse_swipeToRefresh_returnsValidPage() {
|
||||||
weatherScreen {
|
weatherScreen {
|
||||||
// verify empty
|
// verify empty
|
||||||
verifyUnableToRetrieve()
|
verifyUnableToRetrieve()
|
||||||
|
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
refresh()
|
refresh()
|
||||||
verifyCurrentTemperature(2)
|
verifyCurrentTemperature(13)
|
||||||
verifyCurrentLocation("Mock Location")
|
verifyCurrentLocation("Mock Location")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,17 @@ package com.appttude.h_mal.monoWeather.tests
|
|||||||
|
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.BaseTest
|
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.ui.MainActivity
|
||||||
import com.appttude.h_mal.atlas_weather.utils.Stubs
|
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.furtherInfoScreen
|
||||||
import com.appttude.h_mal.monoWeather.robot.settingsScreen
|
|
||||||
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||||
import okio.IOException
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import tools.fastlane.screengrab.Screengrab
|
|
||||||
|
|
||||||
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
clearPrefs()
|
clearPrefs()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +20,7 @@ class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
fun loadApp_validWeatherResponse_returnsValidPage() {
|
fun loadApp_validWeatherResponse_returnsValidPage() {
|
||||||
weatherScreen {
|
weatherScreen {
|
||||||
isDisplayed()
|
isDisplayed()
|
||||||
verifyCurrentTemperature(2)
|
verifyCurrentTemperature(13)
|
||||||
verifyCurrentLocation("Mock Location")
|
verifyCurrentLocation("Mock Location")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -32,35 +29,15 @@ class HomePageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
fun loadApp_validWeatherResponse_viewFurtherDetailsPage() {
|
fun loadApp_validWeatherResponse_viewFurtherDetailsPage() {
|
||||||
weatherScreen {
|
weatherScreen {
|
||||||
isDisplayed()
|
isDisplayed()
|
||||||
verifyCurrentTemperature(2)
|
verifyCurrentTemperature(13)
|
||||||
verifyCurrentLocation("Mock Location")
|
verifyCurrentLocation("Mock Location")
|
||||||
tapDayInformationByPosition(4)
|
tapDayInformationByPosition(4)
|
||||||
}
|
}
|
||||||
furtherInfoScreen {
|
furtherInfoScreen {
|
||||||
isDisplayed()
|
isDisplayed()
|
||||||
verifyMaxTemperature(12)
|
verifyMaxTemperature(15)
|
||||||
verifyAverageTemperature(9)
|
verifyAverageTemperature(11)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun loadApp_changeToImperial_returnsValidPage() {
|
|
||||||
weatherScreen {
|
|
||||||
isDisplayed()
|
|
||||||
verifyCurrentTemperature(2)
|
|
||||||
verifyCurrentLocation("Mock Location")
|
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Imperial)
|
|
||||||
openMenuItem()
|
|
||||||
}
|
|
||||||
settingsScreen {
|
|
||||||
selectWeatherUnits(UnitType.IMPERIAL)
|
|
||||||
goBack()
|
|
||||||
}
|
|
||||||
weatherScreen {
|
|
||||||
isDisplayed()
|
|
||||||
refresh()
|
|
||||||
verifyCurrentTemperature(58)
|
|
||||||
verifyCurrentLocation("Mock Location")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.appttude.h_mal.monoWeather.tests
|
||||||
|
|
||||||
|
|
||||||
|
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.settingsScreen
|
||||||
|
import com.appttude.h_mal.monoWeather.robot.weatherScreen
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class SettingsPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
|
override fun beforeLaunch() {
|
||||||
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
|
clearPrefs()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun loadApp_changeToImperial_returnsValidPage() {
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
verifyCurrentTemperature(13)
|
||||||
|
verifyCurrentLocation("Mock Location")
|
||||||
|
stubEndpoint(baseUrl, Stubs.Imperial)
|
||||||
|
openMenuItem()
|
||||||
|
}
|
||||||
|
settingsScreen {
|
||||||
|
selectWeatherUnits(UnitType.IMPERIAL)
|
||||||
|
goBack()
|
||||||
|
}
|
||||||
|
weatherScreen {
|
||||||
|
isDisplayed()
|
||||||
|
refresh()
|
||||||
|
verifyCurrentTemperature(56)
|
||||||
|
verifyCurrentLocation("Mock Location")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ package com.appttude.h_mal.monoWeather.tests
|
|||||||
import com.appttude.h_mal.atlas_weather.BaseTest
|
import com.appttude.h_mal.atlas_weather.BaseTest
|
||||||
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
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.Stubs
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.baseUrl
|
||||||
import com.appttude.h_mal.monoWeather.robot.ContainerRobot.Tab.WORLD
|
import com.appttude.h_mal.monoWeather.robot.ContainerRobot.Tab.WORLD
|
||||||
import com.appttude.h_mal.monoWeather.robot.addLocation
|
import com.appttude.h_mal.monoWeather.robot.addLocation
|
||||||
import com.appttude.h_mal.monoWeather.robot.container
|
import com.appttude.h_mal.monoWeather.robot.container
|
||||||
@@ -14,7 +15,8 @@ import org.junit.Test
|
|||||||
class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
||||||
|
|
||||||
override fun beforeLaunch() {
|
override fun beforeLaunch() {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Metric)
|
stubEndpoint(baseUrl, Stubs.Metric)
|
||||||
|
stubLocation("London", 51.5064,-0.12721)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -26,8 +28,8 @@ class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
clickFab()
|
clickFab()
|
||||||
}
|
}
|
||||||
addLocation {
|
addLocation {
|
||||||
stubEndpoint("https://api.openweathermap.org/data/2.5/onecall", Stubs.Sydney)
|
stubEndpoint(baseUrl, Stubs.Sydney)
|
||||||
stubLocation("Sydney", -33.89, -151.12)
|
stubLocation("Sydney",-33.8696,151.207)
|
||||||
setLocation("Sydney")
|
setLocation("Sydney")
|
||||||
submit()
|
submit()
|
||||||
}
|
}
|
||||||
@@ -36,7 +38,7 @@ class WorldPageUITest : BaseTest<MainActivity>(MainActivity::class.java) {
|
|||||||
}
|
}
|
||||||
weatherScreen {
|
weatherScreen {
|
||||||
isDisplayed()
|
isDisplayed()
|
||||||
verifyCurrentTemperature(12)
|
verifyCurrentTemperature(16)
|
||||||
verifyCurrentLocation("Sydney")
|
verifyCurrentLocation("Sydney")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.appttude.h_mal.atlas_weather.application
|
|||||||
import android.app.Application
|
import android.app.Application
|
||||||
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
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.location.LocationProvider
|
||||||
|
import com.appttude.h_mal.atlas_weather.data.network.Api
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
import com.appttude.h_mal.atlas_weather.data.network.WeatherApi
|
||||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
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.RepositoryImpl
|
||||||
@@ -12,7 +13,6 @@ import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
|||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import org.kodein.di.Kodein
|
import org.kodein.di.Kodein
|
||||||
import org.kodein.di.KodeinAware
|
import org.kodein.di.KodeinAware
|
||||||
import org.kodein.di.KodeinContainer
|
|
||||||
import org.kodein.di.android.x.androidXModule
|
import org.kodein.di.android.x.androidXModule
|
||||||
import org.kodein.di.generic.bind
|
import org.kodein.di.generic.bind
|
||||||
import org.kodein.di.generic.instance
|
import org.kodein.di.generic.instance
|
||||||
@@ -29,7 +29,7 @@ abstract class BaseAppClass : Application(), KodeinAware {
|
|||||||
val parentModule = Kodein.Module("Parent Module", allowSilentOverride = true) {
|
val parentModule = Kodein.Module("Parent Module", allowSilentOverride = true) {
|
||||||
import(androidXModule(this@BaseAppClass))
|
import(androidXModule(this@BaseAppClass))
|
||||||
|
|
||||||
bind() from singleton { createNetworkModule() }
|
bind() from singleton { createNetworkModule() as WeatherApi }
|
||||||
bind() from singleton { createLocationModule() }
|
bind() from singleton { createLocationModule() }
|
||||||
|
|
||||||
bind() from singleton { Gson() }
|
bind() from singleton { Gson() }
|
||||||
@@ -41,7 +41,7 @@ abstract class BaseAppClass : Application(), KodeinAware {
|
|||||||
bind() from singleton { WeatherSource(instance(), instance()) }
|
bind() from singleton { WeatherSource(instance(), instance()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun createNetworkModule(): WeatherApi
|
abstract fun createNetworkModule(): Api
|
||||||
abstract fun createLocationModule(): LocationProvider
|
abstract fun createLocationModule(): LocationProvider
|
||||||
abstract fun createRoomDatabase(): AppDatabase
|
abstract fun createRoomDatabase(): AppDatabase
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import com.appttude.h_mal.atlas_weather.data.repository.Repository
|
|||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
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.data.room.entity.EntityItem
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
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.model.weather.FullWeather
|
||||||
|
import com.appttude.h_mal.atlas_weather.utils.getSymbol
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class WeatherSource(
|
class WeatherSource(
|
||||||
@@ -33,12 +33,14 @@ class WeatherSource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
suspend fun forceFetchWeather(latLon: Pair<Double, Double>,
|
suspend fun forceFetchWeather(
|
||||||
locationType: LocationType = LocationType.Town): FullWeather {
|
latLon: Pair<Double, Double>,
|
||||||
|
locationType: LocationType = LocationType.Town
|
||||||
|
): FullWeather {
|
||||||
// get data from database
|
// get data from database
|
||||||
val weatherEntity = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
|
val weatherEntity = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
|
||||||
// check unit type - if same do nothing
|
// 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
|
if (weatherEntity.weather.temperatureUnit == units) return weatherEntity.weather
|
||||||
// load data for forced
|
// load data for forced
|
||||||
return fetchWeather(
|
return fetchWeather(
|
||||||
@@ -55,11 +57,13 @@ class WeatherSource(
|
|||||||
// Get weather from api
|
// Get weather from api
|
||||||
val weather = repository
|
val weather = repository
|
||||||
.getWeatherFromApi(latLon.first.toString(), latLon.second.toString())
|
.getWeatherFromApi(latLon.first.toString(), latLon.second.toString())
|
||||||
|
val lat = weather.latitude ?: latLon.first
|
||||||
|
val long = weather.longitude ?: latLon.second
|
||||||
val currentLocation =
|
val currentLocation =
|
||||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon, locationType)
|
locationProvider.getLocationNameFromLatLong(lat, long, locationType)
|
||||||
val unit = repository.getUnitType()
|
val unit = repository.getUnitType().getSymbol()
|
||||||
val fullWeather = FullWeather(weather).apply {
|
val fullWeather = weather.mapData().apply {
|
||||||
temperatureUnit = if (unit == UnitType.METRIC) "°C" else "°F"
|
temperatureUnit = unit
|
||||||
locationString = currentLocation
|
locationString = currentLocation
|
||||||
}
|
}
|
||||||
val entityItem = EntityItem(locationName, fullWeather)
|
val entityItem = EntityItem(locationName, fullWeather)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network
|
package com.appttude.h_mal.atlas_weather.data.network
|
||||||
|
|
||||||
class NetworkModule : BaseNetworkModule() {
|
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/"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network
|
package com.appttude.h_mal.atlas_weather.data.network
|
||||||
|
|
||||||
import org.json.JSONException
|
import retrofit2.HttpException
|
||||||
import org.json.JSONObject
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
abstract class ResponseUnwrap {
|
abstract class ResponseUnwrap {
|
||||||
|
|
||||||
@@ -15,18 +13,7 @@ abstract class ResponseUnwrap {
|
|||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
return response.body()!!
|
return response.body()!!
|
||||||
} else {
|
} else {
|
||||||
val error = response.errorBody()?.string()
|
throw HttpException(response)
|
||||||
|
|
||||||
val errorMessage = error?.let {
|
|
||||||
try {
|
|
||||||
JSONObject(it).getString("message")
|
|
||||||
} catch (e: JSONException) {
|
|
||||||
e.printStackTrace()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} ?: "Error Code: ${response.code()}"
|
|
||||||
|
|
||||||
throw IOException(errorMessage)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network
|
package com.appttude.h_mal.atlas_weather.data.network
|
||||||
|
|
||||||
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 retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
|
||||||
interface WeatherApi : Api {
|
interface WeatherApi : Api {
|
||||||
|
|
||||||
@GET("onecall?")
|
@GET("{location}")
|
||||||
suspend fun getFromApi(
|
suspend fun getFromApi(
|
||||||
@Query("lat") query: String,
|
@Path("location") location: String,
|
||||||
@Query("lon") lon: String,
|
@Query("contentType") exclude: String = "json",
|
||||||
@Query("exclude") exclude: String = "minutely",
|
@Query("unitGroup") units: String = "metric"
|
||||||
@Query("units") units: String = "metric"
|
): Response<WeatherApiResponse>
|
||||||
): Response<WeatherResponse>
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class QueryParamsInterceptor : Interceptor {
|
|||||||
val original = chain.request()
|
val original = chain.request()
|
||||||
|
|
||||||
val url = original.url.newBuilder()
|
val url = original.url.newBuilder()
|
||||||
.addQueryParameter("appid", id)
|
.addQueryParameter("key", id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
// Request customization: add request headers
|
// Request customization: add request headers
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.networkUtils
|
package com.appttude.h_mal.atlas_weather.data.network.networkUtils
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkInterceptor
|
import com.appttude.h_mal.atlas_weather.data.network.interceptors.NetworkInterceptor
|
||||||
|
import com.google.gson.GsonBuilder
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
val loggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
@@ -34,6 +36,13 @@ fun buildOkHttpClient(
|
|||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun createGsonConverterFactory(): GsonConverterFactory {
|
||||||
|
val gson = GsonBuilder()
|
||||||
|
.excludeFieldsWithModifiers(Modifier.TRANSIENT)
|
||||||
|
.create()
|
||||||
|
return GsonConverterFactory.create(gson)
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> createRetrofit(
|
fun <T> createRetrofit(
|
||||||
baseUrl: String,
|
baseUrl: String,
|
||||||
okHttpClient: OkHttpClient,
|
okHttpClient: OkHttpClient,
|
||||||
@@ -42,7 +51,7 @@ fun <T> createRetrofit(
|
|||||||
return Retrofit.Builder()
|
return Retrofit.Builder()
|
||||||
.client(okHttpClient)
|
.client(okHttpClient)
|
||||||
.baseUrl(baseUrl)
|
.baseUrl(baseUrl)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(createGsonConverterFactory())
|
||||||
.build()
|
.build()
|
||||||
.create(service)
|
.create(service)
|
||||||
}
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class Current(
|
|
||||||
|
|
||||||
@field:SerializedName("sunrise")
|
|
||||||
val sunrise: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("temp")
|
|
||||||
val temp: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("visibility")
|
|
||||||
val visibility: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("uvi")
|
|
||||||
val uvi: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("pressure")
|
|
||||||
val pressure: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("clouds")
|
|
||||||
val clouds: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("feels_like")
|
|
||||||
val feelsLike: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dt")
|
|
||||||
val dt: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_deg")
|
|
||||||
val windDeg: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dew_point")
|
|
||||||
val dewPoint: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("sunset")
|
|
||||||
val sunset: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("weather")
|
|
||||||
val weather: List<WeatherItem?>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("humidity")
|
|
||||||
val humidity: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_speed")
|
|
||||||
val windSpeed: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class DailyItem(
|
|
||||||
|
|
||||||
@field:SerializedName("sunrise")
|
|
||||||
val sunrise: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("temp")
|
|
||||||
val temp: Temp? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("uvi")
|
|
||||||
val uvi: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("pressure")
|
|
||||||
val pressure: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("clouds")
|
|
||||||
val clouds: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("feels_like")
|
|
||||||
val feelsLike: FeelsLike? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dt")
|
|
||||||
val dt: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("pop")
|
|
||||||
val pop: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_deg")
|
|
||||||
val windDeg: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dew_point")
|
|
||||||
val dewPoint: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("sunset")
|
|
||||||
val sunset: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("weather")
|
|
||||||
val weather: List<WeatherItem?>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("humidity")
|
|
||||||
val humidity: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_speed")
|
|
||||||
val windSpeed: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("rain")
|
|
||||||
val rain: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class FeelsLike(
|
|
||||||
|
|
||||||
@field:SerializedName("eve")
|
|
||||||
val eve: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("night")
|
|
||||||
val night: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("day")
|
|
||||||
val day: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("morn")
|
|
||||||
val morn: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class Hour(
|
|
||||||
|
|
||||||
@field:SerializedName("sunrise")
|
|
||||||
val sunrise: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("temp")
|
|
||||||
val temp: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("visibility")
|
|
||||||
val visibility: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("uvi")
|
|
||||||
val uvi: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("pressure")
|
|
||||||
val pressure: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("clouds")
|
|
||||||
val clouds: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("feels_like")
|
|
||||||
val feelsLike: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dt")
|
|
||||||
val dt: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_deg")
|
|
||||||
val windDeg: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("dew_point")
|
|
||||||
val dewPoint: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("sunset")
|
|
||||||
val sunset: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("weather")
|
|
||||||
val weather: List<WeatherItem?>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("humidity")
|
|
||||||
val humidity: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("wind_speed")
|
|
||||||
val windSpeed: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class Response(
|
|
||||||
|
|
||||||
@field:SerializedName("current")
|
|
||||||
val current: Current? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("timezone")
|
|
||||||
val timezone: String? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("timezone_offset")
|
|
||||||
val timezoneOffset: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("daily")
|
|
||||||
val daily: List<DailyItem?>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("lon")
|
|
||||||
val lon: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("lat")
|
|
||||||
val lat: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class Temp(
|
|
||||||
|
|
||||||
@field:SerializedName("min")
|
|
||||||
val min: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("max")
|
|
||||||
val max: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("eve")
|
|
||||||
val eve: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("night")
|
|
||||||
val night: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("day")
|
|
||||||
val day: Double? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("morn")
|
|
||||||
val morn: Double? = null
|
|
||||||
)
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class WeatherItem(
|
|
||||||
|
|
||||||
@field:SerializedName("icon")
|
|
||||||
val icon: String? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("description")
|
|
||||||
val description: String? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("main")
|
|
||||||
val main: String? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("id")
|
|
||||||
val id: Int? = null
|
|
||||||
)
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.network.response.forecast
|
|
||||||
|
|
||||||
import com.google.gson.annotations.SerializedName
|
|
||||||
|
|
||||||
data class WeatherResponse(
|
|
||||||
|
|
||||||
@field:SerializedName("current")
|
|
||||||
val current: Current? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("timezone")
|
|
||||||
val timezone: String? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("timezone_offset")
|
|
||||||
val timezoneOffset: Int? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("hourly")
|
|
||||||
val hourly: List<Hour>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("daily")
|
|
||||||
val daily: List<DailyItem>? = null,
|
|
||||||
|
|
||||||
@field:SerializedName("lon")
|
|
||||||
val lon: Double = 0.00,
|
|
||||||
|
|
||||||
@field:SerializedName("lat")
|
|
||||||
val lat: Double = 0.00
|
|
||||||
)
|
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.data.network.response.weather
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class Alerts(
|
||||||
|
@SerializedName("event") var event: String? = null,
|
||||||
|
@SerializedName("headline") var headline: String? = null,
|
||||||
|
@SerializedName("ends") var ends: String? = null,
|
||||||
|
@SerializedName("endsEpoch") var endsEpoch: Int? = null,
|
||||||
|
@SerializedName("onset") var onset: String? = null,
|
||||||
|
@SerializedName("onsetEpoch") var onsetEpoch: Int? = null,
|
||||||
|
@SerializedName("id") var id: String? = null,
|
||||||
|
@SerializedName("language") var language: String? = null,
|
||||||
|
@SerializedName("link") var link: String? = null,
|
||||||
|
@SerializedName("description") var description: String? = null,
|
||||||
|
)
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.data.network.response.weather
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
|
||||||
|
data class CurrentConditions(
|
||||||
|
@SerializedName("datetime") var datetime: String? = null,
|
||||||
|
@SerializedName("datetimeEpoch") var datetimeEpoch: Int? = null,
|
||||||
|
@SerializedName("temp") var temp: Double? = null,
|
||||||
|
@SerializedName("feelslike") var feelslike: Double? = null,
|
||||||
|
@SerializedName("humidity") var humidity: Double? = null,
|
||||||
|
@SerializedName("dew") var dew: Double? = null,
|
||||||
|
@SerializedName("precip") var precip: Double? = null,
|
||||||
|
@SerializedName("precipprob") var precipprob: Double? = null,
|
||||||
|
@SerializedName("snow") var snow: Int? = null,
|
||||||
|
@SerializedName("snowdepth") var snowdepth: Int? = null,
|
||||||
|
@SerializedName("preciptype") var preciptype: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("windgust") var windgust: Double? = null,
|
||||||
|
@SerializedName("windspeed") var windspeed: Double? = null,
|
||||||
|
@SerializedName("winddir") var winddir: Double? = null,
|
||||||
|
@SerializedName("pressure") var pressure: Double? = null,
|
||||||
|
@SerializedName("visibility") var visibility: Double? = null,
|
||||||
|
@SerializedName("cloudcover") var cloudcover: Double? = null,
|
||||||
|
@SerializedName("solarradiation") var solarradiation: Double? = null,
|
||||||
|
@SerializedName("solarenergy") var solarenergy: Double? = null,
|
||||||
|
@SerializedName("uvindex") var uvindex: Int? = null,
|
||||||
|
@SerializedName("conditions") var conditions: String? = null,
|
||||||
|
@SerializedName("icon") var icon: String? = null,
|
||||||
|
@SerializedName("stations") var stations: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("source") var source: String? = null,
|
||||||
|
@SerializedName("sunrise") var sunrise: String? = null,
|
||||||
|
@SerializedName("sunriseEpoch") var sunriseEpoch: Int? = null,
|
||||||
|
@SerializedName("sunset") var sunset: String? = null,
|
||||||
|
@SerializedName("sunsetEpoch") var sunsetEpoch: Int? = null,
|
||||||
|
@SerializedName("moonphase") var moonphase: Double? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.data.network.response.weather
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
|
||||||
|
data class Days(
|
||||||
|
@SerializedName("datetime") var datetime: String? = null,
|
||||||
|
@SerializedName("datetimeEpoch") var datetimeEpoch: Int? = null,
|
||||||
|
@SerializedName("tempmax") var tempmax: Double? = null,
|
||||||
|
@SerializedName("tempmin") var tempmin: Double? = null,
|
||||||
|
@SerializedName("temp") var temp: Double? = null,
|
||||||
|
@SerializedName("feelslikemax") var feelslikemax: Double? = null,
|
||||||
|
@SerializedName("feelslikemin") var feelslikemin: Double? = null,
|
||||||
|
@SerializedName("feelslike") var feelslike: Double? = null,
|
||||||
|
@SerializedName("dew") var dew: Double? = null,
|
||||||
|
@SerializedName("humidity") var humidity: Double? = null,
|
||||||
|
@SerializedName("precip") var precip: Number? = null,
|
||||||
|
@SerializedName("precipprob") var precipprob: Double? = null,
|
||||||
|
@SerializedName("precipcover") var precipcover: Double? = null,
|
||||||
|
@SerializedName("preciptype") var preciptype: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("snow") var snow: Int? = null,
|
||||||
|
@SerializedName("snowdepth") var snowdepth: Int? = null,
|
||||||
|
@SerializedName("windgust") var windgust: Double? = null,
|
||||||
|
@SerializedName("windspeed") var windspeed: Double? = null,
|
||||||
|
@SerializedName("winddir") var winddir: Double? = null,
|
||||||
|
@SerializedName("pressure") var pressure: Double? = null,
|
||||||
|
@SerializedName("cloudcover") var cloudcover: Double? = null,
|
||||||
|
@SerializedName("visibility") var visibility: Double? = null,
|
||||||
|
@SerializedName("solarradiation") var solarradiation: Double? = null,
|
||||||
|
@SerializedName("solarenergy") var solarenergy: Double? = null,
|
||||||
|
@SerializedName("uvindex") var uvindex: Int? = null,
|
||||||
|
@SerializedName("severerisk") var severerisk: Int? = null,
|
||||||
|
@SerializedName("sunrise") var sunrise: String? = null,
|
||||||
|
@SerializedName("sunriseEpoch") var sunriseEpoch: Int? = null,
|
||||||
|
@SerializedName("sunset") var sunset: String? = null,
|
||||||
|
@SerializedName("sunsetEpoch") var sunsetEpoch: Int? = null,
|
||||||
|
@SerializedName("moonphase") var moonphase: Double? = null,
|
||||||
|
@SerializedName("conditions") var conditions: String? = null,
|
||||||
|
@SerializedName("description") var description: String? = null,
|
||||||
|
@SerializedName("icon") var icon: String? = null,
|
||||||
|
@SerializedName("stations") var stations: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("source") var source: String? = null,
|
||||||
|
@SerializedName("hours") var hours: ArrayList<Hours> = arrayListOf()
|
||||||
|
|
||||||
|
)
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.data.network.response.weather
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
|
||||||
|
data class Hours(
|
||||||
|
@SerializedName("datetime") var datetime: String? = null,
|
||||||
|
@SerializedName("datetimeEpoch") var datetimeEpoch: Int? = null,
|
||||||
|
@SerializedName("temp") var temp: Double? = null,
|
||||||
|
@SerializedName("feelslike") var feelslike: Double? = null,
|
||||||
|
@SerializedName("humidity") var humidity: Double? = null,
|
||||||
|
@SerializedName("dew") var dew: Double? = null,
|
||||||
|
@SerializedName("precip") var precip: Number? = null,
|
||||||
|
@SerializedName("precipprob") var precipprob: Double? = null,
|
||||||
|
@SerializedName("snow") var snow: Int? = null,
|
||||||
|
@SerializedName("snowdepth") var snowdepth: Int? = null,
|
||||||
|
@SerializedName("preciptype") var preciptype: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("windgust") var windgust: Double? = null,
|
||||||
|
@SerializedName("windspeed") var windspeed: Double? = null,
|
||||||
|
@SerializedName("winddir") var winddir: Double? = null,
|
||||||
|
@SerializedName("pressure") var pressure: Double? = null,
|
||||||
|
@SerializedName("visibility") var visibility: Double? = null,
|
||||||
|
@SerializedName("cloudcover") var cloudcover: Double? = null,
|
||||||
|
@SerializedName("solarradiation") var solarradiation: Double? = null,
|
||||||
|
@SerializedName("solarenergy") var solarenergy: Double? = null,
|
||||||
|
@SerializedName("uvindex") var uvindex: Int? = null,
|
||||||
|
@SerializedName("severerisk") var severerisk: Int? = null,
|
||||||
|
@SerializedName("conditions") var conditions: String? = null,
|
||||||
|
@SerializedName("icon") var icon: String? = null,
|
||||||
|
@SerializedName("stations") var stations: ArrayList<String> = arrayListOf(),
|
||||||
|
@SerializedName("source") var source: String? = null
|
||||||
|
)
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.data.network.response.weather
|
||||||
|
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.DataMapper
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.weather.Current
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.weather.DailyWeather
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.weather.Hour as FullWeatherHour
|
||||||
|
|
||||||
|
|
||||||
|
data class WeatherApiResponse(
|
||||||
|
@SerializedName("queryCost") var queryCost: Int? = null,
|
||||||
|
@SerializedName("latitude") var latitude: Double? = null,
|
||||||
|
@SerializedName("longitude") var longitude: Double? = null,
|
||||||
|
@SerializedName("resolvedAddress") var resolvedAddress: String? = null,
|
||||||
|
@SerializedName("address") var address: String? = null,
|
||||||
|
@SerializedName("timezone") var timezone: String? = null,
|
||||||
|
@SerializedName("tzoffset") var tzoffset: Int? = null,
|
||||||
|
@SerializedName("description") var description: String? = null,
|
||||||
|
@SerializedName("days") var days: ArrayList<Days> = arrayListOf(),
|
||||||
|
@SerializedName("alerts") var alerts: ArrayList<Alerts> = arrayListOf(),
|
||||||
|
@SerializedName("currentConditions") var currentConditions: CurrentConditions? = CurrentConditions()
|
||||||
|
): DataMapper<FullWeather> {
|
||||||
|
|
||||||
|
override fun mapData(): FullWeather {
|
||||||
|
val hours = mutableListOf(days[0].hours).apply { add(days[1].hours) }.flatten().subList(0,23).map { FullWeatherHour(it) }.toList()
|
||||||
|
val collectedDays = mutableListOf(days.subList(0,7)).flatten().map { DailyWeather(it) }.toList()
|
||||||
|
return FullWeather(
|
||||||
|
current = Current(currentConditions),
|
||||||
|
timezone = timezone,
|
||||||
|
timezoneOffset = tzoffset,
|
||||||
|
hourly = hours,
|
||||||
|
daily = collectedDays,
|
||||||
|
lat = latitude,
|
||||||
|
lon = longitude,
|
||||||
|
locationString = address,
|
||||||
|
temperatureUnit = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.repository
|
package com.appttude.h_mal.atlas_weather.data.repository
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
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.data.room.entity.EntityItem
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||||
|
|
||||||
interface Repository {
|
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 saveCurrentWeatherToRoom(entityItem: EntityItem)
|
||||||
suspend fun saveWeatherListToRoom(list: List<EntityItem>)
|
suspend fun saveWeatherListToRoom(list: List<EntityItem>)
|
||||||
fun loadRoomWeatherLiveData(): LiveData<List<EntityItem>>
|
fun loadRoomWeatherLiveData(): LiveData<List<EntityItem>>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.appttude.h_mal.atlas_weather.data.repository
|
|||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.ResponseUnwrap
|
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.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.LOCATION_CONST
|
||||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||||
@@ -20,8 +20,9 @@ class RepositoryImpl(
|
|||||||
override suspend fun getWeatherFromApi(
|
override suspend fun getWeatherFromApi(
|
||||||
lat: String,
|
lat: String,
|
||||||
long: String
|
long: String
|
||||||
): WeatherResponse {
|
): WeatherApiResponse {
|
||||||
return responseUnwrap { api.getFromApi(lat, long, units = prefs.getUnitsType().name.lowercase()) }
|
val unit = if (prefs.getUnitsType() == UnitType.METRIC) "metric" else "us"
|
||||||
|
return responseUnwrap { api.getFromApi(location = "$lat,$long", units = unit) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem) {
|
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.repository.SettingsRepository
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
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.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.InnerWidgetCellData
|
||||||
import com.appttude.h_mal.atlas_weather.model.widget.InnerWidgetData
|
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.WidgetData
|
||||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetError
|
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.WidgetState
|
||||||
import com.appttude.h_mal.atlas_weather.model.widget.WidgetWeatherCollection
|
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.appttude.h_mal.atlas_weather.utils.toSmallDayName
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import com.squareup.picasso.Target
|
import com.squareup.picasso.Target
|
||||||
@@ -45,9 +44,11 @@ class ServicesHelper(
|
|||||||
// Get weather from api
|
// Get weather from api
|
||||||
val weather = repository
|
val weather = repository
|
||||||
.getWeatherFromApi(latLong.first.toString(), latLong.second.toString())
|
.getWeatherFromApi(latLong.first.toString(), latLong.second.toString())
|
||||||
|
val lat = weather.latitude ?: latLong.first
|
||||||
|
val long = weather.longitude ?: latLong.second
|
||||||
val currentLocation =
|
val currentLocation =
|
||||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
locationProvider.getLocationNameFromLatLong(lat, long)
|
||||||
val fullWeather = FullWeather(weather).apply {
|
val fullWeather = weather.mapData().apply {
|
||||||
temperatureUnit = "°C"
|
temperatureUnit = "°C"
|
||||||
locationString = currentLocation
|
locationString = currentLocation
|
||||||
}
|
}
|
||||||
@@ -105,8 +106,11 @@ class ServicesHelper(
|
|||||||
return WidgetState.HasError(error)
|
return WidgetState.HasError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val lat = weather.latitude ?: latLong.first
|
||||||
|
val long = weather.longitude ?: latLong.second
|
||||||
|
|
||||||
val currentLocation = try {
|
val currentLocation = try {
|
||||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
locationProvider.getLocationNameFromLatLong(lat, long)
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
val data = getWidgetWeatherCollection()
|
val data = getWidgetWeatherCollection()
|
||||||
data?.let {
|
data?.let {
|
||||||
@@ -120,8 +124,8 @@ class ServicesHelper(
|
|||||||
return WidgetState.HasError(error)
|
return WidgetState.HasError(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
val fullWeather = FullWeather(weather).apply {
|
val fullWeather = weather.mapData().apply {
|
||||||
temperatureUnit = if (repository.getUnitType() == UnitType.METRIC) "°C" else "°F"
|
temperatureUnit = repository.getUnitType().getSymbol()
|
||||||
locationString = currentLocation
|
locationString = currentLocation
|
||||||
}
|
}
|
||||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||||
@@ -140,7 +144,7 @@ class ServicesHelper(
|
|||||||
|
|
||||||
result.weather.let {
|
result.weather.let {
|
||||||
val bitmap = it.current?.icon
|
val bitmap = it.current?.icon
|
||||||
val location = locationProvider.getLocationNameFromLatLong(it.lat, it.lon)
|
val location = locationProvider.getLocationNameFromLatLong(it.lat!!, it.lon!!)
|
||||||
val temp = it.current?.temp?.toInt().toString()
|
val temp = it.current?.temp?.toInt().toString()
|
||||||
|
|
||||||
WidgetData(location, bitmap, temp, epoc)
|
WidgetData(location, bitmap, temp, epoc)
|
||||||
@@ -177,7 +181,7 @@ class ServicesHelper(
|
|||||||
|
|
||||||
val widgetData = result.weather.let {
|
val widgetData = result.weather.let {
|
||||||
val bitmap = it.current?.icon
|
val bitmap = it.current?.icon
|
||||||
val location = locationProvider.getLocationNameFromLatLong(it.lat, it.lon)
|
val location = locationProvider.getLocationNameFromLatLong(it.lat!!, it.lon!!)
|
||||||
val temp = it.current?.temp?.toInt().toString()
|
val temp = it.current?.temp?.toInt().toString()
|
||||||
val epoc = System.currentTimeMillis()
|
val epoc = System.currentTimeMillis()
|
||||||
|
|
||||||
@@ -186,7 +190,7 @@ class ServicesHelper(
|
|||||||
|
|
||||||
val list = mutableListOf<InnerWidgetCellData>()
|
val list = mutableListOf<InnerWidgetCellData>()
|
||||||
|
|
||||||
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather ->
|
result.weather.daily?.drop(1)?.dropLast(1)?.forEach { dailyWeather ->
|
||||||
val day = dailyWeather.dt?.toSmallDayName()
|
val day = dailyWeather.dt?.toSmallDayName()
|
||||||
val icon = dailyWeather.icon
|
val icon = dailyWeather.icon
|
||||||
val temp = dailyWeather.max?.toInt().toString()
|
val temp = dailyWeather.max?.toInt().toString()
|
||||||
@@ -216,7 +220,7 @@ class ServicesHelper(
|
|||||||
|
|
||||||
val list = mutableListOf<InnerWidgetCellData>()
|
val list = mutableListOf<InnerWidgetCellData>()
|
||||||
|
|
||||||
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather ->
|
result.weather.daily?.drop(1)?.dropLast(1)?.forEach { dailyWeather ->
|
||||||
val day = dailyWeather.dt?.toSmallDayName()
|
val day = dailyWeather.dt?.toSmallDayName()
|
||||||
val icon = dailyWeather.icon
|
val icon = dailyWeather.icon
|
||||||
val temp = dailyWeather.max?.toInt().toString()
|
val temp = dailyWeather.max?.toInt().toString()
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,8 +40,7 @@ data class Forecast(
|
|||||||
parcel.readString(),
|
parcel.readString(),
|
||||||
parcel.readString(),
|
parcel.readString(),
|
||||||
parcel.readString()
|
parcel.readString()
|
||||||
) {
|
)
|
||||||
}
|
|
||||||
|
|
||||||
constructor(dailyWeather: DailyWeather) : this(
|
constructor(dailyWeather: DailyWeather) : this(
|
||||||
dailyWeather.dt?.toDayString(),
|
dailyWeather.dt?.toDayString(),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ data class WeatherDisplay(
|
|||||||
val averageTemp: Double?,
|
val averageTemp: Double?,
|
||||||
var unit: String?,
|
var unit: String?,
|
||||||
var location: String?,
|
var location: String?,
|
||||||
val iconURL: String?,
|
var iconURL: String?,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
val hourly: List<Hour>?,
|
val hourly: List<Hour>?,
|
||||||
val forecast: List<Forecast>?,
|
val forecast: List<Forecast>?,
|
||||||
@@ -40,8 +40,7 @@ data class WeatherDisplay(
|
|||||||
parcel.readDouble(),
|
parcel.readDouble(),
|
||||||
parcel.readDouble(),
|
parcel.readDouble(),
|
||||||
parcel.readString()
|
parcel.readString()
|
||||||
) {
|
)
|
||||||
}
|
|
||||||
|
|
||||||
constructor(entity: EntityItem) : this(
|
constructor(entity: EntityItem) : this(
|
||||||
entity.weather.current?.temp,
|
entity.weather.current?.temp,
|
||||||
@@ -56,8 +55,8 @@ data class WeatherDisplay(
|
|||||||
entity.weather.daily?.get(0)?.pop?.times(100)?.toInt()?.toString(),
|
entity.weather.daily?.get(0)?.pop?.times(100)?.toInt()?.toString(),
|
||||||
entity.weather.current?.humidity?.toString(),
|
entity.weather.current?.humidity?.toString(),
|
||||||
entity.weather.current?.clouds?.toString(),
|
entity.weather.current?.clouds?.toString(),
|
||||||
entity.weather.lat,
|
entity.weather.lat!!,
|
||||||
entity.weather.lon,
|
entity.weather.lon!!,
|
||||||
entity.weather.locationString
|
entity.weather.locationString
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -8,11 +8,15 @@ enum class UnitType {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getByName(name: String?): UnitType? {
|
fun getByName(name: String?): UnitType? {
|
||||||
return values().firstOrNull {
|
return entries.firstOrNull {
|
||||||
it.name.lowercase(Locale.ROOT) == name?.lowercase(
|
it.name.lowercase(Locale.ROOT) == name?.lowercase(
|
||||||
Locale.ROOT
|
Locale.ROOT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun UnitType.getLabel() = name.lowercase().replaceFirstChar {
|
||||||
|
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.model.weather
|
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
|
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
||||||
|
|
||||||
data class Current(
|
data class Current(
|
||||||
@@ -23,23 +24,23 @@ data class Current(
|
|||||||
val windSpeed: Double? = null
|
val windSpeed: Double? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(dailyItem: Current) : this(
|
constructor(currentConditions: CurrentConditions?) : this(
|
||||||
dailyItem.dt,
|
dt = currentConditions?.datetimeEpoch,
|
||||||
dailyItem.sunrise,
|
sunrise = currentConditions?.sunriseEpoch,
|
||||||
dailyItem.sunset,
|
sunset = currentConditions?.sunsetEpoch,
|
||||||
dailyItem.temp,
|
temp = currentConditions?.temp,
|
||||||
dailyItem.visibility,
|
visibility = currentConditions?.visibility?.toInt(),
|
||||||
dailyItem.uvi,
|
uvi = currentConditions?.uvindex?.toDouble(),
|
||||||
dailyItem.pressure,
|
pressure = currentConditions?.pressure?.toInt(),
|
||||||
dailyItem.clouds,
|
clouds = currentConditions?.cloudcover?.toInt(),
|
||||||
dailyItem.feelsLike,
|
feelsLike = currentConditions?.feelslike,
|
||||||
dailyItem.windDeg,
|
windDeg = currentConditions?.winddir?.toInt(),
|
||||||
dailyItem.dewPoint,
|
dewPoint = currentConditions?.dew,
|
||||||
generateIconUrlString(dailyItem.weather?.getOrNull(0)?.icon),
|
icon = generateIconUrlString(IconMapper.findIconCode(currentConditions?.icon)),
|
||||||
dailyItem.weather?.get(0)?.description,
|
description = currentConditions?.conditions,
|
||||||
dailyItem.weather?.get(0)?.main,
|
main = currentConditions?.conditions,
|
||||||
dailyItem.weather?.get(0)?.id,
|
id = currentConditions?.datetimeEpoch,
|
||||||
dailyItem.humidity,
|
humidity = currentConditions?.humidity?.toInt(),
|
||||||
dailyItem.windSpeed
|
windSpeed = currentConditions?.windspeed
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.model.weather
|
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
|
import com.appttude.h_mal.atlas_weather.utils.generateIconUrlString
|
||||||
|
|
||||||
|
|
||||||
@@ -27,28 +28,29 @@ data class DailyWeather(
|
|||||||
val rain: Double?
|
val rain: Double?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(dailyItem: DailyItem) : this(
|
constructor(days: Days) : this(
|
||||||
dailyItem.dt,
|
days.datetimeEpoch,
|
||||||
dailyItem.sunrise,
|
days.sunriseEpoch,
|
||||||
dailyItem.sunset,
|
days.sunsetEpoch,
|
||||||
dailyItem.temp?.min,
|
days.tempmin,
|
||||||
dailyItem.temp?.max,
|
days.tempmax,
|
||||||
dailyItem.temp?.day,
|
days.temp,
|
||||||
dailyItem.feelsLike?.day,
|
days.feelslike,
|
||||||
dailyItem.pressure,
|
days.pressure?.toInt(),
|
||||||
dailyItem.humidity,
|
days.humidity?.toInt(),
|
||||||
dailyItem.dewPoint,
|
days.dew,
|
||||||
dailyItem.windSpeed,
|
days.windspeed,
|
||||||
dailyItem.windDeg,
|
days.winddir?.toInt(),
|
||||||
generateIconUrlString(dailyItem.weather?.getOrNull(0)?.icon),
|
generateIconUrlString(
|
||||||
dailyItem.weather?.get(0)?.description,
|
IconMapper.findIconCode(days.icon)
|
||||||
dailyItem.weather?.get(0)?.main,
|
),
|
||||||
dailyItem.weather?.get(0)?.id,
|
days.description,
|
||||||
dailyItem.clouds,
|
days.conditions,
|
||||||
dailyItem.pop,
|
days.datetimeEpoch,
|
||||||
dailyItem.uvi,
|
days.cloudcover?.toInt(),
|
||||||
dailyItem.rain
|
days.precipprob,
|
||||||
|
days.uvindex?.toDouble(),
|
||||||
|
days.precip?.toDouble()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,15 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.model.weather
|
package com.appttude.h_mal.atlas_weather.model.weather
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
|
||||||
|
|
||||||
data class FullWeather(
|
data class FullWeather(
|
||||||
val current: Current? = null,
|
val current: Current? = null,
|
||||||
val timezone: String? = null,
|
val timezone: String? = null,
|
||||||
val timezoneOffset: Int? = null,
|
val timezoneOffset: Int? = null,
|
||||||
val hourly: List<Hour>? = null,
|
val hourly: List<Hour>? = null,
|
||||||
val daily: List<DailyWeather>? = null,
|
val daily: List<DailyWeather>? = null,
|
||||||
val lon: Double = 0.00,
|
val lon: Double? = null,
|
||||||
val lat: Double = 0.00,
|
val lat: Double? = null,
|
||||||
var locationString: String? = null,
|
var locationString: String? = null,
|
||||||
var temperatureUnit: String? = null
|
var temperatureUnit: String? = null
|
||||||
) {
|
)
|
||||||
|
|
||||||
constructor(weatherResponse: WeatherResponse) : this(
|
|
||||||
weatherResponse.current?.let { Current(it) },
|
|
||||||
weatherResponse.timezone,
|
|
||||||
weatherResponse.timezoneOffset,
|
|
||||||
weatherResponse.hourly?.subList(0, 23)?.map { Hour(it) },
|
|
||||||
weatherResponse.daily?.map { DailyWeather(it) },
|
|
||||||
weatherResponse.lon,
|
|
||||||
weatherResponse.lat
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ package com.appttude.h_mal.atlas_weather.model.weather
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
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.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(
|
data class Hour(
|
||||||
@@ -16,13 +17,13 @@ data class Hour(
|
|||||||
parcel.readValue(Int::class.java.classLoader) as? Int,
|
parcel.readValue(Int::class.java.classLoader) as? Int,
|
||||||
parcel.readValue(Double::class.java.classLoader) as? Double,
|
parcel.readValue(Double::class.java.classLoader) as? Double,
|
||||||
parcel.readString()
|
parcel.readString()
|
||||||
) {
|
)
|
||||||
}
|
|
||||||
|
|
||||||
constructor(hour: ForecastHour) : this(
|
|
||||||
hour.dt,
|
constructor(weatherHour: WeatherHour) : this(
|
||||||
hour.temp,
|
weatherHour.datetimeEpoch,
|
||||||
generateIconUrlString(hour.weather?.getOrNull(0)?.icon)
|
weatherHour.temp,
|
||||||
|
generateIconUrlString(IconMapper.findIconCode(weatherHour.icon))
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.utils
|
package com.appttude.h_mal.atlas_weather.utils
|
||||||
|
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.types.UnitType
|
||||||
|
|
||||||
|
|
||||||
fun generateIconUrlString(icon: String?): String? {
|
fun generateIconUrlString(icon: String?): String? {
|
||||||
return icon?.let {
|
return icon?.let {
|
||||||
@@ -9,4 +11,6 @@ fun generateIconUrlString(icon: String?): String? {
|
|||||||
.append("@2x.png")
|
.append("@2x.png")
|
||||||
.toString()
|
.toString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun UnitType.getSymbol(): String = if (this == UnitType.METRIC) "°C" else "°F"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data
|
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.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.repository.RepositoryImpl
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
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.data.room.entity.EntityItem
|
||||||
@@ -18,6 +18,7 @@ import kotlinx.coroutines.runBlocking
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import kotlin.properties.Delegates
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class WeatherSourceTest : BaseTest() {
|
class WeatherSourceTest : BaseTest() {
|
||||||
@@ -31,41 +32,43 @@ class WeatherSourceTest : BaseTest() {
|
|||||||
@MockK
|
@MockK
|
||||||
lateinit var locationProvider: LocationProviderImpl
|
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
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
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
|
@Test
|
||||||
fun fetchDataForSingleLocation_validLocation_validReturn() {
|
fun fetchDataForSingleLocation_validLocation_validReturn() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val latlon = Pair(
|
|
||||||
weatherResponse.lat,
|
|
||||||
weatherResponse.lon
|
|
||||||
)
|
|
||||||
val fullWeather = FullWeather(weatherResponse).apply {
|
|
||||||
temperatureUnit = "°C"
|
|
||||||
locationString = CURRENT_LOCATION
|
|
||||||
}
|
|
||||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||||
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
||||||
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
|
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
|
||||||
coEvery {
|
coEvery {
|
||||||
repository.getWeatherFromApi(
|
repository.getWeatherFromApi(
|
||||||
weatherResponse.lat.toString(),
|
lat.toString(),
|
||||||
weatherResponse.lon.toString()
|
long.toString()
|
||||||
)
|
)
|
||||||
}.returns(weatherResponse)
|
}.returns(weatherResponse)
|
||||||
coEvery {
|
coEvery {
|
||||||
locationProvider.getLocationNameFromLatLong(
|
locationProvider.getLocationNameFromLatLong(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon,
|
long,
|
||||||
LocationType.City
|
LocationType.City
|
||||||
)
|
)
|
||||||
}.returns(CURRENT_LOCATION)
|
}.returns(CURRENT_LOCATION)
|
||||||
@@ -82,17 +85,13 @@ class WeatherSourceTest : BaseTest() {
|
|||||||
@Test(expected = IOException::class)
|
@Test(expected = IOException::class)
|
||||||
fun fetchDataForSingleLocation_failedWeatherApi_invalidReturn() {
|
fun fetchDataForSingleLocation_failedWeatherApi_invalidReturn() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val latlon = Pair(
|
|
||||||
weatherResponse.lat,
|
|
||||||
weatherResponse.lon
|
|
||||||
)
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
||||||
coEvery {
|
coEvery {
|
||||||
repository.getWeatherFromApi(
|
repository.getWeatherFromApi(
|
||||||
weatherResponse.lat.toString(),
|
lat.toString(),
|
||||||
weatherResponse.lon.toString()
|
long.toString()
|
||||||
)
|
)
|
||||||
} throws IOException("Unable fetch data")
|
} throws IOException("Unable fetch data")
|
||||||
|
|
||||||
@@ -103,23 +102,19 @@ class WeatherSourceTest : BaseTest() {
|
|||||||
@Test(expected = IOException::class)
|
@Test(expected = IOException::class)
|
||||||
fun fetchDataForSingleLocation_failedLocation_invalidReturn() {
|
fun fetchDataForSingleLocation_failedLocation_invalidReturn() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val latlon = Pair(
|
|
||||||
weatherResponse.lat,
|
|
||||||
weatherResponse.lon
|
|
||||||
)
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
||||||
coEvery {
|
coEvery {
|
||||||
repository.getWeatherFromApi(
|
repository.getWeatherFromApi(
|
||||||
weatherResponse.lat.toString(),
|
lat.toString(),
|
||||||
weatherResponse.lon.toString()
|
long.toString()
|
||||||
)
|
)
|
||||||
} returns weatherResponse
|
} returns weatherResponse
|
||||||
coEvery {
|
coEvery {
|
||||||
locationProvider.getLocationNameFromLatLong(
|
locationProvider.getLocationNameFromLatLong(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon
|
long
|
||||||
)
|
)
|
||||||
}.throws(IOException())
|
}.throws(IOException())
|
||||||
|
|
||||||
@@ -130,14 +125,6 @@ class WeatherSourceTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun searchAboveFallbackTime_validLocation_validReturn() {
|
fun searchAboveFallbackTime_validLocation_validReturn() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val latlon = Pair(
|
|
||||||
weatherResponse.lat,
|
|
||||||
weatherResponse.lon
|
|
||||||
)
|
|
||||||
val fullWeather = FullWeather(weatherResponse).apply {
|
|
||||||
temperatureUnit = "°C"
|
|
||||||
locationString = CURRENT_LOCATION
|
|
||||||
}
|
|
||||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -153,14 +140,6 @@ class WeatherSourceTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun forceFetchDataForSingleLocation_validLocation_validReturn() {
|
fun forceFetchDataForSingleLocation_validLocation_validReturn() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val latlon = Pair(
|
|
||||||
weatherResponse.lat,
|
|
||||||
weatherResponse.lon
|
|
||||||
)
|
|
||||||
val fullWeather = FullWeather(weatherResponse).apply {
|
|
||||||
temperatureUnit = "°C"
|
|
||||||
locationString = CURRENT_LOCATION
|
|
||||||
}
|
|
||||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -169,14 +148,14 @@ class WeatherSourceTest : BaseTest() {
|
|||||||
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
|
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
|
||||||
coEvery {
|
coEvery {
|
||||||
repository.getWeatherFromApi(
|
repository.getWeatherFromApi(
|
||||||
weatherResponse.lat.toString(),
|
weatherResponse.latitude.toString(),
|
||||||
weatherResponse.lon.toString()
|
weatherResponse.longitude.toString()
|
||||||
)
|
)
|
||||||
}.returns(weatherResponse)
|
}.returns(weatherResponse)
|
||||||
coEvery {
|
coEvery {
|
||||||
locationProvider.getLocationNameFromLatLong(
|
locationProvider.getLocationNameFromLatLong(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon,
|
long,
|
||||||
LocationType.City
|
LocationType.City
|
||||||
)
|
)
|
||||||
}.returns(CURRENT_LOCATION)
|
}.returns(CURRENT_LOCATION)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.data.repository
|
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.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.LOCATION_CONST
|
||||||
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
import com.appttude.h_mal.atlas_weather.data.prefs.PreferenceProvider
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||||
@@ -17,7 +17,9 @@ import io.mockk.mockk
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.IOException
|
import org.mockito.ArgumentMatchers.anyDouble
|
||||||
|
import org.mockito.ArgumentMatchers.anyString
|
||||||
|
import retrofit2.HttpException
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
import kotlin.test.assertIs
|
import kotlin.test.assertIs
|
||||||
@@ -29,8 +31,7 @@ class RepositoryImplTest : BaseTest() {
|
|||||||
|
|
||||||
lateinit var repository: RepositoryImpl
|
lateinit var repository: RepositoryImpl
|
||||||
|
|
||||||
@MockK
|
@MockK lateinit var api: WeatherApi
|
||||||
lateinit var api: WeatherApi
|
|
||||||
|
|
||||||
@MockK
|
@MockK
|
||||||
lateinit var db: AppDatabase
|
lateinit var db: AppDatabase
|
||||||
@@ -86,37 +87,44 @@ class RepositoryImplTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun getWeatherFromApi_validLatLong_validSearch() {
|
fun getWeatherFromApi_validLatLong_validSearch() {
|
||||||
//Arrange
|
//Arrange
|
||||||
val mockResponse = createSuccessfulRetrofitMock<WeatherResponse>()
|
val lat = anyDouble().toString()
|
||||||
|
val long = anyDouble().toString()
|
||||||
|
val mockResponse = createSuccessfulRetrofitMock<WeatherApiResponse>()
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
//create a successful retrofit response
|
//create a successful retrofit response
|
||||||
every { prefs.getUnitsType() } returns (UnitType.METRIC)
|
every { prefs.getUnitsType() } returns (UnitType.METRIC)
|
||||||
coEvery { api.getFromApi("", "") }.returns(mockResponse)
|
coEvery { api.getFromApi(location = "$lat,$long") }.returns(mockResponse)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val result = repository.getWeatherFromApi("", "")
|
val result = repository.getWeatherFromApi(lat, long)
|
||||||
assertIs<WeatherResponse>(result)
|
assertIs<WeatherApiResponse>(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun getWeatherFromApi_validLatLong_invalidResponse() {
|
fun getWeatherFromApi_validLatLong_invalidResponse() {
|
||||||
//Arrange
|
//Arrange
|
||||||
val mockResponse = createErrorRetrofitMock<WeatherResponse>()
|
val errorMessage = "Why dont you have a valid api key?"
|
||||||
|
val mockResponse = createErrorRetrofitMock<WeatherApiResponse>(errorMessage)
|
||||||
|
val lat = anyString()
|
||||||
|
val long = anyString()
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
//create a successful retrofit response
|
//create a successful retrofit response
|
||||||
every { prefs.getUnitsType() } returns (UnitType.METRIC)
|
every { prefs.getUnitsType() } returns (UnitType.METRIC)
|
||||||
coEvery { api.getFromApi(any(), any()) } returns (mockResponse)
|
coEvery { api.getFromApi(location = "$lat,$long") } returns (mockResponse)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
val ioExceptionReturned = assertFailsWith<IOException> {
|
val ioExceptionReturned = assertFailsWith<HttpException> {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
repository.getWeatherFromApi("", "")
|
repository.getWeatherFromApi(lat, long)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assertEquals(ioExceptionReturned.message, "Error Code: 400")
|
|
||||||
|
assertEquals(ioExceptionReturned.code(), 400)
|
||||||
|
assertEquals(ioExceptionReturned.message(), "Why dont you have a valid api key?")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.helper
|
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.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.Repository
|
||||||
import com.appttude.h_mal.atlas_weather.data.repository.SettingsRepository
|
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.CURRENT_LOCATION
|
||||||
@@ -17,6 +17,7 @@ import org.junit.Assert.assertTrue
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import kotlin.properties.Delegates
|
||||||
|
|
||||||
class ServicesHelperTest : BaseTest() {
|
class ServicesHelperTest : BaseTest() {
|
||||||
|
|
||||||
@@ -31,40 +32,45 @@ class ServicesHelperTest : BaseTest() {
|
|||||||
@MockK
|
@MockK
|
||||||
lateinit var locationProvider: LocationProviderImpl
|
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
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
MockKAnnotations.init(this)
|
||||||
helper = ServicesHelper(repository, settingsRepository, locationProvider)
|
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
|
@Test
|
||||||
fun testWidgetDataAsync_successfulResponse() = runBlocking {
|
fun testWidgetDataAsync_successfulResponse() = runBlocking {
|
||||||
// Arrange
|
// Arrange
|
||||||
val entityItem = EntityItem(CURRENT_LOCATION, FullWeather(weatherResponse).apply {
|
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
||||||
temperatureUnit = "°C"
|
|
||||||
locationString = CURRENT_LOCATION
|
|
||||||
})
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
coEvery { locationProvider.getCurrentLatLong() } returns Pair(
|
coEvery { locationProvider.getCurrentLatLong() } returns Pair(lat, long)
|
||||||
weatherResponse.lat,
|
|
||||||
weatherResponse.lon
|
|
||||||
)
|
|
||||||
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(true)
|
||||||
coEvery {
|
coEvery {
|
||||||
repository.getWeatherFromApi(
|
repository.getWeatherFromApi(
|
||||||
weatherResponse.lat.toString(),
|
lat.toString(),
|
||||||
weatherResponse.lon.toString()
|
long.toString()
|
||||||
)
|
)
|
||||||
}.returns(weatherResponse)
|
}.returns(weatherResponse)
|
||||||
coEvery {
|
coEvery {
|
||||||
locationProvider.getLocationNameFromLatLong(
|
locationProvider.getLocationNameFromLatLong(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon
|
long
|
||||||
)
|
)
|
||||||
}.returns(CURRENT_LOCATION)
|
}.returns(CURRENT_LOCATION)
|
||||||
every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit
|
every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import com.google.gson.Gson
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
|
import okhttp3.MediaType.Companion.toMediaType
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
|
import okhttp3.ResponseBody.Companion.toResponseBody
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
||||||
|
|
||||||
@@ -29,9 +31,17 @@ open class BaseTest {
|
|||||||
|
|
||||||
fun <T: Any> createErrorRetrofitMock(code: Int = 400): Response<T> {
|
fun <T: Any> createErrorRetrofitMock(code: Int = 400): Response<T> {
|
||||||
val responseBody = mockk<ResponseBody>(relaxed = true)
|
val responseBody = mockk<ResponseBody>(relaxed = true)
|
||||||
val rawResponse = mockk<okhttp3.Response>().also {
|
|
||||||
every { it.code } returns code
|
|
||||||
}
|
|
||||||
return Response.error<T>(code, responseBody)
|
return Response.error<T>(code, responseBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun <T: Any> createErrorRetrofitMock(errorMessage: String, code: Int = 400): Response<T> {
|
||||||
|
val responseBody = errorMessage.toResponseBody("application/json".toMediaType())
|
||||||
|
val rawResponse = mockk<okhttp3.Response>(relaxed = true).also {
|
||||||
|
every { it.code } returns code
|
||||||
|
every { it.isSuccessful } returns false
|
||||||
|
every { it.body } returns responseBody
|
||||||
|
every { it.message } returns errorMessage
|
||||||
|
}
|
||||||
|
return Response.error<T>(responseBody, rawResponse)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,7 +3,7 @@ package com.appttude.h_mal.atlas_weather.viewmodel
|
|||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
import com.appttude.h_mal.atlas_weather.data.WeatherSource
|
||||||
import com.appttude.h_mal.atlas_weather.data.location.LocationProviderImpl
|
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.room.entity.CURRENT_LOCATION
|
import com.appttude.h_mal.atlas_weather.data.room.entity.CURRENT_LOCATION
|
||||||
import com.appttude.h_mal.atlas_weather.model.ViewState
|
import com.appttude.h_mal.atlas_weather.model.ViewState
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
||||||
@@ -21,10 +21,10 @@ import io.mockk.impl.annotations.RelaxedMockK
|
|||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.mockito.ArgumentMatchers
|
|
||||||
import org.mockito.ArgumentMatchers.anyList
|
import org.mockito.ArgumentMatchers.anyList
|
||||||
import org.mockito.ArgumentMatchers.anyString
|
import org.mockito.ArgumentMatchers.anyString
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import kotlin.properties.Delegates
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertIs
|
import kotlin.test.assertIs
|
||||||
|
|
||||||
@@ -43,12 +43,23 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
@MockK
|
@MockK
|
||||||
lateinit var locationProvider: LocationProviderImpl
|
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
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
MockKAnnotations.init(this)
|
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
|
@Test
|
||||||
@@ -58,13 +69,13 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
|
|
||||||
// Act
|
// Act
|
||||||
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair(
|
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon
|
long
|
||||||
)
|
)
|
||||||
coEvery {
|
coEvery {
|
||||||
locationProvider.getLocationNameFromLatLong(
|
locationProvider.getLocationNameFromLatLong(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon,
|
long,
|
||||||
LocationType.City
|
LocationType.City
|
||||||
)
|
)
|
||||||
}.returns(CURRENT_LOCATION)
|
}.returns(CURRENT_LOCATION)
|
||||||
@@ -74,7 +85,7 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
CURRENT_LOCATION,
|
CURRENT_LOCATION,
|
||||||
locationType = LocationType.City
|
locationType = LocationType.City
|
||||||
)
|
)
|
||||||
} returns FullWeather(weatherResponse)
|
} returns fullWeather
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
viewModel.fetchDataForSingleLocation(CURRENT_LOCATION)
|
viewModel.fetchDataForSingleLocation(CURRENT_LOCATION)
|
||||||
@@ -86,17 +97,17 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun fetchDataForSingleLocation_failedLocation_validReturn() {
|
fun fetchDataForSingleLocation_failedLocation_validReturn() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val errorMessage = ArgumentMatchers.anyString()
|
val errorMessage = anyString()
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair(
|
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon
|
long
|
||||||
)
|
)
|
||||||
coEvery {
|
coEvery {
|
||||||
locationProvider.getLocationNameFromLatLong(
|
locationProvider.getLocationNameFromLatLong(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon,
|
long,
|
||||||
LocationType.City
|
LocationType.City
|
||||||
)
|
)
|
||||||
} throws IOException(errorMessage)
|
} throws IOException(errorMessage)
|
||||||
@@ -112,18 +123,17 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun fetchDataForSingleLocation_failedApi_validReturn() {
|
fun fetchDataForSingleLocation_failedApi_validReturn() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val latlon = Pair(weatherResponse.lat, weatherResponse.lon)
|
val errorMessage = anyString()
|
||||||
val errorMessage = ArgumentMatchers.anyString()
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair(
|
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns Pair(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon
|
long
|
||||||
)
|
)
|
||||||
coEvery {
|
coEvery {
|
||||||
locationProvider.getLocationNameFromLatLong(
|
locationProvider.getLocationNameFromLatLong(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon,
|
long,
|
||||||
LocationType.City
|
LocationType.City
|
||||||
)
|
)
|
||||||
}.returns(CURRENT_LOCATION)
|
}.returns(CURRENT_LOCATION)
|
||||||
@@ -146,15 +156,14 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun fetchDataForSingleLocationSearch_validLocation_validReturn() {
|
fun fetchDataForSingleLocationSearch_validLocation_validReturn() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val latlon = Pair(weatherResponse.lat, weatherResponse.lon)
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
every { weatherSource.repository.getSavedLocations() } returns anyList()
|
every { weatherSource.repository.getSavedLocations() } returns anyList()
|
||||||
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
|
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
|
||||||
coEvery {
|
coEvery {
|
||||||
locationProvider.getLocationNameFromLatLong(
|
locationProvider.getLocationNameFromLatLong(
|
||||||
weatherResponse.lat,
|
lat,
|
||||||
weatherResponse.lon,
|
long,
|
||||||
LocationType.City
|
LocationType.City
|
||||||
)
|
)
|
||||||
}.returns(CURRENT_LOCATION)
|
}.returns(CURRENT_LOCATION)
|
||||||
@@ -164,7 +173,7 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
CURRENT_LOCATION,
|
CURRENT_LOCATION,
|
||||||
locationType = LocationType.City
|
locationType = LocationType.City
|
||||||
)
|
)
|
||||||
} returns FullWeather(weatherResponse).apply { locationString = CURRENT_LOCATION }
|
} returns fullWeather.apply { locationString = CURRENT_LOCATION }
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
viewModel.fetchDataForSingleLocationSearch(CURRENT_LOCATION)
|
viewModel.fetchDataForSingleLocationSearch(CURRENT_LOCATION)
|
||||||
@@ -192,7 +201,6 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun fetchDataForSingleLocationSearch_retrievedLocationExists_validError() {
|
fun fetchDataForSingleLocationSearch_retrievedLocationExists_validError() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val latlon = Pair(weatherResponse.lat, weatherResponse.lon)
|
|
||||||
val retrievedLocation = anyString()
|
val retrievedLocation = anyString()
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
@@ -200,8 +208,7 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
|
coEvery { locationProvider.getLatLongFromLocationName(CURRENT_LOCATION) } returns latlon
|
||||||
coEvery {
|
coEvery {
|
||||||
locationProvider.getLocationNameFromLatLong(
|
locationProvider.getLocationNameFromLatLong(
|
||||||
weatherResponse.lat,
|
lat, long,
|
||||||
weatherResponse.lon,
|
|
||||||
LocationType.City
|
LocationType.City
|
||||||
)
|
)
|
||||||
}.returns(CURRENT_LOCATION)
|
}.returns(CURRENT_LOCATION)
|
||||||
@@ -211,7 +218,7 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
CURRENT_LOCATION,
|
CURRENT_LOCATION,
|
||||||
locationType = LocationType.City
|
locationType = LocationType.City
|
||||||
)
|
)
|
||||||
} returns FullWeather(weatherResponse).apply { locationString = retrievedLocation }
|
} returns fullWeather.apply { locationString = retrievedLocation }
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
viewModel.fetchDataForSingleLocationSearch(CURRENT_LOCATION)
|
viewModel.fetchDataForSingleLocationSearch(CURRENT_LOCATION)
|
||||||
|
|||||||
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
@@ -1,317 +0,0 @@
|
|||||||
{
|
|
||||||
"lat": 51.5,
|
|
||||||
"lon": -0.12,
|
|
||||||
"timezone": "Europe/London",
|
|
||||||
"timezone_offset": 0,
|
|
||||||
"current": {
|
|
||||||
"dt": 1608394319,
|
|
||||||
"sunrise": 1608364972,
|
|
||||||
"sunset": 1608393158,
|
|
||||||
"temp": 9.42,
|
|
||||||
"feels_like": 4.9,
|
|
||||||
"pressure": 1007,
|
|
||||||
"humidity": 91,
|
|
||||||
"dew_point": 8.03,
|
|
||||||
"uvi": 0,
|
|
||||||
"clouds": 33,
|
|
||||||
"visibility": 10000,
|
|
||||||
"wind_speed": 5.8,
|
|
||||||
"wind_deg": 215,
|
|
||||||
"weather": [
|
|
||||||
{
|
|
||||||
"id": 802,
|
|
||||||
"main": "Clouds",
|
|
||||||
"description": "scattered clouds",
|
|
||||||
"icon": "03n"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"daily": [
|
|
||||||
{
|
|
||||||
"dt": 1608375600,
|
|
||||||
"sunrise": 1608364972,
|
|
||||||
"sunset": 1608393158,
|
|
||||||
"temp": {
|
|
||||||
"day": 11.78,
|
|
||||||
"min": 9.1,
|
|
||||||
"max": 12.31,
|
|
||||||
"night": 9.1,
|
|
||||||
"eve": 9.8,
|
|
||||||
"morn": 10.8
|
|
||||||
},
|
|
||||||
"feels_like": {
|
|
||||||
"day": 6.46,
|
|
||||||
"night": 5.08,
|
|
||||||
"eve": 5.47,
|
|
||||||
"morn": 4.63
|
|
||||||
},
|
|
||||||
"pressure": 1005,
|
|
||||||
"humidity": 69,
|
|
||||||
"dew_point": 6.42,
|
|
||||||
"wind_speed": 6.37,
|
|
||||||
"wind_deg": 217,
|
|
||||||
"weather": [
|
|
||||||
{
|
|
||||||
"id": 500,
|
|
||||||
"main": "Rain",
|
|
||||||
"description": "light rain",
|
|
||||||
"icon": "10d"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clouds": 97,
|
|
||||||
"pop": 0.98,
|
|
||||||
"rain": 1.55,
|
|
||||||
"uvi": 0.53
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dt": 1608462000,
|
|
||||||
"sunrise": 1608451406,
|
|
||||||
"sunset": 1608479581,
|
|
||||||
"temp": {
|
|
||||||
"day": 9.9,
|
|
||||||
"min": 7.41,
|
|
||||||
"max": 10.52,
|
|
||||||
"night": 7.41,
|
|
||||||
"eve": 8.83,
|
|
||||||
"morn": 8.59
|
|
||||||
},
|
|
||||||
"feels_like": {
|
|
||||||
"day": 4.19,
|
|
||||||
"night": 3.99,
|
|
||||||
"eve": 4.55,
|
|
||||||
"morn": 4.79
|
|
||||||
},
|
|
||||||
"pressure": 1013,
|
|
||||||
"humidity": 64,
|
|
||||||
"dew_point": 3.48,
|
|
||||||
"wind_speed": 6.12,
|
|
||||||
"wind_deg": 226,
|
|
||||||
"weather": [
|
|
||||||
{
|
|
||||||
"id": 500,
|
|
||||||
"main": "Rain",
|
|
||||||
"description": "light rain",
|
|
||||||
"icon": "10d"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clouds": 8,
|
|
||||||
"pop": 0.58,
|
|
||||||
"rain": 0.65,
|
|
||||||
"uvi": 0.45
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dt": 1608548400,
|
|
||||||
"sunrise": 1608537838,
|
|
||||||
"sunset": 1608566008,
|
|
||||||
"temp": {
|
|
||||||
"day": 11.06,
|
|
||||||
"min": 7.01,
|
|
||||||
"max": 13.57,
|
|
||||||
"night": 13.2,
|
|
||||||
"eve": 12.68,
|
|
||||||
"morn": 8.59
|
|
||||||
},
|
|
||||||
"feels_like": {
|
|
||||||
"day": 6.32,
|
|
||||||
"night": 9.99,
|
|
||||||
"eve": 9.75,
|
|
||||||
"morn": 4.64
|
|
||||||
},
|
|
||||||
"pressure": 1005,
|
|
||||||
"humidity": 91,
|
|
||||||
"dew_point": 9.69,
|
|
||||||
"wind_speed": 6.7,
|
|
||||||
"wind_deg": 185,
|
|
||||||
"weather": [
|
|
||||||
{
|
|
||||||
"id": 501,
|
|
||||||
"main": "Rain",
|
|
||||||
"description": "moderate rain",
|
|
||||||
"icon": "10d"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clouds": 100,
|
|
||||||
"pop": 1,
|
|
||||||
"rain": 7.85,
|
|
||||||
"uvi": 0.21
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dt": 1608634800,
|
|
||||||
"sunrise": 1608624266,
|
|
||||||
"sunset": 1608652438,
|
|
||||||
"temp": {
|
|
||||||
"day": 12.97,
|
|
||||||
"min": 11.7,
|
|
||||||
"max": 13.21,
|
|
||||||
"night": 11.7,
|
|
||||||
"eve": 12.37,
|
|
||||||
"morn": 12.93
|
|
||||||
},
|
|
||||||
"feels_like": {
|
|
||||||
"day": 11.39,
|
|
||||||
"night": 10.49,
|
|
||||||
"eve": 10.96,
|
|
||||||
"morn": 9.65
|
|
||||||
},
|
|
||||||
"pressure": 1012,
|
|
||||||
"humidity": 83,
|
|
||||||
"dew_point": 10.31,
|
|
||||||
"wind_speed": 2.38,
|
|
||||||
"wind_deg": 214,
|
|
||||||
"weather": [
|
|
||||||
{
|
|
||||||
"id": 500,
|
|
||||||
"main": "Rain",
|
|
||||||
"description": "light rain",
|
|
||||||
"icon": "10d"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clouds": 100,
|
|
||||||
"pop": 1,
|
|
||||||
"rain": 3.25,
|
|
||||||
"uvi": 0.34
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dt": 1608721200,
|
|
||||||
"sunrise": 1608710690,
|
|
||||||
"sunset": 1608738871,
|
|
||||||
"temp": {
|
|
||||||
"day": 12.28,
|
|
||||||
"min": 10.12,
|
|
||||||
"max": 12.62,
|
|
||||||
"night": 10.12,
|
|
||||||
"eve": 10.12,
|
|
||||||
"morn": 11.73
|
|
||||||
},
|
|
||||||
"feels_like": {
|
|
||||||
"day": 7.48,
|
|
||||||
"night": 6.73,
|
|
||||||
"eve": 6.6,
|
|
||||||
"morn": 8.15
|
|
||||||
},
|
|
||||||
"pressure": 1006,
|
|
||||||
"humidity": 64,
|
|
||||||
"dew_point": 5.76,
|
|
||||||
"wind_speed": 5.45,
|
|
||||||
"wind_deg": 224,
|
|
||||||
"weather": [
|
|
||||||
{
|
|
||||||
"id": 500,
|
|
||||||
"main": "Rain",
|
|
||||||
"description": "light rain",
|
|
||||||
"icon": "10d"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clouds": 97,
|
|
||||||
"pop": 0.94,
|
|
||||||
"rain": 2.52,
|
|
||||||
"uvi": 0.52
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dt": 1608811200,
|
|
||||||
"sunrise": 1608797112,
|
|
||||||
"sunset": 1608825307,
|
|
||||||
"temp": {
|
|
||||||
"day": 7.3,
|
|
||||||
"min": 4.66,
|
|
||||||
"max": 8.32,
|
|
||||||
"night": 4.66,
|
|
||||||
"eve": 5.76,
|
|
||||||
"morn": 5.85
|
|
||||||
},
|
|
||||||
"feels_like": {
|
|
||||||
"day": 0.36,
|
|
||||||
"night": -1.25,
|
|
||||||
"eve": -0.31,
|
|
||||||
"morn": -2.46
|
|
||||||
},
|
|
||||||
"pressure": 1020,
|
|
||||||
"humidity": 60,
|
|
||||||
"dew_point": 0.15,
|
|
||||||
"wind_speed": 7.09,
|
|
||||||
"wind_deg": 5,
|
|
||||||
"weather": [
|
|
||||||
{
|
|
||||||
"id": 500,
|
|
||||||
"main": "Rain",
|
|
||||||
"description": "light rain",
|
|
||||||
"icon": "10d"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clouds": 85,
|
|
||||||
"pop": 0.52,
|
|
||||||
"rain": 0.6,
|
|
||||||
"uvi": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dt": 1608897600,
|
|
||||||
"sunrise": 1608883530,
|
|
||||||
"sunset": 1608911747,
|
|
||||||
"temp": {
|
|
||||||
"day": 4.12,
|
|
||||||
"min": 2.2,
|
|
||||||
"max": 4.63,
|
|
||||||
"night": 2.97,
|
|
||||||
"eve": 3.44,
|
|
||||||
"morn": 2.28
|
|
||||||
},
|
|
||||||
"feels_like": {
|
|
||||||
"day": -0.33,
|
|
||||||
"night": -0.43,
|
|
||||||
"eve": -0.1,
|
|
||||||
"morn": -2.82
|
|
||||||
},
|
|
||||||
"pressure": 1033,
|
|
||||||
"humidity": 70,
|
|
||||||
"dew_point": -3.09,
|
|
||||||
"wind_speed": 3.35,
|
|
||||||
"wind_deg": 334,
|
|
||||||
"weather": [
|
|
||||||
{
|
|
||||||
"id": 800,
|
|
||||||
"main": "Clear",
|
|
||||||
"description": "clear sky",
|
|
||||||
"icon": "01d"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clouds": 0,
|
|
||||||
"pop": 0,
|
|
||||||
"uvi": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dt": 1608984000,
|
|
||||||
"sunrise": 1608969944,
|
|
||||||
"sunset": 1608998190,
|
|
||||||
"temp": {
|
|
||||||
"day": 6.03,
|
|
||||||
"min": 2.76,
|
|
||||||
"max": 6.92,
|
|
||||||
"night": 6.92,
|
|
||||||
"eve": 6.45,
|
|
||||||
"morn": 3.59
|
|
||||||
},
|
|
||||||
"feels_like": {
|
|
||||||
"day": 0.49,
|
|
||||||
"night": -0.96,
|
|
||||||
"eve": -0.28,
|
|
||||||
"morn": -0.44
|
|
||||||
},
|
|
||||||
"pressure": 1024,
|
|
||||||
"humidity": 69,
|
|
||||||
"dew_point": 0.84,
|
|
||||||
"wind_speed": 5.25,
|
|
||||||
"wind_deg": 251,
|
|
||||||
"weather": [
|
|
||||||
{
|
|
||||||
"id": 804,
|
|
||||||
"main": "Clouds",
|
|
||||||
"description": "overcast clouds",
|
|
||||||
"icon": "04d"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"clouds": 100,
|
|
||||||
"pop": 0,
|
|
||||||
"uvi": 1
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -40,7 +40,7 @@ RETROFIT_VERSION = 2.9.0
|
|||||||
OKHTTP_VERSION = 4.9.0
|
OKHTTP_VERSION = 4.9.0
|
||||||
MOKITO_INLINE_VERSION = 2.13.0
|
MOKITO_INLINE_VERSION = 2.13.0
|
||||||
CORE_TEST_VERSION = 2.2.0
|
CORE_TEST_VERSION = 2.2.0
|
||||||
MOCKK_VERSION = 1.10.5
|
MOCKK_VERSION = 1.13.12
|
||||||
TEST_JUNIT_VERSION = 1.2.0
|
TEST_JUNIT_VERSION = 1.2.0
|
||||||
TEST_RUNNER_VERSION = 1.5.2
|
TEST_RUNNER_VERSION = 1.5.2
|
||||||
ESPRESSO_VERSION = 3.6.0
|
ESPRESSO_VERSION = 3.6.0
|
||||||
|
|||||||
Reference in New Issue
Block a user