settings frontend done, just need to setup screenmodel
This commit is contained in:
parent
e132ceb55f
commit
0809535e16
30 changed files with 732 additions and 143 deletions
|
@ -57,25 +57,25 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
val composeVersion = "1.4.0"
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.compose.ui:ui:1.4.0")
|
implementation("androidx.compose.ui:ui:$composeVersion")
|
||||||
implementation("androidx.compose.material3:material3:1.1.0-beta01")
|
implementation("androidx.compose.material3:material3:1.1.0-beta01")
|
||||||
implementation("androidx.compose.ui:ui-tooling-preview:1.4.0")
|
implementation("androidx.compose.material:material-icons-extended:$composeVersion")
|
||||||
|
implementation("androidx.compose.ui:ui-tooling-preview:$composeVersion")
|
||||||
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
|
||||||
implementation("androidx.activity:activity-compose:1.7.0")
|
implementation("androidx.activity:activity-compose:1.7.0")
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
||||||
testImplementation("junit:junit:4.13.2")
|
debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
|
||||||
androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0")
|
|
||||||
debugImplementation("androidx.compose.ui:ui-tooling:1.4.0")
|
|
||||||
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2")
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2")
|
||||||
|
|
||||||
// Voyager
|
// Voyager
|
||||||
val voyagerVersion = "1.0.0-rc04"
|
val voyagerVersion = "1.0.0-rc04"
|
||||||
|
|
||||||
implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion")
|
implementation("cafe.adriel.voyager:voyager-navigator:$voyagerVersion")
|
||||||
|
implementation("cafe.adriel.voyager:voyager-tab-navigator:$voyagerVersion")
|
||||||
implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")
|
implementation("cafe.adriel.voyager:voyager-transitions:$voyagerVersion")
|
||||||
implementation("cafe.adriel.voyager:voyager-androidx:$voyagerVersion")
|
implementation("cafe.adriel.voyager:voyager-androidx:$voyagerVersion")
|
||||||
implementation("cafe.adriel.voyager:voyager-koin:$voyagerVersion")
|
implementation("cafe.adriel.voyager:voyager-koin:$voyagerVersion")
|
||||||
|
@ -92,5 +92,7 @@ dependencies {
|
||||||
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
|
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
|
||||||
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3")
|
implementation("com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3")
|
||||||
|
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
|
// Accompanist
|
||||||
}
|
val accompanistVersion = "0.30.0"
|
||||||
|
implementation("com.google.accompanist:accompanist-permissions:$accompanistVersion")
|
||||||
|
}
|
||||||
|
|
|
@ -14,11 +14,10 @@
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.WeatherApp">
|
android:theme="@style/Theme.WeatherApp">
|
||||||
<activity
|
<activity
|
||||||
android:name=".presentation.activity.MainActivity"
|
android:name=".presentation.QWeatherActivity"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
|
@ -18,11 +18,6 @@ class DefaultLocationTracker constructor(
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
val hasAccessCoarseLocationPermission = ContextCompat.checkSelfPermission(
|
|
||||||
application,
|
|
||||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
|
||||||
|
|
||||||
val locationManager =
|
val locationManager =
|
||||||
application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||||
val isGpsEnabled = locationManager.isProviderEnabled(
|
val isGpsEnabled = locationManager.isProviderEnabled(
|
||||||
|
|
|
@ -15,7 +15,7 @@ fun WeatherDataDto.toWeatherDataMap(): Map<Int, List<WeatherData>> {
|
||||||
IndexedWeatherData(
|
IndexedWeatherData(
|
||||||
index = index, data = WeatherData(
|
index = index, data = WeatherData(
|
||||||
time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME),
|
time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME),
|
||||||
temperatureCelsius = temperatures[index],
|
temperatureCelsius = temperatures[index].toInt(),
|
||||||
pressure = pressures[index],
|
pressure = pressures[index],
|
||||||
windSpeed = windSpeeds[index],
|
windSpeed = windSpeeds[index],
|
||||||
humidity = humidities[index],
|
humidity = humidities[index],
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package com.henryhiles.qweather.di
|
package com.henryhiles.qweather.di
|
||||||
|
|
||||||
import com.henryhiles.qweather.data.remote.WeatherApi
|
import com.henryhiles.qweather.data.remote.WeatherApi
|
||||||
import com.henryhiles.qweather.presentation.viewmodel.WeatherViewModel
|
import com.henryhiles.qweather.presentation.screenmodel.WeatherScreenModel
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModelOf
|
import org.koin.core.module.dsl.factoryOf
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
|
@ -36,5 +36,7 @@ val appModule = module {
|
||||||
// single {
|
// single {
|
||||||
// LocationServices.getFusedLocationProviderClient(get<Application>())
|
// LocationServices.getFusedLocationProviderClient(get<Application>())
|
||||||
// }
|
// }
|
||||||
viewModelOf(::WeatherViewModel)
|
|
||||||
|
factoryOf(::WeatherScreenModel)
|
||||||
|
// factory { WeatherScreenModel(get(), get()) }
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ import java.time.LocalDateTime
|
||||||
|
|
||||||
data class WeatherData(
|
data class WeatherData(
|
||||||
val time: LocalDateTime,
|
val time: LocalDateTime,
|
||||||
val temperatureCelsius: Double,
|
val temperatureCelsius: Int,
|
||||||
val pressure: Double,
|
val pressure: Double,
|
||||||
val windSpeed: Double,
|
val windSpeed: Double,
|
||||||
val humidity: Double,
|
val humidity: Double,
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.henryhiles.qweather.presentation
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import cafe.adriel.voyager.navigator.Navigator
|
||||||
|
import cafe.adriel.voyager.transitions.SlideTransition
|
||||||
|
import com.henryhiles.qweather.presentation.screen.MainScreen
|
||||||
|
import com.henryhiles.qweather.presentation.ui.theme.WeatherAppTheme
|
||||||
|
|
||||||
|
class QWeatherActivity : ComponentActivity() {
|
||||||
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContent {
|
||||||
|
WeatherAppTheme {
|
||||||
|
Surface {
|
||||||
|
Navigator(screen = MainScreen()) {
|
||||||
|
SlideTransition(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,49 +0,0 @@
|
||||||
package com.henryhiles.qweather.presentation.activity
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import cafe.adriel.voyager.navigator.CurrentScreen
|
|
||||||
import cafe.adriel.voyager.navigator.Navigator
|
|
||||||
import com.henryhiles.qweather.presentation.screen.TodayScreen
|
|
||||||
import com.henryhiles.qweather.presentation.ui.theme.WeatherAppTheme
|
|
||||||
import com.henryhiles.qweather.presentation.viewmodel.WeatherViewModel
|
|
||||||
import org.koin.androidx.viewmodel.ext.android.getViewModel
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
|
||||||
private lateinit var permissionLauncher: ActivityResultLauncher<Array<String>>
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
val weatherViewModel = getViewModel<WeatherViewModel>()
|
|
||||||
|
|
||||||
permissionLauncher =
|
|
||||||
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { weatherViewModel.loadWeatherInfo() }
|
|
||||||
permissionLauncher.launch(
|
|
||||||
arrayOf(
|
|
||||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setContent {
|
|
||||||
WeatherAppTheme {
|
|
||||||
Surface {
|
|
||||||
Navigator(TodayScreen()) { navigator ->
|
|
||||||
Scaffold(
|
|
||||||
topBar = { /* ... */ },
|
|
||||||
content = { padding -> Box(modifier = Modifier.padding(padding)) { CurrentScreen() } },
|
|
||||||
bottomBar = { /* ... */ }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.henryhiles.qweather.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowBack
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BackButton() {
|
||||||
|
val nav = LocalNavigator.current
|
||||||
|
|
||||||
|
if (nav?.canPop == true) {
|
||||||
|
IconButton(onClick = { nav.pop() }) {
|
||||||
|
Icon(Icons.Filled.ArrowBack, stringResource(R.string.action_back))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package com.henryhiles.qweather.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
fun LargeToolbar(
|
||||||
|
title: String,
|
||||||
|
actions: @Composable RowScope.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
LargeTopAppBar(
|
||||||
|
title = { Text(text = title) },
|
||||||
|
navigationIcon = { BackButton() },
|
||||||
|
actions = actions,
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.henryhiles.qweather.presentation.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
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(
|
||||||
|
default: E,
|
||||||
|
labelFactory: (E) -> String = { it.toString() },
|
||||||
|
crossinline onChoiceSelected: (E) -> Unit
|
||||||
|
) {
|
||||||
|
var choice by remember { mutableStateOf(default) }
|
||||||
|
val ctx = LocalContext.current
|
||||||
|
|
||||||
|
Column {
|
||||||
|
enumValues<E>().forEach {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable {
|
||||||
|
choice = it
|
||||||
|
onChoiceSelected(it)
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(labelFactory(it))
|
||||||
|
Spacer(Modifier.weight(1f))
|
||||||
|
RadioButton(
|
||||||
|
selected = it == choice,
|
||||||
|
onClick = {
|
||||||
|
choice = it
|
||||||
|
onChoiceSelected(it)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,2 +0,0 @@
|
||||||
package com.henryhiles.qweather.presentation.components
|
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.henryhiles.qweather.presentation.components.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.LocalNavigator
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsCategory(
|
||||||
|
icon: ImageVector,
|
||||||
|
text: String,
|
||||||
|
subtext: String,
|
||||||
|
destination: (() -> Screen)? = null
|
||||||
|
) {
|
||||||
|
val screen = destination?.invoke()
|
||||||
|
val nav = LocalNavigator.current
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
screen?.let { nav?.push(it) }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
SettingItem(
|
||||||
|
icon = { Icon(icon, null) },
|
||||||
|
text = { Text(text) },
|
||||||
|
secondaryText = { Text(subtext) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.henryhiles.qweather.presentation.components.settings
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.material3.AlertDialog
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
|
import com.henryhiles.qweather.presentation.components.EnumRadioController
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun <reified E : Enum<E>> SettingsChoiceDialog(
|
||||||
|
visible: Boolean = false,
|
||||||
|
default: E,
|
||||||
|
noinline title: @Composable () -> Unit,
|
||||||
|
crossinline labelFactory: (E) -> String = { it.toString() },
|
||||||
|
noinline onRequestClose: () -> Unit = {},
|
||||||
|
crossinline description: @Composable () -> Unit = {},
|
||||||
|
noinline onChoice: (E) -> Unit = {},
|
||||||
|
) {
|
||||||
|
|
||||||
|
var choice by remember { mutableStateOf(default) }
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = visible,
|
||||||
|
enter = slideInVertically(),
|
||||||
|
exit = slideOutVertically()
|
||||||
|
) {
|
||||||
|
AlertDialog(
|
||||||
|
onDismissRequest = { onRequestClose() },
|
||||||
|
title = title,
|
||||||
|
text = {
|
||||||
|
description()
|
||||||
|
EnumRadioController(
|
||||||
|
default,
|
||||||
|
labelFactory
|
||||||
|
) { choice = it }
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
FilledTonalButton(onClick = { onChoice(choice) }) {
|
||||||
|
Text(text = stringResource(id = R.string.action_confirm))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package com.henryhiles.qweather.presentation.components.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ProvideTextStyle
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
icon: @Composable (() -> Unit)? = null,
|
||||||
|
text: @Composable () -> Unit,
|
||||||
|
secondaryText: @Composable (() -> Unit) = { },
|
||||||
|
trailing: @Composable (() -> Unit) = { },
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = modifier
|
||||||
|
.heightIn(min = 64.dp)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 18.dp, vertical = 14.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(14.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
if (icon != null) Box(modifier = Modifier.padding(8.dp)) {
|
||||||
|
icon()
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(5.dp)
|
||||||
|
) {
|
||||||
|
ProvideTextStyle(
|
||||||
|
MaterialTheme.typography.titleLarge.copy(
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
fontSize = 19.sp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
text()
|
||||||
|
}
|
||||||
|
ProvideTextStyle(
|
||||||
|
MaterialTheme.typography.bodyMedium.copy(
|
||||||
|
color = MaterialTheme.colorScheme.onSurface.copy(0.6f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
secondaryText()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.weight(1f, true))
|
||||||
|
|
||||||
|
trailing()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package com.henryhiles.qweather.presentation.components.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
inline fun <reified E : Enum<E>> SettingsItemChoice(
|
||||||
|
label: String,
|
||||||
|
title: String = label,
|
||||||
|
disabled: Boolean = false,
|
||||||
|
pref: E,
|
||||||
|
crossinline labelFactory: (E) -> String = { it.toString() },
|
||||||
|
crossinline onPrefChange: (E) -> Unit,
|
||||||
|
) {
|
||||||
|
val choiceLabel = labelFactory(pref)
|
||||||
|
var opened by remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingItem(
|
||||||
|
modifier = Modifier.clickable { opened = true },
|
||||||
|
text = { Text(text = label) },
|
||||||
|
) {
|
||||||
|
SettingsChoiceDialog(
|
||||||
|
visible = opened,
|
||||||
|
title = { Text(title) },
|
||||||
|
default = pref,
|
||||||
|
labelFactory = labelFactory,
|
||||||
|
onRequestClose = {
|
||||||
|
opened = false
|
||||||
|
},
|
||||||
|
onChoice = {
|
||||||
|
opened = false
|
||||||
|
onPrefChange(it)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
FilledTonalButton(onClick = { opened = true }, enabled = !disabled) {
|
||||||
|
Text(choiceLabel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.henryhiles.qweather.presentation.components.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.material3.Switch
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SettingsSwitch(
|
||||||
|
label: String,
|
||||||
|
secondaryLabel: String? = null,
|
||||||
|
disabled: Boolean = false,
|
||||||
|
pref: Boolean,
|
||||||
|
onPrefChange: (Boolean) -> Unit,
|
||||||
|
) {
|
||||||
|
SettingItem(
|
||||||
|
modifier = Modifier.clickable(enabled = !disabled) { onPrefChange(!pref) },
|
||||||
|
text = { Text(text = label) },
|
||||||
|
secondaryText = {
|
||||||
|
secondaryLabel?.let {
|
||||||
|
Text(text = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Switch(
|
||||||
|
checked = pref,
|
||||||
|
enabled = !disabled,
|
||||||
|
onCheckedChange = { onPrefChange(!pref) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ import androidx.compose.ui.res.vectorResource
|
||||||
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.R
|
import com.henryhiles.qweather.R
|
||||||
import com.henryhiles.qweather.presentation.viewmodel.WeatherState
|
import com.henryhiles.qweather.presentation.screenmodel.WeatherState
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ fun WeatherCard(state: WeatherState, modifier: Modifier = Modifier) {
|
||||||
val formattedTime = remember(it) {
|
val formattedTime = remember(it) {
|
||||||
it.time.format(DateTimeFormatter.ofPattern("HH:mm"))
|
it.time.format(DateTimeFormatter.ofPattern("HH:mm"))
|
||||||
}
|
}
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
shape = RoundedCornerShape(8.dp),
|
shape = RoundedCornerShape(8.dp),
|
||||||
modifier = modifier.padding(16.dp)
|
modifier = modifier.padding(16.dp)
|
|
@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
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.viewmodel.WeatherState
|
import com.henryhiles.qweather.presentation.screenmodel.WeatherState
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
|
|
||||||
@Composable
|
@Composable
|
|
@ -0,0 +1,65 @@
|
||||||
|
package com.henryhiles.qweather.presentation.screen
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.koin.getScreenModel
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
|
import com.henryhiles.qweather.presentation.components.LargeToolbar
|
||||||
|
import com.henryhiles.qweather.presentation.components.settings.SettingsItemChoice
|
||||||
|
import com.henryhiles.qweather.presentation.components.settings.SettingsSwitch
|
||||||
|
import com.henryhiles.qweather.presentation.screenmodel.AppearanceSettingsScreenModel
|
||||||
|
|
||||||
|
class AppearanceSettingsScreen : Screen {
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() = Screen()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Screen(
|
||||||
|
screenModel: AppearanceSettingsScreenModel = getScreenModel()
|
||||||
|
) {
|
||||||
|
val ctx = LocalContext.current
|
||||||
|
|
||||||
|
Scaffold(topBar = { Toolbar() }) { pv ->
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(pv)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
|
||||||
|
SettingsSwitch(
|
||||||
|
label = stringResource(R.string.appearance_monet),
|
||||||
|
secondaryLabel = stringResource(R.string.appearance_monet_description),
|
||||||
|
pref = screenModel.prefs.monet,
|
||||||
|
disabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.S
|
||||||
|
) { screenModel.prefs.monet = it }
|
||||||
|
|
||||||
|
SettingsItemChoice(
|
||||||
|
label = stringResource(R.string.appearance_theme),
|
||||||
|
pref = screenModel.prefs.theme,
|
||||||
|
labelFactory = { ctx.getString(it.label) }
|
||||||
|
) { screenModel.prefs.theme = it }
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Toolbar(
|
||||||
|
) {
|
||||||
|
LargeToolbar(
|
||||||
|
title = stringResource(R.string.settings_appearance),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package com.henryhiles.qweather.presentation.screen
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.DateRange
|
||||||
|
import androidx.compose.material.icons.filled.Home
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import cafe.adriel.voyager.core.screen.Screen
|
||||||
|
import cafe.adriel.voyager.navigator.CurrentScreen
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabNavigator
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
|
import com.henryhiles.qweather.presentation.tabs.SettingsTab
|
||||||
|
import com.henryhiles.qweather.presentation.tabs.TodayTab
|
||||||
|
import com.henryhiles.qweather.presentation.tabs.WeekTab
|
||||||
|
|
||||||
|
class MainScreen : Screen {
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
TabNavigator(tab = TodayTab) { navigator ->
|
||||||
|
Scaffold(
|
||||||
|
bottomBar = {
|
||||||
|
NavigationBar {
|
||||||
|
NavigationBarItem(
|
||||||
|
selected = navigator.current == TodayTab,
|
||||||
|
onClick = { navigator.current = TodayTab },
|
||||||
|
label = { Text(text = stringResource(id = R.string.tab_today)) },
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Home,
|
||||||
|
contentDescription = stringResource(id = R.string.tab_today)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
NavigationBarItem(
|
||||||
|
selected = navigator.current == WeekTab,
|
||||||
|
onClick = { navigator.current = WeekTab },
|
||||||
|
label = { Text(text = "Weekly") },
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.DateRange,
|
||||||
|
contentDescription = "Weekly"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
NavigationBarItem(
|
||||||
|
selected = navigator.current == SettingsTab,
|
||||||
|
onClick = { navigator.current = SettingsTab },
|
||||||
|
label = { Text(text = "Settings") },
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Settings,
|
||||||
|
contentDescription = "Settings"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) { padding ->
|
||||||
|
Box(modifier = Modifier.padding(padding)) {
|
||||||
|
CurrentScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
package com.henryhiles.qweather.presentation.screen
|
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
||||||
import androidx.compose.material3.*
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import cafe.adriel.voyager.androidx.AndroidScreen
|
|
||||||
import com.henryhiles.qweather.presentation.components.WeatherCard
|
|
||||||
import com.henryhiles.qweather.presentation.components.WeatherForecast
|
|
||||||
import com.henryhiles.qweather.presentation.viewmodel.WeatherViewModel
|
|
||||||
import org.koin.androidx.compose.getViewModel
|
|
||||||
|
|
||||||
class TodayScreen : AndroidScreen() {
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
override fun Content() {
|
|
||||||
val weatherViewModel = getViewModel<WeatherViewModel>()
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
WeatherCard(state = weatherViewModel.state)
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
WeatherForecast(state = weatherViewModel.state)
|
|
||||||
}
|
|
||||||
if (weatherViewModel.state.isLoading) CircularProgressIndicator(
|
|
||||||
modifier = Modifier.align(
|
|
||||||
Alignment.Center
|
|
||||||
)
|
|
||||||
)
|
|
||||||
weatherViewModel.state.error?.let {
|
|
||||||
AlertDialog(onDismissRequest = {}) {
|
|
||||||
Surface(
|
|
||||||
shape = MaterialTheme.shapes.large
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
|
||||||
Text(
|
|
||||||
text = "An error occurred",
|
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
SelectionContainer {
|
|
||||||
Text(
|
|
||||||
text = it,
|
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
|
||||||
modifier = Modifier.padding(16.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.henryhiles.qweather.presentation.screenmodel
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
|
|
||||||
|
class AppearanceSettingsScreenModel(context: Context) : ScreenModel {
|
||||||
|
|
||||||
|
var theme by enumPreference("theme", Theme.SYSTEM)
|
||||||
|
var monet by booleanPreference("monet", Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Theme(@StringRes val label: Int) {
|
||||||
|
SYSTEM(R.string.theme_system),
|
||||||
|
LIGHT(R.string.theme_light),
|
||||||
|
DARK(R.string.theme_dark);
|
||||||
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package com.henryhiles.qweather.presentation.viewmodel
|
package com.henryhiles.qweather.presentation.screenmodel
|
||||||
|
|
||||||
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 androidx.lifecycle.ViewModel
|
import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import cafe.adriel.voyager.core.model.coroutineScope
|
||||||
import com.henryhiles.qweather.domain.location.LocationTracker
|
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
|
||||||
|
@ -17,15 +17,15 @@ data class WeatherState(
|
||||||
val error: String? = null
|
val error: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
class WeatherViewModel constructor(
|
class WeatherScreenModel constructor(
|
||||||
private val repository: WeatherRepository,
|
private val repository: WeatherRepository,
|
||||||
private val locationTracker: LocationTracker,
|
private val locationTracker: LocationTracker,
|
||||||
) : ViewModel() {
|
) : ScreenModel {
|
||||||
var state by mutableStateOf(WeatherState())
|
var state by mutableStateOf(WeatherState())
|
||||||
private set
|
private set
|
||||||
|
|
||||||
fun loadWeatherInfo() {
|
fun loadWeatherInfo() {
|
||||||
viewModelScope.launch {
|
coroutineScope.launch {
|
||||||
state = state.copy(isLoading = true, error = null)
|
state = state.copy(isLoading = true, error = null)
|
||||||
val currentLocation = locationTracker.getCurrentLocation()
|
val currentLocation = locationTracker.getCurrentLocation()
|
||||||
currentLocation?.let { location ->
|
currentLocation?.let { location ->
|
|
@ -0,0 +1,66 @@
|
||||||
|
package com.henryhiles.qweather.presentation.tabs
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Settings
|
||||||
|
import androidx.compose.material.icons.outlined.Palette
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import cafe.adriel.voyager.navigator.tab.Tab
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
|
import com.henryhiles.qweather.presentation.components.LargeToolbar
|
||||||
|
import com.henryhiles.qweather.presentation.components.settings.SettingsCategory
|
||||||
|
import com.henryhiles.qweather.presentation.screen.AppearanceSettingsScreen
|
||||||
|
|
||||||
|
object SettingsTab : Tab {
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val title = stringResource(R.string.tab_settings)
|
||||||
|
val icon = rememberVectorPainter(Icons.Default.Settings)
|
||||||
|
|
||||||
|
return remember {
|
||||||
|
TabOptions(
|
||||||
|
index = 0u,
|
||||||
|
title = title,
|
||||||
|
icon = icon
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
Scaffold(
|
||||||
|
topBar = { Toolbar() },
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(it)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
) {
|
||||||
|
|
||||||
|
SettingsCategory(
|
||||||
|
icon = Icons.Outlined.Palette,
|
||||||
|
text = stringResource(R.string.settings_appearance),
|
||||||
|
subtext = stringResource(R.string.settings_appearance_description),
|
||||||
|
destination = ::AppearanceSettingsScreen
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Toolbar() {
|
||||||
|
LargeToolbar(
|
||||||
|
title = stringResource(R.string.tab_settings),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
package com.henryhiles.qweather.presentation.tabs
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Home
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import cafe.adriel.voyager.koin.getScreenModel
|
||||||
|
import cafe.adriel.voyager.navigator.tab.Tab
|
||||||
|
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.presentation.components.WeatherCard
|
||||||
|
import com.henryhiles.qweather.presentation.components.WeatherForecast
|
||||||
|
import com.henryhiles.qweather.presentation.screenmodel.WeatherScreenModel
|
||||||
|
|
||||||
|
object TodayTab : Tab {
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val title = stringResource(R.string.tab_today)
|
||||||
|
val icon = rememberVectorPainter(Icons.Default.Home)
|
||||||
|
|
||||||
|
return remember {
|
||||||
|
TabOptions(
|
||||||
|
index = 0u,
|
||||||
|
title = title,
|
||||||
|
icon = icon
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class, ExperimentalPermissionsApi::class)
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
val weatherViewModel = getScreenModel<WeatherScreenModel>()
|
||||||
|
|
||||||
|
val permissionsState = rememberPermissionState(
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
|
) {
|
||||||
|
weatherViewModel.loadWeatherInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = true) {
|
||||||
|
permissionsState.launchPermissionRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(modifier = Modifier.fillMaxSize()) {
|
||||||
|
when {
|
||||||
|
weatherViewModel.state.isLoading -> {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.align(
|
||||||
|
Alignment.Center
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
weatherViewModel.state.error != null -> {
|
||||||
|
AlertDialog(onDismissRequest = {}) {
|
||||||
|
Surface(
|
||||||
|
shape = MaterialTheme.shapes.large
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(
|
||||||
|
text = "An error occurred",
|
||||||
|
style = MaterialTheme.typography.headlineSmall,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
SelectionContainer {
|
||||||
|
Text(
|
||||||
|
text = weatherViewModel.state.error!!,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
) {
|
||||||
|
WeatherCard(state = weatherViewModel.state)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
WeatherForecast(state = weatherViewModel.state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.henryhiles.qweather.presentation.tabs
|
||||||
|
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.DateRange
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import cafe.adriel.voyager.navigator.tab.Tab
|
||||||
|
import cafe.adriel.voyager.navigator.tab.TabOptions
|
||||||
|
import com.henryhiles.qweather.R
|
||||||
|
|
||||||
|
object WeekTab : Tab {
|
||||||
|
override val options: TabOptions
|
||||||
|
@Composable
|
||||||
|
get() {
|
||||||
|
val title = stringResource(R.string.tab_weekly)
|
||||||
|
val icon = rememberVectorPainter(Icons.Default.DateRange)
|
||||||
|
|
||||||
|
return remember {
|
||||||
|
TabOptions(
|
||||||
|
index = 0u,
|
||||||
|
title = title,
|
||||||
|
icon = icon
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
override fun Content() {
|
||||||
|
Text(text = "Week Screen")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,20 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">QWeather</string>
|
<string name="app_name" translatable="false">QWeather</string>
|
||||||
|
<string name="tab_today">Today</string>
|
||||||
|
<string name="tab_weekly">Weekly</string>
|
||||||
|
<string name="tab_settings">Settings</string>
|
||||||
|
|
||||||
|
<string name="action_back">Back</string>
|
||||||
|
<string name="action_confirm">Confirm</string>
|
||||||
|
|
||||||
|
<string name="appearance_theme">Theme</string>
|
||||||
|
<string name="appearance_monet">Dynamic Theme</string>
|
||||||
|
<string name="appearance_monet_description">Available on Android 12+</string>
|
||||||
|
|
||||||
|
<string name="settings_appearance">Appearance</string>
|
||||||
|
<string name="settings_appearance_description">Theme, code style</string>
|
||||||
|
|
||||||
|
<string name="theme_system">System</string>
|
||||||
|
<string name="theme_light">Light</string>
|
||||||
|
<string name="theme_dark">Dark</string>
|
||||||
</resources>
|
</resources>
|
Reference in a new issue