simple screen with lazylist, pull-to-refresh and blur with Haze

This commit is contained in:
2024-08-05 04:56:19 +03:00
parent 2c91f6bb62
commit 3b65d44f2f
8 changed files with 252 additions and 25 deletions
+5 -4
View File
@@ -69,8 +69,9 @@ kotlin {
implementation(libs.multiplatformSettings)
implementation(libs.koin.core)
implementation(libs.koin.compose)
implementation("org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0")
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.haze)
implementation(libs.haze.materials)
}
commonTest.dependencies {
@@ -101,11 +102,11 @@ kotlin {
android {
namespace = "dev.meloda.overseerr"
compileSdk = 34
compileSdk = 35
defaultConfig {
minSdk = 26
targetSdk = 34
targetSdk = 35
applicationId = "dev.meloda.overseerr.androidApp"
versionCode = 1
@@ -6,7 +6,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.transitions.FadeTransition
import dev.meloda.overseerr.screens.url.presentation.UrlScreen
import dev.meloda.overseerr.screens.main.MainScreen
import dev.meloda.overseerr.theme.AppTheme
import org.koin.compose.KoinContext
@@ -14,7 +14,7 @@ import org.koin.compose.KoinContext
internal fun App() = KoinContext {
AppTheme {
Surface(modifier = Modifier.fillMaxSize()) {
Navigator(UrlScreen()) { navigator ->
Navigator(MainScreen()) { navigator ->
FadeTransition(navigator)
}
}
@@ -0,0 +1,58 @@
package dev.meloda.overseerr.screens.main
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.meloda.overseerr.screens.login.presentation.LoginScreen
import dev.meloda.overseerr.screens.requests.RequestsScreen
import dev.meloda.overseerr.screens.url.presentation.UrlScreen
class MainScreen : Screen {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
Scaffold(
topBar = {
TopAppBar(title = { Text(text = "Main screen") })
}
) { padding ->
Row(
modifier = Modifier
.padding(padding)
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
) {
Button(
onClick = { navigator.push(UrlScreen()) },
modifier = Modifier.weight(0.3f)
) {
Text(text = "Url")
}
Button(
onClick = { navigator.push(LoginScreen()) },
modifier = Modifier.weight(0.3f)
) {
Text(text = "Login")
}
Button(
onClick = { navigator.push(RequestsScreen()) },
modifier = Modifier.weight(0.3f)
) {
Text(text = "Requests")
}
}
}
}
}
@@ -0,0 +1,143 @@
package dev.meloda.overseerr.screens.requests
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material3.*
import androidx.compose.material3.pulltorefresh.PullToRefreshDefaults.Indicator
import androidx.compose.material3.pulltorefresh.pullToRefresh
import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.chrisbanes.haze.HazeState
import dev.chrisbanes.haze.haze
import dev.chrisbanes.haze.hazeChild
import dev.chrisbanes.haze.materials.ExperimentalHazeMaterialsApi
import dev.chrisbanes.haze.materials.HazeMaterials
import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds
class RequestsScreen : Screen {
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalHazeMaterialsApi::class
)
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
var isRefreshing by remember {
mutableStateOf(false)
}
val hazeState = remember { HazeState() }
val hazeStyle = HazeMaterials.ultraThin()
val refreshState = rememberPullToRefreshState()
var isNeedToRefresh by remember {
mutableStateOf(false)
}
LaunchedEffect(isNeedToRefresh) {
if (isNeedToRefresh) {
isRefreshing = true
delay(2.seconds)
isRefreshing = false
isNeedToRefresh = false
}
}
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = { Text(text = "Requests") },
navigationIcon = {
IconButton(onClick = navigator::pop) {
Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = null
)
}
},
colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),
modifier = Modifier
.hazeChild(
state = hazeState,
style = hazeStyle
).fillMaxWidth()
)
}
) { padding ->
val bottomPadding = padding.calculateBottomPadding()
Box(
modifier = Modifier
.fillMaxSize()
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr))
) {
LazyColumn(
modifier = Modifier
.fillMaxSize()
.haze(
state = hazeState,
style = hazeStyle
)
.pullToRefresh(
isRefreshing = isRefreshing,
state = refreshState,
onRefresh = { isNeedToRefresh = true }
)
) {
item {
Spacer(modifier = Modifier.height(padding.calculateTopPadding()))
}
items(count = 1000) { index ->
Text(
text = "Text #${index + 1}",
style = MaterialTheme.typography.headlineLarge,
modifier = Modifier.background(Color.Red)
)
Spacer(modifier = Modifier.height(64.dp))
}
item {
Spacer(modifier = Modifier.height(bottomPadding))
}
}
if (bottomPadding.value > 0) {
Box(
modifier = Modifier
.align(Alignment.BottomCenter)
.hazeChild(
state = hazeState,
style = hazeStyle
)
.background(Color.Transparent)
.height(bottomPadding)
.fillMaxWidth()
)
}
Indicator(
state = refreshState,
isRefreshing = isRefreshing,
modifier = Modifier.align(Alignment.TopCenter)
.padding(top = padding.calculateTopPadding())
)
}
}
}
}
@@ -2,5 +2,10 @@ package dev.meloda.overseerr.screens.url
import androidx.lifecycle.ViewModel
class UrlViewModel : ViewModel() {
interface UrlViewModel {
}
class UrlViewModelImpl : ViewModel(), UrlViewModel {
}
@@ -1,37 +1,54 @@
package dev.meloda.overseerr.screens.url.presentation
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.foundation.layout.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowBack
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.LayoutDirection
import androidx.lifecycle.viewmodel.compose.viewModel
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow
import dev.meloda.overseerr.screens.login.presentation.LoginScreen
import dev.meloda.overseerr.screens.url.UrlViewModel
import dev.meloda.overseerr.screens.url.UrlViewModelImpl
class UrlScreen : Screen {
@OptIn(ExperimentalMaterial3Api::class)
@Composable
override fun Content() {
val navigator = LocalNavigator.currentOrThrow
val viewModel: UrlViewModel = viewModel { UrlViewModel() }
val viewModel: UrlViewModel = viewModel { UrlViewModelImpl() }
Scaffold(modifier = Modifier.fillMaxSize()) { padding ->
Column(
modifier = Modifier.fillMaxSize()
.padding(padding)
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = { Text(text = "Url") },
navigationIcon = {
IconButton(onClick = navigator::pop) {
Icon(
imageVector = Icons.AutoMirrored.Rounded.ArrowBack,
contentDescription = null
)
}
}
)
}
) { padding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(start = padding.calculateStartPadding(LayoutDirection.Ltr))
.padding(end = padding.calculateEndPadding(LayoutDirection.Ltr))
.padding(bottom = padding.calculateBottomPadding())
) {
Text(text = "Input url screen")
Button(
onClick = {
navigator.push(LoginScreen())
}
onClick = { navigator.push(LoginScreen()) }
) {
Text(text = "Next")
}
+1 -1
View File
@@ -15,7 +15,7 @@ fun main() = application {
Window(
title = "Overseerr",
state = rememberWindowState(width = 800.dp, height = 600.dp),
onCloseRequest = ::exitApplication,
onCloseRequest = ::exitApplication
) {
window.minimumSize = Dimension(350, 600)
App()
+6 -3
View File
@@ -12,10 +12,13 @@ ktor = "3.0.0-beta-2"
kotlinx-serialization = "1.7.1"
multiplatformSettings = "1.1.1"
koin = "4.0.0-RC1"
viewmodel-compose = "2.8.0"
haze = "0.7.3"
[libraries]
androidx-activityCompose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "viewmodel-compose" }
androidx-uitest-testManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "androidx-uiTest" }
androidx-uitest-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-uiTest" }
voyager-navigator = { module = "cafe.adriel.voyager:voyager-navigator", version.ref = "voyager" }
@@ -30,13 +33,13 @@ ktor-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-js = { module = "io.ktor:ktor-client-js", version.ref = "ktor" }
ktor-client-curl = { module = "io.ktor:ktor-client-curl", version.ref = "ktor" }
ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "ktor" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
multiplatformSettings = { module = "com.russhwolf:multiplatform-settings", version.ref = "multiplatformSettings" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }
koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
haze-materials = { module = "dev.chrisbanes.haze:haze-materials", version.ref = "haze" }
[plugins]