Skip to content

Commit

Permalink
add: catalog screen
Browse files Browse the repository at this point in the history
  • Loading branch information
muedsa committed Mar 19, 2024
1 parent 939e927 commit 5c45c29
Show file tree
Hide file tree
Showing 11 changed files with 565 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ import com.muedsa.compose.tv.widget.LocalRightSideDrawerState
import com.muedsa.compose.tv.widget.NoBackground
import com.muedsa.compose.tv.widget.ScreenBackground
import com.muedsa.compose.tv.widget.ScreenBackgroundType
import com.muedsa.compose.tv.widget.StandardImageCardsRow
import com.muedsa.compose.tv.widget.TwoSideWideButton
import com.muedsa.compose.tv.widget.rememberScreenBackgroundState
import com.muedsa.jcytv.PlaybackActivity
Expand All @@ -71,7 +70,6 @@ import com.muedsa.jcytv.ui.RankIconColor
import com.muedsa.jcytv.ui.nav.LocalAppNavController
import com.muedsa.jcytv.ui.nav.NavigationItems
import com.muedsa.jcytv.ui.nav.navigate
import com.muedsa.jcytv.util.JcyDocTool
import com.muedsa.jcytv.util.Upscayl
import com.muedsa.jcytv.viewmodel.AnimeDetailViewModel
import com.muedsa.jcytv.viewmodel.AppSettingViewModel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text
import com.muedsa.compose.tv.widget.NotImplementScreen
import com.muedsa.compose.tv.widget.ScreenBackgroundType
import com.muedsa.jcytv.ui.features.home.catalog.CatalogScreen
import com.muedsa.jcytv.ui.features.home.favorites.FavoritesScreen
import com.muedsa.jcytv.ui.features.home.main.MainScreen
import com.muedsa.jcytv.ui.features.home.rank.RankScreen
Expand Down Expand Up @@ -119,7 +119,7 @@ fun HomeContent(
viewModel = homePageViewModel
)
}
HomeNavTab.Catalog -> NotImplementScreen()
HomeNavTab.Catalog -> CatalogScreen()
HomeNavTab.Rank -> RankScreen()
HomeNavTab.Search -> SearchScreen()
HomeNavTab.Favorites -> FavoritesScreen()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.muedsa.jcytv.ui.features.home.catalog

import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Check
import androidx.compose.material3.HorizontalDivider
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.FilterChip
import androidx.tv.material3.FilterChipDefaults
import androidx.tv.material3.Icon
import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text

@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalLayoutApi::class)
@Composable
fun CatalogOptionsWidget(
title: String,
selectedKey: String?,
options: Map<String, String>,
onClick: (String, String) -> Unit = { _, _ -> }
) {
Text(
text = title,
color = MaterialTheme.colorScheme.onBackground,
style = MaterialTheme.typography.labelLarge
)
Spacer(modifier = Modifier.height(4.dp))
FlowRow {
options.forEach { (key, text) ->
FilterChip(
modifier = Modifier.padding(8.dp),
selected = key == selectedKey,
leadingIcon = if (key == selectedKey) {
{
Icon(
modifier = Modifier.size(FilterChipDefaults.IconSize),
imageVector = Icons.Outlined.Check,
contentDescription = "选择${text}"
)
}
} else null,
onClick = {
onClick(key, text)
}
) {
Text(text = text)
}
}
}
HorizontalDivider(modifier = Modifier.padding(bottom = 10.dp))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
package com.muedsa.jcytv.ui.features.home.catalog

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.ArrowDropDown
import androidx.compose.material.icons.outlined.KeyboardArrowUp
import androidx.compose.material.icons.outlined.Refresh
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.tv.foundation.lazy.grid.TvGridCells
import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid
import androidx.tv.foundation.lazy.grid.itemsIndexed
import androidx.tv.foundation.lazy.list.TvLazyColumn
import androidx.tv.material3.ButtonDefaults
import androidx.tv.material3.Card
import androidx.tv.material3.ExperimentalTvMaterial3Api
import androidx.tv.material3.Icon
import androidx.tv.material3.OutlinedButton
import androidx.tv.material3.OutlinedIconButton
import androidx.tv.material3.Text
import com.muedsa.compose.tv.model.ContentModel
import com.muedsa.compose.tv.theme.ImageCardRowCardPadding
import com.muedsa.compose.tv.theme.ScreenPaddingLeft
import com.muedsa.compose.tv.widget.CardType
import com.muedsa.compose.tv.widget.ImageContentCard
import com.muedsa.compose.tv.widget.LocalErrorMsgBoxState
import com.muedsa.compose.tv.widget.ScreenBackgroundType
import com.muedsa.jcytv.ui.GirdLastItemHeight
import com.muedsa.jcytv.ui.VideoPosterSize
import com.muedsa.jcytv.ui.features.home.LocalHomeScreenBackgroundState
import com.muedsa.jcytv.ui.nav.LocalAppNavController
import com.muedsa.jcytv.ui.nav.NavigationItems
import com.muedsa.jcytv.ui.nav.navigate
import com.muedsa.jcytv.viewmodel.CatalogViewModel
import com.muedsa.model.LazyType
import com.muedsa.uitl.LogUtil

@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
fun CatalogScreen(
viewModel: CatalogViewModel = hiltViewModel()
) {
val navController = LocalAppNavController.current
val backgroundState = LocalHomeScreenBackgroundState.current
val errorMsgBoxState = LocalErrorMsgBoxState.current

var optionId by viewModel.optionIdState
var optionArea by viewModel.optionAreaState
var optionClass by viewModel.optionClassState
var optionLang by viewModel.optionLangState
var optionYear by viewModel.optionYearState
var optionLetter by viewModel.optionLetterState
var optionBy by viewModel.optionByState

val searchAnimeLP by viewModel.animeLPSF.collectAsState()

var optionsExpand by remember {
mutableStateOf(false)
}

LaunchedEffect(key1 = searchAnimeLP) {
if (searchAnimeLP.type == LazyType.FAILURE) {
errorMsgBoxState.error(searchAnimeLP.error)
}
}

BackHandler(enabled = optionsExpand) {
optionsExpand = false
}

Column(modifier = Modifier.padding(start = ScreenPaddingLeft)) {
Row(
modifier = Modifier
.fillMaxWidth()
.offset(x = -ScreenPaddingLeft)
.padding(vertical = 30.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
OutlinedButton(onClick = {
optionsExpand = !optionsExpand
}) {
Text(text = "筛选项")
Spacer(modifier = Modifier.width(ButtonDefaults.IconSpacing))
Icon(
modifier = Modifier.size(ButtonDefaults.IconSize),
imageVector = if (optionsExpand) Icons.Outlined.KeyboardArrowUp else Icons.Outlined.ArrowDropDown,
contentDescription = "展开筛选项"
)
}
Spacer(modifier = Modifier.width(16.dp))
OutlinedIconButton(onClick = {
viewModel.resetCatalogOptions()
}) {
Icon(
modifier = Modifier.size(ButtonDefaults.IconSize),
imageVector = Icons.Outlined.Refresh,
contentDescription = "重置筛选项"
)
}
}

if (optionsExpand) {
// 筛选项
TvLazyColumn(contentPadding = PaddingValues(top = ImageCardRowCardPadding)) {
item {
CatalogOptionsWidget(
title = "频道",
selectedKey = optionId,
options = CatalogViewModel.ID_OPTIONS,
onClick = { key, _ ->
optionId = key
viewModel.catalogNew()
}
)
}
item {
CatalogOptionsWidget(
title = "地区",
selectedKey = optionArea,
options = CatalogViewModel.AREA_OPTIONS,
onClick = { key, _ ->
optionArea = if (optionArea == key) null else key
viewModel.catalogNew()
}
)
}
item {
CatalogOptionsWidget(
title = "剧情",
selectedKey = optionClass,
options = CatalogViewModel.CLASS_OPTIONS,
onClick = { key, _ ->
optionClass = if (optionClass == key) null else key
viewModel.catalogNew()
}
)
}
item {
CatalogOptionsWidget(
title = "语言",
selectedKey = optionLang,
options = CatalogViewModel.LANG_OPTIONS,
onClick = { key, _ ->
optionLang = if (optionLang == key) null else key
viewModel.catalogNew()
}
)
}
item {
CatalogOptionsWidget(
title = "年份",
selectedKey = optionYear,
options = CatalogViewModel.YEAR_OPTIONS,
onClick = { key, _ ->
optionYear = if (optionYear == key) null else key
viewModel.catalogNew()
}
)
}
item {
CatalogOptionsWidget(
title = "字母",
selectedKey = optionLetter,
options = CatalogViewModel.LETTER_OPTIONS,
onClick = { key, _ ->
optionLetter = if (optionLetter == key) null else key
viewModel.catalogNew()
}
)
}
item {
CatalogOptionsWidget(
title = "排序",
selectedKey = optionBy,
options =CatalogViewModel.ORDER_BY_OPTIONS,
onClick = { key, _ ->
optionBy = if (optionBy == key) null else key
viewModel.catalogNew()
}
)
}
}
} else {
val gridFocusRequester = remember { FocusRequester() }

TvLazyVerticalGrid(
columns = TvGridCells.Adaptive(VideoPosterSize.width + ImageCardRowCardPadding),
contentPadding = PaddingValues(
top = ImageCardRowCardPadding,
bottom = ImageCardRowCardPadding
),
modifier = Modifier
.focusRequester(gridFocusRequester)
.focusProperties {
exit = { gridFocusRequester.saveFocusedChild(); FocusRequester.Default }
enter = {
if (gridFocusRequester.restoreFocusedChild()) {
LogUtil.d("grid restoreFocusedChild")
FocusRequester.Cancel
} else {
LogUtil.d("grid focused default child")
FocusRequester.Default
}
}
}
) {
itemsIndexed(
items = searchAnimeLP.list,
key = { _, item -> item.id }
) { index, item ->
val itemFocusRequester = remember {
FocusRequester()
}
ImageContentCard(
modifier = Modifier
.padding(end = ImageCardRowCardPadding)
.focusRequester(itemFocusRequester),
url = item.imageUrl,
imageSize = VideoPosterSize,
type = CardType.STANDARD,
model = ContentModel(
item.title,
subtitle = item.subTitle,
),
onItemFocus = {
backgroundState.url = item.imageUrl
backgroundState.type = ScreenBackgroundType.BLUR
},
onItemClick = {
LogUtil.d("Click $item")
navController.navigate(
NavigationItems.Detail,
listOf(item.id.toString())
)
}
)

LaunchedEffect(key1 = Unit) {
if (searchAnimeLP.offset == index) {
itemFocusRequester.requestFocus()
}
}
}

if (searchAnimeLP.type != LazyType.LOADING && searchAnimeLP.hasNext) {
item {
Card(
modifier = Modifier
.size(VideoPosterSize)
.padding(end = ImageCardRowCardPadding),
onClick = {
viewModel.catalog(searchAnimeLP)
}
) {
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(text = "继续加载")
}
}
}
}

// 最后一行占位
item {
Spacer(modifier = Modifier.height(GirdLastItemHeight))
}
}
}
}
}

Loading

0 comments on commit 5c45c29

Please sign in to comment.