mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
Numerous UI tweaks esp cards
This commit is contained in:
parent
616ffe8de3
commit
bb517e0857
20 changed files with 324 additions and 58 deletions
|
|
@ -13,7 +13,6 @@ import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -39,6 +38,7 @@ import com.github.damontecres.dolphin.ui.theme.DolphinTheme
|
||||||
import com.github.damontecres.dolphin.util.profile.createDeviceProfile
|
import com.github.damontecres.dolphin.util.profile.createDeviceProfile
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
|
|
@ -59,8 +59,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
@OptIn(ExperimentalTvMaterial3Api::class)
|
@OptIn(ExperimentalTvMaterial3Api::class)
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
Timber.i("MainActivity.onCreate")
|
||||||
setContent {
|
setContent {
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
DolphinTheme(true) {
|
DolphinTheme(true) {
|
||||||
Surface(
|
Surface(
|
||||||
modifier =
|
modifier =
|
||||||
|
|
@ -80,6 +80,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
appPreferences.currentServerId,
|
appPreferences.currentServerId,
|
||||||
appPreferences.currentUserId,
|
appPreferences.currentUserId,
|
||||||
)
|
)
|
||||||
|
Timber.d("MainActivity session restored")
|
||||||
}
|
}
|
||||||
isRestoringSession = false
|
isRestoringSession = false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import androidx.room.PrimaryKey
|
||||||
import androidx.room.Query
|
import androidx.room.Query
|
||||||
import androidx.room.Relation
|
import androidx.room.Relation
|
||||||
import androidx.room.Transaction
|
import androidx.room.Transaction
|
||||||
|
import androidx.room.Update
|
||||||
|
|
||||||
@Entity(tableName = "servers")
|
@Entity(tableName = "servers")
|
||||||
data class JellyfinServer(
|
data class JellyfinServer(
|
||||||
|
|
@ -52,10 +53,13 @@ data class JellyfinServerUsers(
|
||||||
@Dao
|
@Dao
|
||||||
interface JellyfinServerDao {
|
interface JellyfinServerDao {
|
||||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||||
fun addServer(vararg servers: JellyfinServer)
|
fun addServer(server: JellyfinServer): Long
|
||||||
|
|
||||||
|
@Update(onConflict = OnConflictStrategy.ABORT)
|
||||||
|
fun updateServer(server: JellyfinServer): Int
|
||||||
|
|
||||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
fun addUser(vararg users: JellyfinUser)
|
fun addUser(user: JellyfinUser)
|
||||||
|
|
||||||
@Query("DELETE FROM servers WHERE id = :serverId")
|
@Query("DELETE FROM servers WHERE id = :serverId")
|
||||||
fun deleteServer(serverId: String)
|
fun deleteServer(serverId: String)
|
||||||
|
|
|
||||||
|
|
@ -104,3 +104,5 @@ data class BaseItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val BaseItemDto.aspectRatioFloat: Float? get() = width?.let { w -> height?.let { h -> w.toFloat() / h.toFloat() } }
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import coil3.annotation.ExperimentalCoilApi
|
||||||
import coil3.compose.setSingletonImageLoaderFactory
|
import coil3.compose.setSingletonImageLoaderFactory
|
||||||
import coil3.disk.DiskCache
|
import coil3.disk.DiskCache
|
||||||
import coil3.disk.directory
|
import coil3.disk.directory
|
||||||
|
import coil3.memory.MemoryCache
|
||||||
import coil3.network.cachecontrol.CacheControlCacheStrategy
|
import coil3.network.cachecontrol.CacheControlCacheStrategy
|
||||||
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
|
||||||
import coil3.request.crossfade
|
import coil3.request.crossfade
|
||||||
|
|
@ -22,6 +23,7 @@ fun CoilConfig(
|
||||||
setSingletonImageLoaderFactory { ctx ->
|
setSingletonImageLoaderFactory { ctx ->
|
||||||
ImageLoader
|
ImageLoader
|
||||||
.Builder(ctx)
|
.Builder(ctx)
|
||||||
|
.memoryCache(MemoryCache.Builder().maxSizePercent(ctx).build())
|
||||||
.diskCache(
|
.diskCache(
|
||||||
DiskCache
|
DiskCache
|
||||||
.Builder()
|
.Builder()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
package com.github.damontecres.dolphin.ui
|
package com.github.damontecres.dolphin.ui
|
||||||
|
|
||||||
import android.content.res.Configuration.UI_MODE_TYPE_TELEVISION
|
import android.content.res.Configuration.UI_MODE_TYPE_TELEVISION
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import com.github.damontecres.dolphin.R
|
import com.github.damontecres.dolphin.R
|
||||||
import org.jellyfin.sdk.model.api.ItemFields
|
import org.jellyfin.sdk.model.api.ItemFields
|
||||||
|
|
||||||
|
|
@ -30,6 +32,19 @@ val DefaultItemFields =
|
||||||
ItemFields.SORT_NAME,
|
ItemFields.SORT_NAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val DefaultButtonPadding =
|
||||||
|
PaddingValues(
|
||||||
|
start = 12.dp / 2,
|
||||||
|
top = 10.dp / 2,
|
||||||
|
end = 16.dp / 2,
|
||||||
|
bottom = 10.dp / 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
object Cards {
|
||||||
|
val defaultHeight2x3 = 180.dp
|
||||||
|
val playedPercentHeight = 6.dp
|
||||||
|
}
|
||||||
|
|
||||||
@Preview(
|
@Preview(
|
||||||
device = "spec:parent=tv_1080p",
|
device = "spec:parent=tv_1080p",
|
||||||
backgroundColor = 0xFF383535,
|
backgroundColor = 0xFF383535,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -31,6 +32,7 @@ import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.github.damontecres.dolphin.ui.AppColors
|
import com.github.damontecres.dolphin.ui.AppColors
|
||||||
|
import com.github.damontecres.dolphin.ui.Cards
|
||||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -103,7 +105,10 @@ fun BannerCard(
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.background(AppColors.TransparentBlack50),
|
.background(
|
||||||
|
AppColors.TransparentBlack50,
|
||||||
|
shape = RoundedCornerShape(25),
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = cornerText,
|
text = cornerText,
|
||||||
|
|
@ -123,7 +128,7 @@ fun BannerCard(
|
||||||
.background(
|
.background(
|
||||||
MaterialTheme.colorScheme.tertiary,
|
MaterialTheme.colorScheme.tertiary,
|
||||||
).clip(RectangleShape)
|
).clip(RectangleShape)
|
||||||
.height(4.dp)
|
.height(Cards.playedPercentHeight)
|
||||||
.fillMaxWidth((playPercent / 100).toFloat()),
|
.fillMaxWidth((playPercent / 100).toFloat()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,10 @@ fun GridCard(
|
||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(0.dp),
|
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
modifier = Modifier.padding(bottom = spaceBelow).fillMaxWidth(),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(bottom = spaceBelow)
|
||||||
|
.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = item?.name ?: "",
|
text = item?.name ?: "",
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -48,6 +48,7 @@ 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.ui.Cards
|
||||||
import com.github.damontecres.dolphin.ui.FontAwesome
|
import com.github.damontecres.dolphin.ui.FontAwesome
|
||||||
import com.github.damontecres.dolphin.ui.enableMarquee
|
import com.github.damontecres.dolphin.ui.enableMarquee
|
||||||
import com.github.damontecres.dolphin.ui.ifElse
|
import com.github.damontecres.dolphin.ui.ifElse
|
||||||
|
|
@ -240,7 +241,7 @@ fun ItemCardImage(
|
||||||
fontFamily = FontAwesome,
|
fontFamily = FontAwesome,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (watched && watchedPercent?.let { it <= 0 || it >= 100 } == true) {
|
if (watched && (watchedPercent == null || watchedPercent <= 0.0 || watchedPercent >= 100.0)) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.CheckCircle,
|
imageVector = Icons.Default.CheckCircle,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
|
|
@ -248,27 +249,26 @@ fun ItemCardImage(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.size(48.dp)
|
.size(36.dp)
|
||||||
.padding(8.dp),
|
.padding(4.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (unwatchedCount > 0) {
|
if (unwatchedCount > 0) {
|
||||||
Box(
|
Box(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(8.dp)
|
.padding(4.dp)
|
||||||
.align(Alignment.TopEnd),
|
.background(
|
||||||
|
MaterialTheme.colorScheme.border,
|
||||||
|
shape = RoundedCornerShape(25),
|
||||||
|
).align(Alignment.TopEnd),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.background(
|
|
||||||
MaterialTheme.colorScheme.border,
|
|
||||||
shape = CircleShape,
|
|
||||||
),
|
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
|
||||||
text = unwatchedCount.toString(),
|
text = unwatchedCount.toString(),
|
||||||
fontSize = 20.sp,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
// fontSize = 16.sp,
|
||||||
|
modifier = Modifier.padding(4.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -281,7 +281,7 @@ fun ItemCardImage(
|
||||||
.background(
|
.background(
|
||||||
MaterialTheme.colorScheme.tertiary,
|
MaterialTheme.colorScheme.tertiary,
|
||||||
).clip(RectangleShape)
|
).clip(RectangleShape)
|
||||||
.height(4.dp)
|
.height(Cards.playedPercentHeight)
|
||||||
.fillMaxWidth((percent / 100.0).toFloat()),
|
.fillMaxWidth((percent / 100.0).toFloat()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,12 @@ fun ItemRow(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onLongClick: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
) -> Unit = { index, item, modifier, onClick, onLongClick ->
|
) -> Unit = { index, item, mod, onClick, onLongClick ->
|
||||||
ItemCard(
|
ItemCard(
|
||||||
item = item,
|
item = item,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
modifier = modifier,
|
modifier = mod,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
focusPair: FocusPair? = null,
|
focusPair: FocusPair? = null,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
package com.github.damontecres.dolphin.ui.cards
|
||||||
|
|
||||||
|
import androidx.compose.animation.core.animateDpAsState
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.tv.material3.Card
|
||||||
|
import androidx.tv.material3.CardDefaults
|
||||||
|
import androidx.tv.material3.Text
|
||||||
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
|
import com.github.damontecres.dolphin.ui.enableMarquee
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SeasonCard(
|
||||||
|
item: BaseItem?,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: () -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
imageHeight: Dp = Dp.Unspecified,
|
||||||
|
imageWidth: Dp = Dp.Unspecified,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
) {
|
||||||
|
val dto = item?.data
|
||||||
|
val focused by interactionSource.collectIsFocusedAsState()
|
||||||
|
val spaceBetween by animateDpAsState(if (focused) 12.dp else 4.dp)
|
||||||
|
val spaceBelow by animateDpAsState(if (focused) 4.dp else 12.dp)
|
||||||
|
var focusedAfterDelay by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val hideOverlayDelay = 500L
|
||||||
|
if (focused) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
delay(hideOverlayDelay)
|
||||||
|
if (focused) {
|
||||||
|
focusedAfterDelay = true
|
||||||
|
} else {
|
||||||
|
focusedAfterDelay = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
focusedAfterDelay = false
|
||||||
|
}
|
||||||
|
val aspectRatio = dto?.primaryImageAspectRatio?.toFloat() ?: (2f / 3f)
|
||||||
|
val width = imageHeight * aspectRatio
|
||||||
|
val height = imageWidth * (1f / aspectRatio)
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(spaceBetween),
|
||||||
|
modifier = modifier.size(width, height),
|
||||||
|
) {
|
||||||
|
Card(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.size(imageWidth, imageHeight)
|
||||||
|
.aspectRatio(aspectRatio),
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
interactionSource = interactionSource,
|
||||||
|
colors =
|
||||||
|
CardDefaults.colors(
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
ItemCardImage(
|
||||||
|
imageUrl = item?.imageUrl,
|
||||||
|
name = item?.name,
|
||||||
|
showOverlay = false,
|
||||||
|
favorite = dto?.userData?.isFavorite ?: false,
|
||||||
|
watched = dto?.userData?.played ?: false,
|
||||||
|
unwatchedCount = dto?.userData?.unplayedItemCount ?: -1,
|
||||||
|
watchedPercent = dto?.userData?.playedPercentage,
|
||||||
|
useFallbackText = false,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxSize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
verticalArrangement = Arrangement.spacedBy(0.dp),
|
||||||
|
modifier = Modifier.padding(bottom = spaceBelow).fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = dto?.name ?: "",
|
||||||
|
maxLines = 1,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 4.dp)
|
||||||
|
.enableMarquee(focusedAfterDelay),
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = item?.data?.productionYear?.toString() ?: "",
|
||||||
|
maxLines = 1,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 4.dp)
|
||||||
|
.enableMarquee(focusedAfterDelay),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
|
@ -20,7 +21,9 @@ import androidx.tv.material3.ButtonDefaults
|
||||||
import androidx.tv.material3.Icon
|
import androidx.tv.material3.Icon
|
||||||
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.ui.DefaultButtonPadding
|
||||||
import com.github.damontecres.dolphin.ui.FontAwesome
|
import com.github.damontecres.dolphin.ui.FontAwesome
|
||||||
|
import com.github.damontecres.dolphin.ui.ifElse
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -30,6 +33,7 @@ fun PlayButton(
|
||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
onClick: (position: Duration) -> Unit,
|
onClick: (position: Duration) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
mirrorIcon: Boolean = false,
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = { onClick.invoke(resume) },
|
onClick = { onClick.invoke(resume) },
|
||||||
|
|
@ -39,6 +43,7 @@ fun PlayButton(
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
|
modifier = Modifier.ifElse(mirrorIcon, Modifier.graphicsLayer { scaleX = -1f }),
|
||||||
)
|
)
|
||||||
Spacer(Modifier.size(8.dp))
|
Spacer(Modifier.size(8.dp))
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -56,17 +61,19 @@ fun ExpandablePlayButton(
|
||||||
onClick: (position: Duration) -> Unit,
|
onClick: (position: Duration) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
|
mirrorIcon: Boolean = false,
|
||||||
) {
|
) {
|
||||||
val isFocused = interactionSource.collectIsFocusedAsState().value
|
val isFocused = interactionSource.collectIsFocusedAsState().value
|
||||||
Button(
|
Button(
|
||||||
onClick = { onClick.invoke(resume) },
|
onClick = { onClick.invoke(resume) },
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
|
contentPadding = DefaultButtonPadding,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
|
modifier = Modifier.ifElse(mirrorIcon, Modifier.graphicsLayer { scaleX = -1f }),
|
||||||
)
|
)
|
||||||
AnimatedVisibility(isFocused) {
|
AnimatedVisibility(isFocused) {
|
||||||
Spacer(Modifier.size(8.dp))
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
@ -89,14 +96,14 @@ fun ExpandableFaButton(
|
||||||
val isFocused = interactionSource.collectIsFocusedAsState().value
|
val isFocused = interactionSource.collectIsFocusedAsState().value
|
||||||
Button(
|
Button(
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
modifier = modifier.heightIn(min = 44.dp),
|
modifier = modifier.heightIn(min = 40.dp),
|
||||||
contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
|
contentPadding = DefaultButtonPadding,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(iconStringRes),
|
text = stringResource(iconStringRes),
|
||||||
style = MaterialTheme.typography.titleSmall,
|
style = MaterialTheme.typography.titleSmall,
|
||||||
fontSize = 18.sp,
|
fontSize = 16.sp,
|
||||||
fontFamily = FontAwesome,
|
fontFamily = FontAwesome,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,11 @@ import androidx.tv.material3.Icon
|
||||||
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.ui.PreviewTvSpec
|
||||||
|
import com.github.damontecres.dolphin.ui.theme.DolphinTheme
|
||||||
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
import com.github.damontecres.dolphin.ui.tryRequestFocus
|
||||||
import kotlin.time.Duration
|
import kotlin.time.Duration
|
||||||
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PlayButtons(
|
fun PlayButtons(
|
||||||
|
|
@ -70,6 +73,7 @@ fun PlayButtons(
|
||||||
playOnClick,
|
playOnClick,
|
||||||
Modifier
|
Modifier
|
||||||
.onFocusChanged(buttonOnFocusChanged),
|
.onFocusChanged(buttonOnFocusChanged),
|
||||||
|
mirrorIcon = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -154,6 +158,7 @@ fun ExpandablePlayButtons(
|
||||||
Icons.Default.Refresh,
|
Icons.Default.Refresh,
|
||||||
playOnClick,
|
playOnClick,
|
||||||
Modifier.onFocusChanged(buttonOnFocusChanged),
|
Modifier.onFocusChanged(buttonOnFocusChanged),
|
||||||
|
mirrorIcon = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -192,3 +197,19 @@ fun ExpandablePlayButtons(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PreviewTvSpec
|
||||||
|
@Composable
|
||||||
|
private fun ExpandablePlayButtonsPreview() {
|
||||||
|
DolphinTheme(true) {
|
||||||
|
ExpandablePlayButtons(
|
||||||
|
resumePosition = 10.seconds,
|
||||||
|
watched = false,
|
||||||
|
playOnClick = {},
|
||||||
|
watchOnClick = {},
|
||||||
|
moreOnClick = {},
|
||||||
|
buttonOnFocusChanged = {},
|
||||||
|
modifier = Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.github.damontecres.dolphin.ui.detail
|
package com.github.damontecres.dolphin.ui.detail
|
||||||
|
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.focusGroup
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
|
@ -18,6 +19,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
|
import androidx.compose.foundation.lazy.layout.LazyLayoutCacheWindow
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
|
@ -62,6 +64,7 @@ import timber.log.Timber
|
||||||
|
|
||||||
private const val DEBUG = false
|
private const val DEBUG = false
|
||||||
|
|
||||||
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun CardGrid(
|
fun CardGrid(
|
||||||
pager: List<BaseItem?>,
|
pager: List<BaseItem?>,
|
||||||
|
|
@ -78,7 +81,8 @@ fun CardGrid(
|
||||||
val startPosition = initialPosition.coerceIn(0, (pager.size - 1).coerceAtLeast(0))
|
val startPosition = initialPosition.coerceIn(0, (pager.size - 1).coerceAtLeast(0))
|
||||||
val columns = 6
|
val columns = 6
|
||||||
|
|
||||||
val gridState = rememberLazyGridState()
|
val fractionCacheWindow = LazyLayoutCacheWindow(aheadFraction = 1f, behindFraction = 0.5f)
|
||||||
|
val gridState = rememberLazyGridState(cacheWindow = fractionCacheWindow)
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val firstFocus = remember { FocusRequester() }
|
val firstFocus = remember { FocusRequester() }
|
||||||
val zeroFocus = remember { FocusRequester() }
|
val zeroFocus = remember { FocusRequester() }
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
|
@ -48,8 +49,10 @@ 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.data.model.Person
|
import com.github.damontecres.dolphin.data.model.Person
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
|
import com.github.damontecres.dolphin.ui.Cards
|
||||||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
||||||
import com.github.damontecres.dolphin.ui.cards.PersonRow
|
import com.github.damontecres.dolphin.ui.cards.PersonRow
|
||||||
|
import com.github.damontecres.dolphin.ui.cards.SeasonCard
|
||||||
import com.github.damontecres.dolphin.ui.components.ConfirmDialog
|
import com.github.damontecres.dolphin.ui.components.ConfirmDialog
|
||||||
import com.github.damontecres.dolphin.ui.components.DotSeparatedRow
|
import com.github.damontecres.dolphin.ui.components.DotSeparatedRow
|
||||||
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
||||||
|
|
@ -232,6 +235,16 @@ fun SeriesDetailsContent(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
|
cardContent = @Composable { index, item, mod, onClick, onLongClick ->
|
||||||
|
SeasonCard(
|
||||||
|
item = item,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
modifier = mod,
|
||||||
|
imageHeight = Cards.defaultHeight2x3,
|
||||||
|
imageWidth = Dp.Unspecified,
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (people.isNotEmpty()) {
|
if (people.isNotEmpty()) {
|
||||||
|
|
|
||||||
|
|
@ -118,11 +118,11 @@ class SeriesViewModel
|
||||||
ThemeSongVolume.DISABLED,
|
ThemeSongVolume.DISABLED,
|
||||||
-> return
|
-> return
|
||||||
|
|
||||||
ThemeSongVolume.LOWEST -> .1f
|
ThemeSongVolume.LOWEST -> .05f
|
||||||
ThemeSongVolume.LOW -> .25f
|
ThemeSongVolume.LOW -> .1f
|
||||||
ThemeSongVolume.MEDIUM -> .5f
|
ThemeSongVolume.MEDIUM -> .25f
|
||||||
ThemeSongVolume.HIGH -> .75f
|
ThemeSongVolume.HIGH -> .5f
|
||||||
ThemeSongVolume.HIGHEST -> 1f
|
ThemeSongVolume.HIGHEST -> 75f
|
||||||
}
|
}
|
||||||
viewModelScope.launch(ExceptionHandler()) {
|
viewModelScope.launch(ExceptionHandler()) {
|
||||||
val themeSongs = api.libraryApi.getThemeSongs(seriesId).content
|
val themeSongs = api.libraryApi.getThemeSongs(seriesId).content
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import androidx.tv.material3.TabRowDefaults
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
|
import com.github.damontecres.dolphin.data.model.aspectRatioFloat
|
||||||
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
|
||||||
import com.github.damontecres.dolphin.ui.cards.BannerCard
|
import com.github.damontecres.dolphin.ui.cards.BannerCard
|
||||||
import com.github.damontecres.dolphin.ui.ifElse
|
import com.github.damontecres.dolphin.ui.ifElse
|
||||||
|
|
@ -225,12 +226,17 @@ fun SeriesOverviewContent(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
BannerCard(
|
BannerCard(
|
||||||
name = episode?.name,
|
name = episode?.name,
|
||||||
imageUrl = episode?.imageUrl,
|
imageUrl = episode?.imageUrl,
|
||||||
aspectRatio =
|
aspectRatio =
|
||||||
episode?.data?.primaryImageAspectRatio?.toFloat()
|
episode
|
||||||
?: (16f / 9),
|
?.data
|
||||||
|
?.primaryImageAspectRatio
|
||||||
|
?.toFloat()
|
||||||
|
?.coerceAtLeast(episode.data.aspectRatioFloat ?: (4f / 3f))
|
||||||
|
?: (16f / 9), // TODO some episode images don't match the file's aspect ratio
|
||||||
cornerText = "E${episode?.data?.indexNumber}",
|
cornerText = "E${episode?.data?.indexNumber}",
|
||||||
played = episode?.data?.userData?.played ?: false,
|
played = episode?.data?.userData?.played ?: false,
|
||||||
playPercent = episode?.data?.userData?.playedPercentage ?: 0.0,
|
playPercent = episode?.data?.userData?.playedPercentage ?: 0.0,
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ import androidx.tv.material3.Text
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
|
import com.github.damontecres.dolphin.ui.Cards
|
||||||
import com.github.damontecres.dolphin.ui.cards.BannerCard
|
import com.github.damontecres.dolphin.ui.cards.BannerCard
|
||||||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
||||||
import com.github.damontecres.dolphin.ui.components.DotSeparatedRow
|
import com.github.damontecres.dolphin.ui.components.DotSeparatedRow
|
||||||
|
|
@ -194,7 +195,7 @@ fun HomePageContent(
|
||||||
Modifier.focusRequester(positionFocusRequester),
|
Modifier.focusRequester(positionFocusRequester),
|
||||||
),
|
),
|
||||||
interactionSource = null,
|
interactionSource = null,
|
||||||
cardHeight = 200.dp,
|
cardHeight = Cards.defaultHeight2x3,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
|
@ -13,6 +13,7 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
|
@ -25,9 +26,11 @@ 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.preferences.UserPreferences
|
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||||
|
import com.github.damontecres.dolphin.ui.Cards
|
||||||
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
||||||
import com.github.damontecres.dolphin.ui.cards.EpisodeCard
|
import com.github.damontecres.dolphin.ui.cards.EpisodeCard
|
||||||
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
import com.github.damontecres.dolphin.ui.cards.ItemRow
|
||||||
|
import com.github.damontecres.dolphin.ui.cards.SeasonCard
|
||||||
import com.github.damontecres.dolphin.ui.components.EditTextBox
|
import com.github.damontecres.dolphin.ui.components.EditTextBox
|
||||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||||
|
|
@ -125,7 +128,7 @@ fun SearchPage(
|
||||||
val series by viewModel.series.observeAsState(listOf())
|
val series by viewModel.series.observeAsState(listOf())
|
||||||
val episodes by viewModel.episodes.observeAsState(listOf())
|
val episodes by viewModel.episodes.observeAsState(listOf())
|
||||||
|
|
||||||
var query by remember { mutableStateOf("") }
|
var query by rememberSaveable { mutableStateOf("") }
|
||||||
val searchFocusRequester = remember { FocusRequester() }
|
val searchFocusRequester = remember { FocusRequester() }
|
||||||
val resultsFocusRequester = remember { FocusRequester() }
|
val resultsFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
|
@ -163,22 +166,47 @@ fun SearchPage(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
itemsIndexed(listOf(movies, series)) { index, items ->
|
if (movies.isNotEmpty()) {
|
||||||
if (items.isNotEmpty()) {
|
item {
|
||||||
ItemRow(
|
ItemRow(
|
||||||
title =
|
title = "Movies",
|
||||||
when (index) {
|
items = movies,
|
||||||
0 -> "Movies"
|
|
||||||
1 -> "Series"
|
|
||||||
2 -> "Episodes"
|
|
||||||
else -> ""
|
|
||||||
},
|
|
||||||
items = items,
|
|
||||||
onClickItem = {
|
onClickItem = {
|
||||||
viewModel.navigationManager.navigateTo(it.destination())
|
viewModel.navigationManager.navigateTo(it.destination())
|
||||||
},
|
},
|
||||||
onLongClickItem = {},
|
onLongClickItem = {},
|
||||||
modifier = Modifier.focusRequester(resultsFocusRequester),
|
modifier = Modifier,
|
||||||
|
cardContent = @Composable { index, item, mod, onClick, onLongClick ->
|
||||||
|
SeasonCard(
|
||||||
|
item = item,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
imageHeight = Cards.defaultHeight2x3,
|
||||||
|
modifier = mod,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (series.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
ItemRow(
|
||||||
|
title = "Series",
|
||||||
|
items = series,
|
||||||
|
onClickItem = {
|
||||||
|
viewModel.navigationManager.navigateTo(it.destination())
|
||||||
|
},
|
||||||
|
onLongClickItem = {},
|
||||||
|
modifier = Modifier,
|
||||||
|
cardContent = @Composable { index, item, mod, onClick, onLongClick ->
|
||||||
|
SeasonCard(
|
||||||
|
item = item,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
imageHeight = Cards.defaultHeight2x3,
|
||||||
|
modifier = mod,
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -197,8 +225,8 @@ fun SearchPage(
|
||||||
item = item,
|
item = item,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
imageHeight = 160.dp,
|
imageHeight = 140.dp,
|
||||||
modifier = mod,
|
modifier = mod.padding(horizontal = 8.dp),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
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.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -17,9 +18,12 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
import androidx.tv.material3.Button
|
import androidx.tv.material3.Button
|
||||||
import androidx.tv.material3.MaterialTheme
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
|
@ -27,7 +31,9 @@ import androidx.tv.material3.Text
|
||||||
import androidx.tv.material3.surfaceColorAtElevation
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import com.github.damontecres.dolphin.ui.components.BasicDialog
|
import com.github.damontecres.dolphin.ui.components.BasicDialog
|
||||||
import com.github.damontecres.dolphin.ui.components.EditTextBox
|
import com.github.damontecres.dolphin.ui.components.EditTextBox
|
||||||
|
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.tryRequestFocus
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchServerContent(
|
fun SwitchServerContent(
|
||||||
|
|
@ -95,11 +101,17 @@ fun SwitchServerContent(
|
||||||
}
|
}
|
||||||
BasicDialog(
|
BasicDialog(
|
||||||
onDismissRequest = { showAddServer = false },
|
onDismissRequest = { showAddServer = false },
|
||||||
|
properties = DialogProperties(usePlatformDefaultWidth = false),
|
||||||
) {
|
) {
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = Modifier.padding(16.dp),
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.padding(16.dp)
|
||||||
|
.fillMaxWidth(.4f),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Enter Server URL",
|
text = "Enter Server URL",
|
||||||
|
|
@ -117,10 +129,14 @@ fun SwitchServerContent(
|
||||||
KeyboardActions(
|
KeyboardActions(
|
||||||
onDone = { submit.invoke() },
|
onDone = { submit.invoke() },
|
||||||
),
|
),
|
||||||
modifier = Modifier,
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
Button(
|
Button(
|
||||||
onClick = { submit.invoke() },
|
onClick = { submit.invoke() },
|
||||||
|
enabled = url.isNotNullOrBlank(),
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Text(text = "Submit")
|
Text(text = "Submit")
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.github.damontecres.dolphin.ui.setup
|
package com.github.damontecres.dolphin.ui.setup
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.focusGroup
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|
@ -19,6 +20,8 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
@ -31,6 +34,7 @@ import com.github.damontecres.dolphin.ui.components.BasicDialog
|
||||||
import com.github.damontecres.dolphin.ui.components.EditTextBox
|
import com.github.damontecres.dolphin.ui.components.EditTextBox
|
||||||
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
|
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.tryRequestFocus
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchUserContent(
|
fun SwitchUserContent(
|
||||||
|
|
@ -152,13 +156,20 @@ fun SwitchUserContent(
|
||||||
viewModel.navigationManager.goToHome()
|
viewModel.navigationManager.goToHome()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
|
||||||
|
Text(
|
||||||
|
text = "Enter username/password to login to ${server.name ?: server.url}",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
)
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier,
|
modifier = Modifier.focusGroup(),
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Username",
|
text = "Username",
|
||||||
modifier = Modifier,
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
)
|
)
|
||||||
EditTextBox(
|
EditTextBox(
|
||||||
value = username,
|
value = username,
|
||||||
|
|
@ -169,7 +180,7 @@ fun SwitchUserContent(
|
||||||
autoCorrectEnabled = false,
|
autoCorrectEnabled = false,
|
||||||
keyboardType = KeyboardType.Text,
|
keyboardType = KeyboardType.Text,
|
||||||
),
|
),
|
||||||
modifier = Modifier,
|
modifier = Modifier.focusRequester(focusRequester),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
|
|
@ -178,7 +189,7 @@ fun SwitchUserContent(
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "Password",
|
text = "Password",
|
||||||
modifier = Modifier,
|
modifier = Modifier.padding(end = 8.dp),
|
||||||
)
|
)
|
||||||
EditTextBox(
|
EditTextBox(
|
||||||
value = password,
|
value = password,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue