Fancy new buttons and more consistent colors
This commit is contained in:
15
.eslintrc.js
15
.eslintrc.js
@@ -8,7 +8,7 @@ module.exports = {
|
|||||||
'plugin:react/recommended',
|
'plugin:react/recommended',
|
||||||
'plugin:@typescript-eslint/eslint-recommended',
|
'plugin:@typescript-eslint/eslint-recommended',
|
||||||
'plugin:react-hooks/recommended',
|
'plugin:react-hooks/recommended',
|
||||||
// "plugin:@typescript-eslint/recommended"
|
// 'plugin:@typescript-eslint/recommended'
|
||||||
],
|
],
|
||||||
globals: {
|
globals: {
|
||||||
Atomics: 'readonly',
|
Atomics: 'readonly',
|
||||||
@@ -28,14 +28,15 @@ module.exports = {
|
|||||||
'react-hooks'
|
'react-hooks'
|
||||||
],
|
],
|
||||||
rules: {
|
rules: {
|
||||||
indent: [
|
indent: 'off',
|
||||||
|
'@typescript-eslint/indent': [
|
||||||
'error',
|
'error',
|
||||||
4,
|
4,
|
||||||
{
|
{
|
||||||
SwitchCase: 1,
|
SwitchCase: 1,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"linebreak-style": [
|
'linebreak-style': [
|
||||||
'error',
|
'error',
|
||||||
'unix'
|
'unix'
|
||||||
],
|
],
|
||||||
@@ -47,10 +48,10 @@ module.exports = {
|
|||||||
'error',
|
'error',
|
||||||
'always'
|
'always'
|
||||||
],
|
],
|
||||||
"no-unused-vars": "off",
|
'no-unused-vars': 'off',
|
||||||
"react/prop-types": "off",
|
'react/prop-types': 'off',
|
||||||
"@typescript-eslint/no-unused-vars": [
|
'@typescript-eslint/no-unused-vars': [
|
||||||
"error"
|
'error'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -245,15 +245,13 @@ PODS:
|
|||||||
- React-cxxreact (= 0.63.4)
|
- React-cxxreact (= 0.63.4)
|
||||||
- React-jsi (= 0.63.4)
|
- React-jsi (= 0.63.4)
|
||||||
- React-jsinspector (0.63.4)
|
- React-jsinspector (0.63.4)
|
||||||
- react-native-appearance (0.3.4):
|
|
||||||
- React
|
|
||||||
- react-native-safe-area-context (3.1.9):
|
- react-native-safe-area-context (3.1.9):
|
||||||
- React-Core
|
- React-Core
|
||||||
- react-native-slider (3.0.3):
|
- react-native-slider (3.0.3):
|
||||||
- React
|
- React
|
||||||
- react-native-track-player (1.2.3):
|
- react-native-track-player (1.2.3):
|
||||||
- React
|
- React
|
||||||
- react-native-webview (11.2.1):
|
- react-native-webview (11.2.3):
|
||||||
- React-Core
|
- React-Core
|
||||||
- React-RCTActionSheet (0.63.4):
|
- React-RCTActionSheet (0.63.4):
|
||||||
- React-Core/RCTActionSheetHeaders (= 0.63.4)
|
- React-Core/RCTActionSheetHeaders (= 0.63.4)
|
||||||
@@ -325,7 +323,7 @@ PODS:
|
|||||||
- React-Core
|
- React-Core
|
||||||
- SDWebImage (~> 5.8)
|
- SDWebImage (~> 5.8)
|
||||||
- SDWebImageWebPCoder (~> 0.6.1)
|
- SDWebImageWebPCoder (~> 0.6.1)
|
||||||
- RNGestureHandler (1.9.0):
|
- RNGestureHandler (1.10.0):
|
||||||
- React-Core
|
- React-Core
|
||||||
- RNLocalize (2.0.1):
|
- RNLocalize (2.0.1):
|
||||||
- React-Core
|
- React-Core
|
||||||
@@ -388,7 +386,6 @@ DEPENDENCIES:
|
|||||||
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
|
- React-jsi (from `../node_modules/react-native/ReactCommon/jsi`)
|
||||||
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
|
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
|
||||||
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
- 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-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||||
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
|
||||||
- react-native-track-player (from `../node_modules/react-native-track-player`)
|
- react-native-track-player (from `../node_modules/react-native-track-player`)
|
||||||
@@ -465,8 +462,6 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
|
:path: "../node_modules/react-native/ReactCommon/jsiexecutor"
|
||||||
React-jsinspector:
|
React-jsinspector:
|
||||||
:path: "../node_modules/react-native/ReactCommon/jsinspector"
|
:path: "../node_modules/react-native/ReactCommon/jsinspector"
|
||||||
react-native-appearance:
|
|
||||||
:path: "../node_modules/react-native-appearance"
|
|
||||||
react-native-safe-area-context:
|
react-native-safe-area-context:
|
||||||
:path: "../node_modules/react-native-safe-area-context"
|
:path: "../node_modules/react-native-safe-area-context"
|
||||||
react-native-slider:
|
react-native-slider:
|
||||||
@@ -546,11 +541,10 @@ SPEC CHECKSUMS:
|
|||||||
React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31
|
React-jsi: a0418934cf48f25b485631deb27c64dc40fb4c31
|
||||||
React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949
|
React-jsiexecutor: 93bd528844ad21dc07aab1c67cb10abae6df6949
|
||||||
React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a
|
React-jsinspector: 58aef7155bc9a9683f5b60b35eccea8722a4f53a
|
||||||
react-native-appearance: fc2014516054585d531e07aa0b40ab0de1d2be85
|
react-native-safe-area-context: b6e0e284002381d2ff29fa4fff42b4d8282e3c94
|
||||||
react-native-safe-area-context: 86612d2c9a9e94e288319262d10b5f93f0b395f5
|
react-native-slider: e99fc201cefe81270fc9d81714a7a0f5e566b168
|
||||||
react-native-slider: b733e17fdd31186707146debf1f04b5d94aa1a93
|
react-native-track-player: 6ff21d21eb70ecbd2a6bad29822ecf6c8609a3aa
|
||||||
react-native-track-player: ba2416753b58f3cdf9db5a07daa65876d659f925
|
react-native-webview: 6520e3e7b4933de76b95ef542c8d7115cf45b68e
|
||||||
react-native-webview: dbe6c1ad149740f0e2d84a963f1d3c3e77f2d99c
|
|
||||||
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
|
React-RCTActionSheet: 89a0ca9f4a06c1f93c26067af074ccdce0f40336
|
||||||
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
|
React-RCTAnimation: 1bde3ecc0c104c55df246eda516e0deb03c4e49b
|
||||||
React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0
|
React-RCTBlob: a97d378b527740cc667e03ebfa183a75231ab0f0
|
||||||
@@ -561,12 +555,12 @@ SPEC CHECKSUMS:
|
|||||||
React-RCTText: 5c51df3f08cb9dedc6e790161195d12bac06101c
|
React-RCTText: 5c51df3f08cb9dedc6e790161195d12bac06101c
|
||||||
React-RCTVibration: ae4f914cfe8de7d4de95ae1ea6cc8f6315d73d9d
|
React-RCTVibration: ae4f914cfe8de7d4de95ae1ea6cc8f6315d73d9d
|
||||||
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
|
ReactCommon: 73d79c7039f473b76db6ff7c6b159c478acbbb3b
|
||||||
RNCAsyncStorage: cb9a623793918c6699586281f0b51cbc38f046f9
|
RNCAsyncStorage: b03032fdbdb725bea0bd9e5ec5a7272865ae7398
|
||||||
RNCMaskedView: f5c7d14d6847b7b44853f7acb6284c1da30a3459
|
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
|
||||||
RNCPicker: 914b557e20b3b8317b084aca9ff4b4edb95f61e4
|
RNCPicker: 914b557e20b3b8317b084aca9ff4b4edb95f61e4
|
||||||
RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff
|
RNFastImage: d4870d58f5936111c56218dbd7fcfc18e65b58ff
|
||||||
RNGestureHandler: 9b7e605a741412e20e13c512738a31bd1611759b
|
RNGestureHandler: 03f587ba19a7e2d9fe48fb9aa6e33ace5fd07dd5
|
||||||
RNLocalize: dcf0fdb332b37b0d24178e876a7ce4dbbc9c838d
|
RNLocalize: 41026b7c14878f1a1b381bc79f668f1fbf841adb
|
||||||
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
|
RNReanimated: e03f7425cb7a38dcf1b644d680d1bfc91c3337ad
|
||||||
RNScreens: b6c9607e6fe47c1b6e2f1910d2acd46dd7ecea3a
|
RNScreens: b6c9607e6fe47c1b6e2f1910d2acd46dd7ecea3a
|
||||||
RNSentry: 6aeba1adc242fd22a6826acae92f430697b47a9c
|
RNSentry: 6aeba1adc242fd22a6826acae92f430697b47a9c
|
||||||
|
|||||||
15216
package-lock.json
generated
15216
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -28,9 +28,8 @@
|
|||||||
"fuse.js": "^6.4.6",
|
"fuse.js": "^6.4.6",
|
||||||
"i18n-js": "^3.8.0",
|
"i18n-js": "^3.8.0",
|
||||||
"lodash": "^4.17.20",
|
"lodash": "^4.17.20",
|
||||||
"react": "^17.0.1",
|
"react": "^16.13.1",
|
||||||
"react-native": "^0.63.4",
|
"react-native": "^0.63.4",
|
||||||
"react-native-appearance": "^0.3.4",
|
|
||||||
"react-native-dotenv": "^2.5.1",
|
"react-native-dotenv": "^2.5.1",
|
||||||
"react-native-fast-image": "^8.3.4",
|
"react-native-fast-image": "^8.3.4",
|
||||||
"react-native-gesture-handler": "^1.9.0",
|
"react-native-gesture-handler": "^1.9.0",
|
||||||
@@ -57,6 +56,7 @@
|
|||||||
"@types/react-native": "^0.63.48",
|
"@types/react-native": "^0.63.48",
|
||||||
"@types/react-redux": "^7.1.16",
|
"@types/react-redux": "^7.1.16",
|
||||||
"@types/react-test-renderer": "^17.0.0",
|
"@types/react-test-renderer": "^17.0.0",
|
||||||
|
"@types/styled-components-react-native": "^5.1.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.14.2",
|
"@typescript-eslint/eslint-plugin": "^4.14.2",
|
||||||
"@typescript-eslint/parser": "^4.14.2",
|
"@typescript-eslint/parser": "^4.14.2",
|
||||||
"babel-jest": "^26.6.3",
|
"babel-jest": "^26.6.3",
|
||||||
|
|||||||
1
src/assets/queue-append.svg
Normal file
1
src/assets/queue-append.svg
Normal 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 |
1
src/assets/queue-prepend.svg
Normal file
1
src/assets/queue-prepend.svg
Normal 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
1
src/assets/queue.svg
Normal 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 |
@@ -9,10 +9,10 @@ import {
|
|||||||
TapGestureHandlerGestureEvent
|
TapGestureHandlerGestureEvent
|
||||||
} from 'react-native-gesture-handler';
|
} from 'react-native-gesture-handler';
|
||||||
|
|
||||||
interface LetterContainerProps {
|
// interface LetterContainerProps {
|
||||||
onPress: (letter: string) => void;
|
// onPress: (letter: string) => void;
|
||||||
letter: string;
|
// letter: string;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const Container = styled.View`
|
const Container = styled.View`
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import TrackPlayer from 'react-native-track-player';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
import { PersistGate } from 'redux-persist/integration/react';
|
import { PersistGate } from 'redux-persist/integration/react';
|
||||||
import { AppearanceProvider, Appearance, AppearanceListener } from 'react-native-appearance';
|
|
||||||
import Routes from '../screens';
|
import Routes from '../screens';
|
||||||
import store, { persistedStore } from 'store';
|
import store, { persistedStore } from 'store';
|
||||||
import {
|
import {
|
||||||
@@ -10,60 +9,36 @@ import {
|
|||||||
DefaultTheme,
|
DefaultTheme,
|
||||||
DarkTheme,
|
DarkTheme,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
|
import { useColorScheme } from 'react-native';
|
||||||
|
|
||||||
interface State {
|
export default function App(): JSX.Element {
|
||||||
isReady: boolean;
|
const colorScheme = useColorScheme();
|
||||||
colorScheme?: string;
|
// const colorScheme = 'dark';
|
||||||
}
|
|
||||||
|
|
||||||
export default class App extends Component<{}, State> {
|
useEffect(() => {
|
||||||
state: State = {
|
async function setupTrackPlayer() {
|
||||||
isReady: false,
|
await TrackPlayer.setupPlayer();
|
||||||
};
|
await TrackPlayer.updateOptions({
|
||||||
|
capabilities: [
|
||||||
subscription = null;
|
TrackPlayer.CAPABILITY_PLAY,
|
||||||
|
TrackPlayer.CAPABILITY_PAUSE,
|
||||||
async componentDidMount() {
|
TrackPlayer.CAPABILITY_SKIP_TO_NEXT,
|
||||||
await TrackPlayer.setupPlayer();
|
TrackPlayer.CAPABILITY_SKIP_TO_PREVIOUS,
|
||||||
await TrackPlayer.updateOptions({
|
TrackPlayer.CAPABILITY_STOP,
|
||||||
capabilities: [
|
TrackPlayer.CAPABILITY_SEEK_TO,
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
setupTrackPlayer();
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PersistGate loading={null} persistor={persistedStore}>
|
<PersistGate loading={null} persistor={persistedStore}>
|
||||||
<AppearanceProvider>
|
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
||||||
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<Routes />
|
||||||
<Routes />
|
</NavigationContainer>
|
||||||
</NavigationContainer>
|
</PersistGate>
|
||||||
</AppearanceProvider>
|
</Provider>
|
||||||
</PersistGate>
|
);
|
||||||
</Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
69
src/components/Button.tsx
Normal file
69
src/components/Button.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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'),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -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
55
src/components/Colors.ts
Normal 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);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import styled from 'styled-components/native';
|
|||||||
|
|
||||||
const Input = styled.TextInput`
|
const Input = styled.TextInput`
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
border-radius: 5px;
|
border-radius: 8px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,24 +1,48 @@
|
|||||||
import React from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { TouchableOpacityProps, Text } from 'react-native';
|
import { TouchableOpacityProps } from 'react-native';
|
||||||
import ChevronRight from 'assets/chevron-right.svg';
|
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 { THEME_COLOR } from 'CONSTANTS';
|
||||||
import { colors } from './Colors';
|
import useDefaultStyles from './Colors';
|
||||||
|
|
||||||
const BUTTON_SIZE = 14;
|
const BUTTON_SIZE = 14;
|
||||||
|
|
||||||
const Container = styled.TouchableOpacity`
|
const Container = styled.Pressable<{ active?: boolean }>`
|
||||||
padding: 18px 0;
|
padding: 18px 20px;
|
||||||
border-bottom-width: 1px;
|
border-bottom-width: 1px;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
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 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 (
|
return (
|
||||||
<Container {...props} style={colors.border}>
|
<Container
|
||||||
<Text style={{ color: THEME_COLOR, fontSize: 16 }}>{children}</Text>
|
{...props}
|
||||||
<ChevronRight width={BUTTON_SIZE} height={BUTTON_SIZE} fill={THEME_COLOR} />
|
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>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import styled, { css } from 'styled-components/native';
|
import styled, { css } from 'styled-components/native';
|
||||||
import { SafeAreaView, Pressable } from 'react-native';
|
import { SafeAreaView, Pressable } from 'react-native';
|
||||||
import { colors } from './Colors';
|
|
||||||
import { useNavigation, StackActions } from '@react-navigation/native';
|
import { useNavigation, StackActions } from '@react-navigation/native';
|
||||||
|
import useDefaultStyles from './Colors';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fullSize?: boolean;
|
fullSize?: boolean;
|
||||||
@@ -24,15 +24,16 @@ const Container = styled(Pressable)<Pick<Props, 'fullSize'>>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Modal: React.FC<Props> = ({ children, fullSize = true }) => {
|
const Modal: React.FC<Props> = ({ children, fullSize = true }) => {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
const closeModal = useCallback(() => {
|
const closeModal = useCallback(() => {
|
||||||
navigation.dispatch(StackActions.popToTop());
|
navigation.dispatch(StackActions.popToTop());
|
||||||
}, [navigation]);
|
}, [navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Background style={colors.modal} onPress={closeModal}>
|
<Background style={defaultStyles.modal} onPress={closeModal}>
|
||||||
<SafeAreaView style={{ flex: 1 }}>
|
<SafeAreaView style={{ flex: 1 }}>
|
||||||
<Container style={colors.view} fullSize={fullSize}>
|
<Container style={defaultStyles.modalInner} fullSize={fullSize}>
|
||||||
{children}
|
{children}
|
||||||
</Container>
|
</Container>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
|
|||||||
11
src/components/Text.tsx
Normal file
11
src/components/Text.tsx
Normal 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]} />
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
import styled from 'styled-components/native';
|
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;
|
margin: 24px 0 12px 0;
|
||||||
font-size: 36px;
|
font-size: 36px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const SubHeader = styled.Text`
|
export const SubHeader = styled(Text)`
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
margin: 12px 0;
|
margin: 12px 0;
|
||||||
`;
|
`;
|
||||||
@@ -1,26 +1,26 @@
|
|||||||
export type LocaleKeys = 'play-next'
|
export type LocaleKeys = 'play-next'
|
||||||
| 'play-album'
|
| 'play-album'
|
||||||
| 'queue'
|
| 'queue'
|
||||||
| 'add-to-queue'
|
| 'add-to-queue'
|
||||||
| 'clear-queue'
|
| 'clear-queue'
|
||||||
| 'no-results'
|
| 'no-results'
|
||||||
| 'album'
|
| 'album'
|
||||||
| 'albums'
|
| 'albums'
|
||||||
| 'all-albums'
|
| 'all-albums'
|
||||||
| 'search'
|
| 'search'
|
||||||
| 'music'
|
| 'music'
|
||||||
| 'now-playing'
|
| 'now-playing'
|
||||||
| 'onboarding-welcome'
|
| 'onboarding-welcome'
|
||||||
| 'onboarding-intro'
|
| 'onboarding-intro'
|
||||||
| 'onboarding-cta'
|
| 'onboarding-cta'
|
||||||
| 'set-jellyfin-server'
|
| 'set-jellyfin-server'
|
||||||
| 'set-jellyfin-server-instruction'
|
| 'set-jellyfin-server-instruction'
|
||||||
| 'settings'
|
| 'settings'
|
||||||
| 'jellyfin-library'
|
| 'jellyfin-library'
|
||||||
| 'jellyfin-server-url'
|
| 'jellyfin-server-url'
|
||||||
| 'jellyfin-access-token'
|
| 'jellyfin-access-token'
|
||||||
| 'jellyfin-user-id'
|
| 'jellyfin-user-id'
|
||||||
| 'setting-cache'
|
| 'setting-cache'
|
||||||
| 'setting-cache-description'
|
| 'setting-cache-description'
|
||||||
| 'reset-cache'
|
| 'reset-cache'
|
||||||
| 'recent-albums'
|
| 'recent-albums'
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { StackParams } from '../types';
|
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 { useGetImage } from 'utility/JellyfinApi';
|
||||||
import styled, { css } from 'styled-components/native';
|
import styled, { css } from 'styled-components/native';
|
||||||
import { useRoute, RouteProp, useNavigation } from '@react-navigation/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 usePlayAlbum from 'utility/usePlayAlbum';
|
||||||
import TouchableHandler from 'components/TouchableHandler';
|
import TouchableHandler from 'components/TouchableHandler';
|
||||||
import useCurrentTrack from 'utility/useCurrentTrack';
|
import useCurrentTrack from 'utility/useCurrentTrack';
|
||||||
import { colors } from 'components/Colors';
|
|
||||||
import TrackPlayer from 'react-native-track-player';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
import { t } from '@localisation';
|
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'>;
|
type Route = RouteProp<StackParams, 'Album'>;
|
||||||
|
|
||||||
@@ -23,18 +25,15 @@ const Screen = Dimensions.get('screen');
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
name: {
|
name: {
|
||||||
...colors.text,
|
|
||||||
fontSize: 36,
|
fontSize: 36,
|
||||||
fontWeight: 'bold'
|
fontWeight: 'bold'
|
||||||
},
|
},
|
||||||
artist: {
|
artist: {
|
||||||
...colors.text,
|
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
marginBottom: 24
|
marginBottom: 24
|
||||||
},
|
},
|
||||||
index: {
|
index: {
|
||||||
...colors.text,
|
|
||||||
width: 20,
|
width: 20,
|
||||||
opacity: 0.5,
|
opacity: 0.5,
|
||||||
marginRight: 5
|
marginRight: 5
|
||||||
@@ -61,6 +60,8 @@ const TrackContainer = styled.View<{isPlaying: boolean}>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Album: React.FC = () => {
|
const Album: React.FC = () => {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
// Retrieve state
|
// Retrieve state
|
||||||
const { params: { id } } = useRoute<Route>();
|
const { params: { id } } = useRoute<Route>();
|
||||||
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
const tracks = useTypedSelector((state) => state.music.tracks.entities);
|
||||||
@@ -112,25 +113,27 @@ const Album: React.FC = () => {
|
|||||||
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
|
<RefreshControl refreshing={isLoading} onRefresh={refresh} />
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AlbumImage source={{ uri: getImage(album?.Id) }} style={colors.imageBackground} />
|
<AlbumImage source={{ uri: getImage(album?.Id) }} style={defaultStyles.imageBackground} />
|
||||||
<Text style={styles.name} >{album?.Name}</Text>
|
<Text style={[ defaultStyles.text, styles.name ]} >{album?.Name}</Text>
|
||||||
<Text style={styles.artist}>{album?.AlbumArtist}</Text>
|
<Text style={[ defaultStyles.text, styles.artist ]}>{album?.AlbumArtist}</Text>
|
||||||
<Button title={t('play-album')} onPress={selectAlbum} color={THEME_COLOR} />
|
<Button title={t('play-album')} icon={Play} onPress={selectAlbum} />
|
||||||
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
|
<View style={{ marginTop: 15 }}>
|
||||||
<TouchableHandler
|
{album?.Tracks?.length ? album.Tracks.map((trackId) =>
|
||||||
key={trackId}
|
<TouchableHandler
|
||||||
id={trackId}
|
key={trackId}
|
||||||
onPress={selectTrack}
|
id={trackId}
|
||||||
onLongPress={longPressTrack}
|
onPress={selectTrack}
|
||||||
>
|
onLongPress={longPressTrack}
|
||||||
<TrackContainer isPlaying={currentTrack?.id.startsWith(trackId) || false} style={colors.border}>
|
>
|
||||||
<Text style={styles.index}>
|
<TrackContainer isPlaying={currentTrack?.id.startsWith(trackId) || false} style={defaultStyles.border}>
|
||||||
{tracks[trackId]?.IndexNumber}
|
<Text style={[ defaultStyles.text, styles.index ]}>
|
||||||
</Text>
|
{tracks[trackId]?.IndexNumber}
|
||||||
<Text style={colors.text}>{tracks[trackId]?.Name}</Text>
|
</Text>
|
||||||
</TrackContainer>
|
<Text style={defaultStyles.text}>{tracks[trackId]?.Name}</Text>
|
||||||
</TouchableHandler>
|
</TrackContainer>
|
||||||
) : undefined}
|
</TouchableHandler>
|
||||||
|
) : undefined}
|
||||||
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 { useGetImage } from 'utility/JellyfinApi';
|
||||||
import { Album, NavigationProp } from '../types';
|
import { Album, NavigationProp } from '../types';
|
||||||
import { Text, SafeAreaView, SectionList, View } from 'react-native';
|
import { Text, SafeAreaView, SectionList, View } from 'react-native';
|
||||||
@@ -9,13 +9,12 @@ import { useTypedSelector } from 'store';
|
|||||||
import { fetchAllAlbums } from 'store/music/actions';
|
import { fetchAllAlbums } from 'store/music/actions';
|
||||||
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
|
import { ALBUM_CACHE_AMOUNT_OF_DAYS } from 'CONSTANTS';
|
||||||
import TouchableHandler from 'components/TouchableHandler';
|
import TouchableHandler from 'components/TouchableHandler';
|
||||||
import ListContainer from './components/ListContainer';
|
|
||||||
import AlbumImage, { AlbumItem } from './components/AlbumImage';
|
import AlbumImage, { AlbumItem } from './components/AlbumImage';
|
||||||
import { selectAlbumsByAlphabet, SectionedId } from 'store/music/selectors';
|
import { selectAlbumsByAlphabet, SectionedId } from 'store/music/selectors';
|
||||||
import AlphabetScroller from 'components/AlphabetScroller';
|
import AlphabetScroller from 'components/AlphabetScroller';
|
||||||
import { EntityId } from '@reduxjs/toolkit';
|
import { EntityId } from '@reduxjs/toolkit';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { colors } from 'components/Colors';
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
|
||||||
interface VirtualizedItemInfo {
|
interface VirtualizedItemInfo {
|
||||||
section: SectionedId,
|
section: SectionedId,
|
||||||
@@ -51,19 +50,16 @@ const SectionText = styled.Text`
|
|||||||
font-weight: bold;
|
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 }> {
|
return (
|
||||||
render() {
|
<SectionContainer style={defaultStyles.sectionHeading}>
|
||||||
const { label } = this.props;
|
<SectionText style={defaultStyles.text}>{label}</SectionText>
|
||||||
|
</SectionContainer>
|
||||||
return (
|
);
|
||||||
<SectionContainer style={sectionStyles}>
|
});
|
||||||
<SectionText style={colors.text}>{label}</SectionText>
|
|
||||||
</SectionContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface GeneratedAlbumItemProps {
|
interface GeneratedAlbumItemProps {
|
||||||
id: ReactText;
|
id: ReactText;
|
||||||
@@ -77,21 +73,20 @@ const HalfOpacity = styled.Text`
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
class GeneratedAlbumItem extends PureComponent<GeneratedAlbumItemProps> {
|
const GeneratedAlbumItem = React.memo(function GeneratedAlbumItem(props: GeneratedAlbumItemProps) {
|
||||||
render() {
|
const defaultStyles = useDefaultStyles();
|
||||||
const { id, imageUrl, name, artist, onPress } = this.props;
|
const { id, imageUrl, name, artist, onPress } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableHandler id={id as string} onPress={onPress}>
|
<TouchableHandler id={id as string} onPress={onPress}>
|
||||||
<AlbumItem>
|
<AlbumItem>
|
||||||
<AlbumImage source={{ uri: imageUrl }} style={colors.imageBackground} />
|
<AlbumImage source={{ uri: imageUrl }} style={defaultStyles.imageBackground} />
|
||||||
<Text numberOfLines={1} style={colors.text}>{name}</Text>
|
<Text numberOfLines={1} style={defaultStyles.text}>{name}</Text>
|
||||||
<HalfOpacity style={colors.text} numberOfLines={1}>{artist}</HalfOpacity>
|
<HalfOpacity style={defaultStyles.text} numberOfLines={1}>{artist}</HalfOpacity>
|
||||||
</AlbumItem>
|
</AlbumItem>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const Albums: React.FC = () => {
|
const Albums: React.FC = () => {
|
||||||
// Retrieve data from store
|
// Retrieve data from store
|
||||||
|
|||||||
@@ -12,31 +12,36 @@ import AlbumImage, { AlbumItem } from './components/AlbumImage';
|
|||||||
import { useRecentAlbums } from 'store/music/selectors';
|
import { useRecentAlbums } from 'store/music/selectors';
|
||||||
import { Header } from 'components/Typography';
|
import { Header } from 'components/Typography';
|
||||||
import ListButton from 'components/ListButton';
|
import ListButton from 'components/ListButton';
|
||||||
import { colors } from 'components/Colors';
|
|
||||||
import { t } from '@localisation';
|
import { t } from '@localisation';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
artist: {
|
columnWrapper: {
|
||||||
...colors.text,
|
paddingLeft: 10,
|
||||||
opacity: 0.5,
|
paddingRight: 10
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const NavigationHeader: React.FC = () => {
|
const NavigationHeader: React.FC = () => {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
const handleAllAlbumsClick = useCallback(() => { navigation.navigate('Albums'); }, [navigation]);
|
const handleAllAlbumsClick = useCallback(() => { navigation.navigate('Albums'); }, [navigation]);
|
||||||
const handleSearchClick = useCallback(() => { navigation.navigate('Search'); }, [navigation]);
|
const handleSearchClick = useCallback(() => { navigation.navigate('Search'); }, [navigation]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListContainer>
|
<>
|
||||||
<ListButton onPress={handleAllAlbumsClick}>{t('all-albums')}</ListButton>
|
<ListButton onPress={handleAllAlbumsClick}>{t('all-albums')}</ListButton>
|
||||||
<ListButton onPress={handleSearchClick}>{t('search')}</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 RecentAlbums: React.FC = () => {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
// Retrieve data from store
|
// Retrieve data from store
|
||||||
const { entities: albums } = useTypedSelector((state) => state.music.albums);
|
const { entities: albums } = useTypedSelector((state) => state.music.albums);
|
||||||
const recentAlbums = useRecentAlbums(24);
|
const recentAlbums = useRecentAlbums(24);
|
||||||
@@ -56,25 +61,24 @@ const RecentAlbums: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView>
|
<SafeAreaView>
|
||||||
<ListContainer>
|
<FlatList
|
||||||
<FlatList
|
data={recentAlbums as string[]}
|
||||||
data={recentAlbums as string[]}
|
refreshing={isLoading}
|
||||||
refreshing={isLoading}
|
onRefresh={retrieveData}
|
||||||
onRefresh={retrieveData}
|
numColumns={2}
|
||||||
numColumns={2}
|
keyExtractor={d => d}
|
||||||
keyExtractor={d => d}
|
columnWrapperStyle={styles.columnWrapper}
|
||||||
ListHeaderComponent={NavigationHeader}
|
ListHeaderComponent={NavigationHeader}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<TouchableHandler id={item} onPress={selectAlbum}>
|
<TouchableHandler id={item} onPress={selectAlbum}>
|
||||||
<AlbumItem>
|
<AlbumItem>
|
||||||
<AlbumImage source={{ uri: getImage(item) }} style={colors.imageBackground} />
|
<AlbumImage source={{ uri: getImage(item) }} style={defaultStyles.imageBackground} />
|
||||||
<Text style={colors.text} numberOfLines={1}>{albums[item]?.Name}</Text>
|
<Text style={defaultStyles.text} numberOfLines={1}>{albums[item]?.Name}</Text>
|
||||||
<Text style={styles.artist} numberOfLines={1}>{albums[item]?.AlbumArtist}</Text>
|
<Text style={defaultStyles.textHalfOpacity} numberOfLines={1}>{albums[item]?.AlbumArtist}</Text>
|
||||||
</AlbumItem>
|
</AlbumItem>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</ListContainer>
|
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import { useNavigation } from '@react-navigation/native';
|
|||||||
import { useGetImage } from 'utility/JellyfinApi';
|
import { useGetImage } from 'utility/JellyfinApi';
|
||||||
import { NavigationProp } from '../types';
|
import { NavigationProp } from '../types';
|
||||||
import FastImage from 'react-native-fast-image';
|
import FastImage from 'react-native-fast-image';
|
||||||
import { colors } from 'components/Colors';
|
|
||||||
import { t } from '@localisation';
|
import { t } from '@localisation';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
|
||||||
const Container = styled.View`
|
const Container = styled.View`
|
||||||
padding: 0 20px;
|
padding: 0 20px;
|
||||||
@@ -47,6 +47,8 @@ const fuseOptions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
// Prepare state
|
// Prepare state
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const albums = useTypedSelector(state => state.music.albums.entities);
|
const albums = useTypedSelector(state => state.music.albums.entities);
|
||||||
@@ -89,10 +91,10 @@ export default function Search() {
|
|||||||
|
|
||||||
const HeaderComponent = React.useMemo(() => (
|
const HeaderComponent = React.useMemo(() => (
|
||||||
<Container>
|
<Container>
|
||||||
<Input value={searchTerm} onChangeText={setSearchTerm} style={colors.input} placeholder={t('search') + '...'} />
|
<Input value={searchTerm} onChangeText={setSearchTerm} style={defaultStyles.input} placeholder={t('search') + '...'} />
|
||||||
{(searchTerm.length && !results.length) ? <Text>{t('no-results')}</Text> : null}
|
{(searchTerm.length && !results.length) ? <Text style={{ textAlign: 'center' }}>{t('no-results')}</Text> : null}
|
||||||
</Container>
|
</Container>
|
||||||
), [searchTerm, results, setSearchTerm]);
|
), [searchTerm, results, setSearchTerm, defaultStyles]);
|
||||||
|
|
||||||
// GUARD: We cannot search for stuff unless Fuse is loaded with results.
|
// GUARD: We cannot search for stuff unless Fuse is loaded with results.
|
||||||
// Therefore we delay rendering to when we are certain it's there.
|
// Therefore we delay rendering to when we are certain it's there.
|
||||||
@@ -105,13 +107,13 @@ export default function Search() {
|
|||||||
data={results}
|
data={results}
|
||||||
renderItem={({ item: { item: album } }) =>(
|
renderItem={({ item: { item: album } }) =>(
|
||||||
<TouchableHandler id={album.Id} onPress={selectAlbum}>
|
<TouchableHandler id={album.Id} onPress={selectAlbum}>
|
||||||
<SearchResult style={colors.border}>
|
<SearchResult style={defaultStyles.border}>
|
||||||
<AlbumImage source={{ uri: getImage(album.Id) }} />
|
<AlbumImage source={{ uri: getImage(album.Id) }} />
|
||||||
<View>
|
<View>
|
||||||
<Text numberOfLines={1} ellipsizeMode="tail" style={colors.text}>
|
<Text numberOfLines={1} ellipsizeMode="tail" style={defaultStyles.text}>
|
||||||
{album.Name} - {album.AlbumArtist}
|
{album.Name} - {album.AlbumArtist}
|
||||||
</Text>
|
</Text>
|
||||||
<HalfOpacity style={colors.text}>{t('album')}</HalfOpacity>
|
<HalfOpacity style={defaultStyles.text}>{t('album')}</HalfOpacity>
|
||||||
</View>
|
</View>
|
||||||
</SearchResult>
|
</SearchResult>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
||||||
import TrackPlayer, { usePlaybackState, STATE_PLAYING, STATE_PAUSED } from 'react-native-track-player';
|
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 styled from 'styled-components/native';
|
||||||
import { useHasQueue } from 'utility/useQueue';
|
import { useHasQueue } from 'utility/useQueue';
|
||||||
import ForwardIcon from 'assets/forwards.svg';
|
import ForwardIcon from 'assets/forwards.svg';
|
||||||
@@ -9,7 +9,6 @@ import PlayIcon from 'assets/play.svg';
|
|||||||
import PauseIcon from 'assets/pause.svg';
|
import PauseIcon from 'assets/pause.svg';
|
||||||
import RepeatIcon from 'assets/repeat.svg';
|
import RepeatIcon from 'assets/repeat.svg';
|
||||||
// import ShuffleIcon from 'assets/shuffle.svg';
|
// import ShuffleIcon from 'assets/shuffle.svg';
|
||||||
import { useColorScheme } from 'react-native-appearance';
|
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
|
|
||||||
const BUTTON_SIZE = 40;
|
const BUTTON_SIZE = 40;
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import React from 'react';
|
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 useCurrentTrack from 'utility/useCurrentTrack';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import FastImage from 'react-native-fast-image';
|
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');
|
const Screen = Dimensions.get('screen');
|
||||||
|
|
||||||
@@ -16,13 +17,11 @@ const Artwork = styled(FastImage)`
|
|||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
artist: {
|
artist: {
|
||||||
...colors.text,
|
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
title: {
|
title: {
|
||||||
...colors.text,
|
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
@@ -34,17 +33,18 @@ const styles = StyleSheet.create({
|
|||||||
|
|
||||||
export default function NowPlaying() {
|
export default function NowPlaying() {
|
||||||
const track = useCurrentTrack();
|
const track = useCurrentTrack();
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ alignItems: 'center' }}>
|
<View style={{ alignItems: 'center' }}>
|
||||||
<Artwork
|
<Artwork
|
||||||
style={colors.imageBackground}
|
style={defaultStyles.imageBackground}
|
||||||
source={{
|
source={{
|
||||||
uri: track?.artwork,
|
uri: track?.artwork,
|
||||||
priority: FastImage.priority.high,
|
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>
|
<Text style={styles.title}>{track?.title}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import styled from 'styled-components/native';
|
|||||||
import { Text, Platform } from 'react-native';
|
import { Text, Platform } from 'react-native';
|
||||||
import Slider from '@react-native-community/slider';
|
import Slider from '@react-native-community/slider';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import { colors } from 'components/Colors';
|
import { DefaultStylesProvider } from 'components/Colors';
|
||||||
|
|
||||||
const NumberBar = styled.View`
|
const NumberBar = styled.View`
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -67,22 +67,27 @@ export default class ProgressBar extends Component<{}, State> {
|
|||||||
const { position, duration, gesture } = this.state;
|
const { position, duration, gesture } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<DefaultStylesProvider>
|
||||||
<Slider
|
{defaultStyle => (
|
||||||
value={gesture || position}
|
<>
|
||||||
minimumValue={0}
|
<Slider
|
||||||
maximumValue={duration || 0}
|
value={gesture || position}
|
||||||
onValueChange={this.handleGesture}
|
minimumValue={0}
|
||||||
onSlidingComplete={this.handleEndOfGesture}
|
maximumValue={duration || 0}
|
||||||
minimumTrackTintColor={THEME_COLOR}
|
onValueChange={this.handleGesture}
|
||||||
thumbTintColor={Platform.OS === 'android' ? THEME_COLOR : undefined}
|
onSlidingComplete={this.handleEndOfGesture}
|
||||||
disabled={!duration}
|
minimumTrackTintColor={THEME_COLOR}
|
||||||
/>
|
thumbTintColor={Platform.OS === 'android' ? THEME_COLOR : undefined}
|
||||||
<NumberBar>
|
disabled={!duration}
|
||||||
<Text style={colors.text}>{getMinutes(gesture || position)}:{getSeconds(gesture || position)}</Text>
|
/>
|
||||||
<Text style={colors.text}>{getMinutes(duration)}:{getSeconds(duration)}</Text>
|
<NumberBar>
|
||||||
</NumberBar>
|
<Text style={defaultStyle.text}>{getMinutes(gesture || position)}:{getSeconds(gesture || position)}</Text>
|
||||||
</>
|
<Text style={defaultStyle.text}>{getMinutes(duration)}:{getSeconds(duration)}</Text>
|
||||||
|
</NumberBar>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</DefaultStylesProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import useQueue from 'utility/useQueue';
|
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 styled, { css } from 'styled-components/native';
|
||||||
import useCurrentTrack from 'utility/useCurrentTrack';
|
import useCurrentTrack from 'utility/useCurrentTrack';
|
||||||
import TouchableHandler from 'components/TouchableHandler';
|
import TouchableHandler from 'components/TouchableHandler';
|
||||||
import TrackPlayer from 'react-native-track-player';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
|
||||||
import { colors } from 'components/Colors';
|
|
||||||
import { t } from '@localisation';
|
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 }>`
|
const QueueItem = styled.View<{ active?: boolean, alreadyPlayed?: boolean, isDark?: boolean }>`
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
@@ -29,17 +30,13 @@ const ClearQueue = styled.View`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
title: {
|
trackTitle: {
|
||||||
...colors.text,
|
marginBottom: 2
|
||||||
marginBottom: 2,
|
|
||||||
},
|
|
||||||
artist: {
|
|
||||||
...colors.text,
|
|
||||||
opacity: 0.5,
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function Queue() {
|
export default function Queue() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
const queue = useQueue();
|
const queue = useQueue();
|
||||||
const currentTrack = useCurrentTrack();
|
const currentTrack = useCurrentTrack();
|
||||||
const currentIndex = queue.findIndex(d => d.id === currentTrack?.id);
|
const currentIndex = queue.findIndex(d => d.id === currentTrack?.id);
|
||||||
@@ -60,18 +57,18 @@ export default function Queue() {
|
|||||||
active={currentTrack?.id === track.id}
|
active={currentTrack?.id === track.id}
|
||||||
key={i}
|
key={i}
|
||||||
alreadyPlayed={i < currentIndex}
|
alreadyPlayed={i < currentIndex}
|
||||||
style={{
|
style={[
|
||||||
...colors.border,
|
defaultStyles.border,
|
||||||
...currentTrack?.id === track.id ? colors.activeBackground : {},
|
currentTrack?.id === track.id ? defaultStyles.activeBackground : {},
|
||||||
}}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.title}>{track.title}</Text>
|
<Text style={styles.trackTitle}>{track.title}</Text>
|
||||||
<Text style={styles.artist}>{track.artist}</Text>
|
<Text style={defaultStyles.textHalfOpacity}>{track.artist}</Text>
|
||||||
</QueueItem>
|
</QueueItem>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
))}
|
))}
|
||||||
<ClearQueue>
|
<ClearQueue>
|
||||||
<Button title={t('clear-queue')} color={THEME_COLOR} onPress={clearQueue} />
|
<Button title={t('clear-queue')} onPress={clearQueue} />
|
||||||
</ClearQueue>
|
</ClearQueue>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,18 +4,19 @@ import MediaControls from './components/MediaControls';
|
|||||||
import ProgressBar from './components/ProgressBar';
|
import ProgressBar from './components/ProgressBar';
|
||||||
import NowPlaying from './components/NowPlaying';
|
import NowPlaying from './components/NowPlaying';
|
||||||
import Queue from './components/Queue';
|
import Queue from './components/Queue';
|
||||||
import { colors } from 'components/Colors';
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
outer: colors.view,
|
|
||||||
inner: {
|
inner: {
|
||||||
padding: 25,
|
padding: 25,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function Player() {
|
export default function Player() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView contentContainerStyle={styles.inner} style={styles.outer}>
|
<ScrollView contentContainerStyle={styles.inner} style={defaultStyles.view}>
|
||||||
<NowPlaying />
|
<NowPlaying />
|
||||||
<MediaControls />
|
<MediaControls />
|
||||||
<ProgressBar />
|
<ProgressBar />
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import TrackPlayer from 'react-native-track-player';
|
import TrackPlayer from 'react-native-track-player';
|
||||||
import { SubHeader } from 'components/Typography';
|
import { SubHeader } from 'components/Typography';
|
||||||
import { Text, Button } from 'react-native';
|
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import music from 'store/music';
|
import music from 'store/music';
|
||||||
import { t } from '@localisation';
|
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() {
|
export default function CacheSettings() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@@ -21,7 +26,7 @@ export default function CacheSettings() {
|
|||||||
<>
|
<>
|
||||||
<SubHeader>{t('setting-cache')}</SubHeader>
|
<SubHeader>{t('setting-cache')}</SubHeader>
|
||||||
<Text>{t('setting-cache-description')}</Text>
|
<Text>{t('setting-cache-description')}</Text>
|
||||||
<Button title={t('reset-cache')} onPress={handleClearCache} color={THEME_COLOR} />
|
<ClearCache title={t('reset-cache')} onPress={handleClearCache} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { Text, Button } from 'react-native';
|
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
|
||||||
import { SubHeader } from 'components/Typography';
|
import { SubHeader } from 'components/Typography';
|
||||||
import { colors } from 'components/Colors';
|
import useDefaultStyles from 'components/Colors';
|
||||||
import { NavigationProp } from '../..';
|
import { NavigationProp } from '../..';
|
||||||
import { useTypedSelector } from 'store';
|
import { useTypedSelector } from 'store';
|
||||||
import { t } from '@localisation';
|
import { t } from '@localisation';
|
||||||
|
import Button from 'components/Button';
|
||||||
|
import Text from 'components/Text';
|
||||||
|
|
||||||
const InputContainer = styled.View`
|
const InputContainer = styled.View`
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
@@ -20,6 +20,7 @@ const Input = styled.TextInput`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export default function LibrarySettings() {
|
export default function LibrarySettings() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
const { jellyfin } = useTypedSelector(state => state.settings);
|
const { jellyfin } = useTypedSelector(state => state.settings);
|
||||||
const navigation = useNavigation<NavigationProp>();
|
const navigation = useNavigation<NavigationProp>();
|
||||||
const handleSetLibrary = useCallback(() => navigation.navigate('SetJellyfinServer'), [navigation]);
|
const handleSetLibrary = useCallback(() => navigation.navigate('SetJellyfinServer'), [navigation]);
|
||||||
@@ -28,18 +29,18 @@ export default function LibrarySettings() {
|
|||||||
<>
|
<>
|
||||||
<SubHeader>{t('jellyfin-library')}</SubHeader>
|
<SubHeader>{t('jellyfin-library')}</SubHeader>
|
||||||
<InputContainer>
|
<InputContainer>
|
||||||
<Text style={colors.text}>{t('jellyfin-server-url')}</Text>
|
<Text style={defaultStyles.text}>{t('jellyfin-server-url')}</Text>
|
||||||
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} style={colors.input} />
|
<Input placeholder="https://jellyfin.yourserver.com/" value={jellyfin?.uri} editable={false} style={defaultStyles.input} />
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
<InputContainer>
|
<InputContainer>
|
||||||
<Text style={colors.text}>{t('jellyfin-access-token')}</Text>
|
<Text style={defaultStyles.text}>{t('jellyfin-access-token')}</Text>
|
||||||
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} style={colors.input} />
|
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.access_token} editable={false} style={defaultStyles.input} />
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
<InputContainer>
|
<InputContainer>
|
||||||
<Text style={colors.text}>{t('jellyfin-user-id')}</Text>
|
<Text style={defaultStyles.text}>{t('jellyfin-user-id')}</Text>
|
||||||
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} style={colors.input} />
|
<Input placeholder="deadbeefdeadbeefdeadbeef" value={jellyfin?.user_id} editable={false} style={defaultStyles.input} />
|
||||||
</InputContainer>
|
</InputContainer>
|
||||||
<Button title={t('set-jellyfin-server')} onPress={handleSetLibrary} color={THEME_COLOR} />
|
<Button title={t('set-jellyfin-server')} onPress={handleSetLibrary} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { View, SafeAreaView, ScrollView } from 'react-native';
|
import { View, SafeAreaView, ScrollView } from 'react-native';
|
||||||
import { Header } from 'components/Typography';
|
|
||||||
import { colors } from 'components/Colors';
|
|
||||||
import Library from './components/Library';
|
import Library from './components/Library';
|
||||||
import Cache from './components/Cache';
|
import Cache from './components/Cache';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
import { Header } from 'components/Typography';
|
||||||
import { t } from '@localisation';
|
import { t } from '@localisation';
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<SafeAreaView>
|
<SafeAreaView>
|
||||||
<View style={{ padding: 20 }}>
|
<View style={{ padding: 20 }}>
|
||||||
<Header style={colors.text}>{t('settings')}</Header>
|
<Header style={defaultStyles.text}>{t('settings')}</Header>
|
||||||
<Library />
|
<Library />
|
||||||
<Cache />
|
<Cache />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -93,6 +93,6 @@ export default function Routes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type NavigationProp = CompositeNavigationProp<
|
export type NavigationProp = CompositeNavigationProp<
|
||||||
StackNavigationProp<Routes>,
|
StackNavigationProp<Routes>,
|
||||||
BottomTabNavigationProp<Screens>
|
BottomTabNavigationProp<Screens>
|
||||||
>;
|
>;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useCallback } from 'react';
|
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 Modal from 'components/Modal';
|
||||||
import Input from 'components/Input';
|
import Input from 'components/Input';
|
||||||
import { setJellyfinCredentials } from 'store/settings/actions';
|
import { setJellyfinCredentials } from 'store/settings/actions';
|
||||||
@@ -7,10 +7,12 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { useNavigation, StackActions } from '@react-navigation/native';
|
import { useNavigation, StackActions } from '@react-navigation/native';
|
||||||
import CredentialGenerator from './components/CredentialGenerator';
|
import CredentialGenerator from './components/CredentialGenerator';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import { colors } from 'components/Colors';
|
|
||||||
import { t } from '@localisation';
|
import { t } from '@localisation';
|
||||||
|
import useDefaultStyles from 'components/Colors';
|
||||||
|
import Text from 'components/Text';
|
||||||
|
|
||||||
export default function SetJellyfinServer() {
|
export default function SetJellyfinServer() {
|
||||||
|
const defaultStyles = useDefaultStyles();
|
||||||
// State for first screen
|
// State for first screen
|
||||||
const [serverUrl, setServerUrl] = useState<string>();
|
const [serverUrl, setServerUrl] = useState<string>();
|
||||||
const [isLogginIn, setIsLogginIn] = useState<boolean>(false);
|
const [isLogginIn, setIsLogginIn] = useState<boolean>(false);
|
||||||
@@ -34,7 +36,7 @@ export default function SetJellyfinServer() {
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<View style={{ padding: 20, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
<View style={{ padding: 20, flex: 1, justifyContent: 'center', alignItems: 'center' }}>
|
||||||
<Text style={colors.text}>
|
<Text>
|
||||||
{t('set-jellyfin-server-instruction')}
|
{t('set-jellyfin-server-instruction')}
|
||||||
</Text>
|
</Text>
|
||||||
<Input
|
<Input
|
||||||
@@ -44,7 +46,7 @@ export default function SetJellyfinServer() {
|
|||||||
keyboardType="url"
|
keyboardType="url"
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
autoCorrect={false}
|
autoCorrect={false}
|
||||||
style={{ ...colors.input, width: '100%' }}
|
style={[ defaultStyles.input, { width: '100%' } ]}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
title={t('set-jellyfin-server')}
|
title={t('set-jellyfin-server')}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import Modal from 'components/Modal';
|
import Modal from 'components/Modal';
|
||||||
import { Text, Button } from 'react-native';
|
|
||||||
import { useNavigation, StackActions, useRoute, RouteProp } from '@react-navigation/native';
|
import { useNavigation, StackActions, useRoute, RouteProp } from '@react-navigation/native';
|
||||||
import { ModalStackParams } from 'screens/types';
|
import { ModalStackParams } from 'screens/types';
|
||||||
import { useTypedSelector } from 'store';
|
import { useTypedSelector } from 'store';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
|
||||||
import { SubHeader } from 'components/Typography';
|
import { SubHeader } from 'components/Typography';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import usePlayTrack from 'utility/usePlayTrack';
|
import usePlayTrack from 'utility/usePlayTrack';
|
||||||
import { t } from '@localisation';
|
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'>;
|
type Route = RouteProp<ModalStackParams, 'TrackPopupMenu'>;
|
||||||
|
|
||||||
@@ -19,7 +21,11 @@ const Container = styled.View`
|
|||||||
const Buttons = styled.View`
|
const Buttons = styled.View`
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-around;
|
/* justify-content: space-around; */
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ButtonSpacing = styled.View`
|
||||||
|
width: 8px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function TrackPopupMenu() {
|
function TrackPopupMenu() {
|
||||||
@@ -49,8 +55,9 @@ function TrackPopupMenu() {
|
|||||||
<SubHeader>{track?.Name}</SubHeader>
|
<SubHeader>{track?.Name}</SubHeader>
|
||||||
<Text>{track?.Album} - {track?.AlbumArtist}</Text>
|
<Text>{track?.Album} - {track?.AlbumArtist}</Text>
|
||||||
<Buttons>
|
<Buttons>
|
||||||
<Button title={t('play-next')} color={THEME_COLOR} onPress={handlePlayNext} />
|
<Button title={t('play-next')} icon={PlayIcon} onPress={handlePlayNext} />
|
||||||
<Button title={t('add-to-queue')} color={THEME_COLOR} onPress={handleAddToQueue} />
|
<ButtonSpacing />
|
||||||
|
<Button title={t('add-to-queue')} icon={QueueAppendIcon} onPress={handleAddToQueue} />
|
||||||
</Buttons>
|
</Buttons>
|
||||||
</Container>
|
</Container>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
Reference in New Issue
Block a user