Start adding support for multiple locations
This commit is contained in:
parent
b8ab0605e8
commit
b32701b138
35 changed files with 377 additions and 231 deletions
|
@ -2,6 +2,7 @@ plugins {
|
|||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("kotlin-kapt")
|
||||
kotlin("plugin.serialization") version "1.8.10"
|
||||
}
|
||||
|
||||
android {
|
||||
|
@ -72,7 +73,7 @@ dependencies {
|
|||
implementation("androidx.compose.material3:material3:1.1.0-rc01")
|
||||
implementation("androidx.activity:activity-compose:1.7.1")
|
||||
implementation("androidx.core:core-ktx:1.10.0")
|
||||
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
||||
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3")
|
||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3")
|
||||
|
||||
|
@ -110,7 +111,7 @@ dependencies {
|
|||
// Retrofit
|
||||
val retrofitVersion = "2.9.0"
|
||||
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
|
||||
implementation("com.squareup.retrofit2:converter-moshi:$retrofitVersion")
|
||||
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")
|
||||
|
||||
// Accompanist
|
||||
val accompanistVersion = "0.30.0"
|
||||
|
|
|
@ -5,13 +5,15 @@ import android.net.ConnectivityManager
|
|||
import android.net.NetworkCapabilities
|
||||
import com.henryhiles.qweather.domain.remote.GeocodingApi
|
||||
import com.henryhiles.qweather.domain.remote.WeatherApi
|
||||
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.Cache
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.OkHttpClient.Builder
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.module
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import retrofit2.create
|
||||
|
||||
private fun isNetworkAvailable(context: Context): Boolean {
|
||||
|
@ -29,6 +31,9 @@ private fun isNetworkAvailable(context: Context): Boolean {
|
|||
}
|
||||
}
|
||||
|
||||
private val contentType = "application/json".toMediaType()
|
||||
private val json = Json { ignoreUnknownKeys = true }
|
||||
|
||||
val appModule = module {
|
||||
fun provideWeatherApi(context: Context): WeatherApi {
|
||||
val cacheControlInterceptor = Interceptor { chain ->
|
||||
|
@ -56,7 +61,7 @@ val appModule = module {
|
|||
return Retrofit.Builder()
|
||||
.baseUrl("https://api.open-meteo.com")
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(MoshiConverterFactory.create())
|
||||
.addConverterFactory(json.asConverterFactory(contentType))
|
||||
.build()
|
||||
.create()
|
||||
}
|
||||
|
@ -64,7 +69,7 @@ val appModule = module {
|
|||
fun provideGeocodingApi(): GeocodingApi {
|
||||
return Retrofit.Builder()
|
||||
.baseUrl("https://geocoding-api.open-meteo.com")
|
||||
.addConverterFactory(MoshiConverterFactory.create())
|
||||
.addConverterFactory(json.asConverterFactory(contentType))
|
||||
.build()
|
||||
.create()
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package com.henryhiles.qweather.domain.geocoding
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GeocodingData(
|
||||
val location: String,
|
||||
val longitude: Float,
|
||||
val latitude: Float
|
||||
)
|
|
@ -41,9 +41,7 @@ abstract class BasePreferenceManager(
|
|||
getter: (key: String, defaultValue: T) -> T,
|
||||
private val setter: (key: String, newValue: T) -> Unit
|
||||
) {
|
||||
@Suppress("RedundantSetter")
|
||||
var value by mutableStateOf(getter(key, defaultValue))
|
||||
private set
|
||||
private var value by mutableStateOf(getter(key, defaultValue))
|
||||
|
||||
operator fun getValue(thisRef: Any?, property: KProperty<*>) = value
|
||||
operator fun setValue(thisRef: Any?, property: KProperty<*>, newValue: T) {
|
||||
|
@ -102,7 +100,6 @@ abstract class BasePreferenceManager(
|
|||
setter = ::putColor
|
||||
)
|
||||
|
||||
|
||||
protected inline fun <reified E : Enum<E>> enumPreference(
|
||||
key: String,
|
||||
defaultValue: E
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.henryhiles.qweather.domain.mappers
|
||||
|
||||
import com.henryhiles.qweather.domain.remote.GeocodingDto
|
||||
import com.henryhiles.qweather.domain.geocoding.GeocodingData
|
||||
|
||||
fun GeocodingDto.toGeocodingData(): List<GeocodingData> {
|
||||
return results.map {
|
||||
GeocodingData(
|
||||
location = "${it.city}, ${it.admin}, ${it.country}",
|
||||
longitude = it.longitude,
|
||||
latitude = it.latitude,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@ import java.time.LocalDateTime
|
|||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
fun HourlyWeatherDataDto.toHourlyWeatherDataMap(): List<HourlyWeatherData> {
|
||||
fun HourlyWeatherDataDto.toHourlyWeatherData(): List<HourlyWeatherData> {
|
||||
return time.subList(0, 24).mapIndexed { index, time ->
|
||||
HourlyWeatherData(
|
||||
time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME),
|
||||
|
@ -20,12 +20,12 @@ fun HourlyWeatherDataDto.toHourlyWeatherDataMap(): List<HourlyWeatherData> {
|
|||
apparentTemperature = apparentTemperature[index].roundToInt(),
|
||||
windSpeed = windSpeed[index].roundToInt(),
|
||||
precipitationProbability = precipitationProbability.getOrNull(index),
|
||||
weatherType = WeatherType.fromWMO(weatherCode[index])
|
||||
weatherType = WeatherType.fromWMO(weatherCode[index]),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun DailyWeatherDataDto.toDailyWeatherDataMap(): List<DailyWeatherData> {
|
||||
fun DailyWeatherDataDto.toDailyWeatherData(): List<DailyWeatherData> {
|
||||
return date.mapIndexed { index, date ->
|
||||
DailyWeatherData(
|
||||
date = LocalDate.parse(date, DateTimeFormatter.ISO_DATE),
|
||||
|
@ -41,13 +41,16 @@ fun DailyWeatherDataDto.toDailyWeatherDataMap(): List<DailyWeatherData> {
|
|||
}
|
||||
|
||||
fun WeatherDto.toHourlyWeatherInfo(): HourlyWeatherInfo {
|
||||
val weatherDataMap = hourlyWeatherData.toHourlyWeatherDataMap()
|
||||
val weatherDataMap = hourlyWeatherData.toHourlyWeatherData()
|
||||
val now = LocalDateTime.now()
|
||||
val currentWeatherData = weatherDataMap.find {
|
||||
it.time.hour == now.hour
|
||||
}
|
||||
return HourlyWeatherInfo(
|
||||
weatherData = weatherDataMap,
|
||||
currentWeatherData = currentWeatherData
|
||||
currentWeatherData = currentWeatherData,
|
||||
highTemperature = weatherDataMap.maxBy { it.temperature }.temperature,
|
||||
lowTemperature = weatherDataMap.minBy { it.temperature }.temperature,
|
||||
precipitationProbability = weatherDataMap.maxBy { it.precipitationProbability ?: 0}.precipitationProbability
|
||||
)
|
||||
}
|
|
@ -1,24 +1,26 @@
|
|||
package com.henryhiles.qweather.domain.remote
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DailyWeatherDataDto(
|
||||
@field:Json(name = "time")
|
||||
@SerialName("time")
|
||||
val date: List<String>,
|
||||
@field:Json(name = "weathercode")
|
||||
@SerialName("weathercode")
|
||||
val weatherCode: List<Int>,
|
||||
@field:Json(name = "precipitation_probability_max")
|
||||
val precipitationProbabilityMax: List<Int>,
|
||||
@field:Json(name = "precipitation_sum")
|
||||
@SerialName("precipitation_probability_max")
|
||||
val precipitationProbabilityMax: List<Int?>,
|
||||
@SerialName("precipitation_sum")
|
||||
val precipitationSum: List<Float>,
|
||||
@field:Json(name = "windspeed_10m_max")
|
||||
@SerialName("windspeed_10m_max")
|
||||
val windSpeedMax: List<Float>,
|
||||
@field:Json(name = "temperature_2m_max")
|
||||
@SerialName("temperature_2m_max")
|
||||
val temperatureMax: List<Float>,
|
||||
@field:Json(name = "temperature_2m_min")
|
||||
@SerialName("temperature_2m_min")
|
||||
val temperatureMin: List<Float>,
|
||||
@field:Json(name = "apparent_temperature_max")
|
||||
@SerialName("apparent_temperature_max")
|
||||
val apparentTemperatureMax: List<Float>,
|
||||
@field:Json(name = "apparent_temperature_min")
|
||||
@SerialName("apparent_temperature_min")
|
||||
val apparentTemperatureMin: List<Float>
|
||||
)
|
|
@ -4,8 +4,9 @@ import retrofit2.http.GET
|
|||
import retrofit2.http.Query
|
||||
|
||||
interface GeocodingApi {
|
||||
@GET("v1/search?count=10")
|
||||
@GET("v1/search")
|
||||
suspend fun getGeocodingData(
|
||||
@Query("name") location: String,
|
||||
@Query("count") count: Int = 10
|
||||
): GeocodingDto
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package com.henryhiles.qweather.domain.remote
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GeocodingDto(
|
||||
@field:Json(name = "results")
|
||||
val results: List<GeocodingLocationDto>
|
||||
val results: List<GeocodingLocationDto> = listOf()
|
||||
)
|
|
@ -1,16 +1,15 @@
|
|||
package com.henryhiles.qweather.domain.remote
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class GeocodingLocationDto(
|
||||
@field:Json(name = "name")
|
||||
@SerialName("name")
|
||||
val city: String,
|
||||
@field:Json(name = "country")
|
||||
val country: String,
|
||||
@field:Json(name = "admin1")
|
||||
@SerialName("admin1")
|
||||
val admin: String,
|
||||
@field:Json(name = "latitude")
|
||||
val latitude: Float,
|
||||
@field:Json(name = "longitude")
|
||||
val longitude: Float
|
||||
)
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
package com.henryhiles.qweather.domain.remote
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class HourlyWeatherDataDto(
|
||||
@field:Json(name = "time")
|
||||
val time: List<String>,
|
||||
@field:Json(name = "temperature_2m")
|
||||
@SerialName("temperature_2m")
|
||||
val temperature: List<Float>,
|
||||
@field:Json(name = "apparent_temperature")
|
||||
@SerialName("apparent_temperature")
|
||||
val apparentTemperature: List<Float>,
|
||||
@field:Json(name = "weathercode")
|
||||
@SerialName("weathercode")
|
||||
val weatherCode: List<Int>,
|
||||
@field:Json(name = "precipitation_probability")
|
||||
val precipitationProbability: List<Int>,
|
||||
@field:Json(name = "windspeed_10m")
|
||||
@SerialName("precipitation_probability")
|
||||
val precipitationProbability: List<Int?>,
|
||||
@SerialName("windspeed_10m")
|
||||
val windSpeed: List<Float>,
|
||||
)
|
|
@ -1,11 +1,13 @@
|
|||
package com.henryhiles.qweather.domain.remote
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class WeatherDto(
|
||||
@field:Json(name = "hourly")
|
||||
@SerialName("hourly")
|
||||
val hourlyWeatherData: HourlyWeatherDataDto,
|
||||
|
||||
@field:Json(name = "daily")
|
||||
@SerialName("daily")
|
||||
val dailyWeatherData: DailyWeatherDataDto
|
||||
)
|
|
@ -1,14 +1,15 @@
|
|||
package com.henryhiles.qweather.domain.repository
|
||||
|
||||
import com.henryhiles.qweather.domain.mappers.toGeocodingData
|
||||
import com.henryhiles.qweather.domain.remote.GeocodingApi
|
||||
import com.henryhiles.qweather.domain.remote.GeocodingLocationDto
|
||||
import com.henryhiles.qweather.domain.util.Resource
|
||||
import com.henryhiles.qweather.domain.geocoding.GeocodingData
|
||||
|
||||
class GeocodingRepository(private val api: GeocodingApi) {
|
||||
suspend fun getGeocodingData(location: String): Resource<List<GeocodingLocationDto>> {
|
||||
suspend fun getGeocodingData(location: String): Resource<List<GeocodingData>> {
|
||||
return try {
|
||||
Resource.Success(
|
||||
data = api.getGeocodingData(location = location).results
|
||||
data = api.getGeocodingData(location = location).toGeocodingData()
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package com.henryhiles.qweather.domain.repository
|
||||
|
||||
import com.henryhiles.qweather.domain.mappers.toDailyWeatherDataMap
|
||||
import com.henryhiles.qweather.domain.mappers.toDailyWeatherData
|
||||
import com.henryhiles.qweather.domain.mappers.toHourlyWeatherInfo
|
||||
import com.henryhiles.qweather.domain.remote.WeatherApi
|
||||
import com.henryhiles.qweather.domain.util.Resource
|
||||
|
@ -43,7 +43,7 @@ class WeatherRepository(private val api: WeatherApi) {
|
|||
) else api.getWeatherDataWithoutCache(
|
||||
lat = lat,
|
||||
long = long
|
||||
)).dailyWeatherData.toDailyWeatherDataMap()
|
||||
)).dailyWeatherData.toDailyWeatherData()
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
|
|
|
@ -2,5 +2,8 @@ package com.henryhiles.qweather.domain.weather
|
|||
|
||||
data class HourlyWeatherInfo(
|
||||
val weatherData: List<HourlyWeatherData>,
|
||||
val currentWeatherData: HourlyWeatherData?
|
||||
val currentWeatherData: HourlyWeatherData?,
|
||||
val highTemperature: Int,
|
||||
val lowTemperature: Int,
|
||||
val precipitationProbability: Int?
|
||||
)
|
|
@ -7,7 +7,6 @@ import androidx.compose.animation.ExperimentalAnimationApi
|
|||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
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.transitions.SlideTransition
|
||||
|
@ -32,11 +31,10 @@ class QWeatherActivity : ComponentActivity() {
|
|||
Theme.LIGHT -> false
|
||||
Theme.DARK -> true
|
||||
}
|
||||
val isLocationSet = location.location != ""
|
||||
val isLocationSet = location.getLocations().isNotEmpty()
|
||||
|
||||
WeatherAppTheme(darkTheme = isDark, monet = prefs.monet) {
|
||||
Surface(modifier = Modifier.fillMaxSize()) {
|
||||
Text(text = location.location)
|
||||
Navigator(
|
||||
screen = if (isLocationSet) MainScreen() else LocationPickerScreen(),
|
||||
onBackPressed = {
|
||||
|
|
|
@ -10,7 +10,6 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
|
||||
@Composable
|
||||
inline fun <reified E : Enum<E>> EnumRadioController(
|
||||
|
@ -19,7 +18,6 @@ inline fun <reified E : Enum<E>> EnumRadioController(
|
|||
crossinline onChoiceSelected: (E) -> Unit
|
||||
) {
|
||||
var choice by remember { mutableStateOf(default) }
|
||||
val ctx = LocalContext.current
|
||||
|
||||
Column {
|
||||
enumValues<E>().forEach {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package com.henryhiles.qweather.presentation.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.Divider
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun VerticalDivider(modifier: Modifier = Modifier) {
|
||||
Divider(
|
||||
modifier = modifier
|
||||
.fillMaxHeight()
|
||||
.width(1.dp)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package com.henryhiles.qweather.presentation.components.location
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import com.henryhiles.qweather.R
|
||||
import com.henryhiles.qweather.presentation.screen.LocationPickerScreen
|
||||
import com.henryhiles.qweather.presentation.screenmodel.LocationPreferenceManager
|
||||
import org.koin.androidx.compose.get
|
||||
|
||||
@Composable
|
||||
fun LocationsDrawer(drawerState: DrawerState, children: @Composable () -> Unit) {
|
||||
val location: LocationPreferenceManager = get()
|
||||
val navigator = LocalNavigator.current?.parent
|
||||
|
||||
ModalNavigationDrawer(drawerContent = {
|
||||
ModalDrawerSheet {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
val locations = location.getLocations()
|
||||
|
||||
Text(
|
||||
text = stringResource(id = R.string.locations),
|
||||
style = MaterialTheme.typography.headlineSmall
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
locations.forEachIndexed { index, data ->
|
||||
NavigationDrawerItem(
|
||||
label = { Text(text = data.location) },
|
||||
selected = index == location.selectedLocation,
|
||||
onClick = { location.selectedLocation = index },
|
||||
badge = {
|
||||
IconButton(onClick = { location.removeLocation(data) }) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringResource(
|
||||
id = R.string.action_delete
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
NavigationDrawerItem(
|
||||
label = { Text(text = stringResource(id = R.string.location_add)) },
|
||||
icon = {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = stringResource(id = R.string.location_add)
|
||||
)
|
||||
},
|
||||
selected = true,
|
||||
onClick = { navigator?.push(LocationPickerScreen()) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}, drawerState = drawerState) {
|
||||
children()
|
||||
}
|
||||
}
|
|
@ -7,13 +7,14 @@ import androidx.compose.runtime.Composable
|
|||
@Composable
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
fun SmallToolbar(
|
||||
backButton: Boolean = true,
|
||||
title: @Composable () -> Unit,
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
backButton: Boolean = true
|
||||
navigationIcon: @Composable () -> Unit = { if (backButton) BackButton() },
|
||||
) {
|
||||
TopAppBar(
|
||||
title = title,
|
||||
navigationIcon = { if (backButton) BackButton() },
|
||||
navigationIcon = navigationIcon,
|
||||
actions = actions,
|
||||
)
|
||||
}
|
|
@ -6,19 +6,18 @@ import androidx.compose.foundation.basicMarquee
|
|||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Thermostat
|
||||
import androidx.compose.material.icons.outlined.Thermostat
|
||||
import androidx.compose.material.icons.outlined.WaterDrop
|
||||
import androidx.compose.material.icons.outlined.WindPower
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.henryhiles.qweather.R
|
||||
import com.henryhiles.qweather.domain.weather.HourlyWeatherData
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
|
@ -31,7 +30,7 @@ fun WeatherCard(hour: HourlyWeatherData?, location: String, modifier: Modifier =
|
|||
}
|
||||
Card(
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
modifier = modifier.padding(16.dp)
|
||||
modifier = modifier
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
|
@ -58,7 +57,7 @@ fun WeatherCard(hour: HourlyWeatherData?, location: String, modifier: Modifier =
|
|||
Image(
|
||||
painter = painterResource(id = it.weatherType.iconRes),
|
||||
contentDescription = "Image of ${it.weatherType.weatherDesc}",
|
||||
modifier = Modifier.width(200.dp)
|
||||
modifier = Modifier.height(152.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text(text = "${it.temperature}°C", fontSize = 50.sp)
|
||||
|
@ -72,19 +71,19 @@ fun WeatherCard(hour: HourlyWeatherData?, location: String, modifier: Modifier =
|
|||
WeatherDataDisplay(
|
||||
value = it.apparentTemperature,
|
||||
unit = "°C",
|
||||
icon = Icons.Default.Thermostat,
|
||||
icon = Icons.Outlined.Thermostat,
|
||||
description = "Feels like",
|
||||
)
|
||||
WeatherDataDisplay(
|
||||
value = it.precipitationProbability,
|
||||
unit = "%",
|
||||
icon = ImageVector.vectorResource(id = R.drawable.ic_drop),
|
||||
icon = Icons.Outlined.WaterDrop,
|
||||
description = "Chance of precipitation"
|
||||
)
|
||||
WeatherDataDisplay(
|
||||
value = it.windSpeed,
|
||||
unit = "km/h",
|
||||
icon = ImageVector.vectorResource(id = R.drawable.ic_wind),
|
||||
icon = Icons.Outlined.WindPower,
|
||||
description = "Wind Speed",
|
||||
)
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.Water
|
||||
import androidx.compose.material.icons.outlined.WaterDrop
|
||||
import androidx.compose.material.icons.outlined.WindPower
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
|
@ -15,11 +16,8 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.henryhiles.qweather.R
|
||||
import com.henryhiles.qweather.domain.weather.DailyWeatherData
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
|
@ -71,7 +69,7 @@ fun WeatherDay(dailyWeatherData: DailyWeatherData, expanded: Boolean, onExpand:
|
|||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp, 0.dp, 16.dp, 16.dp),
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 16.dp),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
WeatherDataDisplay(
|
||||
|
@ -80,7 +78,6 @@ fun WeatherDay(dailyWeatherData: DailyWeatherData, expanded: Boolean, onExpand:
|
|||
icon = Icons.Outlined.WaterDrop,
|
||||
description = "Chance of rain"
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
WeatherDataDisplay(
|
||||
value = dailyWeatherData.windSpeedMax,
|
||||
|
@ -88,12 +85,11 @@ fun WeatherDay(dailyWeatherData: DailyWeatherData, expanded: Boolean, onExpand:
|
|||
icon = Icons.Outlined.Water,
|
||||
description = "Precipitation Amount"
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
WeatherDataDisplay(
|
||||
value = dailyWeatherData.windSpeedMax,
|
||||
unit = "km/h",
|
||||
icon = ImageVector.vectorResource(id = R.drawable.ic_wind),
|
||||
icon = Icons.Outlined.WindPower,
|
||||
description = "Wind Speed"
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,14 +1,12 @@
|
|||
package com.henryhiles.qweather.presentation.components.weather
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherState
|
||||
import java.time.LocalDateTime
|
||||
|
||||
|
@ -19,24 +17,15 @@ fun WeatherForecast(
|
|||
onChangeSelected: (Int) -> Unit
|
||||
) {
|
||||
state.hourlyWeatherInfo?.weatherData?.let {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
Text(text = "Today", fontSize = 20.sp)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
val rowState = rememberLazyListState(LocalDateTime.now().hour)
|
||||
|
||||
LazyRow(state = rowState) {
|
||||
itemsIndexed(it) { index, data ->
|
||||
WeatherHour(
|
||||
data = data,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp),
|
||||
onChangeSelected = { onChangeSelected(index) }
|
||||
)
|
||||
}
|
||||
val rowState = rememberLazyListState(LocalDateTime.now().hour)
|
||||
LazyRow(state = rowState, modifier = modifier) {
|
||||
itemsIndexed(it) { index, data ->
|
||||
WeatherHour(
|
||||
data = data,
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp),
|
||||
onChangeSelected = { onChangeSelected(index) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,13 +37,11 @@ fun WeatherHour(
|
|||
horizontalAlignment = CenterHorizontally
|
||||
) {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
package com.henryhiles.qweather.presentation.components.weather
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.henryhiles.qweather.R
|
||||
import com.henryhiles.qweather.presentation.components.VerticalDivider
|
||||
import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherState
|
||||
|
||||
@Composable
|
||||
fun WeatherToday(state: HourlyWeatherState) {
|
||||
state.hourlyWeatherInfo?.let {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.height(24.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = stringResource(R.string.weather_high, it.highTemperature),
|
||||
)
|
||||
VerticalDivider(modifier = Modifier.padding(horizontal = 8.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.weather_low, it.lowTemperature)
|
||||
)
|
||||
VerticalDivider(modifier = Modifier.padding(horizontal = 8.dp))
|
||||
Text(
|
||||
text = it.precipitationProbability?.let {
|
||||
stringResource(
|
||||
id = R.string.weather_precipitation,
|
||||
it
|
||||
)
|
||||
} ?: stringResource(
|
||||
id = R.string.unknown
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,7 +15,6 @@ 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
|
||||
|
@ -24,6 +23,7 @@ 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.domain.geocoding.GeocodingData
|
||||
import com.henryhiles.qweather.presentation.components.navigation.SmallToolbar
|
||||
import com.henryhiles.qweather.presentation.screenmodel.LocationPickerScreenModel
|
||||
|
||||
|
@ -32,24 +32,20 @@ class LocationPickerScreen : Screen {
|
|||
@Composable
|
||||
override fun Content() {
|
||||
val screenModel: LocationPickerScreenModel = getScreenModel()
|
||||
var latitude by remember { mutableStateOf(screenModel.prefs.latitude) }
|
||||
var longitude by remember { mutableStateOf(screenModel.prefs.longitude) }
|
||||
var location by remember { mutableStateOf(screenModel.prefs.location) }
|
||||
var location by remember {
|
||||
mutableStateOf<GeocodingData?>(null)
|
||||
}
|
||||
var locationSearch by remember { mutableStateOf("") }
|
||||
var isAboutOpen by remember { mutableStateOf(false) }
|
||||
val navigator = LocalNavigator.current
|
||||
val context = LocalContext.current
|
||||
|
||||
Scaffold(modifier = Modifier.imePadding(),
|
||||
floatingActionButton = {
|
||||
FloatingActionButton(onClick = {
|
||||
if (location == "") isAboutOpen = true
|
||||
else {
|
||||
screenModel.prefs.location = location
|
||||
screenModel.prefs.latitude = latitude
|
||||
screenModel.prefs.longitude = longitude
|
||||
location?.let {
|
||||
screenModel.prefs.addLocation(it)
|
||||
navigator?.push(MainScreen())
|
||||
}
|
||||
} ?: kotlin.run { isAboutOpen = true }
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
|
@ -57,32 +53,32 @@ class LocationPickerScreen : Screen {
|
|||
)
|
||||
}
|
||||
}) {
|
||||
screenModel.state.error?.let {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
confirmButton = {},
|
||||
title = { Text(text = stringResource(id = R.string.error)) },
|
||||
text = {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = it,
|
||||
Column {
|
||||
SmallToolbar(
|
||||
title = { Text(text = stringResource(id = R.string.location_choose)) },
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = { isAboutOpen = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Info,
|
||||
contentDescription = stringResource(id = R.string.help_screen)
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
} ?: kotlin.run {
|
||||
Column {
|
||||
SmallToolbar(
|
||||
title = { Text(text = stringResource(id = R.string.location_choose)) },
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = { isAboutOpen = true }) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Info,
|
||||
contentDescription = stringResource(id = R.string.help_screen)
|
||||
})
|
||||
screenModel.state.error?.let {
|
||||
AlertDialog(
|
||||
onDismissRequest = {},
|
||||
confirmButton = {},
|
||||
title = { Text(text = stringResource(id = R.string.error)) },
|
||||
text = {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
text = it,
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
} ?: kotlin.run {
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
if (isAboutOpen) AlertDialog(
|
||||
title = { Text(text = stringResource(id = R.string.location_choose)) },
|
||||
|
@ -134,27 +130,16 @@ class LocationPickerScreen : Screen {
|
|||
) 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
|
||||
)
|
||||
)
|
||||
}
|
||||
val selected = it == location
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Card(modifier = Modifier.clickable {
|
||||
location = locationText
|
||||
longitude = it.longitude
|
||||
latitude = it.latitude
|
||||
}) {
|
||||
Card(modifier = Modifier.clickable { location = it }) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
if (location == locationText) {
|
||||
if (selected) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Check,
|
||||
contentDescription = stringResource(
|
||||
|
@ -164,7 +149,7 @@ class LocationPickerScreen : Screen {
|
|||
)
|
||||
Spacer(modifier = Modifier.width(8.dp))
|
||||
}
|
||||
Text(text = locationText)
|
||||
Text(text = it.location)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +1,67 @@
|
|||
package com.henryhiles.qweather.presentation.screen
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Menu
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import cafe.adriel.voyager.core.screen.Screen
|
||||
import cafe.adriel.voyager.navigator.CurrentScreen
|
||||
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||
import cafe.adriel.voyager.navigator.tab.TabNavigator
|
||||
import com.henryhiles.qweather.R
|
||||
import com.henryhiles.qweather.domain.util.NavigationTab
|
||||
import com.henryhiles.qweather.presentation.components.location.LocationsDrawer
|
||||
import com.henryhiles.qweather.presentation.components.navigation.BottomBar
|
||||
import com.henryhiles.qweather.presentation.components.navigation.SmallToolbar
|
||||
import com.henryhiles.qweather.presentation.screenmodel.LocationPreferenceManager
|
||||
import com.henryhiles.qweather.presentation.tabs.TodayTab
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.get
|
||||
|
||||
class MainScreen : Screen {
|
||||
@Composable
|
||||
override fun Content() {
|
||||
val drawerState =
|
||||
rememberDrawerState(initialValue = DrawerValue.Closed)
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
TabNavigator(tab = TodayTab) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SmallToolbar(
|
||||
title = { Text(text = "QWeather") },
|
||||
actions = {
|
||||
(it.current as? NavigationTab)?.Actions()
|
||||
LocationsDrawer(drawerState = drawerState) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
SmallToolbar(
|
||||
title = { Text(text = stringResource(R.string.app_name)) },
|
||||
actions = {
|
||||
(it.current as? NavigationTab)?.Actions()
|
||||
}
|
||||
) {
|
||||
IconButton(onClick = {
|
||||
coroutineScope.launch {
|
||||
with(drawerState) { if (isOpen) close() else open() }
|
||||
}
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Menu,
|
||||
contentDescription = stringResource(id = R.string.location_picker_open)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
BottomBar(navigator = it)
|
||||
}
|
||||
) { padding ->
|
||||
Box(modifier = Modifier.padding(padding)) {
|
||||
CurrentScreen()
|
||||
},
|
||||
bottomBar = {
|
||||
BottomBar(navigator = it)
|
||||
}
|
||||
) { padding ->
|
||||
Box(modifier = Modifier.padding(padding)) {
|
||||
CurrentScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,20 +19,20 @@ data class DailyWeatherState(
|
|||
|
||||
class DailyWeatherScreenModel(
|
||||
private val repository: WeatherRepository,
|
||||
private val location: LocationPreferenceManager
|
||||
locationPreferenceManager: LocationPreferenceManager
|
||||
) : ScreenModel {
|
||||
var state by mutableStateOf(DailyWeatherState())
|
||||
private set
|
||||
val location = locationPreferenceManager.getSelectedLocation()
|
||||
|
||||
fun loadWeatherInfo(cache: Boolean = true) {
|
||||
coroutineScope.launch {
|
||||
state = state.copy(isLoading = true, error = null)
|
||||
state = when (val result =
|
||||
repository.getDailyWeatherData(
|
||||
lat = location.latitude,
|
||||
long = location.longitude,
|
||||
cache = cache
|
||||
)) {
|
||||
state = when (val result = repository.getDailyWeatherData(
|
||||
lat = location.latitude,
|
||||
long = location.longitude,
|
||||
cache = cache
|
||||
)) {
|
||||
is Resource.Success -> {
|
||||
state.copy(
|
||||
dailyWeatherData = result.data,
|
||||
|
|
|
@ -19,11 +19,13 @@ data class HourlyWeatherState(
|
|||
|
||||
class HourlyWeatherScreenModel(
|
||||
private val repository: WeatherRepository,
|
||||
val location: LocationPreferenceManager,
|
||||
locationPreferenceManager: LocationPreferenceManager,
|
||||
) : ScreenModel {
|
||||
var state by mutableStateOf(HourlyWeatherState())
|
||||
private set
|
||||
|
||||
val location = locationPreferenceManager.getSelectedLocation()
|
||||
|
||||
fun loadWeatherInfo(cache: Boolean = true) {
|
||||
coroutineScope.launch {
|
||||
state = state.copy(isLoading = true, error = null, selected = null)
|
||||
|
|
|
@ -6,23 +6,46 @@ 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.geocoding.GeocodingData
|
||||
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
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
data class LocationPickerState(
|
||||
val locations: List<GeocodingLocationDto>? = null,
|
||||
val locations: List<GeocodingData>? = 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")
|
||||
private var locations by stringPreference(
|
||||
"locations",
|
||||
Json.encodeToString(value = listOf<GeocodingData>())
|
||||
)
|
||||
var selectedLocation by intPreference("selected_location", 0)
|
||||
|
||||
fun getSelectedLocation(): GeocodingData {
|
||||
return getLocations()[selectedLocation]
|
||||
}
|
||||
|
||||
fun getLocations(): List<GeocodingData> {
|
||||
return Json.decodeFromString(string = locations)
|
||||
}
|
||||
|
||||
fun addLocation(location: GeocodingData) {
|
||||
val currentLocations = getLocations()
|
||||
locations = Json.encodeToString(value = currentLocations + location)
|
||||
}
|
||||
|
||||
fun removeLocation(location: GeocodingData) {
|
||||
val currentLocations = getLocations()
|
||||
locations = Json.encodeToString(value = currentLocations - location)
|
||||
}
|
||||
}
|
||||
|
||||
class LocationPickerScreenModel(
|
||||
|
|
|
@ -20,6 +20,7 @@ import com.henryhiles.qweather.R
|
|||
import com.henryhiles.qweather.domain.util.NavigationTab
|
||||
import com.henryhiles.qweather.presentation.components.weather.WeatherCard
|
||||
import com.henryhiles.qweather.presentation.components.weather.WeatherForecast
|
||||
import com.henryhiles.qweather.presentation.components.weather.WeatherToday
|
||||
import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherScreenModel
|
||||
|
||||
object TodayTab : NavigationTab {
|
||||
|
@ -77,6 +78,7 @@ object TodayTab : NavigationTab {
|
|||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
WeatherCard(
|
||||
hour = weatherViewModel.state.selected?.let {
|
||||
|
@ -84,7 +86,7 @@ object TodayTab : NavigationTab {
|
|||
} ?: weatherViewModel.state.hourlyWeatherInfo?.currentWeatherData,
|
||||
location = weatherViewModel.location.location
|
||||
)
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
WeatherToday(state = weatherViewModel.state)
|
||||
WeatherForecast(
|
||||
state = weatherViewModel.state
|
||||
) { weatherViewModel.setSelected(it) }
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="149.86dp"
|
||||
android:height="249.77dp"
|
||||
android:viewportWidth="149.86"
|
||||
android:viewportHeight="249.77">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M74.93,249.77c41.32,0 74.93,-28.71 74.93,-64 0,-34 -75.48,-178 -78.7,-184.1a3.12,3.12 0,0 0,-5.62 0.2C62.87,8 0,151.88 0,185.77 0,221.06 33.61,249.77 74.93,249.77ZM68.66,10.38c14.36,27.76 75,146.61 75,175.39 0,31.84 -30.82,57.75 -68.69,57.75S6.24,217.61 6.24,185.77C6.24,157 56.53,38.52 68.66,10.38ZM13.11,190.91a3.12,3.12 0,0 1,2.62 -3.55A3.15,3.15 0,0 1,19.28 190c2.64,17.47 15.64,31.71 34.78,38.09a3.12,3.12 0,1 1,-2 5.93C31,227 16.06,210.46 13.11,190.91Z"/>
|
||||
</vector>
|
|
@ -1,33 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="212.3dp"
|
||||
android:height="62.44dp"
|
||||
android:viewportWidth="212.3"
|
||||
android:viewportHeight="62.44">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M209.18,6.24H193.57a3.12,3.12 0,0 1,0 -6.24h15.61a3.12,3.12 0,1 1,0 6.24Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M181.08,6.24H178A3.12,3.12 0,0 1,178 0h3.12a3.12,3.12 0,0 1,0 6.24Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M165.47,6.24H3.12A3.12,3.12 0,0 1,3.12 0H165.47a3.12,3.12 0,1 1,0 6.24Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M181.08,43.71H31.22a3.13,3.13 0,0 1,0 -6.25H181.08a3.13,3.13 0,0 1,0 6.25Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M165.47,62.44H106.15a3.12,3.12 0,0 1,0 -6.24h59.32a3.12,3.12 0,1 1,0 6.24Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M93.66,62.44H90.54a3.12,3.12 0,0 1,0 -6.24h3.12a3.12,3.12 0,1 1,0 6.24Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M78.05,62.44H46.83a3.12,3.12 0,1 1,0 -6.24H78.05a3.12,3.12 0,1 1,0 6.24Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M196.69,25h-153a3.13,3.13 0,0 1,0 -6.25h153a3.13,3.13 0,0 1,0 6.25Z"/>
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M31.22,25H15.61a3.13,3.13 0,0 1,0 -6.25H31.22a3.13,3.13 0,0 1,0 6.25Z"/>
|
||||
</vector>
|
|
@ -1,9 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="277.86dp"
|
||||
android:height="199.81dp"
|
||||
android:viewportWidth="277.86"
|
||||
android:viewportHeight="199.81">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M277.86,57.76c0,27.69 -26.67,48.39 -50.53,48.39L50,106.15a3.12,3.12 0,0 1,0 -6.24L227.33,99.91c20.48,0 44.29,-18.42 44.29,-42.15 0,-23.24 -17.06,-42.15 -38,-42.15 -13.64,0 -25.89,10.36 -32.79,27.72A3.12,3.12 0,1 1,195 41c7.87,-19.82 22.3,-31.65 38.59,-31.65C258,9.37 277.86,31.07 277.86,57.76ZM237.28,93.66a3,3 0,0 0,1.14 -0.22C252,88.06 260,80.06 265.17,66.68a3.12,3.12 0,0 0,-5.83 -2.24c-4.51,11.74 -11.23,18.46 -23.21,23.2a3.12,3.12 0,0 0,1.15 6ZM122.8,125.06a3.13,3.13 0,0 0,-0.87 -0.18L34.34,124.88a3.13,3.13 0,0 0,0 6.25h84.59c18.77,0 34,13.3 34,29.66 0,15.47 -14.56,32.78 -34,32.78 -13.79,0 -25.44,-10.15 -30.85,-20.22a3.13,3.13 0,0 0,-5.5 3c6.3,11.69 20,23.49 36.35,23.49 21.46,0 40.3,-18.23 40.3,-39C159.23,142.15 143.21,126.8 122.8,125.06ZM12.8,78.06a3.12,3.12 0,0 0,3.13 3.12h76.8a3,3 0,0 0,0.61 -0.12c18,-1.24 37.8,-16 37.8,-34.8s-18,-36.88 -37,-36.88c-12.83,0 -27.36,9.71 -33.08,22.1a3.12,3.12 0,0 0,5.67 2.62c4.7,-10.19 17,-18.48 27.41,-18.48 15.52,0 30.75,15.18 30.75,30.64 0,15.89 -18.6,28.68 -34,28.68h-75A3.13,3.13 0,0 0,12.79 78.05ZM108.64,6.18C120.73,8.69 135,23 137.43,35a3.13,3.13 0,0 0,3.06 2.5,3.52 3.52,0 0,0 0.63,-0.06 3.12,3.12 0,0 0,2.43 -3.68C140.65,19.42 124.3,3.06 109.91,0.06a3.13,3.13 0,1 0,-1.27 6.12ZM215.52,168.59c-7.34,0 -16,-6.1 -19.36,-13.6a3.12,3.12 0,1 0,-5.71 2.53c4.39,9.87 15.17,17.32 25.07,17.32 14.39,0 28,-14 28,-28.85s-15,-26.29 -28.57,-27.26a3.55,3.55 0,0 0,-0.47 -0.09L196.69,118.64a3.12,3.12 0,0 0,0 6.24h16.45c10.86,0 24.14,9.74 24.14,21.11S226.5,168.59 215.52,168.59ZM184.2,118.64h-3.12a3.12,3.12 0,0 0,0 6.24h3.12a3.12,3.12 0,0 0,0 -6.24ZM168.59,118.64h-15.3a3.12,3.12 0,1 0,0 6.24h15.3a3.12,3.12 0,0 0,0 -6.24ZM37.46,99.91L34.34,99.91a3.12,3.12 0,0 0,0 6.24h3.12a3.12,3.12 0,1 0,0 -6.24ZM25,103a3.12,3.12 0,0 0,-3.13 -3.12L3.12,99.88a3.12,3.12 0,0 0,0 6.24L21.85,106.12A3.13,3.13 0,0 0,25 103ZM43.73,137.34a3.13,3.13 0,0 0,0 6.25L78.05,143.59a3.13,3.13 0,0 0,0 -6.25Z"/>
|
||||
</vector>
|
|
@ -8,11 +8,12 @@
|
|||
<string name="action_apply">Apply</string>
|
||||
<string name="action_confirm">Confirm</string>
|
||||
<string name="action_open_about">About</string>
|
||||
<string name="action_delete">Delete</string>
|
||||
<string name="action_search">Search</string>
|
||||
<string name="action_reload">Reload</string>
|
||||
<string name="action_try_again">Try Again</string>
|
||||
|
||||
<string name="selected">Selected</string>x
|
||||
<string name="selected">Selected</string>
|
||||
|
||||
<string name="help_screen">How do I use this screen?</string>
|
||||
<string name="help_location_picker">Please search a location, then tap a result. Then tap the apply button in the bottom left corner.</string>
|
||||
|
@ -27,16 +28,21 @@
|
|||
<string name="settings_location_description">Location to fetch data from</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="locations">Locations</string>
|
||||
<string name="location_add">Add Location</string>
|
||||
<string name="location_picker_open">Open location picker</string>
|
||||
<string name="location_choose">Choose a Location</string>
|
||||
|
||||
<string name="theme_system">System</string>
|
||||
<string name="theme_light">Light</string>
|
||||
<string name="theme_dark">Dark</string>
|
||||
|
||||
<string name="weather_high">High: %1$d°C</string>
|
||||
<string name="weather_low">Low: %1$d°C</string>
|
||||
<string name="weather_precipitation">Precipitation: %1$d﹪</string>
|
||||
|
||||
<string name="unknown">Unknown</string>
|
||||
|
||||
<string name="error">An error occurred</string>
|
||||
<string name="error_location">Couldn\'t retrieve location. Make sure to grant permission and enable GPS.</string>
|
||||
<string name="error_location">"Couldn't retrieve location. Make sure to grant permission and enable GPS."</string>
|
||||
</resources>
|
Reference in a new issue