feat: add lyrics and song sharing functionality#149
Conversation
- Add LyricsSelectionModal and SongShareModal for sharing - Add sharing menu items to PlayerFunctionalMenu - Add react-native-view-shot dependency for screenshot support
|
Caution Review failedThe pull request is closed. Walkthrough本 PR 将版本从 2.1.9 升级到 2.1.10,添加 Changes
Sequence Diagram(s)sequenceDiagram
participant User as 用户
participant Menu as 功能菜单
participant Modal as LyricsSelectionModal / SongShareModal
participant Card as LyricsShareCard / SongShareCard
participant ViewShot as react-native-view-shot
participant OS as 系统分享/媒体库 (Expo)
participant Toast as 通知
User->>Menu: 点击“分享歌词”/“分享歌曲”
Menu->>Modal: 打开对应模态
Modal->>Modal: 获取歌词/Track 数据
User->>Modal: 选择/确认内容(或等待自动生成)
Modal->>Card: 渲染隐藏的分享卡片(颜色提取、布局)
Modal->>ViewShot: 捕获卡片并返回图片 URI
ViewShot-->>Modal: 返回预览 URI
User->>Modal: 选择“分享”或“保存”
alt 分享
Modal->>OS: 调用分享 API
OS-->>Toast: 返回结果
else 保存
Modal->>OS: 检查/请求媒体库权限
OS-->>Modal: 权限结果
Modal->>OS: 保存图片到相册
OS-->>Toast: 返回结果
end
Toast-->>User: 显示成功/失败提示
Modal->>Modal: 关闭模态
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
This reverts commit ffe81a6.
This reverts commit e1a6e79.
This reverts commit 8c25c6d.
This reverts commit b79a793.
This reverts commit 90f8bc8.
- Add null check for metadata in fetchRemotePlaylistMetadata - Add null check for bilibiliFavoriteListMetadata.info in sync - Replace optional chaining with non-null assertions where appropriate - Clean up toast config style and add const assertion - Add ts-expect-error comment for legacy streamer option
SafeDep Report SummaryPackage Details
This report is generated by SafeDep Github App |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/app/player.tsx (1)
171-179: useEffect 缺少依赖数组,导致每次渲染都会执行此 useEffect 没有依赖数组,会在每次渲染时执行。由于
playerBackgroundStyle和setSettings不会频繁变化,建议添加依赖数组以避免不必要的执行和潜在的 toast 重复显示。🔧 建议的修复方案
useEffect(() => { // `@ts-expect-error` -- 虽然我们项目内已经移除了 streamer 选项,但部分存量用户可能还在这个选项,需要帮他回退 if (playerBackgroundStyle === 'streamer') { toast.show( '因为会对性能造成较大影响,并且也不好看,所以我们移除了流光效果,已为您回退到渐变模式', ) setSettings({ playerBackgroundStyle: 'gradient' }) } -}) +}, [playerBackgroundStyle, setSettings])
🤖 Fix all issues with AI agents
In `@src/features/player/components/sharing/SongShareCard.tsx`:
- Around line 40-48: The fileName passed to ViewShot uses track.uniqueKey (in
SongShareCard) which contains characters like ":" that are invalid in filenames;
compute a sanitizedUniqueKey (e.g., by replacing non-alphanumeric characters
with "_" or using a safe encoding like encodeURIComponent/base64) and use that
in the fileName option instead of track.uniqueKey; update the ViewShot props
(options.fileName) and the place where viewShotRef/fileName is constructed so
all references use sanitizedUniqueKey to avoid file-save failures.
🧹 Nitpick comments (8)
src/components/modals/player/SongShareModal.tsx (2)
48-54: 缺少useEffect依赖项。
generatePreview在 effect 中被调用但未包含在依赖数组中。虽然当前实现由于[]依赖只运行一次可能不会出问题,但这违反了 React hooks 的规则,可能在未来重构时导致 bug。♻️ 建议修复
+ const generatePreviewRef = useRef(generatePreview) + generatePreviewRef.current = generatePreview + // 打开时自动生成预览 useEffect(() => { const timer = setTimeout(() => { - void generatePreview() + void generatePreviewRef.current() }, 300) // 延迟确保组件已渲染 return () => clearTimeout(timer) }, [])或者将
generatePreview包装在useCallback中并添加到依赖数组。
120-157: 考虑在没有当前曲目时提供更明确的提示。当
currentTrack为null时,模态框仍会渲染,但隐藏的捕获视图为空,导致预览生成失败并显示"预览加载失败"。建议在这种情况下提供更明确的用户提示。♻️ 可选优化
+ if (!currentTrack) { + return ( + <> + <Dialog.Title>分享歌曲</Dialog.Title> + <Dialog.Content style={styles.contentArea}> + <View style={styles.loadingContainer}> + <Text variant='bodyMedium' style={styles.loadingText}> + 暂无播放中的歌曲 + </Text> + </View> + </Dialog.Content> + <Dialog.Actions> + <Button onPress={() => close('SongShare')}>关闭</Button> + </Dialog.Actions> + </> + ) + } + return ( <> <Dialog.Title>分享歌曲</Dialog.Title>src/features/player/components/sharing/SongShareCard.tsx (1)
22-22: 分享 URL 可能过长影响二维码可扫描性。
shareUrl包含完整的coverUrl,可能导致 URL 非常长,生成的二维码密度过高难以扫描。考虑移除cover参数或在服务端通过id查询封面。src/features/player/components/sharing/LyricsShareCard.tsx (2)
28-47: 与SongShareCard存在大量重复代码。
shareUrl构造、背景色提取逻辑、底部 QR 码和品牌区域与SongShareCard.tsx几乎完全相同。建议抽取公共逻辑。♻️ 建议重构方向
- 创建自定义 hook
useShareCardTheme(coverUrl)来处理背景色提取- 抽取
ShareCardFooter组件来复用 QR 码和品牌展示- 创建
buildShareUrl(track)工具函数// hooks/useShareCardTheme.ts export const useShareCardTheme = (coverUrl?: string | null) => { const theme = useTheme() const [backgroundColor, setBackgroundColor] = useState( theme.colors.elevation.level3, ) // ... color extraction logic return backgroundColor }
40-43: 清理开发注释。这些注释看起来是开发过程中的备注,建议在合并前清理或转换为 TODO/FIXME 格式以便追踪。
src/components/modals/player/LyricsSelectionModal.tsx (3)
215-234:renderItem依赖selectedIndices导致不必要的重渲染。每次选择变化时
selectedIndicesSet 引用改变,导致renderItem重新创建。虽然LyricItem使用了memo,但由于isSelected对每个 item 都重新计算,可能导致性能问题。♻️ 建议优化
考虑使用
extraData属性让 FlashList 知道何时需要重新渲染:<FlashList data={lyrics} keyExtractor={keyExtractor} renderItem={renderItem} + extraData={selectedIndices} + estimatedItemSize={60} />或者将
isSelected计算移到LyricItem内部,通过 context 传递selectedIndices。
146-171: 与generatePreview存在重复的图片捕获逻辑。
handleShare中的图片捕获逻辑(Lines 156-170)与generatePreview(Lines 128-135)重复。这种模式在SongShareModal中也存在。建议抽取为共享工具函数。♻️ 建议抽取公共逻辑
// utils/viewShot.ts export const captureShareCard = async ( viewShotRef: React.RefObject<ViewShot>, fileNamePrefix: string ): Promise<string> => { const fileName = `${fileNamePrefix}-${Date.now()}` return captureRef(viewShotRef, { format: 'png', quality: 1, result: 'tmpfile', fileName, }) }然后在两个模态框中复用此函数。
99-115: 选择变化时清除previewUri但未重置showPreview。当用户在预览模式下通过某种方式触发选择变化时,
previewUri被清除但showPreview仍为true,可能导致短暂的 UI 不一致状态。♻️ 建议修复
const toggleSelection = useCallback((index: number) => { setSelectedIndices((prev) => { // ... existing logic }) // 选择变化后清除旧预览 setPreviewUri(null) + setShowPreview(false) }, [])

Summary by CodeRabbit
发布说明
新功能
错误修复
版本更新
✏️ Tip: You can customize this high-level summary in your review settings.