better tabs and more layout
70
App.tsx
@ -1,78 +1,12 @@
|
||||
import React from 'react';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
|
||||
import { Image } from 'react-native';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
|
||||
import SettingsView from './src/components/Settings';
|
||||
import ArtistsList from './src/components/ArtistsList';
|
||||
import NowPlayingLayout from './src/components/NowPlayingLayout';
|
||||
|
||||
|
||||
const SettingsIcon: React.FC<{ focused: boolean }> = ({ focused }) => (
|
||||
<Image
|
||||
style={{
|
||||
height: 32,
|
||||
width: 32,
|
||||
}}
|
||||
source={focused ? require('./res/settings-fill.png') : require('./res/settings.png')} />
|
||||
);
|
||||
|
||||
const NowPlayingIcon: React.FC<{ focused: boolean }> = ({ focused }) => (
|
||||
<Image
|
||||
style={{
|
||||
height: 32,
|
||||
width: 32,
|
||||
}}
|
||||
source={focused ? require('./res/music_notes-fill.png') : require('./res/music_notes.png')} />
|
||||
);
|
||||
|
||||
const ArtistsIcon: React.FC<{ focused: boolean }> = ({ focused }) => (
|
||||
<Image
|
||||
style={{
|
||||
height: 32,
|
||||
width: 32,
|
||||
}}
|
||||
source={focused ? require('./res/mic_on-fill.png') : require('./res/mic_on.png')} />
|
||||
);
|
||||
|
||||
const Tab = createBottomTabNavigator();
|
||||
import RootNavigator from './src/components/RootNavigator';
|
||||
|
||||
const App = () => (
|
||||
<RecoilRoot>
|
||||
<NavigationContainer>
|
||||
<Tab.Navigator>
|
||||
<Tab.Screen
|
||||
name='Settings'
|
||||
component={SettingsView}
|
||||
options={{
|
||||
tabBarIcon: SettingsIcon,
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Artists'
|
||||
component={ArtistsList}
|
||||
options={{
|
||||
tabBarIcon: ArtistsIcon,
|
||||
tabBarVisibilityAnimationConfig: {
|
||||
show: {
|
||||
animation: 'spring',
|
||||
},
|
||||
hide: {
|
||||
animation: 'spring',
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Now Playing'
|
||||
component={NowPlayingLayout}
|
||||
options={{
|
||||
tabBarIcon: NowPlayingIcon,
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
<RootNavigator />
|
||||
</NavigationContainer>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
BIN
android/app/src/main/assets/fonts/Rubik-VariableFont_wght.ttf
Normal file
BIN
assets/fonts/Rubik-VariableFont_wght.ttf
Normal file
@ -12,6 +12,7 @@
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
|
||||
E40802F2F4B64B50BC3D4A71 /* Rubik-VariableFont_wght.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 6882041C5ED04E249BB58448 /* Rubik-VariableFont_wght.ttf */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -36,6 +37,7 @@
|
||||
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = SubSonify/main.m; sourceTree = "<group>"; };
|
||||
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = SubSonify/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
6882041C5ED04E249BB58448 /* Rubik-VariableFont_wght.ttf */ = {isa = PBXFileReference; name = "Rubik-VariableFont_wght.ttf"; path = "../assets/fonts/Rubik-VariableFont_wght.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -109,6 +111,7 @@
|
||||
00E356EF1AD99517003FC87E /* SubSonifyTests */,
|
||||
83CBBA001A601CBA00E9B192 /* Products */,
|
||||
2D16E6871FA4F8E400B85C8A /* Frameworks */,
|
||||
4E728597FECE42D1A816E9C1 /* Resources */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
@ -124,6 +127,15 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
4E728597FECE42D1A816E9C1 /* Resources */ = {
|
||||
isa = "PBXGroup";
|
||||
children = (
|
||||
6882041C5ED04E249BB58448 /* Rubik-VariableFont_wght.ttf */,
|
||||
);
|
||||
name = Resources;
|
||||
sourceTree = "<group>";
|
||||
path = "";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -214,6 +226,7 @@
|
||||
files = (
|
||||
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
|
||||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
|
||||
E40802F2F4B64B50BC3D4A71 /* Rubik-VariableFont_wght.ttf in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
</dict>
|
||||
</dict>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<string/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
@ -51,5 +51,9 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>Rubik-VariableFont_wght.ttf</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
7
react-native.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
project: {
|
||||
ios: {},
|
||||
android: {},
|
||||
},
|
||||
assets: ['./assets/fonts']
|
||||
};
|
||||
BIN
res/chevron_right-fill.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
res/chevron_right.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
res/home-fill.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
res/home.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
res/library-fill.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
res/library.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
res/search-fill.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
res/search.png
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
@ -29,7 +29,7 @@ const List = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const ArtistsList = () => {
|
||||
const ListPlusControls = () => {
|
||||
const resetArtists = useResetRecoilState(artistsState);
|
||||
const updateArtists = useUpdateArtists();
|
||||
|
||||
@ -43,11 +43,17 @@ const ArtistsList = () => {
|
||||
title='Update from API'
|
||||
onPress={updateArtists}
|
||||
/>
|
||||
<React.Suspense fallback={<Text>Loading...</Text>}>
|
||||
<List />
|
||||
</React.Suspense>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const ArtistsList = () => (
|
||||
<View>
|
||||
<React.Suspense fallback={<Text>Loading...</Text>}>
|
||||
<ListPlusControls />
|
||||
</React.Suspense>
|
||||
</View>
|
||||
)
|
||||
|
||||
export default ArtistsList;
|
||||
|
||||
25
src/components/FocusableIcon.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { Image, ImageSourcePropType } from 'react-native';
|
||||
|
||||
export type FocusableIconProps = {
|
||||
focused: boolean,
|
||||
source: ImageSourcePropType;
|
||||
focusedSource?: ImageSourcePropType;
|
||||
width?: number;
|
||||
height?: number;
|
||||
};
|
||||
|
||||
const FocusableIcon: React.FC<FocusableIconProps> = (props) => {
|
||||
props.focusedSource = props.focusedSource || props.source;
|
||||
props.width = props.width || 32;
|
||||
props.height = props.height || 32;
|
||||
|
||||
return (
|
||||
<Image
|
||||
style={{ height: props.height, width: props.width }}
|
||||
source={props.focused ? props.focusedSource : props.source}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default FocusableIcon;
|
||||
48
src/components/Library.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import { Text, View, Image } from 'react-native';
|
||||
|
||||
const SectionHeader: React.FC<{ title: string }> = ({ title }) => {
|
||||
return (
|
||||
<View style={{
|
||||
height: 60,
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
// backgroundColor: 'green',
|
||||
alignItems: 'center',
|
||||
paddingLeft: 15,
|
||||
paddingRight: 15,
|
||||
}}>
|
||||
<Text style={{
|
||||
color: 'white',
|
||||
fontSize: 34,
|
||||
fontWeight: 'normal',
|
||||
fontFamily: 'Rubik-VariableFont_wght',
|
||||
}}>{title}</Text>
|
||||
<Image
|
||||
style={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
tintColor: 'white',
|
||||
}}
|
||||
source={require('../../res/chevron_right.png')}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const AlbumCoverList = () => {
|
||||
|
||||
}
|
||||
|
||||
const Library = () => (
|
||||
<View style={{
|
||||
flex: 1,
|
||||
backgroundColor: '#3b3b3b',
|
||||
}}>
|
||||
<SectionHeader title='Albums' />
|
||||
<SectionHeader title='Artists' />
|
||||
<SectionHeader title='Playlists' />
|
||||
</View>
|
||||
);
|
||||
|
||||
export default Library;
|
||||
23
src/components/RootNavigator.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import { createStackNavigator } from '@react-navigation/stack';
|
||||
import TabNavigator from './TabNavigator';
|
||||
import NowPlayingLayout from './NowPlayingLayout';
|
||||
|
||||
const RootStack = createStackNavigator();
|
||||
|
||||
const RootNavigator = () => (
|
||||
<RootStack.Navigator>
|
||||
<RootStack.Screen
|
||||
name='Main'
|
||||
component={TabNavigator}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
<RootStack.Screen
|
||||
name='Now Playing'
|
||||
component={NowPlayingLayout}
|
||||
options={{ headerShown: false }}
|
||||
/>
|
||||
</RootStack.Navigator>
|
||||
);
|
||||
|
||||
export default RootNavigator;
|
||||
@ -7,6 +7,7 @@ import { musicDb, settingsDb } from '../clients';
|
||||
import { appSettingsState, serversState } from '../state/settings';
|
||||
import { DbStorage } from '../storage/db';
|
||||
import { StackScreenProps } from '@react-navigation/stack';
|
||||
import { useNavigation } from '@react-navigation/core';
|
||||
|
||||
const RecreateDbButton: React.FC<{ db: DbStorage, title: string }> = ({ db, title }) => {
|
||||
const [inProgress, setInProgress] = useState(false);
|
||||
@ -31,10 +32,15 @@ const RecreateDbButton: React.FC<{ db: DbStorage, title: string }> = ({ db, titl
|
||||
}
|
||||
|
||||
const DbControls = () => {
|
||||
const navigation = useNavigation();
|
||||
return (
|
||||
<View>
|
||||
<RecreateDbButton db={musicDb} title='Music' />
|
||||
<RecreateDbButton db={settingsDb} title='Settings' />
|
||||
<Button
|
||||
title='Now Playing'
|
||||
onPress={() => navigation.navigate('Now Playing')}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
58
src/components/TabNavigator.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
||||
import SettingsView from './Settings';
|
||||
import NowPlayingLayout from './NowPlayingLayout';
|
||||
import ArtistsList from './ArtistsList';
|
||||
import FocusableIcon from './FocusableIcon';
|
||||
import Library from './Library';
|
||||
|
||||
const Tab = createBottomTabNavigator();
|
||||
|
||||
const TabNavigator = () => {
|
||||
return (
|
||||
<Tab.Navigator>
|
||||
<Tab.Screen
|
||||
name='Home'
|
||||
component={ArtistsList}
|
||||
options={{
|
||||
tabBarIcon: ({ focused }) => FocusableIcon({ focused,
|
||||
source: require('../../res/home.png'),
|
||||
focusedSource: require('../../res/home-fill.png'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Library'
|
||||
component={Library}
|
||||
options={{
|
||||
tabBarIcon: ({ focused }) => FocusableIcon({ focused,
|
||||
source: require('../../res/library.png'),
|
||||
focusedSource: require('../../res/library-fill.png'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Search'
|
||||
component={NowPlayingLayout}
|
||||
options={{
|
||||
tabBarIcon: ({ focused }) => FocusableIcon({ focused,
|
||||
source: require('../../res/search.png'),
|
||||
focusedSource: require('../../res/search-fill.png'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Settings'
|
||||
component={SettingsView}
|
||||
options={{
|
||||
tabBarIcon: ({ focused }) => FocusableIcon({ focused,
|
||||
source: require('../../res/settings.png'),
|
||||
focusedSource: require('../../res/settings-fill.png'),
|
||||
}),
|
||||
}}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
);
|
||||
}
|
||||
|
||||
export default TabNavigator;
|
||||