From ef5671ca5754a7206e37ca99d7af212770c9237d Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Tue, 6 Aug 2024 00:42:09 +0800 Subject: [PATCH 01/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=97=A0=E6=B3=95=E6=98=BE=E7=A4=BA=E6=AD=A3=E7=A1=AE=E7=9A=84?= =?UTF-8?q?=E5=BD=93=E5=89=8D=E7=BC=96=E7=A0=81=EF=BC=88App=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/aaa1115910/bv/entity/VideoCodec.kt | 16 ++++++++++------ .../bv/viewmodel/VideoPlayerV3ViewModel.kt | 10 +++++++++- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt b/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt index 4a3e5bb3..b97f70fc 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt @@ -4,12 +4,12 @@ import android.content.Context import dev.aaa1115910.biliapi.entity.CodeType import dev.aaa1115910.bv.R -enum class VideoCodec(private val strRes: Int, val prefix: String) { - AVC(R.string.video_codec_avc, "avc1"), - HEVC(R.string.video_codec_hevc, "hev1"), - AV1(R.string.video_codec_av1, "av01"), - DVH1(R.string.video_codec_dvh1, "dvh1"), - HVC1(R.string.video_codec_hvc1, "hvc"); +enum class VideoCodec(private val strRes: Int, val prefix: String, val codecId: Int) { + AVC(R.string.video_codec_avc, "avc1", 7), + HEVC(R.string.video_codec_hevc, "hev1", 12), + AV1(R.string.video_codec_av1, "av01", 13), + DVH1(R.string.video_codec_dvh1, "dvh1", 0), + HVC1(R.string.video_codec_hvc1, "hvc", 0); companion object { fun fromCode(code: Int?) = runCatching { @@ -22,6 +22,10 @@ enum class VideoCodec(private val strRes: Int, val prefix: String) { } return@runCatching null }.getOrNull() + + fun fromCodecId(codecId: Int) = runCatching { + entries.find { it.codecId == codecId }!! + }.getOrDefault(AVC) } fun getDisplayName(context: Context) = context.getString(strRes) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt index fb685256..7c209a8d 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt @@ -291,7 +291,15 @@ class VideoPlayerV3ViewModel( } fun updateAvailableCodec() { - if (Prefs.apiType == ApiType.App) return + if (Prefs.apiType == ApiType.App) { + // 纠正当前实际播放的编码 + val videoItem = playData!!.dashVideos + .find { it.quality == currentQuality } + ?: playData!!.dashVideos.first() + currentVideoCodec = VideoCodec.fromCodecId(videoItem.codecId) + return + } + val supportedCodec = playData!!.codec val codecList = supportedCodec[currentQuality]!!.mapNotNull { VideoCodec.fromCodecString(it) } From d2279debb05c0405685a0bf12f269dcdd4f21950 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Tue, 6 Aug 2024 00:43:10 +0800 Subject: [PATCH 02/27] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- buildSrc/src/main/kotlin/AppConfiguration.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/buildSrc/src/main/kotlin/AppConfiguration.kt b/buildSrc/src/main/kotlin/AppConfiguration.kt index f242d5d3..1f01dc9b 100644 --- a/buildSrc/src/main/kotlin/AppConfiguration.kt +++ b/buildSrc/src/main/kotlin/AppConfiguration.kt @@ -5,12 +5,12 @@ object AppConfiguration { const val targetSdk = 34 private const val major = 0 private const val minor = 2 - private const val patch = 8 - private const val bugFix = 0 + private const val patch = 9 + private const val hotFix = 0 @Suppress("KotlinConstantConditions") val versionName: String by lazy { - "$major.$minor.$patch${".$bugFix".takeIf { bugFix != 0 } ?: ""}" + + "$major.$minor.$patch${".$hotFix".takeIf { hotFix != 0 } ?: ""}" + ".r${versionCode}.${"git rev-list HEAD --abbrev-commit --max-count=1".exec()}" } val versionCode: Int by lazy { "git rev-list --count HEAD".exec().toInt() } From 5e2d37cef6e09de25f19527bf12dda3ad055de9d Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Tue, 6 Aug 2024 01:27:24 +0800 Subject: [PATCH 03/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E5=8A=A0=E8=BD=BD=20Hi-Res=20=E5=92=8C=20Dolby=20Atoms=20?= =?UTF-8?q?=E9=9F=B3=E9=A2=91=EF=BC=88App=E6=8E=A5=E5=8F=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt index 2b4a706e..64ff8603 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt @@ -49,7 +49,7 @@ data class PlayData( DashAudio( baseUrl = it.baseUrl, bandwidth = it.bandwidth, - codecId = it.codecid, + codecId = it.id, backUrl = it.backupUrlList ) } @@ -57,7 +57,7 @@ data class PlayData( DashAudio( baseUrl = it.baseUrl, bandwidth = it.bandwidth, - codecId = it.codecid, + codecId = it.id, backUrl = it.backupUrlList ) } From a3bc393357773db8423d46010fc51e855ff24193 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Wed, 7 Aug 2024 20:45:30 +0800 Subject: [PATCH 04/27] =?UTF-8?q?=E5=90=8C=E6=97=B6=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E5=A4=9A=E7=A7=8D=E8=A7=86=E9=A2=91=E7=BC=96=E7=A0=81=E7=9A=84?= =?UTF-8?q?=E6=92=AD=E6=94=BE=E5=9C=B0=E5=9D=80=EF=BC=88App=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 原本因为 App 接口请求播放地址时只会返回一种编码的视频,这就会导致一些特殊规格视频例如杜比视界可能会无法获取到播放地址 现在在获取视频播放地址时将同时请求多种编码格式,以便用户切换编码和补全单一视频编码下可能缺失的特殊规格视频 --- .../controllers2/playermenu/PictureMenu.kt | 10 +- .../dev/aaa1115910/bv/entity/VideoCodec.kt | 2 +- .../settings/content/VideoCodecSetting.kt | 2 +- .../bv/viewmodel/VideoPlayerV3ViewModel.kt | 8 +- .../dev/aaa1115910/biliapi/entity/CodeType.kt | 18 ++- .../dev/aaa1115910/biliapi/entity/PlayData.kt | 33 ++++- .../repositories/VideoPlayRepository.kt | 113 ++++++++++++------ 7 files changed, 127 insertions(+), 59 deletions(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt index de84e537..bb668ceb 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.tv.foundation.lazy.list.TvLazyColumn import androidx.tv.foundation.lazy.list.itemsIndexed -import dev.aaa1115910.biliapi.entity.ApiType import dev.aaa1115910.bv.component.controllers.LocalVideoPlayerControllerData import dev.aaa1115910.bv.component.controllers2.LocalMenuFocusStateData import dev.aaa1115910.bv.component.controllers2.MenuFocusState @@ -39,7 +38,6 @@ import dev.aaa1115910.bv.entity.Audio import dev.aaa1115910.bv.entity.Resolution import dev.aaa1115910.bv.entity.VideoAspectRatio import dev.aaa1115910.bv.entity.VideoCodec -import dev.aaa1115910.bv.util.Prefs import kotlin.math.roundToInt @Composable @@ -161,13 +159,7 @@ fun PictureMenuList( verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(8.dp) ) { - val menuList = when (Prefs.apiType) { - ApiType.Web -> VideoPlayerPictureMenuItem.entries.toMutableList() - ApiType.App -> VideoPlayerPictureMenuItem.entries.toMutableList().apply { - this.remove(VideoPlayerPictureMenuItem.Codec) - } - } - itemsIndexed(menuList) { index, item -> + itemsIndexed(VideoPlayerPictureMenuItem.entries.toMutableList()) { index, item -> MenuListItem( modifier = Modifier .ifElse(index == 0, focusRestorerModifiers.childModifier), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt b/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt index b97f70fc..26117abe 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/entity/VideoCodec.kt @@ -34,6 +34,6 @@ enum class VideoCodec(private val strRes: Int, val prefix: String, val codecId: AVC -> CodeType.Code264 HEVC -> CodeType.Code265 AV1 -> CodeType.CodeAv1 - DVH1, HVC1 -> CodeType.NoCode + DVH1, HVC1 -> CodeType.Code265 } } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt index 7739cd84..be471d75 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt @@ -50,7 +50,7 @@ fun VideoCodecSetting( TvLazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) ) { - items(items = VideoCodec.entries) { videoCodec -> + items(items = VideoCodec.entries.filter { it != VideoCodec.DVH1 && it != VideoCodec.HVC1 }) { videoCodec -> SettingsMenuSelectItem( text = videoCodec.getDisplayName(context), selected = selectedVideoCodec == videoCodec, diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt index 7c209a8d..b0d64965 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/VideoPlayerV3ViewModel.kt @@ -199,7 +199,6 @@ class VideoPlayerV3ViewModel( videoPlayRepository.getPlayData( aid = avid, cid = cid, - preferCodec = Prefs.defaultVideoCodec.toBiliApiCodeType(), preferApiType = Prefs.apiType ) } @@ -291,7 +290,7 @@ class VideoPlayerV3ViewModel( } fun updateAvailableCodec() { - if (Prefs.apiType == ApiType.App) { + if (Prefs.apiType == ApiType.App && playData!!.codec.isEmpty()) { // 纠正当前实际播放的编码 val videoItem = playData!!.dashVideos .find { it.quality == currentQuality } @@ -327,7 +326,10 @@ class VideoPlayerV3ViewModel( val videoItem = playData!!.dashVideos.find { when (Prefs.apiType) { ApiType.Web -> it.quality == qn && it.codecs!!.startsWith(codec.prefix) - ApiType.App -> it.quality == qn + ApiType.App -> { + if (playData!!.codec.isEmpty()) it.quality == qn + else it.quality == qn && it.codecs!!.startsWith(codec.prefix) + } } } var videoUrl = videoItem?.baseUrl ?: playData!!.dashVideos.first().baseUrl diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CodeType.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CodeType.kt index d8afb511..2d4d7662 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CodeType.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CodeType.kt @@ -3,12 +3,18 @@ package dev.aaa1115910.biliapi.entity import bilibili.pgc.gateway.player.v2.CodeType as PgcPlayUrlCodeType import bilibili.playershared.CodeType as PlayerSharedCodeType -enum class CodeType { - NoCode, - Code264, - Code265, - CodeAv1, - Unrecognized; +enum class CodeType(val str: String, val codecId: Int) { + NoCode("none", 0), + Code264("avc1", 7), + Code265("hev1", 12), + CodeAv1("av01", 13), + Unrecognized("unknown", 0); + + companion object{ + fun fromCodecId(code: Int?) = runCatching { + entries.find { it.codecId == code }!! + }.getOrDefault(NoCode) + } fun toPlayerSharedCodeType() = when (this) { NoCode -> PlayerSharedCodeType.NOCODE diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt index 64ff8603..d1266da4 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/PlayData.kt @@ -34,7 +34,7 @@ data class PlayData( height = it.dashVideo.height, frameRate = it.dashVideo.frameRate, backUrl = it.dashVideo.backupUrlList, - codecs = null + codecs = CodeType.fromCodecId(it.dashVideo.codecid).str ) } val dashAudios = audioList.map { @@ -62,13 +62,16 @@ data class PlayData( ) } + val codecs = playViewUniteReply.vodInfo.streamListList.associate { + it.streamInfo.quality to listOf(CodeType.fromCodecId(it.dashVideo.codecid).str) + } return PlayData( dashVideos = dashVideos, dashAudios = dashAudios, dolby = dolby, flac = flac, - codec = emptyMap(), + codec = codecs, needPay = false ) } @@ -78,6 +81,9 @@ data class PlayData( pgcPlayViewReply.videoInfo.streamListList.filter { it.dashVideoOrNull != null } val audioList = pgcPlayViewReply.videoInfo.dashAudioList val dolbyItem = pgcPlayViewReply.videoInfo.dolbyOrNull?.audio + val codecs = pgcPlayViewReply.videoInfo.streamListList.associate { + it.info.quality to listOf(CodeType.fromCodecId(it.dashVideo.codecid).str) + } val needPay = pgcPlayViewReply.business.isPreview val dashVideos = streamList.map { @@ -90,7 +96,7 @@ data class PlayData( height = it.dashVideo.height, frameRate = it.dashVideo.frameRate, backUrl = it.dashVideo.backupUrlList, - codecs = null + codecs = CodeType.fromCodecId(it.dashVideo.codecid).str ) } val dashAudios = audioList.map { @@ -115,7 +121,7 @@ data class PlayData( dashAudios = dashAudios, dolby = dolby, flac = null, - codec = emptyMap(), + codec = codecs, needPay = needPay ) } @@ -294,6 +300,25 @@ data class PlayData( ) } } + + operator fun plus(other: PlayData): PlayData { + return PlayData( + dashVideos = (dashVideos + other.dashVideos) + .distinctBy { "${it.codecId}_${it.quality}" } + .sortedByDescending { it.quality }, + dashAudios = (dashAudios + other.dashAudios) + .distinctBy { it.codecId } + .sortedByDescending { it.codecId }, + dolby = dolby ?: other.dolby, + flac = flac ?: other.flac, + codec = codec.map { + it.key to (it.value + other.codec[it.key].orEmpty()) + .distinct() + .filter { it != "none" } + }.toMap(), + needPay = needPay || other.needPay + ) + } } /** diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt index 51f36fbe..52a7400a 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt @@ -18,6 +18,10 @@ import dev.aaa1115910.biliapi.entity.video.VideoShot import dev.aaa1115910.biliapi.grpc.utils.handleGrpcException import dev.aaa1115910.biliapi.http.BiliHttpApi import dev.aaa1115910.biliapi.http.BiliHttpProxyApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext import bilibili.pgc.gateway.player.v2.PlayURLGrpcKt as PgcPlayURLGrpcKt class VideoPlayRepository( @@ -46,7 +50,6 @@ class VideoPlayRepository( suspend fun getPlayData( aid: Long, cid: Long, - preferCodec: CodeType = CodeType.NoCode, preferApiType: ApiType = ApiType.Web ): PlayData { return when (preferApiType) { @@ -64,20 +67,40 @@ class VideoPlayRepository( } ApiType.App -> { - val playUniteReplay = runCatching { - playerStub?.playViewUnite(playViewUniteReq { - vod = videoVod { - this.aid = aid - this.cid = cid - fnval = 4048 - qn = 127 - fnver = 0 - fourk = true - preferCodecType = preferCodec.toPlayerSharedCodeType() + withContext(Dispatchers.IO) { + val codecTypes = listOf( + CodeType.Code264, + CodeType.Code265, + CodeType.CodeAv1 + ) + val replies = codecTypes.map { codecType -> + async { + val playUniteReply = runCatching { + playerStub?.playViewUnite(playViewUniteReq { + vod = videoVod { + this.aid = aid + this.cid = cid + fnval = 4048 + qn = 127 + fnver = 0 + fourk = true + preferCodecType = codecType.toPlayerSharedCodeType() + } + }) ?: throw IllegalStateException("Player stub is not initialized") + }.onFailure { + // dont throw + runCatching { handleGrpcException(it) } + }.getOrNull() + playUniteReply } - }) ?: throw IllegalStateException("Player stub is not initialized") - }.onFailure { handleGrpcException(it) }.getOrThrow() - PlayData.fromPlayViewUniteReply(playUniteReplay) + }.awaitAll() + val result = replies.map { + it?.let { PlayData.fromPlayViewUniteReply(it) } + }.reduce { acc, playData -> + acc?.let { playData?.let { acc + playData } ?: acc } ?: playData + } ?: throw IllegalStateException("All codec types are failed to get play data") + result + } } } } @@ -120,27 +143,47 @@ class VideoPlayRepository( } ApiType.App -> { - val pgcPlayViewReply = runCatching { - val req = playViewReq { - this.epid = epid.toLong() - this.cid = cid.toLong() - qn = 127 - fnver = 0 - fnval = 4048 - fourk = true - forceHost = 0 - download = 0 - preferCodecType = preferCodec.toPgcPlayUrlCodeType() - } - if (enableProxy) { - proxyPgcPlayUrlStub?.playView(req) - ?: throw IllegalStateException("Proxy pgc play url stub is not initialized") - } else { - pgcPlayUrlStub?.playView(req) - ?: throw IllegalStateException("Pgc play url stub is not initialized") - } - }.onFailure { handleGrpcException(it) }.getOrThrow() - PlayData.fromPgcPlayViewReply(pgcPlayViewReply) + withContext(Dispatchers.IO) { + val codecTypes = listOf( + CodeType.Code264, + CodeType.Code265, + CodeType.CodeAv1 + ) + val replies = codecTypes.map { codecType -> + val req = playViewReq { + this.epid = epid.toLong() + this.cid = cid + qn = 127 + fnver = 0 + fnval = 4048 + fourk = true + forceHost = 0 + download = 0 + preferCodecType = codecType.toPgcPlayUrlCodeType() + } + async { + val playReply = runCatching { + if (enableProxy) { + proxyPgcPlayUrlStub?.playView(req) + ?: throw IllegalStateException("Proxy pgc play url stub is not initialized") + } else { + pgcPlayUrlStub?.playView(req) + ?: throw IllegalStateException("Pgc play url stub is not initialized") + } + }.onFailure { + // dont throw + runCatching { handleGrpcException(it) } + }.getOrNull() + playReply + } + }.awaitAll() + val result = replies.map { + it?.let { PlayData.fromPgcPlayViewReply(it) } + }.reduce { acc, playData -> + acc?.let { playData?.let { acc + playData } ?: acc } ?: playData + } ?: throw IllegalStateException("All codec types are failed to get play data") + result + } } } } From 34a9002088a1b53244902048ba49912dcb242a16 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Sun, 18 Aug 2024 00:07:58 +0800 Subject: [PATCH 05/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=81=97=E6=BC=8F?= =?UTF-8?q?=E7=9A=84=E5=BC=82=E5=B8=B8=E6=97=A5=E5=BF=97=E8=BE=93=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../biliapi/repositories/VideoPlayRepository.kt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt index 52a7400a..e61920f4 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/VideoPlayRepository.kt @@ -90,6 +90,10 @@ class VideoPlayRepository( }.onFailure { // dont throw runCatching { handleGrpcException(it) } + .onFailure { + println("get play data failed: [aid=$aid, cid=$cid, preferCodec=$codecType, preferApiType=$preferApiType]") + it.printStackTrace() + } }.getOrNull() playUniteReply } @@ -173,6 +177,10 @@ class VideoPlayRepository( }.onFailure { // dont throw runCatching { handleGrpcException(it) } + .onFailure { + println("get pgc play data failed: [aid=$aid, cid=$cid, epid=$epid, preferCodec=$codecType, preferApiType=$preferApiType]") + it.printStackTrace() + } }.getOrNull() playReply } From 526a1a18131ebf764eae71955fa2abdde53211fe Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Sun, 18 Aug 2024 00:09:00 +0800 Subject: [PATCH 06/27] =?UTF-8?q?=E4=BF=9D=E5=AD=98=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E6=97=B6=E5=86=99=E5=85=A5=E8=AE=BE=E5=A4=87?= =?UTF-8?q?=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/aaa1115910/bv/util/LogCatcherUtil.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/util/LogCatcherUtil.kt b/app/src/main/kotlin/dev/aaa1115910/bv/util/LogCatcherUtil.kt index ab4406da..ce1fde82 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/util/LogCatcherUtil.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/util/LogCatcherUtil.kt @@ -5,6 +5,7 @@ import io.github.oshai.kotlinlogging.KotlinLogging import java.io.BufferedReader import java.io.File import java.io.InputStreamReader +import java.io.OutputStreamWriter import java.text.SimpleDateFormat import java.util.Date import java.util.Locale @@ -45,6 +46,9 @@ object LogCatcherUtil { logger.info { "Log file: $logFile" } with(logFile.writer()) { + writeDeviceInfo() + writeAppInfo() + appendLine("======== Logs ========") var line: String? while (reader.readLine().also { line = it } != null) { appendLine(line) @@ -58,6 +62,31 @@ object LogCatcherUtil { } } + private fun OutputStreamWriter.writeDeviceInfo() { + val info = BVApp.context.packageManager.getPackageInfo(BVApp.context.packageName, 0) + appendLine("======== Device info ========") + appendLine("App Version: ${info.versionName} (${info.versionCode})") + appendLine("Android Version: ${android.os.Build.VERSION.RELEASE} (${android.os.Build.VERSION.SDK_INT})") + appendLine("Device: ${android.os.Build.DEVICE}") + appendLine("Model: ${android.os.Build.MODEL}") + appendLine("Manufacturer: ${android.os.Build.MANUFACTURER}") + appendLine("Brand: ${android.os.Build.BRAND}") + appendLine("Product: ${android.os.Build.PRODUCT}") + appendLine("Type: ${android.os.Build.TYPE}") + } + + private fun OutputStreamWriter.writeAppInfo() { + appendLine("======== App Prefs ========") + appendLine("Login: ${Prefs.isLogin}") + appendLine("Incognito Mode: ${Prefs.incognitoMode}") + appendLine("Api Type: ${Prefs.apiType.name}") + appendLine("Default Resolution: ${Prefs.defaultQuality}") + appendLine("Default Codec: ${Prefs.defaultVideoCodec.name}") + appendLine("Default Audio: ${Prefs.defaultAudio.name}") + appendLine("Enabled Proxy: ${Prefs.enableProxy}") + appendLine("Using Old Player: ${Prefs.useOldPlayer}") + } + private fun createFilename(manual: Boolean): String { var filename = "" filename += if (manual) MANUAL_LOG_PREFIX else CRASH_LOG_PREFIX From 1b1e9d8f504ab384f9d6a02362beaf4a2888182b Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Thu, 29 Aug 2024 18:43:26 +0800 Subject: [PATCH 07/27] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gradle/androidx.versions.toml | 14 +++++++------- gradle/gradle.versions.toml | 4 ++-- gradle/libs.versions.toml | 16 ++++++++-------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 5ce80385..5fc9980a 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -1,15 +1,15 @@ [versions] -activity = "1.9.0" -compose = "1.7.0-beta03" +activity = "1.9.1" +compose = "1.7.0-rc01" compose-constraintlayout = "1.0.1" -compose-material3 = "1.3.0-beta03" -compose-tv = "1.0.0-beta01" -compose-tv-foundation = "1.0.0-alpha10" +compose-material3 = "1.3.0-rc01" +compose-tv = "1.0.0" +compose-tv-foundation = "1.0.0-alpha11" core = "1.13.1" core-splashscreen = "1.0.1" dataStore = "1.1.1" -lifecycle = "2.8.0" -media3 = "1.3.1" +lifecycle = "2.8.4" +media3 = "1.4.1" room = "2.6.1" webkit = "1.11.0" diff --git a/gradle/gradle.versions.toml b/gradle/gradle.versions.toml index a0064c8c..7a1b6ae6 100644 --- a/gradle/gradle.versions.toml +++ b/gradle/gradle.versions.toml @@ -1,9 +1,9 @@ [versions] -agp = "8.5.0" +agp = "8.5.2" firebase-crashlytics = "3.0.1" gms = "4.4.2" kotlin = "2.0.0" -ksp = "2.0.0-1.0.22" +ksp = "2.0.0-1.0.24" protobuf = "0.9.4" versions = "0.51.0" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6b24d841..bf2d5525 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,26 +1,26 @@ [versions] akdanmaku = "1.0.3" androidsvg = "1.4" -coil = "2.6.0" -firebase-bom = "33.1.0" +coil = "2.7.0" +firebase-bom = "33.2.0" geetest-sensebot = "4.4.2.1" grpc = "1.63.0" grpc-kotlin = "1.4.1" -jsoup = "1.17.2" +jsoup = "1.18.1" koin = "3.5.6" koin-compose = "3.5.6" kotlinx-coroutines = "1.8.1" -kotlinx-serialization = "1.7.0" -ktor = "2.3.11" +kotlinx-serialization = "1.7.2" +ktor = "2.3.12" ktor-jsoup = "2.3.0" logging = "7.0.0" -lottie = "6.4.1" +lottie = "6.5.1" material = "1.12.0" protobuf = "4.26.1" #noinspection GradleDependency 之后的版本要求 minSDK >= 23 qrcode = "3.3.0" -rememberPreference = "1.0.2" -slf4j-android-mvysny = "2.0.4" +rememberPreference = "1.1.0" +slf4j-android-mvysny = "2.0.13" slf4j-simple = "2.0.9" vlc = "3.6.0-eap5" From 511a788de13c529763d1d1b48ae3e8c1f05c226f Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Thu, 29 Aug 2024 18:43:46 +0800 Subject: [PATCH 08/27] =?UTF-8?q?=E7=94=B1=E4=BA=8E=E5=A4=96=E9=83=A8API?= =?UTF-8?q?=E7=9A=84=E6=94=B9=E5=8F=98=E8=80=8C=E6=9B=B4=E6=96=B0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/aaa1115910/bv/component/UserPanel.kt | 12 +++--- .../controllers/VideoListController.kt | 10 ++--- .../controllers/VideoPlayerMenuController.kt | 20 +++++----- .../controllers2/VideoListController.kt | 10 ++--- .../playermenu/ClosedCaptionMenu.kt | 6 +-- .../controllers2/playermenu/MenuNav.kt | 6 +-- .../controllers2/playermenu/PictureMenu.kt | 6 +-- .../playermenu/component/CheckBoxMenuList.kt | 6 +-- .../playermenu/component/RadioMenuList.kt | 6 +-- .../bv/component/index/IndexFilter.kt | 10 ++--- .../bv/component/videocard/SeasonCard.kt | 6 +-- .../bv/component/videocard/SmallVideoCard.kt | 11 ++--- .../bv/component/videocard/VideosRow.kt | 6 +-- .../dev/aaa1115910/bv/screen/HomeScreen.kt | 22 +++++----- .../aaa1115910/bv/screen/SeasonInfoScreen.kt | 30 +++++++------- .../dev/aaa1115910/bv/screen/TagScreen.kt | 10 ++--- .../aaa1115910/bv/screen/VideoInfoScreen.kt | 40 +++++++++---------- .../aaa1115910/bv/screen/home/AnimeScreen.kt | 20 +++++----- .../bv/screen/home/DynamicsScreen.kt | 24 +++++------ .../bv/screen/home/PopularScreen.kt | 22 +++++----- .../bv/screen/home/RecommendScreen.kt | 22 +++++----- .../bv/screen/home/anime/AnimeIndexScreen.kt | 14 +++---- .../screen/home/anime/AnimeTimelineScreen.kt | 10 ++--- .../bv/screen/search/SearchInputScreen.kt | 10 ++--- .../bv/screen/search/SearchResultFilter.kt | 12 +++--- .../bv/screen/search/SearchResultScreen.kt | 10 ++--- .../bv/screen/settings/LogsScreen.kt | 6 +-- .../bv/screen/settings/MediaCodecScreen.kt | 8 ++-- .../bv/screen/settings/SettingsScreen.kt | 4 +- .../bv/screen/settings/content/ApiSetting.kt | 6 +-- .../screen/settings/content/AudioSetting.kt | 6 +-- .../screen/settings/content/OtherSetting.kt | 4 +- .../settings/content/PlayerTypeSetting.kt | 6 +-- .../settings/content/ResolutionSetting.kt | 6 +-- .../screen/settings/content/StorageSetting.kt | 4 +- .../bv/screen/settings/content/UISetting.kt | 4 +- .../settings/content/VideoCodecSetting.kt | 6 +-- .../bv/screen/user/FavoriteScreen.kt | 20 +++++----- .../aaa1115910/bv/screen/user/FollowScreen.kt | 14 +++---- .../bv/screen/user/FollowingSeasonFilter.kt | 8 ++-- .../bv/screen/user/FollowingSeasonScreen.kt | 14 +++---- .../bv/screen/user/HistoryScreen.kt | 10 ++--- .../aaa1115910/bv/screen/user/UpInfoScreen.kt | 10 ++--- .../bv/screen/user/UserInfoScreen.kt | 12 +++--- .../bv/screen/user/UserSwitchScreen.kt | 10 ++--- .../user/lock/UnlockSwitchUserContent.kt | 6 +-- .../bv/screen/user/lock/UnlockUserScreen.kt | 6 +-- .../user/lock/UserLockSettingsScreen.kt | 4 +- .../aaa1115910/bv/util/VideoShotExtends.kt | 10 ++--- 49 files changed, 273 insertions(+), 272 deletions(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt index 2db2b1d3..b1b0a3f3 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt @@ -13,6 +13,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CrueltyFree @@ -33,9 +36,6 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvGridItemSpan -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid import androidx.tv.material3.ClickableSurfaceDefaults import androidx.tv.material3.Icon import androidx.tv.material3.MaterialTheme @@ -80,12 +80,12 @@ fun UserPanel( }, shape = MaterialTheme.shapes.medium ) { - TvLazyVerticalGrid( - columns = TvGridCells.Fixed(2), + LazyVerticalGrid( + columns = GridCells.Fixed(2), contentPadding = PaddingValues(12.dp) ) { item( - span = { TvGridItemSpan(2) }, + span = { GridItemSpan(2) }, ) { UserPanelMyItem( modifier = Modifier diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers/VideoListController.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers/VideoListController.kt index d6e221a3..2ac6ee22 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers/VideoListController.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers/VideoListController.kt @@ -7,6 +7,9 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember @@ -18,9 +21,6 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.items -import androidx.tv.foundation.lazy.list.rememberTvLazyListState import androidx.tv.material3.Surface import androidx.tv.material3.SurfaceDefaults import dev.aaa1115910.bv.repository.VideoListItem @@ -32,7 +32,7 @@ fun VideoListController( onVideoSwitch: (VideoListItem) -> Unit ) { val scope = rememberCoroutineScope() - val listState = rememberTvLazyListState() + val listState = rememberLazyListState() val videoPlayerControllerData = LocalVideoPlayerControllerData.current val focusRequester = remember { FocusRequester() } @@ -57,7 +57,7 @@ fun VideoListController( .fillMaxHeight(), contentAlignment = Alignment.Center ) { - TvLazyColumn( + LazyColumn( state = listState, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 120.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers/VideoPlayerMenuController.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers/VideoPlayerMenuController.kt index 264469a7..0810d4f1 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers/VideoPlayerMenuController.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers/VideoPlayerMenuController.kt @@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -34,8 +36,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.DenseListItem import androidx.tv.material3.Surface import androidx.tv.material3.SurfaceDefaults @@ -139,7 +139,7 @@ private fun VideoPlayerMenuControllerNav( focusRequester.requestFocus(scope) } - TvLazyColumn( + LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 120.dp) @@ -272,7 +272,7 @@ private fun ResolutionMenuContent( val context = LocalContext.current val qualityMap by remember { mutableStateOf(resolutionMap.toSortedMap(compareByDescending { it })) } - TvLazyColumn( + LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 120.dp) @@ -298,7 +298,7 @@ private fun VideoCodecMenuContent( ) { val context = LocalContext.current - TvLazyColumn( + LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 120.dp) @@ -321,7 +321,7 @@ private fun VideoAspectRatioMenuContent( ) { val context = LocalContext.current - TvLazyColumn( + LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 120.dp) @@ -342,7 +342,7 @@ private fun DanmakuSwitchMenuContent( currentDanmakuEnabled: Boolean, onSwitchDanmaku: (Boolean) -> Unit ) { - TvLazyColumn( + LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 120.dp) @@ -370,7 +370,7 @@ private fun DanmakuSizeMenuContent( currentDanmakuSize: DanmakuSize = DanmakuSize.S2, onDanmakuSizeChange: (DanmakuSize) -> Unit ) { - TvLazyColumn( + LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 120.dp) @@ -391,7 +391,7 @@ private fun DanmakuTransparencyMenuContent( currentDanmakuTransparency: DanmakuTransparency = DanmakuTransparency.T1, onDanmakuTransparencyChange: (DanmakuTransparency) -> Unit ) { - TvLazyColumn( + LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 120.dp) @@ -478,7 +478,7 @@ fun SubtitleContent( availableSubtitle: List = emptyList(), onSubtitleChange: (Long) -> Unit ) { - TvLazyColumn( + LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 120.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/VideoListController.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/VideoListController.kt index 0b0e7036..fbadcdcf 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/VideoListController.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/VideoListController.kt @@ -10,6 +10,9 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.remember @@ -21,9 +24,6 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.items -import androidx.tv.foundation.lazy.list.rememberTvLazyListState import androidx.tv.material3.Surface import androidx.tv.material3.SurfaceDefaults import dev.aaa1115910.bv.component.controllers.MenuListItem @@ -39,7 +39,7 @@ fun VideoListController( onPlayNewVideo: (VideoListItem) -> Unit ) { val scope = rememberCoroutineScope() - val listState = rememberTvLazyListState() + val listState = rememberLazyListState() val focusRequester = remember { FocusRequester() } LaunchedEffect(show) { @@ -69,7 +69,7 @@ fun VideoListController( .fillMaxHeight(), contentAlignment = Alignment.Center ) { - TvLazyColumn( + LazyColumn( state = listState, verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(vertical = 120.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/ClosedCaptionMenu.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/ClosedCaptionMenu.kt index 62252682..6afd0363 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/ClosedCaptionMenu.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/ClosedCaptionMenu.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -26,8 +28,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.itemsIndexed import dev.aaa1115910.biliapi.entity.video.Subtitle import dev.aaa1115910.bv.component.controllers.LocalVideoPlayerControllerData import dev.aaa1115910.bv.component.controllers2.LocalMenuFocusStateData @@ -111,7 +111,7 @@ fun ClosedCaptionMenuList( } } - TvLazyColumn( + LazyColumn( modifier = Modifier .focusRequester(focusRequester) .padding(horizontal = 8.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/MenuNav.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/MenuNav.kt index 9a0885df..7d36955b 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/MenuNav.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/MenuNav.kt @@ -3,12 +3,12 @@ package dev.aaa1115910.bv.component.controllers2.playermenu import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.itemsIndexed import dev.aaa1115910.bv.component.controllers2.VideoPlayerMenuNavItem import dev.aaa1115910.bv.component.controllers2.playermenu.component.MenuListItem import dev.aaa1115910.bv.component.createCustomInitialFocusRestorerModifiers @@ -24,7 +24,7 @@ fun MenuNavList( val context = LocalContext.current val focusRestorerModifiers = createCustomInitialFocusRestorerModifiers() - TvLazyColumn( + LazyColumn( modifier = modifier .animateContentSize() .then(focusRestorerModifiers.parentModifier), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt index bb668ceb..906b427c 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/PictureMenu.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -23,8 +25,6 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.itemsIndexed import dev.aaa1115910.bv.component.controllers.LocalVideoPlayerControllerData import dev.aaa1115910.bv.component.controllers2.LocalMenuFocusStateData import dev.aaa1115910.bv.component.controllers2.MenuFocusState @@ -137,7 +137,7 @@ fun PictureMenuList( } } - TvLazyColumn( + LazyColumn( modifier = Modifier .focusRequester(focusRequester) .padding(horizontal = 8.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/component/CheckBoxMenuList.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/component/CheckBoxMenuList.kt index 13fbda4f..fb009d6c 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/component/CheckBoxMenuList.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/component/CheckBoxMenuList.kt @@ -3,6 +3,8 @@ package dev.aaa1115910.bv.component.controllers2.playermenu.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.key.Key @@ -11,8 +13,6 @@ import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.itemsIndexed import dev.aaa1115910.bv.component.createCustomInitialFocusRestorerModifiers import dev.aaa1115910.bv.component.ifElse @@ -25,7 +25,7 @@ fun CheckBoxMenuList( onFocusBackToParent: () -> Unit ) { val focusRestorerModifiers = createCustomInitialFocusRestorerModifiers() - TvLazyColumn( + LazyColumn( modifier = modifier .onPreviewKeyEvent { println(it) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/component/RadioMenuList.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/component/RadioMenuList.kt index 037c0062..82200f43 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/component/RadioMenuList.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/component/RadioMenuList.kt @@ -3,6 +3,8 @@ package dev.aaa1115910.bv.component.controllers2.playermenu.component import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.input.key.Key @@ -11,8 +13,6 @@ import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.itemsIndexed import dev.aaa1115910.bv.component.createCustomInitialFocusRestorerModifiers import dev.aaa1115910.bv.component.ifElse @@ -25,7 +25,7 @@ fun RadioMenuList( onFocusBackToParent: () -> Unit ) { val focusRestorerModifiers = createCustomInitialFocusRestorerModifiers() - TvLazyColumn( + LazyColumn( modifier = modifier .onPreviewKeyEvent { println(it) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/index/IndexFilter.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/index/IndexFilter.kt index a6b0913a..5661d9a9 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/index/IndexFilter.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/index/IndexFilter.kt @@ -11,6 +11,9 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Check import androidx.compose.material3.AlertDialog @@ -30,9 +33,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.ExperimentalTvMaterial3Api import androidx.tv.material3.FilterChip import androidx.tv.material3.MaterialTheme @@ -123,7 +123,7 @@ fun AnimeIndexFilter( HorizontalDivider( modifier = Modifier.padding(vertical = 8.dp) ) - TvLazyColumn { + LazyColumn { item { IndexFilterChipRow( title = "类型", @@ -302,7 +302,7 @@ fun IndexFilterChipRow( text = title, style = MaterialTheme.typography.labelLarge ) - TvLazyRow( + LazyRow( modifier = modifier .then(focusRestorerModifiers.parentModifier), horizontalArrangement = Arrangement.spacedBy(8.dp), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SeasonCard.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SeasonCard.kt index 11d69317..2729848a 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SeasonCard.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SeasonCard.kt @@ -9,6 +9,8 @@ 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.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -31,8 +33,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid import androidx.tv.material3.Border import androidx.tv.material3.ClickableSurfaceDefaults import androidx.tv.material3.MaterialTheme @@ -155,7 +155,7 @@ fun SeasonCard( @Composable private fun SeasonCardPreview() { BVTheme { - TvLazyVerticalGrid(columns = TvGridCells.Fixed(6)) { + LazyVerticalGrid(columns = GridCells.Fixed(6)) { repeat(6) { item { SeasonCard( diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt index 95bf1fbb..85b1651a 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt @@ -10,6 +10,8 @@ import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -22,8 +24,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid import androidx.tv.material3.Border import androidx.tv.material3.ClickableSurfaceDefaults import androidx.tv.material3.Icon @@ -47,7 +47,8 @@ fun SmallVideoCard( onFocus: () -> Unit = {} ) { Surface( - modifier = modifier.onFocusChanged { if (it.hasFocus) onFocus() }, + modifier = modifier + .onFocusChanged { if (it.hasFocus) onFocus() }, onClick = onClick, onLongClick = onLongClick, colors = ClickableSurfaceDefaults.colors( @@ -203,8 +204,8 @@ fun SmallVideoCardsPreview() { time = 2333 * 1000 ) BVTheme { - TvLazyVerticalGrid( - columns = TvGridCells.Fixed(4) + LazyVerticalGrid( + columns = GridCells.Fixed(4) ) { repeat(20) { item { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/VideosRow.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/VideosRow.kt index b7f28f85..a4863999 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/VideosRow.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/VideosRow.kt @@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -22,8 +24,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.itemsIndexed import androidx.tv.material3.Button import androidx.tv.material3.ButtonDefaults import androidx.tv.material3.MaterialTheme @@ -63,7 +63,7 @@ fun VideosRow( fontSize = titleFontSize.sp, color = titleColor ) - TvLazyRow( + LazyRow( modifier = Modifier .padding(top = 15.dp) .then(focusRestorerModifiers.parentModifier) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/HomeScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/HomeScreen.kt index 23262876..bc2baabc 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/HomeScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/HomeScreen.kt @@ -13,6 +13,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -30,8 +32,6 @@ import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.grid.rememberTvLazyGridState -import androidx.tv.foundation.lazy.list.rememberTvLazyListState import dev.aaa1115910.bv.R import dev.aaa1115910.bv.activities.search.SearchInputActivity import dev.aaa1115910.bv.activities.user.FavoriteActivity @@ -70,10 +70,10 @@ fun HomeScreen( val scope = rememberCoroutineScope() val logger = KotlinLogging.logger { } - val recommendState = rememberTvLazyGridState() - val popularState = rememberTvLazyGridState() - val animeState = rememberTvLazyListState() - val dynamicState = rememberTvLazyGridState() + val recommendState = rememberLazyGridState() + val popularState = rememberLazyGridState() + val animeState = rememberLazyListState() + val dynamicState = rememberLazyGridState() var selectedTab by remember { mutableStateOf(TopNavItem.Popular) } var showUserPanel by remember { mutableStateOf(false) } @@ -224,28 +224,28 @@ fun HomeScreen( ) { screen -> when (screen) { TopNavItem.Recommend -> RecommendScreen( - tvLazyGridState = recommendState, + lazyGridState = recommendState, onBackNav = onFocusBackToNav ) TopNavItem.Popular -> PopularScreen( - tvLazyGridState = popularState, + lazyGridState = popularState, onBackNav = onFocusBackToNav ) TopNavItem.Partition -> PartitionScreen() TopNavItem.Anime -> AnimeScreen( - tvLazyListState = animeState, + lazyListState = animeState, onBackNav = onFocusBackToNav ) TopNavItem.Dynamics -> DynamicsScreen( - tvLazyGridState = dynamicState, + lazyGridState = dynamicState, onBackNav = onFocusBackToNav ) else -> PopularScreen( - tvLazyGridState = popularState, + lazyGridState = popularState, onBackNav = onFocusBackToNav ) } diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/SeasonInfoScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/SeasonInfoScreen.kt index 3bb54132..464f2be9 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/SeasonInfoScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/SeasonInfoScreen.kt @@ -23,6 +23,14 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.foundation.shape.RoundedCornerShape @@ -66,14 +74,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed -import androidx.tv.foundation.lazy.grid.rememberTvLazyGridState -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.itemsIndexed -import androidx.tv.foundation.lazy.list.rememberTvLazyListState import androidx.tv.material3.Border import androidx.tv.material3.Card import androidx.tv.material3.CardDefaults @@ -267,7 +267,7 @@ fun SeasonInfoScreen( Scaffold( modifier = modifier ) { innerPadding -> - TvLazyColumn( + LazyColumn( modifier = Modifier .padding(innerPadding) .fillMaxSize(), @@ -738,7 +738,7 @@ fun SeasonEpisodesDialog( val tabRowFocusRequester = remember { FocusRequester() } val videoListFocusRequester = remember { FocusRequester() } - val listState = rememberTvLazyGridState() + val listState = rememberLazyGridState() LaunchedEffect(selectedTabIndex) { val fromIndex = selectedTabIndex * 20 @@ -804,9 +804,9 @@ fun SeasonEpisodesDialog( } } - TvLazyVerticalGrid( + LazyVerticalGrid( state = listState, - columns = TvGridCells.Fixed(2), + columns = GridCells.Fixed(2), contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), ) { @@ -882,7 +882,7 @@ fun SeasonEpisodeRow( color = titleColor ) - TvLazyRow( + LazyRow( modifier = Modifier .padding(top = 15.dp) .then(focusRestorerModifiers.parentModifier), @@ -999,7 +999,7 @@ private fun SeasonSelectorContent( onClickSeason: (Int) -> Unit ) { val scope = rememberCoroutineScope() - val rowState = rememberTvLazyListState() + val rowState = rememberLazyListState() val logger = KotlinLogging.logger {} val currentSeasonFocusRequester = remember { FocusRequester() } val bringIntoViewRequester = remember { BringIntoViewRequester() } @@ -1094,7 +1094,7 @@ private fun SeasonSelectorContent( Box( modifier = Modifier.align(Alignment.BottomStart) ) { - TvLazyRow( + LazyRow( modifier = Modifier.padding(bottom = 48.dp), state = rowState, contentPadding = PaddingValues(horizontal = 48.dp), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/TagScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/TagScreen.kt index 41fe0991..d103062b 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/TagScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/TagScreen.kt @@ -9,6 +9,9 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -24,9 +27,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed import androidx.tv.material3.Text import dev.aaa1115910.bv.R import dev.aaa1115910.bv.activities.video.VideoInfoActivity @@ -96,9 +96,9 @@ fun TagScreen( } } ) { innerPadding -> - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = Modifier.padding(innerPadding), - columns = TvGridCells.Fixed(4), + columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/VideoInfoScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/VideoInfoScreen.kt index 31abb808..8ef6fcfb 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/VideoInfoScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/VideoInfoScreen.kt @@ -21,6 +21,14 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Add import androidx.compose.material.icons.rounded.Done @@ -64,14 +72,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed -import androidx.tv.foundation.lazy.grid.rememberTvLazyGridState -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.items -import androidx.tv.foundation.lazy.list.itemsIndexed import androidx.tv.material3.ClickableSurfaceDefaults import androidx.tv.material3.ExperimentalTvMaterial3Api import androidx.tv.material3.Glow @@ -481,7 +481,7 @@ fun VideoInfoScreen( contentScale = ContentScale.Crop, alpha = 0.6f ) - TvLazyColumn( + LazyColumn( contentPadding = PaddingValues(top = 16.dp, bottom = 32.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { @@ -850,11 +850,11 @@ fun VideoInfoData( onAddToDefaultFavoriteFolder = onAddToDefaultFavoriteFolder, onUpdateFavoriteFolders = onUpdateFavoriteFolders ) - TvLazyRow( + LazyRow( contentPadding = PaddingValues(horizontal = 16.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) ) { - items(tags) { tag -> + items(items = tags) { tag -> SuggestionChip(onClick = { onClickTip(tag) }) { @@ -998,7 +998,7 @@ fun VideoDescriptionDialog( ) }, text = { - TvLazyColumn { + LazyColumn { item { Text(text = description) } @@ -1109,7 +1109,7 @@ fun VideoPartRow( color = titleColor ) - TvLazyRow( + LazyRow( modifier = Modifier .padding(top = 15.dp) .then(focusRestorerModifiers.parentModifier), @@ -1177,7 +1177,7 @@ fun VideoUgcSeasonRow( color = titleColor ) - TvLazyRow( + LazyRow( modifier = Modifier .padding(top = 15.dp) .then(focusRestorerModifiers.parentModifier), @@ -1231,7 +1231,7 @@ private fun VideoPartListDialog( val tabRowFocusRequester = remember { FocusRequester() } val videoListFocusRequester = remember { FocusRequester() } - val listState = rememberTvLazyGridState() + val listState = rememberLazyGridState() LaunchedEffect(selectedTabIndex) { val fromIndex = selectedTabIndex * 20 @@ -1292,9 +1292,9 @@ private fun VideoPartListDialog( } } - TvLazyVerticalGrid( + LazyVerticalGrid( state = listState, - columns = TvGridCells.Fixed(2), + columns = GridCells.Fixed(2), contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) @@ -1339,7 +1339,7 @@ private fun VideoUgcListDialog( val tabRowFocusRequester = remember { FocusRequester() } val videoListFocusRequester = remember { FocusRequester() } - val listState = rememberTvLazyGridState() + val listState = rememberLazyGridState() LaunchedEffect(selectedTabIndex) { val fromIndex = selectedTabIndex * 20 @@ -1400,9 +1400,9 @@ private fun VideoUgcListDialog( } } - TvLazyVerticalGrid( + LazyVerticalGrid( state = listState, - columns = TvGridCells.Fixed(2), + columns = GridCells.Fixed(2), contentPadding = PaddingValues(8.dp), verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/AnimeScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/AnimeScreen.kt index eb1efece..6cca1a01 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/AnimeScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/AnimeScreen.kt @@ -22,6 +22,10 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.List import androidx.compose.material.icons.rounded.Alarm @@ -50,10 +54,6 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.TvLazyListState -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.itemsIndexed import androidx.tv.material3.Carousel import androidx.tv.material3.ClickableSurfaceDefaults import androidx.tv.material3.ExperimentalTvMaterial3Api @@ -86,7 +86,7 @@ import org.koin.androidx.compose.koinViewModel @Composable fun AnimeScreen( modifier: Modifier = Modifier, - tvLazyListState: TvLazyListState, + lazyListState: LazyListState, onBackNav: () -> Unit, animeViewModel: AnimeViewModel = koinViewModel() ) { @@ -96,14 +96,14 @@ fun AnimeScreen( val carouselItems = animeViewModel.carouselItems val animeFeeds = animeViewModel.feedItems - TvLazyColumn( + LazyColumn( modifier = modifier .onPreviewKeyEvent { when (it.key) { Key.Back -> { if (it.type == KeyEventType.KeyUp) { scope.launch(Dispatchers.Main) { - tvLazyListState.animateScrollToItem(0) + lazyListState.animateScrollToItem(0) } onBackNav() } @@ -112,7 +112,7 @@ fun AnimeScreen( } return@onPreviewKeyEvent false }, - state = tvLazyListState + state = lazyListState ) { item { AnimeCarousel( @@ -360,7 +360,7 @@ fun AnimeFeedVideoRow( data: List ) { val context = LocalContext.current - TvLazyRow( + LazyRow( modifier = modifier, contentPadding = PaddingValues(horizontal = 24.dp), horizontalArrangement = Arrangement.spacedBy(18.dp) @@ -476,7 +476,7 @@ fun AnimeFeedRankRow( ) } - TvLazyRow( + LazyRow( modifier = modifier, contentPadding = PaddingValues(horizontal = 32.dp), horizontalArrangement = Arrangement.spacedBy(18.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt index fb81eb95..73ef756e 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt @@ -4,6 +4,11 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -21,11 +26,6 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvGridItemSpan -import androidx.tv.foundation.lazy.grid.TvLazyGridState -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed import androidx.tv.material3.Text import dev.aaa1115910.bv.activities.video.VideoInfoActivity import dev.aaa1115910.bv.component.LoadingTip @@ -40,7 +40,7 @@ import org.koin.androidx.compose.koinViewModel @Composable fun DynamicsScreen( modifier: Modifier = Modifier, - tvLazyGridState: TvLazyGridState, + lazyGridState: LazyGridState, onBackNav: () -> Unit, dynamicViewModel: DynamicViewModel = koinViewModel() ) { @@ -55,14 +55,14 @@ fun DynamicsScreen( } if (dynamicViewModel.isLogin) { - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = modifier .onPreviewKeyEvent { when (it.key) { Key.Back -> { if (it.type == KeyEventType.KeyUp) { scope.launch(Dispatchers.Main) { - tvLazyGridState.animateScrollToItem(0) + lazyGridState.animateScrollToItem(0) } onBackNav() } @@ -77,8 +77,8 @@ fun DynamicsScreen( } return@onPreviewKeyEvent false }, - state = tvLazyGridState, - columns = TvGridCells.Fixed(4), + state = lazyGridState, + columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) @@ -106,7 +106,7 @@ fun DynamicsScreen( } if (dynamicViewModel.loading) item( - span = { TvGridItemSpan(4) } + span = { GridItemSpan(4) } ) { Box( modifier = Modifier.fillMaxSize(), @@ -118,7 +118,7 @@ fun DynamicsScreen( if (!dynamicViewModel.hasMore) item( - span = { TvGridItemSpan(4) } + span = { GridItemSpan(4) } ) { Text( text = "没有更多了捏", diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt index daa1f41f..20b4fdf1 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt @@ -4,6 +4,11 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -20,11 +25,6 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvGridItemSpan -import androidx.tv.foundation.lazy.grid.TvLazyGridState -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed import dev.aaa1115910.bv.activities.video.VideoInfoActivity import dev.aaa1115910.bv.component.LoadingTip import dev.aaa1115910.bv.component.videocard.SmallVideoCard @@ -37,7 +37,7 @@ import org.koin.androidx.compose.koinViewModel @Composable fun PopularScreen( modifier: Modifier = Modifier, - tvLazyGridState: TvLazyGridState, + lazyGridState: LazyGridState, onBackNav: () -> Unit, popularViewModel: PopularViewModel = koinViewModel() ) { @@ -51,14 +51,14 @@ fun PopularScreen( } } - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = modifier .onPreviewKeyEvent { when (it.key) { Key.Back -> { if (it.type == KeyEventType.KeyUp) { scope.launch(Dispatchers.Main) { - tvLazyGridState.animateScrollToItem(0) + lazyGridState.animateScrollToItem(0) } onBackNav() } @@ -73,8 +73,8 @@ fun PopularScreen( } return@onPreviewKeyEvent false }, - state = tvLazyGridState, - columns = TvGridCells.Fixed(4), + state = lazyGridState, + columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) @@ -96,7 +96,7 @@ fun PopularScreen( } if (popularViewModel.loading) item( - span = { TvGridItemSpan(4) } + span = { GridItemSpan(4) } ) { Box( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt index cd46e75a..66f8f8df 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt @@ -4,6 +4,11 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -20,11 +25,6 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvGridItemSpan -import androidx.tv.foundation.lazy.grid.TvLazyGridState -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed import dev.aaa1115910.bv.activities.video.VideoInfoActivity import dev.aaa1115910.bv.component.LoadingTip import dev.aaa1115910.bv.component.videocard.SmallVideoCard @@ -37,7 +37,7 @@ import org.koin.androidx.compose.koinViewModel @Composable fun RecommendScreen( modifier: Modifier = Modifier, - tvLazyGridState: TvLazyGridState, + lazyGridState: LazyGridState, onBackNav: () -> Unit, recommendViewModel: RecommendViewModel = koinViewModel() ) { @@ -51,14 +51,14 @@ fun RecommendScreen( } } - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = modifier .onPreviewKeyEvent { when (it.key) { Key.Back -> { if (it.type == KeyEventType.KeyUp) { scope.launch(Dispatchers.Main) { - tvLazyGridState.animateScrollToItem(0) + lazyGridState.animateScrollToItem(0) } onBackNav() } @@ -73,8 +73,8 @@ fun RecommendScreen( } return@onPreviewKeyEvent false }, - state = tvLazyGridState, - columns = TvGridCells.Fixed(4), + state = lazyGridState, + columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) @@ -96,7 +96,7 @@ fun RecommendScreen( } if (recommendViewModel.loading) item( - span = { TvGridItemSpan(4) } + span = { GridItemSpan(4) } ) { Box( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeIndexScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeIndexScreen.kt index 746fe72d..89a3847c 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeIndexScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeIndexScreen.kt @@ -9,6 +9,10 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -26,10 +30,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvGridItemSpan -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed import androidx.tv.material3.OutlinedButton import androidx.tv.material3.Text import dev.aaa1115910.biliapi.http.entity.search.SearchMediaResult @@ -126,9 +126,9 @@ fun AnimeIndexScreen( } } ) { innerPadding -> - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = Modifier.padding(innerPadding), - columns = TvGridCells.Fixed(6), + columns = GridCells.Fixed(6), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) @@ -171,7 +171,7 @@ fun AnimeIndexScreen( } if (indexResultItems.isEmpty() && noMore) { item( - span = { TvGridItemSpan(6) } + span = { GridItemSpan(6) } ) { Box( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeTimelineScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeTimelineScreen.kt index 5e90b179..c595ed14 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeTimelineScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeTimelineScreen.kt @@ -9,6 +9,9 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -29,9 +32,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.itemsIndexed -import androidx.tv.foundation.lazy.list.rememberTvLazyListState import androidx.tv.material3.Text import dev.aaa1115910.biliapi.entity.ApiType import dev.aaa1115910.biliapi.entity.season.Timeline @@ -62,7 +62,7 @@ fun AnimeTimelineScreen( val context = LocalContext.current val scope = rememberCoroutineScope() val logger = KotlinLogging.logger { } - val listState = rememberTvLazyListState() + val listState = rememberLazyListState() var currentTimelineIndex by remember { mutableIntStateOf(0) } var currentEpisodeIndex by remember { mutableIntStateOf(0) } @@ -121,7 +121,7 @@ fun AnimeTimelineScreen( } } ) { innerPadding -> - TvLazyColumn( + LazyColumn( state = listState, modifier = Modifier.padding(innerPadding), contentPadding = PaddingValues(bottom = 48.dp, start = 48.dp, end = 48.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchInputScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchInputScreen.kt index 91515df5..f64a401e 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchInputScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchInputScreen.kt @@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.OutlinedTextField @@ -27,8 +29,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.itemsIndexed import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import dev.aaa1115910.bv.R @@ -159,7 +159,7 @@ fun SearchInputScreen( text = stringResource(R.string.search_input_hotword), style = MaterialTheme.typography.titleLarge ) - TvLazyColumn( + LazyColumn( modifier = Modifier .then(hotsFocusRestorerModifiers.parentModifier) ) { @@ -185,7 +185,7 @@ fun SearchInputScreen( text = stringResource(R.string.search_input_suggest), style = MaterialTheme.typography.titleLarge ) - TvLazyColumn( + LazyColumn( modifier = Modifier .then(suggestFocusRestorerModifiers.parentModifier) ) { @@ -215,7 +215,7 @@ fun SearchInputScreen( text = stringResource(R.string.search_input_history), style = MaterialTheme.typography.titleLarge ) - TvLazyColumn( + LazyColumn( modifier = Modifier .then(historyFocusRestorerModifiers.parentModifier) ) { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchResultFilter.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchResultFilter.kt index c4498664..ae334f11 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchResultFilter.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchResultFilter.kt @@ -7,6 +7,8 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.material3.AlertDialog import androidx.compose.material3.FilterChip import androidx.compose.material3.FilterChipDefaults @@ -28,8 +30,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.window.DialogProperties -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.Text import dev.aaa1115910.biliapi.repositories.SearchFilterDuration import dev.aaa1115910.biliapi.repositories.SearchFilterOrderType @@ -72,7 +72,7 @@ fun SearchResultVideoFilter( title = { Text(text = stringResource(R.string.filter_dialog_title)) }, text = { Column { - TvLazyRow( + LazyRow( modifier = Modifier.onPreviewKeyEvent { if (it.key == Key.DirectionDown) { if (it.nativeKeyEvent.action == KeyEvent.ACTION_UP) { @@ -94,7 +94,7 @@ fun SearchResultVideoFilter( ) } } - TvLazyRow( + LazyRow( modifier = Modifier.onPreviewKeyEvent { if (it.key == Key.DirectionDown) { if (it.nativeKeyEvent.action == KeyEvent.ACTION_UP) { @@ -123,7 +123,7 @@ fun SearchResultVideoFilter( ) } } - TvLazyRow( + LazyRow( modifier = Modifier.onPreviewKeyEvent { if (it.key == Key.DirectionDown) { if (selectedChildPartition == null) return@onPreviewKeyEvent false @@ -168,7 +168,7 @@ fun SearchResultVideoFilter( } } AnimatedVisibility(visible = selectedPartition != null) { - TvLazyRow( + LazyRow( modifier = Modifier.onPreviewKeyEvent { if (it.key == Key.DirectionUp) { if (it.nativeKeyEvent.action == KeyEvent.ACTION_UP) { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchResultScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchResultScreen.kt index e605e4b0..c342be23 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchResultScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchResultScreen.kt @@ -12,6 +12,9 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -37,9 +40,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed import androidx.tv.material3.LocalContentColor import androidx.tv.material3.Tab import androidx.tv.material3.TabRow @@ -245,7 +245,7 @@ fun SearchResultScreen( } } - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = Modifier.onPreviewKeyEvent { when (it.key) { Key.Back -> { @@ -255,7 +255,7 @@ fun SearchResultScreen( } false }, - columns = TvGridCells.Fixed(rowSize), + columns = GridCells.Fixed(rowSize), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/LogsScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/LogsScreen.kt index 7a01647d..99ce0c59 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/LogsScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/LogsScreen.kt @@ -17,6 +17,8 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable @@ -42,8 +44,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.ListItem import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text @@ -198,7 +198,7 @@ fun LogsScreenContent( Box( modifier = Modifier.weight(1f) ) { - TvLazyColumn( + LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = PaddingValues( horizontal = 36.dp, diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/MediaCodecScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/MediaCodecScreen.kt index d0fa2597..b183387b 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/MediaCodecScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/MediaCodecScreen.kt @@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Audiotrack import androidx.compose.material.icons.filled.Videocam @@ -39,8 +41,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.Icon import androidx.tv.material3.ListItem import androidx.tv.material3.MaterialTheme @@ -153,7 +153,7 @@ fun MediaCodecListItems( focusRequester.requestFocus(scope) } - TvLazyColumn( + LazyColumn( modifier = modifier, contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(8.dp) @@ -229,7 +229,7 @@ fun MediaCodecDetails( val context = LocalContext.current if (currentCodecInfoData != null) { - TvLazyColumn( + LazyColumn( modifier = modifier .fillMaxSize() .onPreviewKeyEvent { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/SettingsScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/SettingsScreen.kt index db12f98c..51e59365 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/SettingsScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/SettingsScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -34,7 +35,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.list.TvLazyColumn import androidx.tv.material3.ListItem import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text @@ -136,7 +136,7 @@ fun SettingsNav( focusRequester.requestFocus(scope) } - TvLazyColumn( + LazyColumn( modifier = modifier, contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(8.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/ApiSetting.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/ApiSetting.kt index 7fa02887..1db60a9e 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/ApiSetting.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/ApiSetting.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -16,8 +18,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import dev.aaa1115910.biliapi.entity.ApiType @@ -47,7 +47,7 @@ fun ApiSetting( style = MaterialTheme.typography.displaySmall ) Spacer(modifier = Modifier.height(12.dp)) - TvLazyColumn( + LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) ) { items(items = ApiType.entries) { apiType -> diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/AudioSetting.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/AudioSetting.kt index 6d03e847..70c63560 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/AudioSetting.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/AudioSetting.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -16,8 +18,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import dev.aaa1115910.bv.component.settings.SettingsMenuSelectItem @@ -47,7 +47,7 @@ fun AudioSetting( style = MaterialTheme.typography.displaySmall ) Spacer(modifier = Modifier.height(12.dp)) - TvLazyColumn( + LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) ) { items(items = Audio.entries) { audio -> diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/OtherSetting.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/OtherSetting.kt index ea26d54a..176cf723 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/OtherSetting.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/OtherSetting.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -17,7 +18,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import dev.aaa1115910.bv.BuildConfig @@ -51,7 +51,7 @@ fun OtherSetting( style = MaterialTheme.typography.displaySmall ) Spacer(modifier = Modifier.height(12.dp)) - TvLazyColumn( + LazyColumn( modifier = Modifier .fillMaxSize() .padding(horizontal = 48.dp), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/PlayerTypeSetting.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/PlayerTypeSetting.kt index 6afa6246..96a5ebb0 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/PlayerTypeSetting.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/PlayerTypeSetting.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -16,8 +18,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import dev.aaa1115910.bv.component.LibVLCDownloaderDialog @@ -49,7 +49,7 @@ fun PlayerTypeSetting( style = MaterialTheme.typography.displaySmall ) Spacer(modifier = Modifier.height(12.dp)) - TvLazyColumn( + LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) ) { items(items = PlayerType.entries) { playerType -> diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/ResolutionSetting.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/ResolutionSetting.kt index 989d2afb..c8d5183d 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/ResolutionSetting.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/ResolutionSetting.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -16,8 +18,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import dev.aaa1115910.bv.component.settings.SettingsMenuSelectItem @@ -47,7 +47,7 @@ fun ResolutionSetting( style = MaterialTheme.typography.displaySmall ) Spacer(modifier = Modifier.height(12.dp)) - TvLazyColumn( + LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) ) { items(items = Resolution.entries.reversed()) { resolution -> diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/StorageSetting.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/StorageSetting.kt index af278518..cbe83886 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/StorageSetting.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/StorageSetting.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.AlertDialog import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -21,7 +22,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn import androidx.tv.material3.Button import androidx.tv.material3.MaterialTheme import androidx.tv.material3.OutlinedButton @@ -117,7 +117,7 @@ fun StorageSetting( style = MaterialTheme.typography.displaySmall ) Spacer(modifier = Modifier.height(12.dp)) - TvLazyColumn( + LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) ) { item { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/UISetting.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/UISetting.kt index 2f215945..9513bb39 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/UISetting.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/UISetting.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.ArrowDropDown import androidx.compose.material.icons.rounded.ArrowDropUp @@ -38,7 +39,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn import androidx.tv.material3.Icon import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text @@ -72,7 +72,7 @@ fun UISetting( style = MaterialTheme.typography.displaySmall ) Spacer(modifier = Modifier.height(12.dp)) - TvLazyColumn( + LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) ) { item { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt index be471d75..ce3f49f6 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/settings/content/VideoCodecSetting.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -16,8 +18,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Text import dev.aaa1115910.bv.component.settings.SettingsMenuSelectItem @@ -47,7 +47,7 @@ fun VideoCodecSetting( style = MaterialTheme.typography.displaySmall ) Spacer(modifier = Modifier.height(12.dp)) - TvLazyColumn( + LazyColumn( verticalArrangement = Arrangement.spacedBy(8.dp) ) { items(items = VideoCodec.entries.filter { it != VideoCodec.DVH1 && it != VideoCodec.HVC1 }) { videoCodec -> diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FavoriteScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FavoriteScreen.kt index 7bd998d3..86f36663 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FavoriteScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FavoriteScreen.kt @@ -7,6 +7,12 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed +import androidx.compose.foundation.lazy.items import androidx.compose.material3.FilterChip import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable @@ -22,12 +28,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvGridItemSpan -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.Text import dev.aaa1115910.bv.R import dev.aaa1115910.bv.activities.video.VideoInfoActivity @@ -74,17 +74,17 @@ fun FavoriteScreen( } } ) { innerPadding -> - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = Modifier.padding(innerPadding), - columns = TvGridCells.Fixed(4), + columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) ) { item( - span = { TvGridItemSpan(4) } + span = { GridItemSpan(4) } ) { - TvLazyRow( + LazyRow( contentPadding = PaddingValues(horizontal = 24.dp), horizontalArrangement = Arrangement.spacedBy( 12.dp, diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowScreen.kt index e4eb8fe7..e3cfec9e 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowScreen.kt @@ -11,6 +11,10 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable @@ -35,10 +39,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvGridItemSpan -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed import androidx.tv.material3.Border import androidx.tv.material3.ClickableSurfaceDefaults import androidx.tv.material3.MaterialTheme @@ -106,9 +106,9 @@ fun FollowScreen( } } ) { innerPadding -> - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = Modifier.padding(innerPadding), - columns = TvGridCells.Fixed(3), + columns = GridCells.Fixed(3), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(18.dp), horizontalArrangement = Arrangement.spacedBy(20.dp) @@ -136,7 +136,7 @@ fun FollowScreen( } } else { item( - span = { TvGridItemSpan(3) } + span = { GridItemSpan(3) } ) { Box( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowingSeasonFilter.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowingSeasonFilter.kt index f64cb0e2..4b21226d 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowingSeasonFilter.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowingSeasonFilter.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.Done import androidx.compose.material3.AlertDialog @@ -15,8 +17,6 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.ExperimentalTvMaterial3Api import androidx.tv.material3.FilterChip import androidx.tv.material3.Icon @@ -53,7 +53,7 @@ fun FollowingSeasonFilter( Column( verticalArrangement = Arrangement.spacedBy(filterRowSpace) ) { - TvLazyRow( + LazyRow( modifier = Modifier .then(row1FocusRestorerModifiers.parentModifier), horizontalArrangement = Arrangement.spacedBy(filterRowSpace), @@ -72,7 +72,7 @@ fun FollowingSeasonFilter( ) } } - TvLazyRow( + LazyRow( modifier = Modifier .then(row2FocusRestorerModifiers.parentModifier), horizontalArrangement = Arrangement.spacedBy(filterRowSpace), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowingSeasonScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowingSeasonScreen.kt index d9b22b52..9744148e 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowingSeasonScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FollowingSeasonScreen.kt @@ -9,6 +9,10 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.GridItemSpan +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -25,10 +29,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvGridItemSpan -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed import androidx.tv.material3.OutlinedButton import androidx.tv.material3.Text import dev.aaa1115910.biliapi.entity.season.FollowingSeasonStatus @@ -154,9 +154,9 @@ fun FollowingSeasonScreen( } } ) { innerPadding -> - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = Modifier.padding(innerPadding), - columns = TvGridCells.Fixed(6), + columns = GridCells.Fixed(6), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) @@ -188,7 +188,7 @@ fun FollowingSeasonScreen( } if (followingSeasons.isEmpty() && noMore) { item( - span = { TvGridItemSpan(6) } + span = { GridItemSpan(6) } ) { Box( modifier = Modifier.fillMaxSize(), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/HistoryScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/HistoryScreen.kt index 68909ad8..d3991c28 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/HistoryScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/HistoryScreen.kt @@ -7,6 +7,9 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -22,9 +25,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed import androidx.tv.material3.Text import dev.aaa1115910.bv.R import dev.aaa1115910.bv.activities.video.VideoInfoActivity @@ -86,9 +86,9 @@ fun HistoryScreen( } } ) { innerPadding -> - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = Modifier.padding(innerPadding), - columns = TvGridCells.Fixed(4), + columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UpInfoScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UpInfoScreen.kt index 654bf519..27f5b1b2 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UpInfoScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UpInfoScreen.kt @@ -9,6 +9,9 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.itemsIndexed import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -24,9 +27,6 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.itemsIndexed import androidx.tv.material3.Text import dev.aaa1115910.bv.R import dev.aaa1115910.bv.activities.video.VideoInfoActivity @@ -97,9 +97,9 @@ fun UpSpaceScreen( } } ) { innerPadding -> - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = Modifier.padding(innerPadding), - columns = TvGridCells.Fixed(4), + columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UserInfoScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UserInfoScreen.kt index b3198bb7..5bd549c2 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UserInfoScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UserInfoScreen.kt @@ -16,6 +16,9 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.widthIn +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.Scaffold import androidx.compose.material3.Slider @@ -52,9 +55,6 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.LocalLifecycleOwner -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.Button import androidx.tv.material3.ButtonDefaults import androidx.tv.material3.ClickableSurfaceDefaults @@ -314,7 +314,7 @@ fun UserInfoScreen( } ) { innerPadding -> - TvLazyColumn( + LazyColumn( modifier = Modifier.padding(innerPadding), contentPadding = PaddingValues(bottom = 24.dp) ) { @@ -628,7 +628,7 @@ private fun UserRow( label = "animate following number" ) - TvLazyRow( + LazyRow( modifier = modifier.padding(vertical = 28.dp), horizontalArrangement = Arrangement.spacedBy(16.dp, Alignment.Start), contentPadding = PaddingValues(horizontal = 50.dp) @@ -707,7 +707,7 @@ private fun FollowingAnimeVideosRow( fontSize = titleFontSize.sp, color = titleColor ) - TvLazyRow( + LazyRow( modifier = Modifier .padding(top = 15.dp) .onGloballyPositioned { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UserSwitchScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UserSwitchScreen.kt index eb35b1c4..54ca20a6 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UserSwitchScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/UserSwitchScreen.kt @@ -17,6 +17,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -55,9 +58,6 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.viewModelScope -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.Button import androidx.tv.material3.ButtonDefaults import androidx.tv.material3.ClickableSurfaceDefaults @@ -237,7 +237,7 @@ private fun UserSwitchContent( ) } - TvLazyRow( + LazyRow( modifier = Modifier.focusRequester(focusRequester), horizontalArrangement = Arrangement.spacedBy(24.dp), contentPadding = PaddingValues(horizontal = 12.dp) @@ -353,7 +353,7 @@ fun UserMenuDialog( onDismissRequest = onHideDialog, title = { Text(text = username) }, text = { - TvLazyColumn( + LazyColumn( modifier = Modifier.width(240.dp), verticalArrangement = Arrangement.spacedBy(8.dp), contentPadding = PaddingValues(horizontal = 12.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UnlockSwitchUserContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UnlockSwitchUserContent.kt index 0de0c3f8..0ed29668 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UnlockSwitchUserContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UnlockSwitchUserContent.kt @@ -9,6 +9,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -30,8 +32,6 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Surface import androidx.tv.material3.Text @@ -126,7 +126,7 @@ fun UnlockSwitchUserContent( ) } - TvLazyRow( + LazyRow( horizontalArrangement = Arrangement.spacedBy(24.dp), contentPadding = PaddingValues(horizontal = 12.dp) ) { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UnlockUserScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UnlockUserScreen.kt index fa1f2500..e5c30c27 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UnlockUserScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UnlockUserScreen.kt @@ -10,6 +10,8 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -30,8 +32,6 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyRow -import androidx.tv.foundation.lazy.list.items import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Surface import androidx.tv.material3.Text @@ -183,7 +183,7 @@ private fun UnlockUserContent( ) } - TvLazyRow( + LazyRow( modifier = Modifier.focusRequester(defaultFocusRequester), horizontalArrangement = Arrangement.spacedBy(24.dp), contentPadding = PaddingValues(horizontal = 12.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UserLockSettingsScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UserLockSettingsScreen.kt index 6192617f..7733c248 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UserLockSettingsScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/lock/UserLockSettingsScreen.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -28,7 +29,6 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyRow import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Surface import androidx.tv.material3.Text @@ -208,7 +208,7 @@ private fun UserLockSettingsContent( ) } - TvLazyRow( + LazyRow( modifier = Modifier.focusRequester(focusRequester), horizontalArrangement = Arrangement.spacedBy(24.dp), contentPadding = PaddingValues(horizontal = 12.dp) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/util/VideoShotExtends.kt b/app/src/main/kotlin/dev/aaa1115910/bv/util/VideoShotExtends.kt index 1acaab94..9cf51925 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/util/VideoShotExtends.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/util/VideoShotExtends.kt @@ -3,6 +3,9 @@ package dev.aaa1115910.bv.util import android.graphics.Bitmap import android.graphics.BitmapFactory import androidx.compose.foundation.Image +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -11,9 +14,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.asImageBitmap -import androidx.tv.foundation.lazy.grid.TvGridCells -import androidx.tv.foundation.lazy.grid.TvLazyVerticalGrid -import androidx.tv.foundation.lazy.grid.items import dev.aaa1115910.biliapi.entity.video.VideoShot import dev.aaa1115910.biliapi.repositories.VideoPlayRepository import org.koin.compose.getKoin @@ -77,9 +77,9 @@ fun VideoShotTest( } if (videoShot != null) { - TvLazyVerticalGrid( + LazyVerticalGrid( modifier = modifier, - columns = TvGridCells.Fixed(10), + columns = GridCells.Fixed(10), ) { items(videoShot!!.times) { time -> val bitmap = videoShot!!.getImage(time.toInt()) From d763f0031a068cad6b91ae8de2be875bdd63908d Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Fri, 30 Aug 2024 00:16:07 +0800 Subject: [PATCH 09/27] =?UTF-8?q?=E7=94=B1=E4=BA=8E=E5=A4=96=E9=83=A8API?= =?UTF-8?q?=E7=9A=84=E6=94=B9=E5=8F=98=E8=80=8C=E6=9B=B4=E6=96=B0=E4=BB=A3?= =?UTF-8?q?=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bv/component/controllers2/playermenu/DanmakuMenu.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/DanmakuMenu.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/DanmakuMenu.kt index 741c7747..1469809a 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/DanmakuMenu.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/controllers2/playermenu/DanmakuMenu.kt @@ -7,6 +7,8 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -23,8 +25,6 @@ import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.type import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp -import androidx.tv.foundation.lazy.list.TvLazyColumn -import androidx.tv.foundation.lazy.list.itemsIndexed import dev.aaa1115910.bv.component.controllers.LocalVideoPlayerControllerData import dev.aaa1115910.bv.component.controllers2.DanmakuType import dev.aaa1115910.bv.component.controllers2.LocalMenuFocusStateData @@ -156,7 +156,7 @@ fun DanmakuMenuList( } } - TvLazyColumn( + LazyColumn( modifier = Modifier .focusRequester(focusRequester) .padding(horizontal = 8.dp) From a7b5bffc5a310157e4b5cf621c81d8d7415cd627 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Fri, 30 Aug 2024 16:28:37 +0800 Subject: [PATCH 10/27] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=94=B6=E8=97=8F?= =?UTF-8?q?=E5=A4=B9=E7=9A=84=E4=BD=BF=E7=94=A8=E4=BD=93=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 替换掉早该换的临时 tabs - 按下返回键时先返回顶部 --- .../bv/screen/user/FavoriteScreen.kt | 98 +++++++++++++++---- .../bv/viewmodel/user/FavoriteViewModel.kt | 12 ++- 2 files changed, 89 insertions(+), 21 deletions(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FavoriteScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FavoriteScreen.kt index 86f36663..3e2d6196 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FavoriteScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/user/FavoriteScreen.kt @@ -1,38 +1,54 @@ package dev.aaa1115910.bv.screen.user +import androidx.activity.compose.BackHandler import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.PaddingValues 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.lazy.LazyRow +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed -import androidx.compose.foundation.lazy.items -import androidx.compose.material3.FilterChip +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.material3.Scaffold import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf +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.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.tv.material3.LocalContentColor +import androidx.tv.material3.MaterialTheme +import androidx.tv.material3.Tab +import androidx.tv.material3.TabRow import androidx.tv.material3.Text +import dev.aaa1115910.biliapi.entity.FavoriteFolderMetadata import dev.aaa1115910.bv.R import dev.aaa1115910.bv.activities.video.VideoInfoActivity +import dev.aaa1115910.bv.component.createCustomInitialFocusRestorerModifiers +import dev.aaa1115910.bv.component.ifElse import dev.aaa1115910.bv.component.videocard.SmallVideoCard import dev.aaa1115910.bv.viewmodel.user.FavoriteViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel @Composable @@ -41,12 +57,40 @@ fun FavoriteScreen( favoriteViewModel: FavoriteViewModel = koinViewModel() ) { val context = LocalContext.current + val scope = rememberCoroutineScope() var currentIndex by remember { mutableIntStateOf(0) } val showLargeTitle by remember { derivedStateOf { currentIndex < 4 } } val titleFontSize by animateFloatAsState( targetValue = if (showLargeTitle) 48f else 24f, label = "title font size" ) + val focusRestorerModifiers = createCustomInitialFocusRestorerModifiers() + val defaultFocusRequester = remember { FocusRequester() } + var focusOnTabs by remember { mutableStateOf(true) } + val lazyGridState = rememberLazyGridState() + + val currentTabIndex by remember { + derivedStateOf { + favoriteViewModel.favoriteFolderMetadataList.indexOf(favoriteViewModel.currentFavoriteFolderMetadata) + } + } + + val updateCurrentFavoriteFolder: (folderMetadata: FavoriteFolderMetadata) -> Unit = + { folderMetadata -> + favoriteViewModel.currentFavoriteFolderMetadata = folderMetadata + favoriteViewModel.favorites.clear() + favoriteViewModel.resetPageNumber() + favoriteViewModel.updateFolderItems(force = true) + } + + BackHandler( + enabled = !focusOnTabs + ) { + scope.launch(Dispatchers.Main) { + lazyGridState.animateScrollToItem(0) + defaultFocusRequester.requestFocus() + } + } Scaffold( modifier = modifier, @@ -76,6 +120,7 @@ fun FavoriteScreen( ) { innerPadding -> LazyVerticalGrid( modifier = Modifier.padding(innerPadding), + state = lazyGridState, columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), verticalArrangement = Arrangement.spacedBy(24.dp), @@ -84,24 +129,39 @@ fun FavoriteScreen( item( span = { GridItemSpan(4) } ) { - LazyRow( - contentPadding = PaddingValues(horizontal = 24.dp), - horizontalArrangement = Arrangement.spacedBy( - 12.dp, - Alignment.CenterHorizontally - ) + TabRow( + modifier = Modifier + .focusRequester(defaultFocusRequester) + .onFocusChanged { focusOnTabs = it.hasFocus } + .then(focusRestorerModifiers.parentModifier), + selectedTabIndex = currentTabIndex, + separator = { Spacer(modifier = Modifier.width(12.dp)) }, ) { - items(items = favoriteViewModel.favoriteFolderMetadataList) { folderMetadata -> - FilterChip( - selected = favoriteViewModel.currentFavoriteFolderMetadata == folderMetadata, - onClick = { - favoriteViewModel.currentFavoriteFolderMetadata = folderMetadata - favoriteViewModel.favorites.clear() - favoriteViewModel.resetPageNumber() - favoriteViewModel.updateFolderItems() + favoriteViewModel.favoriteFolderMetadataList.forEachIndexed { index, folderMetadata -> + Tab( + modifier = Modifier + .ifElse(index == 0, focusRestorerModifiers.childModifier), + selected = currentTabIndex == index, + onFocus = { + if (favoriteViewModel.currentFavoriteFolderMetadata != folderMetadata) { + updateCurrentFavoriteFolder(folderMetadata) + } }, - label = { Text(text = folderMetadata.title) } - ) + onClick = { updateCurrentFavoriteFolder(folderMetadata) } + ) { + Box( + modifier = Modifier.height(32.dp), + contentAlignment = Alignment.Center + ) { + Text( + modifier = Modifier + .padding(horizontal = 16.dp, vertical = 6.dp), + text = folderMetadata.title, + color = LocalContentColor.current, + style = MaterialTheme.typography.labelLarge + ) + } + } } } } diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/user/FavoriteViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/user/FavoriteViewModel.kt index 026cea19..6126c13a 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/user/FavoriteViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/user/FavoriteViewModel.kt @@ -16,6 +16,7 @@ import dev.aaa1115910.bv.util.fWarn import dev.aaa1115910.bv.util.swapList import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.launch class FavoriteViewModel( @@ -67,11 +68,18 @@ class FavoriteViewModel( } } - fun updateFolderItems() { + private var updateJob: Job? = null + + fun updateFolderItems(force: Boolean = false) { + if (force) { + updateJob?.cancel() + resetPageNumber() + updatingFolderItems = false + } if (updatingFolderItems || !hasMore) return updatingFolderItems = true logger.fInfo { "Updating favorite folder items with media id: ${currentFavoriteFolderMetadata?.id}" } - viewModelScope.launch(Dispatchers.IO) { + updateJob = viewModelScope.launch(Dispatchers.IO) { runCatching { val favoriteFolderData = favoriteRepository.getFavoriteFolderData( mediaId = currentFavoriteFolderMetadata!!.id, From 5ea9a6354070d414e38ab9b284d104f33019a24b Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Wed, 4 Sep 2024 16:53:28 +0800 Subject: [PATCH 11/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=89=A7=E9=9B=86=E6=97=A0=E6=B3=95=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixed #149 --- .../aaa1115910/biliapi/http/entity/season/AppSeasonData.kt | 2 +- .../aaa1115910/biliapi/http/entity/season/WebSeasonData.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/season/AppSeasonData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/season/AppSeasonData.kt index e58b2743..a02e79e8 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/season/AppSeasonData.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/season/AppSeasonData.kt @@ -366,7 +366,7 @@ data class AppSeasonData( val play: String, val reply: Int, val share: Int, - val views: Int, + val views: Long, val vt: Int ) diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/season/WebSeasonData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/season/WebSeasonData.kt index 34c92992..2aa31b20 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/season/WebSeasonData.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/season/WebSeasonData.kt @@ -223,7 +223,7 @@ data class WebSeasonData( val likes: Int, val reply: Int, val share: Int, - val views: Int + val views: Long ) /** @@ -411,7 +411,7 @@ data class OtherSeason( val favorites: Int, @SerialName("series_follow") val seriesFollow: Int, - val views: Int + val views: Long ) @Serializable From 39d34a16ae645a92a07154780675a733507476f1 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Tue, 17 Sep 2024 23:34:21 +0800 Subject: [PATCH 12/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A8=E6=80=81?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E4=B8=AD=E6=97=A0=E6=B3=95=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E5=8A=A8=E6=80=81=E8=A7=86=E9=A2=91=E7=9A=84=E6=A0=87=E9=A2=98?= =?UTF-8?q?=EF=BC=88App=E6=8E=A5=E5=8F=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/dev/aaa1115910/biliapi/entity/user/Dynamic.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/user/Dynamic.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/user/Dynamic.kt index 3a9bf088..2caa008d 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/user/Dynamic.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/user/Dynamic.kt @@ -83,12 +83,12 @@ data class DynamicVideo( fun fromDynamicVideoItem(item: bilibili.app.dynamic.v2.DynamicItem): DynamicVideo { val author = - item.modulesList.first { it.moduleType == DynModuleType.module_author }.moduleAuthor.author + item.modulesList.first { it.moduleType == DynModuleType.module_author }.moduleAuthor val dynamic = item.modulesList.first { it.moduleType == DynModuleType.module_dynamic }.moduleDynamic val desc = item.modulesList.firstOrNull { it.moduleType == DynModuleType.module_desc }?.moduleDesc - val isDynamicVideo = desc?.text?.startsWith("动态视频") ?: false + val isDynamicVideo = author?.ptimeLabelText?.contains("动态视频") ?: false when (dynamic.moduleItemCase) { ModuleDynamic.ModuleItemCase.DYN_ARCHIVE -> { val archive = dynamic.dynArchive @@ -98,7 +98,7 @@ data class DynamicVideo( cid = archive.cid, title = if (!isDynamicVideo) archive.title else desc!!.text.substring(5), cover = archive.cover, - author = author.name, + author = author.author.name, duration = convertStringTimeToSeconds(archive.coverLeftText1), play = convertStringPlayCountToNumberPlayCount(archive.coverLeftText2), danmaku = convertStringPlayCountToNumberPlayCount(archive.coverLeftText3) @@ -115,7 +115,7 @@ data class DynamicVideo( seasonId = pgc.seasonId.toInt(), title = pgc.title, cover = pgc.cover, - author = author.name, + author = author.author.name, duration = convertStringTimeToSeconds(pgc.coverLeftText1), play = convertStringPlayCountToNumberPlayCount(pgc.coverLeftText2), danmaku = convertStringPlayCountToNumberPlayCount(pgc.coverLeftText3) From 996c27db472a5fb3ababde3ab9d3e450642bd134 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Tue, 17 Sep 2024 23:55:45 +0800 Subject: [PATCH 13/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E7=BB=93=E6=9E=9C=E5=8F=AF=E8=83=BD=E6=97=A0=E6=B3=95=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=EF=BC=88Web=E6=8E=A5=E5=8F=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aaa1115910/biliapi/http/entity/search/SearchResultItem.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/search/SearchResultItem.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/search/SearchResultItem.kt index afe47f8b..2fea3d2b 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/search/SearchResultItem.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/search/SearchResultItem.kt @@ -369,7 +369,7 @@ data class SearchTopicResult( @Serializable data class SearchVideoResult( val type: String, - val id: Int, + val id: Long, val author: String, val mid: Long, @SerialName("typeid") From 2326055a342b124b5445fb967af4c7844a55bfd5 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Wed, 18 Sep 2024 19:53:36 +0800 Subject: [PATCH 14/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=97=A0=20google-serv?= =?UTF-8?q?ices.json=20=E6=97=B6=E5=8F=AF=E8=83=BD=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=AD=A3=E5=B8=B8=E6=89=93=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../alpha_build_manually_without_sign.yml | 122 +++--------------- app/build.gradle.kts | 11 +- buildSrc/src/main/kotlin/AppConfiguration.kt | 16 +++ 3 files changed, 39 insertions(+), 110 deletions(-) diff --git a/.github/workflows/alpha_build_manually_without_sign.yml b/.github/workflows/alpha_build_manually_without_sign.yml index 08b96dc3..2edbab21 100644 --- a/.github/workflows/alpha_build_manually_without_sign.yml +++ b/.github/workflows/alpha_build_manually_without_sign.yml @@ -1,11 +1,10 @@ -name: Alpha Build Manually (Without sign) +name: Alpha Build Manually (Without signature) on: workflow_dispatch: inputs: google_services_json: - description: "google-services.json" - required: true + description: "google-services.json (optional)" jobs: build-alpha: @@ -34,71 +33,28 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - - name: Build lite apk - run: ./gradlew assembleLiteAlpha assembleLiteDebug - - - name: Build default apk + - name: Build apk run: ./gradlew assembleDefaultAlpha assembleDefaultDebug - - name: Read lite alpha apk output metadata - id: apk-meta-lite-alpha - uses: juliangruber/read-file-action@v1 - with: - path: app/build/outputs/apk/lite/alpha/output-metadata.json - - - name: Read lite debug apk output metadata - id: apk-meta-lite-debug - uses: juliangruber/read-file-action@v1 - with: - path: app/build/outputs/apk/lite/debug/output-metadata.json - - - name: Read default alpha apk output metadata - id: apk-meta-default-alpha + - name: Read alpha apk output metadata + id: apk-meta-alpha uses: juliangruber/read-file-action@v1 with: path: app/build/outputs/apk/default/alpha/output-metadata.json - - name: Read default debug apk output metadata - id: apk-meta-default-debug + - name: Read alpha debug apk output metadata + id: apk-meta-alpha-debug uses: juliangruber/read-file-action@v1 with: path: app/build/outputs/apk/default/debug/output-metadata.json - - name: Parse lite apks info - id: lite-info - run: | - echo "lite_alpha_info_apk_filename=${{ fromJson(steps.apk-meta-lite-alpha.outputs.content).elements[0].outputFile }}" >> $GITHUB_ENV - echo "lite_debug_info_apk_filename=${{ fromJson(steps.apk-meta-lite-debug.outputs.content).elements[0].outputFile }}" >> $GITHUB_ENV - - - name: Parse default apks info - id: default-info + - name: Parse apk infos + id: apk-infos run: | - echo "default_alpha_info_version_code=${{ fromJson(steps.apk-meta-default-alpha.outputs.content).elements[0].versionCode }}" >> $GITHUB_ENV - echo "default_alpha_info_version_name=${{ fromJson(steps.apk-meta-default-alpha.outputs.content).elements[0].versionName }}" >> $GITHUB_ENV - echo "default_debug_info_version_code=${{ fromJson(steps.apk-meta-default-debug.outputs.content).elements[0].versionCode }}" >> $GITHUB_ENV - echo "default_debug_info_version_name=${{ fromJson(steps.apk-meta-default-debug.outputs.content).elements[0].versionName }}" >> $GITHUB_ENV - - # upload artifacts lite-debug - - - name: Archive lite debug build artifacts - uses: actions/upload-artifact@v4 - with: - name: Lite debug build artifact - path: app/build/outputs/apk/lite/debug/${{ env.lite_debug_info_apk_filename }} - - # upload artifacts lite-alpha - - - name: Archive lite alpha build artifacts - uses: actions/upload-artifact@v4 - with: - name: Lite alpha build artifact - path: app/build/outputs/apk/lite/alpha/${{ env.lite_alpha_info_apk_filename }} - - - name: Archive lite alpha build mappings - uses: actions/upload-artifact@v4 - with: - name: Lite alpha build mappings - path: app/build/outputs/mapping/liteAlpha + echo "alpha_info_version_code=${{ fromJson(steps.apk-meta-alpha.outputs.content).elements[0].versionCode }}" >> $GITHUB_ENV + echo "alpha_info_version_name=${{ fromJson(steps.apk-meta-alpha.outputs.content).elements[0].versionName }}" >> $GITHUB_ENV + echo "alpha_debug_info_version_code=${{ fromJson(steps.apk-meta-alpha-debug.outputs.content).elements[0].versionCode }}" >> $GITHUB_ENV + echo "alpha_debug_info_version_name=${{ fromJson(steps.apk-meta-alpha-debug.outputs.content).elements[0].versionName }}" >> $GITHUB_ENV # upload artifacts default-debug @@ -106,31 +62,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: Default debug build artifact (universal) - path: app/build/outputs/apk/default/debug/BV_${{ env.default_debug_info_version_code }}_${{ env.default_debug_info_version_name }}_default_universal.apk - - - name: Archive default debug build artifacts (armeabi-v7a) - uses: actions/upload-artifact@v4 - with: - name: Default debug build artifact (armeabi-v7a) - path: app/build/outputs/apk/default/debug/BV_${{ env.default_debug_info_version_code }}_${{ env.default_debug_info_version_name }}_default_armeabi-v7a.apk - - - name: Archive default debug build artifacts (arm64-v8a) - uses: actions/upload-artifact@v4 - with: - name: Default debug build artifact (arm64-v8a) - path: app/build/outputs/apk/default/debug/BV_${{ env.default_debug_info_version_code }}_${{ env.default_debug_info_version_name }}_default_arm64-v8a.apk - - - name: Archive default debug build artifacts (x86) - uses: actions/upload-artifact@v4 - with: - name: Default debug build artifact (x86) - path: app/build/outputs/apk/default/debug/BV_${{ env.default_debug_info_version_code }}_${{ env.default_debug_info_version_name }}_default_x86.apk - - - name: Archive default debug build artifacts (x86_64) - uses: actions/upload-artifact@v4 - with: - name: Default debug build artifact (x86_64) - path: app/build/outputs/apk/default/debug/BV_${{ env.default_debug_info_version_code }}_${{ env.default_debug_info_version_name }}_default_x86_64.apk + path: app/build/outputs/apk/default/debug/BV_${{ env.alpha_debug_info_version_code }}_${{ env.alpha_debug_info_version_name }}_default_universal.apk # upload artifacts default-alpha @@ -144,28 +76,4 @@ jobs: uses: actions/upload-artifact@v4 with: name: Default alpha build artifact (universal) - path: app/build/outputs/apk/default/alpha/BV_${{ env.default_alpha_info_version_code }}_${{ env.default_alpha_info_version_name }}_default_universal.apk - - - name: Archive default alpha build artifacts (armeabi-v7a) - uses: actions/upload-artifact@v4 - with: - name: Default alpha build artifact (armeabi-v7a) - path: app/build/outputs/apk/default/alpha/BV_${{ env.default_alpha_info_version_code }}_${{ env.default_alpha_info_version_name }}_default_armeabi-v7a.apk - - - name: Archive default alpha build artifacts (arm64-v8a) - uses: actions/upload-artifact@v4 - with: - name: Default alpha build artifact (arm64-v8a) - path: app/build/outputs/apk/default/alpha/BV_${{ env.default_alpha_info_version_code }}_${{ env.default_alpha_info_version_name }}_default_arm64-v8a.apk - - - name: Archive default alpha build artifacts (x86) - uses: actions/upload-artifact@v4 - with: - name: Default alpha build artifact (x86) - path: app/build/outputs/apk/default/alpha/BV_${{ env.default_alpha_info_version_code }}_${{ env.default_alpha_info_version_name }}_default_x86.apk - - - name: Archive default alpha build artifacts (x86_64) - uses: actions/upload-artifact@v4 - with: - name: Default alpha build artifact (x86_64) - path: app/build/outputs/apk/default/alpha/BV_${{ env.default_alpha_info_version_code }}_${{ env.default_alpha_info_version_name }}_default_x86_64.apk + path: app/build/outputs/apk/default/alpha/BV_${{ env.alpha_info_version_code }}_${{ env.alpha_info_version_name }}_default_universal.apk diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e819c5fd..c3dcb749 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -14,9 +14,8 @@ plugins { alias(gradleLibs.plugins.kotlin.android) alias(gradleLibs.plugins.kotlin.serialization) } -if (file("google-services.json").let { - it.exists() && it.readText().contains(AppConfiguration.appId) - }) { + +if (AppConfiguration.googleServicesAvailable) { apply(plugin = gradleLibs.plugins.google.services.get().pluginId) } @@ -71,6 +70,9 @@ android { "proguard-rules.pro" ) if (signingProp.exists()) signingConfig = signingConfigs.getByName("key") + configure { + mappingFileUploadEnabled = AppConfiguration.googleServicesAvailable + } } debug { isMinifyEnabled = false @@ -102,6 +104,9 @@ android { "proguard-rules.pro" ) if (signingProp.exists()) signingConfig = signingConfigs.getByName("key") + configure { + mappingFileUploadEnabled = AppConfiguration.googleServicesAvailable + } } } // https://issuetracker.google.com/issues/260059413 diff --git a/buildSrc/src/main/kotlin/AppConfiguration.kt b/buildSrc/src/main/kotlin/AppConfiguration.kt index 1f01dc9b..8868d1d5 100644 --- a/buildSrc/src/main/kotlin/AppConfiguration.kt +++ b/buildSrc/src/main/kotlin/AppConfiguration.kt @@ -1,3 +1,5 @@ +import java.io.File + object AppConfiguration { const val appId = "dev.aaa1115910.bv" const val compileSdk = 34 @@ -15,6 +17,20 @@ object AppConfiguration { } val versionCode: Int by lazy { "git rev-list --count HEAD".exec().toInt() } const val libVLCVersion = "3.0.18" + var googleServicesAvailable = true + + init { + initConfigurations() + } + + private fun initConfigurations() { + val googleServicesJsonPath = "pwd".exec() + "/app/google-services.json" + val googleServicesJsonFile = File(googleServicesJsonPath) + googleServicesAvailable = + googleServicesJsonFile.exists() && googleServicesJsonFile.readText().let { + it.contains(appId) && it.contains("$appId.r8test") && it.contains("$appId.debug") + } + } } fun String.exec() = String(Runtime.getRuntime().exec(this).inputStream.readBytes()).trim() \ No newline at end of file From 76edf816d10d5cc3618ea0d8a104ba55b2a3c842 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Tue, 24 Sep 2024 19:46:09 +0800 Subject: [PATCH 15/27] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bv/component/videocard/SmallVideoCard.kt | 407 ++++++++++++------ .../dev/aaa1115910/bv/ui/theme/Theme.kt | 4 +- 2 files changed, 290 insertions(+), 121 deletions(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt index 85b1651a..3b973174 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt @@ -1,18 +1,28 @@ package dev.aaa1115910.bv.component.videocard +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.spring import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid 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.draw.clip @@ -23,9 +33,11 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.tv.material3.Border -import androidx.tv.material3.ClickableSurfaceDefaults +import androidx.tv.material3.Card +import androidx.tv.material3.CardDefaults import androidx.tv.material3.Icon import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Surface @@ -46,133 +58,264 @@ fun SmallVideoCard( onLongClick: () -> Unit = {}, onFocus: () -> Unit = {} ) { - Surface( + var hasFocus by remember { mutableStateOf(false) } + val interactionSource = remember { MutableInteractionSource() } + + SmallVideoCardContent( modifier = modifier - .onFocusChanged { if (it.hasFocus) onFocus() }, + .onFocusChanged { + hasFocus = it.isFocused + if (hasFocus) onFocus() + }, + data = data, + hasFocus = hasFocus, + interactionSource = interactionSource, onClick = onClick, onLongClick = onLongClick, - colors = ClickableSurfaceDefaults.colors( - containerColor = MaterialTheme.colorScheme.surface, - focusedContainerColor = MaterialTheme.colorScheme.surface, - pressedContainerColor = MaterialTheme.colorScheme.surface - ), - shape = ClickableSurfaceDefaults.shape(shape = MaterialTheme.shapes.large), - border = ClickableSurfaceDefaults.border( - focusedBorder = Border( - border = BorderStroke(width = 3.dp, color = Color.White), - shape = MaterialTheme.shapes.large + onFocusChanged = { + hasFocus = it + if (it) onFocus() + } + ) +} + +@Composable +fun SmallVideoCardContent( + modifier: Modifier = Modifier, + data: VideoCardData, + hasFocus: Boolean, + interactionSource: MutableInteractionSource? = null, + onClick: () -> Unit = {}, + onLongClick: () -> Unit = {}, + onFocusChanged: (Boolean) -> Unit = {} +) { + /*val infoScale by animateFloatAsState( + targetValue = if (hasFocus) 1.05f else 1f, + animationSpec = spring(), + label = "info scale" + )*/ + val infoOffsetY by animateDpAsState( + targetValue = if (hasFocus) 8.dp else 0.dp, + animationSpec = spring(), + label = "info offset y" + ) + + Column( + modifier = modifier + ) { + Card( + onClick = onClick, + onLongClick = onLongClick, + colors = CardDefaults.colors( + containerColor = MaterialTheme.colorScheme.surface, + focusedContainerColor = MaterialTheme.colorScheme.surface, + pressedContainerColor = MaterialTheme.colorScheme.surface + ), + shape = CardDefaults.shape(shape = MaterialTheme.shapes.large), + border = CardDefaults.border( + focusedBorder = Border( + border = BorderStroke(width = 3.dp, color = MaterialTheme.colorScheme.border), + shape = MaterialTheme.shapes.large + ) + ) + ) { + CardCover( + cover = data.cover, + play = data.playString, + danmaku = data.danmakuString, + time = data.timeString ) + } + + CardInfo( + modifier = Modifier + //.scale(infoScale) + .offset(y = infoOffsetY), + title = data.title, + upName = data.upName ) + } +} + +@Composable +private fun PlayText( + modifier: Modifier = Modifier, + text: String +) { + if (text.isNotBlank()) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(2.dp) + ) { + Icon( + modifier = Modifier, + painter = painterResource(id = R.drawable.ic_play_count), + contentDescription = null + ) + Text( + text = text, + style = MaterialTheme.typography.bodySmall, + color = Color.White + ) + } + } +} + +@Composable +private fun DanmakuText( + modifier: Modifier = Modifier, + text: String +) { + if (text.isNotBlank()) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(2.dp) + ) { + Icon( + modifier = Modifier, + painter = painterResource(id = R.drawable.ic_danmaku_count), + contentDescription = null + ) + Text( + text = text, + style = MaterialTheme.typography.bodySmall, + color = Color.White + ) + } + } +} + +@Composable +private fun CoverBottomInfo( + modifier: Modifier = Modifier, + play: String, + danmaku: String, + time: String +) { + Row( + modifier = modifier + .fillMaxWidth() + .padding(12.dp, 8.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween ) { - Column { - Box( - modifier = Modifier.clip(MaterialTheme.shapes.large), - contentAlignment = Alignment.BottomCenter - ) { - AsyncImage( - modifier = Modifier - .fillMaxWidth() - .aspectRatio(1.6f) - .clip(MaterialTheme.shapes.large), - model = data.cover.resizedImageUrl(ImageSize.SmallVideoCardCover), - contentDescription = null, - contentScale = ContentScale.FillBounds - ) - Box( - modifier = Modifier - .fillMaxWidth() - .height(48.dp) - .background( - Brush.verticalGradient( - colors = listOf( - Color.Transparent, - Color.Black.copy(alpha = 0.8f) - ) - ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + PlayText(text = play) + DanmakuText(text = danmaku) + } + Text( + text = time, + style = MaterialTheme.typography.bodySmall, + color = Color.White + ) + } +} + +@Composable +fun CardCover( + modifier: Modifier = Modifier, + cover: String, + play: String, + danmaku: String, + time: String +) { + Box( + modifier = modifier.clip(MaterialTheme.shapes.large), + contentAlignment = Alignment.BottomCenter + ) { + AsyncImage( + modifier = Modifier + .fillMaxWidth() + .aspectRatio(1.6f) + .clip(MaterialTheme.shapes.large), + model = cover.resizedImageUrl(ImageSize.SmallVideoCardCover), + contentDescription = null, + contentScale = ContentScale.FillBounds + ) + Box( + modifier = Modifier + .fillMaxWidth() + .height(48.dp) + .background( + Brush.verticalGradient( + colors = listOf( + Color.Transparent, + Color.Black.copy(alpha = 0.8f) ) - ) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(12.dp, 8.dp), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - if (data.playString != "") { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - Icon( - modifier = Modifier, - painter = painterResource(id = R.drawable.ic_play_count), - contentDescription = null - ) - Text( - text = data.playString, - style = MaterialTheme.typography.bodySmall, - color = Color.White - ) - } - } - if (data.danmakuString != "") { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(2.dp) - ) { - Icon( - modifier = Modifier, - painter = painterResource(id = R.drawable.ic_danmaku_count), - contentDescription = null - ) - Text( - text = data.danmakuString, - style = MaterialTheme.typography.bodySmall, - color = Color.White - ) - } - } - } - Text( - text = data.timeString, - style = MaterialTheme.typography.bodySmall, - color = Color.White ) - } - } - Column( - modifier = Modifier.padding(8.dp) - ) { - Text( - text = data.title, - style = MaterialTheme.typography.titleMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis ) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(4.dp) - ) { - UpIcon() - Text( - text = data.upName, - style = MaterialTheme.typography.labelMedium, - maxLines = 1, - overflow = TextOverflow.Ellipsis - ) - } - } + ) + CoverBottomInfo( + play = play, + danmaku = danmaku, + time = time + ) + } +} + +@Composable +private fun CardInfo( + modifier: Modifier = Modifier, + title: String, + upName: String +) { + Column( + modifier = modifier.padding(8.dp) + ) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp) + ) { + UpIcon() + Text( + text = upName, + style = MaterialTheme.typography.labelMedium, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) } } } -@Preview(device = "id:tv_1080p") +@Preview +@Composable +fun SmallVideoCardWithoutFocusPreview() { + val data = VideoCardData( + avid = 0, + title = "震惊!太震惊了!真的是太震惊了!我的天呐!真TMD震惊!", + cover = "http://i2.hdslb.com/bfs/archive/af17fc07b8f735e822563cc45b7b5607a491dfff.jpg", + upName = "bishi", + play = 2333, + danmaku = 666, + time = 2333 * 1000 + ) + BVTheme { + Surface( + modifier = Modifier.width(300.dp) + ) { + SmallVideoCardContent( + modifier = Modifier.padding(20.dp), + data = data, + hasFocus = false + ) + } + } +} + +@Preview @Composable -fun SmallVideoCardPreview() { +fun SmallVideoCardWithFocusPreview() { val data = VideoCardData( avid = 0, title = "震惊!太震惊了!真的是太震惊了!我的天呐!真TMD震惊!", @@ -183,9 +326,13 @@ fun SmallVideoCardPreview() { time = 2333 * 1000 ) BVTheme { - Surface { - SmallVideoCard( - data = data + Surface( + modifier = Modifier.width(300.dp) + ) { + SmallVideoCardContent( + modifier = Modifier.padding(20.dp), + data = data, + hasFocus = true ) } } @@ -197,7 +344,8 @@ fun SmallVideoCardsPreview() { val data = VideoCardData( avid = 0, title = "震惊!太震惊了!真的是太震惊了!我的天呐!真TMD震惊!", - cover = "http://i2.hdslb.com/bfs/archive/af17fc07b8f735e822563cc45b7b5607a491dfff.jpg", + //cover = "http://i2.hdslb.com/bfs/archive/af17fc07b8f735e822563cc45b7b5607a491dfff.jpg", + cover = "", upName = "bishi", play = 2333, danmaku = 666, @@ -205,7 +353,10 @@ fun SmallVideoCardsPreview() { ) BVTheme { LazyVerticalGrid( - columns = GridCells.Fixed(4) + columns = GridCells.Fixed(4), + contentPadding = PaddingValues(24.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + horizontalArrangement = Arrangement.spacedBy(12.dp) ) { repeat(20) { item { @@ -216,4 +367,20 @@ fun SmallVideoCardsPreview() { } } } -} \ No newline at end of file +} + +@Preview +@Composable +private fun DanmakuTextPreview() { + BVTheme { + DanmakuText(text = "233") + } +} + +@Preview +@Composable +private fun PlayTextPreview() { + BVTheme { + PlayText(text = "233") + } +} diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/ui/theme/Theme.kt b/app/src/main/kotlin/dev/aaa1115910/bv/ui/theme/Theme.kt index 5a6af20a..e2131a54 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/ui/theme/Theme.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/ui/theme/Theme.kt @@ -38,7 +38,9 @@ fun BVTheme( val fontScale = LocalDensity.current.fontScale val view = LocalView.current - val colorSchemeTv = darkColorScheme() + val colorSchemeTv = darkColorScheme( + border = Color.White + ) val colorSchemeCommon = androidx.compose.material3.darkColorScheme() val typographyTv = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) android6AndBelowTypographyTv else Typography() From ef2aeb77fdca3e210c62645c69c83f8785070997 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Tue, 24 Sep 2024 19:47:09 +0800 Subject: [PATCH 16/27] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=8E=A8=E8=8D=90/?= =?UTF-8?q?=E7=83=AD=E9=97=A8/=E5=8A=A8=E6=80=81=E7=9A=84=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aaa1115910/bv/screen/home/DynamicsScreen.kt | 15 ++++++++++++--- .../aaa1115910/bv/screen/home/PopularScreen.kt | 14 +++++++++++--- .../aaa1115910/bv/screen/home/RecommendScreen.kt | 15 ++++++++++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt index 73ef756e..a56e2649 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed 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.remember @@ -47,10 +48,18 @@ fun DynamicsScreen( val context = LocalContext.current val scope = rememberCoroutineScope() var currentFocusedIndex by remember { mutableIntStateOf(0) } + val shouldLoadMore by remember { + derivedStateOf { currentFocusedIndex + 24 > dynamicViewModel.dynamicList.size } + } - LaunchedEffect(currentFocusedIndex) { - if (currentFocusedIndex + 24 > dynamicViewModel.dynamicList.size) { - scope.launch(Dispatchers.Default) { dynamicViewModel.loadMore() } + //不能直接使用 LaunchedEffect(currentFocusedIndex),会导致整个页面重组 + LaunchedEffect(shouldLoadMore) { + if (shouldLoadMore) { + scope.launch(Dispatchers.IO) { + dynamicViewModel.loadMore() + //加载完成后重置shouldLoadMore为false,避免如果加载失败后无法重新加载 + currentFocusedIndex = -100 + } } } diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt index 20b4fdf1..d85fea44 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed 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.remember @@ -44,10 +45,17 @@ fun PopularScreen( val context = LocalContext.current val scope = rememberCoroutineScope() var currentFocusedIndex by remember { mutableIntStateOf(0) } + val shouldLoadMore by remember { + derivedStateOf { currentFocusedIndex + 24 > popularViewModel.popularVideoList.size } + } - LaunchedEffect(currentFocusedIndex) { - if (currentFocusedIndex + 24 > popularViewModel.popularVideoList.size) { - scope.launch(Dispatchers.IO) { popularViewModel.loadMore() } + LaunchedEffect(shouldLoadMore) { + if (shouldLoadMore) { + scope.launch(Dispatchers.IO) { + popularViewModel.loadMore() + //加载完成后重置shouldLoadMore为false,避免如果加载失败后无法重新加载 + currentFocusedIndex = -100 + } } } diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt index 66f8f8df..eb90c3d2 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.itemsIndexed 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.remember @@ -44,10 +45,18 @@ fun RecommendScreen( val context = LocalContext.current val scope = rememberCoroutineScope() var currentFocusedIndex by remember { mutableIntStateOf(0) } + val shouldLoadMore by remember { + derivedStateOf { currentFocusedIndex + 24 > recommendViewModel.recommendVideoList.size } + } - LaunchedEffect(currentFocusedIndex) { - if (currentFocusedIndex + 24 > recommendViewModel.recommendVideoList.size) { - scope.launch(Dispatchers.IO) { recommendViewModel.loadMore() } + //不能直接使用 LaunchedEffect(currentFocusedIndex),会导致整个页面重组 + LaunchedEffect(shouldLoadMore) { + if (shouldLoadMore) { + scope.launch(Dispatchers.IO) { + recommendViewModel.loadMore() + //加载完成后重置shouldLoadMore为false,避免如果加载失败后无法重新加载 + currentFocusedIndex = -100 + } } } From ec1a6422e402f9a3810d2ef8f78e5777f3cca6d1 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Wed, 2 Oct 2024 13:18:39 +0800 Subject: [PATCH 17/27] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aaa1115910/bv/activities/MainActivity.kt | 4 +- .../bv/activities/anime/AnimeIndexActivity.kt | 2 +- .../activities/anime/AnimeTimelineActivity.kt | 2 +- .../activities/search/SearchInputActivity.kt | 5 +- .../aaa1115910/bv/component/DevelopingTip.kt | 59 +++ .../dev/aaa1115910/bv/component/TopNav.kt | 360 ++++-------------- .../dev/aaa1115910/bv/component/UserPanel.kt | 14 +- .../dev/aaa1115910/bv/screen/HomeScreen.kt | 309 --------------- .../dev/aaa1115910/bv/screen/MainScreen.kt | 217 +++++++++++ .../bv/screen/main/DrawerContent.kt | 216 +++++++++++ .../aaa1115910/bv/screen/main/HomeContent.kt | 199 ++++++++++ .../screen/{home => main}/PartitionScreen.kt | 2 +- .../aaa1115910/bv/screen/main/PgcContent.kt | 155 ++++++++ .../aaa1115910/bv/screen/main/UgcContent.kt | 167 ++++++++ .../screen/{ => main}/home/DynamicsScreen.kt | 25 +- .../screen/{ => main}/home/PopularScreen.kt | 25 +- .../screen/{ => main}/home/RecommendScreen.kt | 25 +- .../pgc/AnimeContent.kt} | 74 ++-- .../pgc}/anime/AnimeIndexScreen.kt | 2 +- .../pgc}/anime/AnimeTimelineScreen.kt | 2 +- .../bv/screen/search/SearchInputScreen.kt | 37 +- .../kotlin/dev/aaa1115910/bv/util/Extends.kt | 14 +- .../bv/viewmodel/home/AnimeViewModel.kt | 6 +- 23 files changed, 1193 insertions(+), 728 deletions(-) create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/component/DevelopingTip.kt delete mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/HomeScreen.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/MainScreen.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/DrawerContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/HomeContent.kt rename app/src/main/kotlin/dev/aaa1115910/bv/screen/{home => main}/PartitionScreen.kt (79%) create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PgcContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/UgcContent.kt rename app/src/main/kotlin/dev/aaa1115910/bv/screen/{ => main}/home/DynamicsScreen.kt (83%) rename app/src/main/kotlin/dev/aaa1115910/bv/screen/{ => main}/home/PopularScreen.kt (81%) rename app/src/main/kotlin/dev/aaa1115910/bv/screen/{ => main}/home/RecommendScreen.kt (81%) rename app/src/main/kotlin/dev/aaa1115910/bv/screen/{home/AnimeScreen.kt => main/pgc/AnimeContent.kt} (92%) rename app/src/main/kotlin/dev/aaa1115910/bv/screen/{home => main/pgc}/anime/AnimeIndexScreen.kt (99%) rename app/src/main/kotlin/dev/aaa1115910/bv/screen/{home => main/pgc}/anime/AnimeTimelineScreen.kt (99%) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/activities/MainActivity.kt b/app/src/main/kotlin/dev/aaa1115910/bv/activities/MainActivity.kt index 003ec93d..a7b7e5c2 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/activities/MainActivity.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/activities/MainActivity.kt @@ -12,7 +12,7 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import dev.aaa1115910.bv.repository.UserRepository -import dev.aaa1115910.bv.screen.HomeScreen +import dev.aaa1115910.bv.screen.MainScreen import dev.aaa1115910.bv.screen.RegionBlockScreen import dev.aaa1115910.bv.screen.user.lock.UnlockUserScreen import dev.aaa1115910.bv.ui.theme.BVTheme @@ -67,7 +67,7 @@ class MainActivity : ComponentActivity() { } else { //HomeScreen() if (!userLockLocked) { - HomeScreen() + MainScreen() } else { UnlockUserScreen( onUnlockSuccess = { user -> diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeIndexActivity.kt b/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeIndexActivity.kt index c2c995fb..c8d425b9 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeIndexActivity.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeIndexActivity.kt @@ -3,7 +3,7 @@ package dev.aaa1115910.bv.activities.anime import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import dev.aaa1115910.bv.screen.home.anime.AnimeIndexScreen +import dev.aaa1115910.bv.screen.main.pgc.anime.AnimeIndexScreen import dev.aaa1115910.bv.ui.theme.BVTheme class AnimeIndexActivity : ComponentActivity() { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeTimelineActivity.kt b/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeTimelineActivity.kt index 3aa04d79..f2832f76 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeTimelineActivity.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeTimelineActivity.kt @@ -3,7 +3,7 @@ package dev.aaa1115910.bv.activities.anime import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import dev.aaa1115910.bv.screen.home.anime.AnimeTimelineScreen +import dev.aaa1115910.bv.screen.main.pgc.anime.AnimeTimelineScreen import dev.aaa1115910.bv.ui.theme.BVTheme class AnimeTimelineActivity : ComponentActivity() { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/activities/search/SearchInputActivity.kt b/app/src/main/kotlin/dev/aaa1115910/bv/activities/search/SearchInputActivity.kt index 86c27aee..5c093a0d 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/activities/search/SearchInputActivity.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/activities/search/SearchInputActivity.kt @@ -3,6 +3,8 @@ package dev.aaa1115910.bv.activities.search import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.runtime.remember +import androidx.compose.ui.focus.FocusRequester import dev.aaa1115910.bv.screen.search.SearchInputScreen import dev.aaa1115910.bv.ui.theme.BVTheme @@ -10,8 +12,9 @@ class SearchInputActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { + val defaultFocusRequester = remember { FocusRequester() } BVTheme { - SearchInputScreen() + SearchInputScreen(defaultFocusRequester = defaultFocusRequester) } } } diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/DevelopingTip.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/DevelopingTip.kt new file mode 100644 index 00000000..fcd1900a --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/DevelopingTip.kt @@ -0,0 +1,59 @@ +package dev.aaa1115910.bv.component + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.tv.material3.MaterialTheme +import androidx.tv.material3.Text +import dev.aaa1115910.bv.ui.theme.BVTheme + +@Composable +fun DevelopingTip(modifier: Modifier = Modifier) { + Column( + modifier = modifier, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(20.dp) + ) { + Text( + text = "\uD83D\uDEA7", + style = MaterialTheme.typography.displayLarge + ) + Text( + text = "前方施工 请绕行", + style = MaterialTheme.typography.titleLarge + ) + } +} + +@Composable +fun DevelopingTipContent(modifier: Modifier = Modifier) { + Box( + modifier = modifier + .fillMaxSize(), + contentAlignment = Alignment.Center + ) { + DevelopingTip() + } +} + +@Preview +@Composable +private fun DevelopingTipPreview() { + BVTheme { + DevelopingTip() + } +} + +@Preview(device = "id:tv_1080p") +@Composable +private fun DevelopingTipContentPreview() { + BVTheme { + DevelopingTipContent() + } +} \ 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 c8f49d21..ab18d43c 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/TopNav.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/TopNav.kt @@ -1,170 +1,73 @@ package dev.aaa1115910.bv.component import android.content.Context -import android.content.Intent -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween +import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.shape.CircleShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.Search -import androidx.compose.material.icons.rounded.Settings import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf 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.draw.alpha -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.rotate -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.tv.foundation.ExperimentalTvFoundationApi -import androidx.tv.material3.Button -import androidx.tv.material3.ButtonDefaults -import androidx.tv.material3.Icon -import androidx.tv.material3.IconButton -import androidx.tv.material3.IconButtonDefaults import androidx.tv.material3.LocalContentColor import androidx.tv.material3.MaterialTheme -import androidx.tv.material3.Surface -import androidx.tv.material3.SurfaceDefaults import androidx.tv.material3.Tab import androidx.tv.material3.TabRow import androidx.tv.material3.TabRowScope import androidx.tv.material3.Text -import coil.compose.AsyncImage import dev.aaa1115910.bv.BVApp -import dev.aaa1115910.bv.R -import dev.aaa1115910.bv.activities.settings.SettingsActivity -import dev.aaa1115910.bv.activities.user.LoginActivity -import dev.aaa1115910.bv.activities.user.UserInfoActivity -import dev.aaa1115910.bv.ui.theme.BVTheme -import kotlinx.coroutines.delay -@OptIn(ExperimentalTvFoundationApi::class) @Composable fun TopNav( modifier: Modifier = Modifier, - isLogin: Boolean, - username: String, - face: String, - settingsButtonFocusRequester: FocusRequester, - onSelectedChange: (TopNavItem) -> Unit = {}, - onClick: (TopNavItem) -> Unit = {}, - onShowUserPanel: () -> Unit = {} + items: List, + isLargePadding:Boolean, + onSelectedChanged: (TopNavItem) -> Unit = {}, + onClick: (TopNavItem) -> Unit = {} ) { - val context = LocalContext.current - var selectedNav by remember { mutableStateOf(TopNavItem.Popular) } - val navList = - listOf( - TopNavItem.Search, - TopNavItem.Recommend, - TopNavItem.Popular, - TopNavItem.Anime, - TopNavItem.Dynamics - ) + val focusRestorerModifiers = createCustomInitialFocusRestorerModifiers() - LaunchedEffect(selectedNav) { - delay(250) - onSelectedChange(selectedNav) - } + var selectedNav by remember { mutableStateOf(items.first()) } + var selectedTabIndex by remember { mutableIntStateOf(0) } + val verticalPadding by animateDpAsState( + targetValue = if (isLargePadding) 24.dp else 12.dp, + label = "top nav vertical padding" + ) - Box( + Row( modifier = modifier .fillMaxWidth() - .padding(8.dp) + .padding(12.dp, verticalPadding), + horizontalArrangement = Arrangement.Center ) { - FocusGroup { - Row( - modifier = Modifier.fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.SpaceBetween, - ) { - Row(verticalAlignment = Alignment.CenterVertically) { - Text( - text = "Bug Video", - style = MaterialTheme.typography.titleMedium, - modifier = Modifier - .alpha(0.5f) - .padding(horizontal = 26.dp) - ) - - var selectedTabIndex by remember { mutableIntStateOf(1) } - - TabRow( - selectedTabIndex = selectedTabIndex, - separator = { Spacer(modifier = Modifier.width(12.dp)) }, - ) { - navList.forEachIndexed { index, tab -> - NavItemTab( - modifier = if (index == 1) Modifier.initiallyFocused() else Modifier.restorableFocus(), - topNavItem = tab, - selected = index == selectedTabIndex, - onFocus = { - if (tab != TopNavItem.Search) selectedNav = tab - selectedTabIndex = index - }, - onClick = { onClick(tab) } - ) - } - } - } - Row( - verticalAlignment = Alignment.CenterVertically - ) { - SettingsIcon( - modifier = Modifier - .restorableFocus() - .focusRequester(settingsButtonFocusRequester), - onClick = { - context.startActivity(Intent(context, SettingsActivity::class.java)) - } - ) - UserIcon( - modifier = Modifier - .restorableFocus() - .padding(end = 12.dp), - isLogin = isLogin, - username = username, - face = face, - onGotoLogin = { - context.startActivity(Intent(context, LoginActivity::class.java)) - }, - onGotoInfo = { - context.startActivity(Intent(context, UserInfoActivity::class.java)) - }, - onFocused = { - if (isLogin) { - onShowUserPanel() - } - } - ) - } + TabRow( + modifier = Modifier + .then(focusRestorerModifiers.parentModifier), + selectedTabIndex = selectedTabIndex, + separator = { Spacer(modifier = Modifier.width(12.dp)) }, + ) { + items.forEachIndexed { index, tab -> + NavItemTab( + modifier = Modifier + .ifElse(index == 0, focusRestorerModifiers.childModifier), + topNavItem = tab, + selected = index == selectedTabIndex, + onFocus = { + selectedNav = tab + selectedTabIndex = index + onSelectedChanged(tab) + }, + onClick = { onClick(tab) } + ) } } } @@ -186,166 +89,63 @@ private fun TabRowScope.NavItemTab( onFocus = onFocus, onClick = onClick ) { - if (topNavItem == TopNavItem.Search) { - Row( - modifier = Modifier - .height(32.dp) - .padding( - horizontal = 16.dp, - vertical = 6.dp - ), - verticalAlignment = Alignment.CenterVertically - ) { - Icon( - imageVector = Icons.Rounded.Search, - contentDescription = null, - tint = LocalContentColor.current, - ) - AnimatedVisibility(visible = selected) { - Text( - text = topNavItem.getDisplayName(context), - color = LocalContentColor.current, - style = MaterialTheme.typography.labelLarge - - ) - } - } - } else { - Text( - modifier = Modifier - .height(32.dp) - .padding(horizontal = 16.dp, vertical = 6.dp), - text = topNavItem.getDisplayName(context), - color = LocalContentColor.current, - style = MaterialTheme.typography.labelLarge - ) - } + Text( + modifier = Modifier + .height(32.dp) + .padding(horizontal = 16.dp, vertical = 6.dp), + text = topNavItem.getDisplayName(context), + color = LocalContentColor.current, + style = MaterialTheme.typography.labelLarge + ) } } -@Composable -private fun SettingsIcon( - modifier: Modifier = Modifier, - onClick: () -> Unit -) { - var hasFocus by remember { mutableStateOf(false) } - - val infiniteTransition = rememberInfiniteTransition( - label = "settings icon infinite transition" - ) - - val iconRotate by infiniteTransition.animateFloat( - initialValue = 0f, - targetValue = 60f, - animationSpec = infiniteRepeatable( - tween(1000, easing = LinearEasing), RepeatMode.Restart - ), - label = "settings icon rotate" - ) - - IconButton( - modifier = modifier.onFocusChanged { hasFocus = it.hasFocus }, - onClick = onClick, - colors = IconButtonDefaults.colors( - containerColor = Color.Transparent - ) - ) { - Icon( - modifier = Modifier.rotate(if (hasFocus) iconRotate else 0f), - imageVector = Icons.Rounded.Settings, - contentDescription = null - ) - } +interface TopNavItem { + fun getDisplayName(context: Context = BVApp.context): String } -@Composable -private fun UserIcon( - modifier: Modifier = Modifier, - isLogin: Boolean, - username: String, - face: String, - onGotoLogin: () -> Unit, - onGotoInfo: () -> Unit, - onFocused: () -> Unit -) { - var hasFocus by remember { mutableStateOf(false) } - Button( - modifier = modifier - .onFocusChanged { - hasFocus = it.hasFocus - if (it.hasFocus) onFocused() - }, - onClick = { if (isLogin) onGotoInfo() else onGotoLogin() }, - colors = ButtonDefaults.colors( - containerColor = Color.Transparent, - focusedContainerColor = if (isLogin) Color.Transparent else MaterialTheme.colorScheme.onSurface, - focusedContentColor = if (isLogin) MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f) else MaterialTheme.colorScheme.inverseOnSurface - ) - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End) - ) { - Text(text = if (isLogin) username else "未登录") - Box { - Surface( - modifier = Modifier - .size(40.dp) - .clip(CircleShape), - colors = SurfaceDefaults.colors( - containerColor = Color.White - ) - ) { - AsyncImage( - modifier = Modifier - .size(40.dp) - .clip(CircleShape), - model = face, - contentDescription = null, - contentScale = ContentScale.FillBounds - ) - } - } - } +enum class HomeTopNavItem(private val displayName: String) : TopNavItem { + Recommend("推荐"), + Popular("热门"), + Dynamics("动态"); + + override fun getDisplayName(context: Context): String { + return displayName } } -enum class TopNavItem(private val _displayNameResId: Int) { - Search(R.string.top_nav_item_search), - Recommend(R.string.top_nav_item_recommend), - Popular(R.string.top_nav_item_popular), - Partition(R.string.top_nav_item_partition), - Anime(R.string.top_nav_item_anime), - Dynamics(R.string.top_nav_item_dynamics); - - fun getDisplayName(context: Context = BVApp.context): String { - return context.getString(_displayNameResId) +enum class UgcTopNavItem(private val displayName: String) : TopNavItem { + Douga("动画"), + Game("游戏"), + Kichiku("鬼畜"), + Music("音乐"), + Dance("舞蹈"), + Cinephile("影视"), + Ent("娱乐"), + Knowledge("知识"), + Tech("科技"), + Information("资讯"), + Food("美食"), + Life("生活"), + Car("汽车"), + Fashion("时尚"), + Sports("体育"), + Animal("动物圈"); + + override fun getDisplayName(context: Context): String { + return displayName } } -@Preview -@Composable -private fun UserIconPreview() { - var isLogin by remember { mutableStateOf(false) } +enum class PgcTopNavItem(private val displayName: String) : TopNavItem { + Anime("番剧"), + GuoChuang("国创"), + Movie("电影"), + Documentary("纪录片"), + Tv("电视剧"), + Variety("综艺"); - BVTheme { - Surface { - Column( - horizontalAlignment = Alignment.CenterHorizontally - ) { - Button(onClick = { isLogin = !isLogin }) { - Text("is login: $isLogin") - } - UserIcon( - modifier = Modifier.padding(4.dp), - isLogin = isLogin, - username = "bishi", - face = "", - onGotoLogin = {}, - onGotoInfo = {}, - onFocused = {} - ) - } - } + override fun getDisplayName(context: Context): String { + return displayName } } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt index b1b0a3f3..7eef8387 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt @@ -148,7 +148,7 @@ fun UserPanel( .onPreviewKeyEvent { println(it.nativeKeyEvent) when (it.nativeKeyEvent.keyCode) { - KeyEvent.KEYCODE_DPAD_LEFT -> { + KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_DOWN -> { if (it.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) onHide() return@onPreviewKeyEvent true } @@ -165,7 +165,17 @@ fun UserPanel( } item { UserPanelSmallItem( - modifier = Modifier, + modifier = Modifier + .onPreviewKeyEvent { + println(it.nativeKeyEvent) + when (it.nativeKeyEvent.keyCode) { + KeyEvent.KEYCODE_DPAD_DOWN -> { + if (it.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) onHide() + return@onPreviewKeyEvent true + } + } + false + }, title = "现在不看", icon = Icons.Rounded.Schedule, onClick = { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/HomeScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/HomeScreen.kt deleted file mode 100644 index bc2baabc..00000000 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/HomeScreen.kt +++ /dev/null @@ -1,309 +0,0 @@ -package dev.aaa1115910.bv.screen - -import android.app.Activity -import android.content.Intent -import androidx.activity.compose.BackHandler -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.Crossfade -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.scaleIn -import androidx.compose.animation.shrinkHorizontally -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableLongStateOf -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.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp -import dev.aaa1115910.bv.R -import dev.aaa1115910.bv.activities.search.SearchInputActivity -import dev.aaa1115910.bv.activities.user.FavoriteActivity -import dev.aaa1115910.bv.activities.user.FollowingSeasonActivity -import dev.aaa1115910.bv.activities.user.HistoryActivity -import dev.aaa1115910.bv.activities.user.UserInfoActivity -import dev.aaa1115910.bv.component.TopNav -import dev.aaa1115910.bv.component.TopNavItem -import dev.aaa1115910.bv.component.UserPanel -import dev.aaa1115910.bv.screen.home.AnimeScreen -import dev.aaa1115910.bv.screen.home.DynamicsScreen -import dev.aaa1115910.bv.screen.home.PartitionScreen -import dev.aaa1115910.bv.screen.home.PopularScreen -import dev.aaa1115910.bv.screen.home.RecommendScreen -import dev.aaa1115910.bv.util.fInfo -import dev.aaa1115910.bv.util.requestFocus -import dev.aaa1115910.bv.util.toast -import dev.aaa1115910.bv.viewmodel.UserViewModel -import dev.aaa1115910.bv.viewmodel.home.DynamicViewModel -import dev.aaa1115910.bv.viewmodel.home.PopularViewModel -import dev.aaa1115910.bv.viewmodel.home.RecommendViewModel -import io.github.oshai.kotlinlogging.KotlinLogging -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.koin.androidx.compose.koinViewModel - -@Composable -fun HomeScreen( - modifier: Modifier = Modifier, - recommendViewModel: RecommendViewModel = koinViewModel(), - popularViewModel: PopularViewModel = koinViewModel(), - dynamicViewModel: DynamicViewModel = koinViewModel(), - userViewModel: UserViewModel = koinViewModel() -) { - val context = LocalContext.current - val scope = rememberCoroutineScope() - val logger = KotlinLogging.logger { } - - val recommendState = rememberLazyGridState() - val popularState = rememberLazyGridState() - val animeState = rememberLazyListState() - val dynamicState = rememberLazyGridState() - - var selectedTab by remember { mutableStateOf(TopNavItem.Popular) } - var showUserPanel by remember { mutableStateOf(false) } - var lastPressBack: Long by remember { mutableLongStateOf(0L) } - - val settingsButtonFocusRequester = remember { FocusRequester() } - val navFocusRequester = remember { FocusRequester() } - - val onFocusBackToNav: () -> Unit = { - logger.fInfo { "onFocusBackToNav" } - navFocusRequester.requestFocus(scope) - } - - //启动时刷新数据 - LaunchedEffect(Unit) { - navFocusRequester.requestFocus() - scope.launch(Dispatchers.IO) { - recommendViewModel.loadMore() - } - scope.launch(Dispatchers.IO) { - popularViewModel.loadMore() - } - scope.launch(Dispatchers.IO) { - dynamicViewModel.loadMore() - } - scope.launch(Dispatchers.IO) { - userViewModel.updateUserInfo() - } - } - - //监听登录变化 - LaunchedEffect(userViewModel.isLogin) { - if (userViewModel.isLogin) { - //login - userViewModel.updateUserInfo() - } else { - //logout - userViewModel.clearUserInfo() - } - } - - val handleBack = { - val currentTime = System.currentTimeMillis() - if (currentTime - lastPressBack < 1000 * 3) { - logger.fInfo { "Exiting bug video" } - (context as Activity).finish() - } else { - lastPressBack = currentTime - R.string.home_press_back_again_to_exit.toast(context) - } - } - - BackHandler(!showUserPanel) { - handleBack() - } - - Box( - modifier = modifier - ) { - Scaffold( - modifier = Modifier, - topBar = { - TopNav( - modifier = Modifier.focusRequester(navFocusRequester), - isLogin = userViewModel.isLogin, - username = userViewModel.username, - face = userViewModel.face, - settingsButtonFocusRequester = settingsButtonFocusRequester, - onSelectedChange = { nav -> - selectedTab = nav - when (nav) { - TopNavItem.Recommend -> { - - } - - TopNavItem.Popular -> { - //scope.launch(Dispatchers.Default) { popularState.scrollToItem(0, 0) } - } - - TopNavItem.Partition -> { - - } - - TopNavItem.Anime -> { - - } - - TopNavItem.Dynamics -> { - //scope.launch(Dispatchers.Default) { dynamicState.scrollToItem(0, 0) } - if (!dynamicViewModel.loading && dynamicViewModel.isLogin && dynamicViewModel.dynamicList.isEmpty()) { - scope.launch(Dispatchers.Default) { dynamicViewModel.loadMore() } - } - } - - TopNavItem.Search -> { - - } - } - }, - onClick = { nav -> - when (nav) { - TopNavItem.Recommend -> { - logger.fInfo { "clear recommend data" } - recommendViewModel.clearData() - logger.fInfo { "reload recommend data" } - scope.launch(Dispatchers.IO) { recommendViewModel.loadMore() } - } - - TopNavItem.Popular -> { - //scope.launch(Dispatchers.Default) { popularState.scrollToItem(0, 0) } - logger.fInfo { "clear popular data" } - popularViewModel.clearData() - logger.fInfo { "reload popular data" } - scope.launch(Dispatchers.IO) { popularViewModel.loadMore() } - } - - TopNavItem.Partition -> { - - } - - TopNavItem.Anime -> { - - } - - TopNavItem.Dynamics -> { - //scope.launch(Dispatchers.Default) { dynamicState.scrollToItem(0, 0) } - dynamicViewModel.clearData() - scope.launch(Dispatchers.IO) { dynamicViewModel.loadMore() } - } - - TopNavItem.Search -> { - context.startActivity( - Intent(context, SearchInputActivity::class.java) - ) - } - } - }, - onShowUserPanel = { showUserPanel = true } - ) - } - ) { innerPadding -> - Box( - modifier = Modifier.padding(innerPadding) - ) { - Crossfade( - targetState = selectedTab, - label = "home content cross fade" - ) { screen -> - when (screen) { - TopNavItem.Recommend -> RecommendScreen( - lazyGridState = recommendState, - onBackNav = onFocusBackToNav - ) - - TopNavItem.Popular -> PopularScreen( - lazyGridState = popularState, - onBackNav = onFocusBackToNav - ) - - TopNavItem.Partition -> PartitionScreen() - TopNavItem.Anime -> AnimeScreen( - lazyListState = animeState, - onBackNav = onFocusBackToNav - ) - - TopNavItem.Dynamics -> DynamicsScreen( - lazyGridState = dynamicState, - onBackNav = onFocusBackToNav - ) - - else -> PopularScreen( - lazyGridState = popularState, - onBackNav = onFocusBackToNav - ) - } - } - } - } - - AnimatedVisibility( - visible = showUserPanel, - enter = fadeIn(), - exit = fadeOut() - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background(Color.Black.copy(alpha = 0.4f)) - ) { - AnimatedVisibility( - modifier = Modifier - .align(Alignment.TopEnd), - visible = showUserPanel, - enter = fadeIn() + scaleIn(), - exit = shrinkHorizontally() - ) { - UserPanel( - modifier = Modifier - .padding(12.dp) - .onFocusChanged { - if (!it.hasFocus) { - settingsButtonFocusRequester.requestFocus() - } - }, - username = userViewModel.username, - face = userViewModel.face, - onHide = { showUserPanel = false }, - onGoMy = { - context.startActivity(Intent(context, UserInfoActivity::class.java)) - }, - onGoHistory = { - context.startActivity(Intent(context, HistoryActivity::class.java)) - }, - onGoFavorite = { - context.startActivity(Intent(context, FavoriteActivity::class.java)) - }, - onGoFollowing = { - context.startActivity( - Intent( - context, - FollowingSeasonActivity::class.java - ) - ) - }, - onGoLater = { - "按钮放在这只是拿来当摆设的!".toast(context) - } - ) - } - } - } - } -} diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/MainScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/MainScreen.kt new file mode 100644 index 00000000..82db186d --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/MainScreen.kt @@ -0,0 +1,217 @@ +package dev.aaa1115910.bv.screen + +import android.app.Activity +import android.content.Intent +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.shrinkHorizontally +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableLongStateOf +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.FocusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.tv.material3.DrawerValue +import androidx.tv.material3.NavigationDrawer +import androidx.tv.material3.rememberDrawerState +import dev.aaa1115910.bv.R +import dev.aaa1115910.bv.activities.settings.SettingsActivity +import dev.aaa1115910.bv.activities.user.FavoriteActivity +import dev.aaa1115910.bv.activities.user.FollowingSeasonActivity +import dev.aaa1115910.bv.activities.user.HistoryActivity +import dev.aaa1115910.bv.activities.user.LoginActivity +import dev.aaa1115910.bv.activities.user.UserInfoActivity +import dev.aaa1115910.bv.component.UserPanel +import dev.aaa1115910.bv.screen.main.DrawerContent +import dev.aaa1115910.bv.screen.main.DrawerItem +import dev.aaa1115910.bv.screen.main.HomeContent +import dev.aaa1115910.bv.screen.main.PgcContent +import dev.aaa1115910.bv.screen.main.UgcContent +import dev.aaa1115910.bv.screen.search.SearchInputScreen +import dev.aaa1115910.bv.util.fException +import dev.aaa1115910.bv.util.fInfo +import dev.aaa1115910.bv.util.toast +import dev.aaa1115910.bv.viewmodel.UserViewModel +import dev.aaa1115910.bv.viewmodel.home.DynamicViewModel +import dev.aaa1115910.bv.viewmodel.home.PopularViewModel +import dev.aaa1115910.bv.viewmodel.home.RecommendViewModel +import io.github.oshai.kotlinlogging.KotlinLogging +import org.koin.androidx.compose.koinViewModel + +@Composable +fun MainScreen( + modifier: Modifier = Modifier, + recommendViewModel: RecommendViewModel = koinViewModel(), + popularViewModel: PopularViewModel = koinViewModel(), + dynamicViewModel: DynamicViewModel = koinViewModel(), + userViewModel: UserViewModel = koinViewModel() +) { + val context = LocalContext.current + val logger = KotlinLogging.logger("MainScreen") + var showUserPanel by remember { mutableStateOf(false) } + var lastPressBack: Long by remember { mutableLongStateOf(0L) } + var selectedDrawerItem by remember { mutableStateOf(DrawerItem.Home) } + val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + + val mainFocusRequester = remember { FocusRequester() } + val ugcFocusRequester = remember { FocusRequester() } + val pgcFocusRequester = remember { FocusRequester() } + val searchFocusRequester = remember { FocusRequester() } + + val handleBack = { + val currentTime = System.currentTimeMillis() + if (currentTime - lastPressBack < 1000 * 3) { + logger.fInfo { "Exiting bug video" } + (context as Activity).finish() + } else { + lastPressBack = currentTime + R.string.home_press_back_again_to_exit.toast(context) + } + } + + val onFocusToContent = { + when (selectedDrawerItem) { + DrawerItem.Home -> mainFocusRequester.requestFocus() + DrawerItem.UGC -> ugcFocusRequester.requestFocus() + DrawerItem.PGC -> pgcFocusRequester.requestFocus() + DrawerItem.Search -> searchFocusRequester.requestFocus() + else -> {} + } + } + + LaunchedEffect(Unit) { + runCatching { + mainFocusRequester.requestFocus() + }.onFailure { + logger.fException(it) { "request default focus requester failed" } + } + } + + BackHandler { + handleBack() + } + + NavigationDrawer( + modifier = modifier, + drawerContent = { + DrawerContent( + isLogin = userViewModel.isLogin, + avatar = userViewModel.face, + username = userViewModel.username, + //avatar = "https://i2.hdslb.com/bfs/face/ef0457addb24141e15dfac6fbf45293ccf1e32ab.jpg", + //username = "碧诗", + onDrawerItemChanged = { selectedDrawerItem = it }, + onOpenSettings = { + context.startActivity(Intent(context, SettingsActivity::class.java)) + }, + onShowUserPanel = { + showUserPanel = true + }, + onFocusToContent = onFocusToContent, + onLogin = { + context.startActivity(Intent(context, LoginActivity::class.java)) + } + ) + }, + drawerState = drawerState + ) { + Box( + modifier = Modifier + ) { + AnimatedContent( + targetState = selectedDrawerItem, + label = "main animated content", + transitionSpec = { + val coefficient = 20 + if (targetState.ordinal < initialState.ordinal) { + fadeIn() + slideInVertically { -it / coefficient } togetherWith + fadeOut() + slideOutVertically { it / coefficient } + } else { + fadeIn() + slideInVertically { it / coefficient } togetherWith + fadeOut() + slideOutVertically { -it / coefficient } + } + } + ) { screen -> + when (screen) { + DrawerItem.Home -> HomeContent(navFocusRequester = mainFocusRequester) + DrawerItem.UGC -> UgcContent(navFocusRequester = ugcFocusRequester) + DrawerItem.PGC -> PgcContent(navFocusRequester = pgcFocusRequester) + DrawerItem.Search -> SearchInputScreen(defaultFocusRequester = searchFocusRequester) + else -> {} + } + } + + AnimatedVisibility( + visible = showUserPanel, + enter = fadeIn(), + exit = fadeOut() + ) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.4f)) + ) { + AnimatedVisibility( + modifier = Modifier + .align(Alignment.TopEnd), + visible = showUserPanel, + enter = fadeIn() + scaleIn(), + exit = shrinkHorizontally() + ) { + UserPanel( + modifier = Modifier + .padding(12.dp) + .onFocusChanged { + if (!it.hasFocus) { + //settingsButtonFocusRequester.requestFocus() + } + }, + username = userViewModel.username, + face = userViewModel.face, + onHide = { showUserPanel = false }, + onGoMy = { + context.startActivity(Intent(context, UserInfoActivity::class.java)) + }, + onGoHistory = { + context.startActivity(Intent(context, HistoryActivity::class.java)) + }, + onGoFavorite = { + context.startActivity(Intent(context, FavoriteActivity::class.java)) + }, + onGoFollowing = { + context.startActivity( + Intent( + context, + FollowingSeasonActivity::class.java + ) + ) + }, + onGoLater = { + "按钮放在这只是拿来当摆设的!".toast(context) + } + ) + } + } + } + } + } +} diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/DrawerContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/DrawerContent.kt new file mode 100644 index 00000000..cf6bc727 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/DrawerContent.kt @@ -0,0 +1,216 @@ +package dev.aaa1115910.bv.screen.main + +import androidx.compose.foundation.basicMarquee +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material.icons.filled.Home +import androidx.compose.material.icons.filled.Movie +import androidx.compose.material.icons.filled.OndemandVideo +import androidx.compose.material.icons.filled.Search +import androidx.compose.material.icons.filled.Settings +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.tv.material3.DrawerValue +import androidx.tv.material3.Icon +import androidx.tv.material3.NavigationDrawer +import androidx.tv.material3.NavigationDrawerItem +import androidx.tv.material3.NavigationDrawerScope +import androidx.tv.material3.Surface +import androidx.tv.material3.SurfaceDefaults +import androidx.tv.material3.Text +import androidx.tv.material3.rememberDrawerState +import coil.compose.AsyncImage +import dev.aaa1115910.bv.component.createCustomInitialFocusRestorerModifiers +import dev.aaa1115910.bv.component.ifElse +import dev.aaa1115910.bv.ui.theme.BVTheme +import dev.aaa1115910.bv.util.isDpadRight +import dev.aaa1115910.bv.util.isKeyDown + +@Composable +fun NavigationDrawerScope.DrawerContent( + modifier: Modifier = Modifier, + isLogin: Boolean = false, + avatar: String = "", + username: String = "", + onDrawerItemChanged: (DrawerItem) -> Unit = {}, + onOpenSettings: () -> Unit = {}, + onShowUserPanel: () -> Unit = {}, + onFocusToContent: () -> Unit = {}, + onLogin: () -> Unit = {} +) { + var selectedItem by remember { mutableStateOf(DrawerItem.Home) } + val focusRestorerModifiers = createCustomInitialFocusRestorerModifiers() + + LaunchedEffect(selectedItem) { + onDrawerItemChanged(selectedItem) + } + + Column( + modifier = modifier + .fillMaxHeight() + .padding(12.dp) + .onPreviewKeyEvent { keyEvent -> + if (keyEvent.isDpadRight()) { + if (keyEvent.isKeyDown()) { + onFocusToContent() + return@onPreviewKeyEvent true + } + } + false + }, + verticalArrangement = Arrangement.SpaceBetween + ) { + + NavigationDrawerItem( + modifier = Modifier, + onClick = { + if (isLogin) { + onShowUserPanel() + } else { + onLogin() + } + }, + selected = selectedItem == DrawerItem.User, + leadingContent = { + if (isLogin) { + Surface( + modifier = Modifier + .size(40.dp) + .clip(CircleShape), + colors = SurfaceDefaults.colors( + containerColor = Color.Gray + ) + ) { + AsyncImage( + modifier = Modifier + .size(40.dp) + .clip(CircleShape), + model = avatar, + contentDescription = null, + contentScale = ContentScale.FillBounds + ) + } + } else { + Icon( + imageVector = DrawerItem.User.displayIcon, + contentDescription = null + ) + } + } + ) { + Text( + modifier = Modifier + .basicMarquee(), + text = if (isLogin) username + else DrawerItem.User.displayName, + maxLines = 1 + ) + } + LazyColumn( + modifier = Modifier + .then(focusRestorerModifiers.parentModifier), + verticalArrangement = Arrangement.Center + ) { + listOf( + DrawerItem.Search, + DrawerItem.Home, + DrawerItem.UGC, + DrawerItem.PGC, + ).forEach { item -> + item { + NavigationDrawerItem( + modifier = Modifier + .onFocusChanged { if (it.hasFocus) selectedItem = item } + .ifElse( + item == DrawerItem.Home, + focusRestorerModifiers.childModifier + ), + onClick = { selectedItem = item }, + selected = selectedItem == item, + leadingContent = { + Icon( + imageVector = item.displayIcon, + contentDescription = null + ) + } + ) { + Text(text = item.displayName) + } + } + } + } + NavigationDrawerItem( + modifier = Modifier, + onClick = onOpenSettings, + selected = false, + leadingContent = { + Icon( + imageVector = DrawerItem.Settings.displayIcon, + contentDescription = null + ) + } + ) { + Text(text = DrawerItem.Settings.displayName) + } + } +} + +enum class DrawerItem( + val displayName: String, + val displayIcon: ImageVector +) { + User(displayName = "点击登录", displayIcon = Icons.Default.AccountCircle), + Search(displayName = "搜索", displayIcon = Icons.Default.Search), + Home(displayName = "首页", displayIcon = Icons.Default.Home), + UGC(displayName = "UGC", displayIcon = Icons.Default.OndemandVideo), + PGC(displayName = "PGC", displayIcon = Icons.Default.Movie), + Settings(displayName = "设置", displayIcon = Icons.Default.Settings), ; +} + +@Preview(device = "id:tv_1080p") +@Composable +private fun DrawerContentClosedPreview() { + val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) + BVTheme { + NavigationDrawer( + drawerContent = { + DrawerContent() + }, + drawerState = drawerState + ) { } + } +} + +@Preview(device = "id:tv_1080p") +@Composable +private fun DrawerContentOpenPreview() { + val drawerState = rememberDrawerState(initialValue = DrawerValue.Open) + BVTheme { + NavigationDrawer( + drawerContent = { + DrawerContent() + }, + drawerState = drawerState + ) { } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/HomeContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/HomeContent.kt new file mode 100644 index 00000000..5e2e5545 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/HomeContent.kt @@ -0,0 +1,199 @@ +package dev.aaa1115910.bv.screen.main + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +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.grid.rememberLazyGridState +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import dev.aaa1115910.bv.component.HomeTopNavItem +import dev.aaa1115910.bv.component.TopNav +import dev.aaa1115910.bv.screen.main.home.DynamicsScreen +import dev.aaa1115910.bv.screen.main.home.PopularScreen +import dev.aaa1115910.bv.screen.main.home.RecommendScreen +import dev.aaa1115910.bv.util.fInfo +import dev.aaa1115910.bv.util.requestFocus +import dev.aaa1115910.bv.viewmodel.UserViewModel +import dev.aaa1115910.bv.viewmodel.home.DynamicViewModel +import dev.aaa1115910.bv.viewmodel.home.PopularViewModel +import dev.aaa1115910.bv.viewmodel.home.RecommendViewModel +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.koin.androidx.compose.koinViewModel + +@Composable +fun HomeContent( + modifier: Modifier = Modifier, + navFocusRequester: FocusRequester, + recommendViewModel: RecommendViewModel = koinViewModel(), + popularViewModel: PopularViewModel = koinViewModel(), + dynamicViewModel: DynamicViewModel = koinViewModel(), + userViewModel: UserViewModel = koinViewModel() +) { + val context = LocalContext.current + val scope = rememberCoroutineScope() + val logger = KotlinLogging.logger("HomeContent") + + val recommendState = rememberLazyGridState() + val popularState = rememberLazyGridState() + val dynamicState = rememberLazyGridState() + + var selectedTab by remember { mutableStateOf(HomeTopNavItem.Recommend) } + var focusOnContent by remember { mutableStateOf(false) } + var hasFocus by remember { mutableStateOf(false) } + val currentListOnTop by remember { + derivedStateOf { + with( + when (selectedTab) { + HomeTopNavItem.Recommend -> recommendState + HomeTopNavItem.Popular -> popularState + HomeTopNavItem.Dynamics -> dynamicState + } + ) { + firstVisibleItemIndex == 0 && firstVisibleItemScrollOffset == 0 + } + } + } + + //启动时刷新数据 + LaunchedEffect(Unit) { + scope.launch(Dispatchers.IO) { + recommendViewModel.loadMore() + } + scope.launch(Dispatchers.IO) { + popularViewModel.loadMore() + } + scope.launch(Dispatchers.IO) { + dynamicViewModel.loadMore() + } + scope.launch(Dispatchers.IO) { + userViewModel.updateUserInfo() + } + } + + //监听登录变化 + LaunchedEffect(userViewModel.isLogin) { + if (userViewModel.isLogin) { + //login + userViewModel.updateUserInfo() + } else { + //logout + userViewModel.clearUserInfo() + } + } + + LaunchedEffect(hasFocus) { + if (hasFocus) { + navFocusRequester.requestFocus() + } + } + + BackHandler(focusOnContent) { + logger.fInfo { "onFocusBackToNav" } + navFocusRequester.requestFocus(scope) + // scroll to top + scope.launch(Dispatchers.Main) { + when (selectedTab) { + HomeTopNavItem.Recommend -> recommendState.animateScrollToItem(0) + HomeTopNavItem.Popular -> popularState.animateScrollToItem(0) + HomeTopNavItem.Dynamics -> dynamicState.animateScrollToItem(0) + } + } + } + + Scaffold( + modifier = Modifier + .onFocusChanged { hasFocus = it.hasFocus }, + topBar = { + TopNav( + modifier = Modifier + .focusRequester(navFocusRequester) + .focusRequester(navFocusRequester) + .padding(end = 80.dp), + items = HomeTopNavItem.entries, + isLargePadding = !focusOnContent && currentListOnTop, + onSelectedChanged = { nav -> + selectedTab = nav as HomeTopNavItem + when (nav) { + HomeTopNavItem.Recommend -> {} + HomeTopNavItem.Popular -> {} + HomeTopNavItem.Dynamics -> { + if (!dynamicViewModel.loading && dynamicViewModel.isLogin && dynamicViewModel.dynamicList.isEmpty()) { + scope.launch(Dispatchers.IO) { dynamicViewModel.loadMore() } + } + } + } + }, + onClick = { nav -> + when (nav) { + HomeTopNavItem.Recommend -> { + logger.fInfo { "clear recommend data" } + recommendViewModel.clearData() + logger.fInfo { "reload recommend data" } + scope.launch(Dispatchers.IO) { recommendViewModel.loadMore() } + } + + HomeTopNavItem.Popular -> { + logger.fInfo { "clear popular data" } + popularViewModel.clearData() + logger.fInfo { "reload popular data" } + scope.launch(Dispatchers.IO) { popularViewModel.loadMore() } + } + + HomeTopNavItem.Dynamics -> { + dynamicViewModel.clearData() + scope.launch(Dispatchers.IO) { dynamicViewModel.loadMore() } + } + } + } + ) + } + ) { innerPadding -> + Box( + modifier = Modifier + .padding(innerPadding) + .onFocusChanged { focusOnContent = it.hasFocus } + ) { + AnimatedContent( + targetState = selectedTab, + label = "home animated content", + transitionSpec = { + val coefficient = 10 + if (targetState.ordinal < initialState.ordinal) { + fadeIn() + slideInHorizontally { -it / coefficient } togetherWith + fadeOut() + slideOutHorizontally { it / coefficient } + } else { + fadeIn() + slideInHorizontally { it / coefficient } togetherWith + fadeOut() + slideOutHorizontally { -it / coefficient } + } + } + ) { screen -> + when (screen) { + HomeTopNavItem.Recommend -> RecommendScreen(lazyGridState = recommendState) + HomeTopNavItem.Popular -> PopularScreen(lazyGridState = popularState) + HomeTopNavItem.Dynamics -> DynamicsScreen(lazyGridState = dynamicState) + } + } + } + } +} diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PartitionScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PartitionScreen.kt similarity index 79% rename from app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PartitionScreen.kt rename to app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PartitionScreen.kt index f70ae1e8..63d8cdd7 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PartitionScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PartitionScreen.kt @@ -1,4 +1,4 @@ -package dev.aaa1115910.bv.screen.home +package dev.aaa1115910.bv.screen.main import androidx.compose.runtime.Composable import androidx.tv.material3.Text diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PgcContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PgcContent.kt new file mode 100644 index 00000000..9603c2ec --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PgcContent.kt @@ -0,0 +1,155 @@ +package dev.aaa1115910.bv.screen.main + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +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 +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.unit.dp +import dev.aaa1115910.bv.component.DevelopingTipContent +import dev.aaa1115910.bv.component.HomeTopNavItem +import dev.aaa1115910.bv.component.PgcTopNavItem +import dev.aaa1115910.bv.component.TopNav +import dev.aaa1115910.bv.screen.main.pgc.AnimeContent +import dev.aaa1115910.bv.util.fInfo +import dev.aaa1115910.bv.util.requestFocus +import dev.aaa1115910.bv.viewmodel.home.AnimeViewModel +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.koin.androidx.compose.koinViewModel + +@Composable +fun PgcContent( + modifier: Modifier = Modifier, + navFocusRequester: FocusRequester, + animeViewModel: AnimeViewModel = koinViewModel() +) { + val scope = rememberCoroutineScope() + val logger = KotlinLogging.logger("PgcContent") + + val animeState = rememberLazyListState() + val guoChuangState = rememberLazyListState() + val movieState = rememberLazyListState() + val documentaryState = rememberLazyListState() + val tvState = rememberLazyListState() + val varietyState = rememberLazyListState() + + var selectedTab by remember { mutableStateOf(PgcTopNavItem.Anime) } + var focusOnContent by remember { mutableStateOf(false) } + val currentListOnTop by remember { + derivedStateOf { + with( + when (selectedTab) { + PgcTopNavItem.Anime -> animeState + PgcTopNavItem.GuoChuang -> guoChuangState + PgcTopNavItem.Movie -> movieState + PgcTopNavItem.Documentary -> documentaryState + PgcTopNavItem.Tv -> tvState + PgcTopNavItem.Variety -> varietyState + } + ) { + firstVisibleItemIndex == 0 && firstVisibleItemScrollOffset == 0 + } + } + } + + //启动时刷新数据 + LaunchedEffect(Unit) { + + } + + BackHandler(focusOnContent) { + logger.fInfo { "onFocusBackToNav" } + navFocusRequester.requestFocus(scope) + // scroll to top + scope.launch(Dispatchers.Main) { + when (selectedTab) { + PgcTopNavItem.Anime -> animeState.animateScrollToItem(0) + PgcTopNavItem.GuoChuang -> guoChuangState.animateScrollToItem(0) + PgcTopNavItem.Movie -> movieState.animateScrollToItem(0) + PgcTopNavItem.Documentary -> documentaryState.animateScrollToItem(0) + PgcTopNavItem.Tv -> tvState.animateScrollToItem(0) + PgcTopNavItem.Variety -> varietyState.animateScrollToItem(0) + } + } + } + + Scaffold( + modifier = Modifier, + topBar = { + TopNav( + modifier = Modifier + .focusRequester(navFocusRequester) + .padding(end = 80.dp), + items = PgcTopNavItem.entries, + isLargePadding = !focusOnContent && currentListOnTop, + onSelectedChanged = { nav -> + selectedTab = nav as PgcTopNavItem + }, + onClick = { nav -> + when (nav) { + PgcTopNavItem.Anime -> { + logger.fInfo { "reload anime data" } + animeViewModel.reloadAll() + } + + PgcTopNavItem.GuoChuang -> {} + PgcTopNavItem.Movie -> {} + PgcTopNavItem.Documentary -> {} + PgcTopNavItem.Tv -> {} + PgcTopNavItem.Variety -> {} + } + } + ) + } + ) { innerPadding -> + Box( + modifier = Modifier + .padding(innerPadding) + .onFocusChanged { focusOnContent = it.hasFocus } + ) { + AnimatedContent( + targetState = selectedTab, + label = "pgc animated content", + transitionSpec = { + val coefficient = 10 + if (targetState.ordinal < initialState.ordinal) { + fadeIn() + slideInHorizontally { -it / coefficient } togetherWith + fadeOut() + slideOutHorizontally { it / coefficient } + } else { + fadeIn() + slideInHorizontally { it / coefficient } togetherWith + fadeOut() + slideOutHorizontally { -it / coefficient } + } + } + ) { screen -> + when (screen) { + PgcTopNavItem.Anime -> AnimeContent(lazyListState = animeState) + PgcTopNavItem.GuoChuang -> DevelopingTipContent() + PgcTopNavItem.Movie -> DevelopingTipContent() + PgcTopNavItem.Documentary -> DevelopingTipContent() + PgcTopNavItem.Tv -> DevelopingTipContent() + PgcTopNavItem.Variety -> DevelopingTipContent() + } + } + } + } +} \ 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 new file mode 100644 index 00000000..de15afc6 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/UgcContent.kt @@ -0,0 +1,167 @@ +package dev.aaa1115910.bv.screen.main + +import androidx.activity.compose.BackHandler +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +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 +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +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.bv.component.TopNav +import dev.aaa1115910.bv.component.UgcTopNavItem +import dev.aaa1115910.bv.util.fInfo +import dev.aaa1115910.bv.util.requestFocus +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +@Composable +fun UgcContent( + modifier: Modifier = Modifier, + navFocusRequester: FocusRequester, +) { + 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) } + + //启动时刷新数据 + LaunchedEffect(Unit) { + + } + + BackHandler(focusOnContent) { + logger.fInfo { "onFocusBackToNav" } + navFocusRequester.requestFocus(scope) + // 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) + } + } + } + + Scaffold( + modifier = Modifier, + topBar = { + TopNav( + modifier = Modifier + .focusRequester(navFocusRequester), + items = UgcTopNavItem.entries, + isLargePadding = !focusOnContent, + onSelectedChanged = { nav -> + selectedTab = nav as UgcTopNavItem + }, + 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 -> {} + } + } + ) + } + ) { innerPadding -> + Box( + modifier = Modifier + .padding(innerPadding) + .onFocusChanged { focusOnContent = it.hasFocus } + ) { + AnimatedContent( + targetState = selectedTab, + label = "ugc animated content", + transitionSpec = { + val coefficient = 10 + if (targetState.ordinal < initialState.ordinal) { + fadeIn() + slideInHorizontally { -it / coefficient } togetherWith + fadeOut() + slideOutHorizontally { it / coefficient } + } else { + fadeIn() + slideInHorizontally { it / coefficient } togetherWith + fadeOut() + slideOutHorizontally { -it / coefficient } + } + } + ) { 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() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/home/DynamicsScreen.kt similarity index 83% rename from app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt rename to app/src/main/kotlin/dev/aaa1115910/bv/screen/main/home/DynamicsScreen.kt index a56e2649..71cc7457 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/DynamicsScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/home/DynamicsScreen.kt @@ -1,4 +1,4 @@ -package dev.aaa1115910.bv.screen.home +package dev.aaa1115910.bv.screen.main.home import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -42,7 +42,6 @@ import org.koin.androidx.compose.koinViewModel fun DynamicsScreen( modifier: Modifier = Modifier, lazyGridState: LazyGridState, - onBackNav: () -> Unit, dynamicViewModel: DynamicViewModel = koinViewModel() ) { val context = LocalContext.current @@ -65,27 +64,7 @@ fun DynamicsScreen( if (dynamicViewModel.isLogin) { LazyVerticalGrid( - modifier = modifier - .onPreviewKeyEvent { - when (it.key) { - Key.Back -> { - if (it.type == KeyEventType.KeyUp) { - scope.launch(Dispatchers.Main) { - lazyGridState.animateScrollToItem(0) - } - onBackNav() - } - return@onPreviewKeyEvent true - } - - Key.DirectionRight -> { - if (currentFocusedIndex % 4 == 3) { - return@onPreviewKeyEvent true - } - } - } - return@onPreviewKeyEvent false - }, + modifier = modifier, state = lazyGridState, columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/home/PopularScreen.kt similarity index 81% rename from app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt rename to app/src/main/kotlin/dev/aaa1115910/bv/screen/main/home/PopularScreen.kt index d85fea44..15d294fd 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/PopularScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/home/PopularScreen.kt @@ -1,4 +1,4 @@ -package dev.aaa1115910.bv.screen.home +package dev.aaa1115910.bv.screen.main.home import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -39,7 +39,6 @@ import org.koin.androidx.compose.koinViewModel fun PopularScreen( modifier: Modifier = Modifier, lazyGridState: LazyGridState, - onBackNav: () -> Unit, popularViewModel: PopularViewModel = koinViewModel() ) { val context = LocalContext.current @@ -60,27 +59,7 @@ fun PopularScreen( } LazyVerticalGrid( - modifier = modifier - .onPreviewKeyEvent { - when (it.key) { - Key.Back -> { - if (it.type == KeyEventType.KeyUp) { - scope.launch(Dispatchers.Main) { - lazyGridState.animateScrollToItem(0) - } - onBackNav() - } - return@onPreviewKeyEvent true - } - - Key.DirectionRight -> { - if (currentFocusedIndex % 4 == 3) { - return@onPreviewKeyEvent true - } - } - } - return@onPreviewKeyEvent false - }, + modifier = modifier, state = lazyGridState, columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/home/RecommendScreen.kt similarity index 81% rename from app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt rename to app/src/main/kotlin/dev/aaa1115910/bv/screen/main/home/RecommendScreen.kt index eb90c3d2..a1b69be6 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/RecommendScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/home/RecommendScreen.kt @@ -1,4 +1,4 @@ -package dev.aaa1115910.bv.screen.home +package dev.aaa1115910.bv.screen.main.home import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -39,7 +39,6 @@ import org.koin.androidx.compose.koinViewModel fun RecommendScreen( modifier: Modifier = Modifier, lazyGridState: LazyGridState, - onBackNav: () -> Unit, recommendViewModel: RecommendViewModel = koinViewModel() ) { val context = LocalContext.current @@ -61,27 +60,7 @@ fun RecommendScreen( } LazyVerticalGrid( - modifier = modifier - .onPreviewKeyEvent { - when (it.key) { - Key.Back -> { - if (it.type == KeyEventType.KeyUp) { - scope.launch(Dispatchers.Main) { - lazyGridState.animateScrollToItem(0) - } - onBackNav() - } - return@onPreviewKeyEvent true - } - - Key.DirectionRight -> { - if (currentFocusedIndex % 4 == 3) { - return@onPreviewKeyEvent true - } - } - } - return@onPreviewKeyEvent false - }, + modifier = modifier, state = lazyGridState, columns = GridCells.Fixed(4), contentPadding = PaddingValues(24.dp), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/AnimeScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt similarity index 92% rename from app/src/main/kotlin/dev/aaa1115910/bv/screen/home/AnimeScreen.kt rename to app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt index 6cca1a01..0d7fa646 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/AnimeScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt @@ -1,4 +1,4 @@ -package dev.aaa1115910.bv.screen.home +package dev.aaa1115910.bv.screen.main.pgc import android.content.Intent import android.view.KeyEvent @@ -8,6 +8,7 @@ import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints @@ -25,17 +26,21 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.List import androidx.compose.material.icons.rounded.Alarm import androidx.compose.material.icons.rounded.Favorite import androidx.compose.runtime.Composable -import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush @@ -44,10 +49,8 @@ import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.KeyEventType import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onPreviewKeyEvent -import androidx.compose.ui.input.key.type import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource @@ -79,50 +82,43 @@ import dev.aaa1115910.bv.util.focusedBorder import dev.aaa1115910.bv.util.resizedImageUrl import dev.aaa1115910.bv.util.toast import dev.aaa1115910.bv.viewmodel.home.AnimeViewModel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel @Composable -fun AnimeScreen( +fun AnimeContent( modifier: Modifier = Modifier, lazyListState: LazyListState, - onBackNav: () -> Unit, animeViewModel: AnimeViewModel = koinViewModel() ) { val context = LocalContext.current - val scope = rememberCoroutineScope() + val carouselFocusRequester = remember { FocusRequester() } val carouselItems = animeViewModel.carouselItems val animeFeeds = animeViewModel.feedItems LazyColumn( - modifier = modifier - .onPreviewKeyEvent { - when (it.key) { - Key.Back -> { - if (it.type == KeyEventType.KeyUp) { - scope.launch(Dispatchers.Main) { - lazyListState.animateScrollToItem(0) - } - onBackNav() - } - return@onPreviewKeyEvent true - } - } - return@onPreviewKeyEvent false - }, + modifier = modifier, state = lazyListState ) { item { - AnimeCarousel( - modifier = Modifier.padding(32.dp, 0.dp), - data = carouselItems - ) + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.Center + ) { + AnimeCarousel( + modifier = Modifier + .width(880.dp) + .padding(32.dp, 0.dp) + .focusRequester(carouselFocusRequester), + data = carouselItems + ) + } } item { AnimeFeatureButtons( - modifier = Modifier.padding(32.dp, 24.dp), + modifier = Modifier.padding(vertical = 24.dp), onOpenTimeline = { context.startActivity(Intent(context, AnimeTimelineActivity::class.java)) }, @@ -182,7 +178,7 @@ fun AnimeCarousel( Carousel( itemCount = data.size, modifier = modifier - .fillMaxWidth() + //.fillMaxWidth() .height(240.dp) .clip(MaterialTheme.shapes.large) .focusedBorder(), @@ -231,6 +227,7 @@ private fun AnimeFeatureButtons( onOpenIndex: () -> Unit, onOpenGamerAni: () -> Unit = {} ) { + val buttonWidth = 185.dp val buttons = listOf( Triple( stringResource(R.string.anime_home_button_timeline), @@ -254,21 +251,24 @@ private fun AnimeFeatureButtons( ) ) - Row( - modifier = modifier.height(80.dp), - horizontalArrangement = Arrangement.spacedBy(24.dp) + LazyRow( + modifier = modifier + .fillMaxWidth() + .height(80.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterHorizontally), + contentPadding = PaddingValues(horizontal = 32.dp) ) { - buttons.forEach { (title, icon, onClick) -> + items(items = buttons) { (title, icon, onClick) -> when (icon) { is ImageVector -> AnimeFeatureButton( - modifier = Modifier.weight(1f), + modifier = Modifier.width(buttonWidth), title = title, icon = icon, onClick = onClick ) is Painter -> AnimeFeatureButton( - modifier = Modifier.weight(1f), + modifier = Modifier.width(buttonWidth), title = title, icon = icon, onClick = onClick @@ -363,7 +363,7 @@ fun AnimeFeedVideoRow( LazyRow( modifier = modifier, contentPadding = PaddingValues(horizontal = 24.dp), - horizontalArrangement = Arrangement.spacedBy(18.dp) + horizontalArrangement = Arrangement.spacedBy(24.dp) ) { data.forEachIndexed { index, feedItem -> val cardModifier = if (index == data.lastIndex) { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeIndexScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/anime/AnimeIndexScreen.kt similarity index 99% rename from app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeIndexScreen.kt rename to app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/anime/AnimeIndexScreen.kt index 89a3847c..a6ee441f 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeIndexScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/anime/AnimeIndexScreen.kt @@ -1,4 +1,4 @@ -package dev.aaa1115910.bv.screen.home.anime +package dev.aaa1115910.bv.screen.main.pgc.anime import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeTimelineScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/anime/AnimeTimelineScreen.kt similarity index 99% rename from app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeTimelineScreen.kt rename to app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/anime/AnimeTimelineScreen.kt index c595ed14..cf1de93d 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/home/anime/AnimeTimelineScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/anime/AnimeTimelineScreen.kt @@ -1,4 +1,4 @@ -package dev.aaa1115910.bv.screen.home.anime +package dev.aaa1115910.bv.screen.main.pgc.anime import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchInputScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchInputScreen.kt index f64a401e..33b94233 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchInputScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/search/SearchInputScreen.kt @@ -1,15 +1,17 @@ package dev.aaa1115910.bv.screen.search +import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.OutlinedTextField @@ -44,10 +46,10 @@ import org.koin.androidx.compose.koinViewModel @Composable fun SearchInputScreen( modifier: Modifier = Modifier, + defaultFocusRequester: FocusRequester, searchInputViewModel: SearchInputViewModel = koinViewModel() ) { val context = LocalContext.current - val softKeyboardFirstButtonFocusRequester = remember { FocusRequester() } val hotsFocusRestorerModifiers = createCustomInitialFocusRestorerModifiers() val historyFocusRestorerModifiers = createCustomInitialFocusRestorerModifiers() val suggestFocusRestorerModifiers = createCustomInitialFocusRestorerModifiers() @@ -69,12 +71,6 @@ fun SearchInputScreen( searchInputViewModel.updateSuggests() } - LaunchedEffect(Unit) { - runCatching { - softKeyboardFirstButtonFocusRequester.requestFocus() - } - } - Scaffold( modifier = modifier, topBar = { @@ -97,13 +93,15 @@ fun SearchInputScreen( Row( modifier = Modifier .padding(innerPadding) - .padding(horizontal = 24.dp, vertical = 8.dp), - horizontalArrangement = Arrangement.spacedBy(12.dp) + .padding(vertical = 8.dp) + .padding(start = 24.dp) + .horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.spacedBy(20.dp) ) { Box( modifier = Modifier - .weight(1f) - .fillMaxSize(), + .width(280.dp) + .fillMaxHeight(), contentAlignment = Alignment.TopCenter ) { Column( @@ -124,7 +122,7 @@ fun SearchInputScreen( ) ) SoftKeyboard( - firstButtonFocusRequester = softKeyboardFirstButtonFocusRequester, + firstButtonFocusRequester = defaultFocusRequester, showSearchWithProxy = Prefs.enableProxy, enableSearchWithProxy = enableProxy, onClick = { @@ -151,8 +149,8 @@ fun SearchInputScreen( if (searchKeyword.isEmpty()) { Column( modifier = Modifier - .weight(1f) - .fillMaxSize() + .width(250.dp) + .fillMaxHeight(), ) { Text( modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), @@ -177,8 +175,8 @@ fun SearchInputScreen( } else { Column( modifier = Modifier - .weight(1f) - .fillMaxSize() + .width(250.dp) + .fillMaxHeight(), ) { Text( modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), @@ -207,8 +205,9 @@ fun SearchInputScreen( Column( modifier = Modifier - .weight(1f) - .fillMaxSize() + .width(250.dp) + .fillMaxHeight() + .padding(end = 10.dp), ) { Text( modifier = Modifier.padding(horizontal = 12.dp, vertical = 8.dp), diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/util/Extends.kt b/app/src/main/kotlin/dev/aaa1115910/bv/util/Extends.kt index f41228ed..c1c920b4 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/util/Extends.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/util/Extends.kt @@ -5,6 +5,11 @@ import android.widget.Toast import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.KeyEvent +import androidx.compose.ui.input.key.KeyEventType +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.type import androidx.core.text.HtmlCompat import dev.aaa1115910.bv.BVApp import dev.aaa1115910.bv.R @@ -99,4 +104,11 @@ fun FocusRequester.requestFocus(scope: CoroutineScope) { fun String.removeHtmlTags(): String = HtmlCompat.fromHtml( this, HtmlCompat.FROM_HTML_MODE_LEGACY -).toString() \ No newline at end of file +).toString() + +fun KeyEvent.isKeyDown(): Boolean = type == KeyEventType.KeyDown +fun KeyEvent.isKeyUp(): Boolean = type == KeyEventType.KeyUp +fun KeyEvent.isDpadUp(): Boolean = key == Key.DirectionUp +fun KeyEvent.isDpadDown(): Boolean = key == Key.DirectionDown +fun KeyEvent.isDpadLeft(): Boolean = key == Key.DirectionLeft +fun KeyEvent.isDpadRight(): Boolean = key == Key.DirectionRight \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/AnimeViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/AnimeViewModel.kt index 051b3d89..e347a791 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/AnimeViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/AnimeViewModel.kt @@ -55,7 +55,7 @@ class AnimeViewModel : ViewModel() { fun reloadAll() { logger.fInfo { "Reload all" } clearAll() - viewModelScope.launch(Dispatchers.Default) { + viewModelScope.launch(Dispatchers.IO) { updateCarousel() updateFeed() } @@ -103,8 +103,8 @@ class AnimeViewModel : ViewModel() { } } - vCardList.chunked(6).forEach { chunkedVCardList -> - if (chunkedVCardList.size == 6) { + vCardList.chunked(5).forEach { chunkedVCardList -> + if (chunkedVCardList.size == 5) { feedItems.add(chunkedVCardList) } else { restSubItems.clear() From 075cb3f33d4c5566a7e40e85afb789c7b727b47a Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Mon, 7 Oct 2024 15:15:20 +0800 Subject: [PATCH 18/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86?= =?UTF-8?q?=E8=AE=BE=E5=A4=87=E6=92=AD=E6=94=BE=E8=A7=86=E9=A2=91=E6=97=B6?= =?UTF-8?q?=E6=B2=A1=E6=9C=89=E7=94=BB=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compose-tv 1.0.0/1.0.0-rc2 的 Surface 会和 media3 的 ExoPlayer 发生冲突,底层的 Surface 会显示在 PlayerView 内画面的上层(ui 的下层)导致视频画面被遮挡 在我 Chromecast with Android TV 12 和 Android 13 avd 中未能复现,但在 Android TV 9 avd 中能复现,可能低版本 Android 出现概率更大 https://issuetracker.google.com/issues/361611808 #151 --- gradle/androidx.versions.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle/androidx.versions.toml b/gradle/androidx.versions.toml index 5fc9980a..c0a1f1af 100644 --- a/gradle/androidx.versions.toml +++ b/gradle/androidx.versions.toml @@ -3,7 +3,8 @@ activity = "1.9.1" compose = "1.7.0-rc01" compose-constraintlayout = "1.0.1" compose-material3 = "1.3.0-rc01" -compose-tv = "1.0.0" +#noinspection GradleDependency https://issuetracker.google.com/issues/361611808 +compose-tv = "1.0.0-rc01" compose-tv-foundation = "1.0.0-alpha11" core = "1.13.1" core-splashscreen = "1.0.1" From 87b6edbfb7497a446c0272775d14d25b21abd9dd Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Mon, 21 Oct 2024 19:15:35 +0800 Subject: [PATCH 19/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E5=9C=A8=E6=B5=8F=E8=A7=88=E8=A7=86=E9=A2=91=E5=88=97=E8=A1=A8?= =?UTF-8?q?=E6=97=B6=E6=8C=89=E8=BF=94=E5=9B=9E=E9=94=AE=E4=BC=9A=E5=88=87?= =?UTF-8?q?=E5=9B=9E=E6=8E=A8=E8=8D=90=E5=88=97=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/kotlin/dev/aaa1115910/bv/screen/main/HomeContent.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/HomeContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/HomeContent.kt index 5e2e5545..9ae073c2 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/HomeContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/HomeContent.kt @@ -127,7 +127,6 @@ fun HomeContent( topBar = { TopNav( modifier = Modifier - .focusRequester(navFocusRequester) .focusRequester(navFocusRequester) .padding(end = 80.dp), items = HomeTopNavItem.entries, From 4a0f60f6f77248cca686c98ed033ab8f15c7dd1f Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Tue, 22 Oct 2024 19:36:43 +0800 Subject: [PATCH 20/27] =?UTF-8?q?=E5=AE=8C=E5=96=84=20PGC=20=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/dev/aaa1115910/bv/BVApp.kt | 16 +- .../aaa1115910/bv/component/pgc/Carousel.kt | 77 ++++ .../aaa1115910/bv/screen/main/PgcContent.kt | 47 ++- .../bv/screen/main/pgc/AnimeContent.kt | 389 ++---------------- .../bv/screen/main/pgc/DocumentaryContent.kt | 21 + .../bv/screen/main/pgc/GuoChuangContent.kt | 21 + .../bv/screen/main/pgc/MovieContent.kt | 21 + .../bv/screen/main/pgc/PgcCommon.kt | 322 +++++++++++++++ .../bv/screen/main/pgc/TvContent.kt | 21 + .../bv/screen/main/pgc/VarietyContent.kt | 21 + .../bv/viewmodel/home/AnimeViewModel.kt | 118 ------ .../bv/viewmodel/pgc/PgcAnimeViewModel.kt | 11 + .../viewmodel/pgc/PgcDocumentaryViewModel.kt | 11 + .../bv/viewmodel/pgc/PgcGuoChuangViewModel.kt | 11 + .../bv/viewmodel/pgc/PgcMovieViewModel.kt | 11 + .../bv/viewmodel/pgc/PgcTvViewModel.kt | 11 + .../bv/viewmodel/pgc/PgcVarietyViewModel.kt | 11 + .../bv/viewmodel/pgc/PgcViewModel.kt | 170 ++++++++ .../biliapi/entity/pgc/PgcCarouselData.kt | 40 ++ .../biliapi/entity/pgc/PgcFeedData.kt | 88 ++++ .../aaa1115910/biliapi/http/BiliHttpApi.kt | 68 +-- .../http/entity/anime/AnimeHomepageData.kt | 43 -- .../http/entity/anime/AnimeHomepageDataV1.kt | 45 -- .../biliapi/http/entity/pgc/PgcFeed.kt | 49 +++ .../{anime/AnimeFeed.kt => pgc/PgcFeedV3.kt} | 9 +- .../PgcWebInitialStateData.kt} | 24 +- .../biliapi/repositories/PgcRepository.kt | 41 ++ .../biliapi/http/BiliHttpApiTest.kt | 31 +- .../biliapi/repositories/PgcRepositoryTest.kt | 33 ++ 29 files changed, 1140 insertions(+), 641 deletions(-) create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/component/pgc/Carousel.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/DocumentaryContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/GuoChuangContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/MovieContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/PgcCommon.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/TvContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/VarietyContent.kt delete mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/AnimeViewModel.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcAnimeViewModel.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcDocumentaryViewModel.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcGuoChuangViewModel.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcMovieViewModel.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcTvViewModel.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcVarietyViewModel.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcViewModel.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcCarouselData.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcFeedData.kt delete mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageData.kt delete mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageDataV1.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/pgc/PgcFeed.kt rename bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/{anime/AnimeFeed.kt => pgc/PgcFeedV3.kt} (93%) rename bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/{anime/AnimeHomepageDataV2.kt => pgc/PgcWebInitialStateData.kt} (86%) create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepository.kt create mode 100644 bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepositoryTest.kt diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt b/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt index e86dde84..86ac3381 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt @@ -15,6 +15,7 @@ import dev.aaa1115910.biliapi.repositories.FavoriteRepository import dev.aaa1115910.biliapi.repositories.HistoryRepository import dev.aaa1115910.biliapi.repositories.IndexRepository import dev.aaa1115910.biliapi.repositories.LoginRepository +import dev.aaa1115910.biliapi.repositories.PgcRepository import dev.aaa1115910.biliapi.repositories.RecommendVideoRepository import dev.aaa1115910.biliapi.repositories.SearchRepository import dev.aaa1115910.biliapi.repositories.SeasonRepository @@ -34,13 +35,18 @@ import dev.aaa1115910.bv.viewmodel.PlayerViewModel import dev.aaa1115910.bv.viewmodel.TagViewModel import dev.aaa1115910.bv.viewmodel.UserViewModel import dev.aaa1115910.bv.viewmodel.VideoPlayerV3ViewModel -import dev.aaa1115910.bv.viewmodel.home.AnimeViewModel import dev.aaa1115910.bv.viewmodel.home.DynamicViewModel import dev.aaa1115910.bv.viewmodel.home.PopularViewModel import dev.aaa1115910.bv.viewmodel.home.RecommendViewModel import dev.aaa1115910.bv.viewmodel.index.AnimeIndexViewModel import dev.aaa1115910.bv.viewmodel.login.AppQrLoginViewModel import dev.aaa1115910.bv.viewmodel.login.SmsLoginViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcAnimeViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcDocumentaryViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcGuoChuangViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcMovieViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcTvViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcVarietyViewModel import dev.aaa1115910.bv.viewmodel.search.SearchInputViewModel import dev.aaa1115910.bv.viewmodel.search.SearchResultViewModel import dev.aaa1115910.bv.viewmodel.user.FavoriteViewModel @@ -157,6 +163,7 @@ val appModule = module { single { SeasonRepository(get()) } single { dev.aaa1115910.biliapi.repositories.UserRepository(get(), get()) } single { IndexRepository() } + single { PgcRepository() } viewModel { DynamicViewModel(get(), get()) } viewModel { RecommendViewModel(get()) } viewModel { PopularViewModel(get()) } @@ -170,13 +177,18 @@ val appModule = module { viewModel { FollowViewModel(get()) } viewModel { SearchInputViewModel(get()) } viewModel { SearchResultViewModel(get()) } - viewModel { AnimeViewModel() } viewModel { FollowingSeasonViewModel(get()) } viewModel { TagViewModel() } viewModel { VideoPlayerV3ViewModel(get(), get()) } viewModel { VideoDetailViewModel(get()) } viewModel { UserSwitchViewModel(get()) } viewModel { AnimeIndexViewModel(get()) } + viewModel { PgcAnimeViewModel(get()) } + viewModel { PgcGuoChuangViewModel(get()) } + viewModel { PgcDocumentaryViewModel(get()) } + viewModel { PgcMovieViewModel(get()) } + viewModel { PgcTvViewModel(get()) } + viewModel { PgcVarietyViewModel(get()) } } val Context.dataStore: DataStore by preferencesDataStore(name = "Settings") 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 new file mode 100644 index 00000000..5bfd746e --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/pgc/Carousel.kt @@ -0,0 +1,77 @@ +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/PgcContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PgcContent.kt index 9603c2ec..6785897d 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PgcContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PgcContent.kt @@ -24,14 +24,22 @@ import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.unit.dp -import dev.aaa1115910.bv.component.DevelopingTipContent -import dev.aaa1115910.bv.component.HomeTopNavItem import dev.aaa1115910.bv.component.PgcTopNavItem import dev.aaa1115910.bv.component.TopNav import dev.aaa1115910.bv.screen.main.pgc.AnimeContent +import dev.aaa1115910.bv.screen.main.pgc.DocumentaryContent +import dev.aaa1115910.bv.screen.main.pgc.GuoChuangContent +import dev.aaa1115910.bv.screen.main.pgc.MovieContent +import dev.aaa1115910.bv.screen.main.pgc.TvContent +import dev.aaa1115910.bv.screen.main.pgc.VarietyContent import dev.aaa1115910.bv.util.fInfo import dev.aaa1115910.bv.util.requestFocus -import dev.aaa1115910.bv.viewmodel.home.AnimeViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcAnimeViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcDocumentaryViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcGuoChuangViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcMovieViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcTvViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcVarietyViewModel import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -41,7 +49,12 @@ import org.koin.androidx.compose.koinViewModel fun PgcContent( modifier: Modifier = Modifier, navFocusRequester: FocusRequester, - animeViewModel: AnimeViewModel = koinViewModel() + pgcAnimeViewModel: PgcAnimeViewModel = koinViewModel(), + pgcGuoChuangViewModel: PgcGuoChuangViewModel = koinViewModel(), + pgcMovieViewModel: PgcMovieViewModel = koinViewModel(), + pgcDocumentaryViewModel: PgcDocumentaryViewModel = koinViewModel(), + pgcTvViewModel: PgcTvViewModel = koinViewModel(), + pgcVarietyViewModel: PgcVarietyViewModel = koinViewModel() ) { val scope = rememberCoroutineScope() val logger = KotlinLogging.logger("PgcContent") @@ -107,16 +120,12 @@ fun PgcContent( }, onClick = { nav -> when (nav) { - PgcTopNavItem.Anime -> { - logger.fInfo { "reload anime data" } - animeViewModel.reloadAll() - } - - PgcTopNavItem.GuoChuang -> {} - PgcTopNavItem.Movie -> {} - PgcTopNavItem.Documentary -> {} - PgcTopNavItem.Tv -> {} - PgcTopNavItem.Variety -> {} + PgcTopNavItem.Anime -> pgcAnimeViewModel.reloadAll() + PgcTopNavItem.GuoChuang -> pgcGuoChuangViewModel.reloadAll() + PgcTopNavItem.Movie -> pgcMovieViewModel.reloadAll() + PgcTopNavItem.Documentary -> pgcDocumentaryViewModel.reloadAll() + PgcTopNavItem.Tv -> pgcTvViewModel.reloadAll() + PgcTopNavItem.Variety -> pgcVarietyViewModel.reloadAll() } } ) @@ -143,11 +152,11 @@ fun PgcContent( ) { screen -> when (screen) { PgcTopNavItem.Anime -> AnimeContent(lazyListState = animeState) - PgcTopNavItem.GuoChuang -> DevelopingTipContent() - PgcTopNavItem.Movie -> DevelopingTipContent() - PgcTopNavItem.Documentary -> DevelopingTipContent() - PgcTopNavItem.Tv -> DevelopingTipContent() - PgcTopNavItem.Variety -> DevelopingTipContent() + PgcTopNavItem.GuoChuang -> GuoChuangContent(lazyListState = guoChuangState) + PgcTopNavItem.Movie -> MovieContent(lazyListState = movieState) + PgcTopNavItem.Documentary -> DocumentaryContent(lazyListState = documentaryState) + PgcTopNavItem.Tv -> TvContent(lazyListState = tvState) + PgcTopNavItem.Variety -> VarietyContent(lazyListState = varietyState) } } } diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt index 0d7fa646..148b535e 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt @@ -1,221 +1,88 @@ package dev.aaa1115910.bv.screen.main.pgc import android.content.Intent -import android.view.KeyEvent -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.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.BoxWithConstraints -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.lazy.itemsIndexed -import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.List import androidx.compose.material.icons.rounded.Alarm import androidx.compose.material.icons.rounded.Favorite import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.drawWithContent -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.focus.onFocusChanged -import androidx.compose.ui.graphics.BlendMode -import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.input.key.Key -import androidx.compose.ui.input.key.key -import androidx.compose.ui.input.key.onPreviewKeyEvent -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.tv.material3.Carousel import androidx.tv.material3.ClickableSurfaceDefaults -import androidx.tv.material3.ExperimentalTvMaterial3Api import androidx.tv.material3.Icon import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Surface import androidx.tv.material3.Text -import coil.compose.AsyncImage -import dev.aaa1115910.biliapi.http.entity.anime.AnimeFeedData -import dev.aaa1115910.biliapi.http.entity.anime.CarouselItem -import dev.aaa1115910.biliapi.http.entity.web.Hover +import dev.aaa1115910.biliapi.repositories.PgcType import dev.aaa1115910.bv.R import dev.aaa1115910.bv.activities.anime.AnimeIndexActivity import dev.aaa1115910.bv.activities.anime.AnimeTimelineActivity import dev.aaa1115910.bv.activities.user.FollowingSeasonActivity -import dev.aaa1115910.bv.activities.video.SeasonInfoActivity -import dev.aaa1115910.bv.component.videocard.SeasonCard -import dev.aaa1115910.bv.entity.carddata.SeasonCardData -import dev.aaa1115910.bv.entity.proxy.ProxyArea import dev.aaa1115910.bv.ui.theme.BVTheme -import dev.aaa1115910.bv.util.ImageSize -import dev.aaa1115910.bv.util.focusedBorder -import dev.aaa1115910.bv.util.resizedImageUrl import dev.aaa1115910.bv.util.toast -import dev.aaa1115910.bv.viewmodel.home.AnimeViewModel +import dev.aaa1115910.bv.viewmodel.pgc.PgcAnimeViewModel import org.koin.androidx.compose.koinViewModel @Composable fun AnimeContent( modifier: Modifier = Modifier, lazyListState: LazyListState, - animeViewModel: AnimeViewModel = koinViewModel() + pgcViewModel: PgcAnimeViewModel = koinViewModel() ) { val context = LocalContext.current - val carouselFocusRequester = remember { FocusRequester() } - val carouselItems = animeViewModel.carouselItems - val animeFeeds = animeViewModel.feedItems - - LazyColumn( - modifier = modifier, - state = lazyListState - ) { - item { - Row( - modifier = Modifier - .fillMaxWidth() - .horizontalScroll(rememberScrollState()), - horizontalArrangement = Arrangement.Center - ) { - AnimeCarousel( - modifier = Modifier - .width(880.dp) - .padding(32.dp, 0.dp) - .focusRequester(carouselFocusRequester), - data = carouselItems - ) - } + val onOpenTimeline: () -> Unit = { + context.startActivity(Intent(context, AnimeTimelineActivity::class.java)) + } + val onOpenFollowing: () -> Unit = { + context.startActivity(Intent(context, FollowingSeasonActivity::class.java)) + } + val onOpenIndex: () -> Unit = { + context.startActivity(Intent(context, AnimeIndexActivity::class.java)) + } + val onOpenGamerAni: () -> Unit = { + val packageManager = context.packageManager + val gamerAniPackageName = "tw.com.gamer.android.animad" + packageManager.getLeanbackLaunchIntentForPackage(gamerAniPackageName)?.let { + context.startActivity(it) + } ?: run { + R.string.anime_home_button_gamer_ani_launch_failed.toast(context) } - item { + } + + PgcScaffold( + lazyListState = lazyListState, + pgcViewModel = pgcViewModel, + pgcType = PgcType.Anime, + featureButtons = { AnimeFeatureButtons( modifier = Modifier.padding(vertical = 24.dp), - onOpenTimeline = { - context.startActivity(Intent(context, AnimeTimelineActivity::class.java)) - }, - onOpenFollowing = { - context.startActivity(Intent(context, FollowingSeasonActivity::class.java)) - }, - onOpenIndex = { - context.startActivity(Intent(context, AnimeIndexActivity::class.java)) - }, - onOpenGamerAni = { - val packageManager = context.packageManager - val gamerAniPackageName = "tw.com.gamer.android.animad" - packageManager.getLeanbackLaunchIntentForPackage(gamerAniPackageName)?.let { - context.startActivity(it) - } ?: let { - R.string.anime_home_button_gamer_ani_launch_failed.toast(context) - } - } + onOpenTimeline = onOpenTimeline, + onOpenFollowing = onOpenFollowing, + onOpenIndex = onOpenIndex, + onOpenGamerAni = onOpenGamerAni ) } - itemsIndexed(items = animeFeeds) { index, feedItems -> - Box( - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 12.dp) - .onFocusChanged { - if (it.hasFocus) { - if (index + 10 > animeFeeds.size) { - animeViewModel.loadMore() - } - } - }, - contentAlignment = Alignment.Center - ) { - when (feedItems.firstOrNull()?.cardStyle) { - "v_card" -> AnimeFeedVideoRow( - data = feedItems - ) - - "rank" -> AnimeFeedRankRow( - data = feedItems - ) - } - } - } - } -} - -@OptIn(ExperimentalTvMaterial3Api::class) -@Composable -fun AnimeCarousel( - 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 -> - AnimeCarouselCard( - data = data[itemIndex], - onClick = { - SeasonInfoActivity.actionStart( - context = context, - epId = data[itemIndex].episodeId, - seasonId = data[itemIndex].seasonId, - proxyArea = ProxyArea.checkProxyArea(data[itemIndex].title) - ) - } - ) - } -} - -@Composable -fun AnimeCarouselCard( - modifier: Modifier = Modifier, - data: CarouselItem, - onClick: () -> Unit = {} -) { - AsyncImage( - modifier = modifier - .fillMaxWidth() - .clip(MaterialTheme.shapes.large) - .clickable { onClick() }, - model = data.cover, - contentDescription = null, - contentScale = ContentScale.FillWidth, - alignment = Alignment.TopCenter ) } @@ -354,170 +221,6 @@ fun AnimeFeatureButton( } } -@Composable -fun AnimeFeedVideoRow( - modifier: Modifier = Modifier, - data: List -) { - val context = LocalContext.current - LazyRow( - modifier = modifier, - contentPadding = PaddingValues(horizontal = 24.dp), - horizontalArrangement = Arrangement.spacedBy(24.dp) - ) { - data.forEachIndexed { index, feedItem -> - val cardModifier = if (index == data.lastIndex) { - Modifier.onPreviewKeyEvent { - when (it.key) { - Key.DirectionRight -> return@onPreviewKeyEvent true - } - false - } - } else { - Modifier - } - - item { - SeasonCard( - modifier = cardModifier, - coverHeight = 180.dp, - data = SeasonCardData( - seasonId = feedItem.seasonId ?: 0, - title = feedItem.title, - subTitle = feedItem.subTitle, - cover = feedItem.cover.resizedImageUrl(ImageSize.SeasonCoverThumbnail), - rating = feedItem.rating ?: "" - ), - onClick = { - SeasonInfoActivity.actionStart( - context = context, - seasonId = feedItem.seasonId, - proxyArea = ProxyArea.checkProxyArea(feedItem.title) - ) - } - ) - } - } - } -} - -@Composable -fun AnimeFeedRankRow( - modifier: Modifier = Modifier, - data: List -) { - val context = LocalContext.current - Box( - modifier = modifier - .height(300.dp) - ) { - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colors = listOf( - // light theme color: Color(250, 222, 214) - Color(20, 18, 17), - Color(20, 18, 17).copy(alpha = 0.298f) - ) - ) - ) - ) {} - BoxWithConstraints { - AsyncImage( - modifier = Modifier - .fillMaxHeight() - .offset(x = (-1 * (0.25 * 1.6 * this.maxHeight.value)).dp) - .graphicsLayer { alpha = 0.99f } - .drawWithContent { - val colors = listOf( - Color.Black, - Color.Transparent - ) - drawContent() - drawRect( - brush = Brush.horizontalGradient(colors), - blendMode = BlendMode.DstIn - ) - drawRect( - brush = Brush.verticalGradient(colors), - blendMode = BlendMode.DstIn - ) - }, - model = data.first().cover, - contentDescription = null, - contentScale = ContentScale.FillHeight, - alpha = 1f - ) - } - Row( - modifier = Modifier - .fillMaxHeight(), - verticalAlignment = Alignment.CenterVertically - ) { - Column( - modifier = Modifier - .fillMaxHeight() - .width(240.dp) - .padding(32.dp), - verticalArrangement = Arrangement.Bottom, - horizontalAlignment = Alignment.End - ) { - Text( - text = data.first().title, - style = MaterialTheme.typography.titleLarge, - color = Color.White - ) - Text( - text = data.first().subTitle, - style = MaterialTheme.typography.bodySmall, - color = Color.White.copy(alpha = 0.6f) - ) - } - - LazyRow( - modifier = modifier, - contentPadding = PaddingValues(horizontal = 32.dp), - horizontalArrangement = Arrangement.spacedBy(18.dp) - ) { - data.first().subItems?.forEachIndexed { index, feedItem -> - val cardModifier = if (index == data.first().subItems?.lastIndex) { - Modifier.onPreviewKeyEvent { - when (it.nativeKeyEvent.keyCode) { - KeyEvent.KEYCODE_DPAD_RIGHT -> return@onPreviewKeyEvent true - } - false - } - } else { - Modifier - } - - item { - SeasonCard( - modifier = cardModifier, - coverHeight = 180.dp, - data = SeasonCardData( - seasonId = feedItem.seasonId ?: 0, - title = feedItem.title, - subTitle = feedItem.subTitle, - cover = feedItem.cover.resizedImageUrl(ImageSize.SeasonCoverThumbnail), - rating = feedItem.rating ?: "" - ), - onClick = { - SeasonInfoActivity.actionStart( - context = context, - seasonId = feedItem.seasonId, - proxyArea = ProxyArea.checkProxyArea(feedItem.title) - ) - } - ) - } - } - } - } - } -} @Preview(device = "id:tv_1080p") @@ -533,35 +236,3 @@ fun AnimeFeatureButtonsPreview() { ) } } - -@Preview(device = "id:tv_1080p") -@Composable -fun AnimeFeedRankRowPreview() { - val data = listOf( - AnimeFeedData.FeedItem.FeedSubItem( - cardStyle = "rank", - rankId = 126, - cover = "http://i0.hdslb.com/bfs/archive/aae451dabf64ead2e983f92be76039a8ba233ade.png", - title = "热门热血番剧榜", - subTitle = "每小时更新", - report = AnimeFeedData.FeedItem.FeedSubItem.Report(), - subItems = List(8) { - AnimeFeedData.FeedItem.FeedSubItem( - cardStyle = "v_card", - rankId = 0, - cover = "https://i0.hdslb.com/bfs/bangumi/image/f610305ad3922bee9d51748ab38da0c54e785b44.png", - hover = Hover( - img = "http://i0.hdslb.com/bfs/archive/aae451dabf64ead2e983f92be76039a8ba233ade.png", - text = listOf("漫画改", "热血", "更新至第6话") - ), - title = "解雇后走上人生巅峰", - subTitle = "被解雇的暗黑士兵慢生活的第二人生", - report = AnimeFeedData.FeedItem.FeedSubItem.Report() - ) - } - ) - ) - BVTheme { - AnimeFeedRankRow(data = data) - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/DocumentaryContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/DocumentaryContent.kt new file mode 100644 index 00000000..0d55365b --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/DocumentaryContent.kt @@ -0,0 +1,21 @@ +package dev.aaa1115910.bv.screen.main.pgc + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.bv.viewmodel.pgc.PgcDocumentaryViewModel +import org.koin.androidx.compose.koinViewModel + +@Composable +fun DocumentaryContent( + modifier: Modifier = Modifier, + lazyListState: LazyListState, + pgcViewModel: PgcDocumentaryViewModel = koinViewModel() +) { + PgcScaffold( + lazyListState = lazyListState, + pgcViewModel = pgcViewModel, + pgcType = PgcType.Documentary + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/GuoChuangContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/GuoChuangContent.kt new file mode 100644 index 00000000..ef1f259b --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/GuoChuangContent.kt @@ -0,0 +1,21 @@ +package dev.aaa1115910.bv.screen.main.pgc + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.bv.viewmodel.pgc.PgcGuoChuangViewModel +import org.koin.androidx.compose.koinViewModel + +@Composable +fun GuoChuangContent( + modifier: Modifier = Modifier, + lazyListState: LazyListState, + pgcViewModel: PgcGuoChuangViewModel = koinViewModel() +) { + PgcScaffold( + lazyListState = lazyListState, + pgcViewModel = pgcViewModel, + pgcType = PgcType.GuoChuang + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/MovieContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/MovieContent.kt new file mode 100644 index 00000000..9c8a4913 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/MovieContent.kt @@ -0,0 +1,21 @@ +package dev.aaa1115910.bv.screen.main.pgc + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.bv.viewmodel.pgc.PgcMovieViewModel +import org.koin.androidx.compose.koinViewModel + +@Composable +fun MovieContent( + modifier: Modifier = Modifier, + lazyListState: LazyListState, + pgcViewModel: PgcMovieViewModel = koinViewModel() +) { + PgcScaffold( + lazyListState = lazyListState, + pgcViewModel = pgcViewModel, + pgcType = PgcType.Movie + ) +} \ No newline at end of file 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 new file mode 100644 index 00000000..96c075e8 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/PgcCommon.kt @@ -0,0 +1,322 @@ +package dev.aaa1115910.bv.screen.main.pgc + +import android.view.KeyEvent +import androidx.compose.foundation.background +import androidx.compose.foundation.horizontalScroll +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.foundation.rememberScrollState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.key.Key +import androidx.compose.ui.input.key.key +import androidx.compose.ui.input.key.onPreviewKeyEvent +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.tv.material3.MaterialTheme +import androidx.tv.material3.Text +import coil.compose.AsyncImage +import dev.aaa1115910.biliapi.entity.pgc.PgcFeedData +import dev.aaa1115910.biliapi.http.SeasonIndexType +import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.bv.activities.video.SeasonInfoActivity +import dev.aaa1115910.bv.component.pgc.PgcCarousel +import dev.aaa1115910.bv.component.videocard.SeasonCard +import dev.aaa1115910.bv.entity.carddata.SeasonCardData +import dev.aaa1115910.bv.entity.proxy.ProxyArea +import dev.aaa1115910.bv.ui.theme.BVTheme +import dev.aaa1115910.bv.util.ImageSize +import dev.aaa1115910.bv.util.resizedImageUrl +import dev.aaa1115910.bv.viewmodel.pgc.FeedListType +import dev.aaa1115910.bv.viewmodel.pgc.PgcViewModel + +@Composable +fun PgcScaffold( + modifier: Modifier = Modifier, + lazyListState: LazyListState, + pgcViewModel: PgcViewModel, + pgcType: PgcType, + featureButtons: (@Composable () -> Unit)? = null +) { + val carouselFocusRequester = remember { FocusRequester() } + + val carouselItems = pgcViewModel.carouselItems + val pgcFeeds = pgcViewModel.feedItems + + LazyColumn( + modifier = modifier, + state = lazyListState + ) { + item { + Row( + modifier = Modifier + .fillMaxWidth() + .horizontalScroll(rememberScrollState()), + horizontalArrangement = Arrangement.Center + ) { + PgcCarousel( + modifier = Modifier + .width(880.dp) + .padding(32.dp, 0.dp) + .focusRequester(carouselFocusRequester), + data = carouselItems + ) + } + } + if (featureButtons != null) { + item { + featureButtons() + } + }else{ + item { + Spacer( + modifier=Modifier + .fillMaxWidth() + .height(24.dp) + ) + } + } + itemsIndexed(items = pgcFeeds) { index, feedListItem -> + Box( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 12.dp) + .onFocusChanged { + if (it.hasFocus) { + if (index + 10 > pgcFeeds.size) { + pgcViewModel.loadMore() + } + } + }, + contentAlignment = Alignment.Center + ) { + when (feedListItem.type) { + FeedListType.Ep -> PgcFeedVideoRow( + data = feedListItem.items!! + ) + + FeedListType.Rank -> PgcFeedRankRow( + data = feedListItem.rank!! + ) + } + } + } + } +} + +@Composable +fun PgcFeedVideoRow( + modifier: Modifier = Modifier, + data: List +) { + val context = LocalContext.current + LazyRow( + modifier = modifier, + contentPadding = PaddingValues(horizontal = 24.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + data.forEachIndexed { index, feedItem -> + val cardModifier = if (index == data.lastIndex) { + Modifier.onPreviewKeyEvent { + when (it.key) { + Key.DirectionRight -> return@onPreviewKeyEvent true + } + false + } + } else { + Modifier + } + + item { + SeasonCard( + modifier = cardModifier, + coverHeight = 180.dp, + data = SeasonCardData( + seasonId = feedItem.seasonId, + title = feedItem.title, + subTitle = feedItem.subTitle, + cover = feedItem.cover.resizedImageUrl(ImageSize.SeasonCoverThumbnail), + rating = feedItem.rating + ), + onClick = { + SeasonInfoActivity.actionStart( + context = context, + seasonId = feedItem.seasonId, + proxyArea = ProxyArea.checkProxyArea(feedItem.title) + ) + } + ) + } + } + } +} + +@Composable +fun PgcFeedRankRow( + modifier: Modifier = Modifier, + data: PgcFeedData.FeedRank +) { + val context = LocalContext.current + Box( + modifier = modifier + .height(300.dp) + ) { + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colors = listOf( + // light theme color: Color(250, 222, 214) + Color(20, 18, 17), + Color(20, 18, 17).copy(alpha = 0.298f) + ) + ) + ) + ) {} + BoxWithConstraints { + AsyncImage( + modifier = Modifier + .fillMaxHeight() + .offset(x = (-1 * (0.25 * 1.6 * this.maxHeight.value)).dp) + .graphicsLayer { alpha = 0.99f } + .drawWithContent { + val colors = listOf( + Color.Black, + Color.Transparent + ) + drawContent() + drawRect( + brush = Brush.horizontalGradient(colors), + blendMode = BlendMode.DstIn + ) + drawRect( + brush = Brush.verticalGradient(colors), + blendMode = BlendMode.DstIn + ) + }, + model = data.cover, + contentDescription = null, + contentScale = ContentScale.FillHeight, + alpha = 1f + ) + } + Row( + modifier = Modifier + .fillMaxHeight(), + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .fillMaxHeight() + .width(240.dp) + .padding(32.dp), + verticalArrangement = Arrangement.Bottom, + horizontalAlignment = Alignment.End + ) { + Text( + text = data.title, + style = MaterialTheme.typography.titleLarge, + color = Color.White + ) + Text( + text = data.subTitle, + style = MaterialTheme.typography.bodySmall, + color = Color.White.copy(alpha = 0.6f) + ) + } + + LazyRow( + modifier = modifier, + contentPadding = PaddingValues(horizontal = 32.dp), + horizontalArrangement = Arrangement.spacedBy(18.dp) + ) { + data.items.forEachIndexed { index, feedItem -> + val cardModifier = if (index == data.items.lastIndex) { + Modifier.onPreviewKeyEvent { + when (it.nativeKeyEvent.keyCode) { + KeyEvent.KEYCODE_DPAD_RIGHT -> return@onPreviewKeyEvent true + } + false + } + } else { + Modifier + } + + item { + SeasonCard( + modifier = cardModifier, + coverHeight = 180.dp, + data = SeasonCardData( + seasonId = feedItem.seasonId, + title = feedItem.title, + subTitle = feedItem.subTitle, + cover = feedItem.cover.resizedImageUrl(ImageSize.SeasonCoverThumbnail), + rating = feedItem.rating + ), + onClick = { + SeasonInfoActivity.actionStart( + context = context, + seasonId = feedItem.seasonId, + proxyArea = ProxyArea.checkProxyArea(feedItem.title) + ) + } + ) + } + } + } + } + } +} + +@Preview(device = "id:tv_1080p") +@Composable +fun PgcFeedRankRowPreview() { + val data = PgcFeedData.FeedRank( + cover = "http://i0.hdslb.com/bfs/archive/aae451dabf64ead2e983f92be76039a8ba233ade.png", + title = "热门热血番剧榜", + subTitle = "每小时更新", + items = List(8) { + PgcFeedData.FeedItem( + cover = "https://i0.hdslb.com/bfs/bangumi/image/f610305ad3922bee9d51748ab38da0c54e785b44.png", + title = "解雇后走上人生巅峰", + subTitle = "被解雇的暗黑士兵慢生活的第二人生", + episodeId = 0, + seasonId = 0, + seasonType = SeasonIndexType.Anime, + rating = "9.8" + ) + } + ) + BVTheme { + PgcFeedRankRow(data = data) + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/TvContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/TvContent.kt new file mode 100644 index 00000000..085a48dd --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/TvContent.kt @@ -0,0 +1,21 @@ +package dev.aaa1115910.bv.screen.main.pgc + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.bv.viewmodel.pgc.PgcTvViewModel +import org.koin.androidx.compose.koinViewModel + +@Composable +fun TvContent( + modifier: Modifier = Modifier, + lazyListState: LazyListState, + pgcViewModel: PgcTvViewModel = koinViewModel() +) { + PgcScaffold( + lazyListState = lazyListState, + pgcViewModel = pgcViewModel, + pgcType = PgcType.Tv + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/VarietyContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/VarietyContent.kt new file mode 100644 index 00000000..20dfa4de --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/VarietyContent.kt @@ -0,0 +1,21 @@ +package dev.aaa1115910.bv.screen.main.pgc + +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.bv.viewmodel.pgc.PgcVarietyViewModel +import org.koin.androidx.compose.koinViewModel + +@Composable +fun VarietyContent( + modifier: Modifier = Modifier, + lazyListState: LazyListState, + pgcViewModel: PgcVarietyViewModel = koinViewModel() +) { + PgcScaffold( + lazyListState = lazyListState, + pgcViewModel = pgcViewModel, + pgcType = PgcType.Variety + ) +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/AnimeViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/AnimeViewModel.kt deleted file mode 100644 index e347a791..00000000 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/home/AnimeViewModel.kt +++ /dev/null @@ -1,118 +0,0 @@ -package dev.aaa1115910.bv.viewmodel.home - -import androidx.compose.runtime.mutableStateListOf -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dev.aaa1115910.biliapi.http.BiliHttpApi -import dev.aaa1115910.biliapi.http.entity.anime.AnimeFeedData -import dev.aaa1115910.biliapi.http.entity.anime.AnimeHomepageDataType -import dev.aaa1115910.biliapi.http.entity.anime.CarouselItem -import dev.aaa1115910.bv.BVApp -import dev.aaa1115910.bv.util.fInfo -import dev.aaa1115910.bv.util.toast -import io.github.oshai.kotlinlogging.KotlinLogging -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext - -class AnimeViewModel : ViewModel() { - companion object { - private val logger = KotlinLogging.logger { } - } - - val carouselItems = mutableStateListOf() - val feedItems = mutableStateListOf>() - private val restSubItems = mutableListOf() - - private var updating = false - private var cursor = 0 - private var hasNext = true - - init { - loadMore() - viewModelScope.launch(Dispatchers.Default) { - updateCarousel() - } - } - - fun loadMore() { - if (hasNext) { - viewModelScope.launch(Dispatchers.Default) { - updateFeed() - } - } - } - - private fun clearAll() { - logger.fInfo { "Clear all data" } - carouselItems.clear() - feedItems.clear() - restSubItems.clear() - cursor = 0 - hasNext = true - } - - fun reloadAll() { - logger.fInfo { "Reload all" } - clearAll() - viewModelScope.launch(Dispatchers.IO) { - updateCarousel() - updateFeed() - } - } - - private suspend fun updateCarousel() { - logger.fInfo { "Update anime carousel" } - runCatching { - val items = BiliHttpApi.getAnimeHomepageData(dataType = AnimeHomepageDataType.V2) - ?.getCarouselItems() ?: emptyList() - logger.fInfo { "Find anime carousels, size: ${items.size}" } - carouselItems.addAll(items) - }.onFailure { - logger.fInfo { "Update anime carousel failed: ${it.stackTraceToString()}" } - withContext(Dispatchers.Main) { - "加载轮播图失败: ${it.message}".toast(BVApp.context) - } - } - } - - private suspend fun updateFeed() { - if (updating) return - updating = true - logger.fInfo { "Update anime feed" } - runCatching { - val responseData = BiliHttpApi.getAnimeFeed(cursor = cursor).getResponseData() - cursor = responseData.coursor - hasNext = responseData.hasNext - updateFeedItems(responseData.items) - }.onFailure { - logger.fInfo { "Update anime feeds failed: ${it.stackTraceToString()}" } - } - updating = false - } - - private fun updateFeedItems(items: List) { - val vCardList = mutableListOf() - val rankList = mutableStateListOf() - - vCardList.addAll(restSubItems) - items.forEach { feedItem -> - when (feedItem.subItems.firstOrNull()?.cardStyle) { - "v_card" -> vCardList.addAll(feedItem.subItems) - "rank" -> rankList.add(feedItem) - } - } - - vCardList.chunked(5).forEach { chunkedVCardList -> - if (chunkedVCardList.size == 5) { - feedItems.add(chunkedVCardList) - } else { - restSubItems.clear() - restSubItems.addAll(chunkedVCardList) - } - } - rankList.forEach { rankListItem -> - rankListItem.subItems.forEach { feedItems.add(listOf(it)) } - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcAnimeViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcAnimeViewModel.kt new file mode 100644 index 00000000..842b68df --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcAnimeViewModel.kt @@ -0,0 +1,11 @@ +package dev.aaa1115910.bv.viewmodel.pgc + +import dev.aaa1115910.biliapi.repositories.PgcRepository +import dev.aaa1115910.biliapi.repositories.PgcType + +class PgcAnimeViewModel( + override val pgcRepository: PgcRepository +) : PgcViewModel( + pgcRepository = pgcRepository, + pgcType = PgcType.Anime +) \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcDocumentaryViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcDocumentaryViewModel.kt new file mode 100644 index 00000000..3bc460d3 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcDocumentaryViewModel.kt @@ -0,0 +1,11 @@ +package dev.aaa1115910.bv.viewmodel.pgc + +import dev.aaa1115910.biliapi.repositories.PgcRepository +import dev.aaa1115910.biliapi.repositories.PgcType + +class PgcDocumentaryViewModel( + override val pgcRepository: PgcRepository +) : PgcViewModel( + pgcRepository = pgcRepository, + pgcType = PgcType.Documentary +) \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcGuoChuangViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcGuoChuangViewModel.kt new file mode 100644 index 00000000..58f02dde --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcGuoChuangViewModel.kt @@ -0,0 +1,11 @@ +package dev.aaa1115910.bv.viewmodel.pgc + +import dev.aaa1115910.biliapi.repositories.PgcRepository +import dev.aaa1115910.biliapi.repositories.PgcType + +class PgcGuoChuangViewModel( + override val pgcRepository: PgcRepository +) : PgcViewModel( + pgcRepository = pgcRepository, + pgcType = PgcType.GuoChuang +) \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcMovieViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcMovieViewModel.kt new file mode 100644 index 00000000..1f38f857 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcMovieViewModel.kt @@ -0,0 +1,11 @@ +package dev.aaa1115910.bv.viewmodel.pgc + +import dev.aaa1115910.biliapi.repositories.PgcRepository +import dev.aaa1115910.biliapi.repositories.PgcType + +class PgcMovieViewModel( + override val pgcRepository: PgcRepository +) : PgcViewModel( + pgcRepository = pgcRepository, + pgcType = PgcType.Movie +) \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcTvViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcTvViewModel.kt new file mode 100644 index 00000000..0ed5e0d9 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcTvViewModel.kt @@ -0,0 +1,11 @@ +package dev.aaa1115910.bv.viewmodel.pgc + +import dev.aaa1115910.biliapi.repositories.PgcRepository +import dev.aaa1115910.biliapi.repositories.PgcType + +class PgcTvViewModel( + override val pgcRepository: PgcRepository +) : PgcViewModel( + pgcRepository = pgcRepository, + pgcType = PgcType.Tv +) \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcVarietyViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcVarietyViewModel.kt new file mode 100644 index 00000000..55cdaa4f --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcVarietyViewModel.kt @@ -0,0 +1,11 @@ +package dev.aaa1115910.bv.viewmodel.pgc + +import dev.aaa1115910.biliapi.repositories.PgcRepository +import dev.aaa1115910.biliapi.repositories.PgcType + +class PgcVarietyViewModel( + override val pgcRepository: PgcRepository +) : PgcViewModel( + pgcRepository = pgcRepository, + pgcType = PgcType.Variety +) \ No newline at end of file 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 new file mode 100644 index 00000000..acd3e2ca --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcViewModel.kt @@ -0,0 +1,170 @@ +package dev.aaa1115910.bv.viewmodel.pgc + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateListOf +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.pgc.PgcFeedData +import dev.aaa1115910.biliapi.repositories.PgcRepository +import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.bv.BVApp +import dev.aaa1115910.bv.util.fInfo +import dev.aaa1115910.bv.util.toast +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +abstract class PgcViewModel( + open val pgcRepository: PgcRepository, + val pgcType: PgcType, +) : ViewModel() { + private val logger = KotlinLogging.logger("PgcViewModel[$pgcType]") + + /** + * 轮播图 + */ + val carouselItems = mutableStateListOf() + + /** + * 猜你喜欢 + */ + val feedItems = mutableStateListOf() + + /** + * 推荐数据中会穿插排行榜,为了避免出现某一行仅出现单独几个剧集,因此将不满一行的剧集单独存起来 + */ + private val restSubItems = mutableListOf() + + var updating by mutableStateOf(false) + var hasNext by mutableStateOf(true) + var cursor by mutableIntStateOf(0) + + init { + loadMore() + viewModelScope.launch(Dispatchers.IO) { + updateCarousel() + } + } + + /** + * 加载更多推荐数据 + */ + fun loadMore() { + if (hasNext) { + viewModelScope.launch(Dispatchers.IO) { + updateFeed() + } + } + } + + /** + * 重新加载所有数据,点击界面顶部 Tab 时使用 + */ + fun reloadAll() { + logger.fInfo { "Reload all $pgcType data" } + clearAll() + viewModelScope.launch(Dispatchers.IO) { + updateCarousel() + updateFeed() + } + } + + /** + * 清理所有数据 + */ + fun clearAll() { + logger.fInfo { "Clear all data" } + carouselItems.clear() + feedItems.clear() + restSubItems.clear() + cursor = 0 + hasNext = true + } + + /** + * 更新轮播图 + */ + private suspend fun updateCarousel() { + logger.fInfo { "Updating $pgcType carousel" } + runCatching { + val carouselData = pgcRepository.getCarousel(pgcType) + logger.fInfo { "Find $pgcType carousels, size: ${carouselData.items.size}" } + carouselItems.addAll(carouselData.items) + logger.debug { "carouselItems: $carouselItems" } + }.onFailure { + logger.fInfo { "Update $pgcType carousel failed: ${it.stackTraceToString()}" } + withContext(Dispatchers.Main) { + "加载 $pgcType 轮播图失败: ${it.message}".toast(BVApp.context) + } + } + } + + /** + * 获取推荐数据 + */ + private suspend fun updateFeed() { + if (updating) return + updating = true + logger.fInfo { "Update anime feed" } + runCatching { + val pgcFeedData = pgcRepository.getFeed( + pgcType = pgcType, + cursor = cursor + ) + cursor = pgcFeedData.cursor + hasNext = pgcFeedData.hasNext + updateFeedItems(pgcFeedData) + }.onFailure { + logger.fInfo { "Update $pgcType feeds failed: ${it.stackTraceToString()}" } + } + updating = false + } + + /** + * 对 [updateFeed] 获取到得数据进行二次整理并更新到 feedItems + */ + private fun updateFeedItems(data: PgcFeedData) { + logger.fInfo { "update $pgcType feed items: [items: ${data.items.size}, ranks: ${data.ranks.size}]" } + val epList = mutableStateListOf() + epList.addAll(restSubItems) + epList.addAll(data.items) + + epList.chunked(5).forEach { chunkedVCardList -> + if (chunkedVCardList.size == 5) { + feedItems.add( + FeedListItem( + type = FeedListType.Ep, + items = chunkedVCardList + ) + ) + } else { + restSubItems.clear() + restSubItems.addAll(chunkedVCardList) + } + } + + data.ranks.forEach { rank -> + feedItems.add( + FeedListItem( + type = FeedListType.Rank, + rank = rank + ) + ) + } + } +} + +data class FeedListItem( + val type: FeedListType, + val items: List? = emptyList(), + val rank: PgcFeedData.FeedRank? = null +) + +enum class FeedListType { + Ep, Rank +} \ No newline at end of file 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 new file mode 100644 index 00000000..fe8ecb71 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcCarouselData.kt @@ -0,0 +1,40 @@ +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/pgc/PgcFeedData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcFeedData.kt new file mode 100644 index 00000000..261faed1 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcFeedData.kt @@ -0,0 +1,88 @@ +package dev.aaa1115910.biliapi.entity.pgc + +import dev.aaa1115910.biliapi.http.SeasonIndexType + +data class PgcFeedData( + var hasNext: Boolean, + var cursor: Int, + var items: List = emptyList(), + var ranks: List = emptyList() +) { + companion object { + fun fromPgcFeedData(data: dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedData): PgcFeedData { + return PgcFeedData( + hasNext = data.hasNext, + cursor = data.coursor, + items = data.items.map { FeedItem.fromFeedSubItem(it) }, + ranks = emptyList() + ) + } + + fun fromPgcFeedData(data: dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedV3Data): PgcFeedData { + val itemsList = data.items.find { it.subItems.first().cardStyle == "v_card" } + val ranksList = data.items.find { it.subItems.first().cardStyle == "rank" } + return PgcFeedData( + hasNext = data.hasNext, + cursor = data.coursor, + items = itemsList?.subItems?.map { FeedItem.fromFeedSubItem(it) } ?: emptyList(), + ranks = ranksList?.subItems?.map { FeedRank.fromFeedSubItem(it) } ?: emptyList() + ) + } + } + + data class FeedItem( + var cover: String, + var title: String, + var subTitle: String, + var seasonId: Int, + var episodeId: Int, + var seasonType: SeasonIndexType, + var rating: String + ) { + companion object { + fun fromFeedSubItem(feedSubItem: dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedData.FeedSubItem): FeedItem { + return FeedItem( + cover = feedSubItem.cover, + title = feedSubItem.title, + subTitle = feedSubItem.subTitle, + seasonId = feedSubItem.seasonId!!, + episodeId = feedSubItem.episodeId, + seasonType = SeasonIndexType.fromId(feedSubItem.seasonType!!), + rating = feedSubItem.rating ?: "0" + ) + } + + fun fromFeedSubItem(feedSubItem: dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedV3Data.FeedItem.FeedSubItem): FeedItem { + return FeedItem( + cover = feedSubItem.cover, + title = feedSubItem.title, + subTitle = feedSubItem.subTitle, + seasonId = feedSubItem.seasonId!!, + episodeId = feedSubItem.episodeId ?: feedSubItem.inline!!.epId, + seasonType = SeasonIndexType.fromId(feedSubItem.seasonType!!), + rating = feedSubItem.rating ?: "0" + ) + } + } + } + + data class FeedRank( + var cover: String, + var title: String, + var subTitle: String, + var items: List + ) { + companion object { + fun fromFeedSubItem(feedSubItem: dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedV3Data.FeedItem.FeedSubItem): FeedRank { + return FeedRank( + cover = feedSubItem.cover, + title = feedSubItem.title, + subTitle = feedSubItem.subTitle, + items = feedSubItem.subItems?.map { FeedItem.fromFeedSubItem(it) } + ?: emptyList() + ) + } + } + } +} + 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 f3ee8a34..7376e097 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 @@ -3,11 +3,6 @@ package dev.aaa1115910.biliapi.http import com.tfowl.ktor.client.plugins.JsoupPlugin import dev.aaa1115910.biliapi.http.entity.BiliResponse import dev.aaa1115910.biliapi.http.entity.BiliResponseWithoutData -import dev.aaa1115910.biliapi.http.entity.anime.AnimeFeedData -import dev.aaa1115910.biliapi.http.entity.anime.AnimeHomepageData -import dev.aaa1115910.biliapi.http.entity.anime.AnimeHomepageDataType -import dev.aaa1115910.biliapi.http.entity.anime.AnimeHomepageDataV1 -import dev.aaa1115910.biliapi.http.entity.anime.AnimeHomepageDataV2 import dev.aaa1115910.biliapi.http.entity.danmaku.DanmakuData import dev.aaa1115910.biliapi.http.entity.danmaku.DanmakuResponse import dev.aaa1115910.biliapi.http.entity.dynamic.DynamicData @@ -15,6 +10,9 @@ import dev.aaa1115910.biliapi.http.entity.history.HistoryData import dev.aaa1115910.biliapi.http.entity.home.RcmdIndexData import dev.aaa1115910.biliapi.http.entity.home.RcmdTopData 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.search.AppSearchSquareData import dev.aaa1115910.biliapi.http.entity.search.KeywordSuggest import dev.aaa1115910.biliapi.http.entity.search.SearchResultData @@ -60,6 +58,7 @@ import dev.aaa1115910.biliapi.http.entity.video.VideoShot import dev.aaa1115910.biliapi.http.entity.web.NavResponseData import dev.aaa1115910.biliapi.http.util.BiliAppConf import dev.aaa1115910.biliapi.http.util.encApiSign +import dev.aaa1115910.biliapi.repositories.PgcType import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.engine.okhttp.OkHttp @@ -1177,50 +1176,49 @@ object BiliHttpApi { }.body() /** 获取番剧首页数据 */ - suspend fun getAnimeHomepageData( - dataType: AnimeHomepageDataType = AnimeHomepageDataType.V1 - ): AnimeHomepageData? { - val htmlDocuments = client.get("https://www.bilibili.com/anime") { - when (dataType) { - AnimeHomepageDataType.V1 -> header("Cookie", "ogv_channel_version=v1") - AnimeHomepageDataType.V2 -> header("Cookie", "ogv_channel_version=v2") - } - }.body() + suspend fun getPgcWebInitialStateData(pgcType: PgcType): PgcWebInitialStateData { + val path = pgcType.name.lowercase() + val htmlDocuments = client.get("https://www.bilibili.com/$path").body() val dataScriptTagContent = htmlDocuments.body().select("script").find { it.html().contains("__INITIAL_STATE__") - }?.html() ?: return null + }?.html() ?: throw IllegalStateException("initial state data cannot be null") val dataJson = dataScriptTagContent.split("__INITIAL_STATE__=", ";(function()")[1] - - return when (dataType) { - AnimeHomepageDataType.V1 -> { - val dataV1 = - runCatching { json.decodeFromString(dataJson) }.getOrNull() - AnimeHomepageData(_dataV1 = dataV1) - } - - AnimeHomepageDataType.V2 -> { - val dataV2 = - runCatching { json.decodeFromString(dataJson) }.getOrNull() - AnimeHomepageData(_dataV2 = dataV2) - } - } + val initinalData = runCatching { + json.decodeFromString(dataJson) + }.onFailure { + println("parse initial state data failed: ${it.stackTraceToString()}") + }.getOrNull() ?: throw IllegalStateException("parse initial state data failed") + return initinalData } /** - * 获取猜你喜欢 + * 获取 PGC 猜你喜欢 * * 返回数据的前几条内包含每小时更新的分类排行榜 */ - suspend fun getAnimeFeed( + suspend fun getPgcFeedV3( name: String = "anime", cursor: Int = 0 - ): BiliResponse = client.get("/pgc/page/web/v3/feed") { + ): BiliResponse = client.get("/pgc/page/web/v3/feed") { parameter("name", name) parameter("coursor", cursor) }.body() + /** + * 获取 PGC 猜你喜欢 + */ + suspend fun getPgcFeed( + name: String = "movie", + cursor: Int = 0 + ): BiliResponse = client.get("/pgc/page/web/feed") { + parameter("name", name) + parameter("coursor", cursor) + parameter("new_cursor_status", true) + }.body() + + /** * 获取用户[mid]的追剧列表 * @@ -1555,7 +1553,11 @@ object BiliHttpApi { } enum class SeasonIndexType(val id: Int) { - Anime(1), Movie(2), Documentary(3), Guochuang(4), Tv(5), Variety(7) + Anime(1), Movie(2), Documentary(3), Guochuang(4), Tv(5), Variety(7); + + companion object { + fun fromId(id: Int) = entries.first { it.id == id } + } } private fun checkToken(accessKey: String?, sessData: String?) { diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageData.kt deleted file mode 100644 index ef9f26e6..00000000 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageData.kt +++ /dev/null @@ -1,43 +0,0 @@ -package dev.aaa1115910.biliapi.http.entity.anime - -import kotlinx.serialization.Serializable - -@Serializable -data class AnimeHomepageData( - private val _dataV1: AnimeHomepageDataV1? = null, - private val _dataV2: AnimeHomepageDataV2? = null -) { - fun getCarouselItems(): List { - val result = mutableListOf() - _dataV1?.carouselList?.forEach { - result.add( - CarouselItem( - cover = it.img, - title = it.title, - episodeId = it.blink.split("play\\ep", "?from")[1].toIntOrNull() - ) - ) - } - _dataV2?.modules?.banner?.items?.filter { it.episodeId != null }?.forEach { - result.add( - CarouselItem( - cover = it.bigCover, - title = it.title, - seasonId = it.seasonId - ) - ) - } - return result - } -} - -enum class AnimeHomepageDataType { - V1, V2 -} - -data class CarouselItem( - val cover: String, - val title: String, - val seasonId: Int? = null, - val episodeId: Int? = null -) \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageDataV1.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageDataV1.kt deleted file mode 100644 index b9f8f7f1..00000000 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageDataV1.kt +++ /dev/null @@ -1,45 +0,0 @@ -package dev.aaa1115910.biliapi.http.entity.anime - -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.JsonElement - -/** - * 动画首页数据(旧版) - * - * @param ver - * @param pageType - * @param carouselList 轮播推荐 - * @param handPickList 话题精选 - * @param handPickRecom 话题精选 - * @param tid - * @param showBv - */ -@Serializable -data class AnimeHomepageDataV1( - var ver: JsonElement?, - val pageType: Int, - val carouselList: List, - val handPickList: List, - val handPickRecom: List, - val tid: Int, - val showBv: Boolean -) - -@Serializable -data class AnimeHomepageDataItem( - val badge: String? = null, - val blink: String, - val gif: String, - val id: Int, - val img: String, - val index: Int? = null, - @SerialName("index_type") - val indexType: Int? = null, - @SerialName("index_value") - val indexValue: Int? = null, - val link: String, - val simg: String, - val title: String, - val wid: Int? = null -) \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/pgc/PgcFeed.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/pgc/PgcFeed.kt new file mode 100644 index 00000000..00624f19 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/pgc/PgcFeed.kt @@ -0,0 +1,49 @@ +package dev.aaa1115910.biliapi.http.entity.pgc + +import dev.aaa1115910.biliapi.http.entity.web.Hover +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonArray + +@Serializable +data class PgcFeedData( + @Suppress("SpellCheckingInspection") + var coursor: Int, + @SerialName("has_next") + val hasNext: Boolean, + var items: List = emptyList() +) { + @Serializable + data class FeedSubItem( + val cover: String, + @SerialName("episode_id") + val episodeId: Int, + val hover: Hover? = null, + val link: String? = null, + @SerialName("rank_id") + val rankId: Int, + val rating: String? = null, + @SerialName("season_id") + val seasonId: Int? = null, + @SerialName("season_type") + val seasonType: Int? = null, + val stat: Stat? = null, + @SerialName("sub_title") + val subTitle: String, + val text: JsonArray? = null, + val title: String, + val userStatus: UserStatus? = null + ) { + @Serializable + data class Stat( + val danmaku: Int, + val duration: Int, + val view: Long + ) + + @Serializable + data class UserStatus( + val follow: Int + ) + } +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeFeed.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/pgc/PgcFeedV3.kt similarity index 93% rename from bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeFeed.kt rename to bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/pgc/PgcFeedV3.kt index d8b1c108..dd553178 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeFeed.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/pgc/PgcFeedV3.kt @@ -1,4 +1,4 @@ -package dev.aaa1115910.biliapi.http.entity.anime +package dev.aaa1115910.biliapi.http.entity.pgc import dev.aaa1115910.biliapi.http.entity.web.Hover import kotlinx.serialization.SerialName @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonArray @Serializable -data class AnimeFeedData( +data class PgcFeedV3Data( @Suppress("SpellCheckingInspection") var coursor: Int, @SerialName("has_next") @@ -26,6 +26,8 @@ data class AnimeFeedData( @SerialName("card_style") val cardStyle: String, val cover: String, + @SerialName("episode_id") + val episodeId: Int? = null, val evaluate: String? = null, val hover: Hover? = null, val inline: Inline? = null, @@ -48,7 +50,6 @@ data class AnimeFeedData( val text: JsonArray? = null, val title: String, val userStatus: UserStatus? = null - ) { @Serializable data class Inline( @@ -76,7 +77,7 @@ data class AnimeFeedData( data class Stat( val danmaku: Int, val duration: Int, - val view: Int + val view: Long ) @Serializable diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageDataV2.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/pgc/PgcWebInitialStateData.kt similarity index 86% rename from bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageDataV2.kt rename to bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/pgc/PgcWebInitialStateData.kt index 6f00a61c..3313dddb 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/anime/AnimeHomepageDataV2.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/pgc/PgcWebInitialStateData.kt @@ -1,4 +1,4 @@ -package dev.aaa1115910.biliapi.http.entity.anime +package dev.aaa1115910.biliapi.http.entity.pgc import dev.aaa1115910.biliapi.http.entity.web.Hover import kotlinx.serialization.SerialName @@ -7,10 +7,10 @@ import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement /** - * 动画首页数据(新版) + * PGC 首页 ssr 数据 */ @Serializable -data class AnimeHomepageDataV2( +data class PgcWebInitialStateData( val modules: Modules, ) { /** @@ -44,10 +44,10 @@ data class AnimeHomepageDataV2( val cover: String, val link: String, val evaluate: String? = null, - val report: JsonElement, + val report: JsonElement? = null, val hover: Hover? = null, val stat: Stat? = null, - val values: JsonArray, + val values: JsonArray? = null, @SerialName("season_id") val seasonId: Int? = null, @SerialName("season_type") @@ -57,23 +57,23 @@ data class AnimeHomepageDataV2( @SerialName("episode_id") val episodeId: Int? = null, @SerialName("big_cover") - val bigCover: String, + val bigCover: String? = null, @SerialName("play_btn") val playBtn: Int? = null, @SerialName("play_title") - val playTitle: String, + val playTitle: String? = null, @SerialName("rank_id") val rankId: Int, @SerialName("user_status") val userStatus: UserStatus? = null, @SerialName("date_ts") - val dateTs: Int, + val dateTs: Int? = null, @SerialName("day_of_week") - val dayOfWeek: Int, + val dayOfWeek: Int? = null, @SerialName("is_today") - val isToday: Int, + val isToday: Int? = null, @SerialName("is_latest") - val isLatest: Int, + val isLatest: Int? = null, val id: String, @SerialName("showReportData") val showReportData: ShowReportData, @@ -86,7 +86,7 @@ data class AnimeHomepageDataV2( @Serializable data class Stat( - val view: Int + val view: Long ) @Serializable 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 new file mode 100644 index 00000000..622d3f7e --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepository.kt @@ -0,0 +1,41 @@ +package dev.aaa1115910.biliapi.repositories + +import dev.aaa1115910.biliapi.entity.pgc.PgcCarouselData +import dev.aaa1115910.biliapi.entity.pgc.PgcFeedData +import dev.aaa1115910.biliapi.http.BiliHttpApi + +class PgcRepository { + suspend fun getCarousel(pgcType: PgcType): PgcCarouselData { + val initialStateData = BiliHttpApi.getPgcWebInitialStateData(pgcType) + val carouselData = PgcCarouselData.fromPgcWebInitialStateData(initialStateData) + return carouselData + } + + suspend fun getFeed(pgcType: PgcType, cursor: Int): PgcFeedData { + val data = when (pgcType) { + PgcType.Anime, PgcType.GuoChuang -> PgcFeedData.fromPgcFeedData( + BiliHttpApi.getPgcFeedV3( + name = pgcType.name.lowercase(), + cursor = cursor + ).getResponseData() + ) + + PgcType.Movie, PgcType.Tv, PgcType.Documentary, PgcType.Variety -> PgcFeedData.fromPgcFeedData( + BiliHttpApi.getPgcFeed( + name = pgcType.name.lowercase(), + cursor = cursor + ).getResponseData() + ) + } + return data + } +} + +enum class PgcType { + Anime, + GuoChuang, + Movie, + Documentary, + Tv, + Variety +} 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 815a0473..9c5a532f 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 @@ -2,9 +2,9 @@ package dev.aaa1115910.biliapi.http import dev.aaa1115910.biliapi.entity.season.FollowingSeasonStatus import dev.aaa1115910.biliapi.entity.season.FollowingSeasonType -import dev.aaa1115910.biliapi.http.entity.anime.AnimeHomepageDataType import dev.aaa1115910.biliapi.http.entity.user.FollowAction import dev.aaa1115910.biliapi.http.entity.user.FollowActionSource +import dev.aaa1115910.biliapi.repositories.PgcType import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.junit.jupiter.api.Test @@ -559,18 +559,37 @@ internal class BiliHttpApiTest { } @Test - fun `get anime homepage data`() { + fun `get web initial state data`() { runBlocking { - AnimeHomepageDataType.values().forEach { - println(BiliHttpApi.getAnimeHomepageData(dataType = it)) + PgcType.entries.forEach { pgcType -> + println("type: ${pgcType.name}") + println( + BiliHttpApi.getPgcWebInitialStateData(pgcType) + .toString().replace("\n", "") + ) } } } @Test - fun `get anime feed data`() { + fun `get pgc feed data`() { runBlocking { - println(BiliHttpApi.getAnimeFeed()) + PgcType.entries.forEach { pgcType -> + println("type: ${pgcType.name}") + when (pgcType) { + PgcType.Anime, PgcType.GuoChuang -> + println( + BiliHttpApi.getPgcFeedV3(name = pgcType.name.lowercase()) + .toString().replace("\n", "") + ) + + PgcType.Tv, PgcType.Movie, PgcType.Documentary, PgcType.Variety -> + println( + BiliHttpApi.getPgcFeed(name = pgcType.name.lowercase()) + .toString().replace("\n", "") + ) + } + } } } diff --git a/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepositoryTest.kt b/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepositoryTest.kt new file mode 100644 index 00000000..15fe2eee --- /dev/null +++ b/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepositoryTest.kt @@ -0,0 +1,33 @@ +package dev.aaa1115910.biliapi.repositories + +import kotlinx.coroutines.runBlocking +import kotlin.test.Test + +class PgcRepositoryTest { + private val pgcRepository: PgcRepository = PgcRepository() + + @Test + fun `get pgc carousel data`() { + runBlocking { + PgcType.entries.forEach { pgcType -> + println("pgcType: $pgcType") + val data = pgcRepository.getCarousel(pgcType) + println(data) + } + } + } + + @Test + fun `get pgc feed data`() { + runBlocking { + PgcType.entries.forEach { pgcType -> + println("pgcType: $pgcType") + val data = pgcRepository.getFeed( + pgcType = pgcType, + cursor = 0 + ) + println(data) + } + } + } +} \ No newline at end of file From ac8c6ca424a99840e4073d7aa8fe71ea8a898388 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Fri, 25 Oct 2024 22:16:57 +0800 Subject: [PATCH 21/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86?= =?UTF-8?q?=E5=89=A7=E9=9B=86=E6=97=A0=E6=B3=95=E6=92=AD=E6=94=BE=EF=BC=88?= =?UTF-8?q?Web=E6=8E=A5=E5=8F=A3=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aaa1115910/biliapi/http/entity/video/PlayUrlResponse.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/video/PlayUrlResponse.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/video/PlayUrlResponse.kt index 04168a1f..d6e87715 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/video/PlayUrlResponse.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/video/PlayUrlResponse.kt @@ -143,13 +143,16 @@ data class DashFlac( @Serializable data class DashData( val id: Int, + @SerialName("base_url") val baseUrl: String, val backupUrl: List = emptyList(), val bandwidth: Int, + @SerialName("mime_type") val mimeType: String, val codecs: String, val width: Int, val height: Int, + @SerialName("frame_rate") val frameRate: String, val sar: String, @SerialName("start_with_sap") From 9da689f401864e618aca5adafb6e43d7d6e70e36 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Wed, 30 Oct 2024 22:13:58 +0800 Subject: [PATCH 22/27] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20PGC=20=E7=B4=A2?= =?UTF-8?q?=E5=BC=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 6 +- .../main/kotlin/dev/aaa1115910/bv/BVApp.kt | 6 +- .../bv/activities/anime/AnimeIndexActivity.kt | 18 - .../bv/activities/pgc/PgcIndexActivity.kt | 34 ++ .../{ => pgc}/anime/AnimeTimelineActivity.kt | 2 +- .../bv/component/index/IndexFilter.kt | 332 -------------- .../bv/component/pgc/IndexFilter.kt | 367 +++++++++++++++ .../bv/entity/carddata/SeasonCardData.kt | 17 +- .../bv/screen/main/pgc/AnimeContent.kt | 136 +----- .../bv/screen/main/pgc/DocumentaryContent.kt | 73 ++- .../bv/screen/main/pgc/GuoChuangContent.kt | 73 ++- .../bv/screen/main/pgc/MovieContent.kt | 73 ++- .../bv/screen/main/pgc/PgcCommon.kt | 135 +++++- .../AnimeIndexScreen.kt => PgcIndexScreen.kt} | 142 +++--- .../bv/screen/main/pgc/TvContent.kt | 73 ++- .../bv/screen/main/pgc/VarietyContent.kt | 73 ++- .../kotlin/dev/aaa1115910/bv/util/Extends.kt | 4 +- .../bv/util/PgcIndexParamExtends.kt | 67 +++ .../dev/aaa1115910/bv/util/PgcTypeExtends.kt | 14 + .../bv/viewmodel/index/AnimeIndexViewModel.kt | 86 ---- .../bv/viewmodel/index/PgcIndexViewModel.kt | 111 +++++ .../bv/viewmodel/pgc/PgcAnimeViewModel.kt | 2 +- .../viewmodel/pgc/PgcDocumentaryViewModel.kt | 2 +- .../bv/viewmodel/pgc/PgcGuoChuangViewModel.kt | 2 +- .../bv/viewmodel/pgc/PgcMovieViewModel.kt | 2 +- .../bv/viewmodel/pgc/PgcTvViewModel.kt | 2 +- .../bv/viewmodel/pgc/PgcVarietyViewModel.kt | 2 +- .../bv/viewmodel/pgc/PgcViewModel.kt | 9 +- app/src/main/res/values/arrays.xml | 229 ++++++++- app/src/main/res/values/strings.xml | 32 +- .../biliapi/entity/pgc/PgcFeedData.kt | 48 +- .../aaa1115910/biliapi/entity/pgc/PgcItem.kt | 51 ++ .../aaa1115910/biliapi/entity/pgc/PgcType.kt | 10 + .../biliapi/entity/pgc/index/IndexParams.kt | 434 ++++++++++++++++++ .../biliapi/entity/pgc/index/PgcIndexData.kt | 31 ++ .../aaa1115910/biliapi/http/BiliHttpApi.kt | 2 +- .../biliapi/http/entity/index/IndexFilter.kt | 2 + .../http/entity/index/IndexFilterStyle.kt | 6 + .../biliapi/http/entity/index/IndexOrder.kt | 3 +- .../biliapi/repositories/IndexRepository.kt | 152 ------ .../biliapi/repositories/PgcRepository.kt | 117 ++++- .../biliapi/http/BiliHttpApiTest.kt | 2 +- .../repositories/IndexRepositoryTest.kt | 98 ---- .../biliapi/repositories/PgcRepositoryTest.kt | 42 ++ 44 files changed, 2131 insertions(+), 991 deletions(-) delete mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeIndexActivity.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/activities/pgc/PgcIndexActivity.kt rename app/src/main/kotlin/dev/aaa1115910/bv/activities/{ => pgc}/anime/AnimeTimelineActivity.kt (91%) delete mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/component/index/IndexFilter.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/component/pgc/IndexFilter.kt rename app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/{anime/AnimeIndexScreen.kt => PgcIndexScreen.kt} (58%) create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/util/PgcIndexParamExtends.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/util/PgcTypeExtends.kt delete mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/index/AnimeIndexViewModel.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/index/PgcIndexViewModel.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcItem.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcType.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/index/IndexParams.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/index/PgcIndexData.kt delete mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/IndexRepository.kt delete mode 100644 bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/IndexRepositoryTest.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index dfdde6e0..442636ca 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -66,9 +66,9 @@ android:label="@string/title_activity_tag" android:theme="@style/Theme.BV" /> diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt b/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt index 86ac3381..e0c51b6b 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/BVApp.kt @@ -13,7 +13,6 @@ import dev.aaa1115910.biliapi.repositories.AuthRepository import dev.aaa1115910.biliapi.repositories.ChannelRepository import dev.aaa1115910.biliapi.repositories.FavoriteRepository import dev.aaa1115910.biliapi.repositories.HistoryRepository -import dev.aaa1115910.biliapi.repositories.IndexRepository import dev.aaa1115910.biliapi.repositories.LoginRepository import dev.aaa1115910.biliapi.repositories.PgcRepository import dev.aaa1115910.biliapi.repositories.RecommendVideoRepository @@ -38,7 +37,7 @@ import dev.aaa1115910.bv.viewmodel.VideoPlayerV3ViewModel import dev.aaa1115910.bv.viewmodel.home.DynamicViewModel import dev.aaa1115910.bv.viewmodel.home.PopularViewModel import dev.aaa1115910.bv.viewmodel.home.RecommendViewModel -import dev.aaa1115910.bv.viewmodel.index.AnimeIndexViewModel +import dev.aaa1115910.bv.viewmodel.index.PgcIndexViewModel import dev.aaa1115910.bv.viewmodel.login.AppQrLoginViewModel import dev.aaa1115910.bv.viewmodel.login.SmsLoginViewModel import dev.aaa1115910.bv.viewmodel.pgc.PgcAnimeViewModel @@ -162,7 +161,6 @@ val appModule = module { single { VideoDetailRepository(get(), get(), get()) } single { SeasonRepository(get()) } single { dev.aaa1115910.biliapi.repositories.UserRepository(get(), get()) } - single { IndexRepository() } single { PgcRepository() } viewModel { DynamicViewModel(get(), get()) } viewModel { RecommendViewModel(get()) } @@ -182,7 +180,7 @@ val appModule = module { viewModel { VideoPlayerV3ViewModel(get(), get()) } viewModel { VideoDetailViewModel(get()) } viewModel { UserSwitchViewModel(get()) } - viewModel { AnimeIndexViewModel(get()) } + viewModel { PgcIndexViewModel(get()) } viewModel { PgcAnimeViewModel(get()) } viewModel { PgcGuoChuangViewModel(get()) } viewModel { PgcDocumentaryViewModel(get()) } diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeIndexActivity.kt b/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeIndexActivity.kt deleted file mode 100644 index c8d425b9..00000000 --- a/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeIndexActivity.kt +++ /dev/null @@ -1,18 +0,0 @@ -package dev.aaa1115910.bv.activities.anime - -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import dev.aaa1115910.bv.screen.main.pgc.anime.AnimeIndexScreen -import dev.aaa1115910.bv.ui.theme.BVTheme - -class AnimeIndexActivity : ComponentActivity() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContent { - BVTheme { - AnimeIndexScreen() - } - } - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/activities/pgc/PgcIndexActivity.kt b/app/src/main/kotlin/dev/aaa1115910/bv/activities/pgc/PgcIndexActivity.kt new file mode 100644 index 00000000..ff877e2e --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/activities/pgc/PgcIndexActivity.kt @@ -0,0 +1,34 @@ +package dev.aaa1115910.bv.activities.pgc + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.bv.screen.main.pgc.PgcIndexScreen +import dev.aaa1115910.bv.ui.theme.BVTheme + +class PgcIndexActivity : ComponentActivity() { + companion object { + fun actionStart( + context: Context, + pgcType: PgcType + ) { + context.startActivity( + Intent(context, PgcIndexActivity::class.java).apply { + putExtra("pgcType", pgcType.ordinal) + } + ) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + BVTheme { + PgcIndexScreen() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeTimelineActivity.kt b/app/src/main/kotlin/dev/aaa1115910/bv/activities/pgc/anime/AnimeTimelineActivity.kt similarity index 91% rename from app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeTimelineActivity.kt rename to app/src/main/kotlin/dev/aaa1115910/bv/activities/pgc/anime/AnimeTimelineActivity.kt index f2832f76..bb214914 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/activities/anime/AnimeTimelineActivity.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/activities/pgc/anime/AnimeTimelineActivity.kt @@ -1,4 +1,4 @@ -package dev.aaa1115910.bv.activities.anime +package dev.aaa1115910.bv.activities.pgc.anime import android.os.Bundle import androidx.activity.ComponentActivity diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/index/IndexFilter.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/index/IndexFilter.kt deleted file mode 100644 index 5661d9a9..00000000 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/index/IndexFilter.kt +++ /dev/null @@ -1,332 +0,0 @@ -package dev.aaa1115910.bv.component.index - -import android.content.Context -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.heightIn -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Check -import androidx.compose.material3.AlertDialog -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.Icon -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -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.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.window.DialogProperties -import androidx.tv.material3.ExperimentalTvMaterial3Api -import androidx.tv.material3.FilterChip -import androidx.tv.material3.MaterialTheme -import androidx.tv.material3.Surface -import androidx.tv.material3.Text -import dev.aaa1115910.biliapi.http.entity.index.IndexOrder -import dev.aaa1115910.biliapi.http.entity.index.animeIndexOrders -import dev.aaa1115910.biliapi.http.entity.index.indexFilterArea -import dev.aaa1115910.biliapi.http.entity.index.indexFilterCopyright -import dev.aaa1115910.biliapi.http.entity.index.indexFilterIsFinish -import dev.aaa1115910.biliapi.http.entity.index.indexFilterSeasonMonth -import dev.aaa1115910.biliapi.http.entity.index.indexFilterSeasonStatus -import dev.aaa1115910.biliapi.http.entity.index.indexFilterSeasonVersion -import dev.aaa1115910.biliapi.http.entity.index.indexFilterSpokenLanguageType -import dev.aaa1115910.biliapi.http.entity.index.indexFilterStyleIdsAnime -import dev.aaa1115910.biliapi.http.entity.index.indexFilterYear -import dev.aaa1115910.bv.R -import dev.aaa1115910.bv.component.createCustomInitialFocusRestorerModifiers -import dev.aaa1115910.bv.component.ifElse -import dev.aaa1115910.bv.ui.theme.BVTheme - -@Composable -fun AnimeIndexFilter( - modifier: Modifier = Modifier, - show: Boolean, - onDismissRequest: () -> Unit, - order: IndexOrder, - seasonVersion: Int, - spokenLanguageType: Int, - area: Int, - isFinish: Int, - copyright: Int, - seasonStatus: Int, - seasonMonth: Int, - year: String, - styleId: Int, - desc: Boolean, - onOrderChange: (IndexOrder) -> Unit, - onSeasonVersionChange: (Int) -> Unit, - onSpokenLanguageTypeChange: (Int) -> Unit, - onAreaChange: (Int) -> Unit, - onIsFinishChange: (Int) -> Unit, - onCopyrightChange: (Int) -> Unit, - onSeasonStatusChange: (Int) -> Unit, - onSeasonMonthChange: (Int) -> Unit, - onYearChange: (String) -> Unit, - onStyleIdChange: (Int) -> Unit, - onDescChange: (Boolean) -> Unit, - // TODO 重置筛选条件 - onReset: () -> Unit = {} -) { - val context = LocalContext.current - val focusRequester = remember { FocusRequester() } - - LaunchedEffect(show) { - runCatching { - if (show) focusRequester.requestFocus() - } - } - - if (show) { - AlertDialog( - modifier = modifier - .fillMaxWidth(0.8f), - onDismissRequest = onDismissRequest, - confirmButton = { }, - title = { - Text(text = "番剧索引筛选") - }, - text = { - Column( - modifier = Modifier - .heightIn(max = 300.dp) - ) { - IndexFilterChipRow( - modifier = Modifier.focusRequester(focusRequester), - title = "排序方式", - filter = animeIndexOrders.associateWith { it.getDisplayName(context) }, - selectedFilterId = order, - onFilterIdChange = onOrderChange - ) - IndexFilterChipRow( - title = "排序顺序", - filter = mapOf(true to "降序", false to "升序"), - selectedFilterId = desc, - onFilterIdChange = onDescChange - ) - HorizontalDivider( - modifier = Modifier.padding(vertical = 8.dp) - ) - LazyColumn { - item { - IndexFilterChipRow( - title = "类型", - filter = indexFilterSeasonVersion, - selectedFilterId = seasonVersion, - onFilterIdChange = onSeasonVersionChange - ) - } - item { - IndexFilterChipRow( - title = "配音", - filter = indexFilterSpokenLanguageType, - selectedFilterId = spokenLanguageType, - onFilterIdChange = onSpokenLanguageTypeChange - ) - } - item { - IndexFilterChipRow( - title = "地区", - filter = indexFilterArea, - selectedFilterId = area, - onFilterIdChange = onAreaChange - ) - } - item { - IndexFilterChipRow( - title = "状态", - filter = indexFilterIsFinish, - selectedFilterId = isFinish, - onFilterIdChange = onIsFinishChange - ) - } - item { - IndexFilterChipRow( - title = "版权", - filter = indexFilterCopyright, - selectedFilterId = copyright, - onFilterIdChange = onCopyrightChange - ) - } - item { - IndexFilterChipRow( - title = "付费", - filter = indexFilterSeasonStatus, - selectedFilterId = seasonStatus, - onFilterIdChange = onSeasonStatusChange - ) - } - item { - IndexFilterChipRow( - title = "季度", - filter = indexFilterSeasonMonth, - selectedFilterId = seasonMonth, - onFilterIdChange = onSeasonMonthChange - ) - } - item { - IndexFilterChipRow( - title = "年份", - filter = indexFilterYear, - selectedFilterId = year, - onFilterIdChange = onYearChange - ) - } - item { - IndexFilterChipRow( - title = "风格", - filter = indexFilterStyleIdsAnime, - selectedFilterId = styleId, - onFilterIdChange = onStyleIdChange - ) - } - } - } - }, - properties = DialogProperties(usePlatformDefaultWidth = false) - - ) - } -} - -@Preview(device = "id:tv_1080p") -@Composable -private fun AnimeIndexFilterPreview() { - var order by remember { mutableStateOf(IndexOrder.PlayCount) } - var seasonVersion by remember { mutableStateOf(-1) } - var spokenLanguageType by remember { mutableStateOf(-1) } - var area by remember { mutableStateOf(-1) } - var isFinish by remember { mutableStateOf(-1) } - var copyright by remember { mutableStateOf(-1) } - var seasonStatus by remember { mutableStateOf(-1) } - var seasonMonth by remember { mutableStateOf(-1) } - var year by remember { mutableStateOf("-1") } - var styleId by remember { mutableStateOf(-1) } - var desc by remember { mutableStateOf(true) } - - BVTheme { - Surface( - modifier = Modifier.fillMaxSize() - ) { - AnimeIndexFilter( - show = true, - onDismissRequest = { }, - order = order, - seasonVersion = seasonVersion, - spokenLanguageType = spokenLanguageType, - area = area, - isFinish = isFinish, - copyright = copyright, - seasonStatus = seasonStatus, - seasonMonth = seasonMonth, - year = year, - styleId = styleId, - desc = desc, - onOrderChange = { order = it }, - onSeasonVersionChange = { seasonVersion = it }, - onSpokenLanguageTypeChange = { spokenLanguageType = it }, - onAreaChange = { area = it }, - onIsFinishChange = { isFinish = it }, - onCopyrightChange = { copyright = it }, - onSeasonStatusChange = { seasonStatus = it }, - onSeasonMonthChange = { seasonMonth = it }, - onYearChange = { year = it }, - onStyleIdChange = { styleId = it }, - onDescChange = { desc = it }, - onReset = { } - ) - } - } -} - -@OptIn(ExperimentalTvMaterial3Api::class) -@Composable -private fun IndexFilterChip( - modifier: Modifier = Modifier, - selected: Boolean, - onClick: () -> Unit, - label: String -) { - FilterChip( - modifier = modifier, - selected = selected, - onClick = onClick - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), - verticalAlignment = Alignment.CenterVertically - ) { - AnimatedVisibility(visible = selected) { - Icon( - modifier = Modifier.size(20.dp), - imageVector = Icons.Default.Check, - contentDescription = null - ) - } - Text(text = label) - } - } -} - -@Composable -fun IndexFilterChipRow( - modifier: Modifier = Modifier, - title: String, - filter: Map, - selectedFilterId: T, - onFilterIdChange: (T) -> Unit -) { - val focusRestorerModifiers = createCustomInitialFocusRestorerModifiers() - - Row( - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = title, - style = MaterialTheme.typography.labelLarge - ) - LazyRow( - modifier = modifier - .then(focusRestorerModifiers.parentModifier), - horizontalArrangement = Arrangement.spacedBy(8.dp), - contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp) - ) { - items(items = filter.entries.toList()) { (id, label) -> - IndexFilterChip( - modifier = Modifier - .ifElse(selectedFilterId == id, focusRestorerModifiers.childModifier), - selected = selectedFilterId == id, - onClick = { onFilterIdChange(id) }, - label = label - ) - } - } - } -} - -fun IndexOrder.getDisplayName(context: Context) = when (this) { - IndexOrder.UpdateTime -> context.getString(R.string.index_order_update_time) - IndexOrder.DanmakuCount -> context.getString(R.string.index_order_danmaku_count) - IndexOrder.PlayCount -> context.getString(R.string.index_order_play_count) - IndexOrder.FollowCount -> context.getString(R.string.index_order_follow_count) - IndexOrder.Score -> context.getString(R.string.index_order_score) - IndexOrder.StartTime -> context.getString(R.string.index_order_start_time) - IndexOrder.PublishTime -> context.getString(R.string.index_order_publish_time) -} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/pgc/IndexFilter.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/pgc/IndexFilter.kt new file mode 100644 index 00000000..ef7bd2e4 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/pgc/IndexFilter.kt @@ -0,0 +1,367 @@ +package dev.aaa1115910.bv.component.pgc + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Check +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +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.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.DialogProperties +import androidx.tv.material3.ExperimentalTvMaterial3Api +import androidx.tv.material3.FilterChip +import androidx.tv.material3.MaterialTheme +import androidx.tv.material3.Surface +import androidx.tv.material3.Text +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.biliapi.entity.pgc.index.Area +import dev.aaa1115910.biliapi.entity.pgc.index.Copyright +import dev.aaa1115910.biliapi.entity.pgc.index.IndexOrder +import dev.aaa1115910.biliapi.entity.pgc.index.IndexOrderType +import dev.aaa1115910.biliapi.entity.pgc.index.IsFinish +import dev.aaa1115910.biliapi.entity.pgc.index.PgcIndexParam +import dev.aaa1115910.biliapi.entity.pgc.index.Producer +import dev.aaa1115910.biliapi.entity.pgc.index.ReleaseDate +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonMonth +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonStatus +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonVersion +import dev.aaa1115910.biliapi.entity.pgc.index.SpokenLanguage +import dev.aaa1115910.biliapi.entity.pgc.index.Style +import dev.aaa1115910.biliapi.entity.pgc.index.Year +import dev.aaa1115910.bv.R +import dev.aaa1115910.bv.component.createCustomInitialFocusRestorerModifiers +import dev.aaa1115910.bv.component.ifElse +import dev.aaa1115910.bv.ui.theme.BVTheme +import dev.aaa1115910.bv.util.getDisplayName + +@Composable +fun IndexFilter( + modifier: Modifier = Modifier, + type: PgcType, + show: Boolean, + onDismissRequest: () -> Unit, + order: IndexOrder, + orderType: IndexOrderType, + seasonVersion: SeasonVersion, + spokenLanguage: SpokenLanguage, + area: Area, + isFinish: IsFinish, + copyright: Copyright, + seasonStatus: SeasonStatus, + seasonMonth: SeasonMonth, + producer: Producer, + year: Year, + releaseDate: ReleaseDate, + style: Style, + onOrderChange: (IndexOrder) -> Unit, + onOrderTypeChange: (IndexOrderType) -> Unit, + onSeasonVersionChange: (SeasonVersion) -> Unit, + onSpokenLanguageChange: (SpokenLanguage) -> Unit, + onAreaChange: (Area) -> Unit, + onIsFinishChange: (IsFinish) -> Unit, + onCopyrightChange: (Copyright) -> Unit, + onSeasonStatusChange: (SeasonStatus) -> Unit, + onSeasonMonthChange: (SeasonMonth) -> Unit, + onProducerChange: (Producer) -> Unit, + onYearChange: (Year) -> Unit, + onReleaseDateChange: (ReleaseDate) -> Unit, + onStyleChange: (Style) -> Unit +) { + val context = LocalContext.current + + IndexFilterContent( + modifier = modifier, + title = stringResource(R.string.pgc_index_filter_title_prefix) + type.getDisplayName(context), + show = show, + onDismissRequest = onDismissRequest, + content = { + IndexFilterChipRow( + title = stringResource(R.string.pgc_index_filter_order), + filters = IndexOrder.getList(type), + selectedFilter = order, + onFilterChange = onOrderChange + ) + IndexFilterChipRow( + title = stringResource(R.string.pgc_index_filter_order_type), + filters = IndexOrderType.entries, + selectedFilter = orderType, + onFilterChange = onOrderTypeChange + ) + HorizontalDivider( + modifier = Modifier.padding(vertical = 8.dp) + ) + LazyColumn { + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_season_version), + filters = SeasonVersion.getList(type), + selectedFilter = seasonVersion, + onFilterChange = onSeasonVersionChange + ) + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_spoken_language), + filters = SpokenLanguage.getList(type), + selectedFilter = spokenLanguage, + onFilterChange = onSpokenLanguageChange + ) + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_is_finish), + filters = IsFinish.getList(type), + selectedFilter = isFinish, + onFilterChange = onIsFinishChange + ) + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_season_status), + filters = SeasonStatus.getList(type), + selectedFilter = seasonStatus, + onFilterChange = onSeasonStatusChange + ) + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_area), + filters = Area.getList(type), + selectedFilter = area, + onFilterChange = onAreaChange + ) + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_copyright), + filters = Copyright.getList(type), + selectedFilter = copyright, + onFilterChange = onCopyrightChange + ) + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_season_month), + filters = SeasonMonth.getList(type), + selectedFilter = seasonMonth, + onFilterChange = onSeasonMonthChange + ) + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_producer), + filters = Producer.getList(type), + selectedFilter = producer, + onFilterChange = onProducerChange + ) + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_year), + filters = Year.getList(type), + selectedFilter = year, + onFilterChange = onYearChange + ) + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_release_date), + filters = ReleaseDate.getList(type), + selectedFilter = releaseDate, + onFilterChange = onReleaseDateChange + ) + indexFilterChipRow( + title = context.getString(R.string.pgc_index_filter_style), + filters = Style.getList(type), + selectedFilter = style, + onFilterChange = onStyleChange + ) + } + } + ) +} + +@Composable +private fun IndexFilterContent( + modifier: Modifier = Modifier, + title: String, + show: Boolean, + onDismissRequest: () -> Unit, + content: @Composable ColumnScope.() -> Unit +) { + if (show) { + AlertDialog( + modifier = modifier + .fillMaxWidth(0.8f), + onDismissRequest = onDismissRequest, + confirmButton = { }, + title = { + Text(text = title) + }, + text = { + Column( + modifier = Modifier + .heightIn(max = 300.dp) + ) { + content() + } + }, + properties = DialogProperties(usePlatformDefaultWidth = false) + ) + } +} + +@OptIn(ExperimentalTvMaterial3Api::class) +@Composable +private fun IndexFilterChip( + modifier: Modifier = Modifier, + selected: Boolean, + onClick: () -> Unit, + label: String +) { + FilterChip( + modifier = modifier, + selected = selected, + onClick = onClick + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AnimatedVisibility(visible = selected) { + Icon( + modifier = Modifier.size(20.dp), + imageVector = Icons.Default.Check, + contentDescription = null + ) + } + Text(text = label) + } + } +} + +private fun LazyListScope.indexFilterChipRow( + modifier: Modifier = Modifier, + title: String, + filters: List, + selectedFilter: T, + onFilterChange: (T) -> Unit +) { + if (filters.isEmpty()) return + item { + IndexFilterChipRow( + modifier = modifier, + title = title, + filters = filters, + selectedFilter = selectedFilter, + onFilterChange = onFilterChange + ) + } +} + +@Composable +private fun IndexFilterChipRow( + modifier: Modifier = Modifier, + title: String, + filters: List, + selectedFilter: T, + onFilterChange: (T) -> Unit +) { + val context = LocalContext.current + val focusRestorerModifiers = createCustomInitialFocusRestorerModifiers() + + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = title, + style = MaterialTheme.typography.labelLarge + ) + LazyRow( + modifier = modifier + .then(focusRestorerModifiers.parentModifier), + horizontalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(horizontal = 8.dp, vertical = 4.dp) + ) { + items(items = filters) { filter -> + IndexFilterChip( + modifier = Modifier + .ifElse(selectedFilter == filter, focusRestorerModifiers.childModifier), + selected = selectedFilter == filter, + onClick = { onFilterChange(filter) }, + label = (filter as PgcIndexParam).getDisplayName(context) + ) + } + } + } +} + +private class PgcTypeProvider : PreviewParameterProvider { + override val values = PgcType.entries.asSequence() +} + +@Preview(device = "id:tv_1080p") +@Composable +private fun IndexFilterPreview( + @PreviewParameter(PgcTypeProvider::class) pgcType: PgcType +) { + var order by remember { mutableStateOf(IndexOrder.PlayCount) } + var orderType by remember { mutableStateOf(IndexOrderType.Desc) } + var seasonVersion by remember { mutableStateOf(SeasonVersion.All) } + var spokenLanguage by remember { mutableStateOf(SpokenLanguage.All) } + var area by remember { mutableStateOf(Area.All) } + var isFinish by remember { mutableStateOf(IsFinish.All) } + var copyright by remember { mutableStateOf(Copyright.All) } + var seasonStatus by remember { mutableStateOf(SeasonStatus.All) } + var seasonMonth by remember { mutableStateOf(SeasonMonth.All) } + var producer by remember { mutableStateOf(Producer.All) } + var year by remember { mutableStateOf(Year.All) } + var releaseDate by remember { mutableStateOf(ReleaseDate.All) } + var style by remember { mutableStateOf(Style.All) } + + BVTheme { + Surface( + modifier = Modifier.fillMaxSize() + ) { + IndexFilter( + type = pgcType, + show = true, + onDismissRequest = { }, + order = order, + orderType = orderType, + seasonVersion = seasonVersion, + spokenLanguage = spokenLanguage, + area = area, + isFinish = isFinish, + copyright = copyright, + seasonStatus = seasonStatus, + seasonMonth = seasonMonth, + producer = producer, + year = year, + releaseDate = releaseDate, + style = style, + onOrderChange = { order = it }, + onOrderTypeChange = { orderType = it }, + onSeasonVersionChange = { seasonVersion = it }, + onSpokenLanguageChange = { spokenLanguage = it }, + onAreaChange = { area = it }, + onIsFinishChange = { isFinish = it }, + onCopyrightChange = { copyright = it }, + onSeasonStatusChange = { seasonStatus = it }, + onSeasonMonthChange = { seasonMonth = it }, + onProducerChange = { producer = it }, + onYearChange = { year = it }, + onReleaseDateChange = { releaseDate = it }, + onStyleChange = { style = it } + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/entity/carddata/SeasonCardData.kt b/app/src/main/kotlin/dev/aaa1115910/bv/entity/carddata/SeasonCardData.kt index 218b8c9c..a264d212 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/entity/carddata/SeasonCardData.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/entity/carddata/SeasonCardData.kt @@ -1,6 +1,8 @@ package dev.aaa1115910.bv.entity.carddata import dev.aaa1115910.biliapi.http.entity.search.SearchMediaResult +import dev.aaa1115910.bv.util.ImageSize +import dev.aaa1115910.bv.util.resizedImageUrl data class SeasonCardData( val seasonId: Int, @@ -9,4 +11,17 @@ data class SeasonCardData( val cover: String, val rating: String? = null, val badge: SearchMediaResult.Badge? = null, -) +) { + companion object { + fun fromPgcItem(pgcItem: dev.aaa1115910.biliapi.entity.pgc.PgcItem): SeasonCardData { + return SeasonCardData( + seasonId = pgcItem.seasonId, + title = pgcItem.title, + subTitle = pgcItem.subTitle, + cover = pgcItem.cover.resizedImageUrl(ImageSize.SeasonCoverThumbnail), + rating = pgcItem.rating, + badge = null + ) + } + } +} diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt index 148b535e..f7524805 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/AnimeContent.kt @@ -1,42 +1,23 @@ package dev.aaa1115910.bv.screen.main.pgc import android.content.Intent -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.rounded.List import androidx.compose.material.icons.rounded.Alarm import androidx.compose.material.icons.rounded.Favorite import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.tv.material3.ClickableSurfaceDefaults -import androidx.tv.material3.Icon -import androidx.tv.material3.MaterialTheme -import androidx.tv.material3.Surface -import androidx.tv.material3.Text -import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.biliapi.entity.pgc.PgcType import dev.aaa1115910.bv.R -import dev.aaa1115910.bv.activities.anime.AnimeIndexActivity -import dev.aaa1115910.bv.activities.anime.AnimeTimelineActivity +import dev.aaa1115910.bv.activities.pgc.PgcIndexActivity +import dev.aaa1115910.bv.activities.pgc.anime.AnimeTimelineActivity import dev.aaa1115910.bv.activities.user.FollowingSeasonActivity import dev.aaa1115910.bv.ui.theme.BVTheme import dev.aaa1115910.bv.util.toast @@ -58,7 +39,7 @@ fun AnimeContent( context.startActivity(Intent(context, FollowingSeasonActivity::class.java)) } val onOpenIndex: () -> Unit = { - context.startActivity(Intent(context, AnimeIndexActivity::class.java)) + PgcIndexActivity.actionStart(context = context, pgcType = PgcType.Anime) } val onOpenGamerAni: () -> Unit = { val packageManager = context.packageManager @@ -94,7 +75,6 @@ private fun AnimeFeatureButtons( onOpenIndex: () -> Unit, onOpenGamerAni: () -> Unit = {} ) { - val buttonWidth = 185.dp val buttons = listOf( Triple( stringResource(R.string.anime_home_button_timeline), @@ -117,115 +97,15 @@ private fun AnimeFeatureButtons( onOpenGamerAni ) ) - - LazyRow( - modifier = modifier - .fillMaxWidth() - .height(80.dp), - horizontalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterHorizontally), - contentPadding = PaddingValues(horizontal = 32.dp) - ) { - items(items = buttons) { (title, icon, onClick) -> - when (icon) { - is ImageVector -> AnimeFeatureButton( - modifier = Modifier.width(buttonWidth), - title = title, - icon = icon, - onClick = onClick - ) - - is Painter -> AnimeFeatureButton( - modifier = Modifier.width(buttonWidth), - title = title, - icon = icon, - onClick = onClick - ) - - else -> {} - } - } - } -} - -@Composable -fun AnimeFeatureButton( - modifier: Modifier = Modifier, - title: String, - icon: ImageVector, - onClick: () -> Unit -) { - Surface( + PgcFeatureButtons( modifier = modifier, - colors = ClickableSurfaceDefaults.colors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, - focusedContainerColor = MaterialTheme.colorScheme.inverseSurface, - pressedContainerColor = MaterialTheme.colorScheme.inverseSurface - ), - shape = ClickableSurfaceDefaults.shape(shape = MaterialTheme.shapes.large), - onClick = onClick - ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon(imageVector = icon, contentDescription = null) - Text( - text = title, - style = MaterialTheme.typography.titleLarge - ) - } - } - } -} - -@Composable -fun AnimeFeatureButton( - modifier: Modifier = Modifier, - title: String, - icon: Painter, - onClick: () -> Unit -) { - Surface( - modifier = modifier, - colors = ClickableSurfaceDefaults.colors( - containerColor = MaterialTheme.colorScheme.surfaceVariant, - focusedContainerColor = MaterialTheme.colorScheme.inverseSurface, - pressedContainerColor = MaterialTheme.colorScheme.inverseSurface - ), - shape = ClickableSurfaceDefaults.shape(shape = MaterialTheme.shapes.large), - onClick = onClick - ) { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - ) { - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(8.dp) - ) { - Icon( - modifier = Modifier.size(24.dp), - painter = icon, - contentDescription = null - ) - Text( - text = title, - style = MaterialTheme.typography.titleLarge - ) - } - } - } + buttons = buttons + ) } - - @Preview(device = "id:tv_1080p") @Composable -fun AnimeFeatureButtonsPreview() { +private fun AnimeFeatureButtonsPreview() { BVTheme { AnimeFeatureButtons( modifier = Modifier, diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/DocumentaryContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/DocumentaryContent.kt index 0d55365b..83b813e7 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/DocumentaryContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/DocumentaryContent.kt @@ -1,9 +1,20 @@ package dev.aaa1115910.bv.screen.main.pgc +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.List +import androidx.compose.material.icons.rounded.QuestionMark import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.aaa1115910.biliapi.repositories.PgcType +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.bv.R +import dev.aaa1115910.bv.activities.pgc.PgcIndexActivity +import dev.aaa1115910.bv.ui.theme.BVTheme import dev.aaa1115910.bv.viewmodel.pgc.PgcDocumentaryViewModel import org.koin.androidx.compose.koinViewModel @@ -13,9 +24,65 @@ fun DocumentaryContent( lazyListState: LazyListState, pgcViewModel: PgcDocumentaryViewModel = koinViewModel() ) { + val context = LocalContext.current + + val onOpenIndex: () -> Unit = { + PgcIndexActivity.actionStart(context = context, pgcType = PgcType.Documentary) + } + PgcScaffold( lazyListState = lazyListState, pgcViewModel = pgcViewModel, - pgcType = PgcType.Documentary + pgcType = PgcType.Documentary, + featureButtons = { + DocumentaryFeatureButtons( + modifier = Modifier.padding(vertical = 24.dp), + onOpenIndex = onOpenIndex + ) + } + ) +} + +@Composable +private fun DocumentaryFeatureButtons( + modifier: Modifier = Modifier, + onOpenIndex: () -> Unit +) { + val buttons = listOf( + Triple( + stringResource(R.string.anime_home_button_index), + Icons.AutoMirrored.Rounded.List, + onOpenIndex + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ) + ) + PgcFeatureButtons( + modifier = modifier, + buttons = buttons ) -} \ No newline at end of file +} + +@Preview(device = "id:tv_1080p") +@Composable +private fun DocumentaryFeatureButtonsPreview() { + BVTheme { + DocumentaryFeatureButtons( + modifier = Modifier, + onOpenIndex = {}, + ) + } +} diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/GuoChuangContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/GuoChuangContent.kt index ef1f259b..343e0048 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/GuoChuangContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/GuoChuangContent.kt @@ -1,9 +1,20 @@ package dev.aaa1115910.bv.screen.main.pgc +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.List +import androidx.compose.material.icons.rounded.QuestionMark import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.aaa1115910.biliapi.repositories.PgcType +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.bv.R +import dev.aaa1115910.bv.activities.pgc.PgcIndexActivity +import dev.aaa1115910.bv.ui.theme.BVTheme import dev.aaa1115910.bv.viewmodel.pgc.PgcGuoChuangViewModel import org.koin.androidx.compose.koinViewModel @@ -13,9 +24,65 @@ fun GuoChuangContent( lazyListState: LazyListState, pgcViewModel: PgcGuoChuangViewModel = koinViewModel() ) { + val context= LocalContext.current + + val onOpenIndex: () -> Unit = { + PgcIndexActivity.actionStart(context = context, pgcType = PgcType.GuoChuang) + } + PgcScaffold( lazyListState = lazyListState, pgcViewModel = pgcViewModel, - pgcType = PgcType.GuoChuang + pgcType = PgcType.GuoChuang, + featureButtons = { + GuoChuangFeatureButtons( + modifier = Modifier.padding(vertical = 24.dp), + onOpenIndex = onOpenIndex + ) + } + ) +} + +@Composable +private fun GuoChuangFeatureButtons( + modifier: Modifier = Modifier, + onOpenIndex: () -> Unit +) { + val buttons = listOf( + Triple( + stringResource(R.string.anime_home_button_index), + Icons.AutoMirrored.Rounded.List, + onOpenIndex + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ) + ) + PgcFeatureButtons( + modifier = modifier, + buttons = buttons ) -} \ No newline at end of file +} + +@Preview(device = "id:tv_1080p") +@Composable +private fun GuoChuangFeatureButtonsPreview() { + BVTheme { + GuoChuangFeatureButtons( + modifier = Modifier, + onOpenIndex = {}, + ) + } +} diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/MovieContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/MovieContent.kt index 9c8a4913..5519ce34 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/MovieContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/MovieContent.kt @@ -1,9 +1,20 @@ package dev.aaa1115910.bv.screen.main.pgc +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.List +import androidx.compose.material.icons.rounded.QuestionMark import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.aaa1115910.biliapi.repositories.PgcType +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.bv.R +import dev.aaa1115910.bv.activities.pgc.PgcIndexActivity +import dev.aaa1115910.bv.ui.theme.BVTheme import dev.aaa1115910.bv.viewmodel.pgc.PgcMovieViewModel import org.koin.androidx.compose.koinViewModel @@ -13,9 +24,65 @@ fun MovieContent( lazyListState: LazyListState, pgcViewModel: PgcMovieViewModel = koinViewModel() ) { + val context = LocalContext.current + + val onOpenIndex: () -> Unit = { + PgcIndexActivity.actionStart(context = context, pgcType = PgcType.Movie) + } + PgcScaffold( lazyListState = lazyListState, pgcViewModel = pgcViewModel, - pgcType = PgcType.Movie + pgcType = PgcType.Movie, + featureButtons = { + MovieFeatureButtons( + modifier = Modifier.padding(vertical = 24.dp), + onOpenIndex = onOpenIndex + ) + } + ) +} + +@Composable +private fun MovieFeatureButtons( + modifier: Modifier = Modifier, + onOpenIndex: () -> Unit +) { + val buttons = listOf( + Triple( + stringResource(R.string.anime_home_button_index), + Icons.AutoMirrored.Rounded.List, + onOpenIndex + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ) + ) + PgcFeatureButtons( + modifier = modifier, + buttons = buttons ) -} \ No newline at end of file +} + +@Preview(device = "id:tv_1080p") +@Composable +private fun MovieFeatureButtonsPreview() { + BVTheme { + MovieFeatureButtons( + modifier = Modifier, + onOpenIndex = {}, + ) + } +} 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 96c075e8..9b2d689f 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 @@ -16,10 +16,12 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.rememberScrollState import androidx.compose.runtime.Composable @@ -34,6 +36,8 @@ import androidx.compose.ui.graphics.BlendMode import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.key.Key import androidx.compose.ui.input.key.key import androidx.compose.ui.input.key.onPreviewKeyEvent @@ -41,12 +45,17 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.tv.material3.ClickableSurfaceDefaults +import androidx.tv.material3.Icon import androidx.tv.material3.MaterialTheme +import androidx.tv.material3.Surface import androidx.tv.material3.Text import coil.compose.AsyncImage import dev.aaa1115910.biliapi.entity.pgc.PgcFeedData +import dev.aaa1115910.biliapi.entity.pgc.PgcItem +import dev.aaa1115910.biliapi.entity.pgc.PgcType import dev.aaa1115910.biliapi.http.SeasonIndexType -import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.bv.BVApp import dev.aaa1115910.bv.activities.video.SeasonInfoActivity import dev.aaa1115910.bv.component.pgc.PgcCarousel import dev.aaa1115910.bv.component.videocard.SeasonCard @@ -55,6 +64,7 @@ import dev.aaa1115910.bv.entity.proxy.ProxyArea import dev.aaa1115910.bv.ui.theme.BVTheme import dev.aaa1115910.bv.util.ImageSize import dev.aaa1115910.bv.util.resizedImageUrl +import dev.aaa1115910.bv.util.toast import dev.aaa1115910.bv.viewmodel.pgc.FeedListType import dev.aaa1115910.bv.viewmodel.pgc.PgcViewModel @@ -95,10 +105,10 @@ fun PgcScaffold( item { featureButtons() } - }else{ + } else { item { Spacer( - modifier=Modifier + modifier = Modifier .fillMaxWidth() .height(24.dp) ) @@ -135,7 +145,7 @@ fun PgcScaffold( @Composable fun PgcFeedVideoRow( modifier: Modifier = Modifier, - data: List + data: List ) { val context = LocalContext.current LazyRow( @@ -305,7 +315,7 @@ fun PgcFeedRankRowPreview() { title = "热门热血番剧榜", subTitle = "每小时更新", items = List(8) { - PgcFeedData.FeedItem( + PgcItem( cover = "https://i0.hdslb.com/bfs/bangumi/image/f610305ad3922bee9d51748ab38da0c54e785b44.png", title = "解雇后走上人生巅峰", subTitle = "被解雇的暗黑士兵慢生活的第二人生", @@ -319,4 +329,119 @@ fun PgcFeedRankRowPreview() { BVTheme { PgcFeedRankRow(data = data) } +} + +@Composable +fun PgcFeatureButtons( + modifier: Modifier = Modifier, + buttons: List Unit>> +) { + val buttonWidth = 185.dp + + LazyRow( + modifier = modifier + .fillMaxWidth() + .height(80.dp), + horizontalArrangement = Arrangement.spacedBy(24.dp, Alignment.CenterHorizontally), + contentPadding = PaddingValues(horizontal = 32.dp) + ) { + items(items = buttons) { (title, icon, onClick) -> + when (icon) { + is ImageVector -> PgcFeatureButton( + modifier = Modifier.width(buttonWidth), + title = title, + icon = icon, + onClick = { onClick.invoke() } + ) + + is Painter -> PgcFeatureButton( + modifier = Modifier.width(buttonWidth), + title = title, + icon = icon, + onClick = { onClick.invoke() } + ) + + else -> {} + } + } + } +} + + +@Composable +fun PgcFeatureButton( + modifier: Modifier = Modifier, + title: String, + icon: ImageVector, + onClick: () -> Unit +) { + Surface( + modifier = modifier, + colors = ClickableSurfaceDefaults.colors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + focusedContainerColor = MaterialTheme.colorScheme.inverseSurface, + pressedContainerColor = MaterialTheme.colorScheme.inverseSurface + ), + shape = ClickableSurfaceDefaults.shape(shape = MaterialTheme.shapes.large), + onClick = onClick + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon(imageVector = icon, contentDescription = null) + Text( + text = title, + style = MaterialTheme.typography.titleLarge + ) + } + } + } +} + +@Composable +fun PgcFeatureButton( + modifier: Modifier = Modifier, + title: String, + icon: Painter, + onClick: () -> Unit +) { + Surface( + modifier = modifier, + colors = ClickableSurfaceDefaults.colors( + containerColor = MaterialTheme.colorScheme.surfaceVariant, + focusedContainerColor = MaterialTheme.colorScheme.inverseSurface, + pressedContainerColor = MaterialTheme.colorScheme.inverseSurface + ), + shape = ClickableSurfaceDefaults.shape(shape = MaterialTheme.shapes.large), + onClick = onClick + ) { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + Icon( + modifier = Modifier.size(24.dp), + painter = icon, + contentDescription = null + ) + Text( + text = title, + style = MaterialTheme.typography.titleLarge + ) + } + } + } +} + +val showPlaceholderToast: () -> Unit = { + "都说了介个是占位按钮了".toast(BVApp.context) } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/anime/AnimeIndexScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/PgcIndexScreen.kt similarity index 58% rename from app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/anime/AnimeIndexScreen.kt rename to app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/PgcIndexScreen.kt index a6ee441f..9763d1e8 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/anime/AnimeIndexScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/PgcIndexScreen.kt @@ -1,5 +1,6 @@ -package dev.aaa1115910.bv.screen.main.pgc.anime +package dev.aaa1115910.bv.screen.main.pgc +import android.app.Activity import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box @@ -32,25 +33,25 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.tv.material3.OutlinedButton import androidx.tv.material3.Text -import dev.aaa1115910.biliapi.http.entity.search.SearchMediaResult +import dev.aaa1115910.biliapi.entity.pgc.PgcType import dev.aaa1115910.bv.R import dev.aaa1115910.bv.activities.video.SeasonInfoActivity -import dev.aaa1115910.bv.component.index.AnimeIndexFilter +import dev.aaa1115910.bv.component.pgc.IndexFilter import dev.aaa1115910.bv.component.videocard.SeasonCard import dev.aaa1115910.bv.entity.carddata.SeasonCardData import dev.aaa1115910.bv.entity.proxy.ProxyArea -import dev.aaa1115910.bv.util.ImageSize -import dev.aaa1115910.bv.util.resizedImageUrl -import dev.aaa1115910.bv.viewmodel.index.AnimeIndexViewModel +import dev.aaa1115910.bv.util.fInfo +import dev.aaa1115910.bv.util.getDisplayName +import dev.aaa1115910.bv.viewmodel.index.PgcIndexViewModel import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel @Composable -fun AnimeIndexScreen( +fun PgcIndexScreen( modifier: Modifier = Modifier, - animeIndexViewModel: AnimeIndexViewModel = koinViewModel() + pgcIndexViewModel: PgcIndexViewModel = koinViewModel() ) { val context = LocalContext.current val scope = rememberCoroutineScope() @@ -67,8 +68,8 @@ fun AnimeIndexScreen( label = "title font size" ) - val indexResultItems = animeIndexViewModel.indexResultItems - val noMore = animeIndexViewModel.noMore + val pgcItems = pgcIndexViewModel.indexResultItems + val noMore = pgcIndexViewModel.noMore var showFilter by remember { mutableStateOf(false) } val onLongClickSeason = { @@ -77,27 +78,37 @@ fun AnimeIndexScreen( val reloadData = { scope.launch(Dispatchers.IO) { - animeIndexViewModel.clearData() - animeIndexViewModel.loadMore() + pgcIndexViewModel.clearData() + pgcIndexViewModel.loadMore() } } LaunchedEffect(Unit) { + val intent = (context as Activity).intent + val pgcType = runCatching { + PgcType.entries[intent.getIntExtra("pgcType", 0)] + }.onFailure { + logger.warn { "get pgcType from intent failed: ${it.stackTraceToString()}" } + }.getOrDefault(PgcType.Anime) + logger.fInfo { "index pgcType: $pgcType" } + pgcIndexViewModel.changePgcType(pgcType) reloadData() } LaunchedEffect( - animeIndexViewModel.order, - animeIndexViewModel.seasonVersion, - animeIndexViewModel.spokenLanguageType, - animeIndexViewModel.area, - animeIndexViewModel.isFinish, - animeIndexViewModel.copyright, - animeIndexViewModel.seasonStatus, - animeIndexViewModel.seasonMonth, - animeIndexViewModel.year, - animeIndexViewModel.styleId, - animeIndexViewModel.desc + pgcIndexViewModel.indexOrder, + pgcIndexViewModel.indexOrderType, + pgcIndexViewModel.seasonVersion, + pgcIndexViewModel.spokenLanguage, + pgcIndexViewModel.area, + pgcIndexViewModel.isFinish, + pgcIndexViewModel.copyright, + pgcIndexViewModel.seasonStatus, + pgcIndexViewModel.seasonMonth, + pgcIndexViewModel.producer, + pgcIndexViewModel.year, + pgcIndexViewModel.releaseDate, + pgcIndexViewModel.style, ) { reloadData() } @@ -114,7 +125,8 @@ fun AnimeIndexScreen( horizontalArrangement = Arrangement.SpaceBetween ) { Text( - text = stringResource(id = R.string.title_activity_anime_index), + text = stringResource(id = R.string.title_activity_pgc_index) + + " - " + pgcIndexViewModel.pgcType.getDisplayName(context), fontSize = titleFontSize.sp, ) Text( @@ -122,7 +134,6 @@ fun AnimeIndexScreen( color = Color.White.copy(alpha = 0.6f) ) } - } } ) { innerPadding -> @@ -133,43 +144,27 @@ fun AnimeIndexScreen( verticalArrangement = Arrangement.spacedBy(24.dp), horizontalArrangement = Arrangement.spacedBy(24.dp) ) { - itemsIndexed(items = indexResultItems) { index, indexResultItem -> + itemsIndexed(items = pgcItems) { index, pgcItem -> SeasonCard( - data = SeasonCardData( - seasonId = indexResultItem.seasonId, - title = indexResultItem.title, - cover = indexResultItem.cover.resizedImageUrl(ImageSize.SeasonCoverThumbnail), - rating = indexResultItem.score.takeIf { it.isNotEmpty() }, - // TODO 新增一个通用的 Badge - badge = SearchMediaResult.Badge( - text = indexResultItem.badge?.text ?: "", - textColor = "", - textColorNight = "", - bgColor = indexResultItem.badge?.bgColor ?: "", - bgColorNight = indexResultItem.badge?.bgColorNight ?: "", - borderColor = "", - borderColorNight = "", - bgStyle = 0 - ).takeIf { indexResultItem.badge != null }, - ), + data = SeasonCardData.fromPgcItem(pgcItem), onFocus = { currentSeasonIndex = index - if (index + 30 > indexResultItems.size) { + if (index + 30 > pgcItems.size) { println("load more by focus") - scope.launch(Dispatchers.IO) { animeIndexViewModel.loadMore() } + scope.launch(Dispatchers.IO) { pgcIndexViewModel.loadMore() } } }, onClick = { SeasonInfoActivity.actionStart( context = context, - seasonId = indexResultItem.seasonId, - proxyArea = ProxyArea.checkProxyArea(indexResultItem.title) + seasonId = pgcItem.seasonId, + proxyArea = ProxyArea.checkProxyArea(pgcItem.title) ) }, onLongClick = onLongClickSeason ) } - if (indexResultItems.isEmpty() && noMore) { + if (pgcItems.isEmpty() && noMore) { item( span = { GridItemSpan(6) } ) { @@ -192,30 +187,35 @@ fun AnimeIndexScreen( } } - AnimeIndexFilter( + IndexFilter( + type = pgcIndexViewModel.pgcType, show = showFilter, onDismissRequest = { showFilter = false }, - order = animeIndexViewModel.order, - seasonVersion = animeIndexViewModel.seasonVersion, - spokenLanguageType = animeIndexViewModel.spokenLanguageType, - area = animeIndexViewModel.area, - isFinish = animeIndexViewModel.isFinish, - copyright = animeIndexViewModel.copyright, - seasonStatus = animeIndexViewModel.seasonStatus, - seasonMonth = animeIndexViewModel.seasonMonth, - year = animeIndexViewModel.year, - styleId = animeIndexViewModel.styleId, - desc = animeIndexViewModel.desc, - onOrderChange = { animeIndexViewModel.order = it }, - onSeasonVersionChange = { animeIndexViewModel.seasonVersion = it }, - onSpokenLanguageTypeChange = { animeIndexViewModel.spokenLanguageType = it }, - onAreaChange = { animeIndexViewModel.area = it }, - onIsFinishChange = { animeIndexViewModel.isFinish = it }, - onCopyrightChange = { animeIndexViewModel.copyright = it }, - onSeasonStatusChange = { animeIndexViewModel.seasonStatus = it }, - onSeasonMonthChange = { animeIndexViewModel.seasonMonth = it }, - onYearChange = { animeIndexViewModel.year = it }, - onStyleIdChange = { animeIndexViewModel.styleId = it }, - onDescChange = { animeIndexViewModel.desc = it } + order = pgcIndexViewModel.indexOrder, + orderType = pgcIndexViewModel.indexOrderType, + seasonVersion = pgcIndexViewModel.seasonVersion, + spokenLanguage = pgcIndexViewModel.spokenLanguage, + area = pgcIndexViewModel.area, + isFinish = pgcIndexViewModel.isFinish, + copyright = pgcIndexViewModel.copyright, + seasonStatus = pgcIndexViewModel.seasonStatus, + seasonMonth = pgcIndexViewModel.seasonMonth, + producer = pgcIndexViewModel.producer, + year = pgcIndexViewModel.year, + releaseDate = pgcIndexViewModel.releaseDate, + style = pgcIndexViewModel.style, + onOrderChange = { pgcIndexViewModel.indexOrder = it }, + onOrderTypeChange = { pgcIndexViewModel.indexOrderType = it }, + onSeasonVersionChange = { pgcIndexViewModel.seasonVersion = it }, + onSpokenLanguageChange = { pgcIndexViewModel.spokenLanguage = it }, + onAreaChange = { pgcIndexViewModel.area = it }, + onIsFinishChange = { pgcIndexViewModel.isFinish = it }, + onCopyrightChange = { pgcIndexViewModel.copyright = it }, + onSeasonStatusChange = { pgcIndexViewModel.seasonStatus = it }, + onSeasonMonthChange = { pgcIndexViewModel.seasonMonth = it }, + onProducerChange = { pgcIndexViewModel.producer = it }, + onYearChange = { pgcIndexViewModel.year = it }, + onReleaseDateChange = { pgcIndexViewModel.releaseDate = it }, + onStyleChange = { pgcIndexViewModel.style = it } ) } \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/TvContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/TvContent.kt index 085a48dd..5751282e 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/TvContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/TvContent.kt @@ -1,9 +1,20 @@ package dev.aaa1115910.bv.screen.main.pgc +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.List +import androidx.compose.material.icons.rounded.QuestionMark import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.aaa1115910.biliapi.repositories.PgcType +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.bv.R +import dev.aaa1115910.bv.activities.pgc.PgcIndexActivity +import dev.aaa1115910.bv.ui.theme.BVTheme import dev.aaa1115910.bv.viewmodel.pgc.PgcTvViewModel import org.koin.androidx.compose.koinViewModel @@ -13,9 +24,65 @@ fun TvContent( lazyListState: LazyListState, pgcViewModel: PgcTvViewModel = koinViewModel() ) { + val context = LocalContext.current + + val onOpenIndex: () -> Unit = { + PgcIndexActivity.actionStart(context = context, pgcType = PgcType.Tv) + } + PgcScaffold( lazyListState = lazyListState, pgcViewModel = pgcViewModel, - pgcType = PgcType.Tv + pgcType = PgcType.Tv, + featureButtons = { + TvFeatureButtons( + modifier = Modifier.padding(vertical = 24.dp), + onOpenIndex = onOpenIndex + ) + } + ) +} + +@Composable +private fun TvFeatureButtons( + modifier: Modifier = Modifier, + onOpenIndex: () -> Unit +) { + val buttons = listOf( + Triple( + stringResource(R.string.anime_home_button_index), + Icons.AutoMirrored.Rounded.List, + onOpenIndex + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ) + ) + PgcFeatureButtons( + modifier = modifier, + buttons = buttons ) -} \ No newline at end of file +} + +@Preview(device = "id:tv_1080p") +@Composable +private fun TvFeatureButtonsPreview() { + BVTheme { + TvFeatureButtons( + modifier = Modifier, + onOpenIndex = {}, + ) + } +} diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/VarietyContent.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/VarietyContent.kt index 20dfa4de..42659d6c 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/VarietyContent.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/main/pgc/VarietyContent.kt @@ -1,9 +1,20 @@ package dev.aaa1115910.bv.screen.main.pgc +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.rounded.List +import androidx.compose.material.icons.rounded.QuestionMark import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import dev.aaa1115910.biliapi.repositories.PgcType +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.bv.R +import dev.aaa1115910.bv.activities.pgc.PgcIndexActivity +import dev.aaa1115910.bv.ui.theme.BVTheme import dev.aaa1115910.bv.viewmodel.pgc.PgcVarietyViewModel import org.koin.androidx.compose.koinViewModel @@ -13,9 +24,65 @@ fun VarietyContent( lazyListState: LazyListState, pgcViewModel: PgcVarietyViewModel = koinViewModel() ) { + val context = LocalContext.current + + val onOpenIndex: () -> Unit = { + PgcIndexActivity.actionStart(context = context, pgcType = PgcType.Variety) + } + PgcScaffold( lazyListState = lazyListState, pgcViewModel = pgcViewModel, - pgcType = PgcType.Variety + pgcType = PgcType.Variety, + featureButtons = { + VarietyFeatureButtons( + modifier = Modifier.padding(vertical = 24.dp), + onOpenIndex = onOpenIndex + ) + } + ) +} + +@Composable +private fun VarietyFeatureButtons( + modifier: Modifier = Modifier, + onOpenIndex: () -> Unit +) { + val buttons = listOf( + Triple( + stringResource(R.string.anime_home_button_index), + Icons.AutoMirrored.Rounded.List, + onOpenIndex + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ), + Triple( + stringResource(R.string.pgc_home_button_unknown), + Icons.Rounded.QuestionMark, + showPlaceholderToast + ) + ) + PgcFeatureButtons( + modifier = modifier, + buttons = buttons ) -} \ No newline at end of file +} + +@Preview(device = "id:tv_1080p") +@Composable +private fun VarietyFeatureButtonsPreview() { + BVTheme { + VarietyFeatureButtons( + modifier = Modifier, + onOpenIndex = {}, + ) + } +} diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/util/Extends.kt b/app/src/main/kotlin/dev/aaa1115910/bv/util/Extends.kt index c1c920b4..f2627c1b 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/util/Extends.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/util/Extends.kt @@ -111,4 +111,6 @@ fun KeyEvent.isKeyUp(): Boolean = type == KeyEventType.KeyUp fun KeyEvent.isDpadUp(): Boolean = key == Key.DirectionUp fun KeyEvent.isDpadDown(): Boolean = key == Key.DirectionDown fun KeyEvent.isDpadLeft(): Boolean = key == Key.DirectionLeft -fun KeyEvent.isDpadRight(): Boolean = key == Key.DirectionRight \ No newline at end of file +fun KeyEvent.isDpadRight(): Boolean = key == Key.DirectionRight + +fun Int.stringRes(context: Context): String = context.getString(this) \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/util/PgcIndexParamExtends.kt b/app/src/main/kotlin/dev/aaa1115910/bv/util/PgcIndexParamExtends.kt new file mode 100644 index 00000000..b119b7fe --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/util/PgcIndexParamExtends.kt @@ -0,0 +1,67 @@ +package dev.aaa1115910.bv.util + +import android.content.Context +import dev.aaa1115910.biliapi.entity.pgc.index.Area +import dev.aaa1115910.biliapi.entity.pgc.index.Copyright +import dev.aaa1115910.biliapi.entity.pgc.index.IndexOrder +import dev.aaa1115910.biliapi.entity.pgc.index.IndexOrderType +import dev.aaa1115910.biliapi.entity.pgc.index.IsFinish +import dev.aaa1115910.biliapi.entity.pgc.index.PgcIndexParam +import dev.aaa1115910.biliapi.entity.pgc.index.Producer +import dev.aaa1115910.biliapi.entity.pgc.index.ReleaseDate +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonMonth +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonStatus +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonVersion +import dev.aaa1115910.biliapi.entity.pgc.index.SpokenLanguage +import dev.aaa1115910.biliapi.entity.pgc.index.Style +import dev.aaa1115910.biliapi.entity.pgc.index.Year +import dev.aaa1115910.bv.R + +fun PgcIndexParam.getDisplayName(context: Context) = when (this) { + is IndexOrder -> + fromStringArray(context, R.array.pgc_index_filter_order_name, ordinal) + + is IndexOrderType -> + fromStringArray(context, R.array.pgc_index_filter_order_type_name, ordinal) + + is SeasonVersion -> + fromStringArray(context, R.array.pgc_index_filter_season_version_name, ordinal) + + is SpokenLanguage -> + fromStringArray(context, R.array.pgc_index_filter_spoken_language_name, ordinal) + + is Area -> + fromStringArray(context, R.array.pgc_index_filter_area_name, ordinal) + + is IsFinish -> + fromStringArray(context, R.array.pgc_index_filter_is_finish_name, ordinal) + + is Copyright -> + fromStringArray(context, R.array.pgc_index_filter_copyright_name, ordinal) + + is SeasonStatus -> + fromStringArray(context, R.array.pgc_index_filter_season_status_name, ordinal) + + is SeasonMonth -> + fromStringArray(context, R.array.pgc_index_filter_season_month_name, ordinal) + + is Producer -> + fromStringArray(context, R.array.pgc_index_filter_producer_name, ordinal) + + is Year -> + fromStringArray(context, R.array.pgc_index_filter_year_name, ordinal) + + is ReleaseDate -> + fromStringArray(context, R.array.pgc_index_filter_release_date_name, ordinal) + + is Style -> + fromStringArray(context, R.array.pgc_index_filter_style_name, ordinal) + + else -> "" +} + +private fun fromStringArray( + context: Context, + arrayId: Int, + index: Int +): String = context.resources.getStringArray(arrayId)[index] diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/util/PgcTypeExtends.kt b/app/src/main/kotlin/dev/aaa1115910/bv/util/PgcTypeExtends.kt new file mode 100644 index 00000000..21a0cbf1 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/util/PgcTypeExtends.kt @@ -0,0 +1,14 @@ +package dev.aaa1115910.bv.util + +import android.content.Context +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.bv.R + +fun PgcType.getDisplayName(context: Context) = when (this) { + PgcType.Anime -> R.string.pgc_type_anime + PgcType.GuoChuang -> R.string.pgc_type_guochuang + PgcType.Movie -> R.string.pgc_type_movie + PgcType.Documentary -> R.string.pgc_type_documentary + PgcType.Tv -> R.string.pgc_type_tv + PgcType.Variety -> R.string.pgc_type_variety +}.stringRes(context) \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/index/AnimeIndexViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/index/AnimeIndexViewModel.kt deleted file mode 100644 index 365588d4..00000000 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/index/AnimeIndexViewModel.kt +++ /dev/null @@ -1,86 +0,0 @@ -package dev.aaa1115910.bv.viewmodel.index - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel -import dev.aaa1115910.biliapi.entity.season.IndexResultItem -import dev.aaa1115910.biliapi.entity.season.IndexResultPage -import dev.aaa1115910.biliapi.http.entity.index.IndexOrder -import dev.aaa1115910.biliapi.repositories.IndexRepository -import dev.aaa1115910.bv.BVApp -import dev.aaa1115910.bv.util.fError -import dev.aaa1115910.bv.util.toast -import io.github.oshai.kotlinlogging.KotlinLogging -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class AnimeIndexViewModel( - private val indexRepository: IndexRepository -) : ViewModel() { - companion object { - private val logger = KotlinLogging.logger { } - } - - val indexResultItems = mutableStateListOf() - - private var updating = false - private var nextPage = IndexResultPage() - val noMore get() = nextPage.hasNext.not() - - var order by mutableStateOf(IndexOrder.FollowCount) - var seasonVersion by mutableIntStateOf(-1) - var spokenLanguageType by mutableIntStateOf(-1) - var area by mutableIntStateOf(-1) - var isFinish by mutableIntStateOf(-1) - var copyright by mutableIntStateOf(-1) - var seasonStatus by mutableIntStateOf(-1) - var seasonMonth by mutableIntStateOf(-1) - var year by mutableStateOf("-1") - var styleId by mutableIntStateOf(-1) - var desc by mutableStateOf(true) - - suspend fun loadMore() { - if (!updating) loadData() - } - - private suspend fun loadData() { - updating = true - if (!nextPage.hasNext) { - updating = false - return - } - runCatching { - val result = indexRepository.getAnimeIndex( - sort = order, - seasonVersion = seasonVersion, - spokenLanguageType = spokenLanguageType, - area = area, - isFinish = isFinish, - copyright = copyright, - seasonStatus = seasonStatus, - seasonMonth = seasonMonth, - year = year, - styleId = styleId, - desc = desc, - page = nextPage - ) - indexResultItems.addAll(result.list) - nextPage = result.nextPage - }.onFailure { - logger.fError { "Load anime index list failed: ${it.stackTraceToString()}" } - withContext(Dispatchers.Main) { - "加载番剧索引失败: ${it.localizedMessage}".toast(BVApp.context) - } - } - updating = false - } - - fun clearData() { - indexResultItems.clear() - nextPage = IndexResultPage() - updating = false - } -} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/index/PgcIndexViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/index/PgcIndexViewModel.kt new file mode 100644 index 00000000..fbe77851 --- /dev/null +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/index/PgcIndexViewModel.kt @@ -0,0 +1,111 @@ +package dev.aaa1115910.bv.viewmodel.index + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import dev.aaa1115910.biliapi.entity.pgc.PgcItem +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.biliapi.entity.pgc.index.Area +import dev.aaa1115910.biliapi.entity.pgc.index.Copyright +import dev.aaa1115910.biliapi.entity.pgc.index.IndexOrder +import dev.aaa1115910.biliapi.entity.pgc.index.IndexOrderType +import dev.aaa1115910.biliapi.entity.pgc.index.IsFinish +import dev.aaa1115910.biliapi.entity.pgc.index.PgcIndexData +import dev.aaa1115910.biliapi.entity.pgc.index.Producer +import dev.aaa1115910.biliapi.entity.pgc.index.ReleaseDate +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonMonth +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonStatus +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonVersion +import dev.aaa1115910.biliapi.entity.pgc.index.SpokenLanguage +import dev.aaa1115910.biliapi.entity.pgc.index.Style +import dev.aaa1115910.biliapi.entity.pgc.index.Year +import dev.aaa1115910.biliapi.repositories.PgcRepository +import dev.aaa1115910.bv.BVApp +import dev.aaa1115910.bv.util.fError +import dev.aaa1115910.bv.util.toast +import io.github.oshai.kotlinlogging.KotlinLogging +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class PgcIndexViewModel( + private val pgcRepository: PgcRepository, +) : ViewModel() { + companion object { + private val logger = KotlinLogging.logger { } + } + + val indexResultItems = mutableStateListOf() + + private var updating = false + private var nextPage = PgcIndexData.PgcIndexPage() + val noMore get() = nextPage.hasNext.not() + + var pgcType by mutableStateOf(PgcType.Anime) + + var indexOrder by mutableStateOf(IndexOrder.FollowCount) + var indexOrderType by mutableStateOf(IndexOrderType.Desc) + var seasonVersion by mutableStateOf(SeasonVersion.All) + var spokenLanguage by mutableStateOf(SpokenLanguage.All) + var area by mutableStateOf(Area.All) + var isFinish by mutableStateOf(IsFinish.All) + var copyright by mutableStateOf(Copyright.All) + var seasonStatus by mutableStateOf(SeasonStatus.All) + var seasonMonth by mutableStateOf(SeasonMonth.All) + var producer by mutableStateOf(Producer.All) + var year by mutableStateOf(Year.All) + var releaseDate by mutableStateOf(ReleaseDate.All) + var style by mutableStateOf(Style.All) + + fun changePgcType(pgcType: PgcType) { + this.pgcType = pgcType + indexOrder = IndexOrder.getList(pgcType).first() + } + + suspend fun loadMore() { + if (!updating) loadData() + } + + private suspend fun loadData() { + updating = true + if (!nextPage.hasNext) { + updating = false + return + } + runCatching { + val result = pgcRepository.getPgcIndex( + pgcType = pgcType, + indexOrder = indexOrder, + indexOrderType = indexOrderType, + seasonVersion = seasonVersion, + spokenLanguage = spokenLanguage, + area = area, + isFinish = isFinish, + copyright = copyright, + seasonStatus = seasonStatus, + seasonMonth = seasonMonth, + producer = producer, + year = year, + releaseDate = releaseDate, + style = style, + page = nextPage + ) + indexResultItems.addAll(result.list) + nextPage = result.nextPage + logger.info { "load more $pgcType list success, size: ${result.list.size}" } + }.onFailure { + logger.fError { "Load $pgcType index list failed: ${it.stackTraceToString()}" } + withContext(Dispatchers.Main) { + "加载 $pgcType 索引失败: ${it.localizedMessage}".toast(BVApp.context) + } + } + updating = false + } + + fun clearData() { + indexResultItems.clear() + nextPage = PgcIndexData.PgcIndexPage() + updating = false + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcAnimeViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcAnimeViewModel.kt index 842b68df..2c5bb41d 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcAnimeViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcAnimeViewModel.kt @@ -1,7 +1,7 @@ package dev.aaa1115910.bv.viewmodel.pgc import dev.aaa1115910.biliapi.repositories.PgcRepository -import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.biliapi.entity.pgc.PgcType class PgcAnimeViewModel( override val pgcRepository: PgcRepository diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcDocumentaryViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcDocumentaryViewModel.kt index 3bc460d3..968ac425 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcDocumentaryViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcDocumentaryViewModel.kt @@ -1,7 +1,7 @@ package dev.aaa1115910.bv.viewmodel.pgc import dev.aaa1115910.biliapi.repositories.PgcRepository -import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.biliapi.entity.pgc.PgcType class PgcDocumentaryViewModel( override val pgcRepository: PgcRepository diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcGuoChuangViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcGuoChuangViewModel.kt index 58f02dde..6d0ae6d1 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcGuoChuangViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcGuoChuangViewModel.kt @@ -1,7 +1,7 @@ package dev.aaa1115910.bv.viewmodel.pgc import dev.aaa1115910.biliapi.repositories.PgcRepository -import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.biliapi.entity.pgc.PgcType class PgcGuoChuangViewModel( override val pgcRepository: PgcRepository diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcMovieViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcMovieViewModel.kt index 1f38f857..8912278f 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcMovieViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcMovieViewModel.kt @@ -1,7 +1,7 @@ package dev.aaa1115910.bv.viewmodel.pgc import dev.aaa1115910.biliapi.repositories.PgcRepository -import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.biliapi.entity.pgc.PgcType class PgcMovieViewModel( override val pgcRepository: PgcRepository diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcTvViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcTvViewModel.kt index 0ed5e0d9..be181419 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcTvViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcTvViewModel.kt @@ -1,7 +1,7 @@ package dev.aaa1115910.bv.viewmodel.pgc import dev.aaa1115910.biliapi.repositories.PgcRepository -import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.biliapi.entity.pgc.PgcType class PgcTvViewModel( override val pgcRepository: PgcRepository diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcVarietyViewModel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcVarietyViewModel.kt index 55cdaa4f..99bd0f89 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcVarietyViewModel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/viewmodel/pgc/PgcVarietyViewModel.kt @@ -1,7 +1,7 @@ package dev.aaa1115910.bv.viewmodel.pgc import dev.aaa1115910.biliapi.repositories.PgcRepository -import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.biliapi.entity.pgc.PgcType class PgcVarietyViewModel( override val pgcRepository: PgcRepository 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 acd3e2ca..69d8d066 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 @@ -9,8 +9,9 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dev.aaa1115910.biliapi.entity.pgc.PgcCarouselData import dev.aaa1115910.biliapi.entity.pgc.PgcFeedData +import dev.aaa1115910.biliapi.entity.pgc.PgcItem import dev.aaa1115910.biliapi.repositories.PgcRepository -import dev.aaa1115910.biliapi.repositories.PgcType +import dev.aaa1115910.biliapi.entity.pgc.PgcType import dev.aaa1115910.bv.BVApp import dev.aaa1115910.bv.util.fInfo import dev.aaa1115910.bv.util.toast @@ -38,7 +39,7 @@ abstract class PgcViewModel( /** * 推荐数据中会穿插排行榜,为了避免出现某一行仅出现单独几个剧集,因此将不满一行的剧集单独存起来 */ - private val restSubItems = mutableListOf() + private val restSubItems = mutableListOf() var updating by mutableStateOf(false) var hasNext by mutableStateOf(true) @@ -130,7 +131,7 @@ abstract class PgcViewModel( */ private fun updateFeedItems(data: PgcFeedData) { logger.fInfo { "update $pgcType feed items: [items: ${data.items.size}, ranks: ${data.ranks.size}]" } - val epList = mutableStateListOf() + val epList = mutableStateListOf() epList.addAll(restSubItems) epList.addAll(data.items) @@ -161,7 +162,7 @@ abstract class PgcViewModel( data class FeedListItem( val type: FeedListType, - val items: List? = emptyList(), + val items: List? = emptyList(), val rank: PgcFeedData.FeedRank? = null ) diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 5a5ccdd1..062dddc9 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -1,5 +1,228 @@ + - + + 全部 + 中国大陆 + 日本 + 美国 + 英国 + 其他 + 中国港台 + 韩国 + 法国 + 泰国 + 西班牙 + 德国 + 意大利 + + + 全部 + 独家 + 其他 + + + 全部 + 完结 + 连载 + + + 更新时间 + 弹幕数量 + 播放数量 + 追番人数 + 最高评分 + 开播时间 + 上映时间 + + + 降序 + 升序 + + + 全部 + BBC + NHK + SKY + 央视 + ITV + 历史频道 + 探索频道 + 卫视 + 自制 + ZDF + 合作机构 + 国内其他 + 国外其他 + 国家地理 + 索尼 + 环球 + 派拉蒙 + 华纳 + 迪士尼 + HBO + + + 全部 + 2024 + 2023 + 2022 + 2021 + 2020 + 2019 + 2018 + 2017 + 2016 + 2015-2010 + 2009-2005 + 2004-2000 + 90年代 + 80年代 + 更早 + + + 全部 + 1月 + 4月 + 7月 + 10月 + + + 全部 + 免费 + 付费 + 大会员 + + + 全部 + 正片 + 电影 + 其他 + + + 全部 + 原声 + 中文配音 + + + 全部 + 电影 + 原创 + 漫画改 + 小说改 + 游戏改 + 动态漫 + 布袋戏 + 热血 + 穿越 + 奇幻 + 玄幻 + 战斗 + 搞笑 + 日常 + 科幻 + 萌系 + 治愈 + 校园 + 少儿 + 泡面 + 恋爱 + 少女 + 魔法 + 冒险 + 历史 + 架空 + 机战 + 神魔 + 声控 + 运动 + 励志 + 音乐 + 推理 + 社团 + 智斗 + 催泪 + 美食 + 偶像 + 乙女 + 职场 + 古风 + 剧情 + 喜剧 + 爱情 + 动作 + 恐怖 + 犯罪 + 惊悚 + 悬疑 + 战争 + + 传记 + 家庭 + 歌剧 + 纪实 + 灾难 + 人文 + 科技 + 探险 + 通用 + 萌宠 + 社会 + 动物 + 自然 + 医疗 + 军事 + 罪案 + 神秘 + 旅行 + 武侠 + 青春 + 都市 + 古装 + 谍战 + 经典 + 情感 + 神话 + 年代 + 农村 + 刑侦 + 军旅 + 访谈 + 脱口秀 + 真人秀 + + 选秀 + 旅游 + 演唱会 + 亲子 + 晚会 + 养成 + 文化 + + 特摄 + 短剧 + 短片 + + + 全部 + 2024 + 2023 + 2022 + 2021 + 2020 + 2019 + 2018 + 2017 + 2016 + 2015 + 2014-2010 + 2009-2005 + 2004-2000 + 90年代 + 80年代 + 更早 + + + 熟悉的屏幕 你来辣 @@ -9,5 +232,5 @@ BUG 满天飞 ~ 你说得对,但是 让我康康 - - \ No newline at end of file + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3769f367..4c0190ad 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,14 +69,6 @@ 再次按下返回键退出 Bug Video - 弹幕数量 - 追番人数 - 播放数量 - 上映时间 - 最高评分 - 开播时间 - 更新时间 - 已加载 %1$d 条数据 已加载全部共 %1$d 条数据 就只有这么多了 @@ -103,6 +95,28 @@ 无可用数据 + 占位 + 地区 + 版权 + 状态 + 排序方式 + 排序顺序 + 出品 + 年份 + 季度 + 付费 + 类型 + 配音 + 风格 + "索引筛选 - " + 年份 + 番剧 + 纪录片 + 国创 + 电影 + 电视剧 + 综艺 + 已看完 %1$s / %2$s @@ -248,7 +262,6 @@ 手机号 请先发送验证码 - 番剧索引 番剧放送时间表 个人收藏 已关注 @@ -257,6 +270,7 @@ 登录 日志列表 解码器信息 + PGC 索引 遥控板按键演示 搜索输入 搜索结果 diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcFeedData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcFeedData.kt index 261faed1..de5ddeee 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcFeedData.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcFeedData.kt @@ -1,11 +1,9 @@ package dev.aaa1115910.biliapi.entity.pgc -import dev.aaa1115910.biliapi.http.SeasonIndexType - data class PgcFeedData( var hasNext: Boolean, var cursor: Int, - var items: List = emptyList(), + var items: List = emptyList(), var ranks: List = emptyList() ) { companion object { @@ -13,7 +11,7 @@ data class PgcFeedData( return PgcFeedData( hasNext = data.hasNext, cursor = data.coursor, - items = data.items.map { FeedItem.fromFeedSubItem(it) }, + items = data.items.map { PgcItem.fromFeedSubItem(it) }, ranks = emptyList() ) } @@ -24,53 +22,17 @@ data class PgcFeedData( return PgcFeedData( hasNext = data.hasNext, cursor = data.coursor, - items = itemsList?.subItems?.map { FeedItem.fromFeedSubItem(it) } ?: emptyList(), + items = itemsList?.subItems?.map { PgcItem.fromFeedSubItem(it) } ?: emptyList(), ranks = ranksList?.subItems?.map { FeedRank.fromFeedSubItem(it) } ?: emptyList() ) } } - data class FeedItem( - var cover: String, - var title: String, - var subTitle: String, - var seasonId: Int, - var episodeId: Int, - var seasonType: SeasonIndexType, - var rating: String - ) { - companion object { - fun fromFeedSubItem(feedSubItem: dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedData.FeedSubItem): FeedItem { - return FeedItem( - cover = feedSubItem.cover, - title = feedSubItem.title, - subTitle = feedSubItem.subTitle, - seasonId = feedSubItem.seasonId!!, - episodeId = feedSubItem.episodeId, - seasonType = SeasonIndexType.fromId(feedSubItem.seasonType!!), - rating = feedSubItem.rating ?: "0" - ) - } - - fun fromFeedSubItem(feedSubItem: dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedV3Data.FeedItem.FeedSubItem): FeedItem { - return FeedItem( - cover = feedSubItem.cover, - title = feedSubItem.title, - subTitle = feedSubItem.subTitle, - seasonId = feedSubItem.seasonId!!, - episodeId = feedSubItem.episodeId ?: feedSubItem.inline!!.epId, - seasonType = SeasonIndexType.fromId(feedSubItem.seasonType!!), - rating = feedSubItem.rating ?: "0" - ) - } - } - } - data class FeedRank( var cover: String, var title: String, var subTitle: String, - var items: List + var items: List ) { companion object { fun fromFeedSubItem(feedSubItem: dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedV3Data.FeedItem.FeedSubItem): FeedRank { @@ -78,7 +40,7 @@ data class PgcFeedData( cover = feedSubItem.cover, title = feedSubItem.title, subTitle = feedSubItem.subTitle, - items = feedSubItem.subItems?.map { FeedItem.fromFeedSubItem(it) } + items = feedSubItem.subItems?.map { PgcItem.fromFeedSubItem(it) } ?: emptyList() ) } diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcItem.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcItem.kt new file mode 100644 index 00000000..e0bfffd5 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcItem.kt @@ -0,0 +1,51 @@ +package dev.aaa1115910.biliapi.entity.pgc + +import dev.aaa1115910.biliapi.http.SeasonIndexType + +data class PgcItem( + var cover: String, + var title: String, + var subTitle: String, + var seasonId: Int, + var episodeId: Int, + var seasonType: SeasonIndexType, + var rating: String +) { + companion object { + fun fromFeedSubItem(feedSubItem: dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedData.FeedSubItem): PgcItem { + return PgcItem( + cover = feedSubItem.cover, + title = feedSubItem.title, + subTitle = feedSubItem.subTitle, + seasonId = feedSubItem.seasonId!!, + episodeId = feedSubItem.episodeId, + seasonType = SeasonIndexType.fromId(feedSubItem.seasonType!!), + rating = feedSubItem.rating ?: "0" + ) + } + + fun fromFeedSubItem(feedSubItem: dev.aaa1115910.biliapi.http.entity.pgc.PgcFeedV3Data.FeedItem.FeedSubItem): PgcItem { + return PgcItem( + cover = feedSubItem.cover, + title = feedSubItem.title, + subTitle = feedSubItem.subTitle, + seasonId = feedSubItem.seasonId!!, + episodeId = feedSubItem.episodeId ?: feedSubItem.inline!!.epId, + seasonType = SeasonIndexType.fromId(feedSubItem.seasonType!!), + rating = feedSubItem.rating ?: "0" + ) + } + + fun fromIndexResultItem(indexResultItem: dev.aaa1115910.biliapi.http.entity.index.IndexResultData.IndexResultItem): PgcItem { + return PgcItem( + cover = indexResultItem.cover, + title = indexResultItem.title, + subTitle = indexResultItem.subTitle, + seasonId = indexResultItem.seasonId, + episodeId = indexResultItem.firstEp.epId, + seasonType = SeasonIndexType.fromId(indexResultItem.seasonType), + rating = indexResultItem.score + ) + } + } +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcType.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcType.kt new file mode 100644 index 00000000..523118b9 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcType.kt @@ -0,0 +1,10 @@ +package dev.aaa1115910.biliapi.entity.pgc + +enum class PgcType { + Anime, + GuoChuang, + Movie, + Documentary, + Tv, + Variety +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/index/IndexParams.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/index/IndexParams.kt new file mode 100644 index 00000000..43b1a564 --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/index/IndexParams.kt @@ -0,0 +1,434 @@ +package dev.aaa1115910.biliapi.entity.pgc.index + +import dev.aaa1115910.biliapi.entity.pgc.PgcType + +interface PgcIndexParam + +/** + * 排序 + */ +enum class IndexOrder(val id: Int) : PgcIndexParam { + UpdateTime(0), // 更新时间 + DanmakuCount(1), // 弹幕数量 + PlayCount(2), // 播放数量 + FollowCount(3), // 追番人数 + Score(4), // 最高评分 + StartTime(5), // 开播时间 + PublishTime(6); // 上映时间 + + companion object { + fun getList(pgcType: PgcType): List = when (pgcType) { + PgcType.Anime -> listOf(FollowCount, UpdateTime, Score, PlayCount, StartTime) + PgcType.GuoChuang -> listOf(FollowCount, UpdateTime, Score, PlayCount, StartTime) + PgcType.Movie -> listOf(PlayCount, UpdateTime, PublishTime, Score) + PgcType.Documentary -> listOf(PlayCount, Score, UpdateTime, PublishTime, DanmakuCount) + PgcType.Tv -> listOf(PlayCount, UpdateTime, DanmakuCount, Score, FollowCount) + PgcType.Variety -> listOf(PlayCount, UpdateTime, PublishTime, Score, DanmakuCount) + } + } +} + +enum class IndexOrderType(val id: Int) : PgcIndexParam { + Desc(0), // 降序 + Asc(1); // 升序 +} + +/** + * 类型 + */ +enum class SeasonVersion(val id: Int) : PgcIndexParam { + All(-1), // 全部 + FeatureFilm(1), // 正片 + Movies(2), // 电影 + Other(3); // 其他 + + companion object { + fun getList(pgcType: PgcType): List { + return when (pgcType) { + PgcType.Anime, PgcType.GuoChuang -> listOf(All, FeatureFilm, Movies, Other) + else -> emptyList() + } + } + } +} + +/** + * 配音 + */ +enum class SpokenLanguage(val id: Int) : PgcIndexParam { + All(-1), // 全部 + OriginalSoundtrack(1), // 原声 + ChineseDubbing(2); // 中文配音 + + companion object { + fun getList(pgcType: PgcType) = when (pgcType) { + PgcType.Anime -> listOf(All, OriginalSoundtrack, ChineseDubbing) + else -> emptyList() + } + } +} + +/** + * 地区 + */ +enum class Area(val id: Int) : PgcIndexParam { + All(-1), // 全部 + MainlandChina(1), // 中国大陆 + Japan(2), // 日本 + America(3), // 美国 + Britain(4), // 英国 + Other(5), // 其他 + ChinaHongKongTaiwan(6), // 中国港台 6,7 + Korea(8), // 韩国 + France(9), // 法国 + Thailand(10), // 泰国 + Spain(13), // 西班牙 + Germany(15), // 德国 + Italy(35); // 意大利 + + companion object { + fun getList(pgcType: PgcType) = when (pgcType) { + PgcType.Anime -> listOf(All, Japan, America, Other) + PgcType.Movie -> listOf( + All, MainlandChina, ChinaHongKongTaiwan, America, Japan, Korea, France, + Britain, Germany, Thailand, Italy, Spain, Other + ) + + PgcType.Tv -> listOf(All, MainlandChina, Japan, America, Britain, Other) + else -> emptyList() + } + } +} + +/** + * 状态(完结状态) + */ +enum class IsFinish(val id: Int) : PgcIndexParam { + All(-1), // 全部 + Finished(1), // 完结 + Serialization(0); // 连载 + + companion object { + fun getList(pgcType: PgcType) = when (pgcType) { + PgcType.Anime, PgcType.GuoChuang -> listOf(All, Finished, Serialization) + else -> emptyList() + } + } +} + +/** + * 版权 + */ +enum class Copyright(val id: Int) : PgcIndexParam { + All(-1), // 全部 + Exclusive(3), // 独家 + Other(1); // 其他 1,2,4 + + companion object { + fun getList(pgcType: PgcType) = when (pgcType) { + PgcType.Anime, PgcType.GuoChuang -> listOf(All, Exclusive, Other) + else -> emptyList() + } + } +} + +/** + * 付费(付费状态) + */ +enum class SeasonStatus(val id: Int) : PgcIndexParam { + All(-1), // 全部 + Free(1), // 免费 + Paid(2), // 付费 2,6 + Prime(4); // 大会员 4,6 + + companion object { + fun getList(pgcType: PgcType) = when (pgcType) { + PgcType.Anime, PgcType.GuoChuang, PgcType.Movie -> listOf(All, Free, Paid, Prime) + PgcType.Documentary, PgcType.Tv, PgcType.Variety -> listOf(All, Free, Prime) + } + } +} + +/** + * 季度 + */ +enum class SeasonMonth(val id: Int) : PgcIndexParam { + All(-1), // 全部 + January(1), // 1月 + April(4), // 4月 + July(7), // 7月 + October(10); // 10月 + + companion object { + fun getList(pgcType: PgcType) = when (pgcType) { + PgcType.Anime -> listOf(All, January, April, July, October) + else -> emptyList() + } + } +} + +/** + * 出品(方) + */ +enum class Producer(val id: Int) : PgcIndexParam { + All(-1), // 全部 + BBC(1), // BBC + NHK(2), // NHK + SKY(3), // SKY + CCTV(4), // 央视 + ITV(5), // ITV + HistoryChannel(6), // 历史频道 + DiscoveryChannel(7),// 探索频道 + SatelliteTV(8), // 卫视 + SelfMade(9), // 自制 + ZDF(10), // ZDF + Cooperation(11), // 合作机构 + DomesticOther(12), // 国内其他 + ForeignOther(13), // 国外其他 + NationalGeographic(14), // 国家地理 + Sony(15), // 索尼 + Universal(16), // 环球 + Paramount(17), // 派拉蒙 + Warner(18), // 华纳 + Disney(19), // 迪士尼 + HBO(20); // HBO + + companion object { + fun getList(pgcType: PgcType) = when (pgcType) { + PgcType.Documentary -> listOf( + All, CCTV, BBC, DiscoveryChannel, NationalGeographic, NHK, HistoryChannel, + SatelliteTV, SelfMade, ITV, SKY, ZDF, Cooperation, DomesticOther, ForeignOther, + Sony, Universal, Paramount, Warner, Disney, HBO + ) + + else -> emptyList() + } + } +} + +/** + * 年份(Year) + */ +enum class Year(val str: String) : PgcIndexParam { + All("-1"), // 全部 + Year2024("[2024,2025)"), // 2024 + Year2023("[2023,2024)"), // 2023 + Year2022("[2022,2023)"), // 2022 + Year2021("[2021,2022)"), // 2021 + Year2020("[2020,2021)"), // 2020 + Year2019("[2019,2020)"), // 2019 + Year2018("[2018,2019)"), // 2018 + Year2017("[2017,2018)"), // 2017 + Year2016("[2016,2017)"), // 2016 + Year2015("[2015,2016)"), // 2015 + Year2014_2010("[2010,2015)"), // 2014-2010 + Year2009_2005("[2005,2010)"), // 2009-2005 + Year2004_2000("[2000,2005)"), // 2004-2000 + Year199x("[1990,2000)"), // 90年代 + Year198x("[1980,1990)"), // 80年代 + Earlier("[,1980)"); // 更早 + + companion object { + fun getList(pgcType: PgcType) = when (pgcType) { + PgcType.Anime, PgcType.GuoChuang -> listOf( + All, Year2024, Year2023, Year2022, Year2021, Year2020, Year2019, Year2018, + Year2017, Year2016, Year2015, Year2014_2010, Year2009_2005, Year2004_2000, + Year199x, Year198x, Earlier + ) + + else -> emptyList() + } + } +} + +/** + * 年份(发布时间) + */ +enum class ReleaseDate(val str: String) : PgcIndexParam { + All("-1"), // 全部 + Year2024("[2024-01-01 00:00:00,2025-01-01 00:00:00)"), // 2024 + Year2023("[2023-01-01 00:00:00,2024-01-01 00:00:00)"), // 2023 + Year2022("[2022-01-01 00:00:00,2023-01-01 00:00:00)"), // 2022 + Year2021("[2021-01-01 00:00:00,2022-01-01 00:00:00)"), // 2021 + Year2020("[2020-01-01 00:00:00,2021-01-01 00:00:00)"), // 2020 + Year2019("[2019-01-01 00:00:00,2020-01-01 00:00:00)"), // 2019 + Year2018("[2018-01-01 00:00:00,2019-01-01 00:00:00)"), // 2018 + Year2017("[2017-01-01 00:00:00,2018-01-01 00:00:00)"), // 2017 + Year2016("[2016-01-01 00:00:00,2017-01-01 00:00:00)"), // 2016 + Year2015_2010("[2010-01-01 00:00:00,2015-01-01 00:00:00)"), // 2015-2010 + Year2009_2005("[2005-01-01 00:00:00,2010-01-01 00:00:00)"), // 2009-2005 + Year2004_2000("[2000-01-01 00:00:00,2005-01-01 00:00:00)"), // 2004-2000 + Year199x("[1990-01-01 00:00:00,2000-01-01 00:00:00)"), // 90年代 + Year198x("[1980-01-01 00:00:00,1990-01-01 00:00:00)"), // 80年代 + Earlier("[,1980-01-01 00:00:00)"); // 更早 + + companion object { + fun getList(pgcType: PgcType) = when (pgcType) { + PgcType.Movie, PgcType.Documentary, PgcType.Tv -> listOf( + All, Year2024, Year2023, Year2022, Year2021, Year2020, Year2019, Year2018, + Year2017, Year2016, Year2015_2010, Year2009_2005, Year2004_2000, + Year199x, Year198x, Earlier + ) + + else -> emptyList() + } + } +} + +/** + * 风格 + */ +enum class Style(val id: Int) : PgcIndexParam { + All(-1), // 全部 + Movie(-10), // 电影 + + Original(10010), // 原创 + Comic(10011), // 漫画改 + Novel(10012), // 小说改 + Game(10013), // 游戏改 + Animation(10014), // 动态漫 + Puppetry(10015), // 布袋戏 + HotBlood(10016), // 热血 + TimeTravel(10017), // 穿越 + Fantasy(10018), // 奇幻 + XuanHuan(10019), // 玄幻 + + Fight(10020), // 战斗 + Funny(10021), // 搞笑 + Daily(10022), // 日常 + ScienceFiction(10023), // 科幻 + Moe(10024), // 萌系 + Healing(10025), // 治愈 + School(10026), // 校园 + Children(10027), // 少儿 + InstantNoodles(10028), // 泡面 + InLove(10029), // 恋爱 + + Girl(10030), // 少女 + Magic(10031), // 魔法 + Adventure(10032), // 冒险 + History(10033), // 历史 + Fiction(10034), // 架空 + Mecha(10035), // 机战 + GodDemon(10036), // 神魔 + VoiceControl(10037),// 声控 + Sports(10038), // 运动 + Inspirational(10039), // 励志 + + Music(10040), // 音乐 + Reasoning(10041), // 推理 + Club(10042), // 社团 + WisdomFight(10043), // 智斗 + Tearjerker(10044), // 催泪 + Food(10045), // 美食 + Idol(10046), // 偶像 + Maiden(10047), // 乙女 + Workplace(10048), // 职场 + AncientStyle(10049),// 古风 + + Plot(10050), // 剧情 + Comedy(10051), // 喜剧 + Love(10052), // 爱情 + Action(10053), // 动作 + Terror(10054), // 恐怖 + Offense(10055), // 犯罪 + Thriller(10056), // 惊悚 + Suspense(10057), // 悬疑 + War(10058), // 战争 + // 10059 + + Biography(10060), // 传记 + Family(10061), // 家庭 + Opera(10062), // 歌剧 + Documentary(10063), // 纪实 + Disaster(10064), // 灾难 + Humanities(10065), // 人文 + Technology(10066), // 科技 + Explore(10067), // 探险 + Universal(10068), // 通用 + CutePet(10069), // 萌宠 + + Social(10070), // 社会 + Animal(10071), // 动物 + Nature(10072), // 自然 + Medical(10073), // 医疗 + Military(10074), // 军事 + Crime(10075), // 罪案 + Mystery(10076), // 神秘 + Travel(10077), // 旅行 + MartialArts(10078), // 武侠 + Youth(10079), // 青春 + + City(10080), // 都市 + AncientCostume(10081), // 古装 + SpyWar(10082), // 谍战 + Classic(10083), // 经典 + Emotion(10084), // 情感 + Myth(10085), // 神话 + Age(10086), // 年代 + Rural(10087), // 农村 + CriminalInvestigation(10088), // 刑侦 + MilitaryLife(10089),// 军旅 + + Interview(10090), // 访谈 + TalkShow(10091), // 脱口秀 + RealityShow(10092), // 真人秀 + + //10093 + Selection(10094), // 选秀 + Tourism(10095), // 旅游 + Concert(10096), // 演唱会 + ParentChild(10097), // 亲子 + EveningParty(10098),// 晚会 + Cultivate(10099), // 养成 + + Culture(10100), // 文化 + + //10101 + SpecialEffects(10102), // 特摄 + ShortPlay(10103), // 短剧 + ShortFilm(10104); // 短片 + + companion object { + fun getList(pgcType: PgcType) = when (pgcType) { + PgcType.Anime -> listOf( + All, Original, Comic, Novel, Game, SpecialEffects, Puppetry, HotBlood, TimeTravel, + Fantasy, Fight, Funny, Daily, ScienceFiction, Moe, Healing, School, Children, + InstantNoodles, InLove, Girl, Magic, Adventure, History, Fiction, Mecha, GodDemon, + VoiceControl, Sports, Inspirational, Music, Reasoning, Club, WisdomFight, + Tearjerker, Food, Idol, Maiden, Workplace + ) + + PgcType.GuoChuang -> listOf( + All, Original, Comic, Novel, Game, Animation, Puppetry, HotBlood, Fantasy, XuanHuan, + Fight, Funny, MartialArts, Daily, ScienceFiction, Moe, Healing, Suspense, School, + Children, InstantNoodles, InLove, Girl, Magic, History, Mecha, GodDemon, + VoiceControl, Sports, Inspirational, Music, Reasoning, Club, WisdomFight, + Tearjerker, Food, Idol, Maiden, Workplace, AncientStyle + ) + + PgcType.Movie -> listOf( + All, ShortFilm, Plot, Comedy, Love, Action, Terror, ScienceFiction, Offense, + Thriller, Suspense, Fantasy, War, Animation, Biography, Family, Opera, History, + Adventure, Documentary, Disaster, Comic, Novel + ) + + PgcType.Documentary -> listOf( + All, History, Food, Humanities, Technology, Explore, Universal, CutePet, Social, + Animal, Nature, Medical, Military, Disaster, Crime, Mystery, Travel, Sports, Movie + ) + + PgcType.Variety -> listOf( + All, Music, Interview, TalkShow, RealityShow, Selection, Food, Tourism, + EveningParty, Concert, Emotion, Comedy, ParentChild, Culture, Workplace, + CutePet, Cultivate + ) + + PgcType.Tv -> listOf( + All, Plot, Emotion, Funny, Suspense, City, Family, AncientCostume, History, + Fantasy, Youth, War, MartialArts, Inspirational, ShortPlay, ScienceFiction + + ) + } + } +} \ No newline at end of file diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/index/PgcIndexData.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/index/PgcIndexData.kt new file mode 100644 index 00000000..d0e664cb --- /dev/null +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/index/PgcIndexData.kt @@ -0,0 +1,31 @@ +package dev.aaa1115910.biliapi.entity.pgc.index + +import dev.aaa1115910.biliapi.entity.pgc.PgcItem + +data class PgcIndexData( + val list: List, + val nextPage: PgcIndexPage +) { + companion object { + fun fromIndexResultData(data: dev.aaa1115910.biliapi.http.entity.index.IndexResultData): PgcIndexData { + return PgcIndexData( + list = data.list.map { PgcItem.fromIndexResultItem(it) }, + nextPage = PgcIndexPage( + currentPage = data.num, + pageSize = data.size, + totalSize = data.total, + nextPage = data.num + 1, + hasNext = data.hasNext == 1 + ) + ) + } + } + + data class PgcIndexPage( + val currentPage: Int = 1, + val pageSize: Int = 20, + val totalSize: Int = 0, + val nextPage: Int = 1, + val hasNext: Boolean = true + ) +} 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 7376e097..58d94afe 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 @@ -1,6 +1,7 @@ package dev.aaa1115910.biliapi.http import com.tfowl.ktor.client.plugins.JsoupPlugin +import dev.aaa1115910.biliapi.entity.pgc.PgcType import dev.aaa1115910.biliapi.http.entity.BiliResponse import dev.aaa1115910.biliapi.http.entity.BiliResponseWithoutData import dev.aaa1115910.biliapi.http.entity.danmaku.DanmakuData @@ -58,7 +59,6 @@ import dev.aaa1115910.biliapi.http.entity.video.VideoShot import dev.aaa1115910.biliapi.http.entity.web.NavResponseData import dev.aaa1115910.biliapi.http.util.BiliAppConf import dev.aaa1115910.biliapi.http.util.encApiSign -import dev.aaa1115910.biliapi.repositories.PgcType import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.engine.okhttp.OkHttp diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexFilter.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexFilter.kt index 4a89a7ee..d24de7f9 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexFilter.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexFilter.kt @@ -49,6 +49,7 @@ val indexFilterSeasonMonth = mapOf( val indexFilterYear = mapOf( "-1" to "全部", + "[2024,2025)" to "2024", "[2023,2024)" to "2023", "[2022,2023)" to "2022", "[2021,2022)" to "2021", @@ -80,6 +81,7 @@ val indexFilterProducerId get() = IndexFilterProducerId.producerIds val indexFilterReleaseDate = mapOf( "-1" to "全部", + "[2024-01-01 00:00:00,2025-01-01 00:00:00)" to "2024", "[2023-01-01 00:00:00,2024-01-01 00:00:00)" to "2023", "[2022-01-01 00:00:00,2023-01-01 00:00:00)" to "2022", "[2021-01-01 00:00:00,2022-01-01 00:00:00)" to "2021", diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexFilterStyle.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexFilterStyle.kt index 57adb3fe..577a8ad2 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexFilterStyle.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexFilterStyle.kt @@ -58,8 +58,12 @@ object IndexFilterStyle { 10056 to "惊悚", 10057 to "悬疑", 10058 to "战争", + //10059 + 10060 to "传记", 10061 to "家庭", + 10062 to "歌剧", + 10063 to "纪实", 10064 to "灾难", 10065 to "人文", 10066 to "科技", @@ -92,6 +96,7 @@ object IndexFilterStyle { 10090 to "访谈", 10091 to "脱口秀", 10092 to "真人秀", + //10093 10094 to "选秀", 10095 to "旅游", 10096 to "演唱会", @@ -100,6 +105,7 @@ object IndexFilterStyle { 10099 to "养成", 10100 to "文化", + //10101 10102 to "特摄", 10103 to "短剧", 10104 to "短片", diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexOrder.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexOrder.kt index aa86d248..7a0b2087 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexOrder.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/index/IndexOrder.kt @@ -17,4 +17,5 @@ val guochuangIndexOrders by lazy { guochuangIds.map { IndexOrder.entries[it] } } val varietyIndexOrders by lazy { varietyIds.map { IndexOrder.entries[it] } } val tvIndexOrders by lazy { tvIds.map { IndexOrder.entries[it] } } val movieIndexOrders by lazy { movieIds.map { IndexOrder.entries[it] } } -val documentaryIndexOrders by lazy { documentaryIds.map { IndexOrder.entries[it] } } \ No newline at end of file +val documentaryIndexOrders by lazy { documentaryIds.map { IndexOrder.entries[it] } } + diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/IndexRepository.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/IndexRepository.kt deleted file mode 100644 index 25976e1c..00000000 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/IndexRepository.kt +++ /dev/null @@ -1,152 +0,0 @@ -package dev.aaa1115910.biliapi.repositories - -import dev.aaa1115910.biliapi.entity.season.IndexResultData -import dev.aaa1115910.biliapi.entity.season.IndexResultPage -import dev.aaa1115910.biliapi.http.BiliHttpApi -import dev.aaa1115910.biliapi.http.entity.index.IndexOrder - -class IndexRepository { - suspend fun getAnimeIndex( - sort: IndexOrder = IndexOrder.PlayCount, - seasonVersion: Int = -1, - spokenLanguageType: Int = -1, - area: Int = -1, - isFinish: Int = -1, - copyright: Int = -1, - seasonStatus: Int = -1, - seasonMonth: Int = -1, - year: String = "-1", - styleId: Int = -1, - desc: Boolean = true, - page: IndexResultPage = IndexResultPage() - ): IndexResultData { - val biliResponse = BiliHttpApi.seasonIndexAnimeResult( - order = sort.id, - seasonVersion = seasonVersion, - spokenLanguageType = spokenLanguageType, - area = area, - isFinish = isFinish, - copyright = copyright, - seasonStatus = seasonStatus, - seasonMonth = seasonMonth, - year = year, - styleId = styleId, - sort = if (desc) 0 else 1, - page = page.nextPage, - pagesize = 20 - ) - return IndexResultData.fromIndexResultData(biliResponse.getResponseData()) - } - - suspend fun getGuochuangIndex( - sort: IndexOrder = IndexOrder.PlayCount, - seasonVersion: Int = -1, - isFinish: Int = -1, - copyright: Int = -1, - seasonStatus: Int = -1, - year: String = "-1", - styleId: Int = -1, - desc: Boolean = true, - page: IndexResultPage = IndexResultPage() - ): IndexResultData { - val biliResponse = BiliHttpApi.seasonIndexGuochuangResult( - order = sort.id, - seasonVersion = seasonVersion, - isFinish = isFinish, - copyright = copyright, - seasonStatus = seasonStatus, - year = year, - styleId = styleId, - sort = if (desc) 0 else 1, - page = page.nextPage, - pagesize = 20 - ) - return IndexResultData.fromIndexResultData(biliResponse.getResponseData()) - } - - suspend fun getVarietyIndex( - sort: IndexOrder = IndexOrder.PlayCount, - seasonStatus: Int = -1, - styleId: Int = -1, - desc: Boolean = true, - page: IndexResultPage = IndexResultPage() - ): IndexResultData { - val biliResponse = BiliHttpApi.seasonIndexVarietyResult( - order = sort.id, - seasonStatus = seasonStatus, - styleId = styleId, - sort = if (desc) 0 else 1, - page = page.nextPage, - pagesize = 20 - ) - return IndexResultData.fromIndexResultData(biliResponse.getResponseData()) - } - - suspend fun getMovieIndex( - sort: IndexOrder = IndexOrder.PlayCount, - area: Int = -1, - releaseDate: String = "-1", - seasonStatus: Int = -1, - styleId: Int = -1, - desc: Boolean = true, - page: IndexResultPage = IndexResultPage() - ): IndexResultData { - val biliResponse = BiliHttpApi.seasonIndexMovieResult( - order = sort.id, - area = area, - releaseDate = releaseDate, - seasonStatus = seasonStatus, - styleId = styleId, - sort = if (desc) 0 else 1, - page = page.nextPage, - pagesize = 20 - ) - return IndexResultData.fromIndexResultData(biliResponse.getResponseData()) - } - - suspend fun getTvIndex( - sort: IndexOrder = IndexOrder.PlayCount, - area: Int = -1, - releaseDate: String = "-1", - seasonStatus: Int = -1, - styleId: Int = -1, - desc: Boolean = true, - page: IndexResultPage = IndexResultPage() - ): IndexResultData { - val biliResponse = BiliHttpApi.seasonIndexTvResult( - order = sort.id, - area = area, - releaseDate = releaseDate, - seasonStatus = seasonStatus, - styleId = styleId, - sort = if (desc) 0 else 1, - page = page.nextPage, - pagesize = 20 - ) - return IndexResultData.fromIndexResultData(biliResponse.getResponseData()) - } - - suspend fun getDocumentaryIndex( - sort: IndexOrder = IndexOrder.PlayCount, - area: Int = -1, - releaseDate: String = "-1", - seasonStatus: Int = -1, - styleId: Int = -1, - producerId: Int = -1, - desc: Boolean = true, - page: IndexResultPage = IndexResultPage() - ): IndexResultData { - val biliResponse = BiliHttpApi.seasonIndexDocumentaryResult( - order = sort.id, - area = area, - releaseDate = releaseDate, - seasonStatus = seasonStatus, - styleId = styleId, - producerId = producerId, - sort = if (desc) 0 else 1, - page = page.nextPage, - pagesize = 20 - ) - return IndexResultData.fromIndexResultData(biliResponse.getResponseData()) - } -} \ 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 622d3f7e..0696fc64 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 @@ -2,6 +2,21 @@ package dev.aaa1115910.biliapi.repositories import dev.aaa1115910.biliapi.entity.pgc.PgcCarouselData import dev.aaa1115910.biliapi.entity.pgc.PgcFeedData +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.biliapi.entity.pgc.index.Area +import dev.aaa1115910.biliapi.entity.pgc.index.Copyright +import dev.aaa1115910.biliapi.entity.pgc.index.IndexOrder +import dev.aaa1115910.biliapi.entity.pgc.index.IndexOrderType +import dev.aaa1115910.biliapi.entity.pgc.index.IsFinish +import dev.aaa1115910.biliapi.entity.pgc.index.PgcIndexData +import dev.aaa1115910.biliapi.entity.pgc.index.Producer +import dev.aaa1115910.biliapi.entity.pgc.index.ReleaseDate +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonMonth +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonStatus +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonVersion +import dev.aaa1115910.biliapi.entity.pgc.index.SpokenLanguage +import dev.aaa1115910.biliapi.entity.pgc.index.Style +import dev.aaa1115910.biliapi.entity.pgc.index.Year import dev.aaa1115910.biliapi.http.BiliHttpApi class PgcRepository { @@ -29,13 +44,99 @@ class PgcRepository { } return data } -} -enum class PgcType { - Anime, - GuoChuang, - Movie, - Documentary, - Tv, - Variety + suspend fun getPgcIndex( + pgcType: PgcType, + indexOrder: IndexOrder, + indexOrderType: IndexOrderType, + seasonVersion: SeasonVersion, + spokenLanguage: SpokenLanguage, + area: Area, + isFinish: IsFinish, + copyright: Copyright, + seasonStatus: SeasonStatus, + seasonMonth: SeasonMonth, + producer: Producer, + year: Year, + releaseDate: ReleaseDate, + style: Style, + page: PgcIndexData.PgcIndexPage, + ): PgcIndexData { + val data = PgcIndexData.fromIndexResultData( + when (pgcType) { + PgcType.Anime -> BiliHttpApi.seasonIndexAnimeResult( + order = indexOrder.id, + sort = indexOrderType.id, + seasonVersion = seasonVersion.id, + spokenLanguageType = spokenLanguage.id, + area = area.id, + isFinish = isFinish.id, + copyright = copyright.id, + seasonStatus = seasonStatus.id, + seasonMonth = seasonMonth.id, + year = year.str, + styleId = style.id, + page = page.nextPage, + pagesize = page.pageSize + ) + + PgcType.GuoChuang -> BiliHttpApi.seasonIndexGuochuangResult( + order = indexOrder.id, + sort = indexOrderType.id, + seasonVersion = seasonVersion.id, + isFinish = isFinish.id, + copyright = copyright.id, + seasonStatus = seasonStatus.id, + year = year.str, + styleId = style.id, + page = page.nextPage, + pagesize = page.pageSize + ) + + PgcType.Movie -> BiliHttpApi.seasonIndexMovieResult( + order = indexOrder.id, + sort = indexOrderType.id, + area = area.id, + seasonStatus = seasonStatus.id, + releaseDate = releaseDate.str, + styleId = style.id, + page = page.nextPage, + pagesize = page.pageSize + ) + + PgcType.Documentary -> BiliHttpApi.seasonIndexDocumentaryResult( + order = indexOrder.id, + sort = indexOrderType.id, + area = area.id, + seasonStatus = seasonStatus.id, + producerId = producer.id, + releaseDate = releaseDate.str, + styleId = style.id, + page = page.nextPage, + pagesize = page.pageSize + ) + + PgcType.Tv -> BiliHttpApi.seasonIndexTvResult( + order = indexOrder.id, + sort = indexOrderType.id, + area = area.id, + seasonStatus = seasonStatus.id, + releaseDate = releaseDate.str, + styleId = style.id, + page = page.nextPage, + pagesize = page.pageSize + ) + + PgcType.Variety -> BiliHttpApi.seasonIndexVarietyResult( + order = indexOrder.id, + sort = indexOrderType.id, + seasonStatus = seasonStatus.id, + styleId = style.id, + page = page.nextPage, + pagesize = page.pageSize + ) + }.getResponseData() + ) + return data + } } 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 9c5a532f..5547a259 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 @@ -4,7 +4,7 @@ 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.repositories.PgcType +import dev.aaa1115910.biliapi.entity.pgc.PgcType import kotlinx.coroutines.runBlocking import org.junit.jupiter.api.Assertions.assertDoesNotThrow import org.junit.jupiter.api.Test diff --git a/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/IndexRepositoryTest.kt b/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/IndexRepositoryTest.kt deleted file mode 100644 index d867356f..00000000 --- a/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/IndexRepositoryTest.kt +++ /dev/null @@ -1,98 +0,0 @@ -package dev.aaa1115910.biliapi.repositories - -import dev.aaa1115910.biliapi.entity.season.IndexResultPage -import kotlinx.coroutines.runBlocking -import org.junit.jupiter.api.Test - -class IndexRepositoryTest { - - @Test - fun `get indexes`() { - runBlocking { - println("----- get anime index -----") - `get anime index`() - println("----- get guochuang index -----") - `get guochuang index`() - println("----- get variety index -----") - `get variety index`() - println("----- get movie index -----") - `get movie index`() - println("----- get tv index -----") - `get tv index`() - println("----- get documentary index -----") - `get documentary index`() - } - } - - @Test - fun `get anime index`() { - runBlocking { - var page = IndexResultPage() - for (i in 1..5) { - val result = IndexRepository().getAnimeIndex(page = page) - println(result.list.map { it.title }) - page = result.nextPage - } - } - } - - @Test - fun `get guochuang index`() { - runBlocking { - var page = IndexResultPage() - for (i in 1..5) { - val result = IndexRepository().getGuochuangIndex(page = page) - println(result.list.map { it.title }) - page = result.nextPage - } - } - } - - @Test - fun `get variety index`() { - runBlocking { - var page = IndexResultPage() - for (i in 1..5) { - val result = IndexRepository().getVarietyIndex(page = page) - println(result.list.map { it.title }) - page = result.nextPage - } - } - } - - @Test - fun `get movie index`() { - runBlocking { - var page = IndexResultPage() - for (i in 1..5) { - val result = IndexRepository().getMovieIndex(page = page) - println(result.list.map { it.title }) - page = result.nextPage - } - } - } - - @Test - fun `get tv index`() { - runBlocking { - var page = IndexResultPage() - for (i in 1..5) { - val result = IndexRepository().getTvIndex(page = page) - println(result.list.map { it.title }) - page = result.nextPage - } - } - } - - @Test - fun `get documentary index`() { - runBlocking { - var page = IndexResultPage() - for (i in 1..5) { - val result = IndexRepository().getDocumentaryIndex(page = page) - println(result.list.map { it.title }) - page = result.nextPage - } - } - } -} \ No newline at end of file diff --git a/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepositoryTest.kt b/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepositoryTest.kt index 15fe2eee..98b83618 100644 --- a/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepositoryTest.kt +++ b/bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/PgcRepositoryTest.kt @@ -1,5 +1,20 @@ package dev.aaa1115910.biliapi.repositories +import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.biliapi.entity.pgc.index.Area +import dev.aaa1115910.biliapi.entity.pgc.index.Copyright +import dev.aaa1115910.biliapi.entity.pgc.index.IndexOrder +import dev.aaa1115910.biliapi.entity.pgc.index.IndexOrderType +import dev.aaa1115910.biliapi.entity.pgc.index.IsFinish +import dev.aaa1115910.biliapi.entity.pgc.index.PgcIndexData +import dev.aaa1115910.biliapi.entity.pgc.index.Producer +import dev.aaa1115910.biliapi.entity.pgc.index.ReleaseDate +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonMonth +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonStatus +import dev.aaa1115910.biliapi.entity.pgc.index.SeasonVersion +import dev.aaa1115910.biliapi.entity.pgc.index.SpokenLanguage +import dev.aaa1115910.biliapi.entity.pgc.index.Style +import dev.aaa1115910.biliapi.entity.pgc.index.Year import kotlinx.coroutines.runBlocking import kotlin.test.Test @@ -30,4 +45,31 @@ class PgcRepositoryTest { } } } + + @Test + fun `get pgc index`(){ + runBlocking { + PgcType.entries.forEach { pgcType -> + println("pgcType: $pgcType") + val data=pgcRepository.getPgcIndex( + pgcType = pgcType, + indexOrder = IndexOrder.PlayCount, + indexOrderType = IndexOrderType.Desc, + seasonVersion = SeasonVersion.All, + spokenLanguage = SpokenLanguage.All, + area=Area.All, + isFinish = IsFinish.All, + copyright = Copyright.All, + seasonStatus = SeasonStatus.All, + seasonMonth = SeasonMonth.All, + producer = Producer.All, + year = Year.All, + releaseDate = ReleaseDate.All, + style = Style.All, + page = PgcIndexData.PgcIndexPage() + ) + println(data) + } + } + } } \ No newline at end of file From ba61c991074df1a25afdd835366f06d11b9e92d3 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Thu, 28 Nov 2024 19:58:41 +0800 Subject: [PATCH 23/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20PGC=20=E8=BD=AE?= =?UTF-8?q?=E6=92=AD=E5=9B=BE=E5=8F=AF=E8=83=BD=E8=8E=B7=E5=8F=96=E5=A4=B1?= =?UTF-8?q?=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 通过 Koin 注入的 PgcRepository 可能为 null --- .../bv/viewmodel/pgc/PgcViewModel.kt | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) 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 69d8d066..f1da75af 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 @@ -10,13 +10,16 @@ import androidx.lifecycle.viewModelScope import dev.aaa1115910.biliapi.entity.pgc.PgcCarouselData import dev.aaa1115910.biliapi.entity.pgc.PgcFeedData import dev.aaa1115910.biliapi.entity.pgc.PgcItem -import dev.aaa1115910.biliapi.repositories.PgcRepository import dev.aaa1115910.biliapi.entity.pgc.PgcType +import dev.aaa1115910.biliapi.repositories.PgcRepository import dev.aaa1115910.bv.BVApp +import dev.aaa1115910.bv.BuildConfig import dev.aaa1115910.bv.util.fInfo +import dev.aaa1115910.bv.util.fWarn import dev.aaa1115910.bv.util.toast import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -93,6 +96,19 @@ abstract class PgcViewModel( private suspend fun updateCarousel() { logger.fInfo { "Updating $pgcType carousel" } runCatching { + // 由于未知原因,注入的 PgcRepository 可能获取到的对象为 null + var maxRetry = 10 + while (pgcRepository == null && maxRetry > 0) { + delay(10) + maxRetry-- + } + if (BuildConfig.DEBUG && maxRetry != 10) { + logger.fWarn { "Retry ${10 - maxRetry} times to get pgcRepository" } + withContext(Dispatchers.Main) { + "Retry ${10 - maxRetry} times to get pgcRepository($pgcType)".toast(BVApp.context) + } + } + val carouselData = pgcRepository.getCarousel(pgcType) logger.fInfo { "Find $pgcType carousels, size: ${carouselData.items.size}" } carouselItems.addAll(carouselData.items) From 28264c65aea493df6030e3a8d59bb9a7e4025ab9 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Wed, 4 Dec 2024 22:02:33 +0800 Subject: [PATCH 24/27] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E8=AF=A6=E7=BB=86=E9=A1=B5=E6=97=B6=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E5=A4=B1=E8=B4=A5=EF=BC=88Web=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 相关视频可能返回为 null --- .../kotlin/dev/aaa1115910/biliapi/entity/video/VideoDetail.kt | 3 ++- .../dev/aaa1115910/biliapi/http/entity/video/VideoDetail.kt | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/video/VideoDetail.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/video/VideoDetail.kt index 9d0fd5b8..b18efb09 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/video/VideoDetail.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/video/VideoDetail.kt @@ -103,7 +103,8 @@ data class VideoDetail( author = Author.fromVideoOwner(videoDetail.view.owner), pages = videoDetail.view.pages.map { VideoPage.fromVideoPage(it) }, ugcSeason = videoDetail.view.ugcSeason?.let { UgcSeason.fromUgcSeason(it) }, - relatedVideos = videoDetail.related.map { RelatedVideo.fromRelate(it) }, + relatedVideos = videoDetail.related?.map { RelatedVideo.fromRelate(it) } + ?: emptyList(), redirectToEp = videoDetail.view.redirectUrl?.contains("ep") ?: false, epid = videoDetail.view.redirectUrl?.split("ep", "?")?.get(1)?.toInt(), argueTip = videoDetail.view.stat.argueMsg.takeIf { it.isNotEmpty() }, diff --git a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/video/VideoDetail.kt b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/video/VideoDetail.kt index 515f3049..50efaeb8 100644 --- a/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/video/VideoDetail.kt +++ b/bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/video/VideoDetail.kt @@ -18,7 +18,7 @@ data class VideoDetail( //@SerialName("Reply") //val reply:Any @SerialName("Related") - val related: List, + val related: List?, @SerialName("Spec") val spec: JsonObject? = null, @SerialName("hot_share") From 4f0203f4e0a78b8ed1a190dbd3ab908fe9786097 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Wed, 4 Dec 2024 22:02:56 +0800 Subject: [PATCH 25/27] =?UTF-8?q?=E6=96=B0=E5=A2=9E=20UGC=20=E4=B8=BB?= =?UTF-8?q?=E5=88=86=E5=8C=BA=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/kotlin/dev/aaa1115910/bv/BVApp.kt | 2 + .../dev/aaa1115910/bv/component/Carousel.kt | 146 ++++++---- .../dev/aaa1115910/bv/component/TopNav.kt | 57 ++-- .../aaa1115910/bv/component/pgc/Carousel.kt | 77 ----- .../bv/screen/main/PartitionScreen.kt | 9 - .../aaa1115910/bv/screen/main/UgcContent.kt | 152 +++++----- .../bv/screen/main/pgc/PgcCommon.kt | 2 +- .../bv/screen/main/ugc/AnimalContent.kt | 31 +++ .../bv/screen/main/ugc/CarContent.kt | 32 +++ .../bv/screen/main/ugc/CinephileContent.kt | 32 +++ .../bv/screen/main/ugc/DanceContent.kt | 31 +++ .../bv/screen/main/ugc/DougaContent.kt | 31 +++ .../bv/screen/main/ugc/EntContent.kt | 31 +++ .../bv/screen/main/ugc/FashionContent.kt | 30 ++ .../bv/screen/main/ugc/FoodContent.kt | 31 +++ .../bv/screen/main/ugc/GameContent.kt | 31 +++ .../bv/screen/main/ugc/InformationContent.kt | 31 +++ .../bv/screen/main/ugc/KichikuContent.kt | 31 +++ .../bv/screen/main/ugc/KnowledgeContent.kt | 32 +++ .../bv/screen/main/ugc/LifeContent.kt | 31 +++ .../bv/screen/main/ugc/MuiscContent.kt | 32 +++ .../bv/screen/main/ugc/SportsContent.kt | 31 +++ .../bv/screen/main/ugc/TechContent.kt | 31 +++ .../screen/main/ugc/UgcChildRegionButtons.kt | 90 ++++++ .../bv/screen/main/ugc/UgcCommon.kt | 263 ++++++++++++++++++ .../dev/aaa1115910/bv/util/UgcTypeExtends.kt | 148 ++++++++++ .../bv/viewmodel/home/PopularViewModel.kt | 4 +- .../bv/viewmodel/home/RecommendViewModel.kt | 4 +- .../bv/viewmodel/pgc/PgcViewModel.kt | 4 +- app/src/main/res/values/strings.xml | 126 +++++++++ .../aaa1115910/biliapi/entity/CarouselData.kt | 77 +++++ .../biliapi/entity/home/RecommendData.kt | 4 +- .../biliapi/entity/home/RecommendItem.kt | 60 ---- .../biliapi/entity/pgc/PgcCarouselData.kt | 40 --- .../aaa1115910/biliapi/entity/rank/Popular.kt | 49 +--- .../aaa1115910/biliapi/entity/ugc/UgcItem.kt | 102 +++++++ .../aaa1115910/biliapi/entity/ugc/UgcType.kt | 145 ++++++++++ .../entity/ugc/region/UgcRegionData.kt | 20 ++ .../entity/ugc/region/UgcRegionListData.kt | 17 ++ .../entity/ugc/region/UgcRegionPage.kt | 5 + .../aaa1115910/biliapi/http/BiliHttpApi.kt | 62 +++++ .../http/entity/region/RegionDynamic.kt | 90 ++++++ .../http/entity/region/RegionDynamicList.kt | 40 +++ .../biliapi/http/entity/region/RegionLocs.kt | 115 ++++++++ .../biliapi/repositories/PgcRepository.kt | 6 +- .../repositories/RecommendVideoRepository.kt | 11 +- .../biliapi/repositories/UgcRepository.kt | 28 ++ .../aaa1115910/biliapi/util/AvBvConverter.kt | 41 +++ .../dev/aaa1115910/biliapi/util/Extends.kt | 2 + .../dev/aaa1115910/biliapi/util/UrlUtil.kt | 27 ++ .../biliapi/http/BiliHttpApiTest.kt | 59 +++- .../biliapi/repositories/UgcRepositoryTest.kt | 58 ++++ 52 files changed, 2236 insertions(+), 405 deletions(-) delete mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/component/pgc/Carousel.kt delete mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/PartitionScreen.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/AnimalContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/CarContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/CinephileContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/DanceContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/DougaContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/EntContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/FashionContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/FoodContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/GameContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/InformationContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/KichikuContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/KnowledgeContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/LifeContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/MuiscContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/SportsContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/TechContent.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/UgcChildRegionButtons.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/screen/main/ugc/UgcCommon.kt create mode 100644 app/src/main/kotlin/dev/aaa1115910/bv/util/UgcTypeExtends.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/CarouselData.kt delete mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/home/RecommendItem.kt delete mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/pgc/PgcCarouselData.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/UgcItem.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/UgcType.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionData.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionListData.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/entity/ugc/region/UgcRegionPage.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionDynamic.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionDynamicList.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/http/entity/region/RegionLocs.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/repositories/UgcRepository.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/AvBvConverter.kt create mode 100644 bili-api/src/main/kotlin/dev/aaa1115910/biliapi/util/UrlUtil.kt create mode 100644 bili-api/src/test/kotlin/dev/aaa1115910/biliapi/repositories/UgcRepositoryTest.kt 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) + } + } +} From fbadee0e07952fe31344ad5774266ffaeb112a21 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Fri, 6 Dec 2024 19:49:22 +0800 Subject: [PATCH 26/27] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E5=BF=AB=E6=8D=B7=E9=9D=A2=E6=9D=BF=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dev/aaa1115910/bv/component/UserPanel.kt | 99 ++++++------------- .../dev/aaa1115910/bv/screen/MainScreen.kt | 15 +-- 2 files changed, 36 insertions(+), 78 deletions(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt index 7eef8387..3534bcf8 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/UserPanel.kt @@ -4,7 +4,6 @@ import android.view.KeyEvent import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize @@ -13,9 +12,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.GridItemSpan -import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.rounded.CrueltyFree @@ -66,9 +62,8 @@ fun UserPanel( focusRequester.requestFocus(scope) } - Surface( + Box( modifier = modifier - .width(300.dp) .onPreviewKeyEvent { when (it.nativeKeyEvent.keyCode) { KeyEvent.KEYCODE_BACK -> { @@ -77,47 +72,40 @@ fun UserPanel( } } false - }, - shape = MaterialTheme.shapes.medium + } ) { - LazyVerticalGrid( - columns = GridCells.Fixed(2), - contentPadding = PaddingValues(12.dp) + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(24.dp) ) { - item( - span = { GridItemSpan(2) }, - ) { - UserPanelMyItem( - modifier = Modifier - .focusRequester(focusRequester) - .onPreviewKeyEvent { - when (it.nativeKeyEvent.keyCode) { - KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_RIGHT -> { - return@onPreviewKeyEvent true - } - - KeyEvent.KEYCODE_DPAD_LEFT -> { - if (it.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) onHide() - return@onPreviewKeyEvent true - } + UserPanelMyItem( + modifier = Modifier + .width(300.dp) + .focusRequester(focusRequester) + .onPreviewKeyEvent { + when (it.nativeKeyEvent.keyCode) { + KeyEvent.KEYCODE_DPAD_UP, KeyEvent.KEYCODE_DPAD_RIGHT, KeyEvent.KEYCODE_DPAD_LEFT -> { + return@onPreviewKeyEvent true } - false - }, - username = username, - face = face, - onClick = { - onGoMy() - onHide() - } - ) - } - item { + } + false + }, + username = username, + face = face, + onClick = { + onGoMy() + onHide() + } + ) + + val buttonWidth = 120.dp + Row { UserPanelSmallItem( modifier = Modifier + .width(buttonWidth) .onPreviewKeyEvent { when (it.nativeKeyEvent.keyCode) { KeyEvent.KEYCODE_DPAD_LEFT -> { - if (it.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) onHide() return@onPreviewKeyEvent true } } @@ -130,10 +118,9 @@ fun UserPanel( onHide() } ) - } - item { UserPanelSmallItem( - modifier = Modifier, + modifier = Modifier + .width(buttonWidth), title = "私人藏品", icon = Icons.Rounded.FavoriteBorder, onClick = { @@ -141,20 +128,9 @@ fun UserPanel( onHide() } ) - } - item { UserPanelSmallItem( modifier = Modifier - .onPreviewKeyEvent { - println(it.nativeKeyEvent) - when (it.nativeKeyEvent.keyCode) { - KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_DOWN -> { - if (it.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) onHide() - return@onPreviewKeyEvent true - } - } - false - }, + .width(buttonWidth), title = "我追的番", icon = Icons.Rounded.CrueltyFree, onClick = { @@ -162,20 +138,9 @@ fun UserPanel( onHide() } ) - } - item { UserPanelSmallItem( modifier = Modifier - .onPreviewKeyEvent { - println(it.nativeKeyEvent) - when (it.nativeKeyEvent.keyCode) { - KeyEvent.KEYCODE_DPAD_DOWN -> { - if (it.nativeKeyEvent.action == KeyEvent.ACTION_DOWN) onHide() - return@onPreviewKeyEvent true - } - } - false - }, + .width(buttonWidth), title = "现在不看", icon = Icons.Rounded.Schedule, onClick = { @@ -276,7 +241,7 @@ private fun UserPanelSmallItem( } -@Preview +@Preview(device = "id:tv_1080p") @Composable private fun UserPanelPreview() { BVTheme { diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/screen/MainScreen.kt b/app/src/main/kotlin/dev/aaa1115910/bv/screen/MainScreen.kt index 82db186d..0d114c28 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/screen/MainScreen.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/screen/MainScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.scaleIn -import androidx.compose.animation.shrinkHorizontally import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith @@ -26,7 +25,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp @@ -168,23 +166,18 @@ fun MainScreen( Box( modifier = Modifier .fillMaxSize() - .background(Color.Black.copy(alpha = 0.4f)) + .background(Color.Black.copy(alpha = 0.6f)) ) { AnimatedVisibility( modifier = Modifier - .align(Alignment.TopEnd), + .align(Alignment.Center), visible = showUserPanel, enter = fadeIn() + scaleIn(), - exit = shrinkHorizontally() + exit = fadeOut() ) { UserPanel( modifier = Modifier - .padding(12.dp) - .onFocusChanged { - if (!it.hasFocus) { - //settingsButtonFocusRequester.requestFocus() - } - }, + .padding(12.dp), username = userViewModel.username, face = userViewModel.face, onHide = { showUserPanel = false }, From 53dde2c7203994478369d61a478b330ece12a6a6 Mon Sep 17 00:00:00 2001 From: aaa1115910 Date: Fri, 6 Dec 2024 19:50:01 +0800 Subject: [PATCH 27/27] =?UTF-8?q?=E4=BC=98=E5=8C=96=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E5=9C=A8=E5=B0=8F=E5=B0=BA=E5=AF=B8=E4=B8=8B?= =?UTF-8?q?=E7=9A=84=E6=A0=B7=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bv/component/videocard/SmallVideoCard.kt | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt b/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt index 3b973174..98c2bfb6 100644 --- a/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt +++ b/app/src/main/kotlin/dev/aaa1115910/bv/component/videocard/SmallVideoCard.kt @@ -1,12 +1,17 @@ package dev.aaa1115910.bv.component.videocard +import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.background import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row @@ -19,6 +24,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -33,7 +39,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import androidx.tv.material3.Border import androidx.tv.material3.Card @@ -211,7 +216,8 @@ private fun CoverBottomInfo( Text( text = time, style = MaterialTheme.typography.bodySmall, - color = Color.White + color = Color.White, + maxLines = 1 ) } } @@ -224,10 +230,20 @@ fun CardCover( danmaku: String, time: String ) { - Box( + var width by remember { mutableStateOf(200.dp) } + val showInfo by remember { derivedStateOf { width > 160.dp } } + + BoxWithConstraints( modifier = modifier.clip(MaterialTheme.shapes.large), contentAlignment = Alignment.BottomCenter ) { + val boxWithConstraintsScope = this + width = boxWithConstraintsScope.maxWidth + val shadowAlpha by animateFloatAsState( + targetValue = if (showInfo) 0.8f else 0f, + label = "shadow alpha" + ) + AsyncImage( modifier = Modifier .fillMaxWidth() @@ -245,16 +261,22 @@ fun CardCover( Brush.verticalGradient( colors = listOf( Color.Transparent, - Color.Black.copy(alpha = 0.8f) + Color.Black.copy(alpha = shadowAlpha) ) ) ) ) - CoverBottomInfo( - play = play, - danmaku = danmaku, - time = time - ) + AnimatedVisibility( + visible = showInfo, + enter = fadeIn(), + exit = fadeOut() + ) { + CoverBottomInfo( + play = play, + danmaku = danmaku, + time = time + ) + } } }