Merge branch 'main' into develop/customize-home

This commit is contained in:
Damontecres 2026-02-08 13:43:39 -05:00
commit 7cfdd8524c
No known key found for this signature in database
18 changed files with 150 additions and 10 deletions

View file

@ -0,0 +1,80 @@
package com.github.damontecres.wholphin.services
import android.content.Context
import android.os.Build
import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.services.hilt.IoCoroutineScope
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.util.ExceptionHandler
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.serialization.json.Json
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.clientLogApi
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
import org.jellyfin.sdk.model.ClientInfo
import org.jellyfin.sdk.model.DeviceInfo
import org.jellyfin.sdk.model.UUID
import org.jellyfin.sdk.model.api.BaseItemDto
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class MediaReportService
@Inject
constructor(
@param:ApplicationContext private val context: Context,
private val api: ApiClient,
private val serverRepository: ServerRepository,
private val userPreferencesService: UserPreferencesService,
private val clientInfo: ClientInfo,
private val deviceInfo: DeviceInfo,
private val deviceProfileService: DeviceProfileService,
@param:IoCoroutineScope private val ioScope: CoroutineScope,
) {
val json =
Json {
encodeDefaults = false
}
fun sendReportFor(itemId: UUID) {
ioScope.launchIO(ExceptionHandler(autoToast = true)) {
val item = api.userLibraryApi.getItem(itemId = itemId).content
sendReportFor(item)
}
}
suspend fun sendReportFor(item: BaseItemDto) {
val sources =
item.mediaSources ?: api.userLibraryApi
.getItem(itemId = item.id)
.content.mediaSources
val sourcesJson = json.encodeToString(sources)
val playbackPrefs = userPreferencesService.getCurrent().appPreferences.playbackPreferences
val serverVersion = serverRepository.currentServer.value?.serverVersion
val deviceProfile =
deviceProfileService.getOrCreateDeviceProfile(playbackPrefs, serverVersion)
val deviceProfileJson = json.encodeToString(deviceProfile)
val body =
"""
Send media info
serverVersion=$serverVersion
clientInfo=$clientInfo
deviceInfo=$deviceInfo
manufacturer=${Build.MANUFACTURER}
model=${Build.MODEL}
apiLevel=${Build.VERSION.SDK_INT}
playbackPrefs=${playbackPrefs.toString().replace("\n", ", ").replace("\t", " ")}
deviceProfile=$deviceProfileJson
mediaSources=$sourcesJson
""".trimIndent()
Timber.w(body)
val response by api.clientLogApi.logFile(body)
showToast(context, "Sent! Filename=${response.fileName}")
}
}

View file

@ -58,6 +58,7 @@ import com.github.damontecres.wholphin.data.model.LibraryDisplayInfo
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.ui.AspectRatios import com.github.damontecres.wholphin.ui.AspectRatios
import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus import com.github.damontecres.wholphin.ui.RequestOrRestoreFocus
@ -120,6 +121,7 @@ class CollectionFolderViewModel
private val favoriteWatchManager: FavoriteWatchManager, private val favoriteWatchManager: FavoriteWatchManager,
private val backdropService: BackdropService, private val backdropService: BackdropService,
val navigationManager: NavigationManager, val navigationManager: NavigationManager,
val mediaReportService: MediaReportService,
@Assisted itemId: String, @Assisted itemId: String,
@Assisted initialSortAndDirection: SortAndDirection?, @Assisted initialSortAndDirection: SortAndDirection?,
@Assisted("recursive") private val recursive: Boolean, @Assisted("recursive") private val recursive: Boolean,
@ -664,6 +666,7 @@ fun CollectionFolderGrid(
playlistViewModel.loadPlaylists(MediaType.VIDEO) playlistViewModel.loadPlaylists(MediaType.VIDEO)
showPlaylistDialog.makePresent(it) showPlaylistDialog.makePresent(it)
}, },
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
), ),
), ),
onDismissRequest = { moreDialog.makeAbsent() }, onDismissRequest = { moreDialog.makeAbsent() },

View file

@ -22,6 +22,7 @@ import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel import com.github.damontecres.wholphin.ui.data.AddPlaylistViewModel
@ -46,6 +47,7 @@ abstract class RecommendedViewModel(
val context: Context, val context: Context,
val navigationManager: NavigationManager, val navigationManager: NavigationManager,
val favoriteWatchManager: FavoriteWatchManager, val favoriteWatchManager: FavoriteWatchManager,
val mediaReportService: MediaReportService,
private val backdropService: BackdropService, private val backdropService: BackdropService,
) : ViewModel() { ) : ViewModel() {
abstract fun init() abstract fun init()
@ -190,6 +192,7 @@ fun RecommendedContent(
playlistViewModel.loadPlaylists(MediaType.VIDEO) playlistViewModel.loadPlaylists(MediaType.VIDEO)
showPlaylistDialog.makePresent(it) showPlaylistDialog.makePresent(it)
}, },
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
), ),
), ),
onDismissRequest = { moreDialog.makeAbsent() }, onDismissRequest = { moreDialog.makeAbsent() },

View file

@ -14,6 +14,7 @@ import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.SuggestionService import com.github.damontecres.wholphin.services.SuggestionService
import com.github.damontecres.wholphin.services.SuggestionsResource import com.github.damontecres.wholphin.services.SuggestionsResource
@ -58,8 +59,15 @@ class RecommendedMovieViewModel
@Assisted val parentId: UUID, @Assisted val parentId: UUID,
navigationManager: NavigationManager, navigationManager: NavigationManager,
favoriteWatchManager: FavoriteWatchManager, favoriteWatchManager: FavoriteWatchManager,
mediaReportService: MediaReportService,
backdropService: BackdropService, backdropService: BackdropService,
) : RecommendedViewModel(context, navigationManager, favoriteWatchManager, backdropService) { ) : RecommendedViewModel(
context,
navigationManager,
favoriteWatchManager,
mediaReportService,
backdropService,
) {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create(parentId: UUID): RecommendedMovieViewModel fun create(parentId: UUID): RecommendedMovieViewModel

View file

@ -14,6 +14,7 @@ import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.LatestNextUpService import com.github.damontecres.wholphin.services.LatestNextUpService
import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.SuggestionService import com.github.damontecres.wholphin.services.SuggestionService
import com.github.damontecres.wholphin.services.SuggestionsResource import com.github.damontecres.wholphin.services.SuggestionsResource
@ -62,8 +63,15 @@ class RecommendedTvShowViewModel
@Assisted val parentId: UUID, @Assisted val parentId: UUID,
navigationManager: NavigationManager, navigationManager: NavigationManager,
favoriteWatchManager: FavoriteWatchManager, favoriteWatchManager: FavoriteWatchManager,
mediaReportService: MediaReportService,
backdropService: BackdropService, backdropService: BackdropService,
) : RecommendedViewModel(context, navigationManager, favoriteWatchManager, backdropService) { ) : RecommendedViewModel(
context,
navigationManager,
favoriteWatchManager,
mediaReportService,
backdropService,
) {
@AssistedFactory @AssistedFactory
interface Factory { interface Factory {
fun create(parentId: UUID): RecommendedTvShowViewModel fun create(parentId: UUID): RecommendedTvShowViewModel

View file

@ -49,6 +49,7 @@ import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.clientLogApi import org.jellyfin.sdk.api.client.extensions.clientLogApi
import org.jellyfin.sdk.model.ClientInfo import org.jellyfin.sdk.model.ClientInfo
import org.jellyfin.sdk.model.DeviceInfo import org.jellyfin.sdk.model.DeviceInfo
import timber.log.Timber
import java.io.BufferedReader import java.io.BufferedReader
import java.io.InputStreamReader import java.io.InputStreamReader
import javax.inject.Inject import javax.inject.Inject
@ -137,9 +138,13 @@ class DebugViewModel
Send App Logs Send App Logs
clientInfo=$clientInfo clientInfo=$clientInfo
deviceInfo=$deviceInfo deviceInfo=$deviceInfo
manufacturer=${Build.MANUFACTURER}
model=${Build.MODEL}
apiLevel=${Build.VERSION.SDK_INT}
""".trimIndent() + logcat """.trimIndent()
val response by api.clientLogApi.logFile(body) Timber.w(body)
val response by api.clientLogApi.logFile(body + logcat)
showToast(context, "Sent! Filename=${response.fileName}") showToast(context, "Sent! Filename=${response.fileName}")
} }
} }
@ -253,6 +258,7 @@ fun DebugPage(
"DeviceInfo: ${viewModel.deviceInfo}", "DeviceInfo: ${viewModel.deviceInfo}",
"Manufacturer: ${Build.MANUFACTURER}", "Manufacturer: ${Build.MANUFACTURER}",
"Model: ${Build.MODEL}", "Model: ${Build.MODEL}",
"API Level: ${Build.VERSION.SDK_INT}",
"Display Modes:", "Display Modes:",
*viewModel.refreshRateService.supportedDisplayModes, *viewModel.refreshRateService.supportedDisplayModes,
).forEach { ).forEach {

View file

@ -25,9 +25,10 @@ import kotlin.time.Duration.Companion.seconds
data class MoreDialogActions( data class MoreDialogActions(
val navigateTo: (Destination) -> Unit, val navigateTo: (Destination) -> Unit,
var onClickWatch: (UUID, Boolean) -> Unit, val onClickWatch: (UUID, Boolean) -> Unit,
var onClickFavorite: (UUID, Boolean) -> Unit, val onClickFavorite: (UUID, Boolean) -> Unit,
var onClickAddPlaylist: (UUID) -> Unit, val onClickAddPlaylist: (UUID) -> Unit,
val onSendMediaInfo: (UUID) -> Unit,
) )
enum class ClearChosenStreams { enum class ClearChosenStreams {
@ -205,6 +206,14 @@ fun buildMoreDialogItems(
) )
}, },
) )
add(
DialogItem(
text = R.string.send_media_info_log_to_server,
iconStringRes = R.string.fa_file_video,
) {
actions.onSendMediaInfo.invoke(item.id)
},
)
} }
fun buildMoreDialogItemsForHome( fun buildMoreDialogItemsForHome(
@ -314,6 +323,14 @@ fun buildMoreDialogItemsForHome(
}, },
) )
} }
add(
DialogItem(
text = R.string.send_media_info_log_to_server,
iconStringRes = R.string.fa_file_video,
) {
actions.onSendMediaInfo.invoke(itemId)
},
)
} }
fun buildMoreDialogItemsForPerson( fun buildMoreDialogItemsForPerson(

View file

@ -108,6 +108,7 @@ fun DiscoverMovieDetails(
onClickWatch = { itemId, watched -> }, onClickWatch = { itemId, watched -> },
onClickFavorite = { itemId, favorite -> }, onClickFavorite = { itemId, favorite -> },
onClickAddPlaylist = { itemId -> }, onClickAddPlaylist = { itemId -> },
onSendMediaInfo = {},
) )
when (val state = loading) { when (val state = loading) {

View file

@ -97,6 +97,7 @@ fun EpisodeDetails(
playlistViewModel.loadPlaylists(MediaType.VIDEO) playlistViewModel.loadPlaylists(MediaType.VIDEO)
showPlaylistDialog.makePresent(itemId) showPlaylistDialog.makePresent(itemId)
}, },
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
) )
when (val state = loading) { when (val state = loading) {

View file

@ -12,6 +12,7 @@ import com.github.damontecres.wholphin.data.model.ItemPlayback
import com.github.damontecres.wholphin.preferences.ThemeSongVolume import com.github.damontecres.wholphin.preferences.ThemeSongVolume
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.StreamChoiceService import com.github.damontecres.wholphin.services.StreamChoiceService
import com.github.damontecres.wholphin.services.ThemeSongPlayer import com.github.damontecres.wholphin.services.ThemeSongPlayer
@ -48,6 +49,7 @@ class EpisodeViewModel
val serverRepository: ServerRepository, val serverRepository: ServerRepository,
val itemPlaybackRepository: ItemPlaybackRepository, val itemPlaybackRepository: ItemPlaybackRepository,
val streamChoiceService: StreamChoiceService, val streamChoiceService: StreamChoiceService,
val mediaReportService: MediaReportService,
private val themeSongPlayer: ThemeSongPlayer, private val themeSongPlayer: ThemeSongPlayer,
private val favoriteWatchManager: FavoriteWatchManager, private val favoriteWatchManager: FavoriteWatchManager,
private val userPreferencesService: UserPreferencesService, private val userPreferencesService: UserPreferencesService,

View file

@ -125,6 +125,7 @@ fun MovieDetails(
playlistViewModel.loadPlaylists(MediaType.VIDEO) playlistViewModel.loadPlaylists(MediaType.VIDEO)
showPlaylistDialog.makePresent(itemId) showPlaylistDialog.makePresent(itemId)
}, },
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
) )
when (val state = loading) { when (val state = loading) {

View file

@ -18,6 +18,7 @@ import com.github.damontecres.wholphin.preferences.ThemeSongVolume
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.ExtrasService import com.github.damontecres.wholphin.services.ExtrasService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.PeopleFavorites import com.github.damontecres.wholphin.services.PeopleFavorites
import com.github.damontecres.wholphin.services.SeerrService import com.github.damontecres.wholphin.services.SeerrService
@ -64,6 +65,7 @@ class MovieViewModel
val serverRepository: ServerRepository, val serverRepository: ServerRepository,
val itemPlaybackRepository: ItemPlaybackRepository, val itemPlaybackRepository: ItemPlaybackRepository,
val streamChoiceService: StreamChoiceService, val streamChoiceService: StreamChoiceService,
val mediaReportService: MediaReportService,
private val themeSongPlayer: ThemeSongPlayer, private val themeSongPlayer: ThemeSongPlayer,
private val favoriteWatchManager: FavoriteWatchManager, private val favoriteWatchManager: FavoriteWatchManager,
private val peopleFavorites: PeopleFavorites, private val peopleFavorites: PeopleFavorites,

View file

@ -230,6 +230,7 @@ fun SeriesDetails(
playlistViewModel.loadPlaylists(MediaType.VIDEO) playlistViewModel.loadPlaylists(MediaType.VIDEO)
showPlaylistDialog.makePresent(itemId) showPlaylistDialog.makePresent(itemId)
}, },
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
), ),
) )
if (showWatchConfirmation) { if (showWatchConfirmation) {

View file

@ -215,6 +215,7 @@ fun SeriesOverview(
playlistViewModel.loadPlaylists(MediaType.VIDEO) playlistViewModel.loadPlaylists(MediaType.VIDEO)
showPlaylistDialog = it showPlaylistDialog = it
}, },
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
), ),
onChooseVersion = { onChooseVersion = {
chooseVersion = chooseVersion =

View file

@ -16,6 +16,7 @@ import com.github.damontecres.wholphin.data.model.Trailer
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.ExtrasService import com.github.damontecres.wholphin.services.ExtrasService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.PeopleFavorites import com.github.damontecres.wholphin.services.PeopleFavorites
import com.github.damontecres.wholphin.services.SeerrService import com.github.damontecres.wholphin.services.SeerrService
@ -85,6 +86,7 @@ class SeriesViewModel
private val trailerService: TrailerService, private val trailerService: TrailerService,
private val extrasService: ExtrasService, private val extrasService: ExtrasService,
val streamChoiceService: StreamChoiceService, val streamChoiceService: StreamChoiceService,
val mediaReportService: MediaReportService,
private val userPreferencesService: UserPreferencesService, private val userPreferencesService: UserPreferencesService,
private val backdropService: BackdropService, private val backdropService: BackdropService,
private val seerrService: SeerrService, private val seerrService: SeerrService,

View file

@ -139,6 +139,7 @@ fun HomePage(
playlistViewModel.loadPlaylists(MediaType.VIDEO) playlistViewModel.loadPlaylists(MediaType.VIDEO)
showPlaylistDialog = itemId showPlaylistDialog = itemId
}, },
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
), ),
) )
dialog = dialog =

View file

@ -12,6 +12,7 @@ import com.github.damontecres.wholphin.services.DatePlayedService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
import com.github.damontecres.wholphin.services.HomePageResolvedSettings import com.github.damontecres.wholphin.services.HomePageResolvedSettings
import com.github.damontecres.wholphin.services.HomeSettingsService import com.github.damontecres.wholphin.services.HomeSettingsService
import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.services.UserPreferencesService
import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.launchIO
@ -43,14 +44,15 @@ class HomeViewModel
@Inject @Inject
constructor( constructor(
@param:ApplicationContext private val context: Context, @param:ApplicationContext private val context: Context,
private val serverRepository: ServerRepository, val navigationManager: NavigationManager,
private val navDrawerItemRepository: NavDrawerItemRepository, val serverRepository: ServerRepository,
val navDrawerItemRepository: NavDrawerItemRepository,
val mediaReportService: MediaReportService,
private val homeSettingsService: HomeSettingsService, private val homeSettingsService: HomeSettingsService,
private val favoriteWatchManager: FavoriteWatchManager, private val favoriteWatchManager: FavoriteWatchManager,
private val datePlayedService: DatePlayedService, private val datePlayedService: DatePlayedService,
private val backdropService: BackdropService, private val backdropService: BackdropService,
private val userPreferencesService: UserPreferencesService, private val userPreferencesService: UserPreferencesService,
val navigationManager: NavigationManager,
) : ViewModel() { ) : ViewModel() {
private val _state = MutableStateFlow(HomeState.EMPTY) private val _state = MutableStateFlow(HomeState.EMPTY)
val state: StateFlow<HomeState> = _state val state: StateFlow<HomeState> = _state

View file

@ -484,6 +484,7 @@
<string name="zoom_out">Zoom out</string> <string name="zoom_out">Zoom out</string>
<string name="slideshow_duration">Slideshow duration</string> <string name="slideshow_duration">Slideshow duration</string>
<string name="play_videos_during_slideshow">Play videos during slideshow</string> <string name="play_videos_during_slideshow">Play videos during slideshow</string>
<string name="send_media_info_log_to_server">Send media info log to server</string>
<string name="add_row">Add row</string> <string name="add_row">Add row</string>
<string name="genres_in">Genres in %1$s</string> <string name="genres_in">Genres in %1$s</string>