image optimizations

This commit is contained in:
austinried 2021-06-25 11:05:44 +09:00
parent b1944f7791
commit c8ed5bf5cb
8 changed files with 109 additions and 2468 deletions

2441
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@
"md5": "^2.3.0",
"react": "17.0.1",
"react-native": "0.64.1",
"react-native-fast-image": "^8.3.4",
"react-native-fs": "^2.18.0",
"react-native-gesture-handler": "^1.10.3",
"react-native-get-random-values": "^1.7.0",

View File

@ -3,6 +3,7 @@ import { Text, View, Image, Pressable } from 'react-native';
import { BottomTabBarProps } from '@react-navigation/bottom-tabs';
import textStyles from '../../styles/text';
import colors from '../../styles/colors';
import FastImage from 'react-native-fast-image';
const icons: {[key: string]: any} = {
home: {
@ -66,13 +67,13 @@ const BottomTabBar: React.FC<BottomTabBarProps> = ({ state, descriptors, navigat
flex: 1,
}}
>
<Image
<FastImage
source={isFocused ? img.fill : img.regular}
style={{
height: 26,
width: 26,
tintColor: isFocused ? colors.text.primary : colors.text.secondary,
}}
tintColor={isFocused ? colors.text.primary : colors.text.secondary}
/>
<Text style={{
...textStyles.xsmall,

View File

@ -1,62 +1,45 @@
import React, { memo, useEffect, useState } from 'react';
import { View, Image, Text, FlatList, Button, ListRenderItem } from 'react-native';
import { useRecoilState, useRecoilValue } from 'recoil';
import { Album } from '../../models/music';
import { albumsState, albumState, useUpdateAlbums, albumIdsState, useCoverArtUri } from '../../state/albums';
import TopTabContainer from '../common/TopTabContainer';
import textStyles from '../../styles/text';
import { ScrollView } from 'react-native-gesture-handler';
import colors from '../../styles/colors';
import React, { useEffect, useState } from 'react';
import { FlatList, Text, View } from 'react-native';
import FastImage from 'react-native-fast-image';
import LinearGradient from 'react-native-linear-gradient';
import { useRecoilValue } from 'recoil';
import { Album } from '../../models/music';
import { albumsState, useCoverArtUri, useUpdateAlbums } from '../../state/albums';
import colors from '../../styles/colors';
import textStyles from '../../styles/text';
import TopTabContainer from '../common/TopTabContainer';
const AlbumArt: React.FC<{ height: number, width: number, id?: string }> = ({ height, width, id }) => {
const coverArtSource = useCoverArtUri(id);
// useEffect(() => {
// console.log(id);
// });
const coverArtUri = useCoverArtUri(id);
const Placeholder = (
<LinearGradient
colors={[colors.accent, colors.accentLow]}
style={{
height, width,
}}
style={{ height, width }}
>
<Image
<FastImage
source={require('../../../res/record-m.png')}
style={{
height, width,
resizeMode: 'contain',
}}
style={{ height, width }}
resizeMode={FastImage.resizeMode.contain}
/>
</LinearGradient>
);
const CoverArt = (
<View style={{
height, width,
}}>
<Image
source={{ uri: coverArtSource }}
style={{
height, width,
resizeMode: 'contain',
}}
<View style={{ height, width }}>
<FastImage
source={{ uri: coverArtUri }}
style={{ height, width }}
resizeMode={FastImage.resizeMode.contain}
/>
</View>
);
return coverArtSource ? CoverArt : Placeholder;
return coverArtUri ? CoverArt : Placeholder;
}
const MemoAlbumArt = React.memo(AlbumArt);
const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
const album = useRecoilValue(albumState(id));
// useEffect(() => {
// console.log(album.name);
// });
const AlbumItem: React.FC<{ name: string, coverArt?: string } > = ({ name, coverArt }) => {
const size = 125;
return (
@ -68,10 +51,10 @@ const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
// width: size,
flex: 1/3,
}}>
<AlbumArt
<MemoAlbumArt
width={size}
height={size}
id={album.coverArt}
id={coverArt}
/>
<View style={{
flex: 1,
@ -85,7 +68,7 @@ const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
}}
numberOfLines={2}
>
{album.name}
{name}
</Text>
<Text
style={{
@ -94,28 +77,24 @@ const AlbumItem: React.FC<{ id: string } > = ({ id }) => {
}}
numberOfLines={1}
>
{album.name}
{name}
</Text>
</View>
</View>
);
}
const MemoAlbumItem = React.memo(AlbumItem);
const MemoAlbumItem = memo(AlbumItem, (prev, next) => {
// console.log('prev: ' + JSON.stringify(prev) + ' next: ' + JSON.stringify(next))
return prev.id == next.id;
});
const AlbumListRenderItem: React.FC<{ item: Album }> = ({ item }) => (
<MemoAlbumItem name={item.name} coverArt={item.coverArt} />
);
const AlbumsList = () => {
const albumIds = useRecoilValue(albumIdsState);
const albums = useRecoilValue(albumsState);
const updateAlbums = useUpdateAlbums();
const [refreshing, setRefreshing] = useState(false);
const renderItem: React.FC<{ item: string }> = ({ item }) => (
<MemoAlbumItem id={item} />
);
const refresh = async () => {
setRefreshing(true);
await updateAlbums();
@ -123,7 +102,7 @@ const AlbumsList = () => {
}
useEffect(() => {
if (!refreshing && albumIds.length === 0) {
if (!refreshing && Object.keys(albums).length === 0) {
refresh();
}
});
@ -131,13 +110,13 @@ const AlbumsList = () => {
return (
<View style={{ flex: 1 }}>
<FlatList
data={albumIds}
renderItem={renderItem}
keyExtractor={item => item}
data={Object.values(albums)}
renderItem={AlbumListRenderItem}
keyExtractor={item => item.id}
onRefresh={refresh}
refreshing={refreshing}
numColumns={3}
removeClippedSubviews={false}
removeClippedSubviews={true}
/>
</View>
);
@ -151,4 +130,4 @@ const AlbumsTab = () => (
</TopTabContainer>
);
export default AlbumsTab;
export default React.memo(AlbumsTab);

View File

@ -10,6 +10,4 @@ export interface Album {
name: string;
starred?: Date;
coverArt?: string;
coverArtPath?: string;
coverArtModified?: Date;
}

View File

@ -1,4 +1,4 @@
import { atom, DefaultValue, selector, selectorFamily, useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil';
import { atom, DefaultValue, selector, selectorFamily, useRecoilValue, useSetRecoilState } from 'recoil';
import { SubsonicApiClient } from '../subsonic/api';
import { activeServer } from './settings'
import { Album } from '../models/music';
@ -23,21 +23,11 @@ export const albumsState = atom<{ [id: string]: Album }>({
],
});
export const albumIdsState = selector<string[]>({
key: 'albumIdsState',
get: ({get}) => Object.keys(get(albumsState)),
});
export const albumState = selectorFamily<Album, string>({
key: 'albumState',
get: id => ({ get }) => {
return get(albumsState)[id];
},
// set: id => ({ set, get }, newValue) => {
// if (!(newValue instanceof DefaultValue)) {
// set(albumsState, prevState => ({ ...prevState, [id]: newValue }));
// }
// }
});
export const useUpdateAlbums = () => {
@ -49,7 +39,7 @@ export const useUpdateAlbums = () => {
return;
}
const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
const client = new SubsonicApiClient(server);
const response = await client.getAlbumList2({ type: 'alphabeticalByArtist', size: 50 });
const albums = response.data.albums.reduce((acc, x) => {
@ -65,7 +55,7 @@ export const useUpdateAlbums = () => {
};
};
export function useCoverArtUri(id: string | undefined): string | undefined {
export function useCoverArtUri(id?: string): string | undefined {
if (!id) {
return undefined;
}
@ -100,7 +90,7 @@ export function useCoverArtUri(id: string | undefined): string | undefined {
return;
}
const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
const client = new SubsonicApiClient(server);
await client.getCoverArt({ id });
setCoverArtSource(fileUri);

View File

@ -30,7 +30,7 @@ export const useUpdateArtists = () => {
return;
}
const client = new SubsonicApiClient(server.address, server.username, server.token, server.salt);
const client = new SubsonicApiClient(server);
const response = await client.getArtists();
setArtists(response.data.artists.map(x => ({

View File

@ -2,6 +2,7 @@ import { DOMParser } from 'xmldom';
import RNFS from 'react-native-fs';
import { GetAlbumList2Params, GetAlbumListParams, GetArtistInfo2Params, GetArtistInfoParams, GetCoverArtParams, GetIndexesParams } from './params';
import { GetAlbumList2Response, GetAlbumListResponse, GetArtistInfo2Response, GetArtistInfoResponse, GetArtistsResponse, GetIndexesResponse, SubsonicResponse } from './responses';
import { ServerSettings } from '../models/settings';
export class SubsonicApiError extends Error {
method: string;
@ -56,14 +57,14 @@ export class SubsonicApiClient {
private params: URLSearchParams
constructor(address: string, username: string, token: string, salt: string) {
this.address = address;
this.username = username;
constructor(server: ServerSettings) {
this.address = server.address;
this.username = server.username;
this.params = new URLSearchParams();
this.params.append('u', username);
this.params.append('t', token);
this.params.append('s', salt);
this.params.append('u', server.username);
this.params.append('t', server.token);
this.params.append('s', server.salt);
this.params.append('v', '1.15.0');
this.params.append('c', 'subsonify-cool-unique-app-string')
}