refactor: pip optimize
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
## 已知问题
|
## 已知问题
|
||||||
|
|
||||||
|
- 因为没有设备用于(也懒得)测试,应用可能无法正常运行在安卓版本较低的设备上,特别是画中画功能,非常取决于国产 ROM 的实现
|
||||||
- 关闭画中画后不会停止 scrcpy 串流,仍然需要回到应用中点击停止
|
- 关闭画中画后不会停止 scrcpy 串流,仍然需要回到应用中点击停止
|
||||||
- 虚拟按键的截图实现方式为发送
|
- 虚拟按键的截图实现方式为发送
|
||||||
`keycode 120`,安卓官方([keycodes.h#349](https://android.googlesource.com/platform/frameworks/native/+/master/include/android/keycodes.h#349))的定义为
|
`keycode 120`,安卓官方([keycodes.h#349](https://android.googlesource.com/platform/frameworks/native/+/master/include/android/keycodes.h#349))的定义为
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
package io.github.miuzarte.scrcpyforandroid
|
package io.github.miuzarte.scrcpyforandroid
|
||||||
|
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.app.PictureInPictureUiState
|
||||||
import android.app.RemoteAction
|
import android.app.RemoteAction
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Configuration
|
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.app.PictureInPictureParamsCompat
|
import androidx.core.app.PictureInPictureParamsCompat
|
||||||
import androidx.core.pip.BasicPictureInPicture
|
import androidx.core.pip.BasicPictureInPicture
|
||||||
import androidx.core.pip.PictureInPictureDelegate
|
|
||||||
import io.github.miuzarte.scrcpyforandroid.pages.StreamScreen
|
import io.github.miuzarte.scrcpyforandroid.pages.StreamScreen
|
||||||
import io.github.miuzarte.scrcpyforandroid.services.PictureInPictureActionReceiver
|
import io.github.miuzarte.scrcpyforandroid.services.PictureInPictureActionReceiver
|
||||||
import io.github.miuzarte.scrcpyforandroid.widgets.VideoOutputTarget
|
import io.github.miuzarte.scrcpyforandroid.widgets.VideoOutputTarget
|
||||||
@@ -20,26 +21,19 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
class StreamActivity : ComponentActivity() {
|
class StreamActivity : ComponentActivity() {
|
||||||
private lateinit var basicPip: BasicPictureInPicture // = this
|
private val basicPip = BasicPictureInPicture(this)
|
||||||
|
|
||||||
// pip 是否已被配置、允许进入
|
// 是否处于 pip
|
||||||
private var pipConfigured: Boolean = false
|
// 回到全屏时会因重建而变回初始值
|
||||||
private var pipParams = PictureInPictureParamsCompat
|
|
||||||
.Builder()
|
|
||||||
.setEnabled(false)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
// 是否处于 PiP
|
|
||||||
// MIUI 上进入 PiP 时,动画事件比 onPictureInPictureModeChanged() 更稳定,
|
|
||||||
// 所以进入 PiP 直接由动画事件置为 true
|
|
||||||
private val _pipModeState = MutableStateFlow(false)
|
private val _pipModeState = MutableStateFlow(false)
|
||||||
val pipModeState: StateFlow<Boolean> = _pipModeState
|
val pipModeState: StateFlow<Boolean> = _pipModeState
|
||||||
|
|
||||||
val pipStopAction: RemoteAction by lazy {
|
val pipStopAction: RemoteAction by lazy {
|
||||||
val intent = Intent(this, PictureInPictureActionReceiver::class.java).apply {
|
val intent = Intent(this, PictureInPictureActionReceiver::class.java)
|
||||||
action = PictureInPictureActionReceiver.ACTION_STOP_SCRCPY
|
.apply {
|
||||||
`package` = packageName
|
action = PictureInPictureActionReceiver.ACTION_STOP_SCRCPY
|
||||||
}
|
`package` = packageName
|
||||||
|
}
|
||||||
val pendingIntent = PendingIntent.getBroadcast(
|
val pendingIntent = PendingIntent.getBroadcast(
|
||||||
this,
|
this,
|
||||||
1,
|
1,
|
||||||
@@ -54,27 +48,31 @@ class StreamActivity : ComponentActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 每次 进出全屏/进出画中画
|
||||||
|
// 都会重建 activity
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
basicPip = BasicPictureInPicture(this)
|
|
||||||
|
// 声明要画中画
|
||||||
|
basicPip.setEnabled(true)
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
StreamScreen(activity = this)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// 可能以后有用
|
||||||
basicPip.addOnPictureInPictureEventListener(
|
basicPip.addOnPictureInPictureEventListener(
|
||||||
executor = mainExecutor,
|
executor = mainExecutor,
|
||||||
listener = object : PictureInPictureDelegate.OnPictureInPictureEventListener {
|
listener = object : PictureInPictureDelegate.OnPictureInPictureEventListener {
|
||||||
override fun onPictureInPictureEvent(
|
override fun onPictureInPictureEvent(
|
||||||
event: PictureInPictureDelegate.Event,
|
event: PictureInPictureDelegate.Event,
|
||||||
newConfig: Configuration?,
|
config: Configuration?,
|
||||||
) {
|
) {
|
||||||
// HyperOS 3 下稳定收到的是进入动画事件
|
// MIUI 只有这些事件
|
||||||
// 这里直接把进入动画开始视为已经进入 PiP,尽量少依赖平台回调
|
|
||||||
when (event) {
|
when (event) {
|
||||||
PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> {
|
PictureInPictureDelegate.Event.ENTER_ANIMATION_START -> {}
|
||||||
_pipModeState.value = true
|
PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> {}
|
||||||
VideoOutputTargetState.set(VideoOutputTarget.PICTURE_IN_PICTURE)
|
|
||||||
}
|
|
||||||
|
|
||||||
PictureInPictureDelegate.Event.ENTER_ANIMATION_END -> {
|
|
||||||
_pipModeState.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
PictureInPictureDelegate.Event.STASHED -> {}
|
PictureInPictureDelegate.Event.STASHED -> {}
|
||||||
PictureInPictureDelegate.Event.UNSTASHED -> {}
|
PictureInPictureDelegate.Event.UNSTASHED -> {}
|
||||||
@@ -86,49 +84,18 @@ class StreamActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
*/
|
||||||
setContent {
|
|
||||||
StreamScreen(activity = this)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configurePictureInPicture(
|
fun configurePip(params: PictureInPictureParamsCompat) {
|
||||||
enabled: Boolean,
|
basicPip.setPictureInPictureParams(params)
|
||||||
params: PictureInPictureParamsCompat,
|
|
||||||
) {
|
|
||||||
// 由 StreamScreen 决定何时更新 PiP 参数;
|
|
||||||
// StreamActivity 这里只负责缓存并转发最新配置
|
|
||||||
pipConfigured = enabled
|
|
||||||
pipParams = params
|
|
||||||
basicPip
|
|
||||||
.setEnabled(enabled)
|
|
||||||
.setPictureInPictureParams(params)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun enterConfiguredPip(): Boolean {
|
|
||||||
// onUserLeaveHint() 可能发生在 PiP 还没准备好之前,
|
|
||||||
// 所以这里先做一次兜底判断
|
|
||||||
if (!pipConfigured) return false
|
|
||||||
return runCatching {
|
|
||||||
enterPictureInPictureMode(pipParams)
|
|
||||||
true
|
|
||||||
}.getOrElse {
|
|
||||||
VideoOutputTargetState.set(VideoOutputTarget.FULLSCREEN)
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onUserLeaveHint() {
|
|
||||||
super.onUserLeaveHint()
|
|
||||||
|
|
||||||
enterConfiguredPip()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// 回到全屏也会停止, 暂时不做
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
||||||
// 回到全屏也会停止
|
|
||||||
/*
|
|
||||||
if (_pipModeState.value) {
|
if (_pipModeState.value) {
|
||||||
Thread {
|
Thread {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
@@ -136,19 +103,28 @@ class StreamActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
// MIUI 不进
|
//- onPictureInPictureModeChanged
|
||||||
override fun onPictureInPictureModeChanged(
|
//+ onPictureInPictureUiStateChanged
|
||||||
isInPictureInPictureMode: Boolean,
|
//- onUserLeaveHint
|
||||||
newConfig: Configuration,
|
|
||||||
) {
|
|
||||||
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
|
|
||||||
|
|
||||||
if (!isInPictureInPictureMode) {
|
@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
|
||||||
_pipModeState.value = false
|
override fun onPictureInPictureUiStateChanged(pipState: PictureInPictureUiState) {
|
||||||
VideoOutputTargetState.set(VideoOutputTarget.FULLSCREEN)
|
super.onPictureInPictureUiStateChanged(pipState)
|
||||||
|
|
||||||
|
when {
|
||||||
|
// 进入画中画
|
||||||
|
pipState.isTransitioningToPip -> if (!_pipModeState.value) {
|
||||||
|
_pipModeState.value = true
|
||||||
|
VideoOutputTargetState.set(VideoOutputTarget.PICTURE_IN_PICTURE)
|
||||||
|
}
|
||||||
|
// 收进边缘
|
||||||
|
pipState.isStashed -> if (!_pipModeState.value) {
|
||||||
|
_pipModeState.value = true
|
||||||
|
VideoOutputTargetState.set(VideoOutputTarget.PICTURE_IN_PICTURE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ internal fun AboutScreen() {
|
|||||||
val lazyListState = rememberLazyListState()
|
val lazyListState = rememberLazyListState()
|
||||||
var logoHeightPx by remember { mutableIntStateOf(0) }
|
var logoHeightPx by remember { mutableIntStateOf(0) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
AppUpdateChecker.ensureChecked(BuildConfig.VERSION_NAME)
|
||||||
|
}
|
||||||
|
|
||||||
val scrollProgress by remember {
|
val scrollProgress by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
if (logoHeightPx <= 0) {
|
if (logoHeightPx <= 0) {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.MoreVert
|
import androidx.compose.material.icons.rounded.MoreVert
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@@ -238,6 +239,8 @@ fun DeviceTabPage(
|
|||||||
|
|
||||||
var audioForwardingSupported by rememberSaveable { mutableStateOf(true) }
|
var audioForwardingSupported by rememberSaveable { mutableStateOf(true) }
|
||||||
var cameraMirroringSupported by rememberSaveable { mutableStateOf(true) }
|
var cameraMirroringSupported by rememberSaveable { mutableStateOf(true) }
|
||||||
|
var pendingScrollToPreview by rememberSaveable { mutableStateOf(false) }
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
val currentTarget =
|
val currentTarget =
|
||||||
if (currentTargetHost.isNotBlank())
|
if (currentTargetHost.isNotBlank())
|
||||||
@@ -528,6 +531,7 @@ fun DeviceTabPage(
|
|||||||
suspend fun startScrcpySession() {
|
suspend fun startScrcpySession() {
|
||||||
val options = scrcpyOptions.toClientOptions(soBundleShared).fix()
|
val options = scrcpyOptions.toClientOptions(soBundleShared).fix()
|
||||||
val session = scrcpy.start(options)
|
val session = scrcpy.start(options)
|
||||||
|
pendingScrollToPreview = true
|
||||||
if (options.disableScreensaver) {
|
if (options.disableScreensaver) {
|
||||||
setKeepScreenOn(true)
|
setKeepScreenOn(true)
|
||||||
}
|
}
|
||||||
@@ -553,6 +557,15 @@ fun DeviceTabPage(
|
|||||||
snackbar.show("scrcpy 已启动")
|
snackbar.show("scrcpy 已启动")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(pendingScrollToPreview, sessionInfo) {
|
||||||
|
if (!pendingScrollToPreview) return@LaunchedEffect
|
||||||
|
val session = sessionInfo ?: return@LaunchedEffect
|
||||||
|
if (session.width <= 0 || session.height <= 0) return@LaunchedEffect
|
||||||
|
// status/device list/scrcpy panel are above the preview card
|
||||||
|
listState.animateScrollToItem(index = 3)
|
||||||
|
pendingScrollToPreview = false
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun handleAdbConnected(host: String, port: Int) {
|
suspend fun handleAdbConnected(host: String, port: Int) {
|
||||||
currentTargetHost = host
|
currentTargetHost = host
|
||||||
currentTargetPort = port
|
currentTargetPort = port
|
||||||
@@ -707,6 +720,7 @@ fun DeviceTabPage(
|
|||||||
LazyColumn(
|
LazyColumn(
|
||||||
contentPadding = contentPadding,
|
contentPadding = contentPadding,
|
||||||
scrollBehavior = scrollBehavior,
|
scrollBehavior = scrollBehavior,
|
||||||
|
state = listState,
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
StatusCard(
|
StatusCard(
|
||||||
|
|||||||
@@ -77,14 +77,12 @@ fun StreamScreen(activity: StreamActivity) {
|
|||||||
).also { ratio ->
|
).also { ratio ->
|
||||||
lastPipAspectRatio = ratio
|
lastPipAspectRatio = ratio
|
||||||
}
|
}
|
||||||
activity.configurePictureInPicture(
|
|
||||||
enabled = true,
|
activity.configurePip(
|
||||||
params = PictureInPictureParamsCompat.Builder()
|
PictureInPictureParamsCompat.Builder()
|
||||||
.setEnabled(true)
|
.setEnabled(true)
|
||||||
.setAspectRatio(pipAspectRatio)
|
.setAspectRatio(pipAspectRatio)
|
||||||
.setSourceRectHint(
|
.setSourceRectHint(pipSourceRectHint)
|
||||||
if (!isInPip) pipSourceRectHint else null
|
|
||||||
)
|
|
||||||
.setSeamlessResizeEnabled(true)
|
.setSeamlessResizeEnabled(true)
|
||||||
.setCloseAction(activity.pipStopAction)
|
.setCloseAction(activity.pipStopAction)
|
||||||
.build(),
|
.build(),
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class AppSettings(context: Context) : Settings(context, "AppSettings") {
|
|||||||
)
|
)
|
||||||
val DEVICE_PREVIEW_CARD_HEIGHT_DP = Pair(
|
val DEVICE_PREVIEW_CARD_HEIGHT_DP = Pair(
|
||||||
intPreferencesKey("device_preview_card_height_dp"),
|
intPreferencesKey("device_preview_card_height_dp"),
|
||||||
320
|
1080/3
|
||||||
)
|
)
|
||||||
val PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT = Pair(
|
val PREVIEW_VIRTUAL_BUTTON_SHOW_TEXT = Pair(
|
||||||
booleanPreferencesKey("preview_virtual_button_show_text"),
|
booleanPreferencesKey("preview_virtual_button_show_text"),
|
||||||
|
|||||||
@@ -764,17 +764,27 @@ fun ScrcpyVideoSurface(
|
|||||||
session: Scrcpy.Session.SessionInfo?,
|
session: Scrcpy.Session.SessionInfo?,
|
||||||
target: VideoOutputTarget,
|
target: VideoOutputTarget,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
var currentSurface by remember { mutableStateOf<Surface?>(null) }
|
var currentSurface by remember { mutableStateOf<Surface?>(null) }
|
||||||
var currentSurfaceView by remember { mutableStateOf<SurfaceView?>(null) }
|
var currentSurfaceView by remember { mutableStateOf<SurfaceView?>(null) }
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
val taskScope = remember { CoroutineScope(SupervisorJob() + Dispatchers.IO) }
|
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
|
||||||
val currentTarget by VideoOutputTargetState.current.collectAsState()
|
|
||||||
val latestSession by rememberUpdatedState(session)
|
|
||||||
val latestCurrentTarget by rememberUpdatedState(currentTarget)
|
|
||||||
|
|
||||||
LaunchedEffect(session?.width, session?.height, currentSurfaceView) {
|
val currentTarget by VideoOutputTargetState.current.collectAsState()
|
||||||
|
val latestCurrentTarget by rememberUpdatedState(currentTarget)
|
||||||
|
val latestSession by rememberUpdatedState(session)
|
||||||
|
val latestRequestedTarget by rememberUpdatedState(target)
|
||||||
|
|
||||||
|
LaunchedEffect(session?.width, session?.height, currentSurfaceView, target) {
|
||||||
val surfaceView = currentSurfaceView ?: return@LaunchedEffect
|
val surfaceView = currentSurfaceView ?: return@LaunchedEffect
|
||||||
|
if (target == VideoOutputTarget.PICTURE_IN_PICTURE) {
|
||||||
|
// In PiP, let SurfaceView buffer follow viewport to avoid stale portrait frame crop.
|
||||||
|
surfaceView.holder.setSizeFromLayout()
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
|
||||||
val currentSession = session ?: return@LaunchedEffect
|
val currentSession = session ?: return@LaunchedEffect
|
||||||
if (currentSession.width > 0 && currentSession.height > 0) {
|
if (currentSession.width > 0 && currentSession.height > 0) {
|
||||||
surfaceView.holder.setFixedSize(currentSession.width, currentSession.height)
|
surfaceView.holder.setFixedSize(currentSession.width, currentSession.height)
|
||||||
@@ -782,8 +792,8 @@ fun ScrcpyVideoSurface(
|
|||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(session, currentSurface, currentTarget, target) {
|
LaunchedEffect(session, currentSurface, currentTarget, target) {
|
||||||
val surface = currentSurface
|
val surface = currentSurface ?: return@LaunchedEffect
|
||||||
if (currentTarget == target && session != null && surface != null && surface.isValid) {
|
if (currentTarget == target && session != null && surface.isValid) {
|
||||||
NativeCoreFacade.attachVideoSurface(surface)
|
NativeCoreFacade.attachVideoSurface(surface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -844,9 +854,14 @@ fun ScrcpyVideoSurface(
|
|||||||
height: Int,
|
height: Int,
|
||||||
) {
|
) {
|
||||||
if (width <= 0 || height <= 0) return
|
if (width <= 0 || height <= 0) return
|
||||||
|
if (!holder.surface.isValid) return
|
||||||
val surface = holder.surface
|
val surface = holder.surface
|
||||||
if (!surface.isValid) return
|
|
||||||
currentSurface = surface
|
currentSurface = surface
|
||||||
|
if (latestCurrentTarget == latestRequestedTarget && latestSession != null) {
|
||||||
|
scope.launch {
|
||||||
|
NativeCoreFacade.attachVideoSurface(surface)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
override fun surfaceDestroyed(holder: SurfaceHolder) {
|
||||||
@@ -940,7 +955,7 @@ internal fun DeviceTile(
|
|||||||
.weight(1f),
|
.weight(1f),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
// statu dot
|
// status dot
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(8.dp)
|
.size(8.dp)
|
||||||
|
|||||||
@@ -351,6 +351,9 @@ class VirtualButtonBar(
|
|||||||
maxX * offsetXFraction.coerceIn(0f, 1f)
|
maxX * offsetXFraction.coerceIn(0f, 1f)
|
||||||
val currentY =
|
val currentY =
|
||||||
maxY * offsetYFraction.coerceIn(0f, 1f)
|
maxY * offsetYFraction.coerceIn(0f, 1f)
|
||||||
|
val popupAlignment =
|
||||||
|
if (offsetXFraction > 0.5f) PopupPositionProvider.Align.TopEnd
|
||||||
|
else PopupPositionProvider.Align.TopStart
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -422,6 +425,7 @@ class VirtualButtonBar(
|
|||||||
showActions = false
|
showActions = false
|
||||||
},
|
},
|
||||||
renderInRootScaffold = true,
|
renderInRootScaffold = true,
|
||||||
|
popupAlignment = popupAlignment,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,6 +438,7 @@ class VirtualButtonBar(
|
|||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
onAction: suspend (VirtualButtonAction) -> Unit,
|
onAction: suspend (VirtualButtonAction) -> Unit,
|
||||||
renderInRootScaffold: Boolean,
|
renderInRootScaffold: Boolean,
|
||||||
|
popupAlignment: PopupPositionProvider.Align = PopupPositionProvider.Align.TopEnd,
|
||||||
) {
|
) {
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val haptics = LocalAppHaptics.current
|
val haptics = LocalAppHaptics.current
|
||||||
@@ -456,7 +461,7 @@ class VirtualButtonBar(
|
|||||||
OverlayListPopup(
|
OverlayListPopup(
|
||||||
show = show,
|
show = show,
|
||||||
popupPositionProvider = ListPopupDefaults.ContextMenuPositionProvider,
|
popupPositionProvider = ListPopupDefaults.ContextMenuPositionProvider,
|
||||||
alignment = PopupPositionProvider.Align.TopEnd,
|
alignment = popupAlignment,
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
renderInRootScaffold = renderInRootScaffold,
|
renderInRootScaffold = renderInRootScaffold,
|
||||||
enableWindowDim = false,
|
enableWindowDim = false,
|
||||||
|
|||||||
Reference in New Issue
Block a user