rename to appscaffold, remote simpleappbar

This commit is contained in:
2026-08-08 20:34:47 +03:00
parent f1d191c94d
commit 3962ec29da
16 changed files with 224 additions and 258 deletions
@@ -22,7 +22,6 @@ dependencies {
implementation(libs.material) implementation(libs.material)
implementation(project(":apps:vbox:client:shared")) implementation(project(":apps:vbox:client:shared"))
implementation(project(":apps:vbox:common")) implementation(project(":apps:vbox:common"))
implementation(project(":libs:base")) implementation(project(":libs:base"))
implementation(project(":libs:ui")) implementation(project(":libs:ui"))
implementation(project(":libs:http-client")) implementation(project(":libs:http-client"))
@@ -4,11 +4,8 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import ru.shadowsparky.ui.AppScaffold import ru.shadowsparky.ui.AppScaffold
import ru.shadowsparky.ui.LocalBlurState
import ru.shadowsparky.ui.components.TopAppBar import ru.shadowsparky.ui.components.TopAppBar
import ru.shadowsparky.ui.rememberBlurState
import ru.shadowsparky.vbox.shared.presentation.chat.ChatScreen import ru.shadowsparky.vbox.shared.presentation.chat.ChatScreen
import ru.shadowsparky.vbox.shared.presentation.list.VideoContent import ru.shadowsparky.vbox.shared.presentation.list.VideoContent
import ru.shadowsparky.vbox.shared.presentation.nav.Child import ru.shadowsparky.vbox.shared.presentation.nav.Child
@@ -38,18 +35,13 @@ fun BaseMainScreen(
onNavigate: (Route) -> Unit, onNavigate: (Route) -> Unit,
content: @Composable (PaddingValues) -> Unit content: @Composable (PaddingValues) -> Unit
) { ) {
val blurState = rememberBlurState() AppScaffold(
CompositionLocalProvider(LocalBlurState provides blurState) { appBar = { appBar() },
AppScaffold( currentRoute = route,
appBar = { appBar() }, onSelectNavigation = { onNavigate(it) },
currentRoute = route, navigationEntries = createNavigationList(),
onSelectNavigation = { onNavigate(it) }, colorScheme = getColorScheme()
navigationEntries = createNavigationList(), ) { padding -> content(padding) }
colorScheme = getColorScheme()
) { padding ->
content(padding)
}
}
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.material3.ColorScheme import androidx.compose.material3.ColorScheme
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
@@ -12,8 +11,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.seiko.imageloader.LocalImageLoader import com.seiko.imageloader.LocalImageLoader
import ru.shadowsparky.koin import ru.shadowsparky.koin
import ru.shadowsparky.ui.components.BackButton
import ru.shadowsparky.ui.components.TopAppBar
import ru.shadowsparky.ui.nav.Nav import ru.shadowsparky.ui.nav.Nav
import ru.shadowsparky.updater.presentation.UpdateDialog import ru.shadowsparky.updater.presentation.UpdateDialog
import ru.shadowsparky.vbox.shared.presentation.list.VideoScreen import ru.shadowsparky.vbox.shared.presentation.list.VideoScreen
@@ -85,25 +82,7 @@ private fun NavInternal(root: VBoxRootComponent) {
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SimpleAppBar( fun AppScaffold(
title: String,
doBack: (() -> Unit)?,
modifier: Modifier = Modifier,
scrollBehavior: TopAppBarScrollBehavior? = null
) {
TopAppBar(
title = { Text(title) },
navigationIcon = {
doBack?.let { BackButton(it) }
},
scrollBehavior = scrollBehavior,
modifier = modifier
)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BaseScreen(
scrollBehavior: TopAppBarScrollBehavior? = null, scrollBehavior: TopAppBarScrollBehavior? = null,
snackbarState: SnackbarHostState? = null, snackbarState: SnackbarHostState? = null,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@@ -29,7 +29,6 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -57,9 +56,8 @@ import ru.shadowsparky.ui.applyBlurSource
import ru.shadowsparky.ui.components.ErrorContent import ru.shadowsparky.ui.components.ErrorContent
import ru.shadowsparky.ui.components.LazyVerticalGrid import ru.shadowsparky.ui.components.LazyVerticalGrid
import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.ui.rememberBlurState
import ru.shadowsparky.vbox.shared.domain.model.VideoItem import ru.shadowsparky.vbox.shared.domain.model.VideoItem
import ru.shadowsparky.vbox.shared.presentation.BaseScreen import ru.shadowsparky.vbox.shared.presentation.AppScaffold
import ru.shadowsparky.vbox.shared.presentation.search.AppBarWithSearch import ru.shadowsparky.vbox.shared.presentation.search.AppBarWithSearch
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -67,30 +65,27 @@ import ru.shadowsparky.vbox.shared.presentation.search.AppBarWithSearch
fun VideoScreen(component: VideoScreenComponent) { fun VideoScreen(component: VideoScreenComponent) {
val error by component.errorCode.collectAsState(null) val error by component.errorCode.collectAsState(null)
val items by component.items.collectAsState() val items by component.items.collectAsState()
val blurState = rememberBlurState() AppScaffold(
CompositionLocalProvider(LocalBlurState provides blurState) { content = {
BaseScreen( VideoScreenContent(
content = { it,
VideoScreenContent( error,
it, items,
error, onRefresh = { component.load() },
items, onShowOverview = { component.showOverview(it) },
onRefresh = { component.load() }, emptyResource = Res.string.not_found
onShowOverview = { component.showOverview(it) }, )
emptyResource = Res.string.not_found },
) appBar = {
}, val appName = stringResource(Res.string.recommendations)
appBar = { val title = component.search ?: appName
val appName = stringResource(Res.string.recommendations) AppBarWithSearch(
val title = component.search ?: appName title,
AppBarWithSearch( isDefault = title == appName,
title, onBack = { component.doBack() }
isDefault = title == appName, )
onBack = { component.doBack() } }
) )
}
)
}
} }
@Composable @Composable
@@ -6,7 +6,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.vbox.shared.presentation.BaseScreen import ru.shadowsparky.vbox.shared.presentation.AppScaffold
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -15,7 +15,7 @@ fun LoadingScreen(component: LoadingComponent) {
LaunchedEffect(isLoggedIn) { LaunchedEffect(isLoggedIn) {
isLoggedIn?.let { component.onLoaded(it) } isLoggedIn?.let { component.onLoaded(it) }
} }
BaseScreen( AppScaffold(
appBar = {}, appBar = {},
content = { Loading() } content = { Loading() }
) )
@@ -28,6 +28,7 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -42,13 +43,13 @@ import androidx.compose.ui.unit.dp
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import ru.shadowsparky.ui.AppScaffold import ru.shadowsparky.ui.AppScaffold
import ru.shadowsparky.ui.components.BackButton
import ru.shadowsparky.ui.components.ExpressiveLazyColumn import ru.shadowsparky.ui.components.ExpressiveLazyColumn
import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.vbox.shared.domain.EpisodeOfflineVideo import ru.shadowsparky.vbox.shared.domain.EpisodeOfflineVideo
import ru.shadowsparky.vbox.shared.domain.OfflineState import ru.shadowsparky.vbox.shared.domain.OfflineState
import ru.shadowsparky.vbox.shared.domain.OfflineVideo import ru.shadowsparky.vbox.shared.domain.OfflineVideo
import ru.shadowsparky.vbox.shared.domain.OfflineVideoInfo import ru.shadowsparky.vbox.shared.domain.OfflineVideoInfo
import ru.shadowsparky.vbox.shared.presentation.SimpleAppBar
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -56,9 +57,11 @@ fun OfflineVideoScreen(component: OfflineVideoComponent) {
val info by component.info.collectAsState(null) val info by component.info.collectAsState(null)
AppScaffold( AppScaffold(
appBar = { appBar = {
SimpleAppBar( TopAppBar(
title = stringResource(Res.string.offline_video), { Text(stringResource(Res.string.offline_video)) },
doBack = { component.onBack() } navigationIcon = {
BackButton { component.onBack }
}
) )
} }
) { padding -> ) { padding ->
@@ -45,7 +45,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
@@ -76,9 +75,8 @@ import ru.shadowsparky.ui.components.ErrorContent
import ru.shadowsparky.ui.components.LazyColumn import ru.shadowsparky.ui.components.LazyColumn
import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.ui.components.TopAppBar import ru.shadowsparky.ui.components.TopAppBar
import ru.shadowsparky.ui.rememberBlurState
import ru.shadowsparky.vbox.shared.domain.model.VideoDetails import ru.shadowsparky.vbox.shared.domain.model.VideoDetails
import ru.shadowsparky.vbox.shared.presentation.BaseScreen import ru.shadowsparky.vbox.shared.presentation.AppScaffold
import ru.shadowsparky.vbox.shared.presentation.getClipboardText import ru.shadowsparky.vbox.shared.presentation.getClipboardText
import ru.shadowsparky.vbox.shared.presentation.settings.tag.LinkTagBottomSheet import ru.shadowsparky.vbox.shared.presentation.settings.tag.LinkTagBottomSheet
@@ -87,50 +85,47 @@ import ru.shadowsparky.vbox.shared.presentation.settings.tag.LinkTagBottomSheet
fun OverviewScreen(component: OverviewComponent) { fun OverviewScreen(component: OverviewComponent) {
val errorState by component.errorCode.collectAsState(initial = null) val errorState by component.errorCode.collectAsState(initial = null)
val details by component.details.collectAsState() val details by component.details.collectAsState()
val blurState = rememberBlurState()
var tagsBottomSheetVisible by rememberSaveable { mutableStateOf(false) } var tagsBottomSheetVisible by rememberSaveable { mutableStateOf(false) }
CompositionLocalProvider(LocalBlurState provides blurState) { AppScaffold(
BaseScreen( appBar = {
appBar = { OverviewAppBar(
OverviewAppBar( component.offlineSupport,
component.offlineSupport, details,
details, component,
component, tagsRequest = { tagsBottomSheetVisible = true }
tagsRequest = { tagsBottomSheetVisible = true } )
},
content = { padding ->
when {
errorState != null -> ErrorContent(
errorState!!,
modifier = Modifier.fillMaxSize(),
buttonText = stringResource(Res.string.retry)
) { component.loadDetailsIfNeeded() }
details != null -> DetailsInfo(
details = details!!,
paddingValues = padding,
onWatchClicked = { component.watch() }
) )
},
content = { padding ->
when {
errorState != null -> ErrorContent( else -> Loading()
errorState!!,
modifier = Modifier.fillMaxSize(),
buttonText = stringResource(Res.string.retry)
) { component.loadDetailsIfNeeded() }
details != null -> DetailsInfo(
details = details!!,
paddingValues = padding,
onWatchClicked = { component.watch() }
)
else -> Loading()
}
if (tagsBottomSheetVisible) {
val allTags by component.allTags.collectAsState(null)
val movieTags by component.movieTags.collectAsState(null)
LinkTagBottomSheet(
allTags,
movieTags,
{ tagsBottomSheetVisible = false },
{ component.showTags() },
{ id, state -> component.updateTag(id, state) }
)
}
} }
)
} if (tagsBottomSheetVisible) {
val allTags by component.allTags.collectAsState(null)
val movieTags by component.movieTags.collectAsState(null)
LinkTagBottomSheet(
allTags,
movieTags,
{ tagsBottomSheetVisible = false },
{ component.showTags() },
{ id, state -> component.updateTag(id, state) }
)
}
}
)
} }
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -28,7 +28,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -52,11 +51,10 @@ import ru.shadowsparky.ui.components.ErrorContent
import ru.shadowsparky.ui.components.ExpressiveLazyColumn import ru.shadowsparky.ui.components.ExpressiveLazyColumn
import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.ui.components.TopAppBar import ru.shadowsparky.ui.components.TopAppBar
import ru.shadowsparky.ui.rememberBlurState
import ru.shadowsparky.vbox.shared.domain.OfflineState import ru.shadowsparky.vbox.shared.domain.OfflineState
import ru.shadowsparky.vbox.shared.domain.OfflineVideo import ru.shadowsparky.vbox.shared.domain.OfflineVideo
import ru.shadowsparky.vbox.shared.domain.model.Episode import ru.shadowsparky.vbox.shared.domain.model.Episode
import ru.shadowsparky.vbox.shared.presentation.BaseScreen import ru.shadowsparky.vbox.shared.presentation.AppScaffold
import ru.shadowsparky.vbox.shared.presentation.getClipboardText import ru.shadowsparky.vbox.shared.presentation.getClipboardText
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -95,74 +93,70 @@ fun EpisodesOverviewContent(
commitOffline: (Boolean) -> Unit commitOffline: (Boolean) -> Unit
) { ) {
val snackbarState = remember { SnackbarHostState() } val snackbarState = remember { SnackbarHostState() }
val blurState = rememberBlurState()
var isSaveMode by rememberSaveable { mutableStateOf(false) } var isSaveMode by rememberSaveable { mutableStateOf(false) }
CompositionLocalProvider(LocalBlurState provides blurState) { AppScaffold(
BaseScreen( snackbarState = snackbarState,
snackbarState = snackbarState, content = { padding ->
content = { padding -> val clipboardText = getClipboardText()
val clipboardText = getClipboardText() EpisodesOverviewList(
EpisodesOverviewList( onLaunch = { showPlayer(it) },
onLaunch = { showPlayer(it) }, contentPadding = padding,
contentPadding = padding, errorCode = error,
errorCode = error, episodes = episodes,
episodes = episodes, loadLinks = { refresh() },
loadLinks = { refresh() }, onClipboardAction = {
onClipboardAction = { onClipboard(Clip(clipboardText, it.url))
onClipboard(Clip(clipboardText, it.url)) },
}, onOfflineClicked = onOfflineClicked,
onOfflineClicked = onOfflineClicked, isSaveMode = isSaveMode
isSaveMode = isSaveMode )
) },
}, appBar = {
appBar = { TopAppBar(
TopAppBar( title = {
title = { Text(stringResource(Res.string.episodes_overview))
Text(stringResource(Res.string.episodes_overview)) },
}, navigationIcon = {
navigationIcon = { BackButton {
BackButton { if (isSaveMode) {
if (isSaveMode) { commitOffline(true)
commitOffline(true) isSaveMode = false
isSaveMode = false } else {
} else { onBack()
onBack()
}
} }
}, }
actions = { },
if (offlineSupport) { actions = {
IconButton( if (offlineSupport) {
onClick = { IconButton(
isSaveMode = !isSaveMode onClick = {
if (isSaveMode) startOffline() else commitOffline(false) isSaveMode = !isSaveMode
} if (isSaveMode) startOffline() else commitOffline(false)
) { }
) {
val resource =
if (isSaveMode) Res.drawable.folder_check else Res.drawable.save_as
Icon(
painter = painterResource(resource),
contentDescription = null
)
}
if (isSaveMode) {
val selectedAll = episodes?.all { it.offlineInfo != null } ?: false
IconButton(onClick = { onOfflineMultiple(!selectedAll) }) {
val resource = val resource =
if (isSaveMode) Res.drawable.folder_check else Res.drawable.save_as if (selectedAll) Res.drawable.deselect else Res.drawable.select_all
Icon( Icon(
painter = painterResource(resource), painter = painterResource(resource),
contentDescription = null contentDescription = null
) )
} }
if (isSaveMode) {
val selectedAll = episodes?.all { it.offlineInfo != null } ?: false
IconButton(onClick = { onOfflineMultiple(!selectedAll) }) {
val resource =
if (selectedAll) Res.drawable.deselect else Res.drawable.select_all
Icon(
painter = painterResource(resource),
contentDescription = null
)
}
}
} }
} }
) }
} )
) }
} )
} }
@Composable @Composable
@@ -11,7 +11,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -28,43 +27,39 @@ import ru.shadowsparky.ui.components.ErrorContent
import ru.shadowsparky.ui.components.ExpressiveLazyColumn import ru.shadowsparky.ui.components.ExpressiveLazyColumn
import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.ui.components.TopAppBar import ru.shadowsparky.ui.components.TopAppBar
import ru.shadowsparky.ui.rememberBlurState
import ru.shadowsparky.vbox.shared.domain.model.Season import ru.shadowsparky.vbox.shared.domain.model.Season
import ru.shadowsparky.vbox.shared.presentation.BaseScreen import ru.shadowsparky.vbox.shared.presentation.AppScaffold
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun SeasonOverviewScreen(component: SeasonOverviewComponent) { fun SeasonOverviewScreen(component: SeasonOverviewComponent) {
val blurState = rememberBlurState() AppScaffold(
CompositionLocalProvider(LocalBlurState provides blurState) { content = {
BaseScreen( val seasons = component.season.collectAsState(null)
content = { val err = component.error.collectAsState(null)
val seasons = component.season.collectAsState(null) if (err.value != null) {
val err = component.error.collectAsState(null) ErrorContent(
if (err.value != null) { err.value!!,
ErrorContent( modifier = Modifier.fillMaxSize(),
err.value!!, buttonText = stringResource(Res.string.retry)
modifier = Modifier.fillMaxSize(), ) { component.load() }
buttonText = stringResource(Res.string.retry) } else if (seasons.value != null) {
) { component.load() } SeasonsInfo(
} else if (seasons.value != null) { seasons.value!!, it
SeasonsInfo( ) { seasonId -> component.showEpisode(seasonId) }
seasons.value!!, it } else {
) { seasonId -> component.showEpisode(seasonId) } Loading()
} else {
Loading()
}
}, appBar = {
TopAppBar(
title = {
Text(stringResource(Res.string.seasons_overview))
}, navigationIcon = {
BackButton { component.doBack() }
}
)
} }
) }, appBar = {
} TopAppBar(
title = {
Text(stringResource(Res.string.seasons_overview))
}, navigationIcon = {
BackButton { component.doBack() }
}
)
}
)
} }
@Composable @Composable
@@ -19,6 +19,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
@@ -34,14 +35,14 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import ru.shadowsparky.ui.components.BackButton
import ru.shadowsparky.ui.components.ErrorAlert import ru.shadowsparky.ui.components.ErrorAlert
import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.ui.components.PasswordTextField import ru.shadowsparky.ui.components.PasswordTextField
import ru.shadowsparky.vbox.shared.domain.model.EmptyLoginException import ru.shadowsparky.vbox.shared.domain.model.EmptyLoginException
import ru.shadowsparky.vbox.shared.domain.model.EmptyPasswordException import ru.shadowsparky.vbox.shared.domain.model.EmptyPasswordException
import ru.shadowsparky.vbox.shared.domain.model.LoginException import ru.shadowsparky.vbox.shared.domain.model.LoginException
import ru.shadowsparky.vbox.shared.presentation.BaseScreen import ru.shadowsparky.vbox.shared.presentation.AppScaffold
import ru.shadowsparky.vbox.shared.presentation.SimpleAppBar
@Composable @Composable
fun AuthScreen(comp: AuthComponent) { fun AuthScreen(comp: AuthComponent) {
@@ -92,8 +93,15 @@ fun BaseLogin(
else -> {} else -> {}
} }
} }
BaseScreen( AppScaffold(
appBar = { SimpleAppBar(title, doBack) }, appBar = {
TopAppBar(
title = { Text(title) },
navigationIcon = {
doBack?.let { BackButton { it() } }
}
)
},
content = { content = {
if (state is AuthState.Loading) { if (state is AuthState.Loading) {
Loading() Loading()
@@ -19,6 +19,7 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -31,10 +32,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import ru.shadowsparky.ui.components.BackButton
import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.ui.components.PasswordTextField import ru.shadowsparky.ui.components.PasswordTextField
import ru.shadowsparky.vbox.shared.presentation.BaseScreen import ru.shadowsparky.vbox.shared.presentation.AppScaffold
import ru.shadowsparky.vbox.shared.presentation.SimpleAppBar
import ru.shadowsparky.vbox.shared.presentation.settings.auth.AuthState import ru.shadowsparky.vbox.shared.presentation.settings.auth.AuthState
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@@ -43,11 +44,13 @@ fun EditPasswordScreen(component: EditPasswordComponent) {
val state by component.state.collectAsState() val state by component.state.collectAsState()
val snackbarState = remember { SnackbarHostState() } val snackbarState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
BaseScreen( AppScaffold(
appBar = { appBar = {
SimpleAppBar( TopAppBar(
stringResource(Res.string.edit_password), { Text(stringResource(Res.string.edit_password)) },
doBack = { component.onBack() } navigationIcon = {
BackButton { component.onBack() }
}
) )
}, },
snackbarState = snackbarState, snackbarState = snackbarState,
@@ -37,7 +37,7 @@ import org.jetbrains.compose.resources.stringResource
import ru.shadowsparky.ui.components.BackButton import ru.shadowsparky.ui.components.BackButton
import ru.shadowsparky.ui.components.Loading import ru.shadowsparky.ui.components.Loading
import ru.shadowsparky.ui.components.TopAppBar import ru.shadowsparky.ui.components.TopAppBar
import ru.shadowsparky.vbox.shared.presentation.BaseScreen import ru.shadowsparky.vbox.shared.presentation.AppScaffold
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable @Composable
@@ -49,7 +49,7 @@ fun EditServScreen(comp: EditServComponent) {
} }
val keyboardController = LocalSoftwareKeyboardController.current val keyboardController = LocalSoftwareKeyboardController.current
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
BaseScreen( AppScaffold(
appBar = { appBar = {
TopAppBar( TopAppBar(
title = { Text(stringResource(Res.string.edit_custom_serv_addr)) }, title = { Text(stringResource(Res.string.edit_custom_serv_addr)) },
@@ -18,6 +18,7 @@ import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TextField import androidx.compose.material3.TextField
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@@ -35,10 +36,10 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
import ru.shadowsparky.ui.AppScaffold import ru.shadowsparky.ui.AppScaffold
import ru.shadowsparky.ui.components.AlertContent import ru.shadowsparky.ui.components.AlertContent
import ru.shadowsparky.ui.components.BackButton
import ru.shadowsparky.ui.components.ExpressiveLazyColumn import ru.shadowsparky.ui.components.ExpressiveLazyColumn
import ru.shadowsparky.ui.components.TextPreference import ru.shadowsparky.ui.components.TextPreference
import ru.shadowsparky.vbox.shared.domain.tag.TagInfo import ru.shadowsparky.vbox.shared.domain.tag.TagInfo
import ru.shadowsparky.vbox.shared.presentation.SimpleAppBar
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -48,9 +49,9 @@ fun TagScreen(component: TagComponent) {
var activeTag by remember { mutableStateOf<TagInfo?>(null) } var activeTag by remember { mutableStateOf<TagInfo?>(null) }
AppScaffold( AppScaffold(
appBar = { appBar = {
SimpleAppBar( TopAppBar(
stringResource(Res.string.user_tags), { Text(stringResource(Res.string.user_tags)) },
component.onBack navigationIcon = { BackButton { component.onBack() } }
) )
} }
) { ) {
@@ -5,6 +5,7 @@ import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.Action import org.gradle.api.Action
import org.gradle.api.Plugin import org.gradle.api.Plugin
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.plugins.ExtensionAware
import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.configure
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import ru.shadowsparky.applyCoroutines import ru.shadowsparky.applyCoroutines
@@ -37,6 +38,5 @@ class AndroidLibrary : Plugin<Project> {
} }
} }
private fun org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension.android(configure: Action<KotlinMultiplatformAndroidLibraryTarget>): Unit = private fun KotlinMultiplatformExtension.android(configure: Action<KotlinMultiplatformAndroidLibraryTarget>): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("android", configure) (this as ExtensionAware).extensions.configure("android", configure)
@@ -27,6 +27,7 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
@@ -80,35 +81,38 @@ fun <T : Route> AppScaffold(
content: @Composable (padding: PaddingValues) -> Unit, content: @Composable (padding: PaddingValues) -> Unit,
) { ) {
val windowType = currentWindowType() val windowType = currentWindowType()
MaterialExpressiveTheme(colorScheme = colorScheme) { val blurState = rememberBlurState()
Scaffold( CompositionLocalProvider(LocalBlurState provides blurState) {
bottomBar = { MaterialExpressiveTheme(colorScheme = colorScheme) {
if (windowType.isCompact) { Scaffold(
BottomBar(navigationEntries, currentRoute, onSelectNavigation) bottomBar = {
} if (windowType.isCompact) {
}, BottomBar(navigationEntries, currentRoute, onSelectNavigation)
topBar = { }
appBar(scrollBehavior) },
}, content = { topBar = {
if (windowType.isExpanded && navigationEntries?.isNotEmpty() == true) { appBar(scrollBehavior)
ExpandedContent( }, content = {
it, if (windowType.isExpanded && navigationEntries?.isNotEmpty() == true) {
navigationEntries, ExpandedContent(
currentRoute, it,
onSelectNavigation, navigationEntries,
content currentRoute,
) onSelectNavigation,
content
)
} else {
content(it)
}
}, modifier = if (scrollBehavior != null) {
modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
} else { } else {
content(it) modifier
}, snackbarHost = {
snackbarState?.let { SnackbarHost(it) }
} }
}, modifier = if (scrollBehavior != null) { )
modifier.nestedScroll(scrollBehavior.nestedScrollConnection) }
} else {
modifier
}, snackbarHost = {
snackbarState?.let { SnackbarHost(it) }
}
)
} }
} }
@@ -117,8 +117,8 @@ fun ExpressiveLazyColumn(
item(key = node.key, contentType = node.contentType) { item(key = node.key, contentType = node.contentType) {
ExpressiveCard( ExpressiveCard(
topRadius = topRadius, topRadius = if (reverseLayout) bottomRadius else topRadius,
bottomRadius = bottomRadius, bottomRadius = if (reverseLayout) topRadius else bottomRadius,
horizontalPadding = horizontalPadding, horizontalPadding = horizontalPadding,
verticalSpacing = verticalSpacing, verticalSpacing = verticalSpacing,
modifier = Modifier.animateItem( modifier = Modifier.animateItem(
@@ -126,9 +126,7 @@ fun ExpressiveLazyColumn(
fadeOutSpec = spring(), fadeOutSpec = spring(),
placementSpec = spring() placementSpec = spring()
) )
) { ) { node.content(this@item) }
node.content(this@item)
}
} }
} }
} }