Skip to content

Commit

Permalink
视频播放页初步适配平板, close #187
Browse files Browse the repository at this point in the history
  • Loading branch information
Him188 committed Apr 17, 2024
1 parent 326296d commit 9993b88
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 52 deletions.
12 changes: 12 additions & 0 deletions app/shared/foundation/common/platform/Context.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@

package me.him188.ani.app.platform

import androidx.compose.foundation.layout.BoxWithConstraintsScope
import androidx.compose.runtime.Composable
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.Stable
import androidx.compose.ui.unit.dp
import java.io.File

expect val LocalContext: ProvidableCompositionLocal<Context>
Expand All @@ -44,6 +47,15 @@ interface ContextFiles {
@Composable
expect fun isInLandscapeMode(): Boolean

@Stable
fun BoxWithConstraintsScope.showTabletUI(): Boolean {
// https://android-developers.googleblog.com/2023/06/detecting-if-device-is-foldable-tablet.html
// 99.96% of phones have a built-in screen with a width smaller than 600dp when in portrait,
// but that same screen size could be the result of a freeform/split-screen window on a tablet or desktop device.

return maxWidth >= 600.dp && maxHeight >= 600.dp
}

/**
* Request to set the fullscreen, landscape mode, hiding status bars and navigation bars.
*
Expand Down
2 changes: 2 additions & 0 deletions app/shared/pages/episode-play/android/EpisodePage.android.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package me.him188.ani.app.ui.subject.episode

import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import me.him188.ani.app.platform.LocalContext
import me.him188.ani.app.ui.foundation.ProvideCompositionLocalsForPreview

@Composable
@Preview(widthDp = 1080 / 3, heightDp = 2400 / 3, showBackground = true)
@Preview(device = Devices.TABLET, showBackground = true)
internal actual fun PreviewEpisodePage() {
ProvideCompositionLocalsForPreview {
val context = LocalContext.current
Expand Down
179 changes: 141 additions & 38 deletions app/shared/pages/episode-play/common/EpisodePage.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
package me.him188.ani.app.ui.subject.episode

import androidx.compose.animation.Crossfade
import androidx.compose.desktop.ui.tooling.preview.Preview
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
Expand All @@ -24,10 +34,12 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.unit.dp
import me.him188.ani.app.navigation.LocalNavigator
import me.him188.ani.app.platform.LocalContext
import me.him188.ani.app.platform.setRequestFullScreen
import me.him188.ani.app.platform.showTabletUI
import me.him188.ani.app.tools.rememberUiMonoTasker
import me.him188.ani.app.ui.external.placeholder.placeholder
import me.him188.ani.app.ui.foundation.LocalIsPreviewing
Expand All @@ -36,6 +48,7 @@ import me.him188.ani.app.ui.foundation.effects.OnLifecycleEvent
import me.him188.ani.app.ui.foundation.effects.ScreenOnEffect
import me.him188.ani.app.ui.foundation.launchInBackground
import me.him188.ani.app.ui.foundation.rememberViewModel
import me.him188.ani.app.ui.subject.episode.details.EpisodeActionRow
import me.him188.ani.app.ui.subject.episode.details.EpisodeDetails
import me.him188.ani.app.ui.subject.episode.details.EpisodePlayerTitle
import me.him188.ani.danmaku.ui.DanmakuConfig
Expand Down Expand Up @@ -77,15 +90,15 @@ fun EpisodePageContent(
vm: EpisodeViewModel,
modifier: Modifier = Modifier,
) {
val context = LocalContext.current

// 处理当用户点击返回键时, 如果是全屏, 则退出全屏
val navigator = LocalNavigator.current
BackHandler {
vm.playerState.pause()
navigator.navigator.goBack()
}

// 按返回退出全屏
val context by rememberUpdatedState(LocalContext.current)
BackHandler(enabled = vm.isFullscreen) {
context.setRequestFullScreen(false)
vm.isFullscreen = false
Expand All @@ -95,44 +108,84 @@ fun EpisodePageContent(

AutoPauseEffect(vm)

BoxWithConstraints(modifier) {
Crossfade(
targetState = showTabletUI(),
modifier = Modifier.fillMaxSize()
) { isTablet ->
if (isTablet) {
EpisodePageContentTablet(vm, Modifier.fillMaxSize())
} else {
EpisodePageContentPhone(vm, Modifier.fillMaxSize())
}
}
}
}

Column(modifier.then(if (vm.isFullscreen) Modifier.fillMaxSize() else Modifier.navigationBarsPadding())) {
// 视频
val selected by vm.mediaSelected.collectAsStateWithLifecycle(false)
val danmakuConfig by vm.danmaku.config.collectAsStateWithLifecycle(DanmakuConfig.Default)

val danmakuEnabled by vm.danmaku.enabled.collectAsStateWithLifecycle(false)
EpisodeVideo(
vm.playerState,
expanded = vm.isFullscreen,
title = {
val episode = vm.episodePresentation
val subject = vm.subjectPresentation
EpisodePlayerTitle(
episode.sort,
episode.title,
subject.title,
modifier.placeholder(episode.isPlaceholder || subject.isPlaceholder)
)
},
danmakuHostState = vm.danmaku.danmakuHostState,
videoSourceSelected = { selected },
danmakuConfig = { danmakuConfig },
onClickFullScreen = {
if (vm.isFullscreen) {
context.setRequestFullScreen(false)
vm.isFullscreen = false
} else {
vm.isFullscreen = true
context.setRequestFullScreen(true)
@Composable
private fun EpisodePageContentTablet(
vm: EpisodeViewModel,
modifier: Modifier = Modifier,
) {
Row(
modifier
.then(
if (vm.isFullscreen) Modifier.fillMaxSize()
else Modifier.navigationBarsPadding()
.verticalScroll(rememberScrollState())
),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
Column(Modifier.weight(1f)) {
EpisodeVideo(
vm,
expanded = true,
maintainAspectRatio = true,
initialControllerVisible = true,
modifier = Modifier.fillMaxWidth()
)

if (vm.isFullscreen) {
return@Row
}

EpisodeDetails(
vm,
LocalSnackbar.current,
Modifier.fillMaxWidth(),
actionRow = {
EpisodeActionRow(
vm,
snackbar = LocalSnackbar.current,
Modifier.width(400.dp),
)
}
},
danmakuEnabled = { danmakuEnabled },
setDanmakuEnabled = { vm.launchInBackground { danmaku.setEnabled(it) } },
onSendDanmaku = {},
modifier = Modifier.fillMaxWidth().background(Color.Black)
.then(if (vm.isFullscreen) Modifier.fillMaxSize() else Modifier.statusBarsPadding())
)
)
}

if (vm.isFullscreen) {
return@Row
}

Column(Modifier.width(240.dp)) {
// 选集
Text(
"这里是选集, 快有了",
Modifier.padding(16.dp),
style = MaterialTheme.typography.labelLarge,
fontStyle = FontStyle.Italic
)
}
}
}

@Composable
private fun EpisodePageContentPhone(
vm: EpisodeViewModel,
modifier: Modifier = Modifier,
) {
Column(modifier.then(if (vm.isFullscreen) Modifier.fillMaxSize() else Modifier.navigationBarsPadding())) {
EpisodeVideo(vm, vm.isFullscreen, Modifier)

if (vm.isFullscreen) {
return@Column
Expand Down Expand Up @@ -179,6 +232,56 @@ fun EpisodePageContent(
}
}

@Composable
private fun EpisodeVideo(
vm: EpisodeViewModel,
expanded: Boolean,
modifier: Modifier = Modifier,
maintainAspectRatio: Boolean = !expanded,
initialControllerVisible: Boolean = false,
) {
val context by rememberUpdatedState(LocalContext.current)

// 视频
val selected by vm.mediaSelected.collectAsStateWithLifecycle(false)
val danmakuConfig by vm.danmaku.config.collectAsStateWithLifecycle(DanmakuConfig.Default)

val danmakuEnabled by vm.danmaku.enabled.collectAsStateWithLifecycle(false)
EpisodeVideo(
vm.playerState,
expanded = expanded,
maintainAspectRatio = maintainAspectRatio,
title = {
val episode = vm.episodePresentation
val subject = vm.subjectPresentation
EpisodePlayerTitle(
episode.sort,
episode.title,
subject.title,
modifier.placeholder(episode.isPlaceholder || subject.isPlaceholder)
)
},
danmakuHostState = vm.danmaku.danmakuHostState,
videoSourceSelected = { selected },
danmakuConfig = { danmakuConfig },
onClickFullScreen = {
if (vm.isFullscreen) {
context.setRequestFullScreen(false)
vm.isFullscreen = false
} else {
vm.isFullscreen = true
context.setRequestFullScreen(true)
}
},
danmakuEnabled = { danmakuEnabled },
setDanmakuEnabled = { vm.launchInBackground { danmaku.setEnabled(it) } },
onSendDanmaku = {},
initialControllerVisible = initialControllerVisible,
modifier = Modifier.fillMaxWidth().background(Color.Black)
.then(if (expanded) Modifier.fillMaxSize() else Modifier.statusBarsPadding())
)
}

/**
* 切后台自动暂停
*/
Expand Down
5 changes: 4 additions & 1 deletion app/shared/pages/episode-play/common/EpisodeVideo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,14 +70,17 @@ internal fun EpisodeVideo(
setDanmakuEnabled: (enabled: Boolean) -> Unit,
onSendDanmaku: (text: String) -> Unit,
modifier: Modifier = Modifier,
maintainAspectRatio: Boolean = !expanded,
initialControllerVisible: Boolean = false,
) {
// Don't rememberSavable. 刻意让每次切换都是隐藏的
var controllerVisible by remember { mutableStateOf(false) }
var controllerVisible by remember { mutableStateOf(initialControllerVisible) }
var isLocked by remember { mutableStateOf(false) }
var showSettings by remember { mutableStateOf(false) }

VideoScaffold(
expanded = expanded,
maintainAspectRatio = maintainAspectRatio,
modifier = modifier,
controllersVisible = { controllerVisible },
gestureLocked = { isLocked },
Expand Down
13 changes: 6 additions & 7 deletions app/shared/pages/episode-play/common/details/EpisodeActionRow.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ 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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
Expand Down Expand Up @@ -91,7 +90,7 @@ fun EpisodeActionRow(
modifier: Modifier = Modifier,
) {
Row(
modifier.fillMaxWidth(),
modifier,
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.Top,
) {
Expand All @@ -104,28 +103,28 @@ fun EpisodeActionRow(
ActionButton(
onClick = onClickCopyLink,
icon = { Icon(Icons.Rounded.ContentCopy, null) },
text = { Text("复制磁力") },
text = { Text("复制磁力", maxLines = 1, softWrap = false) },
Modifier.weight(1f),
)

ActionButton(
onClick = onClickCache,
icon = { Icon(Icons.Rounded.Download, null) },
text = { Text("缓存") },
text = { Text("缓存", maxLines = 1, softWrap = false) },
Modifier.weight(1f),
)

ActionButton(
onClick = onClickDownload,
icon = { Icon(Icons.Rounded.Outbox, null) },
text = { Text("外部下载") },
text = { Text("外部下载", maxLines = 1, softWrap = false) },
Modifier.weight(1f),
)

ActionButton(
onClick = onClickOriginalPage,
icon = { Icon(Icons.Rounded.ArrowOutward, null) },
text = { Text("原始页面") },
text = { Text("原始页面", maxLines = 1, softWrap = false) },
Modifier.weight(1f),
)
}
Expand All @@ -144,7 +143,7 @@ private fun MediaSelectionAction(
ActionButton(
onClick = { onClick() },
icon = { Icon(Icons.Rounded.DisplaySettings, null) },
text = { Text("数据源") },
text = { Text("数据源", maxLines = 1, softWrap = false) },
modifier,
isLoading
)
Expand Down
14 changes: 9 additions & 5 deletions app/shared/pages/episode-play/common/details/EpisodeDetails.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ fun EpisodeDetails(
viewModel: EpisodeViewModel,
snackbar: SnackbarHostState,
modifier: Modifier = Modifier,
actionRow: @Composable () -> Unit = {
EpisodeActionRow(
viewModel,
snackbar = snackbar,
)
},
) {
Column(modifier) {
// 标题
Expand All @@ -63,11 +69,9 @@ fun EpisodeDetails(
Column(Modifier.padding(vertical = 16.dp).fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(16.dp)) {
NowPlayingLabel(viewModel, Modifier.padding(horizontal = PAGE_HORIZONTAL_PADDING).fillMaxWidth())

EpisodeActionRow(
viewModel,
snackbar = snackbar,
Modifier.padding(horizontal = PAGE_HORIZONTAL_PADDING),
)
Row(Modifier.padding(horizontal = PAGE_HORIZONTAL_PADDING)) {
actionRow()
}

if (viewModel.mediaSelectorVisible) {
ModalBottomSheet(onDismissRequest = { viewModel.mediaSelectorVisible = false }) {
Expand Down
Loading

0 comments on commit 9993b88

Please sign in to comment.