Add back stack for deep link navigation (#1756)

## Description
When testing the new deep link feature from this PR
[#1688](https://github.com/damontecres/Wholphin/pull/1688)
I noticed that the back button would close the app. As in, all routes
are cleared and only the playback is in the nav stack.

I figured a more desirable behavior would be to create a nav stack,
always starting with the home screen, then also inserting the media
item's details page, if available.

To achieve this, I made the return Target.destinations a list of
destinations, and updated the other two uses in IntentService.

Open to implementation tweaks, or scrap it if this isn't how it should
work, haha.

### Testing

Tested deep links, which produced the following nav stacks:
- Movie view
  - Home → Movie details
- Movie playback (with and without position)
  - Home → Movie details → Playback
- Series view
  - Home → Series details
- Season view
  - Home → Season details
- Episode view
  - Home → Episode details
- Episode playback (with and without position)
  - Home → Episode details → Playback
- Trailer/extras playback (no details page available)
  - Home → Playback
- TV channel view
  - Home → Playback
- TV channel playback
  - Home → Playback

## AI or LLM usage
AI was used to generate the initial code, debug and test, then manual
refactoring and cleanup was done.

---------

Co-authored-by: Damontecres <damontecres@gmail.com>
This commit is contained in:
calebvetter 2026-08-11 07:59:20 -07:00 • committed by GitHub
parent a0966dd05a
commit c1abb57cd2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 46 additions and 15 deletions

View file

@ -449,7 +449,14 @@ class MainActivityViewModel
val current = serverRepository.current.value
if (current != null) {
Timber.i("Received valid intent, switching to AppContent")
navigationManager.replace(result.destination)
if (result.addHomeToBackStack) {
navigationManager.reloadHome()
} else {
navigationManager.backStack.clear()
}
navigationManager.backStack.addAll(result.destinations)
setupNavigationManager.navigateTo(
SetupDestination.AppContent(current),
)

View file

@ -27,12 +27,11 @@ class IntentService
Timber.v("Parsing intent %s", intent)
Timber.v("Intent extras: %s", intent.extras)
Timber.v("Intent data: %s", intent.data)
if (intent.getStringParam("type") != null) {
return getDestinationFromChannel(intent)?.let { IntentResult.Target(it) }
?: IntentResult.Error("Invalid parameters")
}
val action = intent.action ?: intent.data?.host
if (action == Intent.ACTION_MAIN || action.isNullOrBlank()) {
if (
intent.getStringParam("type") == null &&
(action == Intent.ACTION_MAIN || action.isNullOrBlank())
) {
return IntentResult.NoOp
}
@ -65,9 +64,19 @@ class IntentService
}
}
if (intent.getStringParam("type") != null) {
return getDestinationFromChannel(intent)?.let {
IntentResult.Target(
destinations = listOf(it),
addHomeToBackStack = false,
)
}
?: IntentResult.Error("Invalid parameters")
}
if (action == Intent.ACTION_SEARCH || action == "search") {
val query = intent.getStringParam(SearchManager.QUERY)
return IntentResult.Target(Destination.Search(query ?: ""))
return IntentResult.Target(listOf(Destination.Search(query ?: "")))
}
val itemId =
intent.getStringParam("itemId")?.toUUIDOrNull()
@ -84,25 +93,39 @@ class IntentService
return IntentResult.Error("Could not fetch item $itemId")
}
val destination =
val itemDestination = item.destination()
val destinations =
when (action) {
Intent.ACTION_VIEW, "view" -> {
item.destination()
listOf(itemDestination)
}
"com.github.damontecres.wholphin.PLAYBACK", "play" -> {
val position = intent.getLongParam("position")?.coerceAtLeast(0)
Destination.Playback(
itemId = itemId,
positionMs = position ?: 0L,
)
val playbackDestination =
Destination.Playback(
itemId = itemId,
positionMs = position ?: 0L,
)
if (itemDestination is Destination.Playback) {
listOf(playbackDestination)
} else {
listOf(
itemDestination,
playbackDestination,
)
}
}
else -> {
return IntentResult.Error("Invalid action: ${intent.action}")
}
}
return IntentResult.Target(destination)
return IntentResult.Target(destinations)
}
private fun Intent.getStringParam(key: String) = getStringExtra(key) ?: data?.getQueryParameter(key)
@ -159,6 +182,7 @@ sealed interface IntentResult {
data object NoOp : IntentResult
data class Target(
val destination: Destination,
val destinations: List<Destination>,
val addHomeToBackStack: Boolean = true,
) : IntentResult
}