加入短视频
This commit is contained in:
@@ -62,7 +62,12 @@ dependencies {
|
|||||||
implementation(libs.androidx.navigation.compose)
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation (libs.androidx.paging.compose)
|
implementation (libs.androidx.paging.compose)
|
||||||
implementation(libs.androidx.paging.runtime)
|
implementation(libs.androidx.paging.runtime)
|
||||||
implementation("com.google.maps.android:maps-compose:4.3.3")
|
implementation(libs.maps.compose)
|
||||||
|
implementation(libs.accompanist.systemuicontroller)
|
||||||
|
implementation(libs.androidx.media3.exoplayer) // 核心播放器
|
||||||
|
implementation(libs.androidx.media3.ui) // UI组件(可选)
|
||||||
|
implementation(libs.androidx.media3.session)
|
||||||
|
implementation(libs.androidx.activity.ktx) // 用于媒体会话(可选)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.aiosman.riderpro
|
package com.aiosman.riderpro
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.app.StatusBarManager
|
import android.app.StatusBarManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.HorizontalScrollView
|
import android.widget.HorizontalScrollView
|
||||||
@@ -8,6 +9,9 @@ import androidx.activity.ComponentActivity
|
|||||||
import androidx.activity.SystemBarStyle
|
import androidx.activity.SystemBarStyle
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.foundation.BorderStroke
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
@@ -60,6 +64,7 @@ import androidx.navigation.compose.composable
|
|||||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||||
import androidx.navigation.compose.rememberNavController
|
import androidx.navigation.compose.rememberNavController
|
||||||
import com.aiosman.riderpro.ui.theme.RiderProTheme
|
import com.aiosman.riderpro.ui.theme.RiderProTheme
|
||||||
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import com.google.android.gms.maps.model.CameraPosition
|
import com.google.android.gms.maps.model.CameraPosition
|
||||||
import com.google.android.gms.maps.model.LatLng
|
import com.google.android.gms.maps.model.LatLng
|
||||||
import com.google.maps.android.compose.GoogleMap
|
import com.google.maps.android.compose.GoogleMap
|
||||||
@@ -82,7 +87,14 @@ class MainActivity : ComponentActivity() {
|
|||||||
@Composable
|
@Composable
|
||||||
fun NavigationController(navController: NavHostController){
|
fun NavigationController(navController: NavHostController){
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController, startDestination = NavigationItem.Home.route){
|
navController = navController,
|
||||||
|
startDestination = NavigationItem.Home.route,
|
||||||
|
enterTransition = {
|
||||||
|
fadeIn(animationSpec = tween(300))
|
||||||
|
},
|
||||||
|
exitTransition = {
|
||||||
|
fadeOut(animationSpec = tween(300))
|
||||||
|
}){
|
||||||
composable(route = NavigationItem.Home.route){
|
composable(route = NavigationItem.Home.route){
|
||||||
Home()
|
Home()
|
||||||
}
|
}
|
||||||
@@ -93,7 +105,7 @@ fun NavigationController(navController: NavHostController){
|
|||||||
Add()
|
Add()
|
||||||
}
|
}
|
||||||
composable(route = NavigationItem.Message.route){
|
composable(route = NavigationItem.Message.route){
|
||||||
Message()
|
Video()
|
||||||
}
|
}
|
||||||
composable(route = NavigationItem.Profile.route){
|
composable(route = NavigationItem.Profile.route){
|
||||||
Profile()
|
Profile()
|
||||||
@@ -124,13 +136,31 @@ fun Navigation(){
|
|||||||
){
|
){
|
||||||
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
val navBackStackEntry by navController.currentBackStackEntryAsState()
|
||||||
val currentRoute = navBackStackEntry?.destination?.route
|
val currentRoute = navBackStackEntry?.destination?.route
|
||||||
|
val systemUiController = rememberSystemUiController()
|
||||||
item.forEach{ it ->
|
item.forEach{ it ->
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
selected = currentRoute == it.route ,
|
selected = currentRoute == it.route ,
|
||||||
onClick = {
|
onClick = {
|
||||||
if(currentRoute != it.route){
|
if(currentRoute != it.route){
|
||||||
navController.navigate(it.route)
|
navController.navigate(it.route)
|
||||||
}
|
}
|
||||||
|
when (it.route) {
|
||||||
|
NavigationItem.Add.route -> {
|
||||||
|
systemUiController.setSystemBarsColor(
|
||||||
|
color = Color.Black
|
||||||
|
)
|
||||||
|
}
|
||||||
|
NavigationItem.Message.route -> {
|
||||||
|
systemUiController.setSystemBarsColor(
|
||||||
|
color = Color.Black
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
systemUiController.setSystemBarsColor(
|
||||||
|
color = Color.Transparent
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
colors = NavigationBarItemColors(
|
colors = NavigationBarItemColors(
|
||||||
selectedIconColor = Color.Red,
|
selectedIconColor = Color.Red,
|
||||||
@@ -193,6 +223,17 @@ fun Add(){
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Video(){
|
||||||
|
Column (
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
verticalArrangement = Arrangement.Top,
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
){
|
||||||
|
ShortVideo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Message(){
|
fun Message(){
|
||||||
Column (
|
Column (
|
||||||
|
|||||||
14
app/src/main/java/com/aiosman/riderpro/ModifierExp.kt
Normal file
14
app/src/main/java/com/aiosman/riderpro/ModifierExp.kt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package com.aiosman.riderpro
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
|
|
||||||
|
inline fun Modifier.noRippleClickable(crossinline onClick: () -> Unit): Modifier = composed {
|
||||||
|
this.clickable(indication = null,
|
||||||
|
interactionSource = remember { MutableInteractionSource() }) {
|
||||||
|
onClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
232
app/src/main/java/com/aiosman/riderpro/Pager.kt
Normal file
232
app/src/main/java/com/aiosman/riderpro/Pager.kt
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
package com.aiosman.riderpro
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.Animatable
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
|
import androidx.compose.foundation.gestures.draggable
|
||||||
|
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.layout.Layout
|
||||||
|
import androidx.compose.ui.layout.Measurable
|
||||||
|
import androidx.compose.ui.layout.ParentDataModifier
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
class PagerState(
|
||||||
|
currentPage: Int = 0,
|
||||||
|
minPage: Int = 0,
|
||||||
|
maxPage: Int = 0
|
||||||
|
) {
|
||||||
|
private var _minPage by mutableStateOf(minPage)
|
||||||
|
var minPage: Int
|
||||||
|
get() = _minPage
|
||||||
|
set(value) {
|
||||||
|
_minPage = value.coerceAtMost(_maxPage)
|
||||||
|
_currentPage = _currentPage.coerceIn(_minPage, _maxPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _maxPage by mutableStateOf(maxPage, structuralEqualityPolicy())
|
||||||
|
var maxPage: Int
|
||||||
|
get() = _maxPage
|
||||||
|
set(value) {
|
||||||
|
_maxPage = value.coerceAtLeast(_minPage)
|
||||||
|
_currentPage = _currentPage.coerceIn(_minPage, maxPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _currentPage by mutableStateOf(currentPage.coerceIn(minPage, maxPage))
|
||||||
|
var currentPage: Int
|
||||||
|
get() = _currentPage
|
||||||
|
set(value) {
|
||||||
|
_currentPage = value.coerceIn(minPage, maxPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class SelectionState { Selected, Undecided }
|
||||||
|
|
||||||
|
var selectionState by mutableStateOf(SelectionState.Selected)
|
||||||
|
|
||||||
|
suspend inline fun <R> selectPage(block: PagerState.() -> R): R = try {
|
||||||
|
selectionState = SelectionState.Undecided
|
||||||
|
block()
|
||||||
|
} finally {
|
||||||
|
selectPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun selectPage() {
|
||||||
|
currentPage -= currentPageOffset.roundToInt()
|
||||||
|
snapToOffset(0f)
|
||||||
|
selectionState = SelectionState.Selected
|
||||||
|
}
|
||||||
|
|
||||||
|
private var _currentPageOffset = Animatable(0f).apply {
|
||||||
|
updateBounds(-1f, 1f)
|
||||||
|
}
|
||||||
|
val currentPageOffset: Float
|
||||||
|
get() = _currentPageOffset.value
|
||||||
|
|
||||||
|
suspend fun snapToOffset(offset: Float) {
|
||||||
|
val max = if (currentPage == minPage) 0f else 1f
|
||||||
|
val min = if (currentPage == maxPage) 0f else -1f
|
||||||
|
_currentPageOffset.snapTo(offset.coerceIn(min, max))
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fling(velocity: Float) {
|
||||||
|
if (velocity < 0 && currentPage == maxPage) return
|
||||||
|
if (velocity > 0 && currentPage == minPage) return
|
||||||
|
|
||||||
|
// 根据 fling 的方向滑动到下一页或上一页
|
||||||
|
_currentPageOffset.animateTo(velocity)
|
||||||
|
selectPage()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "PagerState{minPage=$minPage, maxPage=$maxPage, " +
|
||||||
|
"currentPage=$currentPage, currentPageOffset=$currentPageOffset}"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
private data class PageData(val page: Int) : ParentDataModifier {
|
||||||
|
override fun Density.modifyParentData(parentData: Any?): Any? = this@PageData
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Measurable.page: Int
|
||||||
|
get() = (parentData as? PageData)?.page ?: error("no PageData for measurable $this")
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Pager(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
state: PagerState,
|
||||||
|
orientation: Orientation = Orientation.Horizontal,
|
||||||
|
offscreenLimit: Int = 2,
|
||||||
|
horizontalAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, // 新增水平对齐参数
|
||||||
|
verticalAlignment: Alignment.Vertical = Alignment.CenterVertically, // 新增垂直对齐参数
|
||||||
|
content: @Composable PagerScope.() -> Unit
|
||||||
|
) {
|
||||||
|
var pageSize by remember { mutableStateOf(0) }
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
Layout(
|
||||||
|
content = {
|
||||||
|
// 根据 offscreenLimit 计算页面范围
|
||||||
|
val minPage = maxOf(state.currentPage - offscreenLimit, state.minPage)
|
||||||
|
val maxPage = minOf(state.currentPage + offscreenLimit, state.maxPage)
|
||||||
|
|
||||||
|
for (page in minPage..maxPage) {
|
||||||
|
val pageData = PageData(page)
|
||||||
|
val scope = PagerScope(state, page)
|
||||||
|
key(pageData) {
|
||||||
|
Column(modifier = pageData) {
|
||||||
|
scope.content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = modifier.draggable(
|
||||||
|
orientation = orientation,
|
||||||
|
onDragStarted = {
|
||||||
|
state.selectionState = PagerState.SelectionState.Undecided
|
||||||
|
},
|
||||||
|
onDragStopped = { velocity ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
// 根据速度判断是否滑动到下一页
|
||||||
|
val threshold = 1000f // 速度阈值,可调整
|
||||||
|
if (velocity > threshold) {
|
||||||
|
state.fling(1f) // 向右滑动
|
||||||
|
} else if (velocity < -threshold) {
|
||||||
|
state.fling(-1f) // 向左滑动
|
||||||
|
} else {
|
||||||
|
state.fling(0f) // 保持当前页
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state = rememberDraggableState { dy ->
|
||||||
|
coroutineScope.launch {
|
||||||
|
with(state) {
|
||||||
|
val pos = pageSize * currentPageOffset
|
||||||
|
val max = if (currentPage == minPage) 0 else pageSize
|
||||||
|
val min = if (currentPage == maxPage) 0 else -pageSize
|
||||||
|
|
||||||
|
// 直接将手指的位移应用到 currentPageOffset
|
||||||
|
val newPos = (pos + dy).coerceIn(min.toFloat(), max.toFloat())
|
||||||
|
snapToOffset(newPos / pageSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
) { measurables, constraints ->
|
||||||
|
layout(constraints.maxWidth, constraints.maxHeight) {
|
||||||
|
val currentPage = state.currentPage
|
||||||
|
val offset = state.currentPageOffset
|
||||||
|
val childConstraints = constraints.copy(minWidth = 0, minHeight = 0)
|
||||||
|
|
||||||
|
measurables.forEach { measurable ->
|
||||||
|
val placeable = measurable.measure(childConstraints)
|
||||||
|
val page = measurable.page
|
||||||
|
|
||||||
|
// 根据对齐参数计算 x 和 y 位置
|
||||||
|
val xPosition = when (horizontalAlignment) {
|
||||||
|
Alignment.Start -> 0
|
||||||
|
Alignment.CenterHorizontally -> (constraints.maxWidth - placeable.width) / 2
|
||||||
|
Alignment.End -> constraints.maxWidth - placeable.width
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val yPosition = when (verticalAlignment) {
|
||||||
|
Alignment.Top -> 0
|
||||||
|
Alignment.CenterVertically -> (constraints.maxHeight - placeable.height) / 2
|
||||||
|
Alignment.Bottom -> constraints.maxHeight - placeable.height
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentPage == page) { // 只在当前页面设置 pageSize,避免不必要的设置
|
||||||
|
pageSize = if (orientation == Orientation.Horizontal) {
|
||||||
|
placeable.width
|
||||||
|
} else {
|
||||||
|
placeable.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val isVisible = abs(page - (currentPage - offset)) <= 1
|
||||||
|
|
||||||
|
if (isVisible) {
|
||||||
|
// 修正 x 的计算
|
||||||
|
val xOffset = if (orientation == Orientation.Horizontal) {
|
||||||
|
((page - currentPage) * pageSize + offset * pageSize).roundToInt()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 placeRelative 进行放置
|
||||||
|
placeable.placeRelative(
|
||||||
|
x = xPosition + xOffset,
|
||||||
|
y = yPosition + if (orientation == Orientation.Vertical) ((page - (currentPage - offset)) * placeable.height).roundToInt() else 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PagerScope(
|
||||||
|
private val state: PagerState,
|
||||||
|
val page: Int
|
||||||
|
) {
|
||||||
|
val currentPage: Int
|
||||||
|
get() = state.currentPage
|
||||||
|
|
||||||
|
val currentPageOffset: Float
|
||||||
|
get() = state.currentPageOffset
|
||||||
|
|
||||||
|
val selectionState: PagerState.SelectionState
|
||||||
|
get() = state.selectionState
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
27
app/src/main/java/com/aiosman/riderpro/ShortVideo.kt
Normal file
27
app/src/main/java/com/aiosman/riderpro/ShortVideo.kt
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package com.aiosman.riderpro
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import com.aiosman.riderpro.ShortViewCompose
|
||||||
|
import com.aiosman.riderpro.ui.theme.RiderProTheme
|
||||||
|
|
||||||
|
val videoUrls = listOf(
|
||||||
|
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4",
|
||||||
|
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4",
|
||||||
|
"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/WeAreGoingOnBullrun.mp4"
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShortVideo() {
|
||||||
|
RiderProTheme {
|
||||||
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
|
ShortViewCompose(
|
||||||
|
videoItemsUrl = videoUrls,
|
||||||
|
clickItemPosition = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
180
app/src/main/java/com/aiosman/riderpro/ShortViewCompose.kt
Normal file
180
app/src/main/java/com/aiosman/riderpro/ShortViewCompose.kt
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
package com.aiosman.riderpro
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.net.Uri
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import androidx.annotation.OptIn
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
|
import androidx.media3.common.C
|
||||||
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.Player
|
||||||
|
import androidx.media3.common.util.UnstableApi
|
||||||
|
import androidx.media3.common.util.Util
|
||||||
|
import androidx.media3.datasource.DataSource
|
||||||
|
import androidx.media3.datasource.DefaultDataSourceFactory
|
||||||
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.exoplayer.SimpleExoPlayer
|
||||||
|
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||||
|
import androidx.media3.ui.AspectRatioFrameLayout
|
||||||
|
import androidx.media3.ui.PlayerView
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ShortViewCompose(
|
||||||
|
videoItemsUrl:List<String>,
|
||||||
|
clickItemPosition:Int = 0,
|
||||||
|
videoHeader:@Composable () -> Unit = {},
|
||||||
|
videoBottom:@Composable () -> Unit = {}
|
||||||
|
) {
|
||||||
|
val pagerState: PagerState = run {
|
||||||
|
remember {
|
||||||
|
PagerState(clickItemPosition, 0, videoItemsUrl.size - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val initialLayout= remember {
|
||||||
|
mutableStateOf(true)
|
||||||
|
}
|
||||||
|
val pauseIconVisibleState = remember {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
Pager(
|
||||||
|
state = pagerState,
|
||||||
|
orientation = Orientation.Vertical,
|
||||||
|
offscreenLimit = 1
|
||||||
|
) {
|
||||||
|
pauseIconVisibleState.value=false
|
||||||
|
SingleVideoItemContent(videoItemsUrl[page],
|
||||||
|
pagerState,
|
||||||
|
page,
|
||||||
|
initialLayout,
|
||||||
|
pauseIconVisibleState,
|
||||||
|
videoHeader,
|
||||||
|
videoBottom)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(clickItemPosition){
|
||||||
|
delay(300)
|
||||||
|
initialLayout.value=false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun SingleVideoItemContent(
|
||||||
|
videoUrl: String,
|
||||||
|
pagerState: PagerState,
|
||||||
|
pager: Int,
|
||||||
|
initialLayout: MutableState<Boolean>,
|
||||||
|
pauseIconVisibleState: MutableState<Boolean>,
|
||||||
|
VideoHeader: @Composable() () -> Unit,
|
||||||
|
VideoBottom: @Composable() () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(modifier = Modifier.fillMaxSize()){
|
||||||
|
VideoPlayer(videoUrl,pagerState,pager,pauseIconVisibleState)
|
||||||
|
VideoHeader.invoke()
|
||||||
|
Box(modifier = Modifier.align(Alignment.BottomStart)){
|
||||||
|
VideoBottom.invoke()
|
||||||
|
}
|
||||||
|
if (initialLayout.value) {
|
||||||
|
Box(modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(color = Color.Black))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(UnstableApi::class)
|
||||||
|
@Composable
|
||||||
|
fun VideoPlayer(
|
||||||
|
videoUrl: String,
|
||||||
|
pagerState: PagerState,
|
||||||
|
pager: Int,
|
||||||
|
pauseIconVisibleState: MutableState<Boolean>,
|
||||||
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val scope= rememberCoroutineScope()
|
||||||
|
|
||||||
|
val exoPlayer = remember {
|
||||||
|
SimpleExoPlayer.Builder(context)
|
||||||
|
.build()
|
||||||
|
.apply {
|
||||||
|
val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(
|
||||||
|
context,
|
||||||
|
Util.getUserAgent(context, context.packageName)
|
||||||
|
)
|
||||||
|
val source = ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||||
|
.createMediaSource(MediaItem.fromUri(Uri.parse(videoUrl)))
|
||||||
|
|
||||||
|
this.prepare(source)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pager == pagerState.currentPage) {
|
||||||
|
exoPlayer.playWhenReady = true
|
||||||
|
exoPlayer.play()
|
||||||
|
} else {
|
||||||
|
exoPlayer.pause()
|
||||||
|
}
|
||||||
|
exoPlayer.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
|
||||||
|
exoPlayer.repeatMode = Player.REPEAT_MODE_ONE
|
||||||
|
DisposableEffect(
|
||||||
|
Box(modifier = Modifier.fillMaxSize()){
|
||||||
|
AndroidView(factory = {
|
||||||
|
PlayerView(context).apply {
|
||||||
|
hideController()
|
||||||
|
useController = false
|
||||||
|
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||||
|
|
||||||
|
player = exoPlayer
|
||||||
|
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
|
}
|
||||||
|
},modifier = Modifier.noRippleClickable {
|
||||||
|
pauseIconVisibleState.value=true
|
||||||
|
exoPlayer.pause()
|
||||||
|
scope.launch {
|
||||||
|
delay(500)
|
||||||
|
if (exoPlayer.isPlaying) {
|
||||||
|
exoPlayer.pause()
|
||||||
|
} else {
|
||||||
|
pauseIconVisibleState.value=false
|
||||||
|
exoPlayer.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (pauseIconVisibleState.value)
|
||||||
|
Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.size(80.dp))
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
onDispose {
|
||||||
|
exoPlayer.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
100
app/src/main/java/com/aiosman/riderpro/StatusBarExp.kt
Normal file
100
app/src/main/java/com/aiosman/riderpro/StatusBarExp.kt
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package com.aiosman.riderpro
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.drawable.ColorDrawable
|
||||||
|
import android.os.Build
|
||||||
|
import android.util.TypedValue
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.annotation.ColorRes
|
||||||
|
//import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
|
||||||
|
private const val COLOR_TRANSPARENT = 0
|
||||||
|
|
||||||
|
@SuppressLint("ObsoleteSdkInt")
|
||||||
|
@JvmOverloads
|
||||||
|
fun Activity.immersive(@ColorInt color: Int = COLOR_TRANSPARENT, darkMode: Boolean? = null) {
|
||||||
|
when {
|
||||||
|
Build.VERSION.SDK_INT >= 21 -> {
|
||||||
|
when (color) {
|
||||||
|
COLOR_TRANSPARENT -> {
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||||
|
var systemUiVisibility = window.decorView.systemUiVisibility
|
||||||
|
systemUiVisibility = systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
systemUiVisibility = systemUiVisibility or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
window.decorView.systemUiVisibility = systemUiVisibility
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
window.statusBarColor = color
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||||
|
var systemUiVisibility = window.decorView.systemUiVisibility
|
||||||
|
systemUiVisibility = systemUiVisibility and View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
systemUiVisibility = systemUiVisibility and View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
window.decorView.systemUiVisibility = systemUiVisibility
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||||
|
window.statusBarColor = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Build.VERSION.SDK_INT >= 19 -> {
|
||||||
|
window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||||
|
if (color != COLOR_TRANSPARENT) {
|
||||||
|
setTranslucentView(window.decorView as ViewGroup, color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (darkMode != null) {
|
||||||
|
darkMode(darkMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun Activity.darkMode(darkMode: Boolean = true) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
var systemUiVisibility = window.decorView.systemUiVisibility
|
||||||
|
systemUiVisibility = if (darkMode) {
|
||||||
|
systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||||
|
} else {
|
||||||
|
systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()
|
||||||
|
}
|
||||||
|
window.decorView.systemUiVisibility = systemUiVisibility
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Context.setTranslucentView(container: ViewGroup, color: Int) {
|
||||||
|
if (Build.VERSION.SDK_INT >= 19) {
|
||||||
|
var simulateStatusBar: View? = container.findViewById(android.R.id.custom)
|
||||||
|
if (simulateStatusBar == null && color != 0) {
|
||||||
|
simulateStatusBar = View(container.context)
|
||||||
|
simulateStatusBar.id = android.R.id.custom
|
||||||
|
val lp = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, statusBarHeight)
|
||||||
|
container.addView(simulateStatusBar, lp)
|
||||||
|
}
|
||||||
|
simulateStatusBar?.setBackgroundColor(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val Context?.statusBarHeight: Int
|
||||||
|
get() {
|
||||||
|
this ?: return 0
|
||||||
|
var result = 24
|
||||||
|
val resId = resources.getIdentifier("status_bar_height", "dimen", "android")
|
||||||
|
result = if (resId > 0) {
|
||||||
|
resources.getDimensionPixelSize(resId)
|
||||||
|
} else {
|
||||||
|
TypedValue.applyDimension(
|
||||||
|
TypedValue.COMPLEX_UNIT_DIP,
|
||||||
|
result.toFloat(), Resources.getSystem().displayMetrics
|
||||||
|
).toInt()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
@@ -2,4 +2,4 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.jetbrains.kotlin.android) apply false
|
alias(libs.plugins.jetbrains.kotlin.android) apply false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
[versions]
|
[versions]
|
||||||
|
accompanistSystemuicontroller = "0.27.0"
|
||||||
agp = "8.4.0"
|
agp = "8.4.0"
|
||||||
kotlin = "1.9.0"
|
kotlin = "1.9.0"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
@@ -8,12 +9,19 @@ espressoCore = "3.5.1"
|
|||||||
lifecycleRuntimeKtx = "2.6.1"
|
lifecycleRuntimeKtx = "2.6.1"
|
||||||
activityCompose = "1.8.0"
|
activityCompose = "1.8.0"
|
||||||
composeBom = "2023.08.00"
|
composeBom = "2023.08.00"
|
||||||
|
mapsCompose = "4.3.3"
|
||||||
material3Android = "1.2.1"
|
material3Android = "1.2.1"
|
||||||
|
media3Exoplayer = "1.3.1"
|
||||||
navigationCompose = "2.7.7"
|
navigationCompose = "2.7.7"
|
||||||
pagingRuntime = "3.3.0"
|
pagingRuntime = "3.3.0"
|
||||||
|
activityKtx = "1.9.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
|
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "media3Exoplayer" }
|
||||||
|
androidx-media3-session = { module = "androidx.media3:media3-session", version.ref = "media3Exoplayer" }
|
||||||
|
androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "media3Exoplayer" }
|
||||||
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
|
||||||
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pagingRuntime" }
|
androidx-paging-compose = { module = "androidx.paging:paging-compose", version.ref = "pagingRuntime" }
|
||||||
androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" }
|
androidx-paging-runtime = { module = "androidx.paging:paging-runtime", version.ref = "pagingRuntime" }
|
||||||
@@ -21,7 +29,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" }
|
|||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version = "1.9.0" }
|
||||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||||
@@ -31,6 +39,8 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
|
|||||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" }
|
androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" }
|
||||||
|
maps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "mapsCompose" }
|
||||||
|
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
Reference in New Issue
Block a user