Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions App.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { NavigationContainer } from '@react-navigation/native'
import { initialWindowMetrics, SafeAreaProvider } from 'react-native-safe-area-context'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { useTranslation } from 'react-i18next'
import * as Linking from 'expo-linking'

import { HomeStack, SearchStack, PlaylistsStack, SettingsStack } from '~/screens/Stacks'
import TabBar from '~/components/bar/TabBar'
Expand Down Expand Up @@ -94,6 +95,10 @@ const App = () => {
<UpdateApiContext.Provider value={updateApi}>
<SafeAreaProvider initialMetrics={initialWindowMetrics}>
<NavigationContainer
linking={{
enabled: 'auto',
prefixes: [ Linking.createURL('/') ]
}}
documentTitle={{
formatter: () => {
return `Castafiore`
Expand Down
25 changes: 25 additions & 0 deletions app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module.exports = ({ config }) => {
expo: {
name: "Castafiore",
slug: "Castafiore",
scheme: "castafiore",
description: "Castafiore is a music player that support Navidrome and Subsonic API.",
version: config.version,
orientation: "default",
Expand Down Expand Up @@ -69,6 +70,30 @@ module.exports = ({ config }) => {
],
[
'./plugins/asyncStorage.js'
],
[
"react-native-android-widget",
{
fonts: [
'./node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf'
],
widgets: [
{
name: "Favorited",
targetCellWidth: 2,
targetCellHeight: 2,
minWidth: '110dp',
minHeight: '110dp'
},
{
name: "Player",
targetCellWidth: 4,
targetCellHeight: 2,
minWidth: '220dp',
minHeight: '110dp',
}
]
}
]
]
}
Expand Down
2 changes: 1 addition & 1 deletion app/components/PresHeaderIcon.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react'
import { Text, View, StyleSheet } from 'react-native'
import Icon from 'react-native-vector-icons/FontAwesome'

import { ThemeContext } from '~/contexts/theme'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import BackButton from '~/components/button/BackButton'
import Icon from 'react-native-vector-icons/FontAwesome'
import IconButton from '~/components/button/IconButton'
import presStyles from '~/styles/pres'

Expand Down
3 changes: 2 additions & 1 deletion app/components/settings/ButtonMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import settingStyles from '~/styles/settings';
import size from '~/styles/size';
import mainStyles from '~/styles/main';

const ButtonMenu = ({ title, onPress, icon, endText = "", isLast = false }) => {
const ButtonMenu = ({ title, onPress, onLongPress = () => {}, icon, endText = "", isLast = false }) => {
const theme = React.useContext(ThemeContext)

return (
<Pressable
style={({ pressed }) => ([mainStyles.opacity({ pressed }), settingStyles.optionItem(theme, isLast)])}
onPress={onPress}
onLongPress={onLongPress}
>
<View
style={{
Expand Down
86 changes: 86 additions & 0 deletions app/screens/Settings/TestWidget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from 'react'
import { View, Text, ScrollView } from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTranslation } from 'react-i18next'

import { ThemeContext } from '~/contexts/theme'
import Header from '~/components/Header'
import mainStyles from '~/styles/main'
import SelectItem from '~/components/settings/SelectItem'
import settingStyles from '~/styles/settings'
import { nameToWidget } from '~/widgets/widget-task-handler'
import { WidgetPreview } from 'react-native-android-widget'
import TrackPlayer from 'react-native-track-player'

const PlayerSettings = () => {
const { t } = useTranslation()
const insets = useSafeAreaInsets()
const theme = React.useContext(ThemeContext)
const [widget, setWidget] = React.useState('Favorited')
const [props, setProps] = React.useState({
width: 200,
height: 200,
})

React.useEffect(() => {
if (widget === 'Player') {
TrackPlayer.getActiveTrack()
.then((track) => {
setProps({
width: 320,
height: 200,
coverUrl: track.artwork,
isPlaying: true,
})
})

} else if (widget === 'Favorited') {
setProps({
width: 200,
height: 200,
})
}
}, [widget])

return (
<ScrollView
style={mainStyles.mainContainer(theme)}
contentContainerStyle={mainStyles.contentMainContainer(insets)}
>
<Header title={t("Widgets")} />

<View style={settingStyles.contentMainContainer}>
<View style={settingStyles.optionsContainer(theme)}>
{Object.keys(nameToWidget).map((item, index) => {
return (
<SelectItem
key={index}
text={item}
isSelect={widget === item}
onPress={() => {
setWidget(item)
}}
/>
)
})}
</View>
</View>
<View style={{
width: '100%',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
}}
>
<WidgetPreview
renderWidget={() => nameToWidget[widget](props)}
width={props.width}
height={props.height}
/>
</View>

</ScrollView>
)
}

export default PlayerSettings
2 changes: 2 additions & 0 deletions app/screens/Stacks.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import PlayerSettings from '~/screens/Settings/Player'
import PlaylistsSettings from '~/screens/Settings/Playlists'
import SharesSettings from '~/screens/Settings/Shares'
import ThemeSettings from '~/screens/Settings/Theme'
import TestWidget from '~/screens/Settings/TestWidget'

import { ThemeContext } from '~/contexts/theme'

Expand Down Expand Up @@ -166,6 +167,7 @@ export const SettingsStack = () => {
<Stack.Screen name="Settings/Shares" component={SharesSettings} />
<Stack.Screen name="Settings/Language" component={LanguageSettings} />
<Stack.Screen name="Settings/Logs" component={LogsSettings} />
<Stack.Screen name="Settings/TestWidget" component={TestWidget} />
</Stack.Navigator>
)
}
49 changes: 24 additions & 25 deletions app/screens/tabs/Home.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import React from 'react';
import { Text, View, ScrollView, Animated, StyleSheet, Pressable, Platform } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTranslation } from 'react-i18next';
import React from 'react'
import { Text, View, ScrollView, Animated, StyleSheet, Pressable, Platform } from 'react-native'
import { useNavigation } from '@react-navigation/native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { useTranslation } from 'react-i18next'

import { ConfigContext } from '~/contexts/config';
import { getApi, getApiNetworkFirst } from '~/utils/api';
import { playSong } from '~/utils/player';
import { SettingsContext } from '~/contexts/settings';
import { SongDispatchContext } from '~/contexts/song';
import { ThemeContext } from '~/contexts/theme';
import HorizontalList from '~/components/lists/HorizontalList';
import IconButton from '~/components/button/IconButton';
import mainStyles from '~/styles/main';
import size from '~/styles/size';
import { ConfigContext } from '~/contexts/config'
import { getApi, getApiNetworkFirst } from '~/utils/api'
import { playSong } from '~/utils/player'
import { SettingsContext } from '~/contexts/settings'
import { SongDispatchContext } from '~/contexts/song'
import { ThemeContext } from '~/contexts/theme'
import HorizontalList from '~/components/lists/HorizontalList'
import IconButton from '~/components/button/IconButton'
import mainStyles from '~/styles/main'
import size from '~/styles/size'

const Home = () => {
const { t } = useTranslation();
const navigation = useNavigation();
const insets = useSafeAreaInsets();
const { t } = useTranslation()
const navigation = useNavigation()
const insets = useSafeAreaInsets()
const songDispatch = React.useContext(SongDispatchContext)
const config = React.useContext(ConfigContext)
const settings = React.useContext(SettingsContext)
const theme = React.useContext(ThemeContext)
const [statusRefresh, setStatusRefresh] = React.useState();
const [refresh, setRefresh] = React.useState(0);
const rotationValue = React.useRef(new Animated.Value(0)).current;
const [statusRefresh, setStatusRefresh] = React.useState()
const [refresh, setRefresh] = React.useState(0)
const rotationValue = React.useRef(new Animated.Value(0)).current
const rotation = rotationValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
Expand Down Expand Up @@ -97,8 +97,7 @@ const Home = () => {
/> : null
}
{statusRefresh ?
<Pressable onPress={forceRefresh} style={mainStyles.opacity}
>
<Pressable onPress={forceRefresh} style={mainStyles.opacity}>
<Text style={mainStyles.subTitle(theme)}>
{statusRefresh.count}°
</Text>
Expand All @@ -121,7 +120,7 @@ const Home = () => {
<HorizontalList key={index} refresh={refresh}{...value} />
)}
</ScrollView>
);
)
}

const styles = StyleSheet.create({
Expand All @@ -140,4 +139,4 @@ const styles = StyleSheet.create({
}),
})

export default Home;
export default Home
1 change: 1 addition & 0 deletions app/screens/tabs/Settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ const Settings = ({ navigation }) => {
title="Github"
icon="github"
onPress={() => Linking.openURL('https://github.com/sawyerf/Castafiore')}
onLongPress={() => navigation.navigate('Settings/TestWidget')}
isLast
/>
</View>
Expand Down
34 changes: 34 additions & 0 deletions app/widgets/FavoritedWidget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'
import { FlexWidget, IconWidget } from 'react-native-android-widget'
import * as Linking from 'expo-linking'


const heart = '\uF004'

const FavoritedWidget = () => {
return (
<FlexWidget
style={{
height: 'match_parent',
width: 'match_parent',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#c68588',
borderRadius: 16,
}}
clickAction='OPEN_URI'
clickActionData={{ uri: Linking.createURL('PlaylistsStack/Favorited') }}
>
<IconWidget
font="FontAwesome"
icon={heart}
size={64}
style={{
color: "#cd1921"
}}
/>
</FlexWidget>
)
}

export default FavoritedWidget
75 changes: 75 additions & 0 deletions app/widgets/PlayerWidget.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { FlexWidget, IconWidget, ImageWidget, OverlapWidget } from 'react-native-android-widget'

const playIcon = '\uF04B'
const pauseIcon = '\uF04C'
const nextIcon = '\uF051'
const prevIcon = '\uF048'

const PlayerWidget = ({ height, width, coverUrl, isPlaying }) => {
return (
<OverlapWidget>
<FlexWidget
style={{
width: width > height ? width : height,
height: width > height ? width : height,
marginTop: width > height ? (height - width) / 2 : 0,
marginLeft: width > height ? 0 : (width - height) / 2,
overflow: 'hidden',
}}
>
<ImageWidget
image={coverUrl}
style={{
resizeMode: 'cover',
}}
imageWidth={width > height ? width : height}
imageHeight={width > height ? width : height}
/>
</FlexWidget>
<FlexWidget
style={{
width: width,
height: height,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'rgba(0,0,0,0.4)',
}}
>
<IconWidget
font="FontAwesome"
icon={prevIcon}
size={30}
clickAction={'PREV_ACTION'}
style={{
color: "white",
padding: 8,
}}
/>
<IconWidget
font="FontAwesome"
icon={isPlaying ? pauseIcon : playIcon}
size={45}
clickAction={isPlaying ? 'PAUSE_ACTION' : 'PLAY_ACTION' }
style={{
color: "white",
padding: 8,
marginHorizontal: 20,
}}
/>
<IconWidget
font="FontAwesome"
icon={nextIcon}
size={30}
clickAction={'NEXT_ACTION'}
style={{
color: "white",
padding: 8,
}}
/>
</FlexWidget>
</OverlapWidget >
)
}

export default PlayerWidget
Loading