From c24f5e573d4c81fc7666c60972351436551239b4 Mon Sep 17 00:00:00 2001 From: austinried <4966622+austinried@users.noreply.github.com> Date: Fri, 30 Jul 2021 10:24:42 +0900 Subject: [PATCH] real settings view impl server management mostly working changing active server needs work --- .eslintrc.js | 1 + app/components/Button.tsx | 11 +- app/components/ListPlayerControls.tsx | 2 +- app/components/SettingsItem.tsx | 47 ++++++ app/navigation/BottomTabNavigator.tsx | 43 +++++- app/screens/NowPlayingLayout.tsx | 1 - app/screens/ServerView.tsx | 201 ++++++++++++++++++++++++++ app/screens/Settings.tsx | 135 +++++++++-------- app/state/settings.ts | 39 ++++- 9 files changed, 404 insertions(+), 76 deletions(-) create mode 100644 app/components/SettingsItem.tsx create mode 100644 app/screens/ServerView.tsx diff --git a/.eslintrc.js b/.eslintrc.js index 4829fca..1398b0c 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,5 +6,6 @@ module.exports = { radix: 0, '@typescript-eslint/no-unused-vars': ['warn'], semi: 0, + 'no-spaced-func': 0, }, } diff --git a/app/components/Button.tsx b/app/components/Button.tsx index 13b32a7..7ed49af 100644 --- a/app/components/Button.tsx +++ b/app/components/Button.tsx @@ -1,18 +1,21 @@ import colors from '@app/styles/colors' import font from '@app/styles/font' import React from 'react' -import { GestureResponderEvent, StyleSheet, Text } from 'react-native' +import { GestureResponderEvent, StyleProp, StyleSheet, Text, ViewStyle } from 'react-native' import PressableOpacity from './PressableOpacity' const Button: React.FC<{ title?: string buttonStyle?: 'hollow' | 'highlight' onPress: (event: GestureResponderEvent) => void -}> = ({ title, buttonStyle, onPress, children }) => { + style?: StyleProp + disabled?: boolean +}> = ({ title, buttonStyle, onPress, children, style, disabled }) => { return ( + disabled={disabled} + style={[styles.container, buttonStyle !== undefined ? styles[buttonStyle] : {}, style]}> {title ? {title} : children} ) @@ -28,7 +31,7 @@ const styles = StyleSheet.create({ }, hollow: { backgroundColor: 'transparent', - borderColor: colors.text.secondary, + borderColor: colors.text.primary, borderWidth: 1.5, }, highlight: { diff --git a/app/components/ListPlayerControls.tsx b/app/components/ListPlayerControls.tsx index 3d5f6ff..11ecd23 100644 --- a/app/components/ListPlayerControls.tsx +++ b/app/components/ListPlayerControls.tsx @@ -23,7 +23,7 @@ const ListPlayerControls = React.memo<{ {downloaded ? ( ) : ( - + )} diff --git a/app/components/SettingsItem.tsx b/app/components/SettingsItem.tsx new file mode 100644 index 0000000..908638b --- /dev/null +++ b/app/components/SettingsItem.tsx @@ -0,0 +1,47 @@ +import colors from '@app/styles/colors' +import font from '@app/styles/font' +import React from 'react' +import { StyleSheet, View, Text } from 'react-native' +import PressableOpacity from './PressableOpacity' + +const SettingsItem: React.FC<{ + title: string + subtitle?: string + onPress?: () => void +}> = ({ title, subtitle, onPress, children }) => { + return ( + + + {title} + {subtitle ? {subtitle} : <>} + + {children} + + ) +} + +const styles = StyleSheet.create({ + item: { + height: 60, + marginBottom: 10, + alignItems: 'stretch', + flexDirection: 'row', + }, + itemText: { + flex: 1, + alignSelf: 'stretch', + alignItems: 'flex-start', + }, + itemTitle: { + fontFamily: font.regular, + color: colors.text.primary, + fontSize: 15, + }, + itemSubtitle: { + fontFamily: font.regular, + color: colors.text.secondary, + fontSize: 15, + }, +}) + +export default SettingsItem diff --git a/app/navigation/BottomTabNavigator.tsx b/app/navigation/BottomTabNavigator.tsx index 11f14e0..bc67b88 100644 --- a/app/navigation/BottomTabNavigator.tsx +++ b/app/navigation/BottomTabNavigator.tsx @@ -5,6 +5,7 @@ import ArtistView from '@app/screens/ArtistView' import Home from '@app/screens/Home' import PlaylistView from '@app/screens/PlaylistView' import Search from '@app/screens/Search' +import ServerView from '@app/screens/ServerView' import SettingsView from '@app/screens/Settings' import colors from '@app/styles/colors' import font from '@app/styles/font' @@ -15,7 +16,7 @@ import { StyleSheet } from 'react-native' import { createNativeStackNavigator, NativeStackNavigationProp } from 'react-native-screens/native-stack' type TabStackParamList = { - main: { toTop?: boolean } + main: undefined album: { id: string; title: string } artist: { id: string; title: string } playlist: { id: string; title: string } @@ -83,7 +84,8 @@ function createTabStackNavigator(Component: React.ComponentType) { return navigation.addListener('tabPress', () => { navigation.dispatch(StackActions.popToTop()) }) - }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) return ( @@ -102,6 +104,41 @@ const HomeTab = createTabStackNavigator(Home) const LibraryTab = createTabStackNavigator(LibraryTopTabNavigator) const SearchTab = createTabStackNavigator(Search) +type SettingsStackParamList = { + main: undefined + server?: { id?: string } +} + +type ServerScreenNavigationProp = NativeStackNavigationProp +type ServerScreenRouteProp = RouteProp +type ServerScreenProps = { + route: ServerScreenRouteProp + navigation: ServerScreenNavigationProp +} + +const ServerScreen: React.FC = ({ route }) => + +const SettingsStack = createNativeStackNavigator() + +const SettingsTab = () => { + return ( + + + + + ) +} + const Tab = createBottomTabNavigator() const BottomTabNavigator = () => { @@ -110,7 +147,7 @@ const BottomTabNavigator = () => { - + ) } diff --git a/app/screens/NowPlayingLayout.tsx b/app/screens/NowPlayingLayout.tsx index bfc7e64..0557b65 100644 --- a/app/screens/NowPlayingLayout.tsx +++ b/app/screens/NowPlayingLayout.tsx @@ -29,7 +29,6 @@ import dimensions from '@app/styles/dimensions' import { NativeStackScreenProps } from 'react-native-screens/native-stack' import { useFocusEffect } from '@react-navigation/native' -// eslint-disable-next-line no-spaced-func const NowPlayingHeader = React.memo<{ backHandler: () => void }>(({ backHandler }) => { diff --git a/app/screens/ServerView.tsx b/app/screens/ServerView.tsx new file mode 100644 index 0000000..a2d3805 --- /dev/null +++ b/app/screens/ServerView.tsx @@ -0,0 +1,201 @@ +import Button from '@app/components/Button' +import GradientScrollView from '@app/components/GradientScrollView' +import SettingsItem from '@app/components/SettingsItem' +import { Server } from '@app/models/settings' +import { activeServerAtom, serversAtom } from '@app/state/settings' +import colors from '@app/styles/colors' +import font from '@app/styles/font' +import { useNavigation } from '@react-navigation/native' +import { useAtom } from 'jotai' +import md5 from 'md5' +import React, { useCallback, useState } from 'react' +import { StyleSheet, Text, TextInput, View } from 'react-native' +import { Switch } from 'react-native-gesture-handler' +import { v4 as uuidv4 } from 'uuid' + +function replaceIndex(array: T[], index: number, replacement: T): T[] { + const start = array.slice(0, index) + const end = array.slice(index + 1) + return [...start, replacement, ...end] +} + +const ServerView: React.FC<{ + id?: string +}> = ({ id }) => { + const navigation = useNavigation() + const [activeServer, setActiveServer] = useAtom(activeServerAtom) + const [servers, setServers] = useAtom(serversAtom) + const server = id ? servers.find(s => s.id === id) : undefined + + const [address, setAddress] = useState(server?.address || '') + const [username, setUsername] = useState(server?.username || '') + const [password, setPassword] = useState(server?.token ? 'password' : '') + + const validate = useCallback(() => { + return !!address && !!username && !!password + }, [address, username, password]) + + const canRemove = useCallback(() => { + return id && servers.length > 1 && activeServer?.id !== id + }, [id, servers, activeServer]) + + const exit = useCallback(() => { + if (navigation.canGoBack()) { + navigation.goBack() + } else { + navigation.navigate('main') + } + }, [navigation]) + + const save = () => { + if (!validate()) { + return + } + + const salt = server?.salt || uuidv4() + let token: string + if (password === 'password' && server?.token) { + token = server.token + } else { + token = md5(password + salt) + } + + const update: Server = { + id: server?.id || uuidv4(), + address, + username, + salt, + token, + } + + if (server) { + setServers( + replaceIndex( + servers, + servers.findIndex(s => s.id === id), + update, + ), + ) + } else { + setServers([...servers, update]) + } + + if (!activeServer) { + setActiveServer(update.id) + } + + exit() + } + + const remove = useCallback(() => { + if (!canRemove()) { + return + } + + const update = [...servers] + update.splice( + update.findIndex(s => s.id === id), + 1, + ) + + setServers(update) + exit() + }, [canRemove, exit, id, servers, setServers]) + + return ( + + + Address + + Username + + Password + + + + +