Merge develop into the PreviewParam rename

The 174 commits develop gained since this branch was cut touch preview data
in three ways, and each is resolved so the flipped konsist rule still holds
across the merged tree.

Two screens the branch had renamed were deleted upstream by "Remove the
account provider search screen and homeserver resolver": the
changeaccountprovider and searchaccountprovider packages. Their deletion
wins, so ChangeAccountProviderStatePreviewParam, SearchAccountProviderStatePreviewParam
and the two views that used them go with them rather than coming back
under the new name.

Two preview classes develop added still carried the old suffix and are
renamed here, along with the `@PreviewParameter` reference in each view:
GlobalSearchStateProvider -> GlobalSearchStatePreviewParam and
SendFailureDialogStateProvider -> SendFailureDialogStatePreviewParam.

Everything else merged textually. No production class extending
PreviewParameterProvider is left with a `Provider` name, and no reference
to any of the 194 renamed names survives outside the CHANGES.md entry that
records a past pull request title.
This commit is contained in:
hayaksi1
2026-08-24 10:55:40 +03:00
397 changed files with 6635 additions and 4221 deletions
@@ -11,7 +11,9 @@ package io.element.android.features.call.impl.pip
import io.element.android.features.call.impl.utils.PipController
sealed interface PictureInPictureEvent {
data object OnCallStarted : PictureInPictureEvent
data class SetPipController(val pipController: PipController) : PictureInPictureEvent
data object EnterPictureInPicture : PictureInPictureEvent
data class OnPictureInPictureModeChanged(val isInPip: Boolean) : PictureInPictureEvent
data class SetPipOrientation(val orientation: Int?) : PictureInPictureEvent
}
@@ -38,6 +38,9 @@ class PictureInPicturePresenter(
fun handleEvent(event: PictureInPictureEvent) {
when (event) {
is PictureInPictureEvent.OnCallStarted -> {
pipController?.setupPipCallbacks()
}
is PictureInPictureEvent.SetPipController -> {
pipController = event.pipController
}
@@ -55,6 +58,10 @@ class PictureInPicturePresenter(
pipController?.exitPip()
}
}
is PictureInPictureEvent.SetPipOrientation -> {
Timber.tag(loggerTag.value).d("onOrientationChange: ${event.orientation}")
pipView?.setPipOrientation(event.orientation)
}
}
}
@@ -9,6 +9,7 @@
package io.element.android.features.call.impl.pip
interface PipView {
fun setPipOrientation(orientation: Int?)
fun setPipParams()
fun enterPipMode(): Boolean
fun hangUp()
@@ -118,6 +118,7 @@ internal fun CallScreenView(
if (webViewAudioManager?.isInCallMode?.get() == false) {
Timber.d("URL $url is loaded, starting in-call audio mode")
webViewAudioManager?.onCallStarted()
pipState.eventSink(PictureInPictureEvent.OnCallStarted)
} else {
Timber.d("Can't start in-call audio mode since the app is already in it.")
}
@@ -130,7 +131,12 @@ internal fun CallScreenView(
onInvalidAudioDeviceAdded = { invalidAudioDeviceReason = it },
)
state.eventSink(CallScreenEvent.SetupMessageChannels(interceptor))
val pipController = WebViewPipController(webView)
val pipController = WebViewPipController(
webView = webView,
updatePipOrientation = { orientation ->
pipState.eventSink(PictureInPictureEvent.SetPipOrientation(orientation))
}
)
pipState.eventSink(PictureInPictureEvent.SetPipController(pipController))
},
onDestroyWebView = {
@@ -11,9 +11,9 @@ package io.element.android.features.call.impl.ui
import android.Manifest
import android.app.PictureInPictureParams
import android.content.Intent
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.util.Rational
import android.view.WindowManager
import android.webkit.PermissionRequest
import androidx.activity.compose.setContent
@@ -49,6 +49,7 @@ import io.element.android.features.call.impl.pip.PipView
import io.element.android.features.call.impl.services.CallForegroundService
import io.element.android.features.enterprise.api.EnterpriseService
import io.element.android.libraries.androidutils.browser.ConsoleMessageLogger
import io.element.android.libraries.androidutils.media.setAspectRatioFromOrientation
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.architecture.bindings
import io.element.android.libraries.audio.api.AudioFocus
@@ -86,6 +87,8 @@ class ElementCallActivity :
private var eventSink: ((CallScreenEvent) -> Unit)? = null
private var currentPipOrientation: Int? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -199,6 +202,7 @@ class ElementCallActivity :
}
DisposableEffect(Unit) {
val onPictureInPictureModeChangedListener = Consumer { _: PictureInPictureModeChangedInfo ->
setPipParams()
pipEventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(isInPictureInPictureMode))
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
Timber.tag(loggerTag.value).d("Exiting PiP mode: Hangup the call")
@@ -296,11 +300,17 @@ class ElementCallActivity :
}
}
override fun setPipOrientation(orientation: Int?) {
currentPipOrientation = orientation
setPictureInPictureParams(getPictureInPictureParams())
}
@RequiresApi(Build.VERSION_CODES.O)
private fun getPictureInPictureParams(): PictureInPictureParams {
// Portrait for calls seems more appropriate as a fallback value
val orientation = currentPipOrientation ?: Configuration.ORIENTATION_PORTRAIT
return PictureInPictureParams.Builder()
// Portrait for calls seems more appropriate
.setAspectRatio(Rational(3, 5))
.setAspectRatioFromOrientation(orientation)
.apply {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setAutoEnterEnabled(true)
@@ -9,6 +9,7 @@
package io.element.android.features.call.impl.utils
interface PipController {
fun setupPipCallbacks()
suspend fun canEnterPip(): Boolean
fun enterPip()
fun exitPip()
@@ -250,7 +250,7 @@ class WebViewAudioManager(
hasRegisteredCallbacks = true
}
}
},
)
Timber.d("Setting androidNativeBridge javascript interface in webview")
webView.addJavascriptInterface(webViewAudioDeviceSelectedCallback, "androidNativeBridge")
@@ -8,7 +8,10 @@
package io.element.android.features.call.impl.utils
import android.content.res.Configuration
import android.webkit.JavascriptInterface
import android.webkit.WebView
import timber.log.Timber
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@@ -18,7 +21,18 @@ import kotlin.coroutines.suspendCoroutine
*/
class WebViewPipController(
private val webView: WebView,
updatePipOrientation: (orientation: Int?) -> Unit,
) : PipController {
init {
// Register the JavaScript interface ahead of time to ensure it's available when the WebView content is loaded and tries to call it
webView.addJavascriptInterface(PipBridge(updatePipOrientation), "androidPipBridge")
}
override fun setupPipCallbacks() {
Timber.d("Adding callback in controls.onPipMediaOrientationUpdate")
webView.evaluateJavascript("controls.onPipMediaOrientationUpdate = (orientation) => { androidPipBridge.setPipOrientation(orientation); };", null)
}
override suspend fun canEnterPip(): Boolean {
return suspendCoroutine { continuation ->
webView.evaluateJavascript("controls.canEnterPip()") { result ->
@@ -36,3 +50,19 @@ class WebViewPipController(
webView.evaluateJavascript("controls.disablePip()", null)
}
}
private class PipBridge(
private val onOrientationChange: (orientation: Int?) -> Unit,
) {
@JavascriptInterface
fun setPipOrientation(orientation: String?) {
Timber.d("Picture-in-picture orientation changed in webview, orientation: $orientation")
// This callback is used to update the picture-in-picture orientation in the WebView when it changes
val orientation = when (orientation) {
"portrait" -> Configuration.ORIENTATION_PORTRAIT
"landscape" -> Configuration.ORIENTATION_LANDSCAPE
else -> return
}
onOrientationChange(orientation)
}
}
@@ -12,10 +12,13 @@ import io.element.android.features.call.impl.utils.PipController
import io.element.android.tests.testutils.lambda.lambdaError
class FakePipController(
private val setupPipCallbacksResult: () -> Unit = { lambdaError() },
private val canEnterPipResult: () -> Boolean = { lambdaError() },
private val enterPipResult: () -> Unit = { lambdaError() },
private val exitPipResult: () -> Unit = { lambdaError() },
) : PipController {
override fun setupPipCallbacks() = setupPipCallbacksResult()
override suspend fun canEnterPip(): Boolean = canEnterPipResult()
override fun enterPip() = enterPipResult()
@@ -13,9 +13,11 @@ import io.element.android.tests.testutils.lambda.lambdaError
class FakePipView(
private val setPipParamsResult: () -> Unit = { lambdaError() },
private val enterPipModeResult: () -> Boolean = { lambdaError() },
private val handUpResult: () -> Unit = { lambdaError() }
private val handUpResult: () -> Unit = { lambdaError() },
private val onOrientationChangeResult: (orientation: Int?) -> Unit = { lambdaError() },
) : PipView {
override fun setPipParams() = setPipParamsResult()
override fun enterPipMode(): Boolean = enterPipModeResult()
override fun hangUp() = handUpResult()
override fun setPipOrientation(orientation: Int?) = onOrientationChangeResult(orientation)
}