Skip to content

Migrate WebViews activities to Jetpack Compose #5419

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ android {
}
}

lint {
// Until we fully migrate to Material3 this lint issue is too verbose https://github.com/home-assistant/android/issues/5420
disable += listOf("UsingMaterialAndMaterial3Libraries")
}

experimentalProperties["android.experimental.enableScreenshotTest"] = true

screenshotTests {
Expand Down
60 changes: 35 additions & 25 deletions app/gradle.lockfile

Large diffs are not rendered by default.

367 changes: 81 additions & 286 deletions app/lint-baseline.xml

Large diffs are not rendered by default.

15 changes: 0 additions & 15 deletions app/src/debug/res/layout/activity_demo_exo_player.xml

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package io.homeassistant.companion.android.onboarding.authentication

import android.annotation.SuppressLint
import android.net.Uri
import android.net.http.SslError
import android.os.Build
import android.os.Bundle
Expand All @@ -18,7 +17,7 @@ import android.widget.Toast
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.net.toUri
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import dagger.hilt.android.AndroidEntryPoint
Expand All @@ -32,6 +31,7 @@ import io.homeassistant.companion.android.onboarding.integration.MobileAppIntegr
import io.homeassistant.companion.android.themes.ThemesManager
import io.homeassistant.companion.android.util.TLSWebViewClient
import io.homeassistant.companion.android.util.compose.HomeAssistantAppTheme
import io.homeassistant.companion.android.util.compose.webview.HAWebView
import io.homeassistant.companion.android.util.isStarted
import javax.inject.Inject
import javax.inject.Named
Expand Down Expand Up @@ -66,15 +66,19 @@ class AuthenticationFragment : Fragment() {
return ComposeView(requireContext()).apply {
setContent {
HomeAssistantAppTheme {
AndroidView({
WebView(requireContext()).apply {
HAWebView(
configure = {
themesManager.setThemeForWebView(requireContext(), settings)
settings.javaScriptEnabled = true
settings.domStorageEnabled = true
settings.userAgentString = settings.userAgentString + " ${HomeAssistantApis.USER_AGENT_STRING}"
settings.userAgentString =
settings.userAgentString + " ${HomeAssistantApis.USER_AGENT_STRING}"
webViewClient = object : TLSWebViewClient(keyChainRepository) {
@Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(view: WebView?, url: String): Boolean {
override fun shouldOverrideUrlLoading(
view: WebView?,
url: String
): Boolean {
return onRedirect(url)
}

Expand All @@ -96,7 +100,7 @@ class AuthenticationFragment : Fragment() {
if (error?.description.isNullOrBlank()) {
commonR.string.no_description
} else {
error?.description
error.description
}
),
null,
Expand Down Expand Up @@ -135,7 +139,7 @@ class AuthenticationFragment : Fragment() {
if (errorResponse?.reasonPhrase.isNullOrBlank()) {
requireContext().getString(commonR.string.no_description)
} else {
errorResponse?.reasonPhrase
errorResponse.reasonPhrase
}
),
null,
Expand All @@ -158,7 +162,7 @@ class AuthenticationFragment : Fragment() {
authUrl = buildAuthUrl(viewModel.manualUrl.value)
loadUrl(authUrl!!)
}
})
)
}
}
}
Expand Down Expand Up @@ -191,7 +195,7 @@ class AuthenticationFragment : Fragment() {
}

private fun onRedirect(url: String): Boolean {
val code = Uri.parse(url).getQueryParameter("code")
val code = url.toUri().getQueryParameter("code")
return if (url.startsWith(AUTH_CALLBACK) && !code.isNullOrBlank()) {
viewModel.registerAuthCode(code)
parentFragmentManager
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ fun HAMediaPlayer(
contentScale: ContentScale,
modifier: Modifier = Modifier,
fullscreenModifier: Modifier = Modifier,
onFullscreenClicked: (isFullscreen: Boolean) -> Unit = {},
) {
var showControls by remember { mutableStateOf(true) }

Expand All @@ -106,7 +107,7 @@ fun HAMediaPlayer(

HAMediaPlayer(player, showControls, contentScale, modifier, fullscreenModifier, onPlayerClicked = {
showControls = !showControls
})
}, onFullscreenClicked = onFullscreenClicked)
}

/**
Expand All @@ -122,6 +123,7 @@ fun HAMediaPlayer(
* @param modifier The `Modifier` to be applied to the main player container.
* @param fullscreenModifier The `Modifier` to be applied to the player container when it is in fullscreen mode.
* @param onPlayerClicked Callback invoked when the player's surface (outside of controls) is clicked.
* @param onFullscreenClicked Callback invoked when the fullscreen button is clicked `isFullscreen` indicates the new state.
*/
@Composable
@OptIn(UnstableApi::class)
Expand All @@ -132,6 +134,7 @@ fun HAMediaPlayer(
modifier: Modifier = Modifier,
fullscreenModifier: Modifier = Modifier,
onPlayerClicked: () -> Unit = {},
onFullscreenClicked: (isFullscreen: Boolean) -> Unit = {},
) {
var isFullscreen by remember { mutableStateOf(false) }
val presentationState = rememberPresentationState(player)
Expand All @@ -150,6 +153,7 @@ fun HAMediaPlayer(
isFullScreen = isFullscreen,
onClickFullscreen = {
isFullscreen = !isFullscreen
onFullscreenClicked(isFullscreen)
},
onPlayerClicked = onPlayerClicked,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package io.homeassistant.companion.android.util.compose.webview

import android.webkit.WebView
import android.widget.FrameLayout
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView

/**
* A composable that displays a WebView.
*
* This function provides a convenient way to embed a WebView in a Jetpack Compose UI.
* It allows for customization of the WebView through the `configure` lambda,
* and provides a callback for when the WebView is released via the `onRelease` lambda.
*
* @param modifier The modifier to be applied to the WebView.
* @param configure A lambda that allows for configuration of the WebView instance.
* This is called when the WebView is created.
* @param factory A lambda that creates the WebView instance. If this returns null, a new
* WebView will be created with the current context.
*/
@Composable
fun HAWebView(
modifier: Modifier = Modifier,
configure: WebView.() -> Unit = {},
factory: () -> WebView? = { null },
) {
AndroidView(
factory = { context ->
(factory() ?: WebView(context)).apply {
// We want the modifier to determine the size so the WebView should match the parent
this.layoutParams = FrameLayout.LayoutParams(

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Today, I discovered that this this.layoutParams = [...] with match parent causes Webview to set correct CSS env(safe-area-insets-*) values. I tested it on webview 137, on 119 it is not working.
But I am not sure if it really matters because it's not very stable or/and documented.

FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT
)

configure(this)
}
},
modifier = modifier,
)
}
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy