From b991aee71d84e972b74d8e641ab5501c0fcb5ea7 Mon Sep 17 00:00:00 2001 From: Sawyerf Date: Fri, 6 Dec 2024 14:25:10 +0100 Subject: [PATCH] Add: Slidebar & arrow volume --- app.json | 2 +- app/components/button/SlideBar.js | 52 ++++++++++++++ app/components/player/BoxDesktopPlayer.js | 87 +++++++---------------- app/components/player/BoxPlayer.js | 4 +- app/components/player/FullScreenPlayer.js | 55 +++++--------- app/components/player/Player.js | 18 +++-- app/screens/tabs/Settings.js | 2 +- app/utils/player.native.js | 10 ++- app/utils/player.web.js | 13 +++- package-lock.json | 4 +- package.json | 2 +- 11 files changed, 134 insertions(+), 115 deletions(-) create mode 100644 app/components/button/SlideBar.js diff --git a/app.json b/app.json index 433c185..be13bc6 100755 --- a/app.json +++ b/app.json @@ -3,7 +3,7 @@ "name": "Castafiore", "slug": "Castafiore", "description": "Mobile app for navidrome", - "version": "2024.12.05", + "version": "2024.12.06", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", diff --git a/app/components/button/SlideBar.js b/app/components/button/SlideBar.js new file mode 100644 index 0000000..31f2382 --- /dev/null +++ b/app/components/button/SlideBar.js @@ -0,0 +1,52 @@ +import React from 'react' +import { View, Pressable } from 'react-native' + +import { ThemeContext } from '~/contexts/theme' + +const SlideBar = ({ + progress = 0, + onPress = (progress) => { }, + stylePress = {}, + styleBar = {}, + styleProgress = {}, + isBitogno = false, + sizeBitogno = 12, +}) => { + const [layoutBar, setLayoutBar] = React.useState({ width: 0, height: 0 }) + const theme = React.useContext(ThemeContext) + + const onPressHandler = ({ nativeEvent }) => { + const prog = nativeEvent.locationX / layoutBar.width + if (!prog || prog < 0) return onPress(0) + else if (prog > 1) return onPress(1) + return onPress(prog) + } + + return ( + setLayoutBar({ width: nativeEvent.layout.width, height: nativeEvent.layout.height })} + pressRetentionOffset={{ top: 20, left: 0, right: 0, bottom: 20 }} + > + + + + {isBitogno && } + + ) +} + +const styles = { + bitognoBar: (vol, sizeBitogno, theme) => ({ + position: 'absolute', + width: sizeBitogno, + height: sizeBitogno, + borderRadius: sizeBitogno / 2, + backgroundColor: theme.primaryTouch, + left: `calc(${vol * 100}% - ${sizeBitogno / 2}px)`, top: 7 + }) +} + +export default SlideBar; \ No newline at end of file diff --git a/app/components/player/BoxDesktopPlayer.js b/app/components/player/BoxDesktopPlayer.js index dbf6591..314e50c 100644 --- a/app/components/player/BoxDesktopPlayer.js +++ b/app/components/player/BoxDesktopPlayer.js @@ -1,35 +1,21 @@ import React from 'react'; -import { Text, View, Image, TouchableOpacity, Platform, Pressable } from 'react-native'; -import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { Text, View, Platform } from 'react-native'; import Icon from 'react-native-vector-icons/FontAwesome'; -import { SongContext } from '~/contexts/song'; -import { nextSong, pauseSong, resumeSong, previousSong, setPosition } from '~/utils/player'; import { ConfigContext } from '~/contexts/config'; +import { SongContext } from '~/contexts/song'; import { ThemeContext } from '~/contexts/theme'; -import mainStyles from '~/styles/main'; +import { nextSong, pauseSong, resumeSong, previousSong, setPosition, secondToTime, setVolume } from '~/utils/player'; import { urlCover } from '~/utils/api'; +import mainStyles from '~/styles/main'; import IconButton from '~/components/button/IconButton'; import ImageError from '~/components/ImageError'; +import SlideBar from '~/components/button/SlideBar'; const BoxDesktopPlayer = ({ fullscreen, time }) => { const [song, songDispatch] = React.useContext(SongContext) const config = React.useContext(ConfigContext) - const insets = useSafeAreaInsets(); const theme = React.useContext(ThemeContext) - const [layoutBar, setLayoutBar] = React.useState({ width: 0, height: 0 }) - const [layoutBarTime, setLayoutBarTime] = React.useState({ width: 0, height: 0 }) - - const secondToTime = (second) => { - if (!second) return '00:00' - return `${String((second - second % 60) / 60).padStart(2, '0')}:${String((second - second % 1) % 60).padStart(2, '0')}` - } - - const setVolume = (vol) => { - if (vol < 0) vol = 0 - if (vol > 1) vol = 1 - song.sound.volume = vol - } return ( { {secondToTime(time.position)} - setPosition(song.sound, (nativeEvent.locationX / layoutBarTime.width) * time.duration)} - onPressOut={({ nativeEvent }) => setPosition(song.sound, (nativeEvent.locationX / layoutBarTime.width) * time.duration)} - onLayout={({ nativeEvent }) => setLayoutBarTime({ width: nativeEvent.layout.width, height: nativeEvent.layout.height })} - style={{ flex: 1, height: 6 }} > - - - - + setPosition(song.sound, progress * time.duration)} + stylePress={{ flex: 1, height: 6 }} + styleBar={{ width: '100%', height: '100%', borderRadius: 3, backgroundColor: theme.primaryLight, overflow: 'hidden' }} + styleProgress={{ backgroundColor: theme.primaryTouch }} + /> {secondToTime(time.duration)} @@ -129,24 +113,21 @@ const BoxDesktopPlayer = ({ fullscreen, time }) => { onPress={() => song.sound.volume = 1} /> } - setVolume(nativeEvent.locationX / layoutBar.width)} - onPressOut={({ nativeEvent }) => setVolume(nativeEvent.locationX / layoutBar.width)} - onLayout={({ nativeEvent }) => setLayoutBar({ width: nativeEvent.layout.width, height: nativeEvent.layout.height })} - > - - - - - - fullscreen.set(true)} - /> + setVolume(song.sound ,progress)} + stylePress={{ maxWidth: 100, height: 25, paddingVertical: 10, width: '100%' }} + styleBar={{ width: '100%', height: '100%', borderRadius: 3, backgroundColor: theme.primaryLight, overflow: 'hidden' }} + styleProgress={{ backgroundColor: theme.primaryTouch }} + isBitogno={true} + /> + fullscreen.set(true)} + /> ) @@ -159,20 +140,6 @@ const styles = { marginRight: 10, borderRadius: 4, }, - boxPlayerText: { - }, - boxPlayerButton: { - flex: Platform.OS === 'android' ? 0 : 'initial', - flexDirection: 'row', - }, - bitognoBar: (vol, theme) => ({ - position: 'absolute', - width: 12, - height: 12, - borderRadius: 6, - backgroundColor: theme.primaryTouch, - left: `calc(${vol * 100}% - 6px)`, top: 7 - }) } export default BoxDesktopPlayer; \ No newline at end of file diff --git a/app/components/player/BoxPlayer.js b/app/components/player/BoxPlayer.js index 726cf2b..a7d6c74 100644 --- a/app/components/player/BoxPlayer.js +++ b/app/components/player/BoxPlayer.js @@ -2,13 +2,13 @@ import React from 'react'; import { Text, View, Image, TouchableOpacity, Platform } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import Icon from 'react-native-vector-icons/FontAwesome'; + import { SongContext } from '~/contexts/song'; import { nextSong, pauseSong, resumeSong } from '~/utils/player'; - import { ConfigContext } from '~/contexts/config'; import { ThemeContext } from '~/contexts/theme'; -import mainStyles from '~/styles/main'; import { urlCover } from '~/utils/api'; +import mainStyles from '~/styles/main'; import IconButton from '~/components/button/IconButton'; import ImageError from '~/components/ImageError'; diff --git a/app/components/player/FullScreenPlayer.js b/app/components/player/FullScreenPlayer.js index c567d3a..f04f7af 100644 --- a/app/components/player/FullScreenPlayer.js +++ b/app/components/player/FullScreenPlayer.js @@ -1,18 +1,19 @@ import React from 'react'; import { Text, View, Image, ScrollView, Dimensions, Pressable, Modal } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { SongContext } from '~/contexts/song'; -import { nextSong, previousSong, pauseSong, resumeSong } from '~/utils/player'; -import { ThemeContext } from '~/contexts/theme'; -import mainStyles from '~/styles/main'; import { ConfigContext } from '~/contexts/config'; +import { SongContext } from '~/contexts/song'; +import { ThemeContext } from '~/contexts/theme'; +import { nextSong, previousSong, pauseSong, resumeSong, secondToTime } from '~/utils/player'; +import { parseLrc } from '~/utils/lrc'; +import { setPosition } from '~/utils/player'; import { urlCover, getApi } from '~/utils/api'; -import SongsList from '~/components/lists/SongsList'; import FavoritedButton from '~/components/button/FavoritedButton'; import IconButton from '~/components/button/IconButton'; -import { setPosition } from '~/utils/player'; -import { parseLrc } from '~/utils/lrc'; +import SlideBar from '~/components/button/SlideBar'; +import SongsList from '~/components/lists/SongsList'; +import mainStyles from '~/styles/main'; const preview = { COVER: 0, @@ -21,24 +22,18 @@ const preview = { } const FullScreenPlayer = ({ fullscreen, time }) => { - const [song, songDispatch] = React.useContext(SongContext) const config = React.useContext(ConfigContext) - const insets = useSafeAreaInsets(); - const [isPreview, setIsPreview] = React.useState(preview.COVER) - const [layoutBar, setLayoutBar] = React.useState({ width: 0, height: 0 }) const theme = React.useContext(ThemeContext) + const [song, songDispatch] = React.useContext(SongContext) + const insets = useSafeAreaInsets(); const [lyrics, setLyrics] = React.useState([]) + const [isPreview, setIsPreview] = React.useState(preview.COVER) React.useEffect(() => { setIsPreview(preview.COVER) setLyrics([]) }, [song.songInfo]) - const secondToTime = (second) => { - return `${String((second - second % 60) / 60).padStart(2, '0')}:${String((second - second % 1) % 60).padStart(2, '0')}` - } - - const getNavidromeLyrics = () => { getApi(config, 'getLyricsBySongId', { id: song.songInfo.id }) .then(res => { @@ -147,18 +142,14 @@ const FullScreenPlayer = ({ fullscreen, time }) => { - setPosition(song.sound, (nativeEvent.locationX / layoutBar.width) * time.duration)} - onPressOut={({ nativeEvent }) => setPosition(song.sound, (nativeEvent.locationX / layoutBar.width) * time.duration)} - pressRetentionOffset={{ top: 20, left: 0, right: 0, bottom: 20 }} - onLayout={({ nativeEvent }) => setLayoutBar({ width: nativeEvent.layout.width, height: nativeEvent.layout.height })} - > - - - - - + setPosition(song.sound, progress * time.duration)} + stylePress={{ width: '100%', height: 26, paddingVertical: 10, marginTop: 10 }} + styleBar={{ width: '100%', height: '100%', borderRadius: 3, backgroundColor: theme.primaryLight, overflow: 'hidden' }} + styleProgress={{ backgroundColor: theme.primaryTouch }} + isBitogno={true} + /> {secondToTime(time.position)} @@ -247,14 +238,6 @@ const styles = { borderRadius: 10, } }, - bitognoBar: (time, theme) => ({ - position: 'absolute', - width: 6, - height: 12, - borderRadius: 6, - backgroundColor: theme.primaryTouch, - left: `calc(${(time.position / time.duration) * 100}% - 3px)`, top: 7 - }) } export default FullScreenPlayer; \ No newline at end of file diff --git a/app/components/player/Player.js b/app/components/player/Player.js index 64d64f2..b98dd4a 100644 --- a/app/components/player/Player.js +++ b/app/components/player/Player.js @@ -1,11 +1,9 @@ import React from 'react'; -import { Platform, View, Text } from 'react-native'; -import { SongContext } from '~/contexts/song'; -import { nextSong, handleAction, pauseSong, resumeSong, previousSong } from '~/utils/player'; -import { SettingsContext } from '~/contexts/settings'; import { ConfigContext } from '~/contexts/config'; -import { getApi } from '~/utils/api'; +import { SettingsContext } from '~/contexts/settings'; +import { SongContext } from '~/contexts/song'; +import { nextSong, handleAction, pauseSong, resumeSong, previousSong, setVolume } from '~/utils/player'; import BoxPlayer from './BoxPlayer'; import FullScreenPlayer from './FullScreenPlayer'; import BoxDesktopPlayer from './BoxDesktopPlayer'; @@ -38,14 +36,14 @@ const Player = ({ navigation, state, fullscreen }) => { } } else if (e.code === 'ArrowRight') nextSong(config, song, songDispatch) else if (e.code === 'ArrowLeft') previousSong(config, song, songDispatch) + else if (e.code === 'ArrowUp') setVolume(song.sound, song.sound.volume + 0.1) + else if (e.code === 'ArrowDown') setVolume(song.sound, song.sound.volume - 0.1) } if (!song?.songInfo) return null - if (fullscreen.value) return - else { - if (settings.isDesktop) return - return - } + else if (fullscreen.value) return + else if (settings.isDesktop) return + else return } export default Player; \ No newline at end of file diff --git a/app/screens/tabs/Settings.js b/app/screens/tabs/Settings.js index 475241e..6f4f615 100755 --- a/app/screens/tabs/Settings.js +++ b/app/screens/tabs/Settings.js @@ -6,8 +6,8 @@ import Icon from 'react-native-vector-icons/FontAwesome'; import { ConfigContext, SetConfigContext } from '~/contexts/config'; import { SetSettingsContext, defaultSettings, SettingsContext } from '~/contexts/settings'; -import mainStyles from '~/styles/main'; import { ThemeContext } from '~/contexts/theme'; +import mainStyles from '~/styles/main'; import settingStyles from '~/styles/settings'; import ButtonMenu from '~/components/settings/ButtonMenu'; import ButtonSwitch from '~/components/settings/ButtonSwitch'; diff --git a/app/utils/player.native.js b/app/utils/player.native.js index 1d398ed..85c14f2 100644 --- a/app/utils/player.native.js +++ b/app/utils/player.native.js @@ -87,4 +87,12 @@ export const resumeSong = async (sound) => { export const setPosition = async (sound, position) => { await sound.setPositionAsync(position * 1000) -} \ No newline at end of file +} + +export const setVolume = async (sound, volume) => { +} + +export const secondToTime = (second) => { + if (!second) return '00:00' + return `${String((second - second % 60) / 60).padStart(2, '0')}:${String((second - second % 1) % 60).padStart(2, '0')}` +} diff --git a/app/utils/player.web.js b/app/utils/player.web.js index d79adae..4396977 100644 --- a/app/utils/player.web.js +++ b/app/utils/player.web.js @@ -138,4 +138,15 @@ export const resumeSong = async (sound) => { export const setPosition = async (sound, position) => { sound.currentTime = position -} \ No newline at end of file +} + +export const setVolume = async (sound, volume) => { + if (volume > 1) volume = 1 + if (volume < 0) volume = 0 + if (sound) sound.volume = volume +} + +export const secondToTime = (second) => { + if (!second) return '00:00' + return `${String((second - second % 60) / 60).padStart(2, '0')}:${String((second - second % 1) % 60).padStart(2, '0')}` +} diff --git a/package-lock.json b/package-lock.json index 23a1167..999492c 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "castafiore", - "version": "2024.12.05", + "version": "2024.12.06", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "castafiore", - "version": "2024.12.05", + "version": "2024.12.06", "dependencies": { "@expo/webpack-config": "^19.0.0", "@react-native-async-storage/async-storage": "1.18.2", diff --git a/package.json b/package.json index 1041934..f9cd831 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "castafiore", - "version": "2024.12.05", + "version": "2024.12.06", "main": "node_modules/expo/AppEntry.js", "homepage": ".", "scripts": {