170 lines
5.4 KiB
JavaScript
170 lines
5.4 KiB
JavaScript
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
|
import { View, StyleSheet } from 'react-native'
|
|
import { responsiveHeight } from 'react-native-responsive-dimensions'
|
|
import { SheetManager } from 'react-native-actions-sheet'
|
|
import { useUser } from '../../../providers/UserDataProvider'
|
|
import { Routes } from '../../../navigation'
|
|
import { navigate } from '../../../navigation/NavigationService'
|
|
import { openComments } from '../../../components/bottomsheets/CommentsBottomSheet'
|
|
import { createPlaybackSharePayload, openShareSheet } from '../../../utils/shareSheet'
|
|
import { getProjectLikes, LIKE_TARGET, toggleProjectLike } from '../../../utils/likes'
|
|
import { ensureAuthenticated } from '../../../utils/authRedirect'
|
|
import PlaybackVideo from './PlaybackVideo'
|
|
import PlaybackActions from './PlaybackActions'
|
|
import PlaybackLyricsCard from './PlaybackLyricsCard'
|
|
import usePlaybackOwner from './usePlaybackOwner'
|
|
import usePlaybackPlayer from './usePlaybackPlayer'
|
|
|
|
const PlaybackItem = ({ item, isActive, userCache }) => {
|
|
const { currentUID, followUser, unfollowUser, getUserByUid } = useUser() || {}
|
|
const { owner, isFollowing, isFollowActionPending, toggleFollow } = usePlaybackOwner({
|
|
item,
|
|
userCache,
|
|
getUserByUid,
|
|
currentUID,
|
|
followUser,
|
|
unfollowUser,
|
|
})
|
|
|
|
const initialLikedBy = getProjectLikes(item, LIKE_TARGET.PLAYBACK)
|
|
const [isLiked, setIsLiked] = useState(currentUID ? initialLikedBy.includes(currentUID) : false)
|
|
const [likesCount, setLikesCount] = useState(initialLikedBy.length)
|
|
|
|
useEffect(() => {
|
|
const lb = getProjectLikes(item, LIKE_TARGET.PLAYBACK)
|
|
setLikesCount(lb.length)
|
|
setIsLiked(currentUID ? lb.includes(currentUID) : false)
|
|
}, [item?.likes?.playback, currentUID])
|
|
|
|
const commentsCount = useMemo(() => Number(item?.commentsCount || 0), [item?.commentsCount])
|
|
|
|
const projectTitle = typeof item?.title === 'string' ? item.title.trim() : ''
|
|
|
|
const creatorName = useMemo(() => {
|
|
if (owner?.displayName) return owner.displayName
|
|
if (owner?.artistName) return owner.artistName
|
|
if (owner?.userName) return owner.userName
|
|
if (typeof item?.userName === 'string') return item.userName
|
|
return ''
|
|
}, [item?.userName, owner?.artistName, owner?.displayName, owner?.userName])
|
|
|
|
const { videoUrl, videoPlayer, currentTimeS } = usePlaybackPlayer({
|
|
item,
|
|
isActive,
|
|
})
|
|
|
|
const alignedWords = useMemo(() => {
|
|
const idx = Number(item?.songIndex) || 0
|
|
const ts = item?.musicTimestamps?.[idx]
|
|
const arr = Array.isArray(ts?.alignedWords) ? ts.alignedWords : []
|
|
return arr.map((w) => ({
|
|
word: String(w?.word ?? ''),
|
|
startS: Number(w?.startS ?? 0),
|
|
endS: Number(w?.endS ?? 0),
|
|
}))
|
|
}, [item?.musicTimestamps, item?.songIndex])
|
|
|
|
const sharePayload = useMemo(() => {
|
|
if (!item?.id) return null
|
|
return createPlaybackSharePayload({
|
|
projectId: item.id,
|
|
title: projectTitle || undefined,
|
|
artist: creatorName || undefined,
|
|
playbackUrl: videoUrl || undefined,
|
|
})
|
|
}, [creatorName, item?.id, projectTitle, videoUrl])
|
|
|
|
const handleShare = useCallback(() => {
|
|
if (sharePayload) {
|
|
openShareSheet(sharePayload)
|
|
}
|
|
}, [sharePayload])
|
|
|
|
const handleReport = useCallback(() => {
|
|
if (!item?.id) return
|
|
SheetManager.show('Report', {
|
|
payload: {
|
|
targetType: 'playback',
|
|
projectId: item?.id || null,
|
|
playbackId: item?.id || null,
|
|
title: item?.title || '',
|
|
ownerId: item?.userId || null,
|
|
},
|
|
})
|
|
}, [item?.id, item?.title, item?.userId])
|
|
|
|
const handleLike = useCallback(async () => {
|
|
try {
|
|
if (!item?.id) return
|
|
if (!ensureAuthenticated(currentUID)) {
|
|
return
|
|
}
|
|
const nextLiked = !isLiked
|
|
setIsLiked(nextLiked)
|
|
setLikesCount((c) => Math.max(0, c + (nextLiked ? 1 : -1)))
|
|
|
|
await toggleProjectLike({
|
|
projectId: item.id,
|
|
currentUID,
|
|
target: LIKE_TARGET.PLAYBACK,
|
|
next: nextLiked,
|
|
})
|
|
} catch (e) {
|
|
setIsLiked((v) => !v)
|
|
setLikesCount((c) => (isLiked ? c + 1 : Math.max(0, c - 1)))
|
|
}
|
|
}, [currentUID, isLiked, item?.id])
|
|
|
|
const handleCommentPress = useCallback(() => {
|
|
if (item?.id) openComments(item.id)
|
|
}, [item?.id])
|
|
|
|
const onOpenProfile = useCallback(() => {
|
|
navigate(Routes.SingerProfile, { userId: item?.userId })
|
|
}, [item?.userId])
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
<PlaybackVideo videoUrl={videoUrl} videoPlayer={videoPlayer} />
|
|
<View style={styles.overlay}>
|
|
<PlaybackActions
|
|
owner={owner}
|
|
currentUID={currentUID}
|
|
isFollowing={isFollowing}
|
|
isFollowActionPending={isFollowActionPending}
|
|
onToggleFollow={toggleFollow}
|
|
likesCount={likesCount}
|
|
isLiked={isLiked}
|
|
onToggleLike={handleLike}
|
|
commentsCount={commentsCount}
|
|
onCommentsPress={handleCommentPress}
|
|
onShare={handleShare}
|
|
onReport={handleReport}
|
|
onOpenProfile={onOpenProfile}
|
|
/>
|
|
<PlaybackLyricsCard
|
|
alignedWords={alignedWords}
|
|
currentTimeS={currentTimeS}
|
|
fallbackTitle={item?.title}
|
|
/>
|
|
</View>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
height: responsiveHeight(100),
|
|
position: 'relative',
|
|
backgroundColor: 'black',
|
|
},
|
|
overlay: {
|
|
position: 'absolute',
|
|
width: '100%',
|
|
bottom: 130,
|
|
gap: 18,
|
|
},
|
|
})
|
|
|
|
export default PlaybackItem
|