diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index cfa6faa..dd5cfe1 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -57,25 +57,25 @@ android {
}
dependencies {
+ val composeVersion = "1.4.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.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.activity:activity-compose:1.7.0")
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
- testImplementation("junit:junit:4.13.2")
- androidTestImplementation("androidx.test.ext:junit:1.1.5")
- 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")
+ debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion")
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.2")
// Voyager
val voyagerVersion = "1.0.0-rc04"
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-androidx:$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.okhttp3:logging-interceptor:5.0.0-alpha.3")
- implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
-}
\ No newline at end of file
+ // Accompanist
+ val accompanistVersion = "0.30.0"
+ implementation("com.google.accompanist:accompanist-permissions:$accompanistVersion")
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index fff1a58..95f9c98 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -14,11 +14,10 @@
android:supportsRtl="true"
android:theme="@style/Theme.WeatherApp">
-
diff --git a/app/src/main/java/com/henryhiles/qweather/data/location/DefaultLocationTracker.kt b/app/src/main/java/com/henryhiles/qweather/data/location/DefaultLocationTracker.kt
index 02f0184..2787021 100644
--- a/app/src/main/java/com/henryhiles/qweather/data/location/DefaultLocationTracker.kt
+++ b/app/src/main/java/com/henryhiles/qweather/data/location/DefaultLocationTracker.kt
@@ -18,11 +18,6 @@ class DefaultLocationTracker constructor(
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
- val hasAccessCoarseLocationPermission = ContextCompat.checkSelfPermission(
- application,
- Manifest.permission.ACCESS_COARSE_LOCATION
- ) == PackageManager.PERMISSION_GRANTED
-
val locationManager =
application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
val isGpsEnabled = locationManager.isProviderEnabled(
diff --git a/app/src/main/java/com/henryhiles/qweather/data/mappers/WeatherMappers.kt b/app/src/main/java/com/henryhiles/qweather/data/mappers/WeatherMappers.kt
index 62b05de..b7f6aa8 100644
--- a/app/src/main/java/com/henryhiles/qweather/data/mappers/WeatherMappers.kt
+++ b/app/src/main/java/com/henryhiles/qweather/data/mappers/WeatherMappers.kt
@@ -15,7 +15,7 @@ fun WeatherDataDto.toWeatherDataMap(): Map> {
IndexedWeatherData(
index = index, data = WeatherData(
time = LocalDateTime.parse(time, DateTimeFormatter.ISO_DATE_TIME),
- temperatureCelsius = temperatures[index],
+ temperatureCelsius = temperatures[index].toInt(),
pressure = pressures[index],
windSpeed = windSpeeds[index],
humidity = humidities[index],
diff --git a/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt b/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt
index cefda7b..4c2e87f 100644
--- a/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt
+++ b/app/src/main/java/com/henryhiles/qweather/di/AppModule.kt
@@ -1,8 +1,8 @@
package com.henryhiles.qweather.di
import com.henryhiles.qweather.data.remote.WeatherApi
-import com.henryhiles.qweather.presentation.viewmodel.WeatherViewModel
-import org.koin.androidx.viewmodel.dsl.viewModelOf
+import com.henryhiles.qweather.presentation.screenmodel.WeatherScreenModel
+import org.koin.core.module.dsl.factoryOf
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
import retrofit2.Retrofit
@@ -36,5 +36,7 @@ val appModule = module {
// single {
// LocationServices.getFusedLocationProviderClient(get())
// }
- viewModelOf(::WeatherViewModel)
+
+ factoryOf(::WeatherScreenModel)
+// factory { WeatherScreenModel(get(), get()) }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt b/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt
index 6471ec6..c59c18f 100644
--- a/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt
+++ b/app/src/main/java/com/henryhiles/qweather/domain/weather/WeatherData.kt
@@ -4,7 +4,7 @@ import java.time.LocalDateTime
data class WeatherData(
val time: LocalDateTime,
- val temperatureCelsius: Double,
+ val temperatureCelsius: Int,
val pressure: Double,
val windSpeed: Double,
val humidity: Double,
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/QWeatherActivity.kt b/app/src/main/java/com/henryhiles/qweather/presentation/QWeatherActivity.kt
new file mode 100644
index 0000000..a2704e6
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/QWeatherActivity.kt
@@ -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)
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/activity/MainActivity.kt b/app/src/main/java/com/henryhiles/qweather/presentation/activity/MainActivity.kt
deleted file mode 100644
index cb208c9..0000000
--- a/app/src/main/java/com/henryhiles/qweather/presentation/activity/MainActivity.kt
+++ /dev/null
@@ -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>
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- val weatherViewModel = getViewModel()
-
- 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 = { /* ... */ }
- )
- }
- }
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/BackButton.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/BackButton.kt
new file mode 100644
index 0000000..3f5d410
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/BackButton.kt
@@ -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))
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/LargeToolbar.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/LargeToolbar.kt
new file mode 100644
index 0000000..f899b95
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/LargeToolbar.kt
@@ -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,
+ )
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/RadioController.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/RadioController.kt
new file mode 100644
index 0000000..5827c72
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/RadioController.kt
@@ -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 > EnumRadioController(
+ default: E,
+ labelFactory: (E) -> String = { it.toString() },
+ crossinline onChoiceSelected: (E) -> Unit
+) {
+ var choice by remember { mutableStateOf(default) }
+ val ctx = LocalContext.current
+
+ Column {
+ enumValues().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)
+ })
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDay.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDay.kt
deleted file mode 100644
index 4704f72..0000000
--- a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDay.kt
+++ /dev/null
@@ -1,2 +0,0 @@
-package com.henryhiles.qweather.presentation.components
-
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsCategory.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsCategory.kt
new file mode 100644
index 0000000..ac92fb1
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsCategory.kt
@@ -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) }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsChoiceDialog.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsChoiceDialog.kt
new file mode 100644
index 0000000..1ab86f2
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsChoiceDialog.kt
@@ -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 > 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))
+ }
+ }
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsItem.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsItem.kt
new file mode 100644
index 0000000..427342c
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsItem.kt
@@ -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()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsItemChoice.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsItemChoice.kt
new file mode 100644
index 0000000..a87041d
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsItemChoice.kt
@@ -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 > 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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsSwitch.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsSwitch.kt
new file mode 100644
index 0000000..e2cdd6d
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/settings/SettingsSwitch.kt
@@ -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) }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherCard.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherCard.kt
similarity index 97%
rename from app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherCard.kt
rename to app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherCard.kt
index 7a7e9e2..5d9a277 100644
--- a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherCard.kt
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherCard.kt
@@ -16,7 +16,7 @@ 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.presentation.viewmodel.WeatherState
+import com.henryhiles.qweather.presentation.screenmodel.WeatherState
import java.time.format.DateTimeFormatter
import kotlin.math.roundToInt
@@ -26,7 +26,6 @@ fun WeatherCard(state: WeatherState, modifier: Modifier = Modifier) {
val formattedTime = remember(it) {
it.time.format(DateTimeFormatter.ofPattern("HH:mm"))
}
-
Card(
shape = RoundedCornerShape(8.dp),
modifier = modifier.padding(16.dp)
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDataDisplay.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherDataDisplay.kt
similarity index 100%
rename from app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherDataDisplay.kt
rename to app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherDataDisplay.kt
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherForecast.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherForecast.kt
similarity index 95%
rename from app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherForecast.kt
rename to app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherForecast.kt
index 7df9fbf..dc0b561 100644
--- a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherForecast.kt
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherForecast.kt
@@ -10,7 +10,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
-import com.henryhiles.qweather.presentation.viewmodel.WeatherState
+import com.henryhiles.qweather.presentation.screenmodel.WeatherState
import java.time.LocalDateTime
@Composable
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherHour.kt b/app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherHour.kt
similarity index 100%
rename from app/src/main/java/com/henryhiles/qweather/presentation/components/WeatherHour.kt
rename to app/src/main/java/com/henryhiles/qweather/presentation/components/weather/WeatherHour.kt
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/screen/AppearanceSettingsScreen.kt b/app/src/main/java/com/henryhiles/qweather/presentation/screen/AppearanceSettingsScreen.kt
new file mode 100644
index 0000000..48c079e
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/screen/AppearanceSettingsScreen.kt
@@ -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),
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/screen/MainScreen.kt b/app/src/main/java/com/henryhiles/qweather/presentation/screen/MainScreen.kt
new file mode 100644
index 0000000..20b3ded
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/screen/MainScreen.kt
@@ -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()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/screen/TodayScreen.kt b/app/src/main/java/com/henryhiles/qweather/presentation/screen/TodayScreen.kt
deleted file mode 100644
index 119a634..0000000
--- a/app/src/main/java/com/henryhiles/qweather/presentation/screen/TodayScreen.kt
+++ /dev/null
@@ -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()
- 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)
- )
- }
-
- }
- }
- }
-
- }
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/AppearanceSettingsScreenModel.kt b/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/AppearanceSettingsScreenModel.kt
new file mode 100644
index 0000000..7a12166
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/AppearanceSettingsScreenModel.kt
@@ -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);
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/viewmodel/WeatherViewModel.kt b/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/WeatherScreenModel.kt
similarity index 88%
rename from app/src/main/java/com/henryhiles/qweather/presentation/viewmodel/WeatherViewModel.kt
rename to app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/WeatherScreenModel.kt
index 1600ff3..2061e89 100644
--- a/app/src/main/java/com/henryhiles/qweather/presentation/viewmodel/WeatherViewModel.kt
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/screenmodel/WeatherScreenModel.kt
@@ -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.mutableStateOf
import androidx.compose.runtime.setValue
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
+import cafe.adriel.voyager.core.model.ScreenModel
+import cafe.adriel.voyager.core.model.coroutineScope
import com.henryhiles.qweather.domain.location.LocationTracker
import com.henryhiles.qweather.domain.repository.WeatherRepository
import com.henryhiles.qweather.domain.util.Resource
@@ -17,15 +17,15 @@ data class WeatherState(
val error: String? = null
)
-class WeatherViewModel constructor(
+class WeatherScreenModel constructor(
private val repository: WeatherRepository,
private val locationTracker: LocationTracker,
-) : ViewModel() {
+) : ScreenModel {
var state by mutableStateOf(WeatherState())
private set
fun loadWeatherInfo() {
- viewModelScope.launch {
+ coroutineScope.launch {
state = state.copy(isLoading = true, error = null)
val currentLocation = locationTracker.getCurrentLocation()
currentLocation?.let { location ->
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/tabs/SettingsTab.kt b/app/src/main/java/com/henryhiles/qweather/presentation/tabs/SettingsTab.kt
new file mode 100644
index 0000000..cad3236
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/tabs/SettingsTab.kt
@@ -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),
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/tabs/TodayTab.kt b/app/src/main/java/com/henryhiles/qweather/presentation/tabs/TodayTab.kt
new file mode 100644
index 0000000..bf0b2a9
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/tabs/TodayTab.kt
@@ -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()
+
+ 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)
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/henryhiles/qweather/presentation/tabs/WeekTab.kt b/app/src/main/java/com/henryhiles/qweather/presentation/tabs/WeekTab.kt
new file mode 100644
index 0000000..34c6e1d
--- /dev/null
+++ b/app/src/main/java/com/henryhiles/qweather/presentation/tabs/WeekTab.kt
@@ -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")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 8d12d2e..ff2081b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,20 @@
- QWeather
+ QWeather
+ Today
+ Weekly
+ Settings
+
+ Back
+ Confirm
+
+ Theme
+ Dynamic Theme
+ Available on Android 12+
+
+ Appearance
+ Theme, code style
+
+ System
+ Light
+ Dark
\ No newline at end of file