Skip to content

Commit

Permalink
Add: Slidebar & arrow volume
Browse files Browse the repository at this point in the history
  • Loading branch information
sawyerf committed Dec 6, 2024
1 parent 860b0be commit b991aee
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 115 deletions.
2 changes: 1 addition & 1 deletion app.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
52 changes: 52 additions & 0 deletions app/components/button/SlideBar.js
Original file line number Diff line number Diff line change
@@ -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 (
<Pressable
style={stylePress}
onPressIn={onPressHandler}
onPressOut={onPressHandler}
onLayout={({ nativeEvent }) => setLayoutBar({ width: nativeEvent.layout.width, height: nativeEvent.layout.height })}
pressRetentionOffset={{ top: 20, left: 0, right: 0, bottom: 20 }}
>
<View style={{ borderRadius: 3, backgroundColor: theme.primaryLight, overflow: 'hidden', ...styleBar }}>
<View style={{ width: `${progress * 100}%`, height: '100%', backgroundColor: theme.primaryTouch, ...styleProgress }} />
</View>
{isBitogno && <View style={styles.bitognoBar(progress, sizeBitogno, theme)} />}
</Pressable>
)
}

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;
87 changes: 27 additions & 60 deletions app/components/player/BoxDesktopPlayer.js
Original file line number Diff line number Diff line change
@@ -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 (
<View
Expand Down Expand Up @@ -99,15 +85,13 @@ const BoxDesktopPlayer = ({ fullscreen, time }) => {
</View>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 5, maxWidth: '100%' }}>
<Text style={{ color: theme.primaryLight, fontSize: 13 }}>{secondToTime(time.position)}</Text>
<Pressable
onPressIn={({ nativeEvent }) => 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 }} >
<View style={{ width: '100%', height: '100%', borderRadius: 3, backgroundColor: theme.primaryLight, overflow: 'hidden' }} >
<View style={{ width: `${(time.position / time.duration) * 100}%`, height: '100%', backgroundColor: theme.primaryTouch }} />
</View>
</Pressable>
<SlideBar
progress={time.position / time.duration}
onPress={(progress) => 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 }}
/>
<Text style={{ color: theme.primaryLight, fontSize: 13 }}>{secondToTime(time.duration)}</Text>
</View>
</View>
Expand All @@ -129,24 +113,21 @@ const BoxDesktopPlayer = ({ fullscreen, time }) => {
onPress={() => song.sound.volume = 1}
/>
}
<Pressable
style={{ maxWidth: 100, height: 25, paddingVertical: 10, width: '100%' }}
onPressIn={({ nativeEvent }) => setVolume(nativeEvent.locationX / layoutBar.width)}
onPressOut={({ nativeEvent }) => setVolume(nativeEvent.locationX / layoutBar.width)}
onLayout={({ nativeEvent }) => setLayoutBar({ width: nativeEvent.layout.width, height: nativeEvent.layout.height })}
>
<View style={{ width: '100%', height: '100%', borderRadius: 3, backgroundColor: theme.primaryLight, overflow: 'hidden' }} >
<View style={{ width: `${song.sound.volume * 100}%`, height: '100%', backgroundColor: theme.primaryTouch }} />
</View>
<View style={styles.bitognoBar(song.sound.volume, theme)} />
</Pressable>
<IconButton
icon="expand"
size={17}
style={{ padding: 5, paddingHorizontal: 8, marginStart: 15, borderRadius: 4 }}
color={theme.primaryLight}
onPress={() => fullscreen.set(true)}
/>
<SlideBar
progress={song.sound.volume}
onPress={(progress) => 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}
/>
<IconButton
icon="expand"
size={17}
style={{ padding: 5, paddingHorizontal: 8, marginStart: 15, borderRadius: 4 }}
color={theme.primaryLight}
onPress={() => fullscreen.set(true)}
/>
</View>
</View>
)
Expand All @@ -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;
4 changes: 2 additions & 2 deletions app/components/player/BoxPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
55 changes: 19 additions & 36 deletions app/components/player/FullScreenPlayer.js
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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 => {
Expand Down Expand Up @@ -147,18 +142,14 @@ const FullScreenPlayer = ({ fullscreen, time }) => {
</View>
<FavoritedButton id={song.songInfo.id} isFavorited={song.songInfo.starred} config={config} style={{ flex: 'initial', padding: 20, paddingEnd: 0 }} />
</View>
<Pressable
style={{ width: '100%', height: 26, paddingVertical: 10, marginTop: 10 }}
onPressIn={({ nativeEvent }) => 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 })}
>
<View style={{ width: '100%', height: '100%', borderRadius: 3, backgroundColor: theme.primaryLight, overflow: 'hidden' }} >
<View style={{ width: `${(time.position / time.duration) * 100}%`, height: '100%', backgroundColor: theme.primaryTouch }} />
</View>
<View style={styles.bitognoBar(time, theme)} />
</Pressable>
<SlideBar
progress={time.position / time.duration}
onPress={(progress) => 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}
/>

<View style={{ flexDirection: 'row', width: '100%', justifyContent: 'space-between' }}>
<Text style={{ color: theme.primaryLight, fontSize: 13 }}>{secondToTime(time.position)}</Text>
Expand Down Expand Up @@ -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;
18 changes: 8 additions & 10 deletions app/components/player/Player.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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 <FullScreenPlayer fullscreen={fullscreen} time={time ? time : song} />
else {
if (settings.isDesktop) return <BoxDesktopPlayer fullscreen={fullscreen} time={time ? time : song} />
return <BoxPlayer fullscreen={fullscreen} />
}
else if (fullscreen.value) return <FullScreenPlayer fullscreen={fullscreen} time={time ? time : song} />
else if (settings.isDesktop) return <BoxDesktopPlayer fullscreen={fullscreen} time={time ? time : song} />
else return <BoxPlayer fullscreen={fullscreen} />
}

export default Player;
2 changes: 1 addition & 1 deletion app/screens/tabs/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
10 changes: 9 additions & 1 deletion app/utils/player.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,12 @@ export const resumeSong = async (sound) => {

export const setPosition = async (sound, position) => {
await sound.setPositionAsync(position * 1000)
}
}

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')}`
}
13 changes: 12 additions & 1 deletion app/utils/player.web.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,4 +138,15 @@ export const resumeSong = async (sound) => {

export const setPosition = async (sound, position) => {
sound.currentTime = position
}
}

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')}`
}
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit b991aee

Please sign in to comment.