Merge pull request #3199 from element-hq/feature/bma/improvePip

Improve pip and add feature flag.
This commit is contained in:
Benoit Marty 2024-07-16 10:18:13 +02:00 committed by GitHub
commit 591eac4ddc
11 changed files with 66 additions and 3 deletions

View file

@ -43,6 +43,7 @@ dependencies {
implementation(projects.libraries.architecture) implementation(projects.libraries.architecture)
implementation(projects.libraries.core) implementation(projects.libraries.core)
implementation(projects.libraries.designsystem) implementation(projects.libraries.designsystem)
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.matrix.impl) implementation(projects.libraries.matrix.impl)
implementation(projects.libraries.matrixui) implementation(projects.libraries.matrixui)
implementation(projects.libraries.network) implementation(projects.libraries.network)

View file

@ -16,6 +16,16 @@
package io.element.android.features.call.impl.pip package io.element.android.features.call.impl.pip
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
open class PictureInPictureStateProvider : PreviewParameterProvider<PictureInPictureState> {
override val values: Sequence<PictureInPictureState>
get() = sequenceOf(
aPictureInPictureState(supportPip = true),
aPictureInPictureState(supportPip = true, isInPictureInPicture = true),
)
}
fun aPictureInPictureState( fun aPictureInPictureState(
supportPip: Boolean = false, supportPip: Boolean = false,
isInPictureInPicture: Boolean = false, isInPictureInPicture: Boolean = false,

View file

@ -24,6 +24,9 @@ import com.squareup.anvil.annotations.ContributesBinding
import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.AppScope
import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.di.ApplicationContext
import io.element.android.libraries.featureflag.api.FeatureFlagService
import io.element.android.libraries.featureflag.api.FeatureFlags
import kotlinx.coroutines.runBlocking
import javax.inject.Inject import javax.inject.Inject
interface PipSupportProvider { interface PipSupportProvider {
@ -34,9 +37,15 @@ interface PipSupportProvider {
@ContributesBinding(AppScope::class) @ContributesBinding(AppScope::class)
class DefaultPipSupportProvider @Inject constructor( class DefaultPipSupportProvider @Inject constructor(
@ApplicationContext private val context: Context, @ApplicationContext private val context: Context,
private val featureFlagService: FeatureFlagService,
) : PipSupportProvider { ) : PipSupportProvider {
override fun isPipSupported(): Boolean { override fun isPipSupported(): Boolean {
val hasSystemFeaturePip = context.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).orFalse() val isSupportedByTheOs = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && hasSystemFeaturePip context.packageManager?.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE).orFalse()
return if (isSupportedByTheOs) {
runBlocking { featureFlagService.isFeatureEnabled(FeatureFlags.PictureInPicture) }
} else {
false
}
} }
} }

View file

@ -38,6 +38,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.call.impl.R import io.element.android.features.call.impl.R
import io.element.android.features.call.impl.pip.PictureInPictureEvents import io.element.android.features.call.impl.pip.PictureInPictureEvents
import io.element.android.features.call.impl.pip.PictureInPictureState import io.element.android.features.call.impl.pip.PictureInPictureState
import io.element.android.features.call.impl.pip.PictureInPictureStateProvider
import io.element.android.features.call.impl.pip.aPictureInPictureState import io.element.android.features.call.impl.pip.aPictureInPictureState
import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor import io.element.android.features.call.impl.utils.WebViewWidgetMessageInterceptor
import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.AsyncData
@ -81,7 +82,7 @@ internal fun CallScreenView(
title = { Text(stringResource(R.string.element_call)) }, title = { Text(stringResource(R.string.element_call)) },
navigationIcon = { navigationIcon = {
BackButton( BackButton(
imageVector = CompoundIcons.Close(), imageVector = if (pipState.supportPip) CompoundIcons.ArrowLeft() else CompoundIcons.Close(),
onClick = ::handleBack, onClick = ::handleBack,
) )
} }
@ -195,3 +196,15 @@ internal fun CallScreenViewPreview(
requestPermissions = { _, _ -> }, requestPermissions = { _, _ -> },
) )
} }
@PreviewsDayNight
@Composable
internal fun CallScreenPipViewPreview(
@PreviewParameter(PictureInPictureStateProvider::class) state: PictureInPictureState,
) = ElementPreview {
CallScreenView(
state = aCallScreenState(),
pipState = state,
requestPermissions = { _, _ -> },
)
}

View file

@ -32,6 +32,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.core.content.IntentCompat import androidx.core.content.IntentCompat
import androidx.lifecycle.Lifecycle
import io.element.android.features.call.api.CallType import io.element.android.features.call.api.CallType
import io.element.android.features.call.impl.DefaultElementCallEntryPoint import io.element.android.features.call.impl.DefaultElementCallEntryPoint
import io.element.android.features.call.impl.di.CallBindings import io.element.android.features.call.impl.di.CallBindings
@ -41,6 +42,7 @@ import io.element.android.features.call.impl.utils.CallIntentDataParser
import io.element.android.libraries.architecture.bindings import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.designsystem.theme.ElementThemeApp import io.element.android.libraries.designsystem.theme.ElementThemeApp
import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.preferences.api.store.AppPreferencesStore
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class ElementCallActivity : AppCompatActivity(), CallScreenNavigator { class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
@ -63,6 +65,8 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
private var isDarkMode = false private var isDarkMode = false
private val webViewTarget = mutableStateOf<CallType?>(null) private val webViewTarget = mutableStateOf<CallType?>(null)
private var eventSink: ((CallScreenEvents) -> Unit)? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -91,6 +95,7 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
val pipState = pictureInPicturePresenter.present() val pipState = pictureInPicturePresenter.present()
ElementThemeApp(appPreferencesStore) { ElementThemeApp(appPreferencesStore) {
val state = presenter.present() val state = presenter.present()
eventSink = state.eventSink
CallScreenView( CallScreenView(
state = state, state = state,
pipState = pipState, pipState = pipState,
@ -111,6 +116,11 @@ class ElementCallActivity : AppCompatActivity(), CallScreenNavigator {
override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
pictureInPicturePresenter.onPictureInPictureModeChanged(isInPictureInPictureMode) pictureInPicturePresenter.onPictureInPictureModeChanged(isInPictureInPictureMode)
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
Timber.d("Exiting PiP mode: Hangup the call")
eventSink?.invoke(CallScreenEvents.Hangup)
}
} }
override fun onNewIntent(intent: Intent) { override fun onNewIntent(intent: Intent) {

View file

@ -113,4 +113,11 @@ enum class FeatureFlags(
defaultValue = { true }, defaultValue = { true },
isFinished = false, isFinished = false,
), ),
PictureInPicture(
key = "feature.pictureInPicture",
title = "Picture in Picture for Calls",
description = "Allow the Call to be rendered in PiP mode",
defaultValue = { it.buildType != BuildType.RELEASE },
isFinished = false,
),
} }

View file

@ -72,6 +72,7 @@ class KonsistPreviewTest {
"AsyncIndicatorLoadingPreview", "AsyncIndicatorLoadingPreview",
"BloomInitialsPreview", "BloomInitialsPreview",
"BloomPreview", "BloomPreview",
"CallScreenPipViewPreview",
"ColorAliasesPreview", "ColorAliasesPreview",
"DefaultRoomListTopBarWithIndicatorPreview", "DefaultRoomListTopBarWithIndicatorPreview",
"GradientFloatingActionButtonCircleShapePreview", "GradientFloatingActionButtonCircleShapePreview",

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b61b391f6380189603939badc2ae5912f5cb072200a001ff946af2d52e81b95a
size 12734

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7696eb0f5bf8698d001ea44be6eb2005f414057f9a65703e6d987e8eb75f7f94
size 9441

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c3d14805d85b33ce1f0fdd26eec433db1973924542cd85a3bb0e4514cdcd857d
size 12392

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:146fc2ce4d344c0ea947fe0370e6b8d94c2e724c69c01c2cc3476a756e1f09e4
size 9315