Log while navigating around the app (#195)

Just adds some logging while navigating around the app. If an error or
crash occurs and users want to share their logs, this information is
useful to know what page.

Logs are never sent without explicit user content and they are only
shared with the Jellyfin server the app is currently connected to. [See
here for more
details](https://github.com/damontecres/Wholphin/wiki/Gathering-app-logs#privacy)

Also stops theme music when the app is paused similar to #191
This commit is contained in:
damontecres 2025-11-11 20:10:16 -05:00 committed by GitHub
parent ed2ec2f97e
commit 5e2c75bb70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 40 additions and 3 deletions

View file

@ -38,6 +38,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.acra.ACRA
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.extensions.ticks import org.jellyfin.sdk.model.extensions.ticks
import timber.log.Timber import timber.log.Timber
@ -380,3 +381,12 @@ fun equalsNotNull(
a: Any?, a: Any?,
b: Any?, b: Any?,
) = a != null && b != null && a == b ) = a != null && b != null && a == b
fun logTab(
name: String,
tabIndex: Int,
) {
val info = "$tabIndex, $name"
Timber.i("Current tab: $info")
ACRA.errorReporter.putCustomData("tabInfo", info)
}

View file

@ -38,6 +38,7 @@ import com.github.damontecres.wholphin.ui.data.VideoSortOptions
import com.github.damontecres.wholphin.ui.detail.livetv.DvrSchedule import com.github.damontecres.wholphin.ui.detail.livetv.DvrSchedule
import com.github.damontecres.wholphin.ui.detail.livetv.TvGuideGrid import com.github.damontecres.wholphin.ui.detail.livetv.TvGuideGrid
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.logTab
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.nav.NavigationManager import com.github.damontecres.wholphin.ui.nav.NavigationManager
import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.ui.setValueOnMain
@ -102,6 +103,7 @@ fun CollectionFolderLiveTv(
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() } LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
LaunchedEffect(selectedTabIndex) { LaunchedEffect(selectedTabIndex) {
logTab("livetv", selectedTabIndex)
viewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex) viewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex)
} }
val onClickItem = { item: BaseItem -> val onClickItem = { item: BaseItem ->

View file

@ -33,6 +33,7 @@ import com.github.damontecres.wholphin.ui.components.RecommendedMovie
import com.github.damontecres.wholphin.ui.components.TabRow import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.data.MovieSortOptions import com.github.damontecres.wholphin.ui.data.MovieSortOptions
import com.github.damontecres.wholphin.ui.data.VideoSortOptions import com.github.damontecres.wholphin.ui.data.VideoSortOptions
import com.github.damontecres.wholphin.ui.logTab
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
@ -62,6 +63,7 @@ fun CollectionFolderMovie(
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() } LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
LaunchedEffect(selectedTabIndex) { LaunchedEffect(selectedTabIndex) {
logTab("movie", selectedTabIndex)
preferencesViewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex) preferencesViewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex)
} }

View file

@ -32,6 +32,7 @@ import com.github.damontecres.wholphin.ui.components.GenreCardGrid
import com.github.damontecres.wholphin.ui.components.RecommendedTvShow import com.github.damontecres.wholphin.ui.components.RecommendedTvShow
import com.github.damontecres.wholphin.ui.components.TabRow import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.data.SeriesSortOptions import com.github.damontecres.wholphin.ui.data.SeriesSortOptions
import com.github.damontecres.wholphin.ui.logTab
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
@ -60,6 +61,7 @@ fun CollectionFolderTv(
LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() } LaunchedEffect(Unit) { firstTabFocusRequester.tryRequestFocus() }
LaunchedEffect(selectedTabIndex) { LaunchedEffect(selectedTabIndex) {
logTab("tv", selectedTabIndex)
preferencesViewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex) preferencesViewModel.saveRememberedTab(preferences, destination.itemId, selectedTabIndex)
} }

View file

@ -32,6 +32,7 @@ import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.data.EpisodeSortOptions import com.github.damontecres.wholphin.ui.data.EpisodeSortOptions
import com.github.damontecres.wholphin.ui.data.MovieSortOptions import com.github.damontecres.wholphin.ui.data.MovieSortOptions
import com.github.damontecres.wholphin.ui.data.SeriesSortOptions import com.github.damontecres.wholphin.ui.data.SeriesSortOptions
import com.github.damontecres.wholphin.ui.logTab
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
@ -63,6 +64,7 @@ fun FavoritesPage(
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
LaunchedEffect(selectedTabIndex) { LaunchedEffect(selectedTabIndex) {
logTab("favorites", selectedTabIndex)
preferencesViewModel.saveRememberedTab( preferencesViewModel.saveRememberedTab(
preferences, preferences,
NavDrawerItem.Favorites.id, NavDrawerItem.Favorites.id,

View file

@ -16,6 +16,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.relocation.BringIntoViewRequester import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.relocation.bringIntoViewRequester import androidx.compose.foundation.relocation.bringIntoViewRequester
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
@ -50,6 +51,7 @@ import com.github.damontecres.wholphin.ui.components.LoadingPage
import com.github.damontecres.wholphin.ui.components.TabRow import com.github.damontecres.wholphin.ui.components.TabRow
import com.github.damontecres.wholphin.ui.ifElse import com.github.damontecres.wholphin.ui.ifElse
import com.github.damontecres.wholphin.ui.isNotNullOrBlank import com.github.damontecres.wholphin.ui.isNotNullOrBlank
import com.github.damontecres.wholphin.ui.logTab
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.formatDateTime import com.github.damontecres.wholphin.util.formatDateTime
import kotlin.time.Duration import kotlin.time.Duration
@ -77,6 +79,9 @@ fun SeriesOverviewContent(
) { ) {
val bringIntoViewRequester = remember { BringIntoViewRequester() } val bringIntoViewRequester = remember { BringIntoViewRequester() }
var selectedTabIndex by rememberSaveable(position) { mutableIntStateOf(position.seasonTabIndex) } var selectedTabIndex by rememberSaveable(position) { mutableIntStateOf(position.seasonTabIndex) }
LaunchedEffect(selectedTabIndex) {
logTab("series_overview", selectedTabIndex)
}
val tabRowFocusRequester = remember { FocusRequester() } val tabRowFocusRequester = remember { FocusRequester() }
val focusedEpisode = val focusedEpisode =

View file

@ -11,7 +11,6 @@ import com.github.damontecres.wholphin.data.model.JellyfinServer
import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.data.model.JellyfinUser
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import org.jellyfin.sdk.model.api.DeviceProfile import org.jellyfin.sdk.model.api.DeviceProfile
import timber.log.Timber
/** /**
* This is generally the root composable of the of the app * This is generally the root composable of the of the app
@ -38,7 +37,6 @@ fun ApplicationContent(
entryProvider = { key -> entryProvider = { key ->
key as Destination key as Destination
val contentKey = "${key}_${server?.id}_${user?.id}" val contentKey = "${key}_${server?.id}_${user?.id}"
Timber.d("Navigate: %s", key)
NavEntry(key, contentKey = contentKey) { NavEntry(key, contentKey = contentKey) {
if (key.fullScreen) { if (key.fullScreen) {
DestinationContent( DestinationContent(

View file

@ -1,6 +1,8 @@
package com.github.damontecres.wholphin.ui.nav package com.github.damontecres.wholphin.ui.nav
import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.NavKey
import org.acra.ACRA
import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -18,6 +20,7 @@ class NavigationManager
*/ */
fun navigateTo(destination: Destination) { fun navigateTo(destination: Destination) {
backStack.add(destination) backStack.add(destination)
log()
} }
/** /**
@ -26,6 +29,7 @@ class NavigationManager
fun navigateToFromDrawer(destination: Destination) { fun navigateToFromDrawer(destination: Destination) {
goToHome() goToHome()
backStack.add(destination) backStack.add(destination)
log()
} }
/** /**
@ -33,6 +37,7 @@ class NavigationManager
*/ */
fun goBack() { fun goBack() {
backStack.removeLastOrNull() backStack.removeLastOrNull()
log()
} }
/** /**
@ -45,6 +50,7 @@ class NavigationManager
if (backStack[0] !is Destination.Home) { if (backStack[0] !is Destination.Home) {
backStack[0] = Destination.Home() backStack[0] = Destination.Home()
} }
log()
} }
/** /**
@ -54,5 +60,12 @@ class NavigationManager
goToHome() goToHome()
val id = (backStack[0] as Destination.Home).id + 1 val id = (backStack[0] as Destination.Home).id + 1
backStack[0] = Destination.Home(id) backStack[0] = Destination.Home(id)
log()
}
private fun log() {
val dest = backStack.lastOrNull().toString()
Timber.i("Current Destination: %s", dest)
ACRA.errorReporter.putCustomData("destination", dest)
} }
} }

View file

@ -16,6 +16,7 @@ class PlaybackLifecycleObserver
constructor( constructor(
private val navigationManager: NavigationManager, private val navigationManager: NavigationManager,
private val playerFactory: PlayerFactory, private val playerFactory: PlayerFactory,
private val themeSongPlayer: ThemeSongPlayer,
) : DefaultLifecycleObserver { ) : DefaultLifecycleObserver {
private var wasPlaying: Boolean? = null private var wasPlaying: Boolean? = null
@ -36,11 +37,13 @@ class PlaybackLifecycleObserver
wasPlaying = it.isPlaying wasPlaying = it.isPlaying
it.pause() it.pause()
} }
themeSongPlayer.stop()
} }
override fun onStop(owner: LifecycleOwner) { override fun onStop(owner: LifecycleOwner) {
if (navigationManager.backStack.lastOrNull() is Destination.Playback) { if (navigationManager.backStack.lastOrNull() is Destination.Playback) {
navigationManager.goBack() navigationManager.goBack()
} }
themeSongPlayer.stop()
} }
} }

View file

@ -78,7 +78,6 @@ class ThemeSongPlayer
volumeLevel: ThemeSongVolume, volumeLevel: ThemeSongVolume,
url: String, url: String,
) { ) {
stop()
val volumeLevel = val volumeLevel =
when (volumeLevel) { when (volumeLevel) {
ThemeSongVolume.UNRECOGNIZED, ThemeSongVolume.UNRECOGNIZED,
@ -92,6 +91,7 @@ class ThemeSongPlayer
ThemeSongVolume.HIGHEST -> 75f ThemeSongVolume.HIGHEST -> 75f
} }
player.apply { player.apply {
stop()
volume = volumeLevel volume = volumeLevel
setMediaItem(MediaItem.fromUri(url)) setMediaItem(MediaItem.fromUri(url))
prepare() prepare()