mirror of
https://github.com/hmalik144/Weather-apps.git
synced 2026-03-18 07:26:04 +00:00
- change location retrieval accuracy
- change location retrieval caching from location provider
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -89,6 +89,7 @@ gen-external-apklibs
|
|||||||
.idea/assetWizardSettings.xml
|
.idea/assetWizardSettings.xml
|
||||||
.idea/gradle.xml
|
.idea/gradle.xml
|
||||||
.idea/jarRepositorie
|
.idea/jarRepositorie
|
||||||
|
.idea/assetWizardSettings.xml
|
||||||
|
|
||||||
# Gem/fastlane
|
# Gem/fastlane
|
||||||
Gemfile.lock
|
Gemfile.lock
|
||||||
|
|||||||
@@ -3,26 +3,20 @@ package com.appttude.h_mal.atlas_weather.testSuite
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.room.util.UUIDUtil
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
import com.appttude.h_mal.atlas_weather.data.room.AppDatabase
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.Converter
|
import com.appttude.h_mal.atlas_weather.data.room.Converter
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.WeatherDao
|
import com.appttude.h_mal.atlas_weather.data.room.WeatherDao
|
||||||
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.WeatherEntity
|
||||||
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.test.BuildConfig
|
|
||||||
import com.appttude.h_mal.atlas_weather.utils.getOrAwaitValue
|
import com.appttude.h_mal.atlas_weather.utils.getOrAwaitValue
|
||||||
import io.mockk.every
|
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
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.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
import org.mockito.ArgumentMatchers.any
|
|
||||||
import org.mockito.ArgumentMatchers.anyString
|
|
||||||
import org.mockito.Mockito.mock
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
|
|
||||||
@@ -98,16 +92,16 @@ class RoomDatabaseTests {
|
|||||||
assertNull(dao.getCurrentFullWeatherSingle(id))
|
assertNull(dao.getCurrentFullWeatherSingle(id))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEntity(id: String = CURRENT_LOCATION): EntityItem {
|
private fun createEntity(id: String = CURRENT_LOCATION): WeatherEntity {
|
||||||
val weather = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
val weather = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
FullWeather()
|
FullWeather()
|
||||||
} else {
|
} else {
|
||||||
mockk<FullWeather>()
|
mockk<FullWeather>()
|
||||||
}
|
}
|
||||||
return EntityItem(id, weather)
|
return WeatherEntity(id, weather)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEntityList(size: Int = 4): List<EntityItem> {
|
private fun createEntityList(size: Int = 4): List<WeatherEntity> {
|
||||||
return (0.. size).map {
|
return (0.. size).map {
|
||||||
val id = UUID.randomUUID().toString()
|
val id = UUID.randomUUID().toString()
|
||||||
createEntity(id)
|
createEntity(id)
|
||||||
|
|||||||
@@ -4,8 +4,15 @@ import androidx.lifecycle.LiveData
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.appttude.h_mal.atlas_weather.model.ViewState
|
import com.appttude.h_mal.atlas_weather.model.ViewState
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import okhttp3.internal.wait
|
||||||
|
import java.util.concurrent.CancellationException
|
||||||
|
|
||||||
open class BaseViewModel: ViewModel() {
|
open class BaseViewModel : ViewModel() {
|
||||||
|
|
||||||
private val _uiState = MutableLiveData<ViewState>()
|
private val _uiState = MutableLiveData<ViewState>()
|
||||||
val uiState: LiveData<ViewState> = _uiState
|
val uiState: LiveData<ViewState> = _uiState
|
||||||
@@ -22,4 +29,15 @@ open class BaseViewModel: ViewModel() {
|
|||||||
protected fun <E : Any> onError(error: E) {
|
protected fun <E : Any> onError(error: E) {
|
||||||
_uiState.postValue(ViewState.HasError(error))
|
_uiState.postValue(ViewState.HasError(error))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected var job: Job? = null
|
||||||
|
|
||||||
|
fun cancelOperation() {
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
job?.run {
|
||||||
|
cancelAndJoin()
|
||||||
|
onSuccess(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,29 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.base.baseViewModels
|
package com.appttude.h_mal.atlas_weather.base.baseViewModels
|
||||||
|
|
||||||
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.WeatherEntity
|
||||||
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||||
|
|
||||||
abstract class WeatherViewModel : BaseViewModel() {
|
abstract class WeatherViewModel : BaseViewModel() {
|
||||||
|
|
||||||
fun createFullWeather(
|
fun createFullWeather(
|
||||||
weather: WeatherResponse,
|
weather: WeatherResponse,
|
||||||
location: String
|
locationName: String
|
||||||
): FullWeather {
|
): FullWeather {
|
||||||
return FullWeather(weather).apply {
|
return FullWeather(weather).apply {
|
||||||
temperatureUnit = "°C"
|
temperatureUnit = "°C"
|
||||||
locationString = location
|
locationString = locationName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createWeatherEntity(
|
fun createWeatherEntity(
|
||||||
locationId: String,
|
locationName: String,
|
||||||
weather: FullWeather
|
weather: FullWeather
|
||||||
): EntityItem {
|
): WeatherEntity {
|
||||||
weather.apply {
|
weather.apply {
|
||||||
locationString = locationId
|
locationString = locationName
|
||||||
}
|
}
|
||||||
|
|
||||||
return EntityItem(locationId, weather)
|
return WeatherEntity(locationName, weather)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,9 +3,11 @@ package com.appttude.h_mal.atlas_weather.data.location
|
|||||||
import android.Manifest.permission.ACCESS_COARSE_LOCATION
|
import android.Manifest.permission.ACCESS_COARSE_LOCATION
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.location.Address
|
||||||
import android.location.Geocoder
|
import android.location.Geocoder
|
||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
|
import android.os.Build
|
||||||
import androidx.annotation.RequiresPermission
|
import androidx.annotation.RequiresPermission
|
||||||
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
import com.appttude.h_mal.atlas_weather.model.types.LocationType
|
||||||
import com.google.android.gms.location.LocationServices
|
import com.google.android.gms.location.LocationServices
|
||||||
@@ -20,8 +22,6 @@ import java.util.*
|
|||||||
class LocationProviderImpl(
|
class LocationProviderImpl(
|
||||||
private val applicationContext: Context
|
private val applicationContext: Context
|
||||||
) : LocationProvider, LocationHelper(applicationContext) {
|
) : LocationProvider, LocationHelper(applicationContext) {
|
||||||
private var locationManager =
|
|
||||||
applicationContext.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
|
|
||||||
private val client = LocationServices.getFusedLocationProviderClient(applicationContext)
|
private val client = LocationServices.getFusedLocationProviderClient(applicationContext)
|
||||||
private val geoCoder: Geocoder by lazy { Geocoder(applicationContext, Locale.getDefault()) }
|
private val geoCoder: Geocoder by lazy { Geocoder(applicationContext, Locale.getDefault()) }
|
||||||
|
|
||||||
@@ -70,11 +70,14 @@ class LocationProviderImpl(
|
|||||||
private suspend fun getAFreshLocation(): Location? {
|
private suspend fun getAFreshLocation(): Location? {
|
||||||
return client.getCurrentLocation(
|
return client.getCurrentLocation(
|
||||||
Priority.PRIORITY_HIGH_ACCURACY,
|
Priority.PRIORITY_HIGH_ACCURACY,
|
||||||
object : CancellationToken() {
|
cancellationToken
|
||||||
override fun isCancellationRequested(): Boolean = false
|
).await()
|
||||||
override fun onCanceledRequested(p0: OnTokenCanceledListener): CancellationToken =
|
}
|
||||||
this
|
|
||||||
}).await()
|
private val cancellationToken = object : CancellationToken() {
|
||||||
|
override fun isCancellationRequested(): Boolean = false
|
||||||
|
override fun onCanceledRequested(p0: OnTokenCanceledListener): CancellationToken =
|
||||||
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,20 @@ 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.forecast.WeatherResponse
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.WeatherEntity
|
||||||
|
|
||||||
interface Repository {
|
interface Repository {
|
||||||
|
|
||||||
suspend fun getWeatherFromApi(lat: String, long: String): WeatherResponse
|
suspend fun getWeatherFromApi(lat: String, long: String): WeatherResponse
|
||||||
suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem)
|
suspend fun saveCurrentWeatherToRoom(weatherEntity: WeatherEntity)
|
||||||
suspend fun saveWeatherListToRoom(list: List<EntityItem>)
|
suspend fun saveWeatherListToRoom(list: List<WeatherEntity>)
|
||||||
fun loadRoomWeatherLiveData(): LiveData<List<EntityItem>>
|
fun loadRoomWeatherLiveData(): LiveData<List<WeatherEntity>>
|
||||||
suspend fun loadWeatherList(): List<String>
|
suspend fun loadWeatherList(): List<String>
|
||||||
fun loadCurrentWeatherFromRoom(id: String): LiveData<EntityItem>
|
fun loadCurrentWeatherFromRoom(id: String): LiveData<WeatherEntity>
|
||||||
suspend fun loadSingleCurrentWeatherFromRoom(id: String): EntityItem
|
suspend fun loadSingleCurrentWeatherFromRoom(id: String): WeatherEntity
|
||||||
fun isSearchValid(locationName: String): Boolean
|
fun isSearchValid(locationName: String): Boolean
|
||||||
fun saveLastSavedAt(locationName: String)
|
fun saveLastSavedAt(locationName: String)
|
||||||
suspend fun deleteSavedWeatherEntry(locationName: String): Boolean
|
suspend fun deleteSavedWeatherEntry(locationName: String): Boolean
|
||||||
fun getSavedLocations(): List<String>
|
fun getSavedLocations(): List<String>
|
||||||
suspend fun getSingleWeather(locationName: String): EntityItem
|
suspend fun getSingleWeather(locationName: String): WeatherEntity
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherRe
|
|||||||
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
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.WeatherEntity
|
||||||
import com.appttude.h_mal.atlas_weather.utils.FALLBACK_TIME
|
import com.appttude.h_mal.atlas_weather.utils.FALLBACK_TIME
|
||||||
|
|
||||||
|
|
||||||
@@ -23,12 +23,12 @@ class RepositoryImpl(
|
|||||||
return responseUnwrap { api.getFromApi(lat, long) }
|
return responseUnwrap { api.getFromApi(lat, long) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun saveCurrentWeatherToRoom(entityItem: EntityItem) {
|
override suspend fun saveCurrentWeatherToRoom(weatherEntity: WeatherEntity) {
|
||||||
db.getWeatherDao().upsertFullWeather(entityItem)
|
db.getWeatherDao().upsertFullWeather(weatherEntity)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun saveWeatherListToRoom(
|
override suspend fun saveWeatherListToRoom(
|
||||||
list: List<EntityItem>
|
list: List<WeatherEntity>
|
||||||
) {
|
) {
|
||||||
db.getWeatherDao().upsertListOfFullWeather(list)
|
db.getWeatherDao().upsertListOfFullWeather(list)
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ class RepositoryImpl(
|
|||||||
return prefs.getAllKeys().toList()
|
return prefs.getAllKeys().toList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getSingleWeather(locationName: String): EntityItem {
|
override suspend fun getSingleWeather(locationName: String): WeatherEntity {
|
||||||
return db.getWeatherDao().getCurrentFullWeatherSingle(locationName)
|
return db.getWeatherDao().getCurrentFullWeatherSingle(locationName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import androidx.room.Database
|
|||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
import androidx.room.RoomDatabase
|
import androidx.room.RoomDatabase
|
||||||
import androidx.room.TypeConverters
|
import androidx.room.TypeConverters
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.WeatherEntity
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = [EntityItem::class],
|
entities = [WeatherEntity::class],
|
||||||
version = 1,
|
version = 1,
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,30 +6,30 @@ import androidx.room.Insert
|
|||||||
import androidx.room.OnConflictStrategy
|
import androidx.room.OnConflictStrategy
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
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.WeatherEntity
|
||||||
|
|
||||||
@Dao
|
@Dao
|
||||||
interface WeatherDao {
|
interface WeatherDao {
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun upsertFullWeather(item: EntityItem)
|
fun upsertFullWeather(item: WeatherEntity)
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun upsertListOfFullWeather(items: List<EntityItem>)
|
fun upsertListOfFullWeather(items: List<WeatherEntity>)
|
||||||
|
|
||||||
@Query("SELECT * FROM EntityItem WHERE id = :userId LIMIT 1")
|
@Query("SELECT * FROM WeatherEntity WHERE id = :userId LIMIT 1")
|
||||||
fun getCurrentFullWeather(userId: String): LiveData<EntityItem>
|
fun getCurrentFullWeather(userId: String): LiveData<WeatherEntity>
|
||||||
|
|
||||||
@Query("SELECT * FROM EntityItem WHERE id = :userId LIMIT 1")
|
@Query("SELECT * FROM WeatherEntity WHERE id = :userId LIMIT 1")
|
||||||
fun getCurrentFullWeatherSingle(userId: String): EntityItem
|
fun getCurrentFullWeatherSingle(userId: String): WeatherEntity
|
||||||
|
|
||||||
@Query("SELECT * FROM EntityItem WHERE id != :id")
|
@Query("SELECT * FROM WeatherEntity WHERE id != :id")
|
||||||
fun getAllFullWeatherWithoutCurrent(id: String = CURRENT_LOCATION): LiveData<List<EntityItem>>
|
fun getAllFullWeatherWithoutCurrent(id: String = CURRENT_LOCATION): LiveData<List<WeatherEntity>>
|
||||||
|
|
||||||
@Query("SELECT * FROM EntityItem WHERE id != :id")
|
@Query("SELECT * FROM WeatherEntity WHERE id != :id")
|
||||||
fun getWeatherListWithoutCurrent(id: String = CURRENT_LOCATION): List<EntityItem>
|
fun getWeatherListWithoutCurrent(id: String = CURRENT_LOCATION): List<WeatherEntity>
|
||||||
|
|
||||||
@Query("DELETE FROM EntityItem WHERE id = :userId")
|
@Query("DELETE FROM WeatherEntity WHERE id = :userId")
|
||||||
fun deleteEntry(userId: String): Int
|
fun deleteEntry(userId: String): Int
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
|||||||
const val CURRENT_LOCATION = "CurrentLocation"
|
const val CURRENT_LOCATION = "CurrentLocation"
|
||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
data class EntityItem(
|
data class WeatherEntity(
|
||||||
@PrimaryKey(autoGenerate = false)
|
@PrimaryKey(autoGenerate = false)
|
||||||
val id: String,
|
val id: String,
|
||||||
val weather: FullWeather
|
val weather: FullWeather
|
||||||
@@ -5,15 +5,18 @@ import android.graphics.Bitmap
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import androidx.annotation.RequiresPermission
|
import androidx.annotation.RequiresPermission
|
||||||
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
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.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
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.WeatherEntity
|
||||||
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.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.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.toSmallDayName
|
import com.appttude.h_mal.atlas_weather.utils.toSmallDayName
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
@@ -47,10 +50,10 @@ class ServicesHelper(
|
|||||||
temperatureUnit = "°C"
|
temperatureUnit = "°C"
|
||||||
locationString = currentLocation
|
locationString = currentLocation
|
||||||
}
|
}
|
||||||
val entityItem = EntityItem(CURRENT_LOCATION, fullWeather)
|
val weatherEntity = WeatherEntity(CURRENT_LOCATION, fullWeather)
|
||||||
// Save data if not null
|
// Save data if not null
|
||||||
repository.saveLastSavedAt(CURRENT_LOCATION)
|
repository.saveLastSavedAt(CURRENT_LOCATION)
|
||||||
repository.saveCurrentWeatherToRoom(entityItem)
|
repository.saveCurrentWeatherToRoom(weatherEntity)
|
||||||
true
|
true
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -58,6 +61,77 @@ class ServicesHelper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
|
suspend fun fetchWidgetData(): WidgetState {
|
||||||
|
// Data was loaded within last 5 minutes - no need to retrieve again
|
||||||
|
if (!repository.isSearchValid(CURRENT_LOCATION)) {
|
||||||
|
val data = getWidgetWeatherCollection()
|
||||||
|
data?.let {
|
||||||
|
return WidgetState.HasData(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try and retrieve location
|
||||||
|
val latLong = try {
|
||||||
|
locationProvider.getCurrentLatLong()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
val data = getWidgetWeatherCollection()
|
||||||
|
data?.let {
|
||||||
|
return WidgetState.HasData(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val error = WidgetError(
|
||||||
|
icon = R.drawable.ic_baseline_cloud_off_24,
|
||||||
|
errorMessage = "Failed to retrieve location, check location"
|
||||||
|
)
|
||||||
|
return WidgetState.HasError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get weather from api
|
||||||
|
val weather = try {
|
||||||
|
repository
|
||||||
|
.getWeatherFromApi(latLong.first.toString(), latLong.second.toString())
|
||||||
|
} catch (e: IOException) {
|
||||||
|
val data = getWidgetWeatherCollection()
|
||||||
|
data?.let {
|
||||||
|
return WidgetState.HasData(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val error = WidgetError(
|
||||||
|
icon = R.drawable.ic_baseline_cloud_off_24,
|
||||||
|
errorMessage = "Failed to retrieve weather data, check connection"
|
||||||
|
)
|
||||||
|
return WidgetState.HasError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentLocation = try {
|
||||||
|
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
val data = getWidgetWeatherCollection()
|
||||||
|
data?.let {
|
||||||
|
return WidgetState.HasData(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val error = WidgetError(
|
||||||
|
icon = R.drawable.ic_baseline_cloud_off_24,
|
||||||
|
errorMessage = "Failed to retrieve location name"
|
||||||
|
)
|
||||||
|
return WidgetState.HasError(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
val fullWeather = FullWeather(weather).apply {
|
||||||
|
temperatureUnit = "°C"
|
||||||
|
locationString = currentLocation
|
||||||
|
}
|
||||||
|
val weatherEntity = WeatherEntity(CURRENT_LOCATION, fullWeather)
|
||||||
|
// Save data to database
|
||||||
|
repository.saveLastSavedAt(CURRENT_LOCATION)
|
||||||
|
repository.saveCurrentWeatherToRoom(weatherEntity)
|
||||||
|
|
||||||
|
val data = createWidgetWeatherCollection(weatherEntity, currentLocation)
|
||||||
|
return WidgetState.HasData(data)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun getWidgetWeather(): WidgetData? {
|
suspend fun getWidgetWeather(): WidgetData? {
|
||||||
return try {
|
return try {
|
||||||
val result = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
|
val result = repository.loadSingleCurrentWeatherFromRoom(CURRENT_LOCATION)
|
||||||
@@ -127,6 +201,30 @@ class ServicesHelper(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createWidgetWeatherCollection(result: WeatherEntity, locationName: String): WidgetWeatherCollection {
|
||||||
|
val widgetData = result.weather.let {
|
||||||
|
val bitmap = it.current?.icon
|
||||||
|
val temp = it.current?.temp?.toInt().toString()
|
||||||
|
val epoc = System.currentTimeMillis()
|
||||||
|
|
||||||
|
WidgetData(locationName, bitmap, temp, epoc)
|
||||||
|
}
|
||||||
|
|
||||||
|
val list = mutableListOf<InnerWidgetCellData>()
|
||||||
|
|
||||||
|
result.weather.daily?.drop(1)?.dropLast(2)?.forEach { dailyWeather ->
|
||||||
|
val day = dailyWeather.dt?.toSmallDayName()
|
||||||
|
val icon = dailyWeather.icon
|
||||||
|
val temp = dailyWeather.max?.toInt().toString()
|
||||||
|
|
||||||
|
val item = InnerWidgetCellData(day, icon, temp)
|
||||||
|
list.add(item)
|
||||||
|
}
|
||||||
|
list.toList()
|
||||||
|
|
||||||
|
return WidgetWeatherCollection(widgetData, list)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun getBitmapFromUrl(imageAddress: String?): Bitmap? {
|
private suspend fun getBitmapFromUrl(imageAddress: String?): Bitmap? {
|
||||||
return suspendCoroutine { cont ->
|
return suspendCoroutine { cont ->
|
||||||
Picasso.get().load(imageAddress).into(object : Target {
|
Picasso.get().load(imageAddress).into(object : Target {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package com.appttude.h_mal.atlas_weather.model.forecast
|
|||||||
|
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.WeatherEntity
|
||||||
import com.appttude.h_mal.atlas_weather.model.weather.Hour
|
import com.appttude.h_mal.atlas_weather.model.weather.Hour
|
||||||
|
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ data class WeatherDisplay(
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(entity: EntityItem) : this(
|
constructor(entity: WeatherEntity) : this(
|
||||||
entity.weather.current?.temp,
|
entity.weather.current?.temp,
|
||||||
entity.weather.temperatureUnit,
|
entity.weather.temperatureUnit,
|
||||||
entity.id,
|
entity.id,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.model.widget
|
||||||
|
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
|
||||||
|
data class WidgetError(
|
||||||
|
@DrawableRes
|
||||||
|
val icon: Int,
|
||||||
|
val errorMessage: String
|
||||||
|
)
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package com.appttude.h_mal.atlas_weather.model.widget
|
||||||
|
|
||||||
|
sealed class WidgetState {
|
||||||
|
class HasData<T : Any>(val data: T) : WidgetState()
|
||||||
|
class HasError<T : Any>(val error: T) : WidgetState()
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import androidx.annotation.RequiresPermission
|
|||||||
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.repository.Repository
|
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.WeatherEntity
|
||||||
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
||||||
import com.appttude.h_mal.atlas_weather.base.baseViewModels.WeatherViewModel
|
import com.appttude.h_mal.atlas_weather.base.baseViewModels.WeatherViewModel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -18,8 +18,8 @@ class MainViewModel(
|
|||||||
) : WeatherViewModel() {
|
) : WeatherViewModel() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
repository.loadCurrentWeatherFromRoom(CURRENT_LOCATION).observeForever {
|
repository.loadCurrentWeatherFromRoom(CURRENT_LOCATION).observeForever {w ->
|
||||||
it?.let {
|
w?.let {
|
||||||
val weather = WeatherDisplay(it)
|
val weather = WeatherDisplay(it)
|
||||||
onSuccess(weather)
|
onSuccess(weather)
|
||||||
}
|
}
|
||||||
@@ -29,10 +29,10 @@ class MainViewModel(
|
|||||||
@RequiresPermission(value = Manifest.permission.ACCESS_COARSE_LOCATION)
|
@RequiresPermission(value = Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
fun fetchData() {
|
fun fetchData() {
|
||||||
onStart()
|
onStart()
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
job = CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
// Has the search been conducted in the last 5 minutes
|
// Has the search been conducted in the last 5 minutes
|
||||||
val entityItem = if (repository.isSearchValid(CURRENT_LOCATION)) {
|
val weatherEntity = if (repository.isSearchValid(CURRENT_LOCATION)) {
|
||||||
// Get location
|
// Get location
|
||||||
val latLong = locationProvider.getCurrentLatLong()
|
val latLong = locationProvider.getCurrentLatLong()
|
||||||
// Get weather from api
|
// Get weather from api
|
||||||
@@ -41,13 +41,13 @@ class MainViewModel(
|
|||||||
val currentLocation =
|
val currentLocation =
|
||||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon)
|
||||||
val fullWeather = createFullWeather(weather, currentLocation)
|
val fullWeather = createFullWeather(weather, currentLocation)
|
||||||
EntityItem(CURRENT_LOCATION, fullWeather)
|
WeatherEntity(CURRENT_LOCATION, fullWeather)
|
||||||
} else {
|
} else {
|
||||||
repository.getSingleWeather(CURRENT_LOCATION)
|
repository.getSingleWeather(CURRENT_LOCATION)
|
||||||
}
|
}
|
||||||
// Save data if not null
|
// Save data if not null
|
||||||
repository.saveLastSavedAt(CURRENT_LOCATION)
|
repository.saveLastSavedAt(CURRENT_LOCATION)
|
||||||
repository.saveCurrentWeatherToRoom(entityItem)
|
repository.saveCurrentWeatherToRoom(weatherEntity)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
onError(e.message!!)
|
onError(e.message!!)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package com.appttude.h_mal.atlas_weather.viewmodel
|
|||||||
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.response.forecast.WeatherResponse
|
import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherResponse
|
||||||
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.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.WeatherEntity
|
||||||
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
import com.appttude.h_mal.atlas_weather.model.forecast.WeatherDisplay
|
||||||
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.base.baseViewModels.WeatherViewModel
|
import com.appttude.h_mal.atlas_weather.base.baseViewModels.WeatherViewModel
|
||||||
@@ -102,7 +102,7 @@ class WorldViewModel(
|
|||||||
}
|
}
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
try {
|
try {
|
||||||
val list = mutableListOf<EntityItem>()
|
val list = mutableListOf<WeatherEntity>()
|
||||||
repository.loadWeatherList().forEach { locationName ->
|
repository.loadWeatherList().forEach { locationName ->
|
||||||
// If search not valid move onto next in loop
|
// If search not valid move onto next in loop
|
||||||
if (!repository.isSearchValid(locationName)) return@forEach
|
if (!repository.isSearchValid(locationName)) return@forEach
|
||||||
@@ -151,7 +151,7 @@ class WorldViewModel(
|
|||||||
return repository.getWeatherFromApi(lat.toString(), lon.toString())
|
return repository.getWeatherFromApi(lat.toString(), lon.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun createWeatherEntity(locationName: String): EntityItem {
|
private suspend fun createWeatherEntity(locationName: String): WeatherEntity {
|
||||||
val weather = getWeather(locationName)
|
val weather = getWeather(locationName)
|
||||||
val location =
|
val location =
|
||||||
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon, LocationType.City)
|
locationProvider.getLocationNameFromLatLong(weather.lat, weather.lon, LocationType.City)
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ abstract class BaseWidgetServiceIntentClass<T : AppWidgetProvider> : JobIntentSe
|
|||||||
lateinit var appWidgetManager: AppWidgetManager
|
lateinit var appWidgetManager: AppWidgetManager
|
||||||
lateinit var appWidgetIds: IntArray
|
lateinit var appWidgetIds: IntArray
|
||||||
|
|
||||||
fun initiallizeWidgetData(componentName: ComponentName) {
|
fun initializeWidgetData(componentName: ComponentName) {
|
||||||
appWidgetManager = AppWidgetManager.getInstance(baseContext)
|
appWidgetManager = AppWidgetManager.getInstance(baseContext)
|
||||||
appWidgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
appWidgetIds = appWidgetManager.getAppWidgetIds(componentName)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,21 @@
|
|||||||
package com.appttude.h_mal.atlas_weather.widget
|
package com.appttude.h_mal.atlas_weather.widget
|
||||||
|
|
||||||
import android.Manifest.permission.ACCESS_COARSE_LOCATION
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager.PERMISSION_GRANTED
|
|
||||||
import android.icu.text.SimpleDateFormat
|
import android.icu.text.SimpleDateFormat
|
||||||
import android.os.PowerManager
|
|
||||||
import android.widget.RemoteViews
|
import android.widget.RemoteViews
|
||||||
import androidx.core.app.ActivityCompat.checkSelfPermission
|
|
||||||
import com.appttude.h_mal.atlas_weather.R
|
import com.appttude.h_mal.atlas_weather.R
|
||||||
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
import com.appttude.h_mal.atlas_weather.helper.ServicesHelper
|
||||||
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.WidgetError
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.widget.WidgetState.HasData
|
||||||
|
import com.appttude.h_mal.atlas_weather.model.widget.WidgetState.HasError
|
||||||
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.ui.MainActivity
|
import com.appttude.h_mal.atlas_weather.ui.MainActivity
|
||||||
import com.appttude.h_mal.atlas_weather.utils.isInternetAvailable
|
|
||||||
import com.appttude.h_mal.atlas_weather.utils.tryOrNullSuspended
|
|
||||||
import com.appttude.h_mal.atlas_weather.widget.WidgetState.*
|
import com.appttude.h_mal.atlas_weather.widget.WidgetState.*
|
||||||
import com.appttude.h_mal.atlas_weather.widget.WidgetState.Companion.getWidgetState
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -46,59 +42,35 @@ class WidgetJobServiceIntent : BaseWidgetServiceIntentClass<NewAppWidget>() {
|
|||||||
executeWidgetUpdate()
|
executeWidgetUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
private fun executeWidgetUpdate() {
|
private fun executeWidgetUpdate() {
|
||||||
val componentName = ComponentName(this, NewAppWidget::class.java)
|
val componentName = ComponentName(this, NewAppWidget::class.java)
|
||||||
initiallizeWidgetData(componentName)
|
initializeWidgetData(componentName)
|
||||||
|
|
||||||
initiateWidgetUpdate(getCurrentWidgetState())
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initiateWidgetUpdate(state: WidgetState) {
|
|
||||||
when (state) {
|
|
||||||
NO_LOCATION, SCREEN_ON_CONNECTION_UNAVAILABLE -> updateErrorWidget(state)
|
|
||||||
else -> updateWidget(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateWidget(fromStorage: Boolean) {
|
|
||||||
CoroutineScope(Dispatchers.IO).launch {
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
val result = getWidgetWeather(fromStorage)
|
setLoadingView()
|
||||||
appWidgetIds.forEach { id -> setupView(id, result) }
|
|
||||||
|
val widgetState = helper.fetchWidgetData()
|
||||||
|
appWidgetIds.forEach { id ->
|
||||||
|
when (widgetState) {
|
||||||
|
is HasData<*> -> {
|
||||||
|
val data = widgetState.data as WidgetWeatherCollection
|
||||||
|
setupView(id, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
is HasError<*> -> {
|
||||||
|
if (widgetState.error is WidgetError) {
|
||||||
|
val error = widgetState.error
|
||||||
|
setupErrorView(id, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> return@launch
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateErrorWidget(state: WidgetState) {
|
|
||||||
appWidgetIds.forEach { id -> setEmptyView(id, state) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getCurrentWidgetState(): WidgetState {
|
|
||||||
val pm = getSystemService(POWER_SERVICE) as PowerManager
|
|
||||||
val isScreenOn = pm.isInteractive
|
|
||||||
val locationGranted =
|
|
||||||
checkSelfPermission(this, ACCESS_COARSE_LOCATION) == PERMISSION_GRANTED
|
|
||||||
val internetAvailable = isInternetAvailable(this.applicationContext)
|
|
||||||
|
|
||||||
return getWidgetState(locationGranted, isScreenOn, internetAvailable)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("MissingPermission")
|
|
||||||
suspend fun getWidgetWeather(storageOnly: Boolean): WidgetWeatherCollection? {
|
|
||||||
return tryOrNullSuspended {
|
|
||||||
if (!storageOnly) helper.fetchData()
|
|
||||||
helper.getWidgetWeatherCollection()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setEmptyView(appWidgetId: Int, state: WidgetState) {
|
|
||||||
val error = when (state) {
|
|
||||||
NO_LOCATION -> "No Location Permission"
|
|
||||||
SCREEN_ON_CONNECTION_UNAVAILABLE -> "No network available"
|
|
||||||
else -> "No data"
|
|
||||||
}
|
|
||||||
|
|
||||||
val views = createRemoteView(R.layout.weather_app_widget)
|
|
||||||
bindErrorView(appWidgetId, views, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupView(
|
private fun setupView(
|
||||||
appWidgetId: Int,
|
appWidgetId: Int,
|
||||||
@@ -115,6 +87,49 @@ class WidgetJobServiceIntent : BaseWidgetServiceIntentClass<NewAppWidget>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupErrorView(
|
||||||
|
appWidgetId: Int,
|
||||||
|
error: WidgetError
|
||||||
|
) {
|
||||||
|
val views = createRemoteView(R.layout.weather_app_widget)
|
||||||
|
// setLastUpdated(views, collection?.widgetData?.timeStamp)
|
||||||
|
views.setInt(R.id.whole_widget_view, "setBackgroundColor", helper.getWidgetBackground())
|
||||||
|
|
||||||
|
bindEmptyView(appWidgetId, views, error.errorMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("DiscouragedApi")
|
||||||
|
private fun setLoadingView() {
|
||||||
|
appWidgetIds.forEach { id ->
|
||||||
|
val views = createRemoteView(R.layout.weather_app_widget)
|
||||||
|
views.setInt(R.id.whole_widget_view, "setBackgroundColor", helper.getWidgetBackground())
|
||||||
|
|
||||||
|
val clickUpdate = createUpdatePendingIntent(NewAppWidget::class.java, id)
|
||||||
|
|
||||||
|
views.apply {
|
||||||
|
setTextViewText(R.id.widget_current_location, "Loading...")
|
||||||
|
setImageViewResource(R.id.location_icon, R.drawable.location_flag)
|
||||||
|
setOnClickPendingIntent(R.id.widget_current_location, clickUpdate)
|
||||||
|
|
||||||
|
(0..4).forEach { i ->
|
||||||
|
val dayId: Int =
|
||||||
|
resources.getIdentifier("widget_item_day_$i", "id", packageName)
|
||||||
|
val imageId: Int =
|
||||||
|
resources.getIdentifier("widget_item_image_$i", "id", packageName)
|
||||||
|
val tempId: Int =
|
||||||
|
resources.getIdentifier("widget_item_temp_high_$i", "id", packageName)
|
||||||
|
|
||||||
|
views.setTextViewText(dayId, "loading")
|
||||||
|
views.setTextViewText(tempId, "")
|
||||||
|
setImageViewResource(imageId, R.drawable.baseline_refresh_24)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instruct the widget manager to update the widget
|
||||||
|
appWidgetManager.updateAppWidget(id, views)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun bindErrorView(
|
override fun bindErrorView(
|
||||||
widgetId: Int,
|
widgetId: Int,
|
||||||
views: RemoteViews,
|
views: RemoteViews,
|
||||||
|
|||||||
5
app/src/main/res/drawable/baseline_refresh_24.xml
Normal file
5
app/src/main/res/drawable/baseline_refresh_24.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
||||||
|
</vector>
|
||||||
@@ -5,22 +5,17 @@ import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherRe
|
|||||||
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
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.WeatherEntity
|
||||||
import com.appttude.h_mal.atlas_weather.utils.BaseTest
|
import com.appttude.h_mal.atlas_weather.utils.BaseTest
|
||||||
import com.nhaarman.mockitokotlin2.any
|
import com.nhaarman.mockitokotlin2.any
|
||||||
import com.nhaarman.mockitokotlin2.doAnswer
|
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
import io.mockk.coEvery
|
import io.mockk.coEvery
|
||||||
import io.mockk.coJustRun
|
|
||||||
import io.mockk.every
|
import io.mockk.every
|
||||||
import io.mockk.impl.annotations.MockK
|
import io.mockk.impl.annotations.MockK
|
||||||
import io.mockk.justRun
|
|
||||||
import io.mockk.mockk
|
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 org.mockito.ArgumentMatchers.anyString
|
|
||||||
import retrofit2.Response
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
@@ -124,7 +119,7 @@ class RepositoryImplTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun loadWeatherList_validResponse() {
|
fun loadWeatherList_validResponse() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val elements = listOf<EntityItem>(
|
val elements = listOf<WeatherEntity>(
|
||||||
mockk { every { id } returns any() },
|
mockk { every { id } returns any() },
|
||||||
mockk { every { id } returns any() },
|
mockk { every { id } returns any() },
|
||||||
mockk { every { id } returns any() },
|
mockk { every { id } returns any() },
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import com.appttude.h_mal.atlas_weather.data.network.response.forecast.WeatherRe
|
|||||||
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
|
||||||
import com.appttude.h_mal.atlas_weather.data.room.entity.EntityItem
|
import com.appttude.h_mal.atlas_weather.data.room.entity.WeatherEntity
|
||||||
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.BaseTest
|
import com.appttude.h_mal.atlas_weather.utils.BaseTest
|
||||||
import io.mockk.MockKAnnotations
|
import io.mockk.MockKAnnotations
|
||||||
@@ -44,7 +44,7 @@ class ServicesHelperTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun testWidgetDataAsync_successfulResponse() = runBlocking {
|
fun testWidgetDataAsync_successfulResponse() = runBlocking {
|
||||||
// Arrange
|
// Arrange
|
||||||
val entityItem = EntityItem(CURRENT_LOCATION, FullWeather(weatherResponse).apply {
|
val weatherEntity = WeatherEntity(CURRENT_LOCATION, FullWeather(weatherResponse).apply {
|
||||||
temperatureUnit = "°C"
|
temperatureUnit = "°C"
|
||||||
locationString = CURRENT_LOCATION
|
locationString = CURRENT_LOCATION
|
||||||
})
|
})
|
||||||
@@ -68,7 +68,7 @@ class ServicesHelperTest : BaseTest() {
|
|||||||
)
|
)
|
||||||
}.returns(CURRENT_LOCATION)
|
}.returns(CURRENT_LOCATION)
|
||||||
every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit
|
every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit
|
||||||
coEvery { repository.saveCurrentWeatherToRoom(entityItem) } returns Unit
|
coEvery { repository.saveCurrentWeatherToRoom(weatherEntity) } returns Unit
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
val result = helper.fetchData()
|
val result = helper.fetchData()
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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.forecast.WeatherResponse
|
||||||
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.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.WeatherEntity
|
||||||
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
|
||||||
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
import com.appttude.h_mal.atlas_weather.model.weather.FullWeather
|
||||||
@@ -50,7 +50,7 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun fetchDataForSingleLocation_validLocation_validReturn() {
|
fun fetchDataForSingleLocation_validLocation_validReturn() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val entityItem = EntityItem(CURRENT_LOCATION, FullWeather(weatherResponse).apply {
|
val weatherEntity = WeatherEntity(CURRENT_LOCATION, FullWeather(weatherResponse).apply {
|
||||||
temperatureUnit = "°C"
|
temperatureUnit = "°C"
|
||||||
locationString = CURRENT_LOCATION
|
locationString = CURRENT_LOCATION
|
||||||
})
|
})
|
||||||
@@ -75,7 +75,7 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
)
|
)
|
||||||
}.returns(CURRENT_LOCATION)
|
}.returns(CURRENT_LOCATION)
|
||||||
every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit
|
every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit
|
||||||
coEvery { repository.saveCurrentWeatherToRoom(entityItem) } returns Unit
|
coEvery { repository.saveCurrentWeatherToRoom(weatherEntity) } returns Unit
|
||||||
|
|
||||||
viewModel.fetchDataForSingleLocation(CURRENT_LOCATION)
|
viewModel.fetchDataForSingleLocation(CURRENT_LOCATION)
|
||||||
|
|
||||||
@@ -113,13 +113,13 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun searchAboveFallbackTime_validLocation_validReturn() {
|
fun searchAboveFallbackTime_validLocation_validReturn() {
|
||||||
// Arrange
|
// Arrange
|
||||||
val entityItem = EntityItem(CURRENT_LOCATION, FullWeather(weatherResponse).apply {
|
val weatherEntity = WeatherEntity(CURRENT_LOCATION, FullWeather(weatherResponse).apply {
|
||||||
temperatureUnit = "°C"
|
temperatureUnit = "°C"
|
||||||
locationString = CURRENT_LOCATION
|
locationString = CURRENT_LOCATION
|
||||||
})
|
})
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
coEvery { repository.getSingleWeather(CURRENT_LOCATION) }.returns(entityItem)
|
coEvery { repository.getSingleWeather(CURRENT_LOCATION) }.returns(weatherEntity)
|
||||||
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(false)
|
every { repository.isSearchValid(CURRENT_LOCATION) }.returns(false)
|
||||||
coEvery {
|
coEvery {
|
||||||
repository.getWeatherFromApi(
|
repository.getWeatherFromApi(
|
||||||
@@ -128,7 +128,7 @@ class WorldViewModelTest : BaseTest() {
|
|||||||
)
|
)
|
||||||
}.returns(weatherResponse)
|
}.returns(weatherResponse)
|
||||||
every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit
|
every { repository.saveLastSavedAt(CURRENT_LOCATION) } returns Unit
|
||||||
coEvery { repository.saveCurrentWeatherToRoom(entityItem) } returns Unit
|
coEvery { repository.saveCurrentWeatherToRoom(weatherEntity) } returns Unit
|
||||||
|
|
||||||
viewModel.fetchDataForSingleLocation(CURRENT_LOCATION)
|
viewModel.fetchDataForSingleLocation(CURRENT_LOCATION)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user