mirror of
https://github.com/austinried/subtracks.git
synced 2026-02-10 23:02:43 +01:00
moooore styling
started albums these commits are messy
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Image, ImageSourcePropType } from 'react-native';
|
||||
import { primary } from '../styles/colors';
|
||||
|
||||
export type FocusableIconProps = {
|
||||
focused: boolean,
|
||||
@@ -11,12 +12,16 @@ export type FocusableIconProps = {
|
||||
|
||||
const FocusableIcon: React.FC<FocusableIconProps> = (props) => {
|
||||
props.focusedSource = props.focusedSource || props.source;
|
||||
props.width = props.width || 32;
|
||||
props.height = props.height || 32;
|
||||
props.width = props.width || 26;
|
||||
props.height = props.height || 26;
|
||||
|
||||
return (
|
||||
<Image
|
||||
style={{ height: props.height, width: props.width }}
|
||||
style={{
|
||||
height: props.height,
|
||||
width: props.width,
|
||||
tintColor: props.focused ? primary.focused : primary.blurred,
|
||||
}}
|
||||
source={props.focused ? props.focusedSource : props.source}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,63 +1,144 @@
|
||||
import React from 'react';
|
||||
import { Text, View, Image } from 'react-native';
|
||||
import { createMaterialTopTabNavigator } from '@react-navigation/material-top-tabs';
|
||||
import React, { ReactComponentElement } from 'react';
|
||||
import { Text, View, Image, FlatList } from 'react-native';
|
||||
import { createMaterialTopTabNavigator, MaterialTopTabBarProps } from '@react-navigation/material-top-tabs';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { Artist } from '../models/music';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { artistsState } from '../state/artists';
|
||||
import LinearGradient from 'react-native-linear-gradient';
|
||||
import { primary } from '../styles/colors';
|
||||
import text from '../styles/text';
|
||||
|
||||
const SectionHeader: React.FC<{ title: string }> = ({ title }) => {
|
||||
const TabView: React.FC<{}> = ({ children }) => (
|
||||
<LinearGradient
|
||||
colors={['#383838', '#000000']}
|
||||
// colors={['#395266', '#06172d']}
|
||||
// start={{x: 0.0, y: 0.25}} end={{x: 0.5, y: 1.0}}
|
||||
locations={[0.05,0.75]}
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</LinearGradient>
|
||||
);
|
||||
|
||||
const Albums = () => (
|
||||
<TabView>
|
||||
</TabView>
|
||||
);
|
||||
const Playlists = () => (
|
||||
<TabView>
|
||||
</TabView>
|
||||
);
|
||||
|
||||
const ArtistItem: React.FC<{ item: Artist } > = ({ item }) => (
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
// height: 56,
|
||||
marginVertical: 6,
|
||||
marginLeft: 6,
|
||||
}}>
|
||||
<Image
|
||||
source={item.coverArt ? { uri: 'https://reactnative.dev/img/tiny_logo.png' } : require('../../res/mic_on-fill.png')}
|
||||
style={{
|
||||
width: 56,
|
||||
height: 56,
|
||||
// tintColor: 'white',
|
||||
}}
|
||||
/>
|
||||
<Text style={{
|
||||
...text.regular,
|
||||
marginLeft: 12,
|
||||
}}>{item.name}</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const Artists = () => {
|
||||
const artists = useRecoilValue(artistsState);
|
||||
|
||||
const renderItem: React.FC<{ item: Artist }> = ({ item }) => (
|
||||
<ArtistItem item={item} />
|
||||
);
|
||||
|
||||
return (
|
||||
<TabView>
|
||||
<FlatList
|
||||
data={artists}
|
||||
renderItem={renderItem}
|
||||
keyExtractor={item => item.id}
|
||||
/>
|
||||
</TabView>
|
||||
);
|
||||
}
|
||||
|
||||
const TabBar: React.FC<MaterialTopTabBarProps> = ({ state, descriptors }) => {
|
||||
return (
|
||||
<View style={{
|
||||
height: 60,
|
||||
height: 48,
|
||||
backgroundColor: '#383838',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
// backgroundColor: 'green',
|
||||
alignItems: 'center',
|
||||
paddingLeft: 15,
|
||||
paddingRight: 15,
|
||||
justifyContent: 'space-around',
|
||||
}}>
|
||||
<Text style={{
|
||||
color: 'white',
|
||||
fontSize: 22,
|
||||
fontFamily: 'Lato-Black',
|
||||
}}>{title}</Text>
|
||||
<Image
|
||||
style={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
tintColor: 'white',
|
||||
}}
|
||||
source={require('../../res/chevron_right-fill.png')}
|
||||
/>
|
||||
{state.routes.map((route, index) => {
|
||||
const { options } = descriptors[route.key];
|
||||
const label =
|
||||
options.tabBarLabel !== undefined
|
||||
? options.tabBarLabel
|
||||
: options.title !== undefined
|
||||
? options.title
|
||||
: route.name;
|
||||
|
||||
const isFocused = state.index === index;
|
||||
const fontFamily = isFocused ? 'Ubuntu-Regular' : 'Ubuntu-Light';
|
||||
const color = isFocused ? primary.focused : primary.blurred;
|
||||
const borderBottomColor = isFocused ? primary.focused : '#383838';
|
||||
|
||||
return (
|
||||
<View style={{
|
||||
borderBottomColor,
|
||||
borderBottomWidth: 2,
|
||||
borderBottomLeftRadius: 2,
|
||||
borderBottomEndRadius: 2,
|
||||
paddingBottom: 5,
|
||||
width: 100,
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'row',
|
||||
}}>
|
||||
<Text style={{
|
||||
...text.header,
|
||||
fontFamily, color
|
||||
}}>{label}</Text>
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const Tab = createMaterialTopTabNavigator();
|
||||
|
||||
const Albums = () => (
|
||||
<SectionHeader title='Albums' />
|
||||
);
|
||||
const Artists = () => (
|
||||
<SectionHeader title='Artists' />
|
||||
);
|
||||
const Playlists = () => (
|
||||
<SectionHeader title='Playlists' />
|
||||
);
|
||||
|
||||
const Library = () => (
|
||||
<Tab.Navigator>
|
||||
<Tab.Screen
|
||||
name='Albums'
|
||||
component={Albums}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Artists'
|
||||
component={Artists}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Playlists'
|
||||
component={Playlists}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
<View style={{
|
||||
flex: 1,
|
||||
}}>
|
||||
<Tab.Navigator tabBar={TabBar}>
|
||||
<Tab.Screen
|
||||
name='Albums'
|
||||
component={Albums}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Artists'
|
||||
component={Artists}
|
||||
/>
|
||||
<Tab.Screen
|
||||
name='Playlists'
|
||||
component={Playlists}
|
||||
/>
|
||||
</Tab.Navigator>
|
||||
</View>
|
||||
);
|
||||
|
||||
export default Library;
|
||||
|
||||
@@ -10,7 +10,15 @@ const Tab = createBottomTabNavigator();
|
||||
|
||||
const TabNavigator = () => {
|
||||
return (
|
||||
<Tab.Navigator>
|
||||
<Tab.Navigator
|
||||
tabBarOptions={{
|
||||
style: {
|
||||
backgroundColor: '#383838',
|
||||
borderTopColor: '#383838',
|
||||
height: 44,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Tab.Screen
|
||||
name='Home'
|
||||
component={ArtistsList}
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
export interface Artist {
|
||||
id: string;
|
||||
name: string;
|
||||
coverArt?: string;
|
||||
}
|
||||
|
||||
export interface Album {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
41
src/state/albums.ts
Normal file
41
src/state/albums.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { atom, DefaultValue, selector, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
import { SubsonicApiClient } from '../subsonic/api';
|
||||
import { activeServer } from './settings'
|
||||
import { Artist } from '../models/music';
|
||||
import { musicDb } from '../clients';
|
||||
|
||||
export const albumsState = atom<Artist[]>({
|
||||
key: 'albumsState',
|
||||
default: selector({
|
||||
key: 'albumsState/default',
|
||||
get: () => musicDb.getAlbums(),
|
||||
}),
|
||||
effects_UNSTABLE: [
|
||||
({ onSet }) => {
|
||||
onSet((newValue) => {
|
||||
if (!(newValue instanceof DefaultValue)) {
|
||||
musicDb.updateAlbums(newValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
],
|
||||
});
|
||||
|
||||
// export const useUpdateAlbums = () => {
|
||||
// const setAlbums = useSetRecoilState(albumsState);
|
||||
// const server = useRecoilValue(activeServer);
|
||||
|
||||
// return async () => {
|
||||
// if (!server) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
|
||||
// const response = await client.getAlbums();
|
||||
|
||||
// setAlbums(response.data.albums.map(i => ({
|
||||
// id: i.id,
|
||||
// name: i.name,
|
||||
// })));
|
||||
// };
|
||||
// };
|
||||
@@ -33,9 +33,10 @@ export const useUpdateArtists = () => {
|
||||
const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
|
||||
const response = await client.getArtists();
|
||||
|
||||
setArtists(response.data.artists.map(i => ({
|
||||
id: i.id,
|
||||
name: i.name,
|
||||
setArtists(response.data.artists.map(x => ({
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
coverArt: x.coverArt,
|
||||
})));
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Artist } from '../models/music';
|
||||
import { Album, Artist } from '../models/music';
|
||||
import { DbStorage } from './db';
|
||||
|
||||
export class MusicDb extends DbStorage {
|
||||
@@ -10,6 +10,14 @@ export class MusicDb extends DbStorage {
|
||||
await this.initDb(tx => {
|
||||
tx.executeSql(`
|
||||
CREATE TABLE artists (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
starred INTEGER NOT NULL,
|
||||
coverArt TEXT
|
||||
);
|
||||
`);
|
||||
tx.executeSql(`
|
||||
CREATE TABLE albums (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
starred INTEGER NOT NULL
|
||||
@@ -24,6 +32,7 @@ export class MusicDb extends DbStorage {
|
||||
`))[0].rows.raw().map(x => ({
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
coverArt: x.coverArt || undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -34,7 +43,30 @@ export class MusicDb extends DbStorage {
|
||||
`);
|
||||
for (const a of artists) {
|
||||
tx.executeSql(`
|
||||
INSERT INTO artists (id, name, starred)
|
||||
INSERT INTO artists (id, name, starred, coverArt)
|
||||
VALUES (?, ?, ?, ?);
|
||||
`, [a.id, a.name, false, a.coverArt || null]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getAlbums(): Promise<Album[]> {
|
||||
return (await this.executeSql(`
|
||||
SELECT * FROM albums;
|
||||
`))[0].rows.raw().map(x => ({
|
||||
id: x.id,
|
||||
name: x.name,
|
||||
}));
|
||||
}
|
||||
|
||||
async updateAlbums(albums: Album[]): Promise<void> {
|
||||
await this.transaction((tx) => {
|
||||
tx.executeSql(`
|
||||
DELETE FROM albums
|
||||
`);
|
||||
for (const a of albums) {
|
||||
tx.executeSql(`
|
||||
INSERT INTO albums (id, name, starred)
|
||||
VALUES (?, ?, ?);
|
||||
`, [a.id, a.name, false]);
|
||||
}
|
||||
|
||||
6
src/styles/colors.ts
Normal file
6
src/styles/colors.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { StyleSheet } from "react-native";
|
||||
|
||||
export const primary = {
|
||||
focused: '#fff',
|
||||
blurred: '#bababa',
|
||||
}
|
||||
21
src/styles/text.ts
Normal file
21
src/styles/text.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { StyleSheet, TextStyle } from "react-native";
|
||||
|
||||
const regular: TextStyle = {
|
||||
fontFamily: 'Ubuntu-Light',
|
||||
fontSize: 18,
|
||||
color: '#fff',
|
||||
};
|
||||
|
||||
const header: TextStyle = {
|
||||
...regular,
|
||||
fontSize: 22,
|
||||
};
|
||||
|
||||
export type TextStyles = {
|
||||
[key: string]: TextStyle,
|
||||
}
|
||||
|
||||
export default {
|
||||
regular,
|
||||
header,
|
||||
};
|
||||
Reference in New Issue
Block a user