新增是否允许通过歌词调整播放进度功能

This commit is contained in:
lyswhut 2023-09-01 21:38:22 +08:00
parent cc5628252c
commit 3d735273e6
11 changed files with 504 additions and 89 deletions

View File

@ -12,6 +12,7 @@
- 新增列表设置-是否显示歌曲专辑名,默认关闭
- 新增列表设置-是否显示歌曲时长,默认开启
- 新增是否允许通过歌词调整播放进度功能,默认关闭,可到播放详情页右上角设置开启
### 优化

View File

@ -31,6 +31,7 @@ const defaultSetting: LX.AppSetting = {
'playDetail.style.align': 'center',
'playDetail.vertical.style.lrcFontSize': 176,
'playDetail.horizontal.style.lrcFontSize': 180,
'playDetail.isShowLyricProgressSetting': false,
'desktopLyric.enable': false,
'desktopLyric.isLock': false,

View File

@ -115,6 +115,7 @@
"play_detail_setting_lrc_font_size": "Lyric font size",
"play_detail_setting_playback_rate": "Playback rate",
"play_detail_setting_playback_rate_reset": "reset",
"play_detail_setting_show_lyric_progress_setting": "Allows to adjust playback progress by lyrics",
"play_detail_setting_title": "Player settings",
"play_detail_setting_volume": "Volume",
"play_detail_todo_tip": "What do you want? No, this function has not been implemented yet 😛, But you can try to locate the currently playing song by long pressing (only valid for playing songs in \"My List\")",

View File

@ -115,6 +115,7 @@
"play_detail_setting_lrc_font_size": "歌词字体大小",
"play_detail_setting_playback_rate": "播放速率",
"play_detail_setting_playback_rate_reset": "重置",
"play_detail_setting_show_lyric_progress_setting": "允许通过歌词调整播放进度",
"play_detail_setting_title": "播放器设置",
"play_detail_setting_volume": "音量大小",
"play_detail_todo_tip": "你想干嘛?不可以的,这个功能还没有实现哦😛,不过你可以试着长按来定位当前播放的歌曲(仅对播放“我的列表”里的歌曲有效哦)",

View File

@ -1,5 +1,5 @@
import { memo, useMemo, useEffect, useRef } from 'react'
import { View, FlatList, type FlatListProps } from 'react-native'
import { memo, useMemo, useEffect, useRef, useCallback } from 'react'
import { View, FlatList, type FlatListProps, type NativeSyntheticEvent, type NativeScrollEvent, type LayoutChangeEvent } from 'react-native'
// import { useLayout } from '@/utils/hooks'
import { type Line, useLrcPlay, useLrcSet } from '@/plugins/lyric'
import { createStyle } from '@/utils/tools'
@ -9,18 +9,21 @@ import { useSettingValue } from '@/store/setting/hook'
import { AnimatedColorText } from '@/components/common/Text'
import { setSpText } from '@/utils/pixelRatio'
import playerState from '@/store/player/state'
import { scrollTo } from '@/utils/scroll'
import PlayLine, { type PlayLineType } from '../components/PlayLine'
// import { screenkeepAwake } from '@/utils/nativeModules/utils'
// import { log } from '@/utils/log'
// import { toast } from '@/utils/tools'
type FlatListType = FlatListProps<Line>
const LrcLine = memo(({ line, lineNum, activeLine }: {
interface LineProps {
line: Line
lineNum: number
activeLine: number
}) => {
onLayout: (lineNum: number, height: number, width: number) => void
}
const LrcLine = memo(({ line, lineNum, activeLine, onLayout }: LineProps) => {
const theme = useTheme()
const lrcFontSize = useSettingValue('playDetail.horizontal.style.lrcFontSize')
const textAlign = useSettingValue('playDetail.style.align')
@ -38,10 +41,14 @@ const LrcLine = memo(({ line, lineNum, activeLine }: {
]
}, [activeLine, lineNum, theme])
const handleLayout = ({ nativeEvent }: LayoutChangeEvent) => {
onLayout(lineNum, nativeEvent.layout.height, nativeEvent.layout.width)
}
// textBreakStrategy="simple" 用于解决某些设备上字体被截断的问题
// https://stackoverflow.com/a/72822360
return (
<View style={styles.line}>
<View style={styles.line} onLayout={handleLayout}>
<AnimatedColorText style={{
...styles.lineText,
textAlign,
@ -69,11 +76,16 @@ export default () => {
const lyricLines = useLrcSet()
const { line } = useLrcPlay()
const flatListRef = useRef<FlatList>(null)
const playLineRef = useRef<PlayLineType>(null)
const isPauseScrollRef = useRef(true)
const scrollTimoutRef = useRef<NodeJS.Timeout | null>(null)
const delayScrollTimeout = useRef<NodeJS.Timeout | null>(null)
const lineRef = useRef(0)
const lineRef = useRef({ line: 0, prevLine: 0 })
const isFirstSetLrc = useRef(true)
const scrollInfoRef = useRef<NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'] | null>(null)
const listLayoutInfoRef = useRef<{ spaceHeight: number, lineHeights: number[] }>({ spaceHeight: 0, lineHeights: [] })
const scrollCancelRef = useRef<(() => void) | null>(null)
const isShowLyricProgressSetting = useSettingValue('playDetail.isShowLyricProgressSetting')
// useLock()
// const [imgUrl, setImgUrl] = useState(null)
// const theme = useGetter('common', 'theme')
@ -87,21 +99,45 @@ export default () => {
// }, [playMusicInfo])
// const imgWidth = useMemo(() => layout.width * 0.75, [layout.width])
const handleScrollToActive = (index = lineRef.current) => {
const handleScrollToActive = (index = lineRef.current.line) => {
if (index < 0) return
if (flatListRef.current) {
try {
flatListRef.current.scrollToIndex({
index,
animated: true,
viewPosition: 0.4,
})
} catch {}
// console.log('handleScrollToActive', index)
if (scrollCancelRef.current) {
scrollCancelRef.current()
scrollCancelRef.current = null
}
if (scrollInfoRef.current && lineRef.current.line - lineRef.current.prevLine == 1) {
let offset = listLayoutInfoRef.current.spaceHeight
for (let line = 0; line < index; line++) {
offset += listLayoutInfoRef.current.lineHeights[line]
}
try {
scrollCancelRef.current = scrollTo(flatListRef.current, scrollInfoRef.current, offset - scrollInfoRef.current.layoutMeasurement.height * 0.36, 300, () => {
scrollCancelRef.current = null
})
} catch {}
} else {
try {
flatListRef.current.scrollToIndex({
index,
animated: true,
viewPosition: 0.4,
})
} catch {}
}
}
}
const handleScroll = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
scrollInfoRef.current = nativeEvent
if (isPauseScrollRef.current) {
playLineRef.current?.updateScrollInfo(nativeEvent)
}
}
const handleScrollBeginDrag = () => {
isPauseScrollRef.current = true
playLineRef.current?.setVisible(true)
if (delayScrollTimeout.current) {
clearTimeout(delayScrollTimeout.current)
delayScrollTimeout.current = null
@ -116,6 +152,7 @@ export default () => {
if (!isPauseScrollRef.current) return
if (scrollTimoutRef.current) clearTimeout(scrollTimoutRef.current)
scrollTimoutRef.current = setTimeout(() => {
playLineRef.current?.setVisible(false)
scrollTimoutRef.current = null
isPauseScrollRef.current = false
if (!playerState.isPlay) return
@ -139,25 +176,43 @@ export default () => {
useEffect(() => {
// linesRef.current = lyricLines
listLayoutInfoRef.current.lineHeights = []
lineRef.current.prevLine = 0
lineRef.current.line = 0
if (!flatListRef.current) return
flatListRef.current.scrollToOffset({
offset: 0,
animated: false,
})
if (isFirstSetLrc.current) {
isFirstSetLrc.current = false
setTimeout(() => {
isPauseScrollRef.current = false
handleScrollToActive()
}, 100)
} else {
handleScrollToActive(0)
}
if (!lyricLines.length) return
playLineRef.current?.updateLyricLines(lyricLines)
requestAnimationFrame(() => {
if (isFirstSetLrc.current) {
isFirstSetLrc.current = false
setTimeout(() => {
isPauseScrollRef.current = false
handleScrollToActive()
}, 100)
} else {
if (delayScrollTimeout.current) clearTimeout(delayScrollTimeout.current)
delayScrollTimeout.current = setTimeout(() => {
handleScrollToActive(0)
}, 100)
}
})
}, [lyricLines])
useEffect(() => {
lineRef.current = line
if (line < 0) return
lineRef.current.prevLine = lineRef.current.line
lineRef.current.line = line
if (!flatListRef.current || isPauseScrollRef.current) return
if (line - lineRef.current.prevLine != 1) {
handleScrollToActive()
return
}
delayScrollTimeout.current = setTimeout(() => {
delayScrollTimeout.current = null
handleScrollToActive()
@ -165,39 +220,57 @@ export default () => {
}, [line])
const handleScrollToIndexFailed: FlatListType['onScrollToIndexFailed'] = (info) => {
// console.log(info)
void wait().then(() => {
handleScrollToActive(info.index)
})
}
const handleLineLayout = useCallback<LineProps['onLayout']>((lineNum, height) => {
listLayoutInfoRef.current.lineHeights[lineNum] = height
playLineRef.current?.updateLayoutInfo(listLayoutInfoRef.current)
}, [])
const handleSpaceLayout = useCallback(({ nativeEvent }: LayoutChangeEvent) => {
listLayoutInfoRef.current.spaceHeight = nativeEvent.layout.height
playLineRef.current?.updateLayoutInfo(listLayoutInfoRef.current)
}, [])
const handlePlayLine = useCallback((time: number) => {
playLineRef.current?.setVisible(false)
global.app_event.setProgress(time)
}, [])
const renderItem: FlatListType['renderItem'] = ({ item, index }) => {
return (
<LrcLine line={item} lineNum={index} activeLine={line} />
<LrcLine line={item} lineNum={index} activeLine={line} onLayout={handleLineLayout} />
)
}
const getkey: FlatListType['keyExtractor'] = (item, index) => `${index}${item.text}`
const spaceComponent = useMemo(() => (
<View style={styles.space}></View>
), [])
<View style={styles.space} onLayout={handleSpaceLayout}></View>
), [handleSpaceLayout])
return (
<FlatList
data={lyricLines}
renderItem={renderItem}
keyExtractor={getkey}
style={styles.container}
ref={flatListRef}
showsVerticalScrollIndicator={false}
ListHeaderComponent={spaceComponent}
ListFooterComponent={spaceComponent}
onScrollBeginDrag={handleScrollBeginDrag}
onScrollEndDrag={onScrollEndDrag}
fadingEdgeLength={100}
initialNumToRender={Math.max(line + 10, 10)}
onScrollToIndexFailed={handleScrollToIndexFailed}
/>
<>
<FlatList
data={lyricLines}
renderItem={renderItem}
keyExtractor={getkey}
style={styles.container}
ref={flatListRef}
showsVerticalScrollIndicator={false}
ListHeaderComponent={spaceComponent}
ListFooterComponent={spaceComponent}
onScrollBeginDrag={handleScrollBeginDrag}
onScrollEndDrag={onScrollEndDrag}
fadingEdgeLength={100}
initialNumToRender={Math.max(line + 10, 10)}
onScrollToIndexFailed={handleScrollToIndexFailed}
onScroll={handleScroll}
/>
{ isShowLyricProgressSetting ? <PlayLine ref={playLineRef} onPlayLine={handlePlayLine} /> : null }
</>
)
}

View File

@ -1,5 +1,5 @@
import { memo, useMemo, useEffect, useRef } from 'react'
import { View, FlatList, type FlatListProps } from 'react-native'
import { memo, useMemo, useEffect, useRef, useCallback } from 'react'
import { View, FlatList, type FlatListProps, type LayoutChangeEvent, type NativeSyntheticEvent, type NativeScrollEvent } from 'react-native'
// import { useLayout } from '@/utils/hooks'
import { type Line, useLrcPlay, useLrcSet } from '@/plugins/lyric'
import { createStyle } from '@/utils/tools'
@ -9,6 +9,8 @@ import { useSettingValue } from '@/store/setting/hook'
import { AnimatedColorText } from '@/components/common/Text'
import { setSpText } from '@/utils/pixelRatio'
import playerState from '@/store/player/state'
import { scrollTo } from '@/utils/scroll'
import PlayLine, { type PlayLineType } from '../components/PlayLine'
// import { screenkeepAwake } from '@/utils/nativeModules/utils'
// import { log } from '@/utils/log'
// import { toast } from '@/utils/tools'
@ -54,11 +56,13 @@ type FlatListType = FlatListProps<Line>
// }, [])
// }
const LrcLine = memo(({ line, lineNum, activeLine }: {
interface LineProps {
line: Line
lineNum: number
activeLine: number
}) => {
onLayout: (lineNum: number, height: number, width: number) => void
}
const LrcLine = memo(({ line, lineNum, activeLine, onLayout }: LineProps) => {
const theme = useTheme()
const lrcFontSize = useSettingValue('playDetail.vertical.style.lrcFontSize')
const textAlign = useSettingValue('playDetail.style.align')
@ -76,11 +80,15 @@ const LrcLine = memo(({ line, lineNum, activeLine }: {
]
}, [activeLine, lineNum, theme])
const handleLayout = ({ nativeEvent }: LayoutChangeEvent) => {
onLayout(lineNum, nativeEvent.layout.height, nativeEvent.layout.width)
}
// textBreakStrategy="simple" 用于解决某些设备上字体被截断的问题
// https://stackoverflow.com/a/72822360
return (
<View style={styles.line}>
<View style={styles.line} onLayout={handleLayout}>
<AnimatedColorText style={{
...styles.lineText,
textAlign,
@ -108,11 +116,16 @@ export default () => {
const lyricLines = useLrcSet()
const { line } = useLrcPlay()
const flatListRef = useRef<FlatList>(null)
const playLineRef = useRef<PlayLineType>(null)
const isPauseScrollRef = useRef(true)
const scrollTimoutRef = useRef<NodeJS.Timeout | null>(null)
const delayScrollTimeout = useRef<NodeJS.Timeout | null>(null)
const lineRef = useRef(0)
const lineRef = useRef({ line: 0, prevLine: 0 })
const isFirstSetLrc = useRef(true)
const scrollInfoRef = useRef<NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'] | null>(null)
const listLayoutInfoRef = useRef<{ spaceHeight: number, lineHeights: number[] }>({ spaceHeight: 0, lineHeights: [] })
const scrollCancelRef = useRef<(() => void) | null>(null)
const isShowLyricProgressSetting = useSettingValue('playDetail.isShowLyricProgressSetting')
// useLock()
// const [imgUrl, setImgUrl] = useState(null)
// const theme = useGetter('common', 'theme')
@ -126,21 +139,45 @@ export default () => {
// }, [playMusicInfo])
// const imgWidth = useMemo(() => layout.width * 0.75, [layout.width])
const handleScrollToActive = (index = lineRef.current) => {
const handleScrollToActive = (index = lineRef.current.line) => {
if (index < 0) return
if (flatListRef.current) {
try {
flatListRef.current.scrollToIndex({
index,
animated: true,
viewPosition: 0.4,
})
} catch {}
// console.log('handleScrollToActive', index)
if (scrollCancelRef.current) {
scrollCancelRef.current()
scrollCancelRef.current = null
}
if (scrollInfoRef.current && lineRef.current.line - lineRef.current.prevLine == 1) {
let offset = listLayoutInfoRef.current.spaceHeight
for (let line = 0; line < index; line++) {
offset += listLayoutInfoRef.current.lineHeights[line]
}
try {
scrollCancelRef.current = scrollTo(flatListRef.current, scrollInfoRef.current, offset - scrollInfoRef.current.layoutMeasurement.height * 0.36, 300, () => {
scrollCancelRef.current = null
})
} catch {}
} else {
try {
flatListRef.current.scrollToIndex({
index,
animated: true,
viewPosition: 0.4,
})
} catch {}
}
}
}
const handleScroll = ({ nativeEvent }: NativeSyntheticEvent<NativeScrollEvent>) => {
scrollInfoRef.current = nativeEvent
if (isPauseScrollRef.current) {
playLineRef.current?.updateScrollInfo(nativeEvent)
}
}
const handleScrollBeginDrag = () => {
isPauseScrollRef.current = true
playLineRef.current?.setVisible(true)
if (delayScrollTimeout.current) {
clearTimeout(delayScrollTimeout.current)
delayScrollTimeout.current = null
@ -155,6 +192,7 @@ export default () => {
if (!isPauseScrollRef.current) return
if (scrollTimoutRef.current) clearTimeout(scrollTimoutRef.current)
scrollTimoutRef.current = setTimeout(() => {
playLineRef.current?.setVisible(false)
scrollTimoutRef.current = null
isPauseScrollRef.current = false
if (!playerState.isPlay) return
@ -178,25 +216,43 @@ export default () => {
useEffect(() => {
// linesRef.current = lyricLines
listLayoutInfoRef.current.lineHeights = []
lineRef.current.prevLine = 0
lineRef.current.line = 0
if (!flatListRef.current) return
flatListRef.current.scrollToOffset({
offset: 0,
animated: false,
})
if (isFirstSetLrc.current) {
isFirstSetLrc.current = false
setTimeout(() => {
isPauseScrollRef.current = false
handleScrollToActive()
}, 100)
} else {
handleScrollToActive(0)
}
if (!lyricLines.length) return
playLineRef.current?.updateLyricLines(lyricLines)
requestAnimationFrame(() => {
if (isFirstSetLrc.current) {
isFirstSetLrc.current = false
setTimeout(() => {
isPauseScrollRef.current = false
handleScrollToActive()
}, 100)
} else {
if (delayScrollTimeout.current) clearTimeout(delayScrollTimeout.current)
delayScrollTimeout.current = setTimeout(() => {
handleScrollToActive(0)
}, 100)
}
})
}, [lyricLines])
useEffect(() => {
lineRef.current = line
if (line < 0) return
lineRef.current.prevLine = lineRef.current.line
lineRef.current.line = line
if (!flatListRef.current || isPauseScrollRef.current) return
if (line - lineRef.current.prevLine != 1) {
handleScrollToActive()
return
}
delayScrollTimeout.current = setTimeout(() => {
delayScrollTimeout.current = null
handleScrollToActive()
@ -204,39 +260,57 @@ export default () => {
}, [line])
const handleScrollToIndexFailed: FlatListType['onScrollToIndexFailed'] = (info) => {
// console.log(info)
void wait().then(() => {
handleScrollToActive(info.index)
})
}
const handleLineLayout = useCallback<LineProps['onLayout']>((lineNum, height) => {
listLayoutInfoRef.current.lineHeights[lineNum] = height
playLineRef.current?.updateLayoutInfo(listLayoutInfoRef.current)
}, [])
const handleSpaceLayout = useCallback(({ nativeEvent }: LayoutChangeEvent) => {
listLayoutInfoRef.current.spaceHeight = nativeEvent.layout.height
playLineRef.current?.updateLayoutInfo(listLayoutInfoRef.current)
}, [])
const handlePlayLine = useCallback((time: number) => {
playLineRef.current?.setVisible(false)
global.app_event.setProgress(time)
}, [])
const renderItem: FlatListType['renderItem'] = ({ item, index }) => {
return (
<LrcLine line={item} lineNum={index} activeLine={line} />
<LrcLine line={item} lineNum={index} activeLine={line} onLayout={handleLineLayout} />
)
}
const getkey: FlatListType['keyExtractor'] = (item, index) => `${index}${item.text}`
const spaceComponent = useMemo(() => (
<View style={styles.space}></View>
), [])
<View style={styles.space} onLayout={handleSpaceLayout}></View>
), [handleSpaceLayout])
return (
<FlatList
data={lyricLines}
renderItem={renderItem}
keyExtractor={getkey}
style={styles.container}
ref={flatListRef}
showsVerticalScrollIndicator={false}
ListHeaderComponent={spaceComponent}
ListFooterComponent={spaceComponent}
onScrollBeginDrag={handleScrollBeginDrag}
onScrollEndDrag={onScrollEndDrag}
fadingEdgeLength={100}
initialNumToRender={Math.max(line + 10, 10)}
onScrollToIndexFailed={handleScrollToIndexFailed}
/>
<>
<FlatList
data={lyricLines}
renderItem={renderItem}
keyExtractor={getkey}
style={styles.container}
ref={flatListRef}
showsVerticalScrollIndicator={false}
ListHeaderComponent={spaceComponent}
ListFooterComponent={spaceComponent}
onScrollBeginDrag={handleScrollBeginDrag}
onScrollEndDrag={onScrollEndDrag}
fadingEdgeLength={100}
initialNumToRender={Math.max(line + 10, 10)}
onScrollToIndexFailed={handleScrollToIndexFailed}
onScroll={handleScroll}
/>
{ isShowLyricProgressSetting ? <PlayLine ref={playLineRef} onPlayLine={handlePlayLine} /> : null }
</>
)
}
@ -251,8 +325,8 @@ const styles = createStyle({
paddingTop: '80%',
},
line: {
marginTop: 10,
marginBottom: 10,
paddingTop: 10,
paddingBottom: 10,
// opacity: 0,
},
lineText: {

View File

@ -0,0 +1,136 @@
import { forwardRef, useImperativeHandle, useRef, useState } from 'react'
import { type NativeScrollEvent, type NativeSyntheticEvent, View, TouchableOpacity, Animated } from 'react-native'
import Text from '@/components/common/Text'
import { createStyle } from '@/utils/tools'
import { type Lines } from 'lrc-file-parser'
import { useTheme } from '@/store/theme/hook'
import { BorderWidths } from '@/theme'
import { formatPlayTime2 } from '@/utils'
import { Icon } from '@/components/common/Icon'
export interface PlayLineType {
updateScrollInfo: (scrollInfo: NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'] | null) => void
updateLayoutInfo: (listLayoutInfo: { spaceHeight: number, lineHeights: number[] }) => void
updateLyricLines: (lyricLines: Lines) => void
setVisible: (visible: boolean) => void
}
export interface PlayLineProps {
onPlayLine: (time: number) => void
}
const ANIMATION_DURATION = 300
export default forwardRef<PlayLineType, PlayLineProps>(({ onPlayLine }, ref) => {
const theme = useTheme()
const [scrollInfo, setScrollInfo] = useState<NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'] | null>(null)
const [listLayoutInfo, setListLayoutInfo] = useState<{ spaceHeight: number, lineHeights: number[] }>({ spaceHeight: 0, lineHeights: [] })
const [lyricLines, setLyricLines] = useState<Lines>([])
const [visible, setVisible] = useState(false)
const opsAnim = useRef<Animated.Value>(
new Animated.Value(0),
).current
const setShow = (visible: boolean) => {
Animated.timing(opsAnim, {
toValue: visible ? 1 : 0,
duration: ANIMATION_DURATION,
useNativeDriver: true,
}).start(() => {
if (!visible) setVisible(false)
})
}
useImperativeHandle(ref, () => ({
updateScrollInfo(scrollInfo) {
setScrollInfo(scrollInfo)
},
updateLayoutInfo(listLayoutInfo) {
setListLayoutInfo(listLayoutInfo)
},
updateLyricLines(lyricLines) {
setLyricLines(lyricLines)
},
setVisible(visible) {
if (visible) {
setVisible(true)
}
requestAnimationFrame(() => {
setShow(visible)
})
// setVisible()
},
}), [])
const handlePlayLine = () => {
onPlayLine(time / 1000)
}
if (!scrollInfo || !visible) return null
const offset = scrollInfo.contentOffset.y + scrollInfo.layoutMeasurement.height * 0.4
let lineOffset = listLayoutInfo.spaceHeight
let targetLineNum = -1
for (let line = 0; line < listLayoutInfo.lineHeights.length; line++) {
lineOffset += listLayoutInfo.lineHeights[line]
if (lineOffset < offset) continue
targetLineNum = line
break
}
if (targetLineNum == -1) targetLineNum = listLayoutInfo.lineHeights.length - 1
const time = lyricLines[targetLineNum]?.time ?? 0
const timeLabel = formatPlayTime2(time / 1000)
return (
<Animated.View style={{ ...styles.playLine, opacity: opsAnim }}>
<Text style={styles.label} color={theme['c-primary-font']} size={13}>{timeLabel}</Text>
<View style={styles.lineContent}>
<View style={{ ...styles.line, borderBottomColor: theme['c-primary-alpha-300'] }} />
<TouchableOpacity style={styles.button} onPress={handlePlayLine}>
<Icon name="play" color={theme['c-button-font']} size={14} />
</TouchableOpacity>
</View>
</Animated.View>
)
})
const styles = createStyle({
playLine: {
position: 'absolute',
width: '100%',
top: '40%',
left: 0,
height: 2,
// paddingTop: 5,
// paddingBottom: 5,
// backgroundColor: 'rgba(0,0,0,0.1)',
},
label: {
position: 'absolute',
right: 37,
bottom: 0,
flexDirection: 'row',
alignItems: 'center',
gap: 5,
},
lineContent: {
// backgroundColor: 'rgba(0,0,0,0.1)',
position: 'absolute',
width: '100%',
height: 20,
top: -10,
flexDirection: 'row',
alignItems: 'center',
gap: 5,
},
line: {
marginLeft: 15,
borderBottomWidth: BorderWidths.normal,
borderStyle: 'dashed',
flex: 1,
},
button: {
flex: 0,
paddingLeft: 5,
paddingRight: 15,
},
})

View File

@ -3,6 +3,7 @@ import { ScrollView, View } from 'react-native'
import Popup, { type PopupType, type PopupProps } from '@/components/common/Popup'
import { useI18n } from '@/lang'
import SettingLyricProgress from './settings/SettingLyricProgress'
import SettingVolume from './settings/SettingVolume'
import SettingPlaybackRate from './settings/SettingPlaybackRate'
import SettingLrcFontSize from './settings/SettingLrcFontSize'
@ -41,6 +42,7 @@ export default forwardRef<SettingPopupType, SettingPopupProps>(({ direction, ...
<Popup ref={popupRef} title={t('play_detail_setting_title')} {...props}>
<ScrollView>
<View onStartShouldSetResponder={() => true}>
<SettingLyricProgress />
<SettingVolume />
<SettingPlaybackRate />
<SettingLrcFontSize direction={direction} />

View File

@ -0,0 +1,28 @@
import { View } from 'react-native'
// import Text from '@/components/common/Text'
import { useSettingValue } from '@/store/setting/hook'
import { updateSetting } from '@/core/common'
import { useI18n } from '@/lang'
import CheckBox from '@/components/common/CheckBox'
import styles from './style'
export default () => {
const t = useI18n()
const isShowLyricProgressSetting = useSettingValue('playDetail.isShowLyricProgressSetting')
const setShowLyricProgressSetting = (showLyricProgressSetting: boolean) => {
updateSetting({ 'playDetail.isShowLyricProgressSetting': showLyricProgressSetting })
}
return (
<View style={styles.container}>
<View style={styles.content}>
<View style={styles.content}>
<CheckBox marginBottom={3} check={isShowLyricProgressSetting} label={t('play_detail_setting_show_lyric_progress_setting')} onChange={setShowLyricProgressSetting} />
</View>
</View>
</View>
)
}

View File

@ -171,6 +171,11 @@ declare global {
*/
'playDetail.horizontal.style.lrcFontSize': number
/**
* -
*/
'playDetail.isShowLyricProgressSetting': boolean
/**
*
*/

93
src/utils/scroll.ts Normal file
View File

@ -0,0 +1,93 @@
import { type NativeScrollEvent, type FlatList, type NativeSyntheticEvent } from 'react-native'
const easeInOutQuad = (t: number, b: number, c: number, d: number): number => {
t /= d / 2
if (t < 1) return (c / 2) * t * t + b
t--
return (-c / 2) * (t * (t - 2) - 1) + b
}
type Noop = () => void
const noop: Noop = () => {}
const handleScrollY = (element: FlatList<any>, info: NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'], to: number, duration = 300, fn = noop): Noop => {
if (element == null) {
fn()
return noop
}
const key = Math.random()
const start = info.contentOffset.y
let cancel = false
if (to > start) {
let maxScrollTop = info.contentSize.height - info.layoutMeasurement.height
if (to > maxScrollTop) to = maxScrollTop
} else if (to < start) {
if (to < 0) to = 0
} else {
fn()
return noop
}
const change = to - start
const increment = 10
if (!change) {
fn()
return noop
}
let currentTime = 0
let val
const animateScroll = () => {
// @ts-expect-error
if (cancel || element.lx_scrollKey != key) {
fn()
return
}
currentTime += increment
val = Math.trunc(easeInOutQuad(currentTime, start, change, duration))
element.scrollToOffset({ offset: val, animated: false })
if (currentTime < duration) {
setTimeout(animateScroll, increment)
} else {
fn()
}
}
// @ts-expect-error
element.lx_scrollKey = key
requestAnimationFrame(() => {
animateScroll()
})
return () => {
cancel = true
}
}
/**
*
* @param element dom
* @param to
* @param duration ms
* @param fn
* @param delay
*/
export const scrollTo = (element: FlatList<any>, info: NativeSyntheticEvent<NativeScrollEvent>['nativeEvent'], to: number, duration = 300, fn = () => {}, delay = 0): () => void => {
let cancelFn: () => void
let timeout: NodeJS.Timeout | null
if (delay) {
let scrollCancelFn: Noop
cancelFn = () => {
timeout == null ? scrollCancelFn?.() : clearTimeout(timeout)
}
timeout = setTimeout(() => {
timeout = null
scrollCancelFn = handleScrollY(element, info, to, duration, fn)
}, delay)
} else {
cancelFn = handleScrollY(element, info, to, duration, fn) ?? noop
}
return cancelFn
}