not working
This commit is contained in:
parent
a13db7aa7d
commit
376f28dc9d
21 changed files with 360 additions and 57 deletions
|
@ -3,6 +3,7 @@ package com.henryhiles.qweather.di
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
|
import com.henryhiles.qweather.domain.remote.GeocodingApi
|
||||||
import com.henryhiles.qweather.domain.remote.WeatherApi
|
import com.henryhiles.qweather.domain.remote.WeatherApi
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
|
@ -30,7 +31,6 @@ private fun isNetworkAvailable(context: Context): Boolean {
|
||||||
|
|
||||||
val appModule = module {
|
val appModule = module {
|
||||||
fun provideWeatherApi(context: Context): WeatherApi {
|
fun provideWeatherApi(context: Context): WeatherApi {
|
||||||
|
|
||||||
val cacheControlInterceptor = Interceptor { chain ->
|
val cacheControlInterceptor = Interceptor { chain ->
|
||||||
val originalResponse = chain.proceed(chain.request())
|
val originalResponse = chain.proceed(chain.request())
|
||||||
if (isNetworkAvailable(context)) {
|
if (isNetworkAvailable(context)) {
|
||||||
|
@ -50,13 +50,25 @@ val appModule = module {
|
||||||
val cache = Cache(context.cacheDir, cacheSize.toLong())
|
val cache = Cache(context.cacheDir, cacheSize.toLong())
|
||||||
val builder = Builder()
|
val builder = Builder()
|
||||||
.cache(cache)
|
.cache(cache)
|
||||||
builder.networkInterceptors()
|
builder.networkInterceptors().add(cacheControlInterceptor)
|
||||||
.add(cacheControlInterceptor)
|
|
||||||
val okHttpClient = builder.build()
|
val okHttpClient = builder.build()
|
||||||
|
|
||||||
return Retrofit.Builder().baseUrl("https://api.open-meteo.com").client(okHttpClient)
|
return Retrofit.Builder()
|
||||||
.addConverterFactory(MoshiConverterFactory.create()).build().create()
|
.baseUrl("https://api.open-meteo.com")
|
||||||
|
.client(okHttpClient)
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
.create()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun provideGeocodingApi(): GeocodingApi {
|
||||||
|
return Retrofit.Builder()
|
||||||
|
.baseUrl("https://geocoding-api.open-meteo.com")
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
.create()
|
||||||
}
|
}
|
||||||
|
|
||||||
singleOf(::provideWeatherApi)
|
singleOf(::provideWeatherApi)
|
||||||
|
singleOf(::provideGeocodingApi)
|
||||||
}
|
}
|
|
@ -1,9 +1,12 @@
|
||||||
package com.henryhiles.qweather.di
|
package com.henryhiles.qweather.di
|
||||||
|
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferenceManager
|
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferenceManager
|
||||||
|
import com.henryhiles.qweather.presentation.screenmodel.LocationPreferenceManager
|
||||||
|
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val managerModule = module {
|
val managerModule = module {
|
||||||
singleOf(::AppearancePreferenceManager)
|
singleOf(::AppearancePreferenceManager)
|
||||||
|
singleOf(::LocationPreferenceManager)
|
||||||
}
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
package com.henryhiles.qweather.di
|
package com.henryhiles.qweather.di
|
||||||
|
|
||||||
|
import com.henryhiles.qweather.domain.repository.GeocodingRepository
|
||||||
import com.henryhiles.qweather.domain.repository.WeatherRepository
|
import com.henryhiles.qweather.domain.repository.WeatherRepository
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val repositoryModule = module {
|
val repositoryModule = module {
|
||||||
singleOf(::WeatherRepository)
|
singleOf(::WeatherRepository)
|
||||||
|
singleOf(::GeocodingRepository)
|
||||||
}
|
}
|
|
@ -3,11 +3,13 @@ package com.henryhiles.qweather.di
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferencesScreenModel
|
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferencesScreenModel
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.DailyWeatherScreenModel
|
import com.henryhiles.qweather.presentation.screenmodel.DailyWeatherScreenModel
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherScreenModel
|
import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherScreenModel
|
||||||
|
import com.henryhiles.qweather.presentation.screenmodel.LocationPickerScreenModel
|
||||||
import org.koin.core.module.dsl.factoryOf
|
import org.koin.core.module.dsl.factoryOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val screenModelModule = module {
|
val screenModelModule = module {
|
||||||
factoryOf(::AppearancePreferencesScreenModel)
|
factoryOf(::AppearancePreferencesScreenModel)
|
||||||
|
factoryOf(::LocationPickerScreenModel)
|
||||||
factoryOf(::HourlyWeatherScreenModel)
|
factoryOf(::HourlyWeatherScreenModel)
|
||||||
factoryOf(::DailyWeatherScreenModel)
|
factoryOf(::DailyWeatherScreenModel)
|
||||||
}
|
}
|
|
@ -8,7 +8,7 @@ import android.location.Location
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
|
||||||
class LocationTracker constructor(
|
class LocationTracker(
|
||||||
private val application: Application
|
private val application: Application
|
||||||
) {
|
) {
|
||||||
fun getCurrentLocation(): Location? {
|
fun getCurrentLocation(): Location? {
|
||||||
|
|
|
@ -10,15 +10,15 @@ data class DailyWeatherDataDto(
|
||||||
@field:Json(name = "precipitation_probability_max")
|
@field:Json(name = "precipitation_probability_max")
|
||||||
val precipitationProbabilityMax: List<Int>,
|
val precipitationProbabilityMax: List<Int>,
|
||||||
@field:Json(name = "precipitation_sum")
|
@field:Json(name = "precipitation_sum")
|
||||||
val precipitationSum: List<Double>,
|
val precipitationSum: List<Float>,
|
||||||
@field:Json(name = "windspeed_10m_max")
|
@field:Json(name = "windspeed_10m_max")
|
||||||
val windSpeedMax: List<Double>,
|
val windSpeedMax: List<Float>,
|
||||||
@field:Json(name = "temperature_2m_max")
|
@field:Json(name = "temperature_2m_max")
|
||||||
val temperatureMax: List<Double>,
|
val temperatureMax: List<Float>,
|
||||||
@field:Json(name = "temperature_2m_min")
|
@field:Json(name = "temperature_2m_min")
|
||||||
val temperatureMin: List<Double>,
|
val temperatureMin: List<Float>,
|
||||||
@field:Json(name = "apparent_temperature_max")
|
@field:Json(name = "apparent_temperature_max")
|
||||||
val apparentTemperatureMax: List<Double>,
|
val apparentTemperatureMax: List<Float>,
|
||||||
@field:Json(name = "apparent_temperature_min")
|
@field:Json(name = "apparent_temperature_min")
|
||||||
val apparentTemperatureMin: List<Double>
|
val apparentTemperatureMin: List<Float>
|
||||||
)
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.henryhiles.qweather.domain.remote
|
||||||
|
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface GeocodingApi {
|
||||||
|
@GET("v1/search?count=4")
|
||||||
|
suspend fun getGeocodingData(
|
||||||
|
@Query("name") location: String,
|
||||||
|
): GeocodingDto
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.henryhiles.qweather.domain.remote
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
|
||||||
|
data class GeocodingDto(
|
||||||
|
@field:Json(name = "results")
|
||||||
|
val results: List<GeocodingLocationDto>
|
||||||
|
)
|
|
@ -0,0 +1,16 @@
|
||||||
|
package com.henryhiles.qweather.domain.remote
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
|
||||||
|
data class GeocodingLocationDto(
|
||||||
|
@field:Json(name = "name")
|
||||||
|
val city: String,
|
||||||
|
@field:Json(name = "country")
|
||||||
|
val country: String,
|
||||||
|
@field:Json(name = "admin1")
|
||||||
|
val admin: String,
|
||||||
|
@field:Json(name = "latitude")
|
||||||
|
val latitude: Float,
|
||||||
|
@field:Json(name = "longitude")
|
||||||
|
val longitude: Float
|
||||||
|
)
|
|
@ -6,13 +6,13 @@ data class HourlyWeatherDataDto(
|
||||||
@field:Json(name = "time")
|
@field:Json(name = "time")
|
||||||
val time: List<String>,
|
val time: List<String>,
|
||||||
@field:Json(name = "temperature_2m")
|
@field:Json(name = "temperature_2m")
|
||||||
val temperature: List<Double>,
|
val temperature: List<Float>,
|
||||||
@field:Json(name = "apparent_temperature")
|
@field:Json(name = "apparent_temperature")
|
||||||
val apparentTemperature: List<Double>,
|
val apparentTemperature: List<Float>,
|
||||||
@field:Json(name = "weathercode")
|
@field:Json(name = "weathercode")
|
||||||
val weatherCode: List<Int>,
|
val weatherCode: List<Int>,
|
||||||
@field:Json(name = "precipitation_probability")
|
@field:Json(name = "precipitation_probability")
|
||||||
val precipitationProbability: List<Int>,
|
val precipitationProbability: List<Int>,
|
||||||
@field:Json(name = "windspeed_10m")
|
@field:Json(name = "windspeed_10m")
|
||||||
val windSpeed: List<Double>,
|
val windSpeed: List<Float>,
|
||||||
)
|
)
|
|
@ -15,14 +15,14 @@ const val URL = "v1/forecast?$HOURLY&$DAILY&$TIMEZONE&$FORECAST_DAYS"
|
||||||
interface WeatherApi {
|
interface WeatherApi {
|
||||||
@GET(URL)
|
@GET(URL)
|
||||||
suspend fun getWeatherData(
|
suspend fun getWeatherData(
|
||||||
@Query("latitude") lat: Double,
|
@Query("latitude") lat: Float,
|
||||||
@Query("longitude") long: Double,
|
@Query("longitude") long: Float,
|
||||||
): WeatherDto
|
): WeatherDto
|
||||||
|
|
||||||
@Headers("Cache-Control: no-cache")
|
@Headers("Cache-Control: no-cache")
|
||||||
@GET(URL)
|
@GET(URL)
|
||||||
suspend fun getWeatherDataWithoutCache(
|
suspend fun getWeatherDataWithoutCache(
|
||||||
@Query("latitude") lat: Double,
|
@Query("latitude") lat: Float,
|
||||||
@Query("longitude") long: Double,
|
@Query("longitude") long: Float,
|
||||||
): WeatherDto
|
): WeatherDto
|
||||||
}
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.henryhiles.qweather.domain.repository
|
||||||
|
|
||||||
|
import com.henryhiles.qweather.domain.remote.GeocodingApi
|
||||||
|
import com.henryhiles.qweather.domain.remote.GeocodingLocationDto
|
||||||
|
import com.henryhiles.qweather.domain.util.Resource
|
||||||
|
|
||||||
|
class GeocodingRepository(private val api: GeocodingApi) {
|
||||||
|
suspend fun getGeocodingData(location: String): Resource<List<GeocodingLocationDto>> {
|
||||||
|
return try {
|
||||||
|
Resource.Success(
|
||||||
|
data = api.getGeocodingData(location = location).results
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
Resource.Error(e.message ?: "An unknown error occurred.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,10 +7,10 @@ import com.henryhiles.qweather.domain.util.Resource
|
||||||
import com.henryhiles.qweather.domain.weather.DailyWeatherData
|
import com.henryhiles.qweather.domain.weather.DailyWeatherData
|
||||||
import com.henryhiles.qweather.domain.weather.HourlyWeatherInfo
|
import com.henryhiles.qweather.domain.weather.HourlyWeatherInfo
|
||||||
|
|
||||||
class WeatherRepository constructor(private val api: WeatherApi) {
|
class WeatherRepository(private val api: WeatherApi) {
|
||||||
suspend fun getHourlyWeatherData(
|
suspend fun getHourlyWeatherData(
|
||||||
lat: Double,
|
lat: Float,
|
||||||
long: Double,
|
long: Float,
|
||||||
cache: Boolean = true
|
cache: Boolean = true
|
||||||
): Resource<HourlyWeatherInfo> {
|
): Resource<HourlyWeatherInfo> {
|
||||||
return try {
|
return try {
|
||||||
|
@ -31,8 +31,8 @@ class WeatherRepository constructor(private val api: WeatherApi) {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getDailyWeatherData(
|
suspend fun getDailyWeatherData(
|
||||||
lat: Double,
|
lat: Float,
|
||||||
long: Double,
|
long: Float,
|
||||||
cache: Boolean = true
|
cache: Boolean = true
|
||||||
): Resource<List<DailyWeatherData>> {
|
): Resource<List<DailyWeatherData>> {
|
||||||
return try {
|
return try {
|
||||||
|
@ -43,8 +43,7 @@ class WeatherRepository constructor(private val api: WeatherApi) {
|
||||||
) else api.getWeatherDataWithoutCache(
|
) else api.getWeatherDataWithoutCache(
|
||||||
lat = lat,
|
lat = lat,
|
||||||
long = long
|
long = long
|
||||||
))
|
)).dailyWeatherData.toDailyWeatherDataMap()
|
||||||
.dailyWeatherData.toDailyWeatherDataMap()
|
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|
|
@ -5,17 +5,23 @@ import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
import cafe.adriel.voyager.transitions.SlideTransition
|
import cafe.adriel.voyager.transitions.SlideTransition
|
||||||
|
import com.henryhiles.qweather.presentation.screen.LocationPickerScreen
|
||||||
import com.henryhiles.qweather.presentation.screen.MainScreen
|
import com.henryhiles.qweather.presentation.screen.MainScreen
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferenceManager
|
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferenceManager
|
||||||
|
import com.henryhiles.qweather.presentation.screenmodel.LocationPreferenceManager
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.Theme
|
import com.henryhiles.qweather.presentation.screenmodel.Theme
|
||||||
import com.henryhiles.qweather.presentation.ui.theme.WeatherAppTheme
|
import com.henryhiles.qweather.presentation.ui.theme.WeatherAppTheme
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
class QWeatherActivity : ComponentActivity() {
|
class QWeatherActivity : ComponentActivity() {
|
||||||
private val prefs: AppearancePreferenceManager by inject()
|
private val prefs: AppearancePreferenceManager by inject()
|
||||||
|
private val location: LocationPreferenceManager by inject()
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
@ -26,10 +32,12 @@ class QWeatherActivity : ComponentActivity() {
|
||||||
Theme.LIGHT -> false
|
Theme.LIGHT -> false
|
||||||
Theme.DARK -> true
|
Theme.DARK -> true
|
||||||
}
|
}
|
||||||
|
val isLocationSet = location.location != ""
|
||||||
|
|
||||||
WeatherAppTheme(darkTheme = isDark, monet = prefs.monet) {
|
WeatherAppTheme(darkTheme = isDark, monet = prefs.monet) {
|
||||||
Surface {
|
Surface(modifier = Modifier.fillMaxSize()) {
|
||||||
Navigator(screen = MainScreen()) {
|
Text(text = location.location)
|
||||||
|
Navigator(screen = if (isLocationSet) MainScreen() else LocationPickerScreen()) {
|
||||||
SlideTransition(it)
|
SlideTransition(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
@ -21,8 +23,8 @@ fun WeatherHour(
|
||||||
onChangeSelected: () -> Unit
|
onChangeSelected: () -> Unit
|
||||||
) {
|
) {
|
||||||
data.let {
|
data.let {
|
||||||
val formattedTime = remember(it) {
|
val formattedTime by remember {
|
||||||
it.time.format(DateTimeFormatter.ofPattern("HH:mm"))
|
derivedStateOf { it.time.format(DateTimeFormatter.ofPattern("HH:mm")) }
|
||||||
}
|
}
|
||||||
Card(modifier = modifier.clickable {
|
Card(modifier = modifier.clickable {
|
||||||
onChangeSelected()
|
onChangeSelected()
|
||||||
|
@ -44,21 +46,7 @@ fun WeatherHour(
|
||||||
|
|
||||||
Text(text = "${it.temperature}°C")
|
Text(text = "${it.temperature}°C")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
// Column(
|
|
||||||
// horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
// verticalArrangement = Arrangement.SpaceBetween,
|
|
||||||
// modifier = modifier
|
|
||||||
// ) {
|
|
||||||
// Text(text = formattedTime)
|
|
||||||
// Image(
|
|
||||||
// painter = painterResource(id = it.weatherType.iconRes),
|
|
||||||
// contentDescription = "Image of ${it.weatherType.weatherDesc}",
|
|
||||||
// modifier = Modifier.width(40.dp)
|
|
||||||
// )
|
|
||||||
// Text(text = "${it.temperature}°C")
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -20,14 +20,10 @@ import com.henryhiles.qweather.presentation.components.settings.SettingsSwitch
|
||||||
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferencesScreenModel
|
import com.henryhiles.qweather.presentation.screenmodel.AppearancePreferencesScreenModel
|
||||||
|
|
||||||
class AppearanceSettingsScreen : Screen {
|
class AppearanceSettingsScreen : Screen {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content() = Screen()
|
override fun Content() {
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun Screen() {
|
|
||||||
val screenModel: AppearancePreferencesScreenModel = getScreenModel()
|
val screenModel: AppearancePreferencesScreenModel = getScreenModel()
|
||||||
val ctx = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
Scaffold(topBar = {
|
Scaffold(topBar = {
|
||||||
SmallToolbar(
|
SmallToolbar(
|
||||||
|
@ -51,7 +47,7 @@ class AppearanceSettingsScreen : Screen {
|
||||||
SettingsItemChoice(
|
SettingsItemChoice(
|
||||||
label = stringResource(R.string.appearance_theme),
|
label = stringResource(R.string.appearance_theme),
|
||||||
pref = screenModel.prefs.theme,
|
pref = screenModel.prefs.theme,
|
||||||
labelFactory = { ctx.getString(it.label) }
|
labelFactory = { context.getString(it.label) }
|
||||||
) { screenModel.prefs.theme = it }
|
) { screenModel.prefs.theme = it }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,167 @@
|
||||||
|
package com.henryhiles.qweather.presentation.screen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Check
|
||||||
|
import androidx.compose.material.icons.outlined.MyLocation
|
||||||
|
import androidx.compose.material.icons.outlined.Search
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.koin.getScreenModel
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
|
import com.henryhiles.qweather.presentation.screenmodel.LocationPickerScreenModel
|
||||||
|
|
||||||
|
class LocationPickerScreen : Screen {
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val screenModel: LocationPickerScreenModel = getScreenModel()
|
||||||
|
var latitude by remember { mutableStateOf(0f) }
|
||||||
|
var longitude by remember { mutableStateOf(0f) }
|
||||||
|
var location by remember { mutableStateOf("") }
|
||||||
|
var locationSearch by remember { mutableStateOf("") }
|
||||||
|
val navigator = LocalNavigator.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
screenModel.state.error?.let {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
confirmButton = {},
|
||||||
|
title = { Text(text = stringResource(id = R.string.error)) },
|
||||||
|
text = {
|
||||||
|
SelectionContainer {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} ?: AlertDialog(
|
||||||
|
onDismissRequest = {},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
screenModel.prefs.location = location
|
||||||
|
screenModel.prefs.latitude = latitude
|
||||||
|
screenModel.prefs.longitude = longitude
|
||||||
|
navigator?.push(MainScreen())
|
||||||
|
},
|
||||||
|
enabled = location != ""
|
||||||
|
) {
|
||||||
|
Text(text = stringResource(id = R.string.action_apply))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = { Text(text = stringResource(id = R.string.location_choose)) },
|
||||||
|
text = {
|
||||||
|
Column {
|
||||||
|
OutlinedTextField(
|
||||||
|
label = { Text(text = stringResource(id = R.string.location)) },
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
capitalization = KeyboardCapitalization.Sentences,
|
||||||
|
imeAction = ImeAction.Search
|
||||||
|
),
|
||||||
|
keyboardActions = KeyboardActions(onSearch = {
|
||||||
|
screenModel.loadGeolocationInfo(
|
||||||
|
locationSearch
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
maxLines = 1,
|
||||||
|
value = locationSearch,
|
||||||
|
onValueChange = {
|
||||||
|
locationSearch = it
|
||||||
|
},
|
||||||
|
trailingIcon = {
|
||||||
|
if (locationSearch == "")
|
||||||
|
IconButton(onClick = {
|
||||||
|
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.MyLocation,
|
||||||
|
contentDescription = stringResource(id = R.string.location_auto_pick)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
IconButton(onClick = {
|
||||||
|
screenModel.loadGeolocationInfo(
|
||||||
|
locationSearch
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Outlined.Search,
|
||||||
|
contentDescription = stringResource(id = R.string.action_search)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
Text(text = "${screenModel.state.locations != null}")
|
||||||
|
screenModel.state.locations?.let {
|
||||||
|
Text(
|
||||||
|
text = "hi"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (screenModel.state.isLoading) CircularProgressIndicator(
|
||||||
|
modifier = Modifier.align(
|
||||||
|
Alignment.CenterHorizontally
|
||||||
|
)
|
||||||
|
) else screenModel.state.locations?.let {
|
||||||
|
LazyColumn {
|
||||||
|
items(it) {
|
||||||
|
val locationText by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
context.getString(
|
||||||
|
R.string.location_string,
|
||||||
|
it.city, it.admin, it.country
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Card(modifier = Modifier.clickable {
|
||||||
|
location = locationText
|
||||||
|
longitude = it.longitude
|
||||||
|
latitude = it.latitude
|
||||||
|
}) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
if (location == locationText) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = stringResource(
|
||||||
|
id = R.string.selected
|
||||||
|
),
|
||||||
|
modifier = Modifier.height(16.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
Text(text = locationText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,7 +19,7 @@ data class DailyWeatherState(
|
||||||
val expanded: Int? = null
|
val expanded: Int? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
class DailyWeatherScreenModel constructor(
|
class DailyWeatherScreenModel(
|
||||||
private val repository: WeatherRepository,
|
private val repository: WeatherRepository,
|
||||||
private val locationTracker: LocationTracker,
|
private val locationTracker: LocationTracker,
|
||||||
) : ScreenModel {
|
) : ScreenModel {
|
||||||
|
@ -34,8 +34,8 @@ class DailyWeatherScreenModel constructor(
|
||||||
currentLocation?.let { location ->
|
currentLocation?.let { location ->
|
||||||
state = when (val result =
|
state = when (val result =
|
||||||
repository.getDailyWeatherData(
|
repository.getDailyWeatherData(
|
||||||
lat = location.latitude,
|
lat = location.latitude.toFloat(),
|
||||||
long = location.longitude,
|
long = location.longitude.toFloat(),
|
||||||
cache = cache
|
cache = cache
|
||||||
)) {
|
)) {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
|
|
|
@ -20,7 +20,7 @@ data class HourlyWeatherState(
|
||||||
val selected: Int? = null
|
val selected: Int? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
class HourlyWeatherScreenModel constructor(
|
class HourlyWeatherScreenModel(
|
||||||
private val repository: WeatherRepository,
|
private val repository: WeatherRepository,
|
||||||
private val locationTracker: LocationTracker,
|
private val locationTracker: LocationTracker,
|
||||||
private val context: Context
|
private val context: Context
|
||||||
|
@ -35,8 +35,8 @@ class HourlyWeatherScreenModel constructor(
|
||||||
currentLocation?.let { location ->
|
currentLocation?.let { location ->
|
||||||
state = when (val result =
|
state = when (val result =
|
||||||
repository.getHourlyWeatherData(
|
repository.getHourlyWeatherData(
|
||||||
lat = location.latitude,
|
lat = location.latitude.toFloat(),
|
||||||
long = location.longitude,
|
long = location.longitude.toFloat(),
|
||||||
cache = cache
|
cache = cache
|
||||||
)) {
|
)) {
|
||||||
is Resource.Success -> {
|
is Resource.Success -> {
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.henryhiles.qweather.presentation.screenmodel
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
|
import com.henryhiles.qweather.domain.location.LocationTracker
|
||||||
|
import com.henryhiles.qweather.domain.manager.BasePreferenceManager
|
||||||
|
import com.henryhiles.qweather.domain.remote.GeocodingLocationDto
|
||||||
|
import com.henryhiles.qweather.domain.repository.GeocodingRepository
|
||||||
|
import com.henryhiles.qweather.domain.util.Resource
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
data class LocationPickerState(
|
||||||
|
val locations: List<GeocodingLocationDto>? = null,
|
||||||
|
val isLoading: Boolean = false,
|
||||||
|
val error: String? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
class LocationPreferenceManager(context: Context) :
|
||||||
|
BasePreferenceManager(context.getSharedPreferences("location", Context.MODE_PRIVATE)) {
|
||||||
|
var latitude by floatPreference("lat", 0f)
|
||||||
|
var longitude by floatPreference("long", 0f)
|
||||||
|
var location by stringPreference("string")
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocationPickerScreenModel(
|
||||||
|
val prefs: LocationPreferenceManager,
|
||||||
|
private val repository: GeocodingRepository,
|
||||||
|
private val locationTracker: LocationTracker,
|
||||||
|
private val context: Context
|
||||||
|
) : ScreenModel {
|
||||||
|
var state by mutableStateOf(LocationPickerState())
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun loadGeolocationInfo(location: String) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
state = state.copy(isLoading = true, error = null)
|
||||||
|
|
||||||
|
state = when (val result =
|
||||||
|
repository.getGeocodingData(
|
||||||
|
location = location
|
||||||
|
)) {
|
||||||
|
is Resource.Success -> {
|
||||||
|
state.copy(
|
||||||
|
locations = result.data,
|
||||||
|
isLoading = false,
|
||||||
|
error = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Resource.Error -> {
|
||||||
|
state.copy(
|
||||||
|
locations = null,
|
||||||
|
isLoading = false,
|
||||||
|
error = result.message
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,11 +5,15 @@
|
||||||
<string name="tab_settings">Settings</string>
|
<string name="tab_settings">Settings</string>
|
||||||
|
|
||||||
<string name="action_back">Back</string>
|
<string name="action_back">Back</string>
|
||||||
|
<string name="action_apply">Apply</string>
|
||||||
<string name="action_confirm">Confirm</string>
|
<string name="action_confirm">Confirm</string>
|
||||||
<string name="action_open_about">About</string>
|
<string name="action_open_about">About</string>
|
||||||
|
<string name="action_search">Search</string>
|
||||||
<string name="action_reload">Reload</string>
|
<string name="action_reload">Reload</string>
|
||||||
<string name="action_try_again">Try Again</string>
|
<string name="action_try_again">Try Again</string>
|
||||||
|
|
||||||
|
<string name="selected">Selected</string>
|
||||||
|
|
||||||
<string name="appearance_theme">Theme</string>
|
<string name="appearance_theme">Theme</string>
|
||||||
<string name="appearance_monet">Dynamic Theme</string>
|
<string name="appearance_monet">Dynamic Theme</string>
|
||||||
<string name="appearance_monet_description">Available on Android 12+</string>
|
<string name="appearance_monet_description">Available on Android 12+</string>
|
||||||
|
@ -17,6 +21,11 @@
|
||||||
<string name="settings_appearance">Appearance</string>
|
<string name="settings_appearance">Appearance</string>
|
||||||
<string name="settings_appearance_description">Theme, code style</string>
|
<string name="settings_appearance_description">Theme, code style</string>
|
||||||
|
|
||||||
|
<string name="location">Location</string>
|
||||||
|
<string name="location_string">%1$s, %2$s, %3$s</string>
|
||||||
|
<string name="location_auto_pick">Auto-pick location</string>
|
||||||
|
<string name="location_choose">Choose a Location</string>
|
||||||
|
|
||||||
<string name="theme_system">System</string>
|
<string name="theme_system">System</string>
|
||||||
<string name="theme_light">Light</string>
|
<string name="theme_light">Light</string>
|
||||||
<string name="theme_dark">Dark</string>
|
<string name="theme_dark">Dark</string>
|
||||||
|
|
Reference in a new issue