mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-11-06 10:50:58 +01:00
Parse intents to search, view, or play media (#1688)
## Description Adds parsing for intents to search, view, or play media based on the action and parameters. This allows for remotely opening Wholphin and starting playback. This is considered experimental, so the syntax or behaviors may change! See the `Intents.md` file in this PR for syntax and examples. ### Related issues Replaces https://github.com/damontecres/Wholphin/pull/738 Closes https://github.com/damontecres/Wholphin/discussions/1095 ### Testing Emulator with the test commands in the docs ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
4f5cd3e7b1
commit
b1f9c32a19
10 changed files with 415 additions and 78 deletions
|
|
@ -58,6 +58,13 @@
|
|||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="com.github.damontecres.wholphin.PLAYBACK" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="wholphin" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ import com.github.damontecres.wholphin.services.BackdropService
|
|||
import com.github.damontecres.wholphin.services.DatePlayedInvalidationService
|
||||
import com.github.damontecres.wholphin.services.DeviceProfileService
|
||||
import com.github.damontecres.wholphin.services.ImageUrlService
|
||||
import com.github.damontecres.wholphin.services.IntentResult
|
||||
import com.github.damontecres.wholphin.services.IntentService
|
||||
import com.github.damontecres.wholphin.services.LatestNextUpSchedulerService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.PlaybackLifecycleObserver
|
||||
|
|
@ -54,7 +56,6 @@ import com.github.damontecres.wholphin.ui.CoilConfig
|
|||
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
||||
import com.github.damontecres.wholphin.ui.collectLatestIn
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.playback.PlayExternalViewModel
|
||||
|
|
@ -80,7 +81,6 @@ import kotlinx.coroutines.sync.withLock
|
|||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
|
@ -145,6 +145,9 @@ class MainActivity : AppCompatActivity() {
|
|||
@Inject
|
||||
lateinit var screensaverService: ScreensaverService
|
||||
|
||||
@Inject
|
||||
lateinit var intentService: IntentService
|
||||
|
||||
private var signInAuto = true
|
||||
private var playerBackend: PlayerBackend? = null
|
||||
|
||||
|
|
@ -187,8 +190,7 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
navigationManager.backStack = NavBackStack(*backStack.toTypedArray())
|
||||
} else {
|
||||
val startDestination = intent?.let(::extractDestination) ?: Destination.Home()
|
||||
navigationManager.backStack = NavBackStack(startDestination)
|
||||
navigationManager.backStack = NavBackStack(Destination.Home())
|
||||
}
|
||||
|
||||
viewModel.serverRepository.currentUserFlow
|
||||
|
|
@ -223,7 +225,7 @@ class MainActivity : AppCompatActivity() {
|
|||
playerBackend = prefs.playbackPreferences.playerBackend
|
||||
}
|
||||
|
||||
viewModel.appStart()
|
||||
viewModel.appStart(intent)
|
||||
setContent {
|
||||
Surface(
|
||||
Modifier
|
||||
|
|
@ -307,7 +309,7 @@ class MainActivity : AppCompatActivity() {
|
|||
override fun onRestart() {
|
||||
super.onRestart()
|
||||
Timber.d("onRestart")
|
||||
viewModel.appStart()
|
||||
viewModel.appStart(null)
|
||||
if (!playExternalViewModel.launched.value) {
|
||||
// If restarting during playback that is not external, go back a page
|
||||
val lastDest = navigationManager.backStack.lastOrNull()
|
||||
|
|
@ -384,42 +386,9 @@ class MainActivity : AppCompatActivity() {
|
|||
super.onNewIntent(intent)
|
||||
Timber.v("onNewIntent")
|
||||
setIntent(intent)
|
||||
extractDestination(intent)?.let {
|
||||
navigationManager.replace(it)
|
||||
}
|
||||
viewModel.appStart(intent)
|
||||
}
|
||||
|
||||
private fun extractDestination(intent: Intent): Destination? =
|
||||
intent.let {
|
||||
val itemId =
|
||||
it.getStringExtra(INTENT_ITEM_ID)?.toUUIDOrNull()
|
||||
val type =
|
||||
it.getStringExtra(INTENT_ITEM_TYPE)?.let(BaseItemKind::fromNameOrNull)
|
||||
if (itemId != null && type != null) {
|
||||
val seriesId = it.getStringExtra(INTENT_SERIES_ID)?.toUUIDOrNull()
|
||||
val seasonId = it.getStringExtra(INTENT_SEASON_ID)?.toUUIDOrNull()
|
||||
val episodeNumber = it.getIntExtra(INTENT_EPISODE_NUMBER, -1)
|
||||
val seasonNumber = it.getIntExtra(INTENT_SEASON_NUMBER, -1)
|
||||
if (seriesId != null && seasonId != null && episodeNumber >= 0 && seasonNumber >= 0) {
|
||||
Destination.SeriesOverview(
|
||||
itemId = seriesId,
|
||||
type = BaseItemKind.SERIES,
|
||||
seasonEpisode =
|
||||
SeasonEpisodeIds(
|
||||
seasonId = seasonId,
|
||||
seasonNumber = seasonNumber,
|
||||
episodeId = itemId,
|
||||
episodeNumber = episodeNumber,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
Destination.MediaItem(itemId, type)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun changeDisplayMode(modeId: Int) {
|
||||
lifecycleScope.launch(WholphinDispatchers.Main + ExceptionHandler(autoToast = true)) {
|
||||
val attrs = window.attributes
|
||||
|
|
@ -431,13 +400,6 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
const val INTENT_ITEM_ID = "itemId"
|
||||
const val INTENT_ITEM_TYPE = "itemType"
|
||||
const val INTENT_SERIES_ID = "seriesId"
|
||||
const val INTENT_EPISODE_NUMBER = "epNum"
|
||||
const val INTENT_SEASON_NUMBER = "seaNum"
|
||||
const val INTENT_SEASON_ID = "seaId"
|
||||
|
||||
private const val KEY_BACK_STACK = "backStack"
|
||||
private const val KEY_EXTERNAL_PLAYER = "extPlayer"
|
||||
|
||||
|
|
@ -453,16 +415,55 @@ class MainActivityViewModel
|
|||
@param:ApplicationContext private val context: Context,
|
||||
private val preferences: DataStore<AppPreferences>,
|
||||
val serverRepository: ServerRepository,
|
||||
private val navigationManager: SetupNavigationManager,
|
||||
private val setupNavigationManager: SetupNavigationManager,
|
||||
private val navigationManager: NavigationManager,
|
||||
private val deviceProfileService: DeviceProfileService,
|
||||
private val backdropService: BackdropService,
|
||||
private val appUpgradeHandler: AppUpgradeHandler,
|
||||
private val intentService: IntentService,
|
||||
) : ViewModel() {
|
||||
private val mutex = Mutex()
|
||||
|
||||
fun appStart() {
|
||||
fun appStart(intent: Intent?) {
|
||||
viewModelScope.launchDefault {
|
||||
mutex.withLock {
|
||||
try {
|
||||
val result = intent?.let { intentService.parseIntent(intent) }
|
||||
when (result) {
|
||||
is IntentResult.Error -> {
|
||||
setupNavigationManager.navigateTo(SetupDestination.ServerList)
|
||||
Timber.e("Error parsing intent: %s", result.message)
|
||||
showToast(context, "Invalid intent: ${result.message}")
|
||||
return@withLock
|
||||
}
|
||||
|
||||
is IntentResult.Target -> {
|
||||
val current = serverRepository.current.value
|
||||
if (current != null) {
|
||||
Timber.i("Received valid intent, switching to AppContent")
|
||||
navigationManager.replace(result.destination)
|
||||
setupNavigationManager.navigateTo(
|
||||
SetupDestination.AppContent(current),
|
||||
)
|
||||
} else {
|
||||
// This should never happen, but just reset app state if it does
|
||||
setupNavigationManager.navigateTo(SetupDestination.ServerList)
|
||||
Timber.e("Error parsing intent, no user is active")
|
||||
showToast(context, "An error occurred parsing the intent")
|
||||
}
|
||||
return@withLock
|
||||
}
|
||||
|
||||
IntentResult.NoOp,
|
||||
null,
|
||||
-> {
|
||||
// No-op, proceed below
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error parsing intent")
|
||||
}
|
||||
|
||||
try {
|
||||
val needUpgrade = appUpgradeHandler.needUpgrade()
|
||||
if (needUpgrade) {
|
||||
|
|
@ -479,9 +480,9 @@ class MainActivityViewModel
|
|||
val prefs =
|
||||
preferences.data.firstOrNull() ?: AppPreferences.getDefaultInstance()
|
||||
val profileProtected =
|
||||
serverRepository.current.value?.user?.let {
|
||||
it.hasPin || it.requireLogin
|
||||
} == true
|
||||
serverRepository.current.value
|
||||
?.user
|
||||
?.isProtected == true
|
||||
if (prefs.signInAutomatically && !profileProtected) {
|
||||
val current =
|
||||
serverRepository.restoreSession(
|
||||
|
|
@ -490,38 +491,46 @@ class MainActivityViewModel
|
|||
)
|
||||
if (current != null) {
|
||||
if (current.user.hasPin || current.user.requireLogin) {
|
||||
navigationManager.navigateTo(SetupDestination.UserList(current.server))
|
||||
setupNavigationManager.navigateTo(
|
||||
SetupDestination.UserList(
|
||||
current.server,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
// Restored
|
||||
navigationManager.navigateTo(SetupDestination.AppContent(current))
|
||||
setupNavigationManager.navigateTo(
|
||||
SetupDestination.AppContent(
|
||||
current,
|
||||
),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Did not restore
|
||||
navigationManager.navigateTo(SetupDestination.ServerList)
|
||||
setupNavigationManager.navigateTo(SetupDestination.ServerList)
|
||||
}
|
||||
} else {
|
||||
navigationManager.navigateTo(SetupDestination.Loading)
|
||||
setupNavigationManager.navigateTo(SetupDestination.Loading)
|
||||
backdropService.clearBackdrop()
|
||||
val currentServerId = prefs.currentServerId?.toUUIDOrNull()
|
||||
if (currentServerId != null) {
|
||||
val currentServer =
|
||||
serverRepository.serverDao.getServer(currentServerId)?.server
|
||||
if (currentServer != null) {
|
||||
navigationManager.navigateTo(
|
||||
setupNavigationManager.navigateTo(
|
||||
SetupDestination.UserList(
|
||||
currentServer,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
navigationManager.navigateTo(SetupDestination.ServerList)
|
||||
setupNavigationManager.navigateTo(SetupDestination.ServerList)
|
||||
}
|
||||
} else {
|
||||
navigationManager.navigateTo(SetupDestination.ServerList)
|
||||
setupNavigationManager.navigateTo(SetupDestination.ServerList)
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error during appStart")
|
||||
navigationManager.navigateTo(SetupDestination.ServerList)
|
||||
setupNavigationManager.navigateTo(SetupDestination.ServerList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,8 @@ data class JellyfinUser(
|
|||
) {
|
||||
val hasPin: Boolean get() = pin.isNotNullOrBlank()
|
||||
|
||||
val isProtected: Boolean get() = hasPin || requireLogin
|
||||
|
||||
override fun toString(): String =
|
||||
"JellyfinUser(rowId=$rowId, id=$id, name=$name, serverId=$serverId, lastUsed=$lastUsed, " +
|
||||
"accessToken?=${accessToken.isNotNullOrBlank()}, pin?=${pin.isNotNullOrBlank()}), " +
|
||||
|
|
|
|||
|
|
@ -0,0 +1,164 @@
|
|||
package com.github.damontecres.wholphin.services
|
||||
|
||||
import android.app.SearchManager
|
||||
import android.content.Intent
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import kotlinx.coroutines.flow.first
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class IntentService
|
||||
@Inject
|
||||
constructor(
|
||||
private val api: ApiClient,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val userPreferencesService: UserPreferencesService,
|
||||
) {
|
||||
suspend fun parseIntent(intent: Intent): IntentResult {
|
||||
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()) {
|
||||
return IntentResult.NoOp
|
||||
}
|
||||
|
||||
val userId = intent.getStringParam("userId")?.toUUIDOrNull()
|
||||
val serverId = intent.getStringParam("serverId")?.toUUIDOrNull()
|
||||
if (userId != null && serverId != null) {
|
||||
Timber.v("Intent switches user")
|
||||
val user =
|
||||
serverRepository.serverDao.getUser(serverId, userId)
|
||||
?: return IntentResult.Error("User not found")
|
||||
if (!user.isProtected) {
|
||||
serverRepository.restoreSession(serverId, userId)
|
||||
} else {
|
||||
return IntentResult.Error("Cannot switch to specified user")
|
||||
}
|
||||
} else if (serverRepository.currentUser == null) {
|
||||
Timber.v("No current user, so restoring last")
|
||||
val appPrefs = userPreferencesService.flow.first().appPreferences
|
||||
if (appPrefs.signInAutomatically) {
|
||||
val userId = appPrefs.currentUserId.toUUIDOrNull()
|
||||
val serverId = appPrefs.currentServerId.toUUIDOrNull()
|
||||
if (userId != null && serverId != null) {
|
||||
serverRepository.restoreSession(serverId, userId)
|
||||
?: return IntentResult.Error("Error restoring user")
|
||||
} else {
|
||||
return IntentResult.Error("Could not auto sign-in, specify a server & user")
|
||||
}
|
||||
} else {
|
||||
return IntentResult.Error("Auto sign-in not enabled, specify a server & user")
|
||||
}
|
||||
}
|
||||
|
||||
if (action == Intent.ACTION_SEARCH || action == "search") {
|
||||
val query = intent.getStringParam(SearchManager.QUERY)
|
||||
return IntentResult.Target(Destination.Search(query ?: ""))
|
||||
}
|
||||
val itemId =
|
||||
intent.getStringParam("itemId")?.toUUIDOrNull()
|
||||
?: return IntentResult.Error("No item id provided")
|
||||
|
||||
val item =
|
||||
try {
|
||||
api.userLibraryApi
|
||||
.getItem(itemId)
|
||||
.content
|
||||
.let { BaseItem(it) }
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(ex, "Error fetching item %s", itemId)
|
||||
return IntentResult.Error("Could not fetch item $itemId")
|
||||
}
|
||||
|
||||
val destination =
|
||||
when (action) {
|
||||
Intent.ACTION_VIEW, "view" -> {
|
||||
item.destination()
|
||||
}
|
||||
|
||||
"com.github.damontecres.wholphin.PLAYBACK", "play" -> {
|
||||
val position = intent.getLongParam("position")?.coerceAtLeast(0)
|
||||
Destination.Playback(
|
||||
itemId = itemId,
|
||||
positionMs = position ?: 0L,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
return IntentResult.Error("Invalid action: ${intent.action}")
|
||||
}
|
||||
}
|
||||
return IntentResult.Target(destination)
|
||||
}
|
||||
|
||||
private fun Intent.getStringParam(key: String) = getStringExtra(key) ?: data?.getQueryParameter(key)
|
||||
|
||||
private fun Intent.getLongParam(key: String) =
|
||||
getLongExtra(key, -1).takeIf { it >= 0 } ?: data?.getQueryParameter(key)?.toLongOrNull()
|
||||
|
||||
private fun getDestinationFromChannel(intent: Intent): Destination? =
|
||||
intent.let {
|
||||
val itemId =
|
||||
it.getStringExtra(INTENT_ITEM_ID)?.toUUIDOrNull()
|
||||
val type =
|
||||
it.getStringExtra(INTENT_ITEM_TYPE)?.let(BaseItemKind::fromNameOrNull)
|
||||
if (itemId != null && type != null) {
|
||||
val seriesId = it.getStringExtra(INTENT_SERIES_ID)?.toUUIDOrNull()
|
||||
val seasonId = it.getStringExtra(INTENT_SEASON_ID)?.toUUIDOrNull()
|
||||
val episodeNumber = it.getIntExtra(INTENT_EPISODE_NUMBER, -1)
|
||||
val seasonNumber = it.getIntExtra(INTENT_SEASON_NUMBER, -1)
|
||||
if (seriesId != null && seasonId != null && episodeNumber >= 0 && seasonNumber >= 0) {
|
||||
Destination.SeriesOverview(
|
||||
itemId = seriesId,
|
||||
type = BaseItemKind.SERIES,
|
||||
seasonEpisode =
|
||||
SeasonEpisodeIds(
|
||||
seasonId = seasonId,
|
||||
seasonNumber = seasonNumber,
|
||||
episodeId = itemId,
|
||||
episodeNumber = episodeNumber,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
Destination.MediaItem(itemId, type)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val INTENT_ITEM_ID = "itemId"
|
||||
const val INTENT_ITEM_TYPE = "itemType"
|
||||
const val INTENT_SERIES_ID = "seriesId"
|
||||
const val INTENT_EPISODE_NUMBER = "epNum"
|
||||
const val INTENT_SEASON_NUMBER = "seaNum"
|
||||
const val INTENT_SEASON_ID = "seaId"
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface IntentResult {
|
||||
data class Error(
|
||||
val message: String,
|
||||
) : IntentResult
|
||||
|
||||
data object NoOp : IntentResult
|
||||
|
||||
data class Target(
|
||||
val destination: Destination,
|
||||
) : IntentResult
|
||||
}
|
||||
|
|
@ -24,6 +24,7 @@ import com.github.damontecres.wholphin.data.model.BaseItem
|
|||
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.services.ImageUrlService
|
||||
import com.github.damontecres.wholphin.services.IntentService
|
||||
import com.github.damontecres.wholphin.services.LatestNextUpService
|
||||
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -226,27 +227,27 @@ class TvProviderWorker
|
|||
|
||||
setIntent(
|
||||
Intent(context, MainActivity::class.java)
|
||||
.putExtra(MainActivity.INTENT_ITEM_ID, item.id.toString())
|
||||
.putExtra(MainActivity.INTENT_ITEM_TYPE, item.type.serialName)
|
||||
.putExtra(IntentService.INTENT_ITEM_ID, item.id.toString())
|
||||
.putExtra(IntentService.INTENT_ITEM_TYPE, item.type.serialName)
|
||||
.apply {
|
||||
if (item.type == BaseItemKind.EPISODE) {
|
||||
putExtra(
|
||||
MainActivity.INTENT_SERIES_ID,
|
||||
IntentService.INTENT_SERIES_ID,
|
||||
dto.seriesId?.toString(),
|
||||
)
|
||||
putExtra(
|
||||
MainActivity.INTENT_SEASON_ID,
|
||||
IntentService.INTENT_SEASON_ID,
|
||||
dto.seasonId?.toString(),
|
||||
)
|
||||
dto.parentIndexNumber?.let {
|
||||
putExtra(
|
||||
MainActivity.INTENT_SEASON_NUMBER,
|
||||
IntentService.INTENT_SEASON_NUMBER,
|
||||
it,
|
||||
)
|
||||
}
|
||||
dto.indexNumber?.let {
|
||||
putExtra(
|
||||
MainActivity.INTENT_EPISODE_NUMBER,
|
||||
IntentService.INTENT_EPISODE_NUMBER,
|
||||
it,
|
||||
)
|
||||
}
|
||||
|
|
@ -363,27 +364,27 @@ class TvProviderWorker
|
|||
|
||||
setIntent(
|
||||
Intent(context, MainActivity::class.java)
|
||||
.putExtra(MainActivity.INTENT_ITEM_ID, item.id.toString())
|
||||
.putExtra(MainActivity.INTENT_ITEM_TYPE, item.type.serialName)
|
||||
.putExtra(IntentService.INTENT_ITEM_ID, item.id.toString())
|
||||
.putExtra(IntentService.INTENT_ITEM_TYPE, item.type.serialName)
|
||||
.apply {
|
||||
if (item.type == BaseItemKind.EPISODE) {
|
||||
putExtra(
|
||||
MainActivity.INTENT_SERIES_ID,
|
||||
IntentService.INTENT_SERIES_ID,
|
||||
dto.seriesId?.toString(),
|
||||
)
|
||||
putExtra(
|
||||
MainActivity.INTENT_SEASON_ID,
|
||||
IntentService.INTENT_SEASON_ID,
|
||||
dto.seasonId?.toString(),
|
||||
)
|
||||
dto.parentIndexNumber?.let {
|
||||
putExtra(
|
||||
MainActivity.INTENT_SEASON_NUMBER,
|
||||
IntentService.INTENT_SEASON_NUMBER,
|
||||
it,
|
||||
)
|
||||
}
|
||||
dto.indexNumber?.let {
|
||||
putExtra(
|
||||
MainActivity.INTENT_EPISODE_NUMBER,
|
||||
IntentService.INTENT_EPISODE_NUMBER,
|
||||
it,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -496,6 +496,7 @@ private const val COMBINED_ROW = TAB_ROW + 1
|
|||
|
||||
@Composable
|
||||
fun SearchPage(
|
||||
initialQuery: String,
|
||||
userPreferences: UserPreferences,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SearchViewModel = hiltViewModel(),
|
||||
|
|
@ -513,7 +514,7 @@ fun SearchPage(
|
|||
val voiceSearchButtonVisible = prefs.showVoiceSearchButton
|
||||
|
||||
// val query = rememberTextFieldState()
|
||||
var query by rememberSaveable { mutableStateOf("") }
|
||||
var query by rememberSaveable { mutableStateOf(initialQuery) }
|
||||
val focusRequesters = remember { List(SEERR_ROW + 1) { FocusRequester() } }
|
||||
|
||||
val seerrActive by viewModel.seerrActive.collectAsState(initial = false)
|
||||
|
|
|
|||
|
|
@ -54,10 +54,11 @@ sealed class Destination(
|
|||
) : Destination(true)
|
||||
|
||||
@Serializable
|
||||
data object UserAppPreferences : Destination(true)
|
||||
data class Search(
|
||||
val query: String = "",
|
||||
) : Destination()
|
||||
|
||||
@Serializable
|
||||
data object Search : Destination()
|
||||
data object UserAppPreferences : Destination(true)
|
||||
|
||||
@Serializable
|
||||
data class SeriesOverview(
|
||||
|
|
|
|||
|
|
@ -351,9 +351,10 @@ fun DestinationContent(
|
|||
LicenseInfo(modifier)
|
||||
}
|
||||
|
||||
Destination.Search -> {
|
||||
is Destination.Search -> {
|
||||
LaunchedEffect(Unit) { onClearBackdrop.invoke() }
|
||||
SearchPage(
|
||||
initialQuery = destination.query,
|
||||
userPreferences = preferences,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -409,7 +409,7 @@ fun NavDrawer(
|
|||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
viewModel.setIndex(SEARCH_INDEX)
|
||||
viewModel.navigationManager.navigateToFromDrawer(Destination.Search)
|
||||
viewModel.navigationManager.navigateToFromDrawer(Destination.Search())
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue