Fancy new buttons and more consistent colors

This commit is contained in:
Lei Nelissen
2021-02-11 23:43:21 +01:00
parent 42eb7a169b
commit 7adc96ba12
34 changed files with 15540 additions and 452 deletions

View File

@@ -8,7 +8,7 @@ module.exports = {
'plugin:react/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:react-hooks/recommended',
// "plugin:@typescript-eslint/recommended"
// 'plugin:@typescript-eslint/recommended'
],
globals: {
Atomics: 'readonly',
@@ -28,14 +28,15 @@ module.exports = {
'react-hooks'
],
rules: {
indent: [
indent: 'off',
'@typescript-eslint/indent': [
'error',
4,
{
SwitchCase: 1,
}
],
"linebreak-style": [
'linebreak-style': [
'error',
'unix'
],
@@ -47,10 +48,10 @@ module.exports = {
'error',
'always'
],
"no-unused-vars": "off",
"react/prop-types": "off",
"@typescript-eslint/no-unused-vars": [
"error"
'no-unused-vars': 'off',
'react/prop-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error'
]
}
};

View File

@@ -245,15 +245,13 @@ PODS:
- React-cxxreact (= 0.63.4)
- React-jsi (= 0.63.4)
- React-jsinspector (0.63.4)
- react-native-appearance (0.3.4):
- React
- react-native-safe-area-context (3.1.9):
- React-Core
- react-native-slider (3.0.3):
- React
- react-native-track-player (1.2.3):
- React
- react-native-webview (11.2.1):
- react-native-webview (11.2.3):
- React-Core
- React-RCTActionSheet (0.63.4):
- React-Core/RCTActionSheetHeaders (= 0.63.4)
@@ -325,7 +323,7 @@ PODS:
- React-Core
- SDWebImage (~> 5.8)
- SDWebImageWebPCoder (~> 0.6.1)
- RNGestureHandler (1.9.0):
- RNGestureHandler (1.10.0):
- React-Core
- RNLocalize (2.0.1):
- React-Core
@@ -388,7 +386,6 @@ DEPENDENCIES:
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- react-native-appearance (from `../node_modules/react-native-appearance`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-track-player (from `../node_modules/react-native-track-player`)
@@ -465,8 +462,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
React-jsinspector:
:path: "../node_modules/react-native/ReactCommon/jsinspector"
react-native-appearance:
:path: "../node_modules/react-native-appearance"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-slider:
@@ -546,11 +541,10 @@ SPEC CHECKSUMS:
React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31
React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949
React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a
react-native-appearance: fc2014516054585d531e07aa0b40ab0de1d2be85
react-native-safe-area-context: 86612d2c9a9e94e288319262d10b5f93f0b395f5
react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93
react-native-track-player: ba2416753b58f3cdf9db5a07daa65876d659f925
react-native-webview: dbe6c1ad149740f0e2d84a963f1d3c3e77f2d99c
react-native-safe-area-context: b6e0e284002381d2ff29fa4fff42b4d8282e3c94
react-native-slider: e99fc201cefe81270fc9d81714a7a0f5e566b168
react-native-track-player: 6ff21d21eb70ecbd2a6bad29822ecf6c8609a3aa
react-native-webview: 6520e3e7b4933de76b95ef542c8d7115cf45b68e
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0
@@ -561,12 +555,12 @@ SPEC CHECKSUMS:
React-RCTText: 5c51df3f08cb9dedc6e790161195d12bac06101c
React-RCTVibration: ae4f914cfe8de7d4de95ae1ea6cc8f6315d73d9d
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
RNCPicker: 914b557e20b3b8317b084aca9ff4b4edb95f61e4
RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff
RNGestureHandler: 9b7e605a741412e20e13c512738a31bd1611759b
RNLocalize: dcf0fdb332b37b0d24178e876a7ce4dbbc9c838d
RNGestureHandler: 03f587ba19a7e2d9fe48fb9aa6e33ace5fd07dd5
RNLocalize: 41026b7c14878f1a1b381bc79f668f1fbf841adb
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
RNScreens: b6c9607e6fe47c1b6e2f1910d2acd46dd7ecea3a
RNSentry: 6aeba1adc242fd22a6826acae92f430697b47a9c

15216
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,9 +28,8 @@
"fuse.js": "^6.4.6",
"i18n-js": "^3.8.0",
"lodash": "^4.17.20",
"react": "^17.0.1",
"react": "^16.13.1",
"react-native": "^0.63.4",
"react-native-appearance": "^0.3.4",
"react-native-dotenv": "^2.5.1",
"react-native-fast-image": "^8.3.4",
"react-native-gesture-handler": "^1.9.0",
@@ -57,6 +56,7 @@
"@types/react-native": "^0.63.48",
"@types/react-redux": "^7.1.16",
"@types/react-test-renderer": "^17.0.0",
"@types/styled-components-react-native": "^5.1.1",
"@typescript-eslint/eslint-plugin": "^4.14.2",
"@typescript-eslint/parser": "^4.14.2",
"babel-jest": "^26.6.3",

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98.39 84.08"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Regular-M"><path d="M3.66,0A3.62,3.62,0,0,0,0,3.66,3.67,3.67,0,0,0,3.66,7.37h91a3.75,3.75,0,0,0,3.76-3.71A3.71,3.71,0,0,0,94.63,0Zm0,25.54A3.67,3.67,0,0,0,0,29.25a3.62,3.62,0,0,0,3.66,3.66h91a3.71,3.71,0,0,0,3.76-3.66,3.75,3.75,0,0,0-3.76-3.71ZM.05,50.68v11c0,6.93,4.88,11.18,11.77,11.18h14.4V78c0,4,3.76,5.47,6.84,3l13.23-10.6a3.71,3.71,0,0,0,0-6L33.06,53.86c-3-2.35-6.84-.88-6.84,3v4.88H12.89A1.51,1.51,0,0,1,11.23,60V50.68c0-4-2.05-6.44-5.61-6.44S.05,46.73.05,50.68Zm61.18.44a3.69,3.69,0,0,0,0,7.38h33.4a3.69,3.69,0,1,0,0-7.38Zm0,25.59a3.69,3.69,0,0,0,0,7.37h33.4a3.69,3.69,0,1,0,0-7.37Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 758 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98.39 84.08"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Regular-M"><path d="M61.23,7.37h33.4a3.72,3.72,0,0,0,3.76-3.71A3.71,3.71,0,0,0,94.63,0H61.23a3.62,3.62,0,0,0-3.66,3.66A3.64,3.64,0,0,0,61.23,7.37ZM.05,33.4c0,4,2,6.44,5.57,6.44s5.61-2.49,5.61-6.44V24.07a1.51,1.51,0,0,1,1.66-1.71H26.22v4.89c0,3.85,3.86,5.32,6.84,3L46.29,19.68a3.71,3.71,0,0,0,0-6L33.06,3.12c-3.08-2.49-6.84-1-6.84,3v5.08H11.82C4.93,11.18.05,15.43.05,22.36ZM61.23,33h33.4a3.69,3.69,0,1,0,0-7.37H61.23a3.69,3.69,0,0,0,0,7.37ZM3.66,58.54h91a3.75,3.75,0,0,0,3.76-3.71,3.71,3.71,0,0,0-3.76-3.66h-91A3.62,3.62,0,0,0,0,54.83,3.67,3.67,0,0,0,3.66,58.54Zm0,25.54h91a3.71,3.71,0,0,0,3.76-3.66,3.75,3.75,0,0,0-3.76-3.71h-91A3.67,3.67,0,0,0,0,80.42,3.62,3.62,0,0,0,3.66,84.08Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 848 B

1
src/assets/queue.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98.39 74.76"><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><g id="Regular-M"><path d="M4,15.17,12.65,10a2.45,2.45,0,0,0,0-4.11L4,.62C2-.6,0,0,0,2.33V13.46C0,15.71,2.05,16.29,4,15.17ZM31.3,11.75H94.43a3.93,3.93,0,1,0,0-7.86H31.3a3.88,3.88,0,0,0-4,4A3.87,3.87,0,0,0,31.3,11.75ZM4,44.61l8.69-5.17a2.44,2.44,0,0,0,0-4.1L4,30.06c-2-1.17-4-.58-4,1.71V42.91C0,45.15,2.05,45.74,4,44.61ZM31.3,41.29H94.43a3.93,3.93,0,1,0,0-7.86H31.3a3.93,3.93,0,1,0,0,7.86ZM4,74.2,12.65,69a2.44,2.44,0,0,0,0-4.1L4,59.65C2,58.48,0,59,0,61.36V72.5C0,74.74,2.05,75.33,4,74.2ZM31.3,70.79H94.43a3.93,3.93,0,1,0,0-7.86H31.3a3.93,3.93,0,1,0,0,7.86Z"/></g></g></g></svg>

After

Width:  |  Height:  |  Size: 717 B

View File

@@ -9,10 +9,10 @@ import {
TapGestureHandlerGestureEvent
} from 'react-native-gesture-handler';
interface LetterContainerProps {
onPress: (letter: string) => void;
letter: string;
}
// interface LetterContainerProps {
// onPress: (letter: string) => void;
// letter: string;
// }
const Container = styled.View`
position: absolute;

View File

@@ -1,8 +1,7 @@
import React, { Component } from 'react';
import React, { useEffect } from 'react';
import { Provider } from 'react-redux';
import TrackPlayer from 'react-native-track-player';
import { PersistGate } from 'redux-persist/integration/react';
import { AppearanceProvider, Appearance, AppearanceListener } from 'react-native-appearance';
import Routes from '../screens';
import store, { persistedStore } from 'store';
import {
@@ -10,60 +9,36 @@ import {
DefaultTheme,
DarkTheme,
} from '@react-navigation/native';
import { useColorScheme } from 'react-native';
interface State {
isReady: boolean;
colorScheme?: string;
}
export default function App(): JSX.Element {
const colorScheme = useColorScheme();
// const colorScheme = 'dark';
export default class App extends Component<{}, State> {
state: State = {
isReady: false,
};
subscription = null;
async componentDidMount() {
await TrackPlayer.setupPlayer();
await TrackPlayer.updateOptions({
capabilities: [
TrackPlayer.CAPABILITY_PLAY,
TrackPlayer.CAPABILITY_PAUSE,
TrackPlayer.CAPABILITY_SKIP_TO_NEXT,
TrackPlayer.CAPABILITY_SKIP_TO_PREVIOUS,
TrackPlayer.CAPABILITY_STOP,
TrackPlayer.CAPABILITY_SEEK_TO,
]
});
this.subscription = Appearance.addChangeListener(this.setScheme);
this.setState({ isReady: true, colorScheme: Appearance.getColorScheme() });
}
componentWillUnmount() {
this.subscription?.remove();
}
setScheme: AppearanceListener = ({ colorScheme }) => {
this.setState({ colorScheme });
}
render() {
const { isReady, colorScheme } = this.state;
if (!isReady) {
return null;
useEffect(() => {
async function setupTrackPlayer() {
await TrackPlayer.setupPlayer();
await TrackPlayer.updateOptions({
capabilities: [
TrackPlayer.CAPABILITY_PLAY,
TrackPlayer.CAPABILITY_PAUSE,
TrackPlayer.CAPABILITY_SKIP_TO_NEXT,
TrackPlayer.CAPABILITY_SKIP_TO_PREVIOUS,
TrackPlayer.CAPABILITY_STOP,
TrackPlayer.CAPABILITY_SEEK_TO,
]
});
}
setupTrackPlayer();
}, []);
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistedStore}>
<AppearanceProvider>
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Routes />
</NavigationContainer>
</AppearanceProvider>
</PersistGate>
</Provider>
);
}
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistedStore}>
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<Routes />
</NavigationContainer>
</PersistGate>
</Provider>
);
}

69
src/components/Button.tsx Normal file
View File

@@ -0,0 +1,69 @@
import React, { useCallback, useState } from 'react';
import { SvgProps } from 'react-native-svg';
import {
PressableProps,
} from 'react-native';
import { THEME_COLOR } from 'CONSTANTS';
import styled, { css } from 'styled-components/native';
import useDefaultStyles from './Colors';
interface ButtonProps extends PressableProps {
icon?: React.FC<SvgProps>;
title: string;
}
interface PressableStyleProps {
active: boolean;
}
const BaseButton = styled.Pressable<PressableStyleProps>`
padding: 16px;
border-radius: 8px;
flex-direction: row;
align-items: center;
justify-content: center;
flex-grow: 1;
${props => props.active && css`
background-color: ${THEME_COLOR};
`}
`;
const ButtonText = styled.Text<{ active?: boolean }>`
color: ${THEME_COLOR};
font-weight: 600;
${props => props.active && css`
color: white;
`}
`;
export default function Button(props: ButtonProps) {
const { icon: Icon, title, ...rest } = props;
const defaultStyles = useDefaultStyles();
const [isPressed, setPressed] = useState(false);
const handlePressIn = useCallback(() => setPressed(true), []);
const handlePressOut = useCallback(() => setPressed(false), []);
return (
<BaseButton
{...rest}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
active={isPressed}
style={[ defaultStyles.button, props.style ]}
>
{Icon &&
<Icon
width={12}
height={12}
fill={isPressed ? '#fff' : THEME_COLOR}
style={{
marginRight: 8,
}}
/>
}
<ButtonText active={isPressed}>{title}</ButtonText>
</BaseButton>
);
}

View File

@@ -1,26 +0,0 @@
import { StyleSheet, PlatformColor } from 'react-native';
import { THEME_COLOR } from 'CONSTANTS';
export const colors = StyleSheet.create({
text: {
color: PlatformColor('?attr/colorOnBackground'),
},
view: {
backgroundColor: PlatformColor('?android:colorBackground'),
},
border: {
borderColor: '#88888844'
},
activeBackground: {
backgroundColor: `${THEME_COLOR}44`,
},
imageBackground: {
backgroundColor: PlatformColor('?attr/colorBackgroundFloating'),
},
modal: {
backgroundColor: PlatformColor('?attr/colorBackgroundFloating'),
},
input: {
backgroundColor: PlatformColor('?attr/colorBackgroundFloating'),
}
});

View File

@@ -1,27 +0,0 @@
import { StyleSheet, PlatformColor, DynamicColorIOS } from 'react-native';
import { THEME_COLOR } from 'CONSTANTS';
export const colors = StyleSheet.create({
text: {
color: PlatformColor('label'),
},
view: {
backgroundColor: PlatformColor('systemBackground'),
},
border: {
borderColor: PlatformColor('systemGray5Color'),
},
activeBackground: {
backgroundColor: DynamicColorIOS({ light: `${THEME_COLOR}16`, dark: `${THEME_COLOR}66` })
},
imageBackground: {
backgroundColor: PlatformColor('systemGray5Color')
},
modal: {
backgroundColor: DynamicColorIOS({ light: '#eeeeeeee', dark: '#222222ee' })
},
input: {
backgroundColor: PlatformColor('systemGray5Color'),
color: PlatformColor('label'),
}
});

55
src/components/Colors.ts Normal file
View File

@@ -0,0 +1,55 @@
import { THEME_COLOR } from 'CONSTANTS';
import { StyleSheet, useColorScheme } from 'react-native';
export default function useDefaultStyles() {
const scheme = useColorScheme();
// const scheme = 'dark';
return StyleSheet.create({
text: {
color: scheme === 'dark' ? '#fff' : '#000',
},
textHalfOpacity: {
color: scheme === 'dark' ? '#ffffff88' : '#00000088',
},
view: {
backgroundColor: scheme === 'dark' ? '#111' : '#eee',
},
border: {
borderColor: scheme === 'dark' ? '#262626' : '#ddd',
},
activeBackground: {
backgroundColor: `${THEME_COLOR}${scheme === 'dark' ? '66' : '16'}`,
},
imageBackground: {
backgroundColor: scheme === 'dark' ? '#333' : '#ddd',
},
modal: {
backgroundColor: scheme === 'dark' ? '#222222ee' : '#eeeeeeee',
},
modalInner: {
backgroundColor: scheme === 'dark' ? '#000' : '#fff',
},
button: {
backgroundColor: scheme === 'dark' ? '#161616' : '#e6e6e6',
},
input: {
backgroundColor: scheme === 'dark' ? '#161616' : '#e6e6e6',
color: scheme === 'dark' ? '#fff' : '#000',
},
sectionHeading: {
backgroundColor: scheme === 'dark' ? '#111' : '#eee',
borderColor: scheme === 'dark' ? '#333' : '#ddd',
}
});
}
interface DefaultStylesProviderProps {
children: (defaultStyles: ReturnType<typeof useDefaultStyles>) => JSX.Element;
}
export function DefaultStylesProvider(props: DefaultStylesProviderProps) {
const defaultStyles = useDefaultStyles();
return props.children(defaultStyles);
}

View File

@@ -2,7 +2,7 @@ import styled from 'styled-components/native';
const Input = styled.TextInput`
margin: 10px 0;
border-radius: 5px;
border-radius: 8px;
padding: 15px;
`;

View File

@@ -1,24 +1,48 @@
import React from 'react';
import { TouchableOpacityProps, Text } from 'react-native';
import React, { useCallback, useState } from 'react';
import { TouchableOpacityProps } from 'react-native';
import ChevronRight from 'assets/chevron-right.svg';
import styled from 'styled-components/native';
import styled, { css } from 'styled-components/native';
import { THEME_COLOR } from 'CONSTANTS';
import { colors } from './Colors';
import useDefaultStyles from './Colors';
const BUTTON_SIZE = 14;
const Container = styled.TouchableOpacity`
padding: 18px 0;
const Container = styled.Pressable<{ active?: boolean }>`
padding: 18px 20px;
border-bottom-width: 1px;
flex-direction: row;
justify-content: space-between;
${props => props.active && css`
background-color: ${THEME_COLOR};
`}
`;
const Label = styled.Text<{ active?: boolean }>`
color: ${THEME_COLOR};
font-size: 16px;
${props => props.active && css`
color: white;
`}
`;
const ListButton: React.FC<TouchableOpacityProps> = ({ children, ...props }) => {
const defaultStyles = useDefaultStyles();
const [isPressed, setPressed] = useState(false);
const handlePressIn = useCallback(() => setPressed(true), []);
const handlePressOut = useCallback(() => setPressed(false), []);
return (
<Container {...props} style={colors.border}>
<Text style={{ color: THEME_COLOR, fontSize: 16 }}>{children}</Text>
<ChevronRight width={BUTTON_SIZE} height={BUTTON_SIZE} fill={THEME_COLOR} />
<Container
{...props}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
style={defaultStyles.border}
active={isPressed}
>
<Label active={isPressed}>{children}</Label>
<ChevronRight width={BUTTON_SIZE} height={BUTTON_SIZE} fill={isPressed ? '#fff' : THEME_COLOR} />
</Container>
);
};

View File

@@ -1,8 +1,8 @@
import React, { useCallback } from 'react';
import styled, { css } from 'styled-components/native';
import { SafeAreaView, Pressable } from 'react-native';
import { colors } from './Colors';
import { useNavigation, StackActions } from '@react-navigation/native';
import useDefaultStyles from './Colors';
interface Props {
fullSize?: boolean;
@@ -24,15 +24,16 @@ const Container = styled(Pressable)<Pick<Props, 'fullSize'>>`
`;
const Modal: React.FC<Props> = ({ children, fullSize = true }) => {
const defaultStyles = useDefaultStyles();
const navigation = useNavigation();
const closeModal = useCallback(() => {
navigation.dispatch(StackActions.popToTop());
}, [navigation]);
return (
<Background style={colors.modal} onPress={closeModal}>
<Background style={defaultStyles.modal} onPress={closeModal}>
<SafeAreaView style={{ flex: 1 }}>
<Container style={colors.view} fullSize={fullSize}>
<Container style={defaultStyles.modalInner} fullSize={fullSize}>
{children}
</Container>
</SafeAreaView>

11
src/components/Text.tsx Normal file
View File

@@ -0,0 +1,11 @@
import React, { PropsWithChildren } from 'react';
import { Text as BaseText, TextProps } from 'react-native';
import useDefaultStyles from './Colors';
export default function Text(props: PropsWithChildren<TextProps>) {
const defaultStyles = useDefaultStyles();
return (
<BaseText {...props} style={[defaultStyles.text, props.style]} />
);
}

View File

@@ -1,12 +1,13 @@
import styled from 'styled-components/native';
import Text from './Text';
export const Header = styled.Text`
export const Header = styled(Text)`
margin: 24px 0 12px 0;
font-size: 36px;
font-weight: bold;
`;
export const SubHeader = styled.Text`
export const SubHeader = styled(Text)`
font-size: 24px;
margin: 12px 0;
`;

View File

@@ -1,26 +1,26 @@
export type LocaleKeys = 'play-next'
| 'play-album'
| 'queue'
| 'add-to-queue'
| 'clear-queue'
| 'no-results'
| 'album'
| 'albums'
| 'all-albums'
| 'search'
| 'music'
| 'now-playing'
| 'onboarding-welcome'
| 'onboarding-intro'
| 'onboarding-cta'
| 'set-jellyfin-server'
| 'set-jellyfin-server-instruction'
| 'settings'
| 'jellyfin-library'
| 'jellyfin-server-url'
| 'jellyfin-access-token'
| 'jellyfin-user-id'
| 'setting-cache'
| 'setting-cache-description'
| 'reset-cache'
| 'recent-albums'
| 'play-album'
| 'queue'
| 'add-to-queue'
| 'clear-queue'
| 'no-results'
| 'album'
| 'albums'
| 'all-albums'
| 'search'
| 'music'
| 'now-playing'
| 'onboarding-welcome'
| 'onboarding-intro'
| 'onboarding-cta'
| 'set-jellyfin-server'
| 'set-jellyfin-server-instruction'
| 'settings'
| 'jellyfin-library'
| 'jellyfin-server-url'
| 'jellyfin-access-token'
| 'jellyfin-user-id'
| 'setting-cache'
| 'setting-cache-description'
| 'reset-cache'
| 'recent-albums'

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect } from 'react';
import { StackParams } from '../types';
import { Text, ScrollView, Dimensions, Button, RefreshControl, StyleSheet } from 'react-native';
import { Text, ScrollView, Dimensions, RefreshControl, StyleSheet, View } from 'react-native';
import { useGetImage } from 'utility/JellyfinApi';
import styled, { css } from 'styled-components/native';
import { useRoute, RouteProp, useNavigation } from '@react-navigation/native';
@@ -13,9 +13,11 @@ import { ALBUM_CACHE_AMOUNT_OF_DAYS, THEME_COLOR } from 'CONSTANTS';
import usePlayAlbum from 'utility/usePlayAlbum';
import TouchableHandler from 'components/TouchableHandler';
import useCurrentTrack from 'utility/useCurrentTrack';
import { colors } from 'components/Colors';
import TrackPlayer from 'react-native-track-player';
import { t } from '@localisation';
import Button from 'components/Button';
import Play from 'assets/play.svg';
import useDefaultStyles from 'components/Colors';
type Route = RouteProp<StackParams, 'Album'>;
@@ -23,18 +25,15 @@ const Screen = Dimensions.get('screen');
const styles = StyleSheet.create({
name: {
...colors.text,
fontSize: 36,
fontWeight: 'bold'
},
artist: {
...colors.text,
fontSize: 24,
opacity: 0.5,
marginBottom: 24
},
index: {
...colors.text,
width: 20,
opacity: 0.5,
marginRight: 5
@@ -61,6 +60,8 @@ const TrackContainer = styled.View<{isPlaying: boolean}>`
`;
const Album: React.FC = () => {
const defaultStyles = useDefaultStyles();
// Retrieve state
const { params: { id } } = useRoute<Route>();
const tracks = useTypedSelector((state) => state.music.tracks.entities);
@@ -112,25 +113,27 @@ const Album: React.FC = () => {
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
}
>
<AlbumImage source={{ uri: getImage(album?.Id) }} style={colors.imageBackground} />
<Text style={styles.name} >{album?.Name}</Text>
<Text style={styles.artist}>{album?.AlbumArtist}</Text>
<Button title={t('play-album')} onPress={selectAlbum} color={THEME_COLOR} />
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
<TouchableHandler
key={trackId}
id={trackId}
onPress={selectTrack}
onLongPress={longPressTrack}
>
<TrackContainer isPlaying={currentTrack?.id.startsWith(trackId) || false} style={colors.border}>
<Text style={styles.index}>
{tracks[trackId]?.IndexNumber}
</Text>
<Text style={colors.text}>{tracks[trackId]?.Name}</Text>
</TrackContainer>
</TouchableHandler>
) : undefined}
<AlbumImage source={{ uri: getImage(album?.Id) }} style={defaultStyles.imageBackground} />
<Text style={[ defaultStyles.text, styles.name ]} >{album?.Name}</Text>
<Text style={[ defaultStyles.text, styles.artist ]}>{album?.AlbumArtist}</Text>
<Button title={t('play-album')} icon={Play} onPress={selectAlbum} />
<View style={{ marginTop: 15 }}>
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
<TouchableHandler
key={trackId}
id={trackId}
onPress={selectTrack}
onLongPress={longPressTrack}
>
<TrackContainer isPlaying={currentTrack?.id.startsWith(trackId) || false} style={defaultStyles.border}>
<Text style={[ defaultStyles.text, styles.index ]}>
{tracks[trackId]?.IndexNumber}
</Text>
<Text style={defaultStyles.text}>{tracks[trackId]?.Name}</Text>
</TrackContainer>
</TouchableHandler>
) : undefined}
</View>
</ScrollView>
);
};

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef, PureComponent, ReactText } from 'react';
import React, { useCallback, useEffect, useRef, ReactText } from 'react';
import { useGetImage } from 'utility/JellyfinApi';
import { Album, NavigationProp } from '../types';
import { Text, SafeAreaView, SectionList, View } from 'react-native';
@@ -9,13 +9,12 @@ import { useTypedSelector } from 'store';
import { fetchAllAlbums } from 'store/music/actions';
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
import TouchableHandler from 'components/TouchableHandler';
import ListContainer from './components/ListContainer';
import AlbumImage, { AlbumItem } from './components/AlbumImage';
import { selectAlbumsByAlphabet, SectionedId } from 'store/music/selectors';
import AlphabetScroller from 'components/AlphabetScroller';
import { EntityId } from '@reduxjs/toolkit';
import styled from 'styled-components/native';
import { colors } from 'components/Colors';
import useDefaultStyles from 'components/Colors';
interface VirtualizedItemInfo {
section: SectionedId,
@@ -51,19 +50,16 @@ const SectionText = styled.Text`
font-weight: bold;
`;
const sectionStyles = { ...colors.view, ...colors.border };
const SectionHeading = React.memo(function SectionHeading(props: { label: string }) {
const defaultStyles = useDefaultStyles();
const { label } = props;
class SectionHeading extends PureComponent<{ label: string }> {
render() {
const { label } = this.props;
return (
<SectionContainer style={sectionStyles}>
<SectionText style={colors.text}>{label}</SectionText>
</SectionContainer>
);
}
}
return (
<SectionContainer style={defaultStyles.sectionHeading}>
<SectionText style={defaultStyles.text}>{label}</SectionText>
</SectionContainer>
);
});
interface GeneratedAlbumItemProps {
id: ReactText;
@@ -77,21 +73,20 @@ const HalfOpacity = styled.Text`
opacity: 0.5;
`;
class GeneratedAlbumItem extends PureComponent<GeneratedAlbumItemProps> {
render() {
const { id, imageUrl, name, artist, onPress } = this.props;
const GeneratedAlbumItem = React.memo(function GeneratedAlbumItem(props: GeneratedAlbumItemProps) {
const defaultStyles = useDefaultStyles();
const { id, imageUrl, name, artist, onPress } = props;
return (
<TouchableHandler id={id as string} onPress={onPress}>
<AlbumItem>
<AlbumImage source={{ uri: imageUrl }} style={colors.imageBackground} />
<Text numberOfLines={1} style={colors.text}>{name}</Text>
<HalfOpacity style={colors.text} numberOfLines={1}>{artist}</HalfOpacity>
</AlbumItem>
</TouchableHandler>
);
}
}
return (
<TouchableHandler id={id as string} onPress={onPress}>
<AlbumItem>
<AlbumImage source={{ uri: imageUrl }} style={defaultStyles.imageBackground} />
<Text numberOfLines={1} style={defaultStyles.text}>{name}</Text>
<HalfOpacity style={defaultStyles.text} numberOfLines={1}>{artist}</HalfOpacity>
</AlbumItem>
</TouchableHandler>
);
});
const Albums: React.FC = () => {
// Retrieve data from store

View File

@@ -12,31 +12,36 @@ import AlbumImage, { AlbumItem } from './components/AlbumImage';
import { useRecentAlbums } from 'store/music/selectors';
import { Header } from 'components/Typography';
import ListButton from 'components/ListButton';
import { colors } from 'components/Colors';
import { t } from '@localisation';
import useDefaultStyles from 'components/Colors';
const styles = StyleSheet.create({
artist: {
...colors.text,
opacity: 0.5,
columnWrapper: {
paddingLeft: 10,
paddingRight: 10
}
});
const NavigationHeader: React.FC = () => {
const navigation = useNavigation();
const defaultStyles = useDefaultStyles();
const handleAllAlbumsClick = useCallback(() => { navigation.navigate('Albums'); }, [navigation]);
const handleSearchClick = useCallback(() => { navigation.navigate('Search'); }, [navigation]);
return (
<ListContainer>
<>
<ListButton onPress={handleAllAlbumsClick}>{t('all-albums')}</ListButton>
<ListButton onPress={handleSearchClick}>{t('search')}</ListButton>
<Header style={colors.text}>{t('recent-albums')}</Header>
</ListContainer>
<ListContainer>
<Header style={defaultStyles.text}>{t('recent-albums')}</Header>
</ListContainer>
</>
);
};
const RecentAlbums: React.FC = () => {
const defaultStyles = useDefaultStyles();
// Retrieve data from store
const { entities: albums } = useTypedSelector((state) => state.music.albums);
const recentAlbums = useRecentAlbums(24);
@@ -56,25 +61,24 @@ const RecentAlbums: React.FC = () => {
return (
<SafeAreaView>
<ListContainer>
<FlatList
data={recentAlbums as string[]}
refreshing={isLoading}
onRefresh={retrieveData}
numColumns={2}
keyExtractor={d => d}
ListHeaderComponent={NavigationHeader}
renderItem={({ item }) => (
<TouchableHandler id={item} onPress={selectAlbum}>
<AlbumItem>
<AlbumImage source={{ uri: getImage(item) }} style={colors.imageBackground} />
<Text style={colors.text} numberOfLines={1}>{albums[item]?.Name}</Text>
<Text style={styles.artist} numberOfLines={1}>{albums[item]?.AlbumArtist}</Text>
</AlbumItem>
</TouchableHandler>
)}
/>
</ListContainer>
<FlatList
data={recentAlbums as string[]}
refreshing={isLoading}
onRefresh={retrieveData}
numColumns={2}
keyExtractor={d => d}
columnWrapperStyle={styles.columnWrapper}
ListHeaderComponent={NavigationHeader}
renderItem={({ item }) => (
<TouchableHandler id={item} onPress={selectAlbum}>
<AlbumItem>
<AlbumImage source={{ uri: getImage(item) }} style={defaultStyles.imageBackground} />
<Text style={defaultStyles.text} numberOfLines={1}>{albums[item]?.Name}</Text>
<Text style={defaultStyles.textHalfOpacity} numberOfLines={1}>{albums[item]?.AlbumArtist}</Text>
</AlbumItem>
</TouchableHandler>
)}
/>
</SafeAreaView>
);
};

View File

@@ -11,8 +11,8 @@ import { useNavigation } from '@react-navigation/native';
import { useGetImage } from 'utility/JellyfinApi';
import { NavigationProp } from '../types';
import FastImage from 'react-native-fast-image';
import { colors } from 'components/Colors';
import { t } from '@localisation';
import useDefaultStyles from 'components/Colors';
const Container = styled.View`
padding: 0 20px;
@@ -47,6 +47,8 @@ const fuseOptions = {
};
export default function Search() {
const defaultStyles = useDefaultStyles();
// Prepare state
const [searchTerm, setSearchTerm] = useState('');
const albums = useTypedSelector(state => state.music.albums.entities);
@@ -89,10 +91,10 @@ export default function Search() {
const HeaderComponent = React.useMemo(() => (
<Container>
<Input value={searchTerm} onChangeText={setSearchTerm} style={colors.input} placeholder={t('search') + '...'} />
{(searchTerm.length && !results.length) ? <Text>{t('no-results')}</Text> : null}
<Input value={searchTerm} onChangeText={setSearchTerm} style={defaultStyles.input} placeholder={t('search') + '...'} />
{(searchTerm.length && !results.length) ? <Text style={{ textAlign: 'center' }}>{t('no-results')}</Text> : null}
</Container>
), [searchTerm, results, setSearchTerm]);
), [searchTerm, results, setSearchTerm, defaultStyles]);
// GUARD: We cannot search for stuff unless Fuse is loaded with results.
// Therefore we delay rendering to when we are certain it's there.
@@ -105,13 +107,13 @@ export default function Search() {
data={results}
renderItem={({ item: { item: album } }) =>(
<TouchableHandler id={album.Id} onPress={selectAlbum}>
<SearchResult style={colors.border}>
<SearchResult style={defaultStyles.border}>
<AlbumImage source={{ uri: getImage(album.Id) }} />
<View>
<Text numberOfLines={1} ellipsizeMode="tail" style={colors.text}>
<Text numberOfLines={1} ellipsizeMode="tail" style={defaultStyles.text}>
{album.Name} - {album.AlbumArtist}
</Text>
<HalfOpacity style={colors.text}>{t('album')}</HalfOpacity>
<HalfOpacity style={defaultStyles.text}>{t('album')}</HalfOpacity>
</View>
</SearchResult>
</TouchableHandler>

View File

@@ -1,6 +1,6 @@
import React, { useState, useCallback, useEffect, useRef } from 'react';
import TrackPlayer, { usePlaybackState, STATE_PLAYING, STATE_PAUSED } from 'react-native-track-player';
import { TouchableOpacity } from 'react-native';
import { TouchableOpacity, useColorScheme } from 'react-native';
import styled from 'styled-components/native';
import { useHasQueue } from 'utility/useQueue';
import ForwardIcon from 'assets/forwards.svg';
@@ -9,7 +9,6 @@ import PlayIcon from 'assets/play.svg';
import PauseIcon from 'assets/pause.svg';
import RepeatIcon from 'assets/repeat.svg';
// import ShuffleIcon from 'assets/shuffle.svg';
import { useColorScheme } from 'react-native-appearance';
import { THEME_COLOR } from 'CONSTANTS';
const BUTTON_SIZE = 40;

View File

@@ -1,9 +1,10 @@
import React from 'react';
import { Text, Dimensions, View, StyleSheet } from 'react-native';
import { Dimensions, View, StyleSheet } from 'react-native';
import useCurrentTrack from 'utility/useCurrentTrack';
import styled from 'styled-components/native';
import FastImage from 'react-native-fast-image';
import { colors } from 'components/Colors';
import useDefaultStyles from 'components/Colors';
import Text from 'components/Text';
const Screen = Dimensions.get('screen');
@@ -16,13 +17,11 @@ const Artwork = styled(FastImage)`
const styles = StyleSheet.create({
artist: {
...colors.text,
fontWeight: 'bold',
fontSize: 24,
marginBottom: 12,
},
title: {
...colors.text,
fontSize: 18,
marginBottom: 12,
textAlign: 'center',
@@ -34,17 +33,18 @@ const styles = StyleSheet.create({
export default function NowPlaying() {
const track = useCurrentTrack();
const defaultStyles = useDefaultStyles();
return (
<View style={{ alignItems: 'center' }}>
<Artwork
style={colors.imageBackground}
style={defaultStyles.imageBackground}
source={{
uri: track?.artwork,
priority: FastImage.priority.high,
}}
/>
<Text style={styles.artist} >{track?.artist}</Text>
<Text style={styles.artist}>{track?.artist}</Text>
<Text style={styles.title}>{track?.title}</Text>
</View>
);

View File

@@ -4,7 +4,7 @@ import styled from 'styled-components/native';
import { Text, Platform } from 'react-native';
import Slider from '@react-native-community/slider';
import { THEME_COLOR } from 'CONSTANTS';
import { colors } from 'components/Colors';
import { DefaultStylesProvider } from 'components/Colors';
const NumberBar = styled.View`
flex-direction: row;
@@ -65,24 +65,29 @@ export default class ProgressBar extends Component<{}, State> {
render() {
const { position, duration, gesture } = this.state;
return (
<>
<Slider
value={gesture || position}
minimumValue={0}
maximumValue={duration || 0}
onValueChange={this.handleGesture}
onSlidingComplete={this.handleEndOfGesture}
minimumTrackTintColor={THEME_COLOR}
thumbTintColor={Platform.OS === 'android' ? THEME_COLOR : undefined}
disabled={!duration}
/>
<NumberBar>
<Text style={colors.text}>{getMinutes(gesture || position)}:{getSeconds(gesture || position)}</Text>
<Text style={colors.text}>{getMinutes(duration)}:{getSeconds(duration)}</Text>
</NumberBar>
</>
<DefaultStylesProvider>
{defaultStyle => (
<>
<Slider
value={gesture || position}
minimumValue={0}
maximumValue={duration || 0}
onValueChange={this.handleGesture}
onSlidingComplete={this.handleEndOfGesture}
minimumTrackTintColor={THEME_COLOR}
thumbTintColor={Platform.OS === 'android' ? THEME_COLOR : undefined}
disabled={!duration}
/>
<NumberBar>
<Text style={defaultStyle.text}>{getMinutes(gesture || position)}:{getSeconds(gesture || position)}</Text>
<Text style={defaultStyle.text}>{getMinutes(duration)}:{getSeconds(duration)}</Text>
</NumberBar>
</>
)
}
</DefaultStylesProvider>
);
}
}

View File

@@ -1,13 +1,14 @@
import React, { useCallback } from 'react';
import useQueue from 'utility/useQueue';
import { View, Text, StyleSheet, Button } from 'react-native';
import { View, StyleSheet } from 'react-native';
import styled, { css } from 'styled-components/native';
import useCurrentTrack from 'utility/useCurrentTrack';
import TouchableHandler from 'components/TouchableHandler';
import TrackPlayer from 'react-native-track-player';
import { THEME_COLOR } from 'CONSTANTS';
import { colors } from 'components/Colors';
import { t } from '@localisation';
import useDefaultStyles from 'components/Colors';
import Text from 'components/Text';
import Button from 'components/Button';
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean, isDark?: boolean }>`
padding: 10px;
@@ -29,17 +30,13 @@ const ClearQueue = styled.View`
`;
const styles = StyleSheet.create({
title: {
...colors.text,
marginBottom: 2,
},
artist: {
...colors.text,
opacity: 0.5,
trackTitle: {
marginBottom: 2
}
});
export default function Queue() {
const defaultStyles = useDefaultStyles();
const queue = useQueue();
const currentTrack = useCurrentTrack();
const currentIndex = queue.findIndex(d => d.id === currentTrack?.id);
@@ -60,18 +57,18 @@ export default function Queue() {
active={currentTrack?.id === track.id}
key={i}
alreadyPlayed={i < currentIndex}
style={{
...colors.border,
...currentTrack?.id === track.id ? colors.activeBackground : {},
}}
style={[
defaultStyles.border,
currentTrack?.id === track.id ? defaultStyles.activeBackground : {},
]}
>
<Text style={styles.title}>{track.title}</Text>
<Text style={styles.artist}>{track.artist}</Text>
<Text style={styles.trackTitle}>{track.title}</Text>
<Text style={defaultStyles.textHalfOpacity}>{track.artist}</Text>
</QueueItem>
</TouchableHandler>
))}
<ClearQueue>
<Button title={t('clear-queue')} color={THEME_COLOR} onPress={clearQueue} />
<Button title={t('clear-queue')} onPress={clearQueue} />
</ClearQueue>
</View>
);

View File

@@ -4,18 +4,19 @@ import MediaControls from './components/MediaControls';
import ProgressBar from './components/ProgressBar';
import NowPlaying from './components/NowPlaying';
import Queue from './components/Queue';
import { colors } from 'components/Colors';
import useDefaultStyles from 'components/Colors';
const styles = StyleSheet.create({
outer: colors.view,
inner: {
padding: 25,
}
});
export default function Player() {
const defaultStyles = useDefaultStyles();
return (
<ScrollView contentContainerStyle={styles.inner} style={styles.outer}>
<ScrollView contentContainerStyle={styles.inner} style={defaultStyles.view}>
<NowPlaying />
<MediaControls />
<ProgressBar />

View File

@@ -1,11 +1,16 @@
import React, { useCallback } from 'react';
import TrackPlayer from 'react-native-track-player';
import { SubHeader } from 'components/Typography';
import { Text, Button } from 'react-native';
import { THEME_COLOR } from 'CONSTANTS';
import { useDispatch } from 'react-redux';
import music from 'store/music';
import { t } from '@localisation';
import Button from 'components/Button';
import styled from 'styled-components/native';
import Text from 'components/Text';
const ClearCache = styled(Button)`
margin-top: 16px;
`;
export default function CacheSettings() {
const dispatch = useDispatch();
@@ -21,7 +26,7 @@ export default function CacheSettings() {
<>
<SubHeader>{t('setting-cache')}</SubHeader>
<Text>{t('setting-cache-description')}</Text>
<Button title={t('reset-cache')} onPress={handleClearCache} color={THEME_COLOR} />
<ClearCache title={t('reset-cache')} onPress={handleClearCache} />
</>
);
}

View File

@@ -1,13 +1,13 @@
import styled from 'styled-components/native';
import { useNavigation } from '@react-navigation/native';
import React, { useCallback } from 'react';
import { Text, Button } from 'react-native';
import { THEME_COLOR } from 'CONSTANTS';
import { SubHeader } from 'components/Typography';
import { colors } from 'components/Colors';
import useDefaultStyles from 'components/Colors';
import { NavigationProp } from '../..';
import { useTypedSelector } from 'store';
import { t } from '@localisation';
import Button from 'components/Button';
import Text from 'components/Text';
const InputContainer = styled.View`
margin: 10px 0;
@@ -20,6 +20,7 @@ const Input = styled.TextInput`
`;
export default function LibrarySettings() {
const defaultStyles = useDefaultStyles();
const { jellyfin } = useTypedSelector(state => state.settings);
const navigation = useNavigation<NavigationProp>();
const handleSetLibrary = useCallback(() => navigation.navigate('SetJellyfinServer'), [navigation]);
@@ -28,18 +29,18 @@ export default function LibrarySettings() {
<>
<SubHeader>{t('jellyfin-library')}</SubHeader>
<InputContainer>
<Text style={colors.text}>{t('jellyfin-server-url')}</Text>
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} style={colors.input} />
<Text style={defaultStyles.text}>{t('jellyfin-server-url')}</Text>
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} style={defaultStyles.input} />
</InputContainer>
<InputContainer>
<Text style={colors.text}>{t('jellyfin-access-token')}</Text>
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} style={colors.input} />
<Text style={defaultStyles.text}>{t('jellyfin-access-token')}</Text>
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} style={defaultStyles.input} />
</InputContainer>
<InputContainer>
<Text style={colors.text}>{t('jellyfin-user-id')}</Text>
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} style={colors.input} />
<Text style={defaultStyles.text}>{t('jellyfin-user-id')}</Text>
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} style={defaultStyles.input} />
</InputContainer>
<Button title={t('set-jellyfin-server')} onPress={handleSetLibrary} color={THEME_COLOR} />
<Button title={t('set-jellyfin-server')} onPress={handleSetLibrary} />
</>
);
}

View File

@@ -1,17 +1,19 @@
import React from 'react';
import { View, SafeAreaView, ScrollView } from 'react-native';
import { Header } from 'components/Typography';
import { colors } from 'components/Colors';
import Library from './components/Library';
import Cache from './components/Cache';
import useDefaultStyles from 'components/Colors';
import { Header } from 'components/Typography';
import { t } from '@localisation';
export default function Settings() {
const defaultStyles = useDefaultStyles();
return (
<ScrollView>
<SafeAreaView>
<View style={{ padding: 20 }}>
<Header style={colors.text}>{t('settings')}</Header>
<Header style={defaultStyles.text}>{t('settings')}</Header>
<Library />
<Cache />
</View>

View File

@@ -93,6 +93,6 @@ export default function Routes() {
}
export type NavigationProp = CompositeNavigationProp<
StackNavigationProp<Routes>,
BottomTabNavigationProp<Screens>
StackNavigationProp<Routes>,
BottomTabNavigationProp<Screens>
>;

View File

@@ -1,5 +1,5 @@
import React, { useState, useCallback } from 'react';
import { Text, Button, View } from 'react-native';
import { Button, View } from 'react-native';
import Modal from 'components/Modal';
import Input from 'components/Input';
import { setJellyfinCredentials } from 'store/settings/actions';
@@ -7,10 +7,12 @@ import { useDispatch } from 'react-redux';
import { useNavigation, StackActions } from '@react-navigation/native';
import CredentialGenerator from './components/CredentialGenerator';
import { THEME_COLOR } from 'CONSTANTS';
import { colors } from 'components/Colors';
import { t } from '@localisation';
import useDefaultStyles from 'components/Colors';
import Text from 'components/Text';
export default function SetJellyfinServer() {
const defaultStyles = useDefaultStyles();
// State for first screen
const [serverUrl, setServerUrl] = useState<string>();
const [isLogginIn, setIsLogginIn] = useState<boolean>(false);
@@ -34,7 +36,7 @@ export default function SetJellyfinServer() {
/>
) : (
<View style={{ padding: 20, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={colors.text}>
<Text>
{t('set-jellyfin-server-instruction')}
</Text>
<Input
@@ -44,7 +46,7 @@ export default function SetJellyfinServer() {
keyboardType="url"
autoCapitalize="none"
autoCorrect={false}
style={{ ...colors.input, width: '100%' }}
style={[ defaultStyles.input, { width: '100%' } ]}
/>
<Button
title={t('set-jellyfin-server')}

View File

@@ -1,14 +1,16 @@
import React, { useCallback } from 'react';
import Modal from 'components/Modal';
import { Text, Button } from 'react-native';
import { useNavigation, StackActions, useRoute, RouteProp } from '@react-navigation/native';
import { ModalStackParams } from 'screens/types';
import { useTypedSelector } from 'store';
import { THEME_COLOR } from 'CONSTANTS';
import { SubHeader } from 'components/Typography';
import styled from 'styled-components/native';
import usePlayTrack from 'utility/usePlayTrack';
import { t } from '@localisation';
import Button from 'components/Button';
import PlayIcon from 'assets/play.svg';
import QueueAppendIcon from 'assets/queue-append.svg';
import Text from 'components/Text';
type Route = RouteProp<ModalStackParams, 'TrackPopupMenu'>;
@@ -19,7 +21,11 @@ const Container = styled.View`
const Buttons = styled.View`
margin-top: 20px;
flex-direction: row;
justify-content: space-around;
/* justify-content: space-around; */
`;
const ButtonSpacing = styled.View`
width: 8px;
`;
function TrackPopupMenu() {
@@ -49,8 +55,9 @@ function TrackPopupMenu() {
<SubHeader>{track?.Name}</SubHeader>
<Text>{track?.Album} - {track?.AlbumArtist}</Text>
<Buttons>
<Button title={t('play-next')} color={THEME_COLOR} onPress={handlePlayNext} />
<Button title={t('add-to-queue')} color={THEME_COLOR} onPress={handleAddToQueue} />
<Button title={t('play-next')} icon={PlayIcon} onPress={handlePlayNext} />
<ButtonSpacing />
<Button title={t('add-to-queue')} icon={QueueAppendIcon} onPress={handleAddToQueue} />
</Buttons>
</Container>
</Modal>