fix everything

This commit is contained in:
Henry Hiles 2023-04-17 21:11:24 -04:00
parent 376f28dc9d
commit 1f245aa337
11 changed files with 139 additions and 137 deletions

View file

@ -4,7 +4,7 @@ import retrofit2.http.GET
import retrofit2.http.Query import retrofit2.http.Query
interface GeocodingApi { interface GeocodingApi {
@GET("v1/search?count=4") @GET("v1/search?count=10")
suspend fun getGeocodingData( suspend fun getGeocodingData(
@Query("name") location: String, @Query("name") location: String,
): GeocodingDto ): GeocodingDto

View file

@ -37,7 +37,11 @@ class QWeatherActivity : ComponentActivity() {
WeatherAppTheme(darkTheme = isDark, monet = prefs.monet) { WeatherAppTheme(darkTheme = isDark, monet = prefs.monet) {
Surface(modifier = Modifier.fillMaxSize()) { Surface(modifier = Modifier.fillMaxSize()) {
Text(text = location.location) Text(text = location.location)
Navigator(screen = if (isLocationSet) MainScreen() else LocationPickerScreen()) { Navigator(
screen = if (isLocationSet) MainScreen() else LocationPickerScreen(),
onBackPressed = {
it !is MainScreen
}) {
SlideTransition(it) SlideTransition(it)
} }
} }

View file

@ -11,7 +11,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource import androidx.compose.ui.res.vectorResource
@ -22,7 +21,7 @@ import com.henryhiles.qweather.domain.weather.HourlyWeatherData
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
@Composable @Composable
fun WeatherCard(hour: HourlyWeatherData?, modifier: Modifier = Modifier) { fun WeatherCard(hour: HourlyWeatherData?, location: String, modifier: Modifier = Modifier) {
hour?.let { hour?.let {
val formattedTime = remember(it) { val formattedTime = remember(it) {
it.time.format(DateTimeFormatter.ofPattern("HH:mm")) it.time.format(DateTimeFormatter.ofPattern("HH:mm"))
@ -37,10 +36,17 @@ fun WeatherCard(hour: HourlyWeatherData?, modifier: Modifier = Modifier) {
.padding(16.dp), .padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Row(
horizontalArrangement = Arrangement.SpaceBetween,
modifier = Modifier.fillMaxWidth()
) {
Text(
text = location,
)
Text( Text(
text = "Today $formattedTime", text = "Today $formattedTime",
modifier = Modifier.align(Alignment.End), color = Color.White
) )
}
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Image( Image(
painter = painterResource(id = it.weatherType.iconRes), painter = painterResource(id = it.weatherType.iconRes),

View file

@ -7,7 +7,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherState import com.henryhiles.qweather.presentation.screenmodel.HourlyWeatherState
@ -25,7 +24,7 @@ fun WeatherForecast(
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
) { ) {
Text(text = "Today", fontSize = 20.sp, color = Color.White) Text(text = "Today", fontSize = 20.sp)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
val rowState = rememberLazyListState(LocalDateTime.now().hour) val rowState = rememberLazyListState(LocalDateTime.now().hour)

View file

@ -9,6 +9,7 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.MyLocation import androidx.compose.material.icons.outlined.MyLocation
import androidx.compose.material.icons.outlined.Search import androidx.compose.material.icons.outlined.Search
import androidx.compose.material3.* import androidx.compose.material3.*
@ -24,20 +25,35 @@ import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import com.henryhiles.qweather.R import com.henryhiles.qweather.R
import com.henryhiles.qweather.presentation.components.navigation.SmallToolbar
import com.henryhiles.qweather.presentation.screenmodel.LocationPickerScreenModel import com.henryhiles.qweather.presentation.screenmodel.LocationPickerScreenModel
class LocationPickerScreen : Screen { class LocationPickerScreen : Screen {
@Composable @Composable
override fun Content() { override fun Content() {
val screenModel: LocationPickerScreenModel = getScreenModel() val screenModel: LocationPickerScreenModel = getScreenModel()
var latitude by remember { mutableStateOf(0f) } var latitude by remember { mutableStateOf(screenModel.prefs.latitude) }
var longitude by remember { mutableStateOf(0f) } var longitude by remember { mutableStateOf(screenModel.prefs.longitude) }
var location by remember { mutableStateOf("") } var location by remember { mutableStateOf(screenModel.prefs.location) }
var locationSearch by remember { mutableStateOf("") } var locationSearch by remember { mutableStateOf("") }
var isAboutOpen by remember { mutableStateOf(false) }
val navigator = LocalNavigator.current val navigator = LocalNavigator.current
val context = LocalContext.current val context = LocalContext.current
Box(modifier = Modifier.fillMaxSize()) { Scaffold(floatingActionButton = {
FloatingActionButton(onClick = {
screenModel.prefs.location = location
screenModel.prefs.latitude = latitude
screenModel.prefs.longitude = longitude
navigator?.push(MainScreen())
}) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = stringResource(id = R.string.action_apply)
)
}
}) {
screenModel.state.error?.let { screenModel.state.error?.let {
AlertDialog( AlertDialog(
onDismissRequest = {}, onDismissRequest = {},
@ -51,24 +67,31 @@ class LocationPickerScreen : Screen {
} }
}, },
) )
} ?: AlertDialog( } ?: kotlin.run {
onDismissRequest = {}, 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)
)
}
})
Column(modifier = Modifier.padding(16.dp)) {
if (isAboutOpen) AlertDialog(
title = { Text(text = stringResource(id = R.string.location_choose)) },
text = { Text(text = stringResource(id = R.string.help_location_picker)) },
onDismissRequest = { isAboutOpen = false },
confirmButton = { confirmButton = {
TextButton( TextButton(
onClick = { onClick = { isAboutOpen = false }) {
screenModel.prefs.location = location Text(text = stringResource(id = R.string.action_confirm))
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( OutlinedTextField(
label = { Text(text = stringResource(id = R.string.location)) }, label = { Text(text = stringResource(id = R.string.location)) },
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
@ -82,9 +105,7 @@ class LocationPickerScreen : Screen {
}), }),
maxLines = 1, maxLines = 1,
value = locationSearch, value = locationSearch,
onValueChange = { onValueChange = { locationSearch = it },
locationSearch = it
},
trailingIcon = { trailingIcon = {
if (locationSearch == "") if (locationSearch == "")
IconButton(onClick = { IconButton(onClick = {
@ -106,20 +127,17 @@ class LocationPickerScreen : Screen {
contentDescription = stringResource(id = R.string.action_search) contentDescription = stringResource(id = R.string.action_search)
) )
} }
} },
modifier = Modifier.fillMaxWidth()
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text(text = "${screenModel.state.locations != null}")
screenModel.state.locations?.let {
Text(
text = "hi"
)
}
if (screenModel.state.isLoading) CircularProgressIndicator( if (screenModel.state.isLoading) CircularProgressIndicator(
modifier = Modifier.align( modifier = Modifier
.align(
Alignment.CenterHorizontally Alignment.CenterHorizontally
) )
.padding(16.dp)
) else screenModel.state.locations?.let { ) else screenModel.state.locations?.let {
LazyColumn { LazyColumn {
items(it) { items(it) {
@ -161,7 +179,7 @@ class LocationPickerScreen : Screen {
} }
} }
} }
) }
} }
} }
} }

View file

@ -1,12 +1,10 @@
package com.henryhiles.qweather.presentation.screenmodel package com.henryhiles.qweather.presentation.screenmodel
import android.location.Location
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.coroutineScope
import com.henryhiles.qweather.domain.location.LocationTracker
import com.henryhiles.qweather.domain.repository.WeatherRepository import com.henryhiles.qweather.domain.repository.WeatherRepository
import com.henryhiles.qweather.domain.util.Resource import com.henryhiles.qweather.domain.util.Resource
import com.henryhiles.qweather.domain.weather.DailyWeatherData import com.henryhiles.qweather.domain.weather.DailyWeatherData
@ -21,21 +19,18 @@ data class DailyWeatherState(
class DailyWeatherScreenModel( class DailyWeatherScreenModel(
private val repository: WeatherRepository, private val repository: WeatherRepository,
private val locationTracker: LocationTracker, private val location: LocationPreferenceManager
) : ScreenModel { ) : ScreenModel {
var state by mutableStateOf(DailyWeatherState()) var state by mutableStateOf(DailyWeatherState())
private set private set
private var currentLocation: Location? = null
fun loadWeatherInfo(cache: Boolean = true) { fun loadWeatherInfo(cache: Boolean = true) {
coroutineScope.launch { coroutineScope.launch {
state = state.copy(isLoading = true, error = null) state = state.copy(isLoading = true, error = null)
currentLocation = locationTracker.getCurrentLocation()
currentLocation?.let { location ->
state = when (val result = state = when (val result =
repository.getDailyWeatherData( repository.getDailyWeatherData(
lat = location.latitude.toFloat(), lat = location.latitude,
long = location.longitude.toFloat(), long = location.longitude,
cache = cache cache = cache
)) { )) {
is Resource.Success -> { is Resource.Success -> {
@ -53,12 +48,6 @@ class DailyWeatherScreenModel(
) )
} }
} }
} ?: kotlin.run {
state = state.copy(
isLoading = false,
error = "Couldn't retrieve location. Make sure to grant permission and enable GPS."
)
}
} }
} }

View file

@ -1,13 +1,10 @@
package com.henryhiles.qweather.presentation.screenmodel package com.henryhiles.qweather.presentation.screenmodel
import android.content.Context
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import cafe.adriel.voyager.core.model.ScreenModel import cafe.adriel.voyager.core.model.ScreenModel
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.coroutineScope
import com.henryhiles.qweather.R
import com.henryhiles.qweather.domain.location.LocationTracker
import com.henryhiles.qweather.domain.repository.WeatherRepository import com.henryhiles.qweather.domain.repository.WeatherRepository
import com.henryhiles.qweather.domain.util.Resource import com.henryhiles.qweather.domain.util.Resource
import com.henryhiles.qweather.domain.weather.HourlyWeatherInfo import com.henryhiles.qweather.domain.weather.HourlyWeatherInfo
@ -22,8 +19,7 @@ data class HourlyWeatherState(
class HourlyWeatherScreenModel( class HourlyWeatherScreenModel(
private val repository: WeatherRepository, private val repository: WeatherRepository,
private val locationTracker: LocationTracker, val location: LocationPreferenceManager,
private val context: Context
) : ScreenModel { ) : ScreenModel {
var state by mutableStateOf(HourlyWeatherState()) var state by mutableStateOf(HourlyWeatherState())
private set private set
@ -31,12 +27,10 @@ class HourlyWeatherScreenModel(
fun loadWeatherInfo(cache: Boolean = true) { fun loadWeatherInfo(cache: Boolean = true) {
coroutineScope.launch { coroutineScope.launch {
state = state.copy(isLoading = true, error = null, selected = null) state = state.copy(isLoading = true, error = null, selected = null)
val currentLocation = locationTracker.getCurrentLocation()
currentLocation?.let { location ->
state = when (val result = state = when (val result =
repository.getHourlyWeatherData( repository.getHourlyWeatherData(
lat = location.latitude.toFloat(), lat = location.latitude,
long = location.longitude.toFloat(), long = location.longitude,
cache = cache cache = cache
)) { )) {
is Resource.Success -> { is Resource.Success -> {
@ -55,12 +49,6 @@ class HourlyWeatherScreenModel(
) )
} }
} }
} ?: kotlin.run {
state = state.copy(
isLoading = false,
error = context.getString(R.string.error_location)
)
}
} }
} }

View file

@ -3,6 +3,7 @@ package com.henryhiles.qweather.presentation.tabs
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.material.icons.outlined.GpsFixed
import androidx.compose.material.icons.outlined.Info import androidx.compose.material.icons.outlined.Info
import androidx.compose.material.icons.outlined.Palette import androidx.compose.material.icons.outlined.Palette
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -19,6 +20,7 @@ import com.henryhiles.qweather.domain.util.NavigationTab
import com.henryhiles.qweather.presentation.components.settings.SettingsCategory import com.henryhiles.qweather.presentation.components.settings.SettingsCategory
import com.henryhiles.qweather.presentation.screen.AboutScreen import com.henryhiles.qweather.presentation.screen.AboutScreen
import com.henryhiles.qweather.presentation.screen.AppearanceSettingsScreen import com.henryhiles.qweather.presentation.screen.AppearanceSettingsScreen
import com.henryhiles.qweather.presentation.screen.LocationPickerScreen
object SettingsTab : NavigationTab { object SettingsTab : NavigationTab {
override val options: TabOptions override val options: TabOptions
@ -45,6 +47,12 @@ object SettingsTab : NavigationTab {
subtext = stringResource(R.string.settings_appearance_description), subtext = stringResource(R.string.settings_appearance_description),
destination = ::AppearanceSettingsScreen destination = ::AppearanceSettingsScreen
) )
SettingsCategory(
icon = Icons.Outlined.GpsFixed,
text = stringResource(R.string.settings_location),
subtext = stringResource(R.string.settings_location_description),
destination = ::LocationPickerScreen
)
} }
} }

View file

@ -1,6 +1,5 @@
package com.henryhiles.qweather.presentation.tabs package com.henryhiles.qweather.presentation.tabs
import android.Manifest
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -17,8 +16,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.koin.getScreenModel import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.tab.TabOptions import cafe.adriel.voyager.navigator.tab.TabOptions
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import com.henryhiles.qweather.R import com.henryhiles.qweather.R
import com.henryhiles.qweather.domain.util.NavigationTab import com.henryhiles.qweather.domain.util.NavigationTab
import com.henryhiles.qweather.presentation.components.weather.WeatherCard import com.henryhiles.qweather.presentation.components.weather.WeatherCard
@ -41,18 +38,12 @@ object TodayTab : NavigationTab {
} }
} }
@OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
override fun Content() { override fun Content() {
val weatherViewModel = getScreenModel<HourlyWeatherScreenModel>() val weatherViewModel = getScreenModel<HourlyWeatherScreenModel>()
val permissionsState = rememberPermissionState(
Manifest.permission.ACCESS_FINE_LOCATION,
) {
weatherViewModel.loadWeatherInfo()
}
LaunchedEffect(key1 = true) { LaunchedEffect(key1 = false) {
permissionsState.launchPermissionRequest() weatherViewModel.loadWeatherInfo()
} }
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
@ -90,7 +81,8 @@ object TodayTab : NavigationTab {
WeatherCard( WeatherCard(
hour = weatherViewModel.state.selected?.let { hour = weatherViewModel.state.selected?.let {
weatherViewModel.state.hourlyWeatherInfo?.weatherData?.get(it) weatherViewModel.state.hourlyWeatherInfo?.weatherData?.get(it)
} ?: weatherViewModel.state.hourlyWeatherInfo?.currentWeatherData } ?: weatherViewModel.state.hourlyWeatherInfo?.currentWeatherData,
location = weatherViewModel.location.location
) )
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
WeatherForecast( WeatherForecast(

View file

@ -1,7 +1,7 @@
package com.henryhiles.qweather.presentation.tabs package com.henryhiles.qweather.presentation.tabs
import android.Manifest import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
@ -9,15 +9,15 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DateRange import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.* import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import cafe.adriel.voyager.koin.getScreenModel import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.tab.TabOptions import cafe.adriel.voyager.navigator.tab.TabOptions
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberPermissionState
import com.henryhiles.qweather.R import com.henryhiles.qweather.R
import com.henryhiles.qweather.domain.util.NavigationTab import com.henryhiles.qweather.domain.util.NavigationTab
import com.henryhiles.qweather.presentation.components.weather.WeatherDay import com.henryhiles.qweather.presentation.components.weather.WeatherDay
@ -39,21 +39,14 @@ object WeekTab : NavigationTab {
} }
} }
@OptIn(ExperimentalPermissionsApi::class)
@Composable @Composable
override fun Content() { override fun Content() {
val weatherViewModel = getScreenModel<DailyWeatherScreenModel>() val weatherViewModel = getScreenModel<DailyWeatherScreenModel>()
val permissionsState = rememberPermissionState( LaunchedEffect(key1 = false) {
Manifest.permission.ACCESS_FINE_LOCATION,
) {
weatherViewModel.loadWeatherInfo() weatherViewModel.loadWeatherInfo()
} }
LaunchedEffect(key1 = true) {
permissionsState.launchPermissionRequest()
}
Box(modifier = Modifier.fillMaxSize()) { Box(modifier = Modifier.fillMaxSize()) {
when { when {
weatherViewModel.state.isLoading -> { weatherViewModel.state.isLoading -> {

View file

@ -12,14 +12,19 @@
<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="selected">Selected</string>x
<string name="help_screen">How do I use this screen?</string>
<string name="help_location_picker">Please either tap the auto-pick button or enter a location. Then tap the apply button in the bottom left corner.</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>
<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, dynamic colors</string>
<string name="settings_location">Location</string>
<string name="settings_location_description">Location to fetch data from</string>
<string name="location">Location</string> <string name="location">Location</string>
<string name="location_string">%1$s, %2$s, %3$s</string> <string name="location_string">%1$s, %2$s, %3$s</string>