Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
6d53869
WIP, basic MVP
damontecres Jan 23, 2026
5dcdde3
Handle adding rows & back handling
damontecres Jan 23, 2026
1ce380f
Apply view options to preview
damontecres Jan 23, 2026
71ddb59
Add apply all button
damontecres Jan 23, 2026
2c6e7a5
Use typed HomeRow in HomeRowLoadingState.Success
damontecres Jan 23, 2026
45e023d
Revert "Use typed HomeRow in HomeRowLoadingState.Success"
damontecres Jan 23, 2026
00f6de8
Handle genre cards
damontecres Jan 24, 2026
a90e6bc
Separate type for continue & next up
damontecres Jan 24, 2026
d809cc2
Switch ids to ints
damontecres Jan 24, 2026
2da34a8
Add some tests to ensure samples
damontecres Jan 24, 2026
cd71d5b
Support collections
damontecres Jan 24, 2026
b2016bf
Support different rows
damontecres Jan 24, 2026
84dc562
Renaming
damontecres Jan 24, 2026
ee8a4ac
Basic save/load locally
damontecres Jan 24, 2026
a174d98
Refactoring to expose current configuration
damontecres Jan 24, 2026
9834991
Move data loading to HomeSettingsService
damontecres Jan 25, 2026
ce4f847
Move home page over to use custom settings
damontecres Jan 25, 2026
025c253
Remove unused code
damontecres Jan 25, 2026
2d0e5b8
Add settings to go to home settings
damontecres Jan 25, 2026
d7236e7
Ensure home settings are saved
damontecres Jan 25, 2026
668a396
Refactoring
damontecres Jan 25, 2026
f58b371
Parse by row
damontecres Jan 25, 2026
15ce562
Move ID into display models & add documentation
damontecres Jan 25, 2026
efb87bd
UI controls
damontecres Jan 26, 2026
d99c947
Merge branch 'main' into fea/customize-home
damontecres Jan 27, 2026
ccb7925
Add row for favorite items
damontecres Jan 27, 2026
9cd3e7d
WIP live tv home rows
damontecres Jan 28, 2026
01bdf2c
Merge branch 'main' into fea/customize-home
damontecres Jan 29, 2026
7518380
Use navigation
damontecres Jan 29, 2026
e11b9ec
Scroll to new rows
damontecres Jan 29, 2026
f315531
Save/load from server
damontecres Jan 29, 2026
b73900b
Move some settings to new page
damontecres Jan 29, 2026
44a08f2
Add toggle for using series image
damontecres Jan 29, 2026
43fde88
Disable discover & use different settings for genre rows
damontecres Jan 29, 2026
7e53f95
Focus improvements
damontecres Jan 29, 2026
fb89179
Backdrops for genre cards
damontecres Jan 29, 2026
d344877
More consistent titles
damontecres Jan 30, 2026
8437ddb
Load from web client
damontecres Jan 30, 2026
bec833b
Move code into coroutines
damontecres Jan 30, 2026
8c33738
Merge branch 'main' into fea/customize-home
damontecres Jan 30, 2026
7345b04
Fix crash
damontecres Jan 30, 2026
f786ec6
Fix tests
damontecres Jan 30, 2026
9a86e85
Better support for using thumbnails
damontecres Feb 1, 2026
034cf5d
Merge branch 'main' into develop/customize-home
damontecres Feb 4, 2026
2d77249
Merge branch 'main' into develop/customize-home
damontecres Feb 5, 2026
b31c004
Add option for suggestions row for supported library types
damontecres Feb 5, 2026
a8352e7
Match style to regular preferences
damontecres Feb 5, 2026
3f4ec24
Fix tests
damontecres Feb 5, 2026
70ad1ec
Refresh watching rows first
damontecres Feb 5, 2026
99b15f2
Add option to quickly change to thumb images
damontecres Feb 5, 2026
1828e94
Option to show title below cards
damontecres Feb 5, 2026
682b81d
Add global reset to default
damontecres Feb 5, 2026
c844395
Move home page specific settings
damontecres Feb 5, 2026
c3c054a
Show empty rows in settings
damontecres Feb 5, 2026
d5e3baf
Merge branch 'main' into develop/customize-home
damontecres Feb 8, 2026
7cfdd85
Merge branch 'main' into develop/customize-home
damontecres Feb 8, 2026
9fa169a
Merge branch 'main' into develop/customize-home
damontecres Feb 9, 2026
8724573
Merge branch 'main' into develop/customize-home
damontecres Feb 10, 2026
3879072
Check version when parsing
damontecres Feb 10, 2026
7005adc
A few UI tweaks
damontecres Feb 10, 2026
561b12f
Merge branch 'main' into develop/customize-home
damontecres Feb 12, 2026
f592f30
Add some display option presets
damontecres Feb 12, 2026
9ea407f
Ensure changes are reflected when going back to home page
damontecres Feb 12, 2026
290070f
Merge branch 'main' into develop/customize-home
damontecres Feb 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import kotlin.time.Duration
data class BaseItem(
val data: BaseItemDto,
val useSeriesForPrimary: Boolean,
val imageUrlOverride: String? = null,
) : CardGridItem {
val id get() = data.id

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
@file:UseSerializers(UUIDSerializer::class)

package com.github.damontecres.wholphin.data.model

import com.github.damontecres.wholphin.preferences.PrefContentScale
import com.github.damontecres.wholphin.ui.AspectRatio
import com.github.damontecres.wholphin.ui.Cards
import com.github.damontecres.wholphin.ui.components.ViewOptionImageType
import com.github.damontecres.wholphin.ui.data.SortAndDirection
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.request.GetItemsRequest
import org.jellyfin.sdk.model.serializer.UUIDSerializer
import java.util.UUID

@Serializable
sealed interface HomeRowConfig {
val viewOptions: HomeRowViewOptions

fun updateViewOptions(viewOptions: HomeRowViewOptions): HomeRowConfig

/**
* Continue watching media that the user has started but not finished
*/
@Serializable
@SerialName("ContinueWatching")
data class ContinueWatching(
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): ContinueWatching = this.copy(viewOptions = viewOptions)
}

/**
* Next up row for next episodes in a series the user has started
*/
@Serializable
@SerialName("NextUp")
data class NextUp(
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): NextUp = this.copy(viewOptions = viewOptions)
}

/**
* Combined [ContinueWatching] and [NextUp]
*/
@Serializable
@SerialName("ContinueWatchingCombined")
data class ContinueWatchingCombined(
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): ContinueWatchingCombined = this.copy(viewOptions = viewOptions)
}

/**
* Media recently added to a library
*/
@Serializable
@SerialName("RecentlyAdded")
data class RecentlyAdded(
val parentId: UUID,
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): RecentlyAdded = this.copy(viewOptions = viewOptions)
}

/**
* Media recently released (premiere date) in a library
*/
@Serializable
@SerialName("RecentlyReleased")
data class RecentlyReleased(
val parentId: UUID,
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): RecentlyReleased = this.copy(viewOptions = viewOptions)
}

/**
* Row of a genres in a library
*/
@Serializable
@SerialName("Genres")
data class Genres(
val parentId: UUID,
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions.genreDefault,
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): Genres = this.copy(viewOptions = viewOptions)
}

/**
* Favorites for a specific type
*/
@Serializable
@SerialName("Favorite")
data class Favorite(
val kind: BaseItemKind,
override val viewOptions: HomeRowViewOptions =
if (kind == BaseItemKind.EPISODE) {
HomeRowViewOptions(
heightDp = Cards.HEIGHT_EPISODE,
aspectRatio = AspectRatio.WIDE,
)
} else {
HomeRowViewOptions()
},
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): Favorite = this.copy(viewOptions = viewOptions)
}

/**
*
*/
@Serializable
@SerialName("Recordings")
data class Recordings(
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): Recordings = this.copy(viewOptions = viewOptions)
}

/**
*
*/
@Serializable
@SerialName("TvPrograms")
data class TvPrograms(
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): TvPrograms = this.copy(viewOptions = viewOptions)
}

/**
* Fetch suggestions from [com.github.damontecres.wholphin.services.SuggestionService] for the given parent ID
*/
@Serializable
@SerialName("Suggestions")
data class Suggestions(
val parentId: UUID,
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): Suggestions = this.copy(viewOptions = viewOptions)
}

/**
* Fetch by parent ID such as a library, collection, or playlist with optional simple sorting
*/
@Serializable
@SerialName("ByParent")
data class ByParent(
val parentId: UUID,
val recursive: Boolean,
val sort: SortAndDirection? = null,
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): ByParent = this.copy(viewOptions = viewOptions)
}

/**
* An arbitrary [GetItemsRequest] allowing to query for anything
*/
@Serializable
@SerialName("GetItems")
data class GetItems(
val name: String,
val getItems: GetItemsRequest,
override val viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
) : HomeRowConfig {
override fun updateViewOptions(viewOptions: HomeRowViewOptions): GetItems = this.copy(viewOptions = viewOptions)
}
}

/**
* Root class for home page settings
*
* Contains the list of rows and a version
*/
@Serializable
@SerialName("HomePageSettings")
data class HomePageSettings(
val rows: List<HomeRowConfig>,
val version: Int,
) {
companion object {
val EMPTY = HomePageSettings(listOf(), SUPPORTED_HOME_PAGE_SETTINGS_VERSION)
}
}

/**
* This is the max version supported by this version of the app
*/
const val SUPPORTED_HOME_PAGE_SETTINGS_VERSION = 1

/**
* View options for displaying a row
*
* Allows for changing things like height or aspect ratio
*/
@Serializable
data class HomeRowViewOptions(
val heightDp: Int = Cards.HEIGHT_2X3_DP,
val spacing: Int = 16,
val contentScale: PrefContentScale = PrefContentScale.FIT,
val aspectRatio: AspectRatio = AspectRatio.TALL,
val imageType: ViewOptionImageType = ViewOptionImageType.PRIMARY,
val showTitles: Boolean = false,
val useSeries: Boolean = true,
val episodeContentScale: PrefContentScale = PrefContentScale.FIT,
val episodeAspectRatio: AspectRatio = AspectRatio.TALL,
val episodeImageType: ViewOptionImageType = ViewOptionImageType.PRIMARY,
) {
companion object {
val genreDefault =
HomeRowViewOptions(
heightDp = Cards.HEIGHT_EPISODE,
aspectRatio = AspectRatio.WIDE,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,12 @@ sealed interface AppPreference<Pref, T> {
setter = { prefs, _ -> prefs },
)

val CustomizeHome =
AppDestinationPreference<AppPreferences>(
title = R.string.customize_home,
destination = Destination.HomeSettings,
)

val SendCrashReports =
AppSwitchPreference<AppPreferences>(
title = R.string.send_crash_reports,
Expand Down Expand Up @@ -1000,10 +1006,6 @@ val basicPreferences =
preferences =
listOf(
AppPreference.SignInAuto,
AppPreference.HomePageItems,
AppPreference.CombineContinueNext,
AppPreference.RewatchNextUp,
AppPreference.MaxDaysNextUp,
AppPreference.PlayThemeMusic,
AppPreference.RememberSelectedTab,
AppPreference.SubtitleStyle,
Expand Down Expand Up @@ -1034,6 +1036,7 @@ val basicPreferences =
preferences =
listOf(
AppPreference.RequireProfilePin,
AppPreference.CustomizeHome,
AppPreference.UserPinnedNavDrawerItems,
),
),
Expand Down Expand Up @@ -1099,6 +1102,7 @@ val advancedPreferences =
preferences =
listOf(
AppPreference.ShowClock,
AppPreference.CombineContinueNext,
// Temporarily disabled, see https://github.com/damontecres/Wholphin/pull/127#issuecomment-3478058418
// AppPreference.NavDrawerSwitchOnFocus,
AppPreference.ControllerTimeout,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageType
import timber.log.Timber
import javax.inject.Inject
Expand All @@ -47,7 +48,12 @@ class BackdropService

suspend fun submit(item: BaseItem) =
withContext(Dispatchers.IO) {
val imageUrl = imageUrlService.getItemImageUrl(item, ImageType.BACKDROP)!!
val imageUrl =
if (item.type == BaseItemKind.GENRE) {
item.imageUrlOverride
} else {
imageUrlService.getItemImageUrl(item, ImageType.BACKDROP)!!
}
submit(item.id.toString(), imageUrl)
}

Expand Down
Loading
Loading