Skip to content

Port back navigation improvements to FrontendScreen#6623

Draft
Gifford47 wants to merge 19 commits intohome-assistant:mainfrom
Gifford47:frontend-back-navigation
Draft

Port back navigation improvements to FrontendScreen#6623
Gifford47 wants to merge 19 commits intohome-assistant:mainfrom
Gifford47:frontend-back-navigation

Conversation

@Gifford47
Copy link
Copy Markdown

Summary

  • Port the enhanced back navigation from WebViewActivity to the Compose-based FrontendScreen (visible in debug builds)
  • Move BackHandler out of HAWebView (generic composable) into FrontendScreen where the HA-specific logic belongs
  • Extract resolveBackAction() as a testable pure function with unit tests

Changes

  • HAWebView: Remove BackHandler and onBackPressed parameter — back handling is now the caller's responsibility
  • FrontendScreen: Add WebViewBackHandler composable with:
    • Cross-origin check: only navigate back if previous history entry has same origin (prevents stale entries from connection switches)
    • Navigate-to-root: back on a sub-page goes to root URL before exiting
    • Exit: on root page, delegates to navController.popBackStack()
  • WebViewBackHandlerTest: Unit tests for resolveBackAction() covering root/sub-path/null scenarios

Context

Follow-up to #6447 as requested by @TimoPtr: #6447 (comment)

Test plan

  • Enable USE_FRONTEND_V2 debug flag
  • Navigate to a sub-page (e.g. /history), press back → should go to root
  • On root page, press back → should exit the screen
  • Switch internal/external connection, press back → should not navigate to stale history entries
  • Verify predictive back animations work on Android 14+
  • Run ./gradlew :app:testFullDebugUnitTest --tests "*.WebViewBackHandlerTest"

Gifford47 and others added 15 commits March 2, 2026 20:00
- Remove duplicate getCurrentWebViewPath() call from Activity (Presenter handles fallback)
- Move intent.removeExtra(EXTRA_PATH) before presenter.load()
- Fix moreInfoEntity: only set for JS dispatch path, clear for URL path approach
- Simplify getCurrentWebViewPath() using takeIf, remove unnecessary try-catch
- Use full GitHub issue URL in comment

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of only preserving the path when the connection switches between
internal and external URLs, preserve the complete relative URL including
query parameters and fragment. This ensures filtered views (e.g. history
page with date ranges) survive URL switches seamlessly.

- Rename getCurrentWebViewPath() to getCurrentWebViewRelativeUrl()
- Extract path + query params + fragment from the current WebView URL
- Strip 'external_auth' param to avoid duplication (presenter re-adds it)
- Update presenter to use the new method with descriptive variable names

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When the base URL changes (e.g. switching from internal Wi-Fi to
external mobile data), old URLs in the back stack become unreachable
on the new network. Pressing back would attempt to load those old
URLs, causing timeouts and error messages.

Track the last base URL in collectUrlStateChanges and set
isNewServer=true when a change is detected, which triggers
keepHistory=false and clears the navigation history after loading
the new URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
After switching between internal and external URLs, stale history
entries from the old connection may remain in the WebView. This
change validates that the previous history entry has the same origin
as the current URL before navigating back. If the origin differs,
it navigates to the base URL instead of attempting to load an
unreachable page. Also keeps the back callback enabled when on a
non-root path so pressing back navigates to root before exiting.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The WebView interface gained a new getCurrentWebViewRelativeUrl()
method but the FakeWebViewContext test wrapper was not updated,
causing a compilation failure in unit tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
A relaxed MockK mock returns "" instead of null for String? return
types. This caused UrlUtil.handle to normalize the URL with a trailing
slash, breaking the exact string assertion in the "previous load in
progress" test. Explicitly mock getCurrentWebViewRelativeUrl to return
null, matching the real behavior when no WebView page is loaded.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Guard back navigation against about:blank and other non-HTTP history
  entries that may appear before the first real page has loaded.
- Use Uri.Builder in getCurrentWebViewRelativeUrl instead of manual
  string concatenation for safer URL construction.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Extract URL relative-path logic into a reusable Uri.toRelativeUrl()
  extension in UrlUtil.kt, using queryParameterNames API instead of
  string splitting (as suggested by TimoPtr)
- Add KDoc documentation to the new extension
- Simplify getCurrentWebViewRelativeUrl() to a single-line delegation
- Add comments explaining the callback disable/enable pattern and the
  about:blank edge case in back navigation
- Add 10 unit tests for toRelativeUrl() in UriExtensionsTest.kt
Restore original placement to keep git history clean, as requested
by TimoPtr.
…tyle

- Move hideSystemUI/showSystemUI block back before path processing
  to preserve original code order and keep git history clean
- Refactor toRelativeUrl() query parameter loop to functional style
  using filterNot/flatMap/forEach as suggested by TimoPtr
- Don't preserve path on server switch to avoid leaking info and
  broken pages; only preserve on internal/external URL changes
- Restore OnBackPressedCallback(webView.canGoBack()) to keep
  predictive back animations on Android 14+
- Restore entityId comment with core.py link
- Make toRelativeUrl KDoc more generic (it's a util function)
- Fix misleading test name that suggested null return
Move BackHandler from HAWebView into FrontendScreen with enhanced
logic matching WebViewActivity:
- Cross-origin check: only go back if previous history entry has
  the same origin as the current base URL
- Navigate-to-root: press back on a sub-page navigates to root
  before exiting the screen
- Extract resolveBackAction() for testability

Remove onBackPressed parameter from HAWebView as the back handling
is now the caller's responsibility.
Copilot AI review requested due to automatic review settings March 24, 2026 10:08
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Ports enhanced WebView back-navigation behavior from the legacy WebViewActivity into the Compose-based FrontendScreen, and adds URL utilities + tests to support preserving relative navigation across connection changes.

Changes:

  • Added a Uri.toRelativeUrl() utility (with tests) to extract relative paths including filtered query/fragment.
  • Extended the WebView interface to expose the current relative URL, and updated WebViewPresenterImpl to preserve it on internal/external base URL switches.
  • Moved back handling responsibilities out of HAWebView and implemented HA-specific back navigation in FrontendScreen, with new unit tests for the pure resolveBackAction() function.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
common/src/main/kotlin/io/homeassistant/companion/android/util/UrlUtil.kt Adds Uri.toRelativeUrl() helper for extracting relative URLs with optional query filtering
common/src/test/kotlin/io/homeassistant/companion/android/util/UriExtensionsTest.kt Adds unit coverage for toRelativeUrl() behavior
app/src/main/kotlin/io/homeassistant/companion/android/webview/WebView.kt Extends the app WebView interface with getCurrentWebViewRelativeUrl()
app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewActivity.kt Implements getCurrentWebViewRelativeUrl() and improves Activity back navigation behavior
app/src/main/kotlin/io/homeassistant/companion/android/webview/WebViewPresenterImpl.kt Preserves relative URL on base URL changes and clears history for unreachable stale entries
app/src/test/kotlin/io/homeassistant/companion/android/webview/WebViewPresenterImplTest.kt Updates presenter tests for the new WebView method
app/src/main/kotlin/io/homeassistant/companion/android/util/compose/webview/HAWebView.kt Removes generic back handling from HAWebView composable
app/src/main/kotlin/io/homeassistant/companion/android/frontend/FrontendScreen.kt Adds Compose back handler + pure resolveBackAction() logic
app/src/test/kotlin/io/homeassistant/companion/android/frontend/WebViewBackHandlerTest.kt Adds Robolectric tests for resolveBackAction()

- Remove stale onBackPressed param from HAWebView call in ConnectionScreen
- Add BackHandler directly in ConnectionScreen
- Use mockk<WebView> instead of real WebView(context) to avoid JVM crashes
- Add tests for GoBack (same-origin) and NavigateToRoot (cross-origin)
- Track currentUrl state so it updates after navigating to root,
  preventing repeated NavigateToRoot on subsequent back presses
- Add onNavigatedToRoot callback to WebViewBackHandler
- Fix misleading test name "root path with fragment only"
Resolve conflicts: keep HAWebView without onBackPressed (back handling
in callers), adopt upstream improvements (preview placeholder, try/catch,
onWebViewCreationFailed).
@TimoPtr TimoPtr marked this pull request as draft March 25, 2026 15:32
@TimoPtr
Copy link
Copy Markdown
Member

TimoPtr commented Mar 25, 2026

Let's first merge the first PR before even reviewing that one.

Move BackAction/resolveBackAction into WebViewBackNavigation.kt
(shared with WebViewActivity) and remove duplicated logic from
FrontendScreen. Tests moved to WebViewBackNavigationTest.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants