feat: update tab bars with blurview

This commit is contained in:
Lei Nelissen
2023-04-22 21:58:27 +02:00
parent 4509ef1ec6
commit 7601408d49
6 changed files with 101 additions and 71 deletions

View File

@@ -2,23 +2,35 @@ import React, { useCallback, useRef } from 'react';
import { Platform, TextInput, TextInputProps } from 'react-native'; import { Platform, TextInput, TextInputProps } from 'react-native';
import styled, { css } from 'styled-components/native'; import styled, { css } from 'styled-components/native';
import useDefaultStyles from './Colors'; import useDefaultStyles from './Colors';
import { Gap } from './Utility';
export interface InputProps extends TextInputProps { export interface InputProps extends TextInputProps {
icon?: React.ReactNode; icon?: React.ReactNode;
} }
const Container = styled.Pressable` const Container = styled.Pressable<{ hasIcon?: boolean }>`
position: relative;
margin: 6px 0; margin: 6px 0;
border-radius: 8px; border-radius: 12px;
display: flex; display: flex;
flex-direction: row;
align-items: center;
${Platform.select({ ${Platform.select({
ios: css`padding: 12px;`, ios: css`padding: 12px;`,
android: css`padding: 4px 12px;`, android: css`padding: 4px 12px;`,
})} })}
${({ hasIcon }) => hasIcon && css`
padding-left: 36px;
`}
`;
const IconWrapper = styled.View`
position: absolute;
left: 0;
top: 0;
bottom: 0;
display: flex;
justify-content: center;
padding-left: 12px;
`; `;
function Input({ icon = null, style, testID, ...rest }: InputProps) { function Input({ icon = null, style, testID, ...rest }: InputProps) {
@@ -28,12 +40,17 @@ function Input({ icon = null, style, testID, ...rest }: InputProps) {
const handlePress = useCallback(() => inputRef.current?.focus(), []); const handlePress = useCallback(() => inputRef.current?.focus(), []);
return ( return (
<Container style={[defaultStyles.input, style]} onPress={handlePress} testID={`${testID}-container`} accessible={false}> <Container
style={[defaultStyles.input, style]}
onPress={handlePress}
testID={`${testID}-container`}
accessible={false}
hasIcon={!!icon}
>
{icon && ( {icon && (
<> <IconWrapper>
{icon} {icon}
<Gap size={8} /> </IconWrapper>
</>
)} )}
<TextInput <TextInput
{...rest} {...rest}

View File

@@ -15,7 +15,8 @@ export function Text(props: PropsWithChildren<TextProps>) {
export const Header = styled(Text)` export const Header = styled(Text)`
margin: 0 0 6px 0; margin: 0 0 6px 0;
font-size: 28px; font-size: 28px;
font-weight: 400; font-weight: 500;
letter-spacing: -0.3px;
`; `;
export const SubHeader = styled(Text)` export const SubHeader = styled(Text)`

View File

@@ -17,13 +17,13 @@ import { calculateProgressTranslation } from 'components/Progresstrack';
import { THEME_COLOR } from 'CONSTANTS'; import { THEME_COLOR } from 'CONSTANTS';
import { MusicNavigationProp } from 'screens/Music/types'; import { MusicNavigationProp } from 'screens/Music/types';
import { ShadowWrapper } from 'components/Shadow'; import { ShadowWrapper } from 'components/Shadow';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
const NOW_PLAYING_POPOVER_MARGIN = 6; const NOW_PLAYING_POPOVER_MARGIN = 6;
const NOW_PLAYING_POPOVER_WIDTH = Dimensions.get('screen').width - 2 * NOW_PLAYING_POPOVER_MARGIN; const NOW_PLAYING_POPOVER_WIDTH = Dimensions.get('screen').width - 2 * NOW_PLAYING_POPOVER_MARGIN;
const PopoverPosition = css` const PopoverPosition = css`
position: absolute; position: absolute;
bottom: ${NOW_PLAYING_POPOVER_MARGIN}px;
left: ${NOW_PLAYING_POPOVER_MARGIN}px; left: ${NOW_PLAYING_POPOVER_MARGIN}px;
right: ${NOW_PLAYING_POPOVER_MARGIN}px; right: ${NOW_PLAYING_POPOVER_MARGIN}px;
border-radius: 8px; border-radius: 8px;
@@ -111,6 +111,7 @@ function NowPlaying() {
const { index, track } = useCurrentTrack(); const { index, track } = useCurrentTrack();
const { buffered, position } = useProgress(); const { buffered, position } = useProgress();
const defaultStyles = useDefaultStyles(); const defaultStyles = useDefaultStyles();
const tabBarHeight = useBottomTabBarHeight();
const previousBuffered = usePrevious(buffered); const previousBuffered = usePrevious(buffered);
const previousPosition = usePrevious(position); const previousPosition = usePrevious(position);
@@ -164,7 +165,7 @@ function NowPlaying() {
} }
return ( return (
<Container> <Container style={{ bottom: tabBarHeight + NOW_PLAYING_POPOVER_MARGIN }}>
{/** TODO: Fix shadow overflow on Android */} {/** TODO: Fix shadow overflow on Android */}
{Platform.OS === 'ios' ? ( {Platform.OS === 'ios' ? (
<ShadowOverlay pointerEvents='none'> <ShadowOverlay pointerEvents='none'>

View File

@@ -11,7 +11,7 @@ import { useNavigation } from '@react-navigation/native';
import { useGetImage } from 'utility/JellyfinApi'; import { useGetImage } from 'utility/JellyfinApi';
import FastImage from 'react-native-fast-image'; import FastImage from 'react-native-fast-image';
import { t } from '@localisation'; import { t } from '@localisation';
import useDefaultStyles from 'components/Colors'; import useDefaultStyles, { ColoredBlurView } from 'components/Colors';
import { searchAndFetchAlbums } from 'store/music/actions'; import { searchAndFetchAlbums } from 'store/music/actions';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { Text } from 'components/Typography'; import { Text } from 'components/Typography';
@@ -21,6 +21,7 @@ import ChevronRight from 'assets/icons/chevron-right.svg';
import SearchIcon from 'assets/icons/magnifying-glass.svg'; import SearchIcon from 'assets/icons/magnifying-glass.svg';
import { ShadowWrapper } from 'components/Shadow'; import { ShadowWrapper } from 'components/Shadow';
import { useKeyboardHeight } from 'utility/useKeyboardHeight'; import { useKeyboardHeight } from 'utility/useKeyboardHeight';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
// import MicrophoneIcon from 'assets/icons/microphone.svg'; // import MicrophoneIcon from 'assets/icons/microphone.svg';
// import AlbumIcon from 'assets/icons/collection.svg'; // import AlbumIcon from 'assets/icons/collection.svg';
// import TrackIcon from 'assets/icons/note.svg'; // import TrackIcon from 'assets/icons/note.svg';
@@ -29,8 +30,8 @@ import { useKeyboardHeight } from 'utility/useKeyboardHeight';
// import LocalIcon from 'assets/icons/internal-drive.svg'; // import LocalIcon from 'assets/icons/internal-drive.svg';
// import SelectableFilter from './components/SelectableFilter'; // import SelectableFilter from './components/SelectableFilter';
const Container = styled(Animated.View)` const Container = styled(View)`
padding: 4px 32px 0 32px; padding: 4px 24px 0 24px;
margin-bottom: 0px; margin-bottom: 0px;
padding-bottom: 0px; padding-bottom: 0px;
border-top-width: 0.5px; border-top-width: 0.5px;
@@ -95,6 +96,7 @@ type CombinedResults = (AudioResult | AlbumResult)[];
export default function Search() { export default function Search() {
const defaultStyles = useDefaultStyles(); const defaultStyles = useDefaultStyles();
const tabBarHeight = useBottomTabBarHeight();
// Prepare state for fuse and albums // Prepare state for fuse and albums
const [fuseIsReady, setFuseReady] = useState(false); const [fuseIsReady, setFuseReady] = useState(false);
@@ -210,60 +212,61 @@ export default function Search() {
); );
const HeaderComponent = React.useMemo(() => ( const HeaderComponent = React.useMemo(() => (
<View> <Animated.View style={[
<Container style={[ { position: 'absolute', bottom: tabBarHeight, right: 0, left: 0 },
defaultStyles.border, { transform: [{ translateY: keyboardHeight }] },
defaultStyles.view, ]}>
{ transform: [{ translateY: keyboardHeight }]}, <ColoredBlurView>
]}> <Container style={[ defaultStyles.border ]}>
<View> <View>
<Input <Input
value={searchTerm} value={searchTerm}
onChangeText={setSearchTerm} onChangeText={setSearchTerm}
style={[defaultStyles.input, { marginBottom: 12 }]} style={[defaultStyles.view, { marginBottom: 12 }]}
placeholder={t('search') + '...'} placeholder={t('search') + '...'}
icon={<SearchIcon width={14} height={14} fill={defaultStyles.textHalfOpacity.color} />} icon={<SearchIcon width={14} height={14} fill={defaultStyles.textHalfOpacity.color} />}
testID="search-input" testID="search-input"
/> />
{isLoading && <Loading><ActivityIndicator /></Loading>} {isLoading && <Loading style={{ marginTop: -4 }}><ActivityIndicator /></Loading>}
</View> </View>
</Container> </Container>
{/* <ScrollView horizontal showsHorizontalScrollIndicator={false}> {/* <ScrollView horizontal showsHorizontalScrollIndicator={false}>
<View style={{ paddingHorizontal: 32, paddingBottom: 12, flex: 1, flexDirection: 'row' }}> <View style={{ paddingHorizontal: 32, paddingBottom: 12, flex: 1, flexDirection: 'row' }}>
<SelectableFilter <SelectableFilter
text="Artists" text="Artists"
icon={MicrophoneIcon} icon={MicrophoneIcon}
active active
/> />
<SelectableFilter <SelectableFilter
text="Albums" text="Albums"
icon={AlbumIcon} icon={AlbumIcon}
active={false} active={false}
/> />
<SelectableFilter <SelectableFilter
text="Tracks" text="Tracks"
icon={TrackIcon} icon={TrackIcon}
active={false} active={false}
/> />
<SelectableFilter <SelectableFilter
text="Playlist" text="Playlist"
icon={PlaylistIcon} icon={PlaylistIcon}
active={false} active={false}
/> />
<SelectableFilter <SelectableFilter
text="Streaming" text="Streaming"
icon={StreamIcon} icon={StreamIcon}
active={false} active={false}
/> />
<SelectableFilter <SelectableFilter
text="Local Playback" text="Local Playback"
icon={LocalIcon} icon={LocalIcon}
active={false} active={false}
/> />
</View> </View>
</ScrollView> */} </ScrollView> */}
</View> </ColoredBlurView>
), [searchTerm, setSearchTerm, defaultStyles, isLoading, keyboardHeight]); </Animated.View>
), [searchTerm, setSearchTerm, defaultStyles, isLoading, keyboardHeight, tabBarHeight]);
// GUARD: We cannot search for stuff unless Fuse is loaded with results. // GUARD: We cannot search for stuff unless Fuse is loaded with results.
// Therefore we delay rendering to when we are certain it's there. // Therefore we delay rendering to when we are certain it's there.

View File

@@ -23,6 +23,8 @@ import { t } from '@localisation';
import ErrorReportingAlert from 'utility/ErrorReportingAlert'; import ErrorReportingAlert from 'utility/ErrorReportingAlert';
import ErrorReportingPopup from './modals/ErrorReportingPopup'; import ErrorReportingPopup from './modals/ErrorReportingPopup';
import Player from './modals/Player'; import Player from './modals/Player';
import { StyleSheet } from 'react-native';
import { ColoredBlurView } from 'components/Colors';
const Stack = createNativeStackNavigator<ModalStackParams>(); const Stack = createNativeStackNavigator<ModalStackParams>();
const Tab = createBottomTabNavigator(); const Tab = createBottomTabNavigator();
@@ -63,6 +65,11 @@ function Screens() {
tabBarActiveTintColor: THEME_COLOR, tabBarActiveTintColor: THEME_COLOR,
tabBarInactiveTintColor: 'gray', tabBarInactiveTintColor: 'gray',
headerShown: false, headerShown: false,
tabBarShowLabel: false,
tabBarStyle: { position: 'absolute' },
tabBarBackground: () => (
<ColoredBlurView style={StyleSheet.absoluteFill} />
)
})} })}
> >
<Tab.Screen name="Music" component={Music} options={{ tabBarLabel: t('music'), tabBarTestID: 'music-tab' }} /> <Tab.Screen name="Music" component={Music} options={{ tabBarLabel: t('music'), tabBarTestID: 'music-tab' }} />

View File

@@ -1,6 +1,6 @@
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useRef, useEffect } from 'react'; import { useRef, useEffect } from 'react';
import { Animated, Keyboard, KeyboardEvent } from 'react-native'; import { Animated, Easing, Keyboard, KeyboardEvent } from 'react-native';
/** /**
* This returns an animated height that the keyboard is poking up from the * This returns an animated height that the keyboard is poking up from the
@@ -15,9 +15,10 @@ export const useKeyboardHeight = () => {
useEffect(() => { useEffect(() => {
const keyboardWillShow = (e: KeyboardEvent) => { const keyboardWillShow = (e: KeyboardEvent) => {
Animated.timing(keyboardHeight, { Animated.timing(keyboardHeight, {
duration: e.duration, duration: e.duration - 20,
toValue: tabBarHeight - e.endCoordinates.height, toValue: tabBarHeight - e.endCoordinates.height,
useNativeDriver: true, useNativeDriver: true,
easing: Easing.ease,
}).start(); }).start();
}; };