diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt b/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt index e0c51b6b..23170cba 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt @@ -18,6 +18,7 @@ import dev.aaa1115910.biliapi.repositories.PgcRepository import dev.aaa1115910.biliapi.repositories.RecommendVideoRepository import dev.aaa1115910.biliapi.repositories.SearchRepository import dev.aaa1115910.biliapi.repositories.SeasonRepository +import dev.aaa1115910.biliapi.repositories.UgcRepository import dev.aaa1115910.biliapi.repositories.VideoDetailRepository import dev.aaa1115910.biliapi.repositories.VideoPlayRepository import dev.aaa1115910.bv.dao.AppDatabase @@ -162,6 +163,7 @@ val appModule = module { single { SeasonRepository(get()) } single { dev.aaa1115910.biliapi.repositories.UserRepository(get(), get()) } single { PgcRepository() } + single { UgcRepository(get()) } viewModel { DynamicViewModel(get(), get()) } viewModel { RecommendViewModel(get()) } viewModel { PopularViewModel(get()) } diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/Carousel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/Carousel.kt index e116cb21..e9925104 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/Carousel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/Carousel.kt @@ -1,84 +1,110 @@ package dev.aaa1115910.bv.component -import androidx.compose.foundation.background -import androidx.compose.foundation.border -import androidx.compose.foundation.focusable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable -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.Modifier -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.material3.Button import androidx.tv.material3.Carousel import androidx.tv.material3.ExperimentalTvMaterial3Api -import androidx.tv.material3.Text +import androidx.tv.material3.MaterialTheme +import coil.compose.AsyncImage +import dev.aaa1115910.biliapi.entity.CarouselData +import dev.aaa1115910.bv.activities.video.SeasonInfoActivity +import dev.aaa1115910.bv.activities.video.VideoInfoActivity +import dev.aaa1115910.bv.entity.proxy.ProxyArea +import dev.aaa1115910.bv.util.focusedBorder @OptIn(ExperimentalTvMaterial3Api::class) @Composable -fun HomeCarousel( - modifier: Modifier = Modifier +fun PgcCarousel( + modifier: Modifier = Modifier, + data: List ) { - val backgrounds = listOf( - Color.Red.copy(alpha = 0.3f), - Color.Yellow.copy(alpha = 0.3f), - Color.Green.copy(alpha = 0.3f) + val context = LocalContext.current + + CarouselContent( + modifier = modifier, + data = data, + onClick = { item -> + SeasonInfoActivity.actionStart( + context = context, + epId = item.episodeId, + seasonId = item.seasonId, + proxyArea = ProxyArea.checkProxyArea(item.title) + ) + } ) +} + +@OptIn(ExperimentalTvMaterial3Api::class) +@Composable +fun UgcCarousel( + modifier: Modifier = Modifier, + data: List +) { + val context = LocalContext.current + + CarouselContent( + modifier = modifier, + data = data, + onClick = { item -> + VideoInfoActivity.actionStart( + context = context, + aid = item.avid!! + ) + } + ) +} +@OptIn(ExperimentalTvMaterial3Api::class) +@Composable +fun CarouselContent( + modifier: Modifier = Modifier, + data: List, + onClick: (CarouselData.CarouselItem) -> Unit +) { Carousel( - itemCount = backgrounds.size, + itemCount = data.size, modifier = modifier - .height(300.dp) - .fillMaxWidth(), + .height(240.dp) + .clip(MaterialTheme.shapes.large) + .focusedBorder(), + contentTransformEndToStart = + fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))), + contentTransformStartToEnd = + fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))) ) { itemIndex -> - Box( - modifier = Modifier - .background(backgrounds[itemIndex]) - .border(2.dp, Color.White.copy(alpha = 0.5f)) - .fillMaxSize() - ) { - CarouselCard() - } + CarouselCard( + data = data[itemIndex], + onClick = { onClick(data[itemIndex]) } + ) } } @Composable -private fun CarouselCard() { - Box( - modifier = Modifier - .fillMaxSize() - .focusable() - .padding(40.dp), - contentAlignment = Alignment.CenterStart - ) { - var isFocused by remember { mutableStateOf(false) } - - Box( - modifier = Modifier - .border( - width = 2.dp, - color = if (isFocused) Color.Red else Color.Transparent, - shape = RoundedCornerShape(50) - ) - ) { - Button( - onClick = { }, - modifier = Modifier - .onFocusChanged { isFocused = it.isFocused } - .padding(vertical = 2.dp, horizontal = 5.dp) - ) { - Text(text = "Play") - } - } - } +fun CarouselCard( + modifier: Modifier = Modifier, + data: CarouselData.CarouselItem, + onClick: () -> Unit = {} +) { + AsyncImage( + modifier = modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.large) + .clickable { onClick() }, + model = data.cover, + contentDescription = null, + contentScale = ContentScale.Crop, + alignment = Alignment.TopCenter + ) } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/TopNav.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/TopNav.kt index ab18d43c..fb2e3feb 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/TopNav.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/TopNav.kt @@ -24,13 +24,16 @@ import androidx.tv.material3.Tab import androidx.tv.material3.TabRow import androidx.tv.material3.TabRowScope import androidx.tv.material3.Text +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.biliapi.entity.ugc.UgcType import dev.aaa1115910.bv.BVApp +import dev.aaa1115910.bv.util.getDisplayName @Composable fun TopNav( modifier: Modifier = Modifier, items: List, - isLargePadding:Boolean, + isLargePadding: Boolean, onSelectedChanged: (TopNavItem) -> Unit = {}, onClick: (TopNavItem) -> Unit = {} ) { @@ -114,38 +117,38 @@ enum class HomeTopNavItem(private val displayName: String) : TopNavItem { } } -enum class UgcTopNavItem(private val displayName: String) : TopNavItem { - Douga("动画"), - Game("游戏"), - Kichiku("鬼畜"), - Music("音乐"), - Dance("舞蹈"), - Cinephile("影视"), - Ent("娱乐"), - Knowledge("知识"), - Tech("科技"), - Information("资讯"), - Food("美食"), - Life("生活"), - Car("汽车"), - Fashion("时尚"), - Sports("体育"), - Animal("动物圈"); +enum class UgcTopNavItem(private val ugcType: UgcType) : TopNavItem { + Douga(UgcType.Douga), + Game(UgcType.Game), + Kichiku(UgcType.Kichiku), + Music(UgcType.Music), + Dance(UgcType.Dance), + Cinephile(UgcType.Cinephile), + Ent(UgcType.Ent), + Knowledge(UgcType.Knowledge), + Tech(UgcType.Tech), + Information(UgcType.Information), + Food(UgcType.Food), + Life(UgcType.Life), + Car(UgcType.Car), + Fashion(UgcType.Fashion), + Sports(UgcType.Sports), + Animal(UgcType.Animal); override fun getDisplayName(context: Context): String { - return displayName + return ugcType.getDisplayName(context) } } -enum class PgcTopNavItem(private val displayName: String) : TopNavItem { - Anime("番剧"), - GuoChuang("国创"), - Movie("电影"), - Documentary("纪录片"), - Tv("电视剧"), - Variety("综艺"); +enum class PgcTopNavItem(private val pgcType: PgcType) : TopNavItem { + Anime(PgcType.Anime), + GuoChuang(PgcType.GuoChuang), + Movie(PgcType.Movie), + Documentary(PgcType.Documentary), + Tv(PgcType.Tv), + Variety(PgcType.Variety); override fun getDisplayName(context: Context): String { - return displayName + return pgcType.getDisplayName(context) } } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/pgc/Carousel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/pgc/Carousel.kt deleted file mode 100644 index 5bfd746e..00000000 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/pgc/Carousel.kt +++ /dev/null @@ -1,77 +0,0 @@ -package dev.aaa1115910.bv.component.pgc - -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.togetherWith -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp -import androidx.tv.material3.Carousel -import androidx.tv.material3.ExperimentalTvMaterial3Api -import androidx.tv.material3.MaterialTheme -import coil.compose.AsyncImage -import dev.aaa1115910.biliapi.entity.pgc.PgcCarouselData -import dev.aaa1115910.bv.activities.video.SeasonInfoActivity -import dev.aaa1115910.bv.entity.proxy.ProxyArea -import dev.aaa1115910.bv.util.focusedBorder - - -@OptIn(ExperimentalTvMaterial3Api::class) -@Composable -fun PgcCarousel( - modifier: Modifier = Modifier, - data: List -) { - val context = LocalContext.current - - Carousel( - itemCount = data.size, - modifier = modifier - //.fillMaxWidth() - .height(240.dp) - .clip(MaterialTheme.shapes.large) - .focusedBorder(), - contentTransformEndToStart = - fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))), - contentTransformStartToEnd = - fadeIn(tween(1000)).togetherWith(fadeOut(tween(1000))) - ) { itemIndex -> - PgcCarouselCard( - data = data[itemIndex], - onClick = { - SeasonInfoActivity.actionStart( - context = context, - epId = data[itemIndex].episodeId, - seasonId = data[itemIndex].seasonId, - proxyArea = ProxyArea.checkProxyArea(data[itemIndex].title) - ) - } - ) - } -} - -@Composable -fun PgcCarouselCard( - modifier: Modifier = Modifier, - data: PgcCarouselData.CarouselItem, - onClick: () -> Unit = {} -) { - AsyncImage( - modifier = modifier - .fillMaxWidth() - .clip(MaterialTheme.shapes.large) - .clickable { onClick() }, - model = data.cover, - contentDescription = null, - contentScale = ContentScale.Crop, - alignment = Alignment.TopCenter - ) -} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PartitionScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PartitionScreen.kt deleted file mode 100644 index 63d8cdd7..00000000 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PartitionScreen.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.aaa1115910.bv.screen.main - -import androidx.compose.runtime.Composable -import androidx.tv.material3.Text - -@Composable -fun PartitionScreen() { - Text(text = "Partition") -} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/UgcContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/UgcContent.kt index de15afc6..bc990928 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/UgcContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/UgcContent.kt @@ -9,7 +9,6 @@ import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -22,9 +21,27 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged -import dev.aaa1115910.bv.component.DevelopingTipContent +import dev.aaa1115910.biliapi.entity.ugc.UgcType import dev.aaa1115910.bv.component.TopNav import dev.aaa1115910.bv.component.UgcTopNavItem +import dev.aaa1115910.bv.screen.main.ugc.AnimalContent +import dev.aaa1115910.bv.screen.main.ugc.CarContent +import dev.aaa1115910.bv.screen.main.ugc.CinephileContent +import dev.aaa1115910.bv.screen.main.ugc.DanceContent +import dev.aaa1115910.bv.screen.main.ugc.DougaContent +import dev.aaa1115910.bv.screen.main.ugc.EntContent +import dev.aaa1115910.bv.screen.main.ugc.FashionContent +import dev.aaa1115910.bv.screen.main.ugc.FoodContent +import dev.aaa1115910.bv.screen.main.ugc.GameContent +import dev.aaa1115910.bv.screen.main.ugc.InformationContent +import dev.aaa1115910.bv.screen.main.ugc.KichikuContent +import dev.aaa1115910.bv.screen.main.ugc.KnowledgeContent +import dev.aaa1115910.bv.screen.main.ugc.LifeContent +import dev.aaa1115910.bv.screen.main.ugc.MusicContent +import dev.aaa1115910.bv.screen.main.ugc.SportsContent +import dev.aaa1115910.bv.screen.main.ugc.TechContent +import dev.aaa1115910.bv.screen.main.ugc.UgcScaffoldState +import dev.aaa1115910.bv.screen.main.ugc.rememberUgcScaffoldState import dev.aaa1115910.bv.util.fInfo import dev.aaa1115910.bv.util.requestFocus import io.github.oshai.kotlinlogging.KotlinLogging @@ -35,27 +52,26 @@ import kotlinx.coroutines.launch fun UgcContent( modifier: Modifier = Modifier, navFocusRequester: FocusRequester, + dougaState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Douga), + gameState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Game), + kichikuState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Kichiku), + musicState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Music), + danceState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Dance), + cinephileState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Cinephile), + entState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Ent), + knowledgeState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Knowledge), + techState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Tech), + informationState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Information), + foodState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Food), + lifeState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Life), + carState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Car), + fashionState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Fashion), + sportsState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Sports), + animalState: UgcScaffoldState = rememberUgcScaffoldState(ugcType = UgcType.Animal) ) { val scope = rememberCoroutineScope() val logger = KotlinLogging.logger("UgcContent") - val dougaState = rememberLazyListState() - val gameState = rememberLazyListState() - val kichikuState = rememberLazyListState() - val musicState = rememberLazyListState() - val danceState = rememberLazyListState() - val cinephileState = rememberLazyListState() - val entState = rememberLazyListState() - val knowledgeState = rememberLazyListState() - val techState = rememberLazyListState() - val informationState = rememberLazyListState() - val foodState = rememberLazyListState() - val lifeState = rememberLazyListState() - val carState = rememberLazyListState() - val fashionState = rememberLazyListState() - val sportsState = rememberLazyListState() - val animalState = rememberLazyListState() - var selectedTab by remember { mutableStateOf(UgcTopNavItem.Douga) } var focusOnContent by remember { mutableStateOf(false) } @@ -70,28 +86,28 @@ fun UgcContent( // scroll to top scope.launch(Dispatchers.Main) { when (selectedTab) { - UgcTopNavItem.Douga -> dougaState.animateScrollToItem(0) - UgcTopNavItem.Game -> gameState.animateScrollToItem(0) - UgcTopNavItem.Kichiku -> kichikuState.animateScrollToItem(0) - UgcTopNavItem.Music -> musicState.animateScrollToItem(0) - UgcTopNavItem.Dance -> danceState.animateScrollToItem(0) - UgcTopNavItem.Cinephile -> cinephileState.animateScrollToItem(0) - UgcTopNavItem.Ent -> entState.animateScrollToItem(0) - UgcTopNavItem.Knowledge -> knowledgeState.animateScrollToItem(0) - UgcTopNavItem.Tech -> techState.animateScrollToItem(0) - UgcTopNavItem.Information -> informationState.animateScrollToItem(0) - UgcTopNavItem.Food -> foodState.animateScrollToItem(0) - UgcTopNavItem.Life -> lifeState.animateScrollToItem(0) - UgcTopNavItem.Car -> carState.animateScrollToItem(0) - UgcTopNavItem.Fashion -> fashionState.animateScrollToItem(0) - UgcTopNavItem.Sports -> sportsState.animateScrollToItem(0) - UgcTopNavItem.Animal -> animalState.animateScrollToItem(0) + UgcTopNavItem.Douga -> dougaState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Game -> gameState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Kichiku -> kichikuState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Music -> musicState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Dance -> danceState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Cinephile -> cinephileState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Ent -> entState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Knowledge -> knowledgeState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Tech -> techState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Information -> informationState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Food -> foodState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Life -> lifeState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Car -> carState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Fashion -> fashionState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Sports -> sportsState.lazyListState.animateScrollToItem(0) + UgcTopNavItem.Animal -> animalState.lazyListState.animateScrollToItem(0) } } } Scaffold( - modifier = Modifier, + modifier = modifier, topBar = { TopNav( modifier = Modifier @@ -103,22 +119,22 @@ fun UgcContent( }, onClick = { nav -> when (nav) { - UgcTopNavItem.Douga -> {} - UgcTopNavItem.Game -> {} - UgcTopNavItem.Kichiku -> {} - UgcTopNavItem.Music -> {} - UgcTopNavItem.Dance -> {} - UgcTopNavItem.Cinephile -> {} - UgcTopNavItem.Ent -> {} - UgcTopNavItem.Knowledge -> {} - UgcTopNavItem.Tech -> {} - UgcTopNavItem.Information -> {} - UgcTopNavItem.Food -> {} - UgcTopNavItem.Life -> {} - UgcTopNavItem.Car -> {} - UgcTopNavItem.Fashion -> {} - UgcTopNavItem.Sports -> {} - UgcTopNavItem.Animal -> {} + UgcTopNavItem.Douga -> dougaState.reloadAll() + UgcTopNavItem.Game -> gameState.reloadAll() + UgcTopNavItem.Kichiku -> kichikuState.reloadAll() + UgcTopNavItem.Music -> musicState.reloadAll() + UgcTopNavItem.Dance -> danceState.reloadAll() + UgcTopNavItem.Cinephile -> cinephileState.reloadAll() + UgcTopNavItem.Ent -> entState.reloadAll() + UgcTopNavItem.Knowledge -> knowledgeState.reloadAll() + UgcTopNavItem.Tech -> techState.reloadAll() + UgcTopNavItem.Information -> informationState.reloadAll() + UgcTopNavItem.Food -> foodState.reloadAll() + UgcTopNavItem.Life -> lifeState.reloadAll() + UgcTopNavItem.Car -> carState.reloadAll() + UgcTopNavItem.Fashion -> fashionState.reloadAll() + UgcTopNavItem.Sports -> sportsState.reloadAll() + UgcTopNavItem.Animal -> animalState.reloadAll() } } ) @@ -144,22 +160,22 @@ fun UgcContent( } ) { screen -> when (screen) { - UgcTopNavItem.Douga -> DevelopingTipContent() - UgcTopNavItem.Game -> DevelopingTipContent() - UgcTopNavItem.Kichiku -> DevelopingTipContent() - UgcTopNavItem.Music -> DevelopingTipContent() - UgcTopNavItem.Dance -> DevelopingTipContent() - UgcTopNavItem.Cinephile -> DevelopingTipContent() - UgcTopNavItem.Ent -> DevelopingTipContent() - UgcTopNavItem.Knowledge -> DevelopingTipContent() - UgcTopNavItem.Tech -> DevelopingTipContent() - UgcTopNavItem.Information -> DevelopingTipContent() - UgcTopNavItem.Food -> DevelopingTipContent() - UgcTopNavItem.Life -> DevelopingTipContent() - UgcTopNavItem.Car -> DevelopingTipContent() - UgcTopNavItem.Fashion -> DevelopingTipContent() - UgcTopNavItem.Sports -> DevelopingTipContent() - UgcTopNavItem.Animal -> DevelopingTipContent() + UgcTopNavItem.Douga -> DougaContent(state = dougaState) + UgcTopNavItem.Game -> GameContent(state = gameState) + UgcTopNavItem.Kichiku -> KichikuContent(state = kichikuState) + UgcTopNavItem.Music -> MusicContent(state = musicState) + UgcTopNavItem.Dance -> DanceContent(state = danceState) + UgcTopNavItem.Cinephile -> CinephileContent(state = cinephileState) + UgcTopNavItem.Ent -> EntContent(state = entState) + UgcTopNavItem.Knowledge -> KnowledgeContent(state = knowledgeState) + UgcTopNavItem.Tech -> TechContent(state = techState) + UgcTopNavItem.Information -> InformationContent(state = informationState) + UgcTopNavItem.Food -> FoodContent(state = foodState) + UgcTopNavItem.Life -> LifeContent(state = lifeState) + UgcTopNavItem.Car -> CarContent(state = carState) + UgcTopNavItem.Fashion -> FashionContent(state = fashionState) + UgcTopNavItem.Sports -> SportsContent(state = sportsState) + UgcTopNavItem.Animal -> AnimalContent(state = animalState) } } } diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/PgcCommon.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/PgcCommon.kt index 9b2d689f..4d012d6a 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/PgcCommon.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/PgcCommon.kt @@ -57,7 +57,7 @@ import dev.aaa1115910.biliapi.entity.pgc.PgcType import dev.aaa1115910.biliapi.http.SeasonIndexType import dev.aaa1115910.bv.BVApp import dev.aaa1115910.bv.activities.video.SeasonInfoActivity -import dev.aaa1115910.bv.component.pgc.PgcCarousel +import dev.aaa1115910.bv.component.PgcCarousel import dev.aaa1115910.bv.component.videocard.SeasonCard import dev.aaa1115910.bv.entity.carddata.SeasonCardData import dev.aaa1115910.bv.entity.proxy.ProxyArea diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/AnimalContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/AnimalContent.kt new file mode 100644 index 00000000..226064c2 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/AnimalContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun AnimalContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { AnimalChildRegionButtons() }, + ) +} + +@Composable +fun AnimalChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.AnimalCat, UgcType.AnimalDog, UgcType.AnimalReptiles, UgcType.AnimalWildAnima, + UgcType.AnimalSecondEdition, UgcType.AnimalComposite + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/CarContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/CarContent.kt new file mode 100644 index 00000000..fb558e90 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/CarContent.kt @@ -0,0 +1,32 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun CarContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { CarChildRegionButtons() } + ) +} + +@Composable +fun CarChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.CarKnowledge, UgcType.CarStrategy, UgcType.CarNewEnergyVehicle, + UgcType.CarRacing, UgcType.CarModifiedVehicle, UgcType.CarMotorcycle, + UgcType.CarTouringCar, UgcType.CarLife + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/CinephileContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/CinephileContent.kt new file mode 100644 index 00000000..6d032394 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/CinephileContent.kt @@ -0,0 +1,32 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun CinephileContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { CinephileChildRegionButtons() } + ) +} + +@Composable +fun CinephileChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.CinephileCinecism, UgcType.CinephileNibtage, UgcType.CinephileMashup, + UgcType.CinephileAiImagine, UgcType.CinephileTrailerInfo, UgcType.CinephileShortPlay, + UgcType.CinephileShortFilm, UgcType.CinephileComperhensive + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/DanceContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/DanceContent.kt new file mode 100644 index 00000000..3fd1b3db --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/DanceContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun DanceContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { DanceChildRegionButtons() } + ) +} + +@Composable +fun DanceChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.DanceOtaku, UgcType.DanceHiphop, UgcType.DanceStar, UgcType.DanceChina, + UgcType.DanceGestures, UgcType.DanceThreeD, UgcType.DanceDemo + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/DougaContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/DougaContent.kt new file mode 100644 index 00000000..01b27145 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/DougaContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun DougaContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { DougaChildRegionButtons() } + ) +} + +@Composable +fun DougaChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.DougaMad, UgcType.DougaMmd, UgcType.DougaHandDrawn, UgcType.DougaVoice, + UgcType.DougaGarageKit, UgcType.DougaTokusatsu, UgcType.DougaAcgnTalks, UgcType.DougaOther + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/EntContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/EntContent.kt new file mode 100644 index 00000000..1508cc16 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/EntContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun EntContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { EntChildRegionButtons() } + ) +} + +@Composable +fun EntChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.EntTalker, UgcType.EntCpRecommendation, UgcType.EntBeauty, UgcType.EntFans, + UgcType.EntEntertainmentNews, UgcType.EntCelebrity, UgcType.EntVariety + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/FashionContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/FashionContent.kt new file mode 100644 index 00000000..e28c9008 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/FashionContent.kt @@ -0,0 +1,30 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun FashionContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { FashionChildRegionButtons() } + ) +} + +@Composable +fun FashionChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.FashionMakeup, UgcType.FashionCos, UgcType.FashionClothing, UgcType.FashionCatwalk + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/FoodContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/FoodContent.kt new file mode 100644 index 00000000..b0487ee4 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/FoodContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun FoodContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { FoodChildRegionButtons() } + ) +} + +@Composable +fun FoodChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.FoodMake, UgcType.FoodDetective, UgcType.FoodMeasurement, + UgcType.FoodRural, UgcType.FoodRecord + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/GameContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/GameContent.kt new file mode 100644 index 00000000..ee2c7a53 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/GameContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun GameContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { GameChildRegionButtons() } + ) +} + +@Composable +fun GameChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.GameStandAlone, UgcType.GameESports, UgcType.GameMobile, UgcType.GameOnline, + UgcType.GameBoard, UgcType.GameGmv, UgcType.GameMusic, UgcType.GameMugen + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/InformationContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/InformationContent.kt new file mode 100644 index 00000000..9cd6b0db --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/InformationContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun InformationContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { InformationChildRegionButtons() } + ) +} + +@Composable +fun InformationChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.InformationHotspot, UgcType.InformationGlobal, + UgcType.InformationSocial, UgcType.InformationMultiple + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/KichikuContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/KichikuContent.kt new file mode 100644 index 00000000..06641385 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/KichikuContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun KichikuContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { KichikuChildRegionButtons() } + ) +} + +@Composable +fun KichikuChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.KichikuGuide, UgcType.KichikuMad, UgcType.KichikuManualVocaloid, + UgcType.KichikuTheatre, UgcType.KichikuCourse + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/KnowledgeContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/KnowledgeContent.kt new file mode 100644 index 00000000..b7c6bbe3 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/KnowledgeContent.kt @@ -0,0 +1,32 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun KnowledgeContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { KnowledgeChildRegionButtons() } + ) +} + +@Composable +fun KnowledgeChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.KnowledgeScience, UgcType.KnowledgeSocialScience, UgcType.KnowledgeHumanity, + UgcType.KnowledgeBusiness, UgcType.KnowledgeCampus, UgcType.KnowledgeCareer, + UgcType.KnowledgeDesign, UgcType.KnowledgeSkill + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/LifeContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/LifeContent.kt new file mode 100644 index 00000000..be8f1517 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/LifeContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun LifeContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { LifeChildRegionButtons() } + ) +} + +@Composable +fun LifeChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.LifeFunny, UgcType.LifeParenting, UgcType.LifeTravel, UgcType.LiseRuralLife, + UgcType.LifeHome, UgcType.LifeHandMake, UgcType.LifePainting, UgcType.LifeDaily + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/MuiscContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/MuiscContent.kt new file mode 100644 index 00000000..9c6d92c2 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/MuiscContent.kt @@ -0,0 +1,32 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun MusicContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { MusicChildRegionButtons() } + ) +} + +@Composable +fun MusicChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.MusicOriginal, UgcType.MusicLive, UgcType.MusicCover, UgcType.MusicPerform, + UgcType.MusicCommentary, UgcType.MusicVocaloidUtau, UgcType.MusicMv, UgcType.MusicFanVideos, + UgcType.MusicAiMusic, UgcType.MusicRadio, UgcType.MusicTutorial, UgcType.MusicOther + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/SportsContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/SportsContent.kt new file mode 100644 index 00000000..19fb2529 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/SportsContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun SportsContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { SportsChildRegionButtons() } + ) +} + +@Composable +fun SportsChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.SportsBasketball, UgcType.SportsFootball, UgcType.SportsAerobics, + UgcType.SportsAthletic, UgcType.SportsCulture, UgcType.SportsComprehensive + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/TechContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/TechContent.kt new file mode 100644 index 00000000..96516703 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/TechContent.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.entity.ugc.UgcType + +@Composable +fun TechContent( + modifier: Modifier = Modifier, + state: UgcScaffoldState +) { + UgcRegionScaffold( + modifier = modifier, + state = state, + childRegionButtons = { TechChildRegionButtons() } + ) +} + +@Composable +fun TechChildRegionButtons(modifier: Modifier = Modifier) { + val ugcTypes = listOf( + UgcType.TechDigital, UgcType.TechApplication, UgcType.TechComputerTech, + UgcType.TechIndustry, UgcType.TechDiy + ) + + UgcChildRegionButtons( + modifier = modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/UgcChildRegionButtons.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/UgcChildRegionButtons.kt new file mode 100644 index 00000000..be66fc25 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/UgcChildRegionButtons.kt @@ -0,0 +1,90 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.tv.material3.ExperimentalTvMaterial3Api +import androidx.tv.material3.SuggestionChip +import androidx.tv.material3.Text +import dev.aaa1115910.biliapi.entity.ugc.UgcType +import dev.aaa1115910.bv.component.createCustomInitialFocusRestorerModifiers +import dev.aaa1115910.bv.component.ifElse +import dev.aaa1115910.bv.ui.theme.BVTheme +import dev.aaa1115910.bv.util.fInfo +import dev.aaa1115910.bv.util.getDisplayName +import dev.aaa1115910.bv.util.toast +import io.github.oshai.kotlinlogging.KotlinLogging + +@OptIn(ExperimentalTvMaterial3Api::class) +@Composable +fun UgcChildRegionButtons( + modifier: Modifier = Modifier, + childUgcTypes: List +) { + val context = LocalContext.current + val logger = KotlinLogging.logger { } + + val onClickChildRegion: (UgcType) -> Unit = { ugcType -> + logger.fInfo { "onClickChildRegion: $ugcType" } + "占位".toast(context) + } + + UgcChildRegionButtonsContent( + modifier = modifier + .padding(vertical = 12.dp), + childUgcTypes = childUgcTypes, + onClickChildRegion = onClickChildRegion + ) +} + +@OptIn(ExperimentalTvMaterial3Api::class) +@Composable +fun UgcChildRegionButtonsContent( + modifier: Modifier = Modifier, + childUgcTypes: List, + onClickChildRegion: (UgcType) -> Unit +) { + val context = LocalContext.current + val focusRestorerModifiers = createCustomInitialFocusRestorerModifiers() + + LazyRow( + modifier = modifier.then(focusRestorerModifiers.parentModifier), + contentPadding = PaddingValues(horizontal = 24.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally), + ) { + itemsIndexed(items = childUgcTypes) { index, ugcType -> + SuggestionChip( + modifier = Modifier.ifElse(index == 0, focusRestorerModifiers.childModifier), + onClick = { onClickChildRegion(ugcType) } + ) { + Text(text = ugcType.getDisplayName(context)) + } + } + } +} + +@Preview(device = "id:tv_1080p") +@Composable +private fun UgcChildRegionButtonsPreview() { + val ugcTypes = listOf( + UgcType.Douga, UgcType.DougaMad, UgcType.DougaMmd, UgcType.DougaHandDrawn, + UgcType.DougaVoice, UgcType.DougaGarageKit, UgcType.DougaTokusatsu, + UgcType.DougaAcgnTalks, UgcType.DougaOther + ) + + BVTheme { + UgcChildRegionButtons( + modifier = Modifier.fillMaxWidth(), + childUgcTypes = ugcTypes + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/UgcCommon.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/UgcCommon.kt new file mode 100644 index 00000000..5467e67b --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/UgcCommon.kt @@ -0,0 +1,263 @@ +package dev.aaa1115910.bv.screen.main.ugc + +import android.content.Context +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import dev.aaa1115910.biliapi.entity.CarouselData +import dev.aaa1115910.biliapi.entity.ugc.UgcItem +import dev.aaa1115910.biliapi.entity.ugc.UgcType +import dev.aaa1115910.biliapi.entity.ugc.region.UgcRegionPage +import dev.aaa1115910.biliapi.repositories.UgcRepository +import dev.aaa1115910.bv.activities.video.VideoInfoActivity +import dev.aaa1115910.bv.component.UgcCarousel +import dev.aaa1115910.bv.component.videocard.SmallVideoCard +import dev.aaa1115910.bv.entity.carddata.VideoCardData +import dev.aaa1115910.bv.util.fInfo +import dev.aaa1115910.bv.util.toast +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.compose.koinInject + +@Composable +fun UgcRegionScaffold( + modifier: Modifier = Modifier, + state: UgcScaffoldState, + childRegionButtons: (@Composable () -> Unit)? = null +) { + val context = LocalContext.current + var currentFocusedIndex by remember { mutableIntStateOf(0) } + val shouldLoadMore by remember { + derivedStateOf { currentFocusedIndex + 24 > state.ugcItems.size } + } + + LaunchedEffect(Unit) { if (state.ugcItems.isEmpty()) state.initUgcRegionData() } + LaunchedEffect(shouldLoadMore) { + if (shouldLoadMore) { + state.loadMore() + currentFocusedIndex = -100 + } + } + + LazyColumn( + modifier = modifier, + state = state.lazyListState + ) { + if (state.showCarousel) { + item { + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.Center + ) { + UgcCarousel( + modifier = Modifier + .width(880.dp) + .padding(32.dp, 0.dp), + data = state.carouselItems + ) + } + } + } + + if (childRegionButtons != null) { + item { + childRegionButtons() + } + } else { + item { + Spacer( + modifier = Modifier + .fillMaxWidth() + .height(12.dp) + ) + } + } + + gridItems( + data = state.ugcItems, + columnCount = 4, + modifier = Modifier.padding(horizontal = 24.dp, vertical = 12.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp), + itemContent = { index, item -> + SmallVideoCard( + data = VideoCardData( + avid = item.aid, + title = item.title, + cover = item.cover, + play = item.play, + danmaku = item.danmaku, + upName = item.author, + time = item.duration * 1000L + ), + onClick = { VideoInfoActivity.actionStart(context, item.aid) }, + onFocus = { currentFocusedIndex = index } + ) + } + ) + } +} + +fun LazyListScope.gridItems( + data: List, + key: ((index: Int) -> Any)? = null, + columnCount: Int, + modifier: Modifier = Modifier, + verticalAlignment: Alignment.Vertical = Alignment.Top, + horizontalArrangement: Arrangement.Horizontal = Arrangement.Start, + itemContent: @Composable BoxScope.(Int, T) -> Unit, +) { + val size = data.count() + val rows = if (size == 0) 0 else 1 + (size - 1) / columnCount + items(rows, key = key) { rowIndex -> + Row( + verticalAlignment = verticalAlignment, + horizontalArrangement = horizontalArrangement, + modifier = modifier + ) { + for (columnIndex in 0 until columnCount) { + val itemIndex = rowIndex * columnCount + columnIndex + if (itemIndex < size) { + Box( + modifier = Modifier.weight(1F, fill = true), + propagateMinConstraints = true + ) { + itemContent(itemIndex, data[itemIndex]) + } + } else { + Spacer(Modifier.weight(1F, fill = true)) + } + } + } + } +} + +data class UgcScaffoldState( + val context: Context, + val scope: CoroutineScope, + val lazyListState: LazyListState, + val ugcType: UgcType, + private val ugcRepository: UgcRepository +) { + companion object { + val logger = KotlinLogging.logger { } + } + + val carouselItems = mutableStateListOf() + val ugcItems = mutableStateListOf() + var nextPage by mutableStateOf(UgcRegionPage()) + var hasMore by mutableStateOf(true) + var updating by mutableStateOf(false) + var showCarousel by mutableStateOf(true) + + suspend fun initUgcRegionData() { + loadUgcRegionData() + loadMore() + } + + suspend fun loadUgcRegionData() { + if (!hasMore && updating) return + updating = true + logger.fInfo { "load ugc $ugcType region data" } + runCatching { + val data = ugcRepository.getRegionData(ugcType) + carouselItems.clear() + ugcItems.clear() + carouselItems.addAll(data.carouselData?.items ?: emptyList()) + ugcItems.addAll(data.items) + nextPage = data.next + showCarousel = carouselItems.isNotEmpty() + }.onFailure { + logger.fInfo { "load $ugcType data failed: ${it.stackTraceToString()}" } + withContext(Dispatchers.Main) { + "加载 $ugcType 数据失败: ${it.message}".toast(context) + } + } + hasMore = true + updating = false + } + + fun reloadAll() { + logger.fInfo { "reload all $ugcType data" } + scope.launch(Dispatchers.IO) { + nextPage = UgcRegionPage() + hasMore = true + showCarousel = true + carouselItems.clear() + ugcItems.clear() + initUgcRegionData() + } + } + + suspend fun loadMore() { + if (!hasMore && updating) return + updating = true + runCatching { + val data = ugcRepository.getRegionMoreData(ugcType) + ugcItems.addAll(data.items) + nextPage = data.next + hasMore = data.items.isNotEmpty() + }.onFailure { + logger.fInfo { "load more $ugcType data failed: ${it.stackTraceToString()}" } + withContext(Dispatchers.Main) { + "加载 $ugcType 更多推荐失败: ${it.message}".toast(context) + } + } + updating = false + } +} + +@Composable +fun rememberUgcScaffoldState( + context: Context = LocalContext.current, + scope: CoroutineScope = rememberCoroutineScope(), + lazyListState: LazyListState = rememberLazyListState(), + ugcType: UgcType, + ugcRepository: UgcRepository = koinInject() +): UgcScaffoldState { + return remember( + context, + scope, + lazyListState, + ugcType, + ugcRepository + ) { + UgcScaffoldState( + context = context, + scope = scope, + lazyListState = lazyListState, + ugcType = ugcType, + ugcRepository = ugcRepository + ) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/util/UgcTypeExtends.kt b/app/src/main/kotlin/dev/aaa1115910/bv/util/UgcTypeExtends.kt new file mode 100644 index 00000000..1aed2090 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/util/UgcTypeExtends.kt @@ -0,0 +1,148 @@ +package dev.aaa1115910.bv.util + +import android.content.Context +import dev.aaa1115910.biliapi.entity.ugc.UgcType +import dev.aaa1115910.bv.R + +fun UgcType.getDisplayName(context: Context) = when (this) { + UgcType.Douga -> R.string.ugc_type_douga + UgcType.DougaMad -> R.string.ugc_type_douga_mad + UgcType.DougaMmd -> R.string.ugc_type_douga_mmd + UgcType.DougaHandDrawn -> R.string.ugc_type_douga_hand_drawn + UgcType.DougaVoice -> R.string.ugc_type_douga_voice + UgcType.DougaGarageKit -> R.string.ugc_type_douga_garage_kit + UgcType.DougaTokusatsu -> R.string.ugc_type_douga_tokusatsu + UgcType.DougaAcgnTalks -> R.string.ugc_type_douga_acgn_talks + UgcType.DougaOther -> R.string.ugc_type_douga_other + + UgcType.Game -> R.string.ugc_type_game + UgcType.GameStandAlone -> R.string.ugc_type_game_stand_alone + UgcType.GameESports -> R.string.ugc_type_game_e_sports + UgcType.GameMobile -> R.string.ugc_type_game_mobile + UgcType.GameOnline -> R.string.ugc_type_game_online + UgcType.GameBoard -> R.string.ugc_type_game_board + UgcType.GameGmv -> R.string.ugc_type_game_gmv + UgcType.GameMusic -> R.string.ugc_type_game_music + UgcType.GameMugen -> R.string.ugc_type_game_mugen + + UgcType.Kichiku -> R.string.ugc_type_kichiku + UgcType.KichikuGuide -> R.string.ugc_type_kichiku_guild + UgcType.KichikuMad -> R.string.ugc_type_kichiku_mad + UgcType.KichikuManualVocaloid -> R.string.ugc_type_kichiku_manual_vocaloid + UgcType.KichikuTheatre -> R.string.ugc_type_kichiku_theatre + UgcType.KichikuCourse -> R.string.ugc_type_kichiku_course + + UgcType.Music -> R.string.ugc_type_music + UgcType.MusicOriginal -> R.string.ugc_type_music_original + UgcType.MusicLive -> R.string.ugc_type_music_live + UgcType.MusicCover -> R.string.ugc_type_music_cover + UgcType.MusicPerform -> R.string.ugc_type_music_perform + UgcType.MusicCommentary -> R.string.ugc_type_music_commentary + UgcType.MusicVocaloidUtau -> R.string.ugc_type_music_vocaloid_utau + UgcType.MusicMv -> R.string.ugc_type_music_mv + UgcType.MusicFanVideos -> R.string.ugc_type_music_fan_videos + UgcType.MusicAiMusic -> R.string.ugc_type_music_ai_music + UgcType.MusicRadio -> R.string.ugc_type_music_radio + UgcType.MusicTutorial -> R.string.ugc_type_music_tutorial + UgcType.MusicOther -> R.string.ugc_type_music_other + + UgcType.Dance -> R.string.ugc_type_dance + UgcType.DanceOtaku -> R.string.ugc_type_dance_otaku + UgcType.DanceHiphop -> R.string.ugc_type_dance_hiphop + UgcType.DanceStar -> R.string.ugc_type_dance_star + UgcType.DanceChina -> R.string.ugc_type_dance_china + UgcType.DanceGestures -> R.string.ugc_type_dance_gestures + UgcType.DanceThreeD -> R.string.ugc_type_dance_three_d + UgcType.DanceDemo -> R.string.ugc_type_dance_demo + + UgcType.Cinephile -> R.string.ugc_type_cinephile + UgcType.CinephileCinecism -> R.string.ugc_type_cinephile_cinecism + UgcType.CinephileNibtage -> R.string.ugc_type_cinephile_nibtage + UgcType.CinephileMashup -> R.string.ugc_type_cinephile_mashup + UgcType.CinephileAiImagine -> R.string.ugc_type_cinephile_ai_imagine + UgcType.CinephileTrailerInfo -> R.string.ugc_type_cinephile_trailer_info + UgcType.CinephileShortPlay -> R.string.ugc_type_cinephile_short_play + UgcType.CinephileShortFilm -> R.string.ugc_type_cinephile_short_film + UgcType.CinephileComperhensive -> R.string.ugc_type_cinephile_comperhensive + + UgcType.Ent -> R.string.ugc_type_ent + UgcType.EntTalker -> R.string.ugc_type_ent_talker + UgcType.EntCpRecommendation -> R.string.ugc_type_ent_cp_recommendation + UgcType.EntBeauty -> R.string.ugc_type_ent_beauty + UgcType.EntFans -> R.string.ugc_type_ent_fans + UgcType.EntEntertainmentNews -> R.string.ugc_type_ent_entertainment_news + UgcType.EntCelebrity -> R.string.ugc_type_ent_celebrity + UgcType.EntVariety -> R.string.ugc_type_ent_variety + + UgcType.Knowledge -> R.string.ugc_type_knowledge + UgcType.KnowledgeScience -> R.string.ugc_type_knowledge_science + UgcType.KnowledgeSocialScience -> R.string.ugc_type_knowledge_social_science + UgcType.KnowledgeHumanity -> R.string.ugc_type_knowledge_humanity + UgcType.KnowledgeBusiness -> R.string.ugc_type_knowledge_business + UgcType.KnowledgeCampus -> R.string.ugc_type_knowledge_campus + UgcType.KnowledgeCareer -> R.string.ugc_type_knowledge_career + UgcType.KnowledgeDesign -> R.string.ugc_type_knowledge_design + UgcType.KnowledgeSkill -> R.string.ugc_type_knowledge_skill + + UgcType.Tech -> R.string.ugc_type_tech + UgcType.TechDigital -> R.string.ugc_type_tech_digital + UgcType.TechApplication -> R.string.ugc_type_tech_application + UgcType.TechComputerTech -> R.string.ugc_type_tech_computer_tech + UgcType.TechIndustry -> R.string.ugc_type_tech_industry + UgcType.TechDiy -> R.string.ugc_type_tech_diy + + UgcType.Information -> R.string.ugc_type_information + UgcType.InformationHotspot -> R.string.ugc_type_information_hotspot + UgcType.InformationGlobal -> R.string.ugc_type_information_global + UgcType.InformationSocial -> R.string.ugc_type_information_social + UgcType.InformationMultiple -> R.string.ugc_type_information_multiple + + UgcType.Food -> R.string.ugc_type_food + UgcType.FoodMake -> R.string.ugc_type_food_make + UgcType.FoodDetective -> R.string.ugc_type_food_detective + UgcType.FoodMeasurement -> R.string.ugc_type_food_measurement + UgcType.FoodRural -> R.string.ugc_type_food_rural + UgcType.FoodRecord -> R.string.ugc_type_food_record + + UgcType.Life -> R.string.ugc_type_life + UgcType.LifeFunny -> R.string.ugc_type_life_funny + UgcType.LifeParenting -> R.string.ugc_type_life_parenting + UgcType.LifeTravel -> R.string.ugc_type_life_travel + UgcType.LiseRuralLife -> R.string.ugc_type_life_rural_life + UgcType.LifeHome -> R.string.ugc_type_life_home + UgcType.LifeHandMake -> R.string.ugc_type_life_hand_make + UgcType.LifePainting -> R.string.ugc_type_life_painting + UgcType.LifeDaily -> R.string.ugc_type_life_daily + + UgcType.Car -> R.string.ugc_type_car + UgcType.CarKnowledge -> R.string.ugc_type_car_knowledge + UgcType.CarStrategy -> R.string.ugc_type_car_strategy + UgcType.CarNewEnergyVehicle -> R.string.ugc_type_car_new_energy_vehicle + UgcType.CarRacing -> R.string.ugc_type_car_racing + UgcType.CarModifiedVehicle -> R.string.ugc_type_car_modified_vehicle + UgcType.CarMotorcycle -> R.string.ugc_type_car_motorcycle + UgcType.CarTouringCar -> R.string.ugc_type_car_touring_car + UgcType.CarLife -> R.string.ugc_type_car_life + + UgcType.Fashion -> R.string.ugc_type_fashion + UgcType.FashionMakeup -> R.string.ugc_type_fashion_makeup + UgcType.FashionCos -> R.string.ugc_type_fashion_cos + UgcType.FashionClothing -> R.string.ugc_type_fashion_clothing + UgcType.FashionCatwalk -> R.string.ugc_type_fashion_catwalk + + UgcType.Sports -> R.string.ugc_type_sports + UgcType.SportsBasketball -> R.string.ugc_type_sports_basketball + UgcType.SportsFootball -> R.string.ugc_type_sports_football + UgcType.SportsAerobics -> R.string.ugc_type_sports_aerobics + UgcType.SportsAthletic -> R.string.ugc_type_sports_athletic + UgcType.SportsCulture -> R.string.ugc_type_sports_culture + UgcType.SportsComprehensive -> R.string.ugc_type_sports_comprehensive + + UgcType.Animal -> R.string.ugc_type_animal + UgcType.AnimalCat -> R.string.ugc_type_animal_cat + UgcType.AnimalDog -> R.string.ugc_type_animal_dog + UgcType.AnimalReptiles -> R.string.ugc_type_animal_reptiles + UgcType.AnimalWildAnima -> R.string.ugc_type_animal_wild_anima + UgcType.AnimalSecondEdition -> R.string.ugc_type_animal_second_edition + UgcType.AnimalComposite -> R.string.ugc_type_animal_composite +}.stringRes(context) \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/PopularViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/PopularViewModel.kt index 0f5e8f27..763212c1 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/PopularViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/PopularViewModel.kt @@ -2,8 +2,8 @@ package dev.aaa1115910.bv.viewmodel.home import androidx.compose.runtime.mutableStateListOf import androidx.lifecycle.ViewModel -import dev.aaa1115910.biliapi.entity.rank.PopularVideo import dev.aaa1115910.biliapi.entity.rank.PopularVideoPage +import dev.aaa1115910.biliapi.entity.ugc.UgcItem import dev.aaa1115910.biliapi.repositories.RecommendVideoRepository import dev.aaa1115910.bv.BVApp import dev.aaa1115910.bv.util.Prefs @@ -18,7 +18,7 @@ class PopularViewModel( private val recommendVideoRepository: RecommendVideoRepository ) : ViewModel() { private val logger = KotlinLogging.logger {} - val popularVideoList = mutableStateListOf() + val popularVideoList = mutableStateListOf() private var nextPage = PopularVideoPage() var loading = false diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/RecommendViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/RecommendViewModel.kt index a0328d84..ebcb44a6 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/RecommendViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/RecommendViewModel.kt @@ -2,8 +2,8 @@ package dev.aaa1115910.bv.viewmodel.home import androidx.compose.runtime.mutableStateListOf import androidx.lifecycle.ViewModel -import dev.aaa1115910.biliapi.entity.home.RecommendItem import dev.aaa1115910.biliapi.entity.home.RecommendPage +import dev.aaa1115910.biliapi.entity.ugc.UgcItem import dev.aaa1115910.biliapi.repositories.RecommendVideoRepository import dev.aaa1115910.bv.BVApp import dev.aaa1115910.bv.util.Prefs @@ -18,7 +18,7 @@ class RecommendViewModel( private val recommendVideoRepository: RecommendVideoRepository ) : ViewModel() { private val logger = KotlinLogging.logger {} - val recommendVideoList = mutableStateListOf() + val recommendVideoList = mutableStateListOf() private var nextPage = RecommendPage() var loading = false diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcViewModel.kt index f1da75af..89c417d5 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcViewModel.kt @@ -7,7 +7,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import dev.aaa1115910.biliapi.entity.pgc.PgcCarouselData +import dev.aaa1115910.biliapi.entity.CarouselData import dev.aaa1115910.biliapi.entity.pgc.PgcFeedData import dev.aaa1115910.biliapi.entity.pgc.PgcItem import dev.aaa1115910.biliapi.entity.pgc.PgcType @@ -32,7 +32,7 @@ abstract class PgcViewModel( /** * 轮播图 */ - val carouselItems = mutableStateListOf() + val carouselItems = mutableStateListOf() /** * 猜你喜欢 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4c0190ad..08cfae31 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -293,6 +293,132 @@ 推荐 搜索 + 动物圈 + 喵星人 + 动物综合 + 汪星人 + 小宠异宠 + 动物二创 + 野生动物 + 汽车 + 汽车知识科普 + 汽车生活 + 改装玩车 + 摩托车 + 新能源车 + 赛车 + 购车攻略 + 房车 + 影视 + AI影像 + 影视杂谈 + 影视综合 + 影视整活 + 影视剪辑 + 短片 + 小剧场 + 预告·资讯 + 舞蹈 + 国风舞蹈 + 舞蹈教程 + 颜值·网红舞 + 街舞 + 宅舞 + 明星舞蹈 + 舞蹈综合 + 动画 + 动漫杂谈 + 手办·模玩 + 同人·手书 + MAD·AMV + MMD·3D + 综合 + 特摄 + 配音 + 娱乐 + 颜值安利 + 明星综合 + CP安利 + 娱乐资讯 + 娱乐粉丝创作 + 娱乐杂谈 + 综艺 + 时尚 + 时尚潮流 + 穿搭 + 仿妆cos + 美妆护肤 + 美食 + 美食侦探 + 美食制作 + 美食测评 + 美食记录 + 田园美食 + 游戏 + 桌游棋牌 + 电子竞技 + GMV + 手机游戏 + Mugen + 音游 + 网络游戏 + 单机游戏 + 资讯 + 环球 + 热点 + 综合 + 社会 + 鬼畜 + 教程演示 + 鬼畜调教 + 音MAD + 人力VOCALOID + 鬼畜剧场 + 知识 + 财经商业 + 校园学习 + 职业职场 + 设计·创意 + 人文历史 + 科学科普 + 野生技术协会 + 社科·法律·心理 + 生活 + 日常 + 搞笑 + 手工 + 家居房产 + 绘画 + 亲子 + 三农 + 出行 + 音乐 + AI音乐 + 乐评盘点 + 翻唱 + 音乐粉丝饭拍 + 音乐现场 + MV + 原创音乐 + 音乐综合 + 演奏 + 电台 + 音乐教学 + VOCALOID·UTAU + 运动 + 健身 + 竞技体育 + 篮球 + 运动综合 + 运动文化 + 足球 + 科技 + 软件应用 + 计算机技术 + 数码 + 极客DIY + 科工机械 + 我追的番 私人藏品 已关注 diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CarouselData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CarouselData.kt new file mode 100644 index 00000000..7e65c4a3 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CarouselData.kt @@ -0,0 +1,77 @@ +package dev.aaa1115910.biliapi.entity + +import dev.aaa1115910.biliapi.util.UrlUtil +import dev.aaa1115910.biliapi.util.toBv +import io.ktor.http.Url + +data class CarouselData( + val items: List +) { + companion object { + fun fromPgcWebInitialStateData(data: dev.aaa1115910.biliapi.http.entity.pgc.PgcWebInitialStateData): CarouselData { + val result = mutableListOf() + var isMovie = false + // 电影板块里的轮播图数据里没有直接包含 episodeId 和 seasonId + if (data.modules.banner.moduleId == 1668) isMovie = true + data.modules.banner.items.filter { + it.episodeId != null || (isMovie && it.link.contains("bangumi/play/ep")) + }.forEach { + var cover = it.bigCover ?: it.cover + if (cover.startsWith("//")) cover = "https:$cover" + result.add( + CarouselItem( + cover = cover, + title = it.title, + seasonId = it.seasonId ?: -1, + episodeId = it.episodeId + ?: Url(it.link).pathSegments.last().substring(2).toInt() + ) + ) + } + return CarouselData(result) + } + + fun fromUgcRegionDynamicBanner(data: dev.aaa1115910.biliapi.http.entity.region.RegionDynamic.Banner): CarouselData { + val result = mutableListOf() + data.top.forEach { top -> + if (!UrlUtil.isVideoUrl(top.uri)) return@forEach + val avid = UrlUtil.parseAidFromUrl(top.uri) + val bvid = avid.toBv() + result.add( + CarouselItem( + cover = top.image, + title = top.title, + avid = avid, + bvid = bvid + ) + ) + } + return CarouselData(result) + } + + fun fromUgcRegionLocs(data: dev.aaa1115910.biliapi.http.entity.region.RegionLocs): CarouselData { + val result = mutableListOf() + data.data.forEach { (_, value) -> + value.filter { it.url.contains("/video/") }.forEach { item -> + result.add( + CarouselItem( + cover = item.pic, + title = item.title, + bvid = Url(item.url).pathSegments.last() + ) + ) + } + } + return CarouselData(result) + } + } + + data class CarouselItem( + val cover: String, + val title: String, + val seasonId: Int? = null, + val episodeId: Int? = null, + val avid: Long? = null, + val bvid: String? = null + ) +} diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/home/RecommendData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/home/RecommendData.kt index 329060fa..2e7b73ca 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/home/RecommendData.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/home/RecommendData.kt @@ -1,7 +1,9 @@ package dev.aaa1115910.biliapi.entity.home +import dev.aaa1115910.biliapi.entity.ugc.UgcItem + data class RecommendData( - val items: List, + val items: List, val nextPage: RecommendPage ) diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/home/RecommendItem.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/home/RecommendItem.kt deleted file mode 100644 index b1e59ed5..00000000 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/home/RecommendItem.kt +++ /dev/null @@ -1,60 +0,0 @@ -package dev.aaa1115910.biliapi.entity.home - -import dev.aaa1115910.biliapi.util.convertStringTimeToSeconds - -data class RecommendItem( - val aid: Long, - val bvid: String, - val title: String, - val cover: String, - val author: String, - val play: Int, - val danmaku: Int, - val duration: Int, - val idx: Int -) { - companion object { - fun fromRcmdItem(rcmdItem: dev.aaa1115910.biliapi.http.entity.home.RcmdIndexData.RcmdItem) = - RecommendItem( - aid = rcmdItem.args.aid ?: 0, - bvid = "", - title = rcmdItem.title, - cover = rcmdItem.cover, - author = rcmdItem.args.upName ?: "", - play = with(rcmdItem.coverLeftText1) { - runCatching { - if (this.endsWith("万")) { - (this.substring(0, this.length - 1).toDouble() * 10000).toInt() - } else { - this.toInt() - } - }.getOrDefault(-1) - }, - danmaku = with(rcmdItem.coverLeftText2) { - if (this == null) return@with -1 - runCatching { - if (this.endsWith("万")) { - (this.substring(0, this.length - 1).toDouble() * 10000).toInt() - } else { - this.toInt() - } - }.getOrDefault(-1) - }, - duration = rcmdItem.coverRightText?.convertStringTimeToSeconds() ?: 0, - idx = rcmdItem.idx - ) - - fun fromRcmdItem(rcmdItem: dev.aaa1115910.biliapi.http.entity.home.RcmdTopData.RcmdItem) = - RecommendItem( - aid = rcmdItem.id, - bvid = rcmdItem.bvid, - title = rcmdItem.title, - cover = rcmdItem.pic, - author = rcmdItem.owner?.name ?: "", - play = rcmdItem.stat?.view ?: -1, - danmaku = rcmdItem.stat?.danmaku ?: -1, - duration = rcmdItem.duration, - idx = -1 - ) - } -} diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcCarouselData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcCarouselData.kt deleted file mode 100644 index fe8ecb71..00000000 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcCarouselData.kt +++ /dev/null @@ -1,40 +0,0 @@ -package dev.aaa1115910.biliapi.entity.pgc - -import dev.aaa1115910.biliapi.http.entity.pgc.PgcWebInitialStateData -import io.ktor.http.Url - -data class PgcCarouselData( - val items: List -) { - companion object { - fun fromPgcWebInitialStateData(data: PgcWebInitialStateData): PgcCarouselData { - val result = mutableListOf() - var isMovie = false - // 电影板块里的轮播图数据里没有直接包含 episodeId 和 seasonId - if (data.modules.banner.moduleId == 1668) isMovie = true - data.modules.banner.items.filter { - it.episodeId != null || (isMovie && it.link.contains("bangumi/play/ep")) - }.forEach { - var cover = it.bigCover ?: it.cover - if (cover.startsWith("//")) cover = "https:$cover" - result.add( - CarouselItem( - cover = cover, - title = it.title, - seasonId = it.seasonId ?: -1, - episodeId = it.episodeId - ?: Url(it.link).pathSegments.last().substring(2).toInt() - ) - ) - } - return PgcCarouselData(result) - } - } - - data class CarouselItem( - val cover: String, - val title: String, - val seasonId: Int, - val episodeId: Int - ) -} diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/rank/Popular.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/rank/Popular.kt index c61d79cc..804d0ba0 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/rank/Popular.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/rank/Popular.kt @@ -1,7 +1,9 @@ package dev.aaa1115910.biliapi.entity.rank +import dev.aaa1115910.biliapi.entity.ugc.UgcItem + data class PopularVideoData( - val list: List, + val list: List, val nextPage: PopularVideoPage, val noMore: Boolean ) @@ -11,48 +13,3 @@ data class PopularVideoPage( val nextWebPageNumber: Int = 1, val nextAppIndex: Int = 0, ) - -data class PopularVideo( - val aid: Long, - val title: String, - val duration: Int, - val author: String, - val cover: String, - val play: Int, - val danmaku: Int, - val idx: Int, -) { - companion object { - fun fromVideoInfo(videoInfo: dev.aaa1115910.biliapi.http.entity.video.VideoInfo) = - PopularVideo( - aid = videoInfo.aid, - title = videoInfo.title, - duration = videoInfo.duration, - author = videoInfo.owner.name, - cover = videoInfo.pic, - play = videoInfo.stat.view, - danmaku = videoInfo.stat.danmaku, - idx = -1 - ) - - fun fromSmallCoverV5(card: bilibili.app.card.v1.SmallCoverV5) = - PopularVideo( - aid = card.base.param.toLong(), - title = card.base.title, - duration = convertStringTimeToSeconds(card.coverRightText1), - author = card.rightDesc1, - cover = card.base.cover, - play = -1, - danmaku = -1, - idx = card.base.idx.toInt() - ) - } -} - -private fun convertStringTimeToSeconds(time: String): Int { - val parts = time.split(":") - val hours = if (parts.size == 3) parts[0].toInt() else 0 - val minutes = parts[parts.size - 2].toInt() - val seconds = parts[parts.size - 1].toInt() - return (hours * 3600) + (minutes * 60) + seconds -} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/UgcItem.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/UgcItem.kt new file mode 100644 index 00000000..b6c67a31 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/UgcItem.kt @@ -0,0 +1,102 @@ +package dev.aaa1115910.biliapi.entity.ugc + +import dev.aaa1115910.biliapi.http.entity.home.RcmdIndexData +import dev.aaa1115910.biliapi.http.entity.home.RcmdTopData +import dev.aaa1115910.biliapi.util.convertStringTimeToSeconds + +data class UgcItem( + val aid: Long, + val bvid: String = "", + val title: String, + val cover: String, + val author: String, + val play: Int, + val danmaku: Int, + val duration: Int, + val idx: Int = -1 +) { + companion object { + fun fromRcmdItem(rcmdItem: RcmdIndexData.RcmdItem) = + UgcItem( + aid = rcmdItem.args.aid ?: 0, + title = rcmdItem.title, + cover = rcmdItem.cover, + author = rcmdItem.args.upName ?: "", + play = with(rcmdItem.coverLeftText1) { + runCatching { + if (this.endsWith("万")) { + (this.substring(0, this.length - 1).toDouble() * 10000).toInt() + } else { + this.toInt() + } + }.getOrDefault(-1) + }, + danmaku = with(rcmdItem.coverLeftText2) { + if (this == null) return@with -1 + runCatching { + if (this.endsWith("万")) { + (this.substring(0, this.length - 1).toDouble() * 10000).toInt() + } else { + this.toInt() + } + }.getOrDefault(-1) + }, + duration = rcmdItem.coverRightText?.convertStringTimeToSeconds() ?: 0, + idx = rcmdItem.idx + ) + + fun fromRcmdItem(rcmdItem: RcmdTopData.RcmdItem) = + UgcItem( + aid = rcmdItem.id, + bvid = rcmdItem.bvid, + title = rcmdItem.title, + cover = rcmdItem.pic, + author = rcmdItem.owner?.name ?: "", + play = rcmdItem.stat?.view ?: -1, + danmaku = rcmdItem.stat?.danmaku ?: -1, + duration = rcmdItem.duration + ) + + fun fromVideoInfo(videoInfo: dev.aaa1115910.biliapi.http.entity.video.VideoInfo) = + UgcItem( + aid = videoInfo.aid, + title = videoInfo.title, + duration = videoInfo.duration, + author = videoInfo.owner.name, + cover = videoInfo.pic, + play = videoInfo.stat.view, + danmaku = videoInfo.stat.danmaku + ) + + fun fromSmallCoverV5(card: bilibili.app.card.v1.SmallCoverV5) = + UgcItem( + aid = card.base.param.toLong(), + title = card.base.title, + duration = convertStringTimeToSeconds(card.coverRightText1), + author = card.rightDesc1, + cover = card.base.cover, + play = -1, + danmaku = -1, + idx = card.base.idx.toInt() + ) + + fun fromRegionDynamicListItem(item: dev.aaa1115910.biliapi.http.entity.region.RegionDynamicList.Item) = + UgcItem( + aid = item.param.toLong(), + title = item.title, + duration = item.duration, + author = item.name, + cover = item.cover, + play = item.play ?: -1, + danmaku = item.danmaku ?: -1 + ) + } +} + +private fun convertStringTimeToSeconds(time: String): Int { + val parts = time.split(":") + val hours = if (parts.size == 3) parts[0].toInt() else 0 + val minutes = parts[parts.size - 2].toInt() + val seconds = parts[parts.size - 1].toInt() + return (hours * 3600) + (minutes * 60) + seconds +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/UgcType.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/UgcType.kt new file mode 100644 index 00000000..907598e3 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/UgcType.kt @@ -0,0 +1,145 @@ +package dev.aaa1115910.biliapi.entity.ugc + +enum class UgcType(val rid: Int, val codename: String, val locId: Int = -1) { + Douga(1, "douga", 4973), + DougaMad(24, "mad"), + DougaMmd(25, "mmd"), + DougaHandDrawn(47, "handdrawn"), + DougaVoice(257, "voice"), + DougaGarageKit(210, "garage_kit"), + DougaTokusatsu(86, "tokusatsu"), + DougaAcgnTalks(253, "acgntalks"), + DougaOther(27, "other"), + + Game(4, "game", 4991), + GameStandAlone(17, "stand_alone"), + GameESports(171, "esports"), + GameMobile(172, "mobile"), + GameOnline(65, "online"), + GameBoard(173, "board"), + GameGmv(121, "gmv"), + GameMusic(136, "music"), + GameMugen(19, "mugen"), + + Kichiku(119, "kichiku", 5004), + KichikuGuide(22, "guide"), + KichikuMad(26, "mad"), + KichikuManualVocaloid(126, "manual_vocaloid"), + KichikuTheatre(216, "theatre"), + KichikuCourse(127, "course"), + + Music(3, "music", 4979), + MusicOriginal(28, "original"), + MusicLive(29, "live"), + MusicCover(31, "cover"), + MusicPerform(31, "perform"), + MusicCommentary(243, "commentary"), + MusicVocaloidUtau(30, "vocaloid"), + MusicMv(193, "mv"), + MusicFanVideos(266, "fan_videos"), + MusicAiMusic(265, "ai_music"), + MusicRadio(267, "radio"), + MusicTutorial(244, "tutorial"), + MusicOther(130, "other"), + + Dance(129, "dance", 4985), + DanceOtaku(20, "otaku"), + DanceHiphop(198, "hiphop"), + DanceStar(199, "star"), + DanceChina(200, "china"), + DanceGestures(255, "gestures"), + DanceThreeD(154, "three_d"), + DanceDemo(156, "demo"), + + Cinephile(181, "cinephile", 5008), + CinephileCinecism(182, "cinecism"), + CinephileNibtage(183, "montage"), + CinephileMashup(260, "mashup"), + CinephileAiImagine(259, "ai_imaging"), + CinephileTrailerInfo(184, "trailer_info"), + CinephileShortPlay(85, "shortplay"), + CinephileShortFilm(256, "shortfilm"), + CinephileComperhensive(261, "comprehensive"), + + Ent(5, "ent", 5007), + EntTalker(241, "talker"), + EntCpRecommendation(262, "cp_recommendation"), + EntBeauty(263, "beauty"), + EntFans(242, "fans"), + EntEntertainmentNews(264, "entertainment_news"), + EntCelebrity(137, "celebrity"), + EntVariety(71, "variety"), + + Knowledge(36, "knowledge", 4997), + KnowledgeScience(201, "science"), + KnowledgeSocialScience(124, "social_science"), + KnowledgeHumanity(228, "humanity_history"), + KnowledgeBusiness(207, "business"), + KnowledgeCampus(208, "campus"), + KnowledgeCareer(209, "career"), + KnowledgeDesign(229, "design"), + KnowledgeSkill(122, "skill"), + + Tech(188, "tech", 4998), + TechDigital(95, "digital"), + TechApplication(230, "application"), + TechComputerTech(231, "computer_tech"), + TechIndustry(232, "industry"), + TechDiy(233, "diy"), + + Information(202, "information", 5005), + InformationHotspot(203, "hotspot"), + InformationGlobal(204, "global"), + InformationSocial(205, "social"), + InformationMultiple(206, "multiple"), + + Food(211, "food", 5002), + FoodMake(76, "make"), + FoodDetective(212, "detective"), + FoodMeasurement(213, "measurement"), + FoodRural(214, "rural"), + FoodRecord(215, "record"), + + Life(160, "life", 5001), + LifeFunny(138, "funny"), + LifeParenting(254, "parenting"), + LifeTravel(250, "travel"), + LiseRuralLife(251, "rurallife"), + LifeHome(239, "home"), + LifeHandMake(161, "handmake"), + LifePainting(162, "painting"), + LifeDaily(21, "daily"), + + Car(223, "car", 5000), + CarKnowledge(258, "knowledge"), + CarStrategy(227, "strategy"), + CarNewEnergyVehicle(247, "newenergyvehicle"), + CarRacing(245, "racing"), + CarModifiedVehicle(246, "modifiedvehicle"), + CarMotorcycle(240, "motorcycle"), + CarTouringCar(248, "touringcar"), + CarLife(176, "life"), + + Fashion(155, "fashion", 5006), + FashionMakeup(157, "makeup"), + FashionCos(252, "cos"), + FashionClothing(158, "clothing"), + FashionCatwalk(159, "catwalk"), + + Sports(234, "sports", 4999), + SportsBasketball(235, "basketball"), + SportsFootball(249, "football"), + SportsAerobics(164, "aerobics"), + SportsAthletic(236, "athletic"), + SportsCulture(237, "culture"), + SportsComprehensive(238, "comprehensive"), + + Animal(217, "animal", 5003), + AnimalCat(218, "cat"), + AnimalDog(291, "dog"), + AnimalReptiles(222, "reptiles"), + AnimalWildAnima(221, "wild_animal"), + AnimalSecondEdition(220, "second_edition"), + AnimalComposite(75, "animal_composite") + +} diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionData.kt new file mode 100644 index 00000000..5e0986d3 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionData.kt @@ -0,0 +1,20 @@ +package dev.aaa1115910.biliapi.entity.ugc.region + +import dev.aaa1115910.biliapi.entity.CarouselData +import dev.aaa1115910.biliapi.entity.ugc.UgcItem + +data class UgcRegionData( + val carouselData: CarouselData?, + val items: List, + val next: UgcRegionPage +) { + companion object { + fun fromRegionDynamic(data: dev.aaa1115910.biliapi.http.entity.region.RegionDynamic): UgcRegionData { + return UgcRegionData( + carouselData = data.banner?.let { CarouselData.fromUgcRegionDynamicBanner(it) }, + items = data.new.map { UgcItem.fromRegionDynamicListItem(it) }, + next = UgcRegionPage(data.cBottom) + ) + } + } +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionListData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionListData.kt new file mode 100644 index 00000000..eff94e62 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionListData.kt @@ -0,0 +1,17 @@ +package dev.aaa1115910.biliapi.entity.ugc.region + +import dev.aaa1115910.biliapi.entity.ugc.UgcItem + +data class UgcRegionListData( + val items: List, + val next: UgcRegionPage +) { + companion object { + fun fromRegionDynamicList(data: dev.aaa1115910.biliapi.http.entity.region.RegionDynamicList): UgcRegionListData { + return UgcRegionListData( + items = data.new.map { UgcItem.fromRegionDynamicListItem(it) }, + next = UgcRegionPage(data.cBottom) + ) + } + } +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionPage.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionPage.kt new file mode 100644 index 00000000..3ab7924d --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionPage.kt @@ -0,0 +1,5 @@ +package dev.aaa1115910.biliapi.entity.ugc.region + +data class UgcRegionPage( + val nextPage: Long = 0 +) \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApi.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApi.kt index 58d94afe..7a2e7a18 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApi.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApi.kt @@ -14,6 +14,9 @@ import dev.aaa1115910.biliapi.http.entity.index.IndexResultData import dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedData import dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedV3Data import dev.aaa1115910.biliapi.http.entity.pgc.PgcWebInitialStateData +import dev.aaa1115910.biliapi.http.entity.region.RegionDynamic +import dev.aaa1115910.biliapi.http.entity.region.RegionDynamicList +import dev.aaa1115910.biliapi.http.entity.region.RegionLocs import dev.aaa1115910.biliapi.http.entity.search.AppSearchSquareData import dev.aaa1115910.biliapi.http.entity.search.KeywordSuggest import dev.aaa1115910.biliapi.http.entity.search.SearchResultData @@ -1550,6 +1553,65 @@ object BiliHttpApi { parameter("part", part.value) header("Cookie", "SESSDATA=$sessData;") }.body() + + /** + * 获取分区动态(App),包含顶部轮播图,大卡片活动推广位,和视频列表第一页 + */ + suspend fun getRegionDynamic( + rid: Int, + accessKey: String + ): BiliResponse = client.get("https://app.bilibili.com/x/v2/region/dynamic") { + parameter("access_key", accessKey) + parameter("build", BiliAppConf.APP_BUILD_CODE) + parameter("rid", rid) + }.body() + + /** + * 获取分区视频列表(App),用于[getRegionDynamic]加载数据后下滑加载更多数据 + */ + suspend fun getRegionDynamicList( + rid: Int, + ctime: Long = 0, + accessKey: String + ): BiliResponse = + client.get("https://app.bilibili.com/x/v2/region/dynamic/list") { + parameter("access_key", accessKey) + parameter("build", BiliAppConf.APP_BUILD_CODE) + parameter("rid", rid) + parameter("ctime", ctime) + parameter("pull", "false") + }.body() + + // + + /** + * 获取分区内各种插入的banner,例如顶部轮播图,还有插入的广告横幅(Web) + * + * id: + * 4973 动画 douga + * 4991 游戏 game + * 5004 鬼畜 kichiku + * 4979 音乐 music + * 4985 舞蹈 dance + * 5008 影视 cinephile + * 5007 娱乐 ent + * 4997 知识 knowledge + * 4998 科技 tech + * 5005 资讯 information + * 5002 美食 food + * 5001 生活 life + * 5000 汽车 car + * 5006 时尚 fashion + * 4999 运动 sports + * 5003 动物圈 animal + */ + suspend fun getLocs( + ids: List, + sessData: String? = null + ): RegionLocs = client.get("/x/web-show/res/locs") { + parameter("ids", ids.joinToString(",")) + sessData?.let { header("Cookie", "SESSDATA=$it;") } + }.body() } enum class SeasonIndexType(val id: Int) { diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionDynamic.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionDynamic.kt new file mode 100644 index 00000000..938effc4 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionDynamic.kt @@ -0,0 +1,90 @@ +package dev.aaa1115910.biliapi.http.entity.region + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * 分区动态 + * @param banner 轮播图 + * @param card 卡片推荐位 + * @param cBottom 往下滚动页面加载数据使用的参数 + * @param cTop 往上滚动页面加载数据使用的参数 + * @param new 推荐内容,可看作加载分区视频列表的第一页 + */ +@Serializable +data class RegionDynamic( + val banner: Banner? = null, + val card: List = emptyList(), + @SerialName("cbottom") + val cBottom: Long, + @SerialName("ctop") + val cTop: Long, + val new: List +) { + @Serializable + data class Banner( + val top: List + ) { + @Serializable + data class Top( + @SerialName("client_ip") + val clientIp: String? = null, + @SerialName("cm_mark") + val cmMark: Int, + val hash: String, + val id: Int, + val image: String, + val index: Int, + @SerialName("is_ad") + val isAd: Boolean? = null, + @SerialName("is_ad_loc") + val isAdLoc: Boolean? = null, + @SerialName("request_id") + val requestId: String, + @SerialName("resource_id") + val resourceId: Int, + @SerialName("server_type") + val serverType: Int, + @SerialName("src_id") + val srcId: Int? = null, + val title: String, + val uri: String + ) + } + + @Serializable + data class Card( + val body: List, + @SerialName("card_id") + val cardId: Int, + val title: String, + val type: String + ) + + @Serializable + data class Item( + val cover: String, + @SerialName("cover_left_icon_1") + val coverLeftIcon1: Int? = null, + @SerialName("cover_left_text_1") + val coverLeftText1: String? = null, + val danmaku: Int? = null, + val duration: Int? = null, + val face: String? = null, + val favourite: Int? = null, + val goto: String, + val like: Int? = null, + val name: String? = null, + val param: String, + val play: Int? = null, + @SerialName("pubdate") + val pubDate: Int, + val reply: Int? = null, + val rid: Int? = null, + @SerialName("rname") + val rName: String? = null, + val title: String, + val uri: String, + val children: List? = null + ) +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionDynamicList.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionDynamicList.kt new file mode 100644 index 00000000..dfe13af0 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionDynamicList.kt @@ -0,0 +1,40 @@ +package dev.aaa1115910.biliapi.http.entity.region + + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RegionDynamicList( + @SerialName("cbottom") + val cBottom: Long, + @SerialName("ctop") + val cTop: Long, + val new: List +) { + @Serializable + data class Item( + val cover: String, + @SerialName("cover_left_icon_1") + val coverLeftIcon1: Int, + @SerialName("cover_left_text_1") + val coverLeftText1: String, + val danmaku: Int? = null, + val duration: Int, + val face: String, + val favourite: Int? = null, + val goto: String, + val like: Int? = null, + val name: String, + val param: String, + val play: Int? = null, + @SerialName("pubdate") + val pubDate: Int, + val reply: Int? = null, + val rid: Int, + @SerialName("rname") + val rName: String, + val title: String, + val uri: String + ) +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionLocs.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionLocs.kt new file mode 100644 index 00000000..92947a8c --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionLocs.kt @@ -0,0 +1,115 @@ +package dev.aaa1115910.biliapi.http.entity.region + + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +@Serializable +data class RegionLocs( + @SerialName("ads_control") + val adsControl: AdsControl, + val code: Int, + val count: Int, + val data: Map>, + val live: JsonObject? = null, + val message: String +) { + @Serializable + data class AdsControl( + @SerialName("has_danmu") + val hasDanmu: Int, + @SerialName("has_live_booking_ad") + val hasLiveBookingAd: Boolean, + @SerialName("under_player_scroller_seconds") + val underPlayerScrollerSeconds: Int + ) + + @Serializable + data class LocData( + @SerialName("activity_type") + val activityType: Int, + @SerialName("ad_cb") + val adCb: String, + @SerialName("ad_desc") + val adDesc: String, + @SerialName("adver_name") + val adverName: String, + val agency: String, + val area: Int, + @SerialName("asg_id") + val asgId: Int, + @SerialName("business_mark") + val businessMark: JsonObject? = null, + @SerialName("card_type") + val cardType: Int, + @SerialName("click_urls") + val clickUrls: JsonObject? = null, + @SerialName("cm_mark") + val cmMark: Int, + @SerialName("contract_id") + val contractId: String, + @SerialName("creative_type") + val creativeType: Int, + @SerialName("epid") + val epId: Int, + @SerialName("feedback_panel") + val feedbackPanel: JsonObject? = null, + val id: Int, + val inline: Inline, + val intro: String, + @SerialName("is_ad_loc") + val isAdLoc: Boolean, + @SerialName("jump_target") + val jumpTarget: Int, + val label: String, + @SerialName("litpic") + val litPic: String, + val mid: String, + val name: String, + @SerialName("null_frame") + val nullFrame: Boolean, + @SerialName("operater") + val operater: String, + val pic: String, + @SerialName("pic_main_color") + val picMainColor: String, + @SerialName("pos_num") + val posNum: Int, + @SerialName("request_id") + val requestId: String, + @SerialName("res_id") + val resId: Int, + val room: JsonObject? = null, + @SerialName("sales_type") + val salesType: Int, + val season: JsonObject? = null, + @SerialName("server_type") + val serverType: Int, + @SerialName("show_urls") + val showUrls: JsonObject? = null, + @SerialName("src_id") + val srcId: Int, + @SerialName("stime") + val sTime: Int, + val style: Int, + @SerialName("sub_title") + val subTitle: String, + val title: String, + @SerialName("track_id") + val trackId: String, + val url: String + ) { + @Serializable + data class Inline( + @SerialName("inline_barrage_switch") + val inlineBarrageSwitch: Int, + @SerialName("inline_type") + val inlineType: Int, + @SerialName("inline_url") + val inlineUrl: String, + @SerialName("inline_use_same") + val inlineUseSame: Int + ) + } +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepository.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepository.kt index 0696fc64..058f554a 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepository.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepository.kt @@ -1,6 +1,6 @@ package dev.aaa1115910.biliapi.repositories -import dev.aaa1115910.biliapi.entity.pgc.PgcCarouselData +import dev.aaa1115910.biliapi.entity.CarouselData import dev.aaa1115910.biliapi.entity.pgc.PgcFeedData import dev.aaa1115910.biliapi.entity.pgc.PgcType import dev.aaa1115910.biliapi.entity.pgc.index.Area @@ -20,9 +20,9 @@ import dev.aaa1115910.biliapi.entity.pgc.index.Year import dev.aaa1115910.biliapi.http.BiliHttpApi class PgcRepository { - suspend fun getCarousel(pgcType: PgcType): PgcCarouselData { + suspend fun getCarousel(pgcType: PgcType): CarouselData { val initialStateData = BiliHttpApi.getPgcWebInitialStateData(pgcType) - val carouselData = PgcCarouselData.fromPgcWebInitialStateData(initialStateData) + val carouselData = CarouselData.fromPgcWebInitialStateData(initialStateData) return carouselData } diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/RecommendVideoRepository.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/RecommendVideoRepository.kt index 76586813..8418dbb1 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/RecommendVideoRepository.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/RecommendVideoRepository.kt @@ -4,11 +4,10 @@ import bilibili.app.show.v1.PopularGrpcKt import bilibili.app.show.v1.popularResultReq import dev.aaa1115910.biliapi.entity.ApiType import dev.aaa1115910.biliapi.entity.home.RecommendData -import dev.aaa1115910.biliapi.entity.home.RecommendItem import dev.aaa1115910.biliapi.entity.home.RecommendPage -import dev.aaa1115910.biliapi.entity.rank.PopularVideo import dev.aaa1115910.biliapi.entity.rank.PopularVideoData import dev.aaa1115910.biliapi.entity.rank.PopularVideoPage +import dev.aaa1115910.biliapi.entity.ugc.UgcItem import dev.aaa1115910.biliapi.http.BiliHttpApi class RecommendVideoRepository( @@ -31,7 +30,7 @@ class RecommendVideoRepository( pageNumber = page.nextWebPageNumber, sessData = authRepository.sessionData ?: "" ).getResponseData() - val list = response.list.map { PopularVideo.fromVideoInfo(it) } + val list = response.list.map { UgcItem.fromVideoInfo(it) } val nextPage = PopularVideoPage( nextWebPageSize = page.nextWebPageSize, nextWebPageNumber = page.nextWebPageNumber + 1 @@ -49,7 +48,7 @@ class RecommendVideoRepository( }) val list = reply?.itemsList ?.filter { it.itemCase == bilibili.app.card.v1.Card.ItemCase.SMALL_COVER_V5 } - ?.map { PopularVideo.fromSmallCoverV5(it.smallCoverV5) } + ?.map { UgcItem.fromSmallCoverV5(it.smallCoverV5) } ?: emptyList() val nextPage = PopularVideoPage( nextAppIndex = list.lastOrNull()?.idx ?: -1 @@ -73,7 +72,7 @@ class RecommendVideoRepository( sessData = authRepository.sessionData ) .getResponseData().item - .map { RecommendItem.fromRcmdItem(it) } + .map { UgcItem.fromRcmdItem(it) } ApiType.App -> BiliHttpApi.getFeedIndex( idx = page.nextAppIdx, @@ -81,7 +80,7 @@ class RecommendVideoRepository( ) .getResponseData().items .filter { it.cardGoto == "av" } - .map { RecommendItem.fromRcmdItem(it) } + .map { UgcItem.fromRcmdItem(it) } } val nextPage = when (preferApiType) { ApiType.Web -> RecommendPage( diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/UgcRepository.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/UgcRepository.kt new file mode 100644 index 00000000..4d802dd6 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/UgcRepository.kt @@ -0,0 +1,28 @@ +package dev.aaa1115910.biliapi.repositories + +import dev.aaa1115910.biliapi.entity.ugc.UgcType +import dev.aaa1115910.biliapi.entity.ugc.region.UgcRegionData +import dev.aaa1115910.biliapi.entity.ugc.region.UgcRegionListData +import dev.aaa1115910.biliapi.http.BiliHttpApi + +class UgcRepository( + private val authRepository: AuthRepository +) { + suspend fun getRegionData(ugcType: UgcType): UgcRegionData { + val responseData = BiliHttpApi.getRegionDynamic( + rid = ugcType.rid, + accessKey = authRepository.accessToken ?: "", + ).getResponseData() + val data = UgcRegionData.fromRegionDynamic(responseData) + return data + } + + suspend fun getRegionMoreData(ugcType: UgcType): UgcRegionListData { + val responseData = BiliHttpApi.getRegionDynamicList( + rid = ugcType.rid, + accessKey = authRepository.accessToken ?: "", + ).getResponseData() + val data = UgcRegionListData.fromRegionDynamicList(responseData) + return data + } +} diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/AvBvConverter.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/AvBvConverter.kt new file mode 100644 index 00000000..81f31ac3 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/AvBvConverter.kt @@ -0,0 +1,41 @@ +package dev.aaa1115910.biliapi.util + +object AvBvConverter { + private val XOR_CODE = 23442827791579L.toBigInteger() + private val MASK_CODE = 2251799813685247L.toBigInteger() + private val MAX_AID = 1.toBigInteger() shl 51 + private val BASE = 58.toBigInteger() + + private const val DATA = "FcwAPNKTMug3GV5Lj7EJnHpWsx4tb8haYeviqBz6rkCy12mUSDQX9RdoZf" + + fun av2bv(aid: Long): String { + val bytes = "BV1000000000".toCharArray() + var bvIndex = bytes.size - 1 + var tmp = MAX_AID or aid.toBigInteger() xor XOR_CODE + while (tmp > 0.toBigInteger()) { + bytes[bvIndex] = DATA[(tmp % BASE).toInt()] + tmp /= BASE + bvIndex-- + } + bytes.swap(3, 9) + bytes.swap(4, 7) + return String(bytes) + } + + fun bv2av(bvid: String): Long { + val bvidArr = bvid.toCharArray() + bvidArr.swap(3, 9) + bvidArr.swap(4, 7) + val adjustedBvid = String(bvidArr, 3, bvidArr.size - 3) + var tmp = 0.toBigInteger() + for (c in adjustedBvid.toCharArray()) { + tmp = tmp * BASE + DATA.indexOf(c).toBigInteger() + } + val xor = tmp and MASK_CODE xor XOR_CODE + return xor.toLong() + } + + private fun CharArray.swap(i: Int, j: Int) { + this[i] = this[j].also { this[j] = this[i] } + } +} diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/Extends.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/Extends.kt index 66f1f895..605062f2 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/Extends.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/Extends.kt @@ -8,3 +8,5 @@ fun String.convertStringTimeToSeconds(): Int { return (hours * 3600) + (minutes * 60) + seconds } +fun Long.toBv(): String = AvBvConverter.av2bv(this) +fun String.toAv(): Long = AvBvConverter.bv2av(this) \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/UrlUtil.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/UrlUtil.kt new file mode 100644 index 00000000..f5931989 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/UrlUtil.kt @@ -0,0 +1,27 @@ +package dev.aaa1115910.biliapi.util + +import io.ktor.http.Url + +object UrlUtil { + fun isVideoUrl(url: String): Boolean { + return url.startsWith("bilibili://video/") + || url.startsWith("https://www.bilibili.com/video/") + } + + fun parseAidFromUrl(url: String): Long { + if (url.startsWith("bilibili://video/")) { + return url.split("/").last().toLong() + } else { + val pathSegments = Url(url).pathSegments + val videoSegmentIndex = pathSegments.indexOf("video") + val videoId = pathSegments[videoSegmentIndex + 1] + return if (videoId.startsWith("BV")) { + AvBvConverter.bv2av(videoId) + } else { + videoId.drop(2).toLong() + } + } + } + + fun parseBvidFromUrl(url: String) = parseAidFromUrl(url).toBv() +} diff --git a/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApiTest.kt b/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApiTest.kt index 5547a259..d15d6210 100644 --- a/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApiTest.kt +++ b/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/http/BiliHttpApiTest.kt @@ -1,10 +1,11 @@ package dev.aaa1115910.biliapi.http +import dev.aaa1115910.biliapi.entity.pgc.PgcType import dev.aaa1115910.biliapi.entity.season.FollowingSeasonStatus import dev.aaa1115910.biliapi.entity.season.FollowingSeasonType import dev.aaa1115910.biliapi.http.entity.user.FollowAction import dev.aaa1115910.biliapi.http.entity.user.FollowActionSource -import dev.aaa1115910.biliapi.entity.pgc.PgcType +import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.junit.jupiter.api.Test @@ -694,4 +695,60 @@ internal class BiliHttpApiTest { val result = BiliHttpApi.getAppVideoShot(aid = 170001, cid = 279786) println(result) } + + @Test + fun `get app region dynamic`() = runBlocking { + val rids = listOf( + 1, 13, 167, 3, 129, 4, 36, 188, 234, 223, 160, + 211, 217, 119, 155, 202, 5, 181, 177, 23, 11 + ) + rids + .shuffled() + .forEach { rid -> + println("rid $rid:") + val result = BiliHttpApi.getRegionDynamic( + rid = rid, + accessKey = ACCESS_TOKEN + ) + println(result) + delay((800L..2000L).random()) + } + } + + + @Test + fun `get app region dynamic list`() = runBlocking { + val rids = listOf( + 1, 13, 167, 3, 129, 4, 36, 188, 234, 223, 160, + 211, 217, 119, 155, 202, 5, 181, 177, 23, 11 + ) + rids + .shuffled() + .forEach { rid -> + println("rid $rid:") + val result = BiliHttpApi.getRegionDynamicList( + rid = rid, + accessKey = ACCESS_TOKEN + ) + println(result) + delay((800L..2000L).random()) + } + } + + @Test + fun `get locs`() = runBlocking { + val locIds = listOf( + 4973, 4991, 5004, 4979, 4985, 5008, 5007, 4997, + 4998, 5005, 5002, 5001, 5000, 5006, 4999, 5003 + ) + + locIds.chunked(3).forEach { locs -> + println("${locs.joinToString(",")}:") + val result = BiliHttpApi.getLocs( + ids = locs + ) + println(result) + delay((800L..2000L).random()) + } + } } \ No newline at end of file diff --git a/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/UgcRepositoryTest.kt b/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/UgcRepositoryTest.kt new file mode 100644 index 00000000..1acee283 --- /dev/null +++ b/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/UgcRepositoryTest.kt @@ -0,0 +1,58 @@ +package dev.aaa1115910.biliapi.repositories + +import dev.aaa1115910.biliapi.entity.ugc.UgcType +import kotlinx.coroutines.runBlocking +import java.io.File +import java.nio.file.Paths +import java.util.Properties +import kotlin.test.Test + +class UgcRepositoryTest { + companion object { + private val localProperties = Properties().apply { + val path = Paths.get("../local.properties").toAbsolutePath().toString() + load(File(path).bufferedReader()) + } + val SESSDATA: String = + runCatching { localProperties.getProperty("test.sessdata") }.getOrNull() ?: "" + val BILI_JCT: String = + runCatching { localProperties.getProperty("test.bili_jct") }.getOrNull() ?: "" + val UID: Long = + runCatching { localProperties.getProperty("test.uid") }.getOrNull()?.toLongOrNull() ?: 2 + val ACCESS_TOKEN: String = + runCatching { localProperties.getProperty("test.access_token") }.getOrNull() ?: "" + val BUVID: String = + runCatching { localProperties.getProperty("test.buvid") }.getOrNull() ?: "" + } + + private val authRepository = AuthRepository() + private val ugcRepository: UgcRepository = UgcRepository(authRepository) + + init { + authRepository.sessionData = SESSDATA + authRepository.accessToken = ACCESS_TOKEN + authRepository.biliJct = BILI_JCT + } + + @Test + fun `get region data`() = runBlocking { + UgcType.entries + .filter { it.locId != -1 } + .forEach { ugcType -> + println("ugcType: $ugcType") + val result = ugcRepository.getRegionData(ugcType) + println(result) + } + } + + @Test + fun `get region more data`() = runBlocking { + UgcType.entries + .filter { it.locId != -1 } + .forEach { ugcType -> + println("ugcType: $ugcType") + val result = ugcRepository.getRegionMoreData(ugcType) + println(result) + } + } +}