feat: update tab bars with blurview
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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)`
|
||||||
|
|||||||
@@ -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'>
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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' }} />
|
||||||
|
|||||||
@@ -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();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user