This commit is contained in:
Damontecres 2025-09-23 20:03:25 -04:00
parent bd75539cb6
commit 913334b43d
No known key found for this signature in database
24 changed files with 41 additions and 117 deletions

View file

@ -24,6 +24,7 @@ import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Surface import androidx.tv.material3.Surface
import com.github.damontecres.dolphin.data.ServerRepository import com.github.damontecres.dolphin.data.ServerRepository
import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.CoilConfig
import com.github.damontecres.dolphin.ui.ServerLoginPage import com.github.damontecres.dolphin.ui.ServerLoginPage
import com.github.damontecres.dolphin.ui.nav.ApplicationContent import com.github.damontecres.dolphin.ui.nav.ApplicationContent
import com.github.damontecres.dolphin.ui.theme.DolphinTheme import com.github.damontecres.dolphin.ui.theme.DolphinTheme

View file

@ -8,6 +8,6 @@ import androidx.room.RoomDatabase
version = 2, version = 2,
exportSchema = false, exportSchema = false,
) )
abstract class DolphinDatabase : RoomDatabase() { abstract class AppDatabase : RoomDatabase() {
abstract fun serverDao(): JellyfinServerDao abstract fun serverDao(): JellyfinServerDao
} }

View file

@ -19,7 +19,7 @@ import javax.inject.Singleton
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
object DolphinModule { object AppModule {
@Provides @Provides
@Singleton @Singleton
fun okHttpClient() = fun okHttpClient() =

View file

@ -6,7 +6,7 @@ import androidx.datastore.core.DataStoreFactory
import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
import androidx.datastore.dataStoreFile import androidx.datastore.dataStoreFile
import androidx.room.Room import androidx.room.Room
import com.github.damontecres.dolphin.data.DolphinDatabase import com.github.damontecres.dolphin.data.AppDatabase
import com.github.damontecres.dolphin.data.JellyfinServerDao import com.github.damontecres.dolphin.data.JellyfinServerDao
import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.preferences.UserPreferencesSerializer import com.github.damontecres.dolphin.preferences.UserPreferencesSerializer
@ -24,18 +24,18 @@ object DatabaseModule {
@Singleton @Singleton
fun database( fun database(
@ApplicationContext context: Context, @ApplicationContext context: Context,
): DolphinDatabase = ): AppDatabase =
Room Room
.databaseBuilder( .databaseBuilder(
context, context,
DolphinDatabase::class.java, AppDatabase::class.java,
"dolphin", "dolphin",
).fallbackToDestructiveMigration(false) ).fallbackToDestructiveMigration(false)
.build() .build()
@Provides @Provides
@Singleton @Singleton
fun serverDao(db: DolphinDatabase): JellyfinServerDao = db.serverDao() fun serverDao(db: AppDatabase): JellyfinServerDao = db.serverDao()
@Provides @Provides
@Singleton @Singleton

View file

@ -1,4 +1,4 @@
package com.github.damontecres.dolphin package com.github.damontecres.dolphin.ui
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import coil3.ImageLoader import coil3.ImageLoader

View file

@ -1,4 +1,4 @@
package com.github.damontecres.dolphin package com.github.damontecres.dolphin.ui
import android.view.KeyEvent import android.view.KeyEvent
import androidx.compose.foundation.MarqueeAnimationMode import androidx.compose.foundation.MarqueeAnimationMode

View file

@ -1,31 +0,0 @@
package com.github.damontecres.dolphin.ui.cards
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.tv.material3.Text
import com.github.damontecres.dolphin.data.model.Library
import com.github.damontecres.dolphin.data.model.Video
import org.jellyfin.sdk.model.api.BaseItemDto
@Composable
fun DolphinCard(
item: Any?,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
when (item) {
is Video ->
VideoCard(
item = item,
onClick = onClick,
modifier = modifier,
)
is Library -> TODO()
is BaseItemDto -> {
Text(item.id.toString())
}
null -> NullCard(modifier = modifier)
}
}

View file

@ -41,8 +41,8 @@ import androidx.tv.material3.Text
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import com.github.damontecres.dolphin.R import com.github.damontecres.dolphin.R
import com.github.damontecres.dolphin.data.model.BaseItem import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.enableMarquee
import com.github.damontecres.dolphin.ui.FontAwesome import com.github.damontecres.dolphin.ui.FontAwesome
import com.github.damontecres.dolphin.ui.enableMarquee
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind

View file

@ -1,48 +0,0 @@
package com.github.damontecres.dolphin.ui.cards
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.tv.material3.Card
import androidx.tv.material3.Text
import coil3.compose.AsyncImage
import com.github.damontecres.dolphin.data.model.Video
@Composable
fun VideoCard(
item: Video,
onClick: () -> Unit,
modifier: Modifier = Modifier,
cardWidth: Dp = 150.dp,
cardHeight: Dp = 200.dp,
) {
Card(
modifier = modifier,
onClick = onClick,
) {
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.width(cardWidth),
) {
AsyncImage(
model = item.imageUrl,
contentDescription = item.name,
contentScale = ContentScale.Fit,
modifier =
Modifier
.fillMaxWidth()
.height(cardHeight),
)
Text(
text = item.name ?: "",
)
}
}
}

View file

@ -42,16 +42,16 @@ import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.dolphin.R import com.github.damontecres.dolphin.R
import com.github.damontecres.dolphin.data.model.BaseItem import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.ifElse
import com.github.damontecres.dolphin.tryRequestFocus
import com.github.damontecres.dolphin.ui.AppColors import com.github.damontecres.dolphin.ui.AppColors
import com.github.damontecres.dolphin.ui.FontAwesome import com.github.damontecres.dolphin.ui.FontAwesome
import com.github.damontecres.dolphin.ui.cards.ItemCard import com.github.damontecres.dolphin.ui.cards.ItemCard
import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.nav.NavigationManager import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.playback.isBackwardButton import com.github.damontecres.dolphin.ui.playback.isBackwardButton
import com.github.damontecres.dolphin.ui.playback.isForwardButton import com.github.damontecres.dolphin.ui.playback.isForwardButton
import com.github.damontecres.dolphin.ui.playback.isPlayKeyUp import com.github.damontecres.dolphin.ui.playback.isPlayKeyUp
import com.github.damontecres.dolphin.util.DolphinPager import com.github.damontecres.dolphin.ui.tryRequestFocus
import com.github.damontecres.dolphin.util.ItemPager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import kotlin.math.max import kotlin.math.max
@ -60,7 +60,7 @@ private const val DEBUG = false
@Composable @Composable
fun CardGrid( fun CardGrid(
pager: DolphinPager, pager: ItemPager,
itemOnClick: (BaseItem) -> Unit, itemOnClick: (BaseItem) -> Unit,
longClicker: (BaseItem) -> Unit, longClicker: (BaseItem) -> Unit,
letterPosition: suspend (Char) -> Int, letterPosition: suspend (Char) -> Int,

View file

@ -10,13 +10,13 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.dolphin.OneTimeLaunchedEffect
import com.github.damontecres.dolphin.data.model.BaseItem import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.data.model.Library import com.github.damontecres.dolphin.data.model.Library
import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.dolphin.ui.nav.Destination import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.util.DolphinPager import com.github.damontecres.dolphin.util.ItemPager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -37,7 +37,7 @@ class CollectionFolderViewModel
constructor( constructor(
api: ApiClient, api: ApiClient,
) : ItemViewModel<Library>(api) { ) : ItemViewModel<Library>(api) {
val pager = MutableLiveData<DolphinPager?>() val pager = MutableLiveData<ItemPager?>()
override fun init( override fun init(
itemId: UUID, itemId: UUID,
@ -63,7 +63,7 @@ class CollectionFolderViewModel
sortOrder = listOf(SortOrder.ASCENDING), sortOrder = listOf(SortOrder.ASCENDING),
fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO), fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO),
) )
val newPager = DolphinPager(api, request, viewModelScope) val newPager = ItemPager(api, request, viewModelScope)
newPager.init() newPager.init()
pager.value = newPager pager.value = newPager
} }
@ -124,7 +124,7 @@ fun TVShowCollectionDetails(
navigationManager: NavigationManager, navigationManager: NavigationManager,
library: Library, library: Library,
item: BaseItem, item: BaseItem,
pager: DolphinPager, pager: ItemPager,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val gridFocusRequester = remember { FocusRequester() } val gridFocusRequester = remember { FocusRequester() }

View file

@ -17,7 +17,7 @@ import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.cards.ItemRow import com.github.damontecres.dolphin.ui.cards.ItemRow
import com.github.damontecres.dolphin.ui.nav.Destination import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.util.DolphinPager import com.github.damontecres.dolphin.util.ItemPager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -54,7 +54,8 @@ class SeasonViewModel
sortOrder = listOf(SortOrder.ASCENDING), sortOrder = listOf(SortOrder.ASCENDING),
fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, ItemFields.CHILD_COUNT), fields = listOf(ItemFields.PRIMARY_IMAGE_ASPECT_RATIO, ItemFields.CHILD_COUNT),
) )
val pager = DolphinPager(api, request, viewModelScope, itemCount = item.data.childCount) val pager =
ItemPager(api, request, viewModelScope, itemCount = item.data.childCount)
pager.init() pager.init()
episodes.value = pager episodes.value = pager
} }

View file

@ -17,7 +17,7 @@ import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.cards.ItemRow import com.github.damontecres.dolphin.ui.cards.ItemRow
import com.github.damontecres.dolphin.ui.nav.Destination import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.util.DolphinPager import com.github.damontecres.dolphin.util.ItemPager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -59,7 +59,8 @@ class SeriesViewModel
ItemFields.SEASON_USER_DATA, ItemFields.SEASON_USER_DATA,
), ),
) )
val pager = DolphinPager(api, request, viewModelScope, itemCount = item.data.childCount) val pager =
ItemPager(api, request, viewModelScope, itemCount = item.data.childCount)
pager.init() pager.init()
seasons.value = pager seasons.value = pager
} }

View file

@ -17,10 +17,10 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.damontecres.dolphin.data.model.BaseItem import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.isNotNullOrBlank
import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.DefaultItemFields import com.github.damontecres.dolphin.ui.DefaultItemFields
import com.github.damontecres.dolphin.ui.cards.ItemRow import com.github.damontecres.dolphin.ui.cards.ItemRow
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
import com.github.damontecres.dolphin.ui.nav.Destination import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager import com.github.damontecres.dolphin.ui.nav.NavigationManager
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel

View file

@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.consumeAsFlow
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
class ControllerViewState internal constructor( class ControllerViewState internal constructor(
@IntRange(from = 0) @param:IntRange(from = 0)
private val hideMilliseconds: Int, private val hideMilliseconds: Int,
val controlsEnabled: Boolean, val controlsEnabled: Boolean,
) { ) {
@ -31,7 +31,6 @@ class ControllerViewState internal constructor(
} }
fun pulseControls(milliseconds: Int = hideMilliseconds) { fun pulseControls(milliseconds: Int = hideMilliseconds) {
// Log.i("PlaybackPageContent", "pulseControls=$milliseconds")
channel.trySend(milliseconds) channel.trySend(milliseconds)
} }
@ -41,7 +40,6 @@ class ControllerViewState internal constructor(
.consumeAsFlow() .consumeAsFlow()
.debounce { it.toLong() } .debounce { it.toLong() }
.collect { .collect {
// Log.i("PlaybackPageContent", "collect")
_controlsVisible = false _controlsVisible = false
} }
} }

View file

@ -45,9 +45,9 @@ import androidx.media3.ui.compose.state.rememberPresentationState
import androidx.media3.ui.compose.state.rememberPreviousButtonState import androidx.media3.ui.compose.state.rememberPreviousButtonState
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.tryRequestFocus
import com.github.damontecres.dolphin.ui.nav.Destination import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.tryRequestFocus
import com.github.damontecres.stashapp.ui.components.playback.SkipIndicator import com.github.damontecres.stashapp.ui.components.playback.SkipIndicator
import com.github.damontecres.stashapp.ui.components.playback.rememberSeekBarState import com.github.damontecres.stashapp.ui.components.playback.rememberSeekBarState
import org.jellyfin.sdk.model.api.DeviceProfile import org.jellyfin.sdk.model.api.DeviceProfile

View file

@ -57,8 +57,9 @@ import androidx.tv.material3.ListItem
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.dolphin.R import com.github.damontecres.dolphin.R
import com.github.damontecres.dolphin.tryRequestFocus
import com.github.damontecres.dolphin.ui.AppColors import com.github.damontecres.dolphin.ui.AppColors
import com.github.damontecres.dolphin.ui.tryRequestFocus
import com.github.damontecres.dolphin.util.TrackSupport
import com.github.damontecres.stashapp.ui.components.playback.SeekBarImpl import com.github.damontecres.stashapp.ui.components.playback.SeekBarImpl
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive

View file

@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.media3.common.Player import androidx.media3.common.Player
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.dolphin.util.TrackSupport
@Composable @Composable
fun PlaybackOverlay( fun PlaybackOverlay(

View file

@ -9,6 +9,7 @@ import androidx.media3.common.C
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import com.github.damontecres.dolphin.ui.nav.Destination import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.util.TrackActivityPlaybackListener
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View file

@ -41,7 +41,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.StrokeCap import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import com.github.damontecres.dolphin.handleDPadKeyEvents import com.github.damontecres.dolphin.ui.handleDPadKeyEvents
import com.github.damontecres.dolphin.ui.playback.ControllerViewState import com.github.damontecres.dolphin.ui.playback.ControllerViewState
@Composable @Composable

View file

@ -22,7 +22,7 @@ import androidx.compose.ui.unit.sp
import androidx.tv.material3.MaterialTheme import androidx.tv.material3.MaterialTheme
import androidx.tv.material3.Text import androidx.tv.material3.Text
import com.github.damontecres.dolphin.R import com.github.damontecres.dolphin.R
import com.github.damontecres.dolphin.ifElse import com.github.damontecres.dolphin.ui.ifElse
import kotlin.math.abs import kotlin.math.abs
@Composable @Composable

View file

@ -18,7 +18,7 @@ import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.model.api.request.GetItemsRequest import org.jellyfin.sdk.model.api.request.GetItemsRequest
import timber.log.Timber import timber.log.Timber
class DolphinPager( class ItemPager(
val api: ApiClient, val api: ApiClient,
val request: GetItemsRequest, val request: GetItemsRequest,
private val scope: CoroutineScope, private val scope: CoroutineScope,
@ -101,8 +101,7 @@ class DolphinPager(
} }
companion object { companion object {
private const val TAG = "DolphinPager" private const val DEBUG = false
private const val DEBUG = true
} }
class ItemList<T>( class ItemList<T>(

View file

@ -1,4 +1,4 @@
package com.github.damontecres.dolphin.ui.playback package com.github.damontecres.dolphin.util
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.Player import androidx.media3.common.Player
@ -64,7 +64,7 @@ class TrackActivityPlaybackListener(
} }
timestamp = now timestamp = now
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.w(ex, "Exception during track activity timer") Timber.Forest.w(ex, "Exception during track activity timer")
} }
} }
} }
@ -98,7 +98,7 @@ class TrackActivityPlaybackListener(
override fun onPlaybackStateChanged(playbackState: Int) { override fun onPlaybackStateChanged(playbackState: Int) {
if (playbackState == Player.STATE_ENDED) { if (playbackState == Player.STATE_ENDED) {
Timber.v("onPlaybackStateChanged STATE_ENDED") Timber.Forest.v("onPlaybackStateChanged STATE_ENDED")
// TODO mark as watched // TODO mark as watched
} }
} }
@ -108,7 +108,7 @@ class TrackActivityPlaybackListener(
val totalDuration = totalPlayDurationMilliseconds.get() val totalDuration = totalPlayDurationMilliseconds.get()
val calcPosition = val calcPosition =
(if (position >= 0) position else player.currentPosition).milliseconds (if (position >= 0) position else player.currentPosition).milliseconds
Timber.v("saveActivity: itemId=$itemId, pos=$calcPosition") Timber.Forest.v("saveActivity: itemId=$itemId, pos=$calcPosition")
// TODO parameters // TODO parameters
api.playStateApi.reportPlaybackProgress( api.playStateApi.reportPlaybackProgress(
PlaybackProgressInfo( PlaybackProgressInfo(

View file

@ -1,4 +1,4 @@
package com.github.damontecres.dolphin.ui.playback package com.github.damontecres.dolphin.util
import android.content.Context import android.content.Context
import androidx.annotation.OptIn import androidx.annotation.OptIn