mirror of
https://github.com/anultravioletaurora/Jellify.git
synced 2025-12-20 13:00:10 -06:00
Enhance ItemRow component with layout adjustments and runtime visibility control (#692)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
|
import { BaseItemDto, BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models'
|
||||||
import { XStack, YStack } from 'tamagui'
|
import { XStack, YStack, getToken } from 'tamagui'
|
||||||
import { Text } from '../helpers/text'
|
import { Text } from '../helpers/text'
|
||||||
import Icon from './icon'
|
import Icon from './icon'
|
||||||
import { QueuingType } from '../../../enums/queuing-type'
|
import { QueuingType } from '../../../enums/queuing-type'
|
||||||
@@ -14,7 +14,8 @@ import { useNetworkStatus } from '../../../stores/network'
|
|||||||
import useStreamingDeviceProfile from '../../../stores/device-profile'
|
import useStreamingDeviceProfile from '../../../stores/device-profile'
|
||||||
import useItemContext from '../../../hooks/use-item-context'
|
import useItemContext from '../../../hooks/use-item-context'
|
||||||
import { RouteProp, useRoute } from '@react-navigation/native'
|
import { RouteProp, useRoute } from '@react-navigation/native'
|
||||||
import { useCallback } from 'react'
|
import React, { useCallback, useState } from 'react'
|
||||||
|
import { LayoutChangeEvent } from 'react-native'
|
||||||
import Animated, { useAnimatedStyle } from 'react-native-reanimated'
|
import Animated, { useAnimatedStyle } from 'react-native-reanimated'
|
||||||
import { useSwipeableRowContext } from './swipeable-row-context'
|
import { useSwipeableRowContext } from './swipeable-row-context'
|
||||||
import SwipeableRow from './SwipeableRow'
|
import SwipeableRow from './SwipeableRow'
|
||||||
@@ -23,6 +24,7 @@ import { buildSwipeConfig } from '../helpers/swipe-actions'
|
|||||||
import { useIsFavorite } from '../../../api/queries/user-data'
|
import { useIsFavorite } from '../../../api/queries/user-data'
|
||||||
import { useAddFavorite, useRemoveFavorite } from '../../../api/mutations/favorite'
|
import { useAddFavorite, useRemoveFavorite } from '../../../api/mutations/favorite'
|
||||||
import { useApi } from '../../../stores'
|
import { useApi } from '../../../stores'
|
||||||
|
import { useHideRunTimesSetting } from '../../../stores/settings/app'
|
||||||
|
|
||||||
interface ItemRowProps {
|
interface ItemRowProps {
|
||||||
item: BaseItemDto
|
item: BaseItemDto
|
||||||
@@ -50,6 +52,8 @@ export default function ItemRow({
|
|||||||
onPress,
|
onPress,
|
||||||
queueName,
|
queueName,
|
||||||
}: ItemRowProps): React.JSX.Element {
|
}: ItemRowProps): React.JSX.Element {
|
||||||
|
const [artworkAreaWidth, setArtworkAreaWidth] = useState(0)
|
||||||
|
|
||||||
const api = useApi()
|
const api = useApi()
|
||||||
|
|
||||||
const [networkStatus] = useNetworkStatus()
|
const [networkStatus] = useNetworkStatus()
|
||||||
@@ -60,6 +64,7 @@ export default function ItemRow({
|
|||||||
const addToQueue = useAddToQueue()
|
const addToQueue = useAddToQueue()
|
||||||
const { mutate: addFavorite } = useAddFavorite()
|
const { mutate: addFavorite } = useAddFavorite()
|
||||||
const { mutate: removeFavorite } = useRemoveFavorite()
|
const { mutate: removeFavorite } = useRemoveFavorite()
|
||||||
|
const [hideRunTimes] = useHideRunTimesSetting()
|
||||||
|
|
||||||
const warmContext = useItemContext()
|
const warmContext = useItemContext()
|
||||||
const { data: isFavorite } = useIsFavorite(item)
|
const { data: isFavorite } = useIsFavorite(item)
|
||||||
@@ -113,7 +118,7 @@ export default function ItemRow({
|
|||||||
}
|
}
|
||||||
}, [loadNewQueue, item, navigation])
|
}, [loadNewQueue, item, navigation])
|
||||||
|
|
||||||
const renderRunTime = item.Type === BaseItemKind.Audio
|
const renderRunTime = item.Type === BaseItemKind.Audio && !hideRunTimes
|
||||||
|
|
||||||
const isAudio = item.Type === 'Audio'
|
const isAudio = item.Type === 'Audio'
|
||||||
|
|
||||||
@@ -171,9 +176,18 @@ export default function ItemRow({
|
|||||||
pressStyle={{ opacity: 0.5 }}
|
pressStyle={{ opacity: 0.5 }}
|
||||||
paddingVertical={'$2'}
|
paddingVertical={'$2'}
|
||||||
paddingRight={'$2'}
|
paddingRight={'$2'}
|
||||||
|
paddingLeft={'$1'}
|
||||||
|
backgroundColor={'$background'}
|
||||||
|
borderRadius={'$2'}
|
||||||
>
|
>
|
||||||
<HideableArtwork item={item} circular={circular} />
|
<HideableArtwork
|
||||||
<StickyText item={item} />
|
item={item}
|
||||||
|
circular={circular}
|
||||||
|
onLayout={(e) => setArtworkAreaWidth(e.nativeEvent.layout.width)}
|
||||||
|
/>
|
||||||
|
<SlidingTextArea leftGapWidth={artworkAreaWidth}>
|
||||||
|
<ItemRowDetails item={item} />
|
||||||
|
</SlidingTextArea>
|
||||||
|
|
||||||
<XStack justifyContent='flex-end' alignItems='center' flex={2}>
|
<XStack justifyContent='flex-end' alignItems='center' flex={2}>
|
||||||
{renderRunTime ? (
|
{renderRunTime ? (
|
||||||
@@ -241,9 +255,11 @@ function ItemRowDetails({ item }: { item: BaseItemDto }): React.JSX.Element {
|
|||||||
function HideableArtwork({
|
function HideableArtwork({
|
||||||
item,
|
item,
|
||||||
circular,
|
circular,
|
||||||
|
onLayout,
|
||||||
}: {
|
}: {
|
||||||
item: BaseItemDto
|
item: BaseItemDto
|
||||||
circular?: boolean
|
circular?: boolean
|
||||||
|
onLayout?: (event: LayoutChangeEvent) => void
|
||||||
}): React.JSX.Element {
|
}): React.JSX.Element {
|
||||||
const { tx } = useSwipeableRowContext()
|
const { tx } = useSwipeableRowContext()
|
||||||
// Hide artwork as soon as swiping starts (any non-zero tx)
|
// Hide artwork as soon as swiping starts (any non-zero tx)
|
||||||
@@ -251,7 +267,7 @@ function HideableArtwork({
|
|||||||
opacity: tx.value === 0 ? 1 : 0,
|
opacity: tx.value === 0 ? 1 : 0,
|
||||||
}))
|
}))
|
||||||
return (
|
return (
|
||||||
<Animated.View style={style}>
|
<Animated.View style={style} onLayout={onLayout}>
|
||||||
<YStack marginHorizontal={'$3'} justifyContent='center'>
|
<YStack marginHorizontal={'$3'} justifyContent='center'>
|
||||||
<ItemImage
|
<ItemImage
|
||||||
item={item}
|
item={item}
|
||||||
@@ -264,12 +280,34 @@ function HideableArtwork({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text/details remain visible. No counter-translation needed now that underlays are width-bound.
|
function SlidingTextArea({
|
||||||
function StickyText({ item }: { item: BaseItemDto }): React.JSX.Element {
|
leftGapWidth,
|
||||||
const style = useAnimatedStyle(() => ({}))
|
children,
|
||||||
|
}: {
|
||||||
|
leftGapWidth: number
|
||||||
|
children: React.ReactNode
|
||||||
|
}): React.JSX.Element {
|
||||||
|
const { tx, rightWidth } = useSwipeableRowContext()
|
||||||
|
const tokenValue = getToken('$2', 'space')
|
||||||
|
const spacingValue = typeof tokenValue === 'number' ? tokenValue : parseFloat(`${tokenValue}`)
|
||||||
|
const quickActionBuffer = Number.isFinite(spacingValue) ? spacingValue : 8
|
||||||
|
const style = useAnimatedStyle(() => {
|
||||||
|
const t = tx.value
|
||||||
|
let offset = 0
|
||||||
|
if (t > 0 && leftGapWidth > 0) {
|
||||||
|
offset = -Math.min(t, leftGapWidth)
|
||||||
|
} else if (t < 0) {
|
||||||
|
const rightSpace = Math.max(0, rightWidth)
|
||||||
|
const compensate = Math.min(-t, rightSpace)
|
||||||
|
const progress = rightSpace > 0 ? compensate / rightSpace : 1
|
||||||
|
offset = compensate * 0.7 + quickActionBuffer * progress
|
||||||
|
}
|
||||||
|
return { transform: [{ translateX: offset }] }
|
||||||
|
})
|
||||||
|
const paddingRightValue = Number.isFinite(spacingValue) ? spacingValue : 8
|
||||||
return (
|
return (
|
||||||
<Animated.View style={[style, { flex: 5 }]}>
|
<Animated.View style={[{ flex: 5, paddingRight: paddingRightValue }, style]}>
|
||||||
<ItemRowDetails item={item} />
|
{children}
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user