First overhaul of search screen
This commit is contained in:
@@ -7,13 +7,29 @@ import store, { persistedStore } from 'store';
|
|||||||
import {
|
import {
|
||||||
NavigationContainer,
|
NavigationContainer,
|
||||||
DefaultTheme,
|
DefaultTheme,
|
||||||
DarkTheme,
|
DarkTheme as BaseDarkTheme,
|
||||||
} from '@react-navigation/native';
|
} from '@react-navigation/native';
|
||||||
import { useColorScheme } from 'react-native';
|
import { useColorScheme } from 'react-native';
|
||||||
import { ColorSchemeContext, themes } from './Colors';
|
import { ColorSchemeContext, themes } from './Colors';
|
||||||
import DownloadManager from './DownloadManager';
|
import DownloadManager from './DownloadManager';
|
||||||
// import ErrorReportingAlert from 'utility/ErrorReportingAlert';
|
// import ErrorReportingAlert from 'utility/ErrorReportingAlert';
|
||||||
|
|
||||||
|
const LightTheme = {
|
||||||
|
...DefaultTheme,
|
||||||
|
colors: {
|
||||||
|
...DefaultTheme.colors,
|
||||||
|
background: themes.light.view.backgroundColor,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const DarkTheme = {
|
||||||
|
...BaseDarkTheme,
|
||||||
|
colors: {
|
||||||
|
...BaseDarkTheme.colors,
|
||||||
|
background: themes.light.view.backgroundColor,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export default function App(): JSX.Element {
|
export default function App(): JSX.Element {
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
// const colorScheme = 'dark';
|
// const colorScheme = 'dark';
|
||||||
@@ -41,7 +57,9 @@ export default function App(): JSX.Element {
|
|||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<PersistGate loading={null} persistor={persistedStore}>
|
<PersistGate loading={null} persistor={persistedStore}>
|
||||||
<ColorSchemeContext.Provider value={theme}>
|
<ColorSchemeContext.Provider value={theme}>
|
||||||
<NavigationContainer theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<NavigationContainer
|
||||||
|
theme={colorScheme === 'dark' ? DarkTheme : LightTheme}
|
||||||
|
>
|
||||||
<Routes />
|
<Routes />
|
||||||
<DownloadManager />
|
<DownloadManager />
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
|
|||||||
@@ -48,13 +48,9 @@ function generateStyles(scheme: ColorSchemeName) {
|
|||||||
backgroundColor: scheme === 'dark' ? '#161616' : '#eee',
|
backgroundColor: scheme === 'dark' ? '#161616' : '#eee',
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: scheme === 'dark' ? '#161616' : '#e6e6e6',
|
backgroundColor: scheme === 'dark' ? '#161616' : '#f3f3f3',
|
||||||
color: scheme === 'dark' ? '#fff' : '#000',
|
color: scheme === 'dark' ? '#fff' : '#000',
|
||||||
},
|
},
|
||||||
sectionHeading: {
|
|
||||||
backgroundColor: scheme === 'dark' ? '#111' : '#eee',
|
|
||||||
borderColor: scheme === 'dark' ? '#333' : '#ddd',
|
|
||||||
},
|
|
||||||
stackHeader: {
|
stackHeader: {
|
||||||
color: scheme === 'dark' ? 'white' : 'black'
|
color: scheme === 'dark' ? 'white' : 'black'
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const Input = styled.TextInput`
|
|||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
|
padding-left: 40px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default Input;
|
export default Input;
|
||||||
@@ -1,30 +1,24 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import { TouchableOpacityProps } from 'react-native';
|
import { TouchableOpacityProps } from 'react-native';
|
||||||
import ChevronRight from 'assets/icons/chevron-right.svg';
|
import ChevronRight from 'assets/icons/chevron-right.svg';
|
||||||
import styled, { css } from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { THEME_COLOR } from 'CONSTANTS';
|
import { THEME_COLOR } from 'CONSTANTS';
|
||||||
import useDefaultStyles from './Colors';
|
import useDefaultStyles from './Colors';
|
||||||
|
|
||||||
const BUTTON_SIZE = 14;
|
const BUTTON_SIZE = 14;
|
||||||
|
|
||||||
const Container = styled.Pressable<{ active?: boolean }>`
|
const Container = styled.Pressable<{ active?: boolean }>`
|
||||||
padding: 18px 20px;
|
padding: 14px 16px;
|
||||||
border-bottom-width: 1px;
|
border-radius: 8px;
|
||||||
|
margin: 4px 8px;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
${props => props.active && css`
|
|
||||||
background-color: ${THEME_COLOR};
|
|
||||||
`}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const Label = styled.Text<{ active?: boolean }>`
|
const Label = styled.Text<{ active?: boolean }>`
|
||||||
color: ${THEME_COLOR};
|
color: ${THEME_COLOR};
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
|
|
||||||
${props => props.active && css`
|
|
||||||
color: white;
|
|
||||||
`}
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const ListButton: React.FC<TouchableOpacityProps> = ({ children, ...props }) => {
|
const ListButton: React.FC<TouchableOpacityProps> = ({ children, ...props }) => {
|
||||||
@@ -34,16 +28,17 @@ const ListButton: React.FC<TouchableOpacityProps> = ({ children, ...props }) =>
|
|||||||
const handlePressOut = useCallback(() => setPressed(false), []);
|
const handlePressOut = useCallback(() => setPressed(false), []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// @ts-expect-error styled-components has outdated react-native typings
|
|
||||||
<Container
|
<Container
|
||||||
{...props}
|
{...props}
|
||||||
onPressIn={handlePressIn}
|
onPressIn={handlePressIn}
|
||||||
onPressOut={handlePressOut}
|
onPressOut={handlePressOut}
|
||||||
style={defaultStyles.border}
|
style={[
|
||||||
active={isPressed}
|
defaultStyles.border,
|
||||||
|
isPressed ? defaultStyles.activeBackground : undefined
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<Label active={isPressed}>{children}</Label>
|
<Label>{children}</Label>
|
||||||
<ChevronRight width={BUTTON_SIZE} height={BUTTON_SIZE} fill={isPressed ? '#fff' : THEME_COLOR} />
|
<ChevronRight width={BUTTON_SIZE} height={BUTTON_SIZE} fill={THEME_COLOR} />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ const DownloadedTrack = styled.View`
|
|||||||
padding: 8px 0;
|
padding: 8px 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 0 20px;
|
margin: 0 20px;
|
||||||
border-bottom-width: 1px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function Downloads() {
|
function Downloads() {
|
||||||
@@ -87,7 +86,7 @@ function Downloads() {
|
|||||||
), [totalDownloadSize, defaultStyles, failedIds.length, handleRetryFailed, handleDeleteAllTracks, ids.length]);
|
), [totalDownloadSize, defaultStyles, failedIds.length, handleRetryFailed, handleDeleteAllTracks, ids.length]);
|
||||||
|
|
||||||
const renderItem = useCallback<NonNullable<FlatListProps<EntityId>['renderItem']>>(({ item }) => (
|
const renderItem = useCallback<NonNullable<FlatListProps<EntityId>['renderItem']>>(({ item }) => (
|
||||||
<DownloadedTrack style={defaultStyles.border}>
|
<DownloadedTrack>
|
||||||
<View style={{ marginRight: 12 }}>
|
<View style={{ marginRight: 12 }}>
|
||||||
<DownloadIcon trackId={item} />
|
<DownloadIcon trackId={item} />
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useCallback, useEffect, useRef, ReactText } from 'react';
|
import React, { useCallback, useEffect, useRef, ReactText } from 'react';
|
||||||
import { useGetImage } from 'utility/JellyfinApi';
|
import { useGetImage } from 'utility/JellyfinApi';
|
||||||
import { MusicNavigationProp } from '../types';
|
import { MusicNavigationProp } from '../types';
|
||||||
import { Text, SafeAreaView, SectionList, View } from 'react-native';
|
import { SafeAreaView, SectionList, View } from 'react-native';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { differenceInDays } from 'date-fns';
|
import { differenceInDays } from 'date-fns';
|
||||||
@@ -9,13 +9,16 @@ 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 AlbumImage, { AlbumItem } from './components/AlbumImage';
|
import AlbumImage, { AlbumHeight, 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 useDefaultStyles from 'components/Colors';
|
import useDefaultStyles, { ColoredBlurView } from 'components/Colors';
|
||||||
import { Album } from 'store/music/types';
|
import { Album } from 'store/music/types';
|
||||||
|
import { Text } from 'components/Typography';
|
||||||
|
|
||||||
|
const HeadingHeight = 50;
|
||||||
|
|
||||||
interface VirtualizedItemInfo {
|
interface VirtualizedItemInfo {
|
||||||
section: SectionedId,
|
section: SectionedId,
|
||||||
@@ -40,25 +43,25 @@ function generateSection({ section }: { section: SectionedId }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const SectionContainer = styled.View`
|
const SectionContainer = styled.View`
|
||||||
border-bottom-width: 1px;
|
height: ${HeadingHeight}px;
|
||||||
height: 50px;
|
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 0 10px;
|
padding: 0 24px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SectionText = styled.Text`
|
const SectionText = styled(Text)`
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
font-weight: bold;
|
font-weight: 400;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SectionHeading = React.memo(function SectionHeading(props: { label: string }) {
|
const SectionHeading = React.memo(function SectionHeading(props: { label: string }) {
|
||||||
const defaultStyles = useDefaultStyles();
|
|
||||||
const { label } = props;
|
const { label } = props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SectionContainer style={defaultStyles.sectionHeading}>
|
<ColoredBlurView>
|
||||||
<SectionText style={defaultStyles.text}>{label}</SectionText>
|
<SectionContainer>
|
||||||
</SectionContainer>
|
<SectionText>{label}</SectionText>
|
||||||
|
</SectionContainer>
|
||||||
|
</ColoredBlurView>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -118,21 +121,20 @@ const Albums: React.FC = () => {
|
|||||||
|
|
||||||
// We can then determine the "length" (=height) of this item. Header items
|
// We can then determine the "length" (=height) of this item. Header items
|
||||||
// end up with an itemIndex of -1, thus are easy to identify.
|
// end up with an itemIndex of -1, thus are easy to identify.
|
||||||
const length = header ? 50 : (itemIndex % 2 === 0 ? 220 : 0);
|
const length = header ? 50 : (itemIndex % 2 === 0 ? AlbumHeight : 0);
|
||||||
|
|
||||||
// We'll also need to account for any unevenly-ended lists up until the
|
// We'll also need to account for any unevenly-ended lists up until the
|
||||||
// current item.
|
// current item.
|
||||||
const previousRows = data?.filter((row, i) => i < sectionIndex)
|
const previousRows = data?.filter((row, i) => i < sectionIndex)
|
||||||
.reduce((sum, row) => sum + Math.ceil(row.data.length / 2), 0) || 0;
|
.reduce((sum, row) => sum + Math.ceil(row.data.length / 2), 0) || 0;
|
||||||
|
|
||||||
|
|
||||||
// We must also calcuate the offset, total distance from the top of the
|
// We must also calcuate the offset, total distance from the top of the
|
||||||
// screen. First off, we'll account for each sectionIndex that is shown up
|
// screen. First off, we'll account for each sectionIndex that is shown up
|
||||||
// until now. This only includes the heading for the current section if the
|
// until now. This only includes the heading for the current section if the
|
||||||
// item is not the section header
|
// item is not the section header
|
||||||
const headingOffset = 50 * (header ? sectionIndex : sectionIndex + 1);
|
const headingOffset = HeadingHeight * (header ? sectionIndex : sectionIndex + 1);
|
||||||
const currentRows = itemIndex > 1 ? Math.ceil((itemIndex + 1) / 2) : 0;
|
const currentRows = itemIndex > 1 ? Math.ceil((itemIndex + 1) / 2) : 0;
|
||||||
const itemOffset = 220 * (previousRows + currentRows);
|
const itemOffset = AlbumHeight * (previousRows + currentRows);
|
||||||
const offset = headingOffset + itemOffset;
|
const offset = headingOffset + itemOffset;
|
||||||
|
|
||||||
return { index, length, offset };
|
return { index, length, offset };
|
||||||
@@ -189,7 +191,7 @@ const Albums: React.FC = () => {
|
|||||||
onRefresh={retrieveData}
|
onRefresh={retrieveData}
|
||||||
getItemLayout={getItemLayout}
|
getItemLayout={getItemLayout}
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
keyExtractor={(item, index) => `${item}_${index}`}
|
keyExtractor={(item) => item as string}
|
||||||
renderSectionHeader={generateSection}
|
renderSectionHeader={generateSection}
|
||||||
renderItem={generateItem}
|
renderItem={generateItem}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,17 +15,23 @@ import ListButton from 'components/ListButton';
|
|||||||
import { t } from '@localisation';
|
import { t } from '@localisation';
|
||||||
import useDefaultStyles from 'components/Colors';
|
import useDefaultStyles from 'components/Colors';
|
||||||
import { Album } from 'store/music/types';
|
import { Album } from 'store/music/types';
|
||||||
|
import Divider from 'components/Divider';
|
||||||
|
import styled from 'styled-components/native';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
columnWrapper: {
|
columnWrapper: {
|
||||||
paddingLeft: 10,
|
paddingHorizontal: 16,
|
||||||
paddingRight: 10
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const HeaderContainer = styled.View`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
`;
|
||||||
|
|
||||||
const NavigationHeader: React.FC = () => {
|
const NavigationHeader: React.FC = () => {
|
||||||
const navigation = useNavigation<MusicNavigationProp>();
|
const navigation = useNavigation<MusicNavigationProp>();
|
||||||
const defaultStyles = useDefaultStyles();
|
|
||||||
const handleAllAlbumsClick = useCallback(() => { navigation.navigate('Albums'); }, [navigation]);
|
const handleAllAlbumsClick = useCallback(() => { navigation.navigate('Albums'); }, [navigation]);
|
||||||
const handlePlaylistsClick = useCallback(() => { navigation.navigate('Playlists'); }, [navigation]);
|
const handlePlaylistsClick = useCallback(() => { navigation.navigate('Playlists'); }, [navigation]);
|
||||||
|
|
||||||
@@ -34,7 +40,10 @@ const NavigationHeader: React.FC = () => {
|
|||||||
<ListButton onPress={handleAllAlbumsClick}>{t('all-albums')}</ListButton>
|
<ListButton onPress={handleAllAlbumsClick}>{t('all-albums')}</ListButton>
|
||||||
<ListButton onPress={handlePlaylistsClick}>{t('playlists')}</ListButton>
|
<ListButton onPress={handlePlaylistsClick}>{t('playlists')}</ListButton>
|
||||||
<ListContainer>
|
<ListContainer>
|
||||||
<Header style={defaultStyles.text}>{t('recent-albums')}</Header>
|
<HeaderContainer>
|
||||||
|
<Header>{t('recent-albums')}</Header>
|
||||||
|
<Divider style={{ marginLeft: 24 }} />
|
||||||
|
</HeaderContainer>
|
||||||
</ListContainer>
|
</ListContainer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,17 +3,20 @@ import FastImage from 'react-native-fast-image';
|
|||||||
import { Dimensions } from 'react-native';
|
import { Dimensions } from 'react-native';
|
||||||
|
|
||||||
const Screen = Dimensions.get('screen');
|
const Screen = Dimensions.get('screen');
|
||||||
|
export const AlbumWidth = Screen.width / 2 - 24;
|
||||||
|
export const AlbumHeight = AlbumWidth + 40;
|
||||||
|
export const CoverSize = AlbumWidth - 16;
|
||||||
|
|
||||||
export const AlbumItem = styled.View`
|
export const AlbumItem = styled.View`
|
||||||
width: ${Screen.width / 2 - 10}px;
|
width: ${AlbumWidth}px;
|
||||||
padding: 10px;
|
height: ${AlbumHeight}px;
|
||||||
height: 220px;
|
padding: 8px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const AlbumImage = styled(FastImage)`
|
const AlbumImage = styled(FastImage)`
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
width: ${Screen.width / 2 - 40}px;
|
width: ${CoverSize}px;
|
||||||
height: ${Screen.width / 2 - 40}px;
|
height: ${CoverSize}px;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
|
|
||||||
const ListContainer = styled.View`
|
const ListContainer = styled.View`
|
||||||
padding: 10px;
|
padding: 24px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default ListContainer;
|
export default ListContainer;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import Input from 'components/Input';
|
import Input from 'components/Input';
|
||||||
import { ActivityIndicator, SafeAreaView, Text, TextInput, View } from 'react-native';
|
import { ActivityIndicator, SafeAreaView, TextInput, View } from 'react-native';
|
||||||
import styled from 'styled-components/native';
|
import styled from 'styled-components/native';
|
||||||
import { useTypedSelector } from 'store';
|
import { useTypedSelector } from 'store';
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
@@ -16,9 +16,14 @@ import useDefaultStyles from 'components/Colors';
|
|||||||
import { searchAndFetchAlbums } from 'store/music/actions';
|
import { searchAndFetchAlbums } from 'store/music/actions';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { Text } from 'components/Typography';
|
||||||
|
import DownloadIcon from 'components/DownloadIcon';
|
||||||
|
import ChevronRight from 'assets/icons/chevron-right.svg';
|
||||||
|
import SearchIcon from 'assets/icons/magnifying-glass.svg';
|
||||||
|
|
||||||
|
|
||||||
const Container = styled.View`
|
const Container = styled.View`
|
||||||
padding: 0 20px;
|
padding: 0 32px;
|
||||||
position: relative;
|
position: relative;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -37,8 +42,8 @@ const Loading = styled.View`
|
|||||||
|
|
||||||
const AlbumImage = styled(FastImage)`
|
const AlbumImage = styled(FastImage)`
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
width: 25px;
|
width: 32px;
|
||||||
height: 25px;
|
height: 32px;
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -46,15 +51,14 @@ const HalfOpacity = styled.Text`
|
|||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
flex: 1 1 auto;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const SearchResult = styled.View`
|
const SearchResult = styled.View`
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom-width: 1px;
|
padding: 8px 32px;
|
||||||
margin-left: 15px;
|
height: 54px;
|
||||||
padding-right: 15px;
|
|
||||||
height: 50px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const fuseOptions = {
|
const fuseOptions = {
|
||||||
@@ -208,13 +212,13 @@ export default function Search() {
|
|||||||
const HeaderComponent = React.useMemo(() => (
|
const HeaderComponent = React.useMemo(() => (
|
||||||
<Container>
|
<Container>
|
||||||
<Input
|
<Input
|
||||||
// @ts-expect-error styled-components has outdated react-native typings
|
|
||||||
ref={searchElement}
|
ref={searchElement}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChangeText={setSearchTerm}
|
onChangeText={setSearchTerm}
|
||||||
style={defaultStyles.input}
|
style={defaultStyles.input}
|
||||||
placeholder={t('search') + '...'}
|
placeholder={t('search') + '...'}
|
||||||
/>
|
/>
|
||||||
|
<SearchIcon width={14} height={14} fill={defaultStyles.textHalfOpacity.color} style={{ position: 'absolute', left: 48, top: 26}} />
|
||||||
{isLoading && <Loading><ActivityIndicator /></Loading>}
|
{isLoading && <Loading><ActivityIndicator /></Loading>}
|
||||||
</Container>
|
</Container>
|
||||||
), [searchTerm, setSearchTerm, defaultStyles, isLoading]);
|
), [searchTerm, setSearchTerm, defaultStyles, isLoading]);
|
||||||
@@ -249,16 +253,25 @@ export default function Search() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<TouchableHandler<string> id={album.Id} onPress={selectAlbum}>
|
<TouchableHandler<string> id={album.Id} onPress={selectAlbum}>
|
||||||
<SearchResult style={defaultStyles.border}>
|
<SearchResult>
|
||||||
<AlbumImage source={{ uri: getImage(album.Id) }} />
|
<AlbumImage source={{ uri: getImage(album.Id) }} />
|
||||||
<View>
|
<View style={{ flex: 1 }}>
|
||||||
<Text numberOfLines={1} ellipsizeMode="tail" style={defaultStyles.text}>
|
<Text numberOfLines={1}>
|
||||||
{trackName || album.Name} - {album.AlbumArtist}
|
{trackName || album.Name}
|
||||||
</Text>
|
</Text>
|
||||||
<HalfOpacity style={defaultStyles.text}>
|
<HalfOpacity style={defaultStyles.text} numberOfLines={1}>
|
||||||
{type === 'AlbumArtist' ? t('album'): t('track')}
|
{type === 'AlbumArtist'
|
||||||
|
? `${t('album')} • ${album.AlbumArtist}`
|
||||||
|
: `${t('track')} • ${album.AlbumArtist} — ${album.Name}`
|
||||||
|
}
|
||||||
</HalfOpacity>
|
</HalfOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
<View style={{ marginLeft: 16 }}>
|
||||||
|
<DownloadIcon trackId={id} />
|
||||||
|
</View>
|
||||||
|
<View style={{ marginLeft: 16 }}>
|
||||||
|
<ChevronRight width={14} height={14} fill={defaultStyles.textQuarterOpacity.color} />
|
||||||
|
</View>
|
||||||
</SearchResult>
|
</SearchResult>
|
||||||
</TouchableHandler>
|
</TouchableHandler>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user