[MOB-85] Better headers (#2375)

* wip

* improve headers

* cleanup
This commit is contained in:
ameer2468 2024-04-24 01:21:31 +03:00 committed by GitHub
parent b4037d6537
commit 6a556a457d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
42 changed files with 462 additions and 287 deletions

View file

@ -1,8 +1,8 @@
import { useNavigation } from '@react-navigation/native';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import { DotsThreeOutline, Plus } from 'phosphor-react-native';
import { useRef } from 'react';
import { Text, View } from 'react-native';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import { ModalRef } from '~/components/layout/Modal';
import { tw } from '~/lib/tailwind';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
@ -64,7 +64,7 @@ const BrowseLocations = () => {
initial: false
})
}
onPress={() => navigation.navigate('Location', { id: location.id })}
onPress={() => navigation.navigate('Location', { id: location.id, title: location.name })}
/>
))}
</>

View file

@ -1,8 +1,8 @@
import { useNavigation } from '@react-navigation/native';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import { DotsThreeOutline, Plus } from 'phosphor-react-native';
import React, { useRef } from 'react';
import { Text, View } from 'react-native';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import { ModalRef } from '~/components/layout/Modal';
import { tw } from '~/lib/tailwind';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
@ -57,7 +57,7 @@ const BrowseTags = () => {
key={tag.id}
tag={tag}
onPress={() =>
navigation.navigate('Tag', { id: tag.id, color: tag.color! })
navigation.navigate('Tag', { id: tag.id, color: tag.color!, title: tag.name })
}
/>
))

View file

@ -1,19 +1,22 @@
import { useNavigation } from '@react-navigation/native';
import { NativeStackHeaderProps } from '@react-navigation/native-stack';
import { SearchData, isPath, type ExplorerItem } from '@sd/client';
import { FlashList } from '@shopify/flash-list';
import { UseInfiniteQueryResult } from '@tanstack/react-query';
import { ActivityIndicator, Pressable } from 'react-native';
import { isPath, SearchData, type ExplorerItem } from '@sd/client';
import { SharedValue } from 'react-native-reanimated';
import Layout from '~/constants/Layout';
import { tw } from '~/lib/tailwind';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import { useExplorerStore } from '~/stores/explorerStore';
import { useActionsModalStore } from '~/stores/modalStore';
import { HeaderProps } from '../header/Header';
import ScreenContainer from '../layout/ScreenContainer';
import FileItem from './FileItem';
import FileRow from './FileRow';
import Menu from './menu/Menu';
type ExplorerProps = {
tabHeight?: boolean;
items: ExplorerItem[] | null;
@ -21,13 +24,15 @@ type ExplorerProps = {
loadMore: () => void;
query: UseInfiniteQueryResult<SearchData<ExplorerItem>>;
count?: number;
scrollY?: SharedValue<number>;
route?: NativeStackHeaderProps['route'];
headerKind?: HeaderProps['headerKind'];
hideHeader?: boolean;
};
const Explorer = (props: ExplorerProps) => {
const navigation = useNavigation<BrowseStackScreenProps<'Location'>['navigation']>();
const store = useExplorerStore();
const { modalRef, setData } = useActionsModalStore();
function handlePress(data: ExplorerItem) {
@ -43,8 +48,20 @@ const Explorer = (props: ExplorerProps) => {
}
return (
<ScreenContainer tabHeight={props.tabHeight} scrollview={false} style={'gap-0 py-0'}>
<Menu />
<ScreenContainer
hideHeader={props.hideHeader}
header={{
scrollY: props.scrollY,
route: props.route,
headerKind: props.headerKind,
showSearch: true,
navBack: true,
}}
tabHeight={props.tabHeight}
scrollview={false}
style={'gap-0 py-0'}
>
<Menu/>
{/* Items */}
<FlashList
key={store.layoutMode}
@ -66,6 +83,11 @@ const Explorer = (props: ExplorerProps) => {
)}
</Pressable>
)}
scrollEventThrottle={1}
onScroll={(e) => {
if (!props.scrollY) return;
props.scrollY.value = e.nativeEvent.contentOffset.y;
}}
contentContainerStyle={tw`px-2 py-5`}
extraData={store.layoutMode}
estimatedItemSize={

View file

@ -9,7 +9,6 @@ import SortByMenu from './SortByMenu';
const Menu = () => {
const store = useExplorerStore();
return (
<AnimatePresence>
{store.toggleMenu && (

View file

@ -2,32 +2,42 @@ import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript
import { useNavigation } from '@react-navigation/native';
import { NativeStackHeaderProps } from '@react-navigation/native-stack';
import { ArrowLeft, DotsThreeOutline, List, MagnifyingGlass } from 'phosphor-react-native';
import { Platform, Pressable, Text, View } from 'react-native';
import React from 'react';
import { Platform, Pressable, View } from 'react-native';
import Animated, {
Extrapolation,
SharedValue,
interpolate,
useAnimatedStyle
} from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { tw, twStyle } from '~/lib/tailwind';
import { getExplorerStore, useExplorerStore } from '~/stores/explorerStore';
import { Icon } from '../icons/Icon';
import { AnimatedPressable } from '../reanimated/components';
import Search from '../search/Search';
type HeaderProps = {
type Props = {
title?: string; //title of the page
showSearch?: boolean; //show the search button
showDrawer?: boolean; //show the drawer button
searchType?: 'explorer' | 'location' | 'categories'; //Temporary
searchType?: 'location' | 'categories' | 'tags'; //Temporary
navBack?: boolean; //navigate back to the previous screen
headerKind?: 'default' | 'location' | 'tag'; //kind of header
route?: never;
routeTitle?: never;
scrollY?: SharedValue<number>; //scrollY of screen
};
//you can pass in a routeTitle only if route is passed in
type Props =
| HeaderProps
export type HeaderProps =
| Props
| ({
route: NativeStackHeaderProps;
route: NativeStackHeaderProps['route'];
routeTitle?: boolean;
} & Omit<HeaderProps, 'route' | 'routeTitle'>);
} & Omit<Props, 'route' | 'routeTitle'>);
// Default header with search bar and button to open drawer
export default function Header({
@ -35,55 +45,94 @@ export default function Header({
searchType,
navBack,
route,
routeTitle,
headerKind = 'default',
showDrawer = false,
showSearch = true
}: Props) {
showSearch = false,
scrollY
}: HeaderProps) {
const navigation = useNavigation<DrawerNavigationHelpers>();
const explorerStore = useExplorerStore();
const routeParams = route?.route.params as any;
const headerHeight = useSafeAreaInsets().top;
const routeParams = route?.params as any;
const headerSafeArea = useSafeAreaInsets();
const isAndroid = Platform.OS === 'android';
const scrollYTitle = useAnimatedStyle(() => {
return {
fontSize: interpolate(scrollY?.value || 0, [0, 50], [20, 16], Extrapolation.CLAMP)
};
});
const scrollYHeader = useAnimatedStyle(() => {
// this makes sure the header looks good on different devices
const outputRange = [headerSafeArea.top + (isAndroid ? 56 : 40), headerSafeArea.top + (isAndroid ? 44 : 32)];
return {
height: interpolate(
scrollY?.value || 0,
[0, 50],
outputRange,
Extrapolation.CLAMP
)
};
});
const scrollYIcon = useAnimatedStyle(() => {
return {
transform: [
{
scale: interpolate(scrollY?.value || 0, [0, 50], [1, 0.95], Extrapolation.CLAMP)
}
]
};
});
return (
<View
style={twStyle('relative h-auto w-full border-b border-app-cardborder bg-app-header', {
paddingTop: headerHeight + (isAndroid ? 15 : 0)
})}
<Animated.View
style={[
twStyle('mt-0 w-full border-b border-app-cardborder bg-app-header', {
paddingTop: headerSafeArea.top + (isAndroid ? 15 : 5),
}),
scrollYHeader
]}
>
<View style={tw`mx-auto h-auto w-full justify-center px-5 pb-4`}>
<View style={tw`mx-auto h-auto w-full justify-center px-5 pb-6`}>
<View style={tw`w-full flex-row items-center justify-between`}>
<View style={tw`flex-row items-center gap-3`}>
<View style={tw`flex-row items-center`}>
{navBack && (
<Pressable
<AnimatedPressable
style={scrollYIcon}
hitSlop={24}
onPress={() => {
navigation.goBack();
}}
onPress={() => navigation.goBack()}
>
<ArrowLeft size={23} color={tw.color('ink')} />
</Pressable>
</AnimatedPressable>
)}
<View style={tw`flex-row items-center gap-2`}>
<HeaderIconKind headerKind={headerKind} routeParams={routeParams} />
<View style={tw`flex-row items-center gap-1.5`}>
<Animated.View style={scrollYIcon}>
<HeaderIconKind headerKind={headerKind} routeParams={routeParams} />
</Animated.View>
{showDrawer && (
<Pressable onPress={() => navigation.openDrawer()}>
<List size={24} color={tw.color('text-zinc-300')} />
</Pressable>
<AnimatedPressable
style={scrollYIcon}
onPress={() => navigation.openDrawer()}
>
<List style={twStyle({
top: isAndroid ? 2 : 0 //fixes the icon alignment on android
})} size={24} color={tw.color('text-zinc-300')} />
</AnimatedPressable>
)}
<Text
<Animated.Text
numberOfLines={1}
style={tw`max-w-[200px] text-xl font-bold text-white`}
style={[twStyle('max-w-[200px] text-md font-bold text-ink'), scrollYTitle]}
>
{title || (routeTitle && route?.options.title)}
</Text>
{title || routeParams?.title}
</Animated.Text>
</View>
</View>
<View style={tw`relative flex-row items-center gap-3`}>
{showSearch && (
<View style={tw`flex-row items-center gap-2`}>
<Pressable
<AnimatedPressable
style={scrollYIcon}
hitSlop={24}
onPress={() => {
navigation.navigate('SearchStack', {
@ -92,11 +141,10 @@ export default function Header({
}}
>
<MagnifyingGlass
size={24}
weight="bold"
color={tw.color('text-zinc-300')}
/>
</Pressable>
</AnimatedPressable>
</View>
)}
{(headerKind === 'location' || headerKind === 'tag') && (
@ -118,7 +166,7 @@ export default function Header({
</View>
{searchType && <HeaderSearchType searchType={searchType} />}
</View>
</View>
</Animated.View>
);
}
@ -128,10 +176,10 @@ interface HeaderSearchTypeProps {
const HeaderSearchType = ({ searchType }: HeaderSearchTypeProps) => {
switch (searchType) {
case 'explorer':
return 'Explorer'; //TODO
case 'location':
return <Search placeholder="Location name..." />;
case 'tags':
return <Search placeholder="Tag name..." />;
case 'categories':
return <Search placeholder="Category name..." />;
default:
@ -147,11 +195,11 @@ interface HeaderIconKindProps {
const HeaderIconKind = ({ headerKind, routeParams }: HeaderIconKindProps) => {
switch (headerKind) {
case 'location':
return <Icon size={30} name="Folder" />;
return <Icon style={tw`ml-3`} size={30} name="Folder" />;
case 'tag':
return (
<View
style={twStyle('h-[30px] w-[30px] rounded-full', {
style={twStyle('ml-3 h-[24px] w-[24px] rounded-full', {
backgroundColor: routeParams.color
})}
/>

View file

@ -1,9 +1,7 @@
import { useRoute } from '@react-navigation/native';
import { DimensionValue, Platform } from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import { ClassInput } from 'twrnc';
import { tw, twStyle } from '~/lib/tailwind';
import { useExplorerStore } from '~/stores/explorerStore';
interface Props {
children: React.ReactNode; // children of fade
@ -13,7 +11,6 @@ interface Props {
orientation?: 'horizontal' | 'vertical'; // orientation of fade
fadeSides?: 'left-right' | 'top-bottom'; // which sides to fade
screenFade?: boolean; // if true, the fade will consider the bottom tab bar height
noConditions?: boolean; // if true, the fade will be rendered as is
bottomFadeStyle?: ClassInput; // tailwind style for bottom fade
topFadeStyle?: ClassInput; // tailwind style for top fade
}
@ -25,20 +22,15 @@ const Fade = ({
height,
bottomFadeStyle,
topFadeStyle,
noConditions = false,
screenFade = false,
fadeSides = 'left-right',
orientation = 'horizontal'
}: Props) => {
const route = useRoute();
const { toggleMenu } = useExplorerStore();
const bottomTabBarHeight = Platform.OS === 'ios' ? 80 : 60;
const gradientStartEndMap = {
'left-right': { start: { x: 0, y: 0 }, end: { x: 1, y: 0 } },
'top-bottom': { start: { x: 0, y: 1 }, end: { x: 0, y: 0 } }
};
const menuHeight = 57; // height of the explorer menu
const routesWithMenu = ['Location', 'Search', 'Tag']; // routes that are associated with the explorer
return (
<>
<LinearGradient
@ -46,10 +38,7 @@ const Fade = ({
width: orientation === 'vertical' ? height : width,
height: orientation === 'vertical' ? width : height,
position: 'absolute',
top:
!noConditions && toggleMenu && routesWithMenu.includes(route.name)
? menuHeight
: 0,
top: 0,
alignSelf: 'center',
left: fadeSides === 'left-right' ? 0 : undefined,
transform: fadeSides === 'left-right' ? undefined : [{ rotate: '180deg' }],

View file

@ -1,9 +1,12 @@
import { ReactNode, useRef } from 'react';
import { Platform, ScrollView, View } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { ReactNode, useEffect, useRef } from 'react';
import { Platform, View } from 'react-native';
import Animated, { SharedValue, useAnimatedScrollHandler } from 'react-native-reanimated';
import { AnimatedScrollView } from 'react-native-reanimated/lib/typescript/reanimated2/component/ScrollView';
import { ClassInput } from 'twrnc/dist/esm/types';
import { tw, twStyle } from '~/lib/tailwind';
import Fade from './Fade';
import Header, { HeaderProps } from '../header/Header';
interface Props {
children: ReactNode;
@ -16,37 +19,60 @@ interface Props {
/** Styling of both side fades */
topFadeStyle?: string;
bottomFadeStyle?: string;
scrollY?: SharedValue<number>;
/* Header properties */
header?: HeaderProps;
hideHeader?: boolean; // Hide the header
}
const ScreenContainer = ({
children,
style,
topFadeStyle,
bottomFadeStyle,
header,
scrollY,
hideHeader = false,
scrollview = true,
tabHeight = true,
scrollToBottomOnChange = false
scrollToBottomOnChange = false,
}: Props) => {
const ref = useRef<ScrollView>(null);
const ref = useRef<AnimatedScrollView>(null);
const bottomTabBarHeight = Platform.OS === 'ios' ? 80 : 60;
const scrollHandler = useAnimatedScrollHandler((e) => {
if (scrollY) scrollY.value = e.contentOffset.y;
});
const navigation = useNavigation();
// Reset scroll position to 0 whenever the tab blurs or focuses
useEffect(() => {
const resetScroll = () => {
ref.current?.scrollTo({ y: 0, animated: false });
if (scrollY) scrollY.value = 0;
};
// Subscribe to blur and focus events
const unsubscribeBlur = navigation.addListener('blur', resetScroll);
const unsubscribeFocus = navigation.addListener('focus', resetScroll);
// Cleanup function to remove event listeners
return () => {
unsubscribeBlur();
unsubscribeFocus();
};
}, [navigation, scrollY]);
return scrollview ? (
<View style={tw`relative flex-1`}>
<Fade
topFadeStyle={topFadeStyle}
bottomFadeStyle={bottomFadeStyle}
screenFade
fadeSides="top-bottom"
orientation="vertical"
color="black"
width={30}
height="100%"
>
<ScrollView
{!hideHeader && <Header {...header} scrollY={scrollY} />}
<Animated.ScrollView
ref={ref}
onContentSizeChange={() => {
if (!scrollToBottomOnChange) return;
ref.current?.scrollToEnd({ animated: true });
}}
scrollEventThrottle={1}
onScroll={scrollHandler}
contentContainerStyle={twStyle('justify-between gap-10 py-6', style)}
style={twStyle(
'flex-1 bg-black',
@ -54,21 +80,11 @@ const ScreenContainer = ({
)}
>
{children}
</ScrollView>
</Fade>
</Animated.ScrollView>
</View>
) : (
<View style={tw`relative flex-1`}>
<Fade
topFadeStyle={topFadeStyle}
bottomFadeStyle={bottomFadeStyle}
screenFade
fadeSides="top-bottom"
orientation="vertical"
color="black"
width={30}
height="100%"
>
{!hideHeader && <Header {...header} />}
<View
style={twStyle(
'flex-1 justify-between gap-10 bg-black py-6',
@ -78,7 +94,6 @@ const ScreenContainer = ({
>
{children}
</View>
</Fade>
</View>
);
};

View file

@ -1,6 +1,6 @@
import { Location } from '@sd/client';
import { useRef } from 'react';
import { Pressable } from 'react-native';
import { Location } from '@sd/client';
import { twStyle } from '~/lib/tailwind';
import { ModalRef } from '../layout/Modal';
@ -23,7 +23,6 @@ export const LocationItem = ({
}: LocationItemProps) => {
const modalRef = useRef<ModalRef>(null);
return (
<>
<Pressable
style={twStyle(viewStyle === 'grid' ? `w-[31.5%]` : `flex-1`)}
onPress={onPress}
@ -44,6 +43,5 @@ export const LocationItem = ({
<ListLocation location={location} />
)}
</Pressable>
</>
);
};

View file

@ -1,8 +1,8 @@
import { useNavigation } from '@react-navigation/native';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import React, { useRef } from 'react';
import { Pressable, Text, View } from 'react-native';
import { FlatList } from 'react-native-gesture-handler';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import { tw, twStyle } from '~/lib/tailwind';
import { OverviewStackScreenProps } from '~/navigation/tabs/OverviewStack';
@ -63,7 +63,7 @@ const Locations = () => {
navigation.jumpTo('BrowseStack', {
initial: false,
screen: 'Location',
params: { id: item.id }
params: { id: item.id, title: item.name }
})
}
>

View file

@ -14,7 +14,7 @@ const OverviewSection = ({ title, count, children }: Props) => {
<View style={tw`flex-row items-center gap-3 px-6 pb-3`}>
<Text style={tw`text-lg font-bold text-white`}>{title}</Text>
<View
style={tw`flex h-[24px] w-[24px] items-center justify-center rounded-full border border-app-button/40 px-1`}
style={tw`flex h-[24px] w-[24px] items-center justify-center rounded-full border border-app-button px-1`}
>
<Text style={tw`text-xs text-ink`}>{count}</Text>
</View>

View file

@ -0,0 +1,4 @@
import { Pressable } from 'react-native';
import Animated from 'react-native-reanimated';
export const AnimatedPressable = Animated.createAnimatedComponent(Pressable);

View file

@ -18,12 +18,12 @@ export default function Search({ placeholder }: Props) {
}, [searchStore]);
return (
<View
style={tw`mt-4 flex h-11 w-full flex-row items-center justify-between rounded-md border border-app-inputborder bg-app-input px-3 shadow-sm`}
style={tw`mt-3 flex h-auto w-full flex-row items-center justify-between rounded-md border border-app-inputborder bg-app-input px-3 py-2 shadow-sm`}
>
<TextInput
onChangeText={(text) => searchStore.setSearch(text)}
placeholderTextColor={tw.color('text-ink-dull')}
style={tw`w-[90%] text-white`}
style={tw`leading-0 w-[90%] text-sm text-white`}
placeholder={placeholder}
/>
<MagnifyingGlass size={20} weight="bold" color={tw.color('text-ink-dull')} />

View file

@ -11,7 +11,6 @@ import {
import { useEffect, useRef } from 'react';
import { FlatList, Pressable, Text, View } from 'react-native';
import { Icon } from '~/components/icons/Icon';
import Fade from '~/components/layout/Fade';
import { Button } from '~/components/primitive/Button';
import { tw, twStyle } from '~/lib/tailwind';
import { SearchStackScreenProps } from '~/navigation/SearchStack';
@ -47,7 +46,6 @@ const FiltersBar = () => {
<Plus weight="bold" size={20} color={tw.color('text-ink-dull')} />
</Button>
<View style={tw`relative flex-1`}>
<Fade noConditions height={'100%'} width={30} color="app-header">
<FlatList
ref={flatListRef}
showsHorizontalScrollIndicator={false}
@ -60,7 +58,6 @@ const FiltersBar = () => {
)}
contentContainerStyle={tw`flex-row gap-2 pl-4 pr-4`}
/>
</Fade>
</View>
</View>
);

View file

@ -1,6 +1,6 @@
import { Tag } from '@sd/client';
import { DotsThreeOutlineVertical } from 'phosphor-react-native';
import { Pressable, Text, View } from 'react-native';
import { Tag } from '@sd/client';
import { tw, twStyle } from '~/lib/tailwind';
import Card from '../layout/Card';
@ -20,7 +20,7 @@ const GridTag = ({ tag, modalRef }: GridTagProps) => {
backgroundColor: tag.color!
})}
/>
<Pressable hitSlop={24} onPress={() => modalRef.current?.present()}>
<Pressable hitSlop={12} onPress={() => modalRef.current?.present()}>
<DotsThreeOutlineVertical
weight="fill"
size={20}

View file

@ -1,6 +1,5 @@
import { createNativeStackNavigator, NativeStackScreenProps } from '@react-navigation/native-stack';
import React from 'react';
import Header from '~/components/header/Header';
import FiltersScreen from '~/screens/search/Filters';
import SearchScreen from '~/screens/search/Search';
@ -8,22 +7,18 @@ const Stack = createNativeStackNavigator<SearchStackParamList>();
export default function SearchStack() {
return (
<Stack.Navigator initialRouteName="Search">
<Stack.Navigator
screenOptions={{
headerShown: false
}}
initialRouteName="Search">
<Stack.Screen
name="Search"
component={SearchScreen}
options={{
headerShown: false
}}
/>
<Stack.Screen
name="Filters"
component={FiltersScreen}
options={{
header: () => {
return <Header navBack showSearch={false} title="Search filters" />;
}
}}
/>
</Stack.Navigator>
);

View file

@ -1,6 +1,5 @@
import { CompositeScreenProps } from '@react-navigation/native';
import { createNativeStackNavigator, NativeStackScreenProps } from '@react-navigation/native-stack';
import Header from '~/components/header/Header';
import BrowseScreen from '~/screens/browse/Browse';
import LibraryScreen from '~/screens/browse/Library';
import LocationScreen from '~/screens/browse/Location';
@ -14,48 +13,35 @@ const Stack = createNativeStackNavigator<BrowseStackParamList>();
export default function BrowseStack() {
return (
<Stack.Navigator initialRouteName="Browse">
<Stack.Navigator
screenOptions={{
headerShown: false
}}
initialRouteName="Browse">
<Stack.Screen
name="Browse"
component={BrowseScreen}
options={{ header: () => <Header showDrawer title="Browse" /> }}
/>
<Stack.Screen
name="Location"
component={LocationScreen}
options={{
header: (route) => (
<Header route={route} headerKind="location" routeTitle navBack />
)
}}
/>
>
{(props) => <LocationScreen {...props}/>}
</Stack.Screen>
<Stack.Screen
name="Tags"
component={TagsScreen}
options={{
header: () => <Header navBack title="Tags" />
}}
/>
<Stack.Screen
name="Locations"
component={LocationsScreen}
options={{
header: () => <Header navBack searchType="location" title="Locations" />
}}
/>
component={LocationsScreen}/>
<Stack.Screen
name="Tag"
component={TagScreen}
options={{
header: (route) => <Header navBack routeTitle route={route} headerKind="tag" />
}}
/>
>
{(props) => <TagScreen {...props} />}
</Stack.Screen>
<Stack.Screen
name="Library"
component={LibraryScreen}
options={{
header: () => <Header navBack title="Library" />
}}
/>
</Stack.Navigator>
);
@ -63,9 +49,9 @@ export default function BrowseStack() {
export type BrowseStackParamList = {
Browse: undefined;
Location: { id: number; path?: string };
Location: { id: number; path?: string, title?: string | null };
Locations: undefined;
Tag: { id: number; color: string };
Tag: { id: number; color: string, title?: string | null };
Tags: undefined;
Library: undefined;
};

View file

@ -1,6 +1,5 @@
import { CompositeScreenProps } from '@react-navigation/native';
import { createNativeStackNavigator, NativeStackScreenProps } from '@react-navigation/native-stack';
import Header from '~/components/header/Header';
import NetworkScreen from '~/screens/network/Network';
import { TabScreenProps } from '../TabNavigator';
@ -9,11 +8,12 @@ const Stack = createNativeStackNavigator<NetworkStackParamList>();
export default function NetworkStack() {
return (
<Stack.Navigator initialRouteName="Network">
<Stack.Navigator screenOptions={{
headerShown: false
}} initialRouteName="Network">
<Stack.Screen
name="Network"
component={NetworkScreen}
options={{ header: () => <Header showDrawer title="Network" /> }}
/>
</Stack.Navigator>
);

View file

@ -1,27 +1,24 @@
import { CompositeScreenProps } from '@react-navigation/native';
import { createNativeStackNavigator, NativeStackScreenProps } from '@react-navigation/native-stack';
import Header from '~/components/header/Header';
import CategoriesScreen from '~/screens/overview/Categories';
import OverviewScreen from '~/screens/overview/Overview';
import { TabScreenProps } from '../TabNavigator';
const Stack = createNativeStackNavigator<OverviewStackParamList>();
export default function OverviewStack() {
return (
<Stack.Navigator initialRouteName="Overview">
<Stack.Navigator
screenOptions={{
headerShown: false
}}>
<Stack.Screen
name="Overview"
component={OverviewScreen}
options={{ header: () => <Header showDrawer title="Overview" /> }}
/>
<Stack.Screen
name="Categories"
component={CategoriesScreen}
options={{
header: () => <Header searchType="categories" navBack title="Categories" />
}}
/>
</Stack.Navigator>
);

View file

@ -2,7 +2,7 @@ import { CompositeScreenProps } from '@react-navigation/native';
// import KeysSettingsScreen from '~/screens/settings/library/KeysSettings';
import { createNativeStackNavigator, NativeStackScreenProps } from '@react-navigation/native-stack';
import Header from '~/components/header/Header';
import LocationsScreen from '~/screens/browse/Locations';
import AppearanceSettingsScreen from '~/screens/settings/client/AppearanceSettings';
import ExtensionsSettingsScreen from '~/screens/settings/client/ExtensionsSettings';
import GeneralSettingsScreen from '~/screens/settings/client/GeneralSettings';
@ -13,7 +13,6 @@ import DebugScreen from '~/screens/settings/info/Debug';
import SupportScreen from '~/screens/settings/info/Support';
import EditLocationSettingsScreen from '~/screens/settings/library/EditLocationSettings';
import LibraryGeneralSettingsScreen from '~/screens/settings/library/LibraryGeneralSettings';
import LocationSettingsScreen from '~/screens/settings/library/LocationSettings';
import NodesSettingsScreen from '~/screens/settings/library/NodesSettings';
import TagsSettingsScreen from '~/screens/settings/library/TagsSettings';
import SettingsScreen from '~/screens/settings/Settings';
@ -24,65 +23,59 @@ const Stack = createNativeStackNavigator<SettingsStackParamList>();
export default function SettingsStack() {
return (
<Stack.Navigator initialRouteName="Settings">
<Stack.Navigator
screenOptions={{
headerShown: false
}}
initialRouteName="Settings">
<Stack.Screen
name="Settings"
component={SettingsScreen}
options={{ header: () => <Header showDrawer title="Settings" /> }}
/>
>
{(props) => <SettingsScreen {...props}/>}
</Stack.Screen>
{/* Client */}
<Stack.Screen
name="GeneralSettings"
component={GeneralSettingsScreen}
options={{ header: () => <Header navBack title="General" /> }}
/>
<Stack.Screen
name="LibrarySettings"
component={LibrarySettingsScreen}
options={{ header: () => <Header navBack title="Libraries" /> }}
/>
>
{(props) => <LibrarySettingsScreen {...props} />}
</Stack.Screen>
<Stack.Screen
name="AppearanceSettings"
component={AppearanceSettingsScreen}
options={{ header: () => <Header navBack title="Appearance" /> }}
/>
<Stack.Screen
name="PrivacySettings"
component={PrivacySettingsScreen}
options={{ header: () => <Header navBack title="Privacy" /> }}
/>
<Stack.Screen
name="ExtensionsSettings"
component={ExtensionsSettingsScreen}
options={{ header: () => <Header navBack title="Extensions" /> }}
/>
{/* Library */}
<Stack.Screen
name="LibraryGeneralSettings"
component={LibraryGeneralSettingsScreen}
options={{ header: () => <Header navBack title="Library Settings" /> }}
/>
<Stack.Screen
name="LocationSettings"
component={LocationSettingsScreen}
options={{
header: () => <Header searchType="location" navBack title="Locations" />
}}
component={LocationsScreen}
/>
<Stack.Screen
name="EditLocationSettings"
component={EditLocationSettingsScreen}
options={{ header: () => <Header navBack title="Edit Location" /> }}
/>
>
{(props) => <EditLocationSettingsScreen {...props} />}
</Stack.Screen>
<Stack.Screen
name="NodesSettings"
component={NodesSettingsScreen}
options={{ header: () => <Header navBack title="Nodes" /> }}
/>
<Stack.Screen
name="TagsSettings"
component={TagsSettingsScreen}
options={{ header: () => <Header navBack title="Tags" /> }}
/>
{/* <Stack.Screen
name="KeysSettings"
@ -93,17 +86,14 @@ export default function SettingsStack() {
<Stack.Screen
name="About"
component={AboutScreen}
options={{ header: () => <Header navBack title="About" /> }}
/>
/>
<Stack.Screen
name="Support"
component={SupportScreen}
options={{ header: () => <Header navBack title="Support" /> }}
/>
<Stack.Screen
name="Debug"
component={DebugScreen}
options={{ header: () => <Header navBack title="Debug" /> }}
/>
</Stack.Navigator>
);

View file

@ -1,11 +1,18 @@
import { useSharedValue } from 'react-native-reanimated';
import BrowseCategories from '~/components/browse/BrowseCategories';
import BrowseLocations from '~/components/browse/BrowseLocations';
import BrowseTags from '~/components/browse/BrowseTags';
import ScreenContainer from '~/components/layout/ScreenContainer';
export default function BrowseScreen() {
const scrollY = useSharedValue(0);
return (
<ScreenContainer>
<ScreenContainer header={{
scrollY: scrollY,
showSearch: true,
showDrawer: true,
title: 'Browse',
}}>
<BrowseCategories />
<BrowseLocations />
<BrowseTags />

View file

@ -6,7 +6,13 @@ import { tw } from '~/lib/tailwind';
export default function LibraryScreen() {
return (
<ScreenContainer style={tw`px-6 py-0`} scrollview={false}>
<ScreenContainer
header={{
title: 'Library',
navBack: true,
showSearch: true,
}}
style={tw`px-6 py-0`} scrollview={false}>
<FlatList
data={CATEGORIES_LIST}
contentContainerStyle={tw`py-6`}

View file

@ -1,12 +1,20 @@
import { useEffect } from 'react';
import { RouteProp } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useCache, useLibraryQuery, useNodes, usePathsExplorerQuery } from '@sd/client';
import { useEffect } from 'react';
import { useSharedValue } from 'react-native-reanimated';
import Explorer from '~/components/explorer/Explorer';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import { BrowseStackParamList } from '~/navigation/tabs/BrowseStack';
import { getExplorerStore } from '~/stores/explorerStore';
export default function LocationScreen({ navigation, route }: BrowseStackScreenProps<'Location'>) {
const { id, path } = route.params;
interface Props {
route: RouteProp<BrowseStackParamList, 'Location'>;
navigation: NativeStackNavigationProp<BrowseStackParamList, 'Location'>;
}
export default function LocationScreen({ navigation, route }: Props) {
const { id, path } = route.params;
const scrollY = useSharedValue(0);
const location = useLibraryQuery(['locations.get', route.params.id]);
useNodes(location.data?.nodes);
const locationData = useCache(location.data?.item);
@ -59,5 +67,7 @@ export default function LocationScreen({ navigation, route }: BrowseStackScreenP
getExplorerStore().path = path ?? '';
}, [id, path]);
return <Explorer {...paths} />;
return (
<Explorer headerKind='location' route={route} scrollY={scrollY} {...paths} />
);
}

View file

@ -1,9 +1,10 @@
import { useNavigation } from '@react-navigation/native';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import { Plus } from 'phosphor-react-native';
import { useMemo, useRef } from 'react';
import { FlatList, Pressable, View } from 'react-native';
import { Pressable, View } from 'react-native';
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated';
import { useDebounce } from 'use-debounce';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import Empty from '~/components/layout/Empty';
import { ModalRef } from '~/components/layout/Modal';
import ScreenContainer from '~/components/layout/ScreenContainer';
@ -19,6 +20,7 @@ interface Props {
}
export default function LocationsScreen({ viewStyle }: Props) {
const scrollY = useSharedValue(0);
const locationsQuery = useLibraryQuery(['locations.list']);
useNodes(locationsQuery.data?.nodes);
const locations = useCache(locationsQuery.data?.items);
@ -37,8 +39,17 @@ export default function LocationsScreen({ viewStyle }: Props) {
BrowseStackScreenProps<'Browse'>['navigation'] &
SettingsStackScreenProps<'Settings'>['navigation']
>();
const scrollHandler = useAnimatedScrollHandler((e) => {
scrollY.value = e.contentOffset.y;
});
return (
<ScreenContainer scrollview={false} style={tw`relative px-6 py-0`}>
<ScreenContainer
header={{
title: 'Locations',
navBack: true,
searchType: 'location',
}}
scrollview={false} style={tw`relative px-6 py-0`}>
<Pressable
style={tw`absolute bottom-7 right-7 z-10 h-12 w-12 items-center justify-center rounded-full bg-accent`}
onPress={() => {
@ -47,12 +58,13 @@ export default function LocationsScreen({ viewStyle }: Props) {
>
<Plus size={20} weight="bold" style={tw`text-ink`} />
</Pressable>
<FlatList
<Animated.FlatList
data={filteredLocations}
contentContainerStyle={twStyle(
`py-6`,
filteredLocations.length === 0 && 'h-full items-center justify-center'
)}
onScroll={scrollHandler}
keyExtractor={(location) => location.id.toString()}
ItemSeparatorComponent={() => <View style={tw`h-2`} />}
showsVerticalScrollIndicator={false}
@ -71,7 +83,7 @@ export default function LocationsScreen({ viewStyle }: Props) {
onPress={() =>
navigation.navigate('BrowseStack', {
screen: 'Location',
params: { id: item.id }
params: { id: item.id, title: item.name }
})
}
editLocation={() =>

View file

@ -1,11 +1,21 @@
import { useEffect } from 'react';
import { useCache, useLibraryQuery, useNodes, useObjectsExplorerQuery } from '@sd/client';
import { useEffect } from 'react';
import { useSharedValue } from 'react-native-reanimated';
import Explorer from '~/components/explorer/Explorer';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
export default function TagScreen({ navigation, route }: BrowseStackScreenProps<'Tag'>) {
const { id } = route.params;
interface Props {
route: BrowseStackScreenProps<'Tag'>['route'];
navigation: BrowseStackScreenProps<'Tag'>['navigation'];
}
export default function TagScreen({
navigation,
route,
}: Props) {
const { id } = route.params;
const scrollY = useSharedValue(0);
const tag = useLibraryQuery(['tags.get', id]);
useNodes(tag.data?.nodes);
const tagData = useCache(tag.data?.item);
@ -22,5 +32,7 @@ export default function TagScreen({ navigation, route }: BrowseStackScreenProps<
});
}, [tagData?.name, navigation]);
return <Explorer {...objects} />;
return (
<Explorer headerKind='tag' route={route} scrollY={scrollY} {...objects} />
);
}

View file

@ -1,17 +1,18 @@
import { useNavigation } from '@react-navigation/native';
import { Plus } from 'phosphor-react-native';
import { useRef } from 'react';
import { Pressable, View } from 'react-native';
import { FlatList } from 'react-native-gesture-handler';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import { Plus } from 'phosphor-react-native';
import { useMemo, useRef } from 'react';
import { Pressable, View } from 'react-native';
import Animated from 'react-native-reanimated';
import { useDebounce } from 'use-debounce';
import Empty from '~/components/layout/Empty';
import Fade from '~/components/layout/Fade';
import { ModalRef } from '~/components/layout/Modal';
import ScreenContainer from '~/components/layout/ScreenContainer';
import CreateTagModal from '~/components/modal/tag/CreateTagModal';
import { TagItem } from '~/components/tags/TagItem';
import { tw, twStyle } from '~/lib/tailwind';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import { useSearchStore } from '~/stores/searchStore';
interface Props {
viewStyle?: 'grid' | 'list';
@ -20,13 +21,32 @@ interface Props {
export default function TagsScreen({ viewStyle = 'list' }: Props) {
const navigation = useNavigation<BrowseStackScreenProps<'Browse'>['navigation']>();
const modalRef = useRef<ModalRef>(null);
const { search } = useSearchStore();
const tags = useLibraryQuery(['tags.list']);
useNodes(tags.data?.nodes);
const tagData = useCache(tags.data?.items);
const [debouncedSearch] = useDebounce(search, 200);
const filteredTags = useMemo(
() =>
tagData?.filter((tag) =>
tag.name?.toLowerCase().includes(debouncedSearch.toLowerCase())
) ?? [],
[debouncedSearch, tagData]
);
return (
<ScreenContainer scrollview={false} style={tw`relative px-6 py-0`}>
<ScreenContainer
header={{
title: 'Tags',
showSearch: false,
navBack: true,
searchType: 'tags',
}}
scrollview={false}
style={tw`relative px-6 py-0`}>
<Pressable
style={tw`absolute bottom-7 right-7 z-10 flex h-12 w-12 items-center justify-center rounded-full bg-accent`}
testID="create-tag-modal"
@ -36,15 +56,9 @@ export default function TagsScreen({ viewStyle = 'list' }: Props) {
>
<Plus size={20} weight="bold" style={tw`text-ink`} />
</Pressable>
<Fade
fadeSides="top-bottom"
orientation="vertical"
color="black"
width={30}
height="100%"
>
<FlatList
data={tagData}
<Animated.FlatList
data={filteredTags}
scrollEventThrottle={1}
renderItem={({ item }) => (
<TagItem
viewStyle={viewStyle}
@ -76,7 +90,6 @@ export default function TagsScreen({ viewStyle = 'list' }: Props) {
tagData.length === 0 && 'h-full items-center justify-center'
)}
/>
</Fade>
<CreateTagModal ref={modalRef} />
</ScreenContainer>
);

View file

@ -6,7 +6,11 @@ import { NetworkStackScreenProps } from '~/navigation/tabs/NetworkStack';
export default function NetworkScreen({ navigation }: NetworkStackScreenProps<'Network'>) {
return (
<ScreenContainer scrollview={false} style={tw`items-center justify-center gap-0`}>
<ScreenContainer header={{
showDrawer: true,
title: 'Network',
showSearch: true,
}} scrollview={false} style={tw`items-center justify-center gap-0`}>
<Icon name="Globe" size={128} />
<Text style={tw`mt-4 text-lg font-bold text-white`}>Your Local Network</Text>
<Text style={tw`mt-1 max-w-sm text-center text-sm text-ink-dull`}>

View file

@ -1,7 +1,8 @@
import { useMemo } from 'react';
import { FlatList, View } from 'react-native';
import { useDebounce } from 'use-debounce';
import { useLibraryQuery } from '@sd/client';
import { useMemo } from 'react';
import { View } from 'react-native';
import Animated, { useAnimatedScrollHandler, useSharedValue } from 'react-native-reanimated';
import { useDebounce } from 'use-debounce';
import { IconName } from '~/components/icons/Icon';
import ScreenContainer from '~/components/layout/ScreenContainer';
import CategoryItem from '~/components/overview/CategoryItem';
@ -9,6 +10,7 @@ import { tw } from '~/lib/tailwind';
import { useSearchStore } from '~/stores/searchStore';
const CategoriesScreen = () => {
const scrollY = useSharedValue(0);
const kinds = useLibraryQuery(['library.kindStatistics']);
const { search } = useSearchStore();
const [debouncedSearch] = useDebounce(search, 200);
@ -19,13 +21,26 @@ const CategoriesScreen = () => {
) ?? [],
[debouncedSearch, kinds]
);
const scrollHandler = useAnimatedScrollHandler((e) => {
scrollY.value = e.contentOffset.y;
});
return (
<ScreenContainer scrollview={false} style={tw`relative px-6 py-0`}>
<FlatList
<ScreenContainer
header={{
title: 'Categories',
searchType: 'categories',
navBack: true,
}}
scrollY={scrollY}
scrollview={false}
style={tw`relative px-6 py-0`}>
<Animated.FlatList
data={filteredKinds?.sort((a, b) => b.count - a.count).filter((i) => i.kind !== 0)}
numColumns={3}
onScroll={scrollHandler}
contentContainerStyle={tw`py-6`}
keyExtractor={(item) => item.name}
scrollEventThrottle={1}
ItemSeparatorComponent={() => <View style={tw`h-2`} />}
showsVerticalScrollIndicator={false}
showsHorizontalScrollIndicator={false}

View file

@ -1,4 +1,5 @@
import { useBridgeQuery, useLibraryQuery } from '@sd/client';
import { useSharedValue } from 'react-native-reanimated';
import ScreenContainer from '~/components/layout/ScreenContainer';
import Categories from '~/components/overview/Categories';
import Cloud from '~/components/overview/Cloud';
@ -20,13 +21,19 @@ const EMPTY_STATISTICS = {
export default function OverviewScreen() {
const { data: node } = useBridgeQuery(['nodeState']);
const scrollY = useSharedValue(0);
const stats = useLibraryQuery(['library.statistics'], {
initialData: { ...EMPTY_STATISTICS }
});
return (
<ScreenContainer>
<ScreenContainer
header={{
title: 'Overview',
showSearch: true,
showDrawer: true,
}}
scrollY={scrollY}>
<OverviewStats stats={stats} />
<Categories />
<Devices stats={stats} node={node} />

View file

@ -5,7 +5,13 @@ import SaveAdd from '~/components/search/filters/SaveAdd';
const FiltersScreen = () => {
return (
<>
<ScreenContainer bottomFadeStyle="bottom-0" tabHeight={false}>
<ScreenContainer
header={{
title: 'Filters',
showSearch: false,
navBack: true,
}}
tabHeight={false}>
<FiltersList />
</ScreenContainer>
<SaveAdd />

View file

@ -1,8 +1,8 @@
import { SearchFilterArgs, useObjectsExplorerQuery } from '@sd/client';
import { ArrowLeft, DotsThreeOutline, FunnelSimple, MagnifyingGlass } from 'phosphor-react-native';
import { Suspense, useDeferredValue, useMemo, useState } from 'react';
import { ActivityIndicator, Platform, Pressable, TextInput, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { SearchFilterArgs, useObjectsExplorerQuery } from '@sd/client';
import Explorer from '~/components/explorer/Explorer';
import FiltersBar from '~/components/search/filters/FiltersBar';
import { tw, twStyle } from '~/lib/tailwind';
@ -124,7 +124,7 @@ const SearchScreen = ({ navigation }: SearchStackScreenProps<'Search'>) => {
{/* Content */}
<View style={tw`flex-1`}>
<Suspense fallback={<ActivityIndicator />}>
<Explorer {...objects} tabHeight={false} />
<Explorer hideHeader {...objects} tabHeight={false} />
</Suspense>
</View>
</View>

View file

@ -1,3 +1,4 @@
import { DebugState, useDebugState, useDebugStateEnabler } from '@sd/client';
import {
Books,
FlyingSaucer,
@ -12,9 +13,8 @@ import {
ShieldCheck,
TagSimple
} from 'phosphor-react-native';
import React from 'react';
import { Platform, SectionList, Text, TouchableWithoutFeedback, View } from 'react-native';
import { DebugState, useDebugState, useDebugStateEnabler } from '@sd/client';
import { Platform, Text, TouchableWithoutFeedback, View } from 'react-native';
import { useSharedValue } from 'react-native-reanimated';
import ScreenContainer from '~/components/layout/ScreenContainer';
import { SettingsItem } from '~/components/settings/SettingsItem';
import { tw, twStyle } from '~/lib/tailwind';
@ -129,7 +129,7 @@ function renderSectionHeader({ section }: { section: { title: string } }) {
<Text
style={twStyle(
'mb-3 text-lg font-bold text-ink',
section.title === 'Client' ? 'mt-2' : 'mt-5'
section.title === 'Client' ? 'mt-0' : 'mt-5'
)}
>
{section.title}
@ -137,28 +137,34 @@ function renderSectionHeader({ section }: { section: { title: string } }) {
);
}
export default function SettingsScreen({ navigation }: SettingsStackScreenProps<'Settings'>) {
export default function SettingsScreen({
navigation,
}: SettingsStackScreenProps<'Settings'>) {
const debugState = useDebugState();
const scrollY = useSharedValue(0);
return (
<ScreenContainer tabHeight={false} scrollview={false} style={tw`gap-0 px-6 py-0`}>
<SectionList
sections={sections(debugState)}
contentContainerStyle={tw`h-auto pb-5 pt-3`}
renderItem={({ item }) => (
<SettingsItem
title={item.title}
leftIcon={item.icon}
onPress={() => navigation.navigate(item.navigateTo as any)}
rounded={item.rounded}
/>
)}
renderSectionHeader={renderSectionHeader}
ListFooterComponent={<FooterComponent />}
showsVerticalScrollIndicator={false}
stickySectionHeadersEnabled={false}
initialNumToRender={50}
/>
<ScreenContainer
header={{
title: 'Settings',
showSearch: true,
showDrawer: true,
}}
scrollY={scrollY} tabHeight={false} style={tw`gap-0 px-6`}>
{sections(debugState).map((section, i) => (
<View key={i}>
{renderSectionHeader({ section })}
{section.data.map((item, i) => (
<SettingsItem
key={i}
title={item.title}
leftIcon={item.icon}
onPress={() => navigation.navigate(item.navigateTo as any)}
rounded={item.rounded}
/>
))}
</View>
))}
<FooterComponent />
</ScreenContainer>
);
}

View file

@ -1,7 +1,7 @@
import { Themes, useThemeStore } from '@sd/client';
import { CheckCircle } from 'phosphor-react-native';
import React, { useState } from 'react';
import { ColorValue, Pressable, ScrollView, Text, View, ViewStyle } from 'react-native';
import { Themes, useThemeStore } from '@sd/client';
import ScreenContainer from '~/components/layout/ScreenContainer';
import { SettingsTitle } from '~/components/settings/SettingsContainer';
import Colors from '~/constants/style/Colors';
@ -126,7 +126,10 @@ const AppearanceSettingsScreen = ({
// TODO: Hook this up to the theme store once light theme is fixed.
return (
<ScreenContainer scrollview={false} style={tw`gap-2 px-6`}>
<ScreenContainer header={{
navBack: true,
title: 'Appearance'
}} scrollview={false} style={tw`gap-2 px-6`}>
<SettingsTitle>Theme</SettingsTitle>
<ScrollView
horizontal

View file

@ -1,5 +1,5 @@
import React from 'react';
import { Text, View } from 'react-native';
import { Text } from 'react-native';
import ScreenContainer from '~/components/layout/ScreenContainer';
import { tw } from '~/lib/tailwind';
import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack';
@ -8,7 +8,11 @@ const ExtensionsSettingsScreen = ({
navigation
}: SettingsStackScreenProps<'ExtensionsSettings'>) => {
return (
<ScreenContainer style={tw`px-6`}>
<ScreenContainer header={{
title: 'Extensions',
navBack: true,
}}
style={tw`px-6`}>
<Text style={tw`text-ink`}>TODO</Text>
</ScreenContainer>
);

View file

@ -1,14 +1,13 @@
import { Text, View } from 'react-native';
import { useBridgeQuery, useDebugState } from '@sd/client';
import { Text, View } from 'react-native';
import Card from '~/components/layout/Card';
import ScreenContainer from '~/components/layout/ScreenContainer';
import { Divider } from '~/components/primitive/Divider';
import { Input } from '~/components/primitive/Input';
import { SettingsTitle } from '~/components/settings/SettingsContainer';
import { tw } from '~/lib/tailwind';
import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack';
const GeneralSettingsScreen = ({ navigation }: SettingsStackScreenProps<'GeneralSettings'>) => {
const GeneralSettingsScreen = () => {
const { data: node } = useBridgeQuery(['nodeState']);
const debugState = useDebugState();
@ -16,7 +15,12 @@ const GeneralSettingsScreen = ({ navigation }: SettingsStackScreenProps<'General
if (!node) return null;
return (
<ScreenContainer style={tw`justify-start gap-0 px-6`} scrollview={false}>
<ScreenContainer
header={{
title: 'General',
navBack: true,
}}
style={tw`justify-start gap-0 px-6`} scrollview={false}>
<Card>
{/* Card Header */}
<View style={tw`flex flex-row justify-between`}>

View file

@ -1,8 +1,9 @@
import { LibraryConfigWrapped, useBridgeQuery, useCache, useNodes } from '@sd/client';
import { DotsThreeOutlineVertical, Pen, Trash } from 'phosphor-react-native';
import React, { useEffect, useRef } from 'react';
import { Animated, FlatList, Pressable, Text, View } from 'react-native';
import { Animated, Pressable, Text, View } from 'react-native';
import { Swipeable } from 'react-native-gesture-handler';
import { LibraryConfigWrapped, useBridgeQuery, useCache, useNodes } from '@sd/client';
import { default as Reanimated } from 'react-native-reanimated';
import Fade from '~/components/layout/Fade';
import { ModalRef } from '~/components/layout/Modal';
import ScreenContainer from '~/components/layout/ScreenContainer';
@ -78,7 +79,9 @@ function LibraryItem({
);
}
const LibrarySettingsScreen = ({ navigation }: SettingsStackScreenProps<'LibrarySettings'>) => {
const LibrarySettingsScreen = ({
navigation,
}: SettingsStackScreenProps<'LibrarySettings'>) => {
const libraryList = useBridgeQuery(['library.list']);
useNodes(libraryList.data?.nodes);
const libraries = useCache(libraryList.data?.items);
@ -101,7 +104,10 @@ const LibrarySettingsScreen = ({ navigation }: SettingsStackScreenProps<'Library
const modalRef = useRef<ModalRef>(null);
return (
<ScreenContainer style={tw`justify-start gap-0 px-6 py-0`} scrollview={false}>
<ScreenContainer header={{
navBack: true,
title: 'Libraries',
}} scrollview={false} style={tw`justify-start gap-0 px-6 py-0`}>
<Fade
fadeSides="top-bottom"
orientation="vertical"
@ -109,7 +115,7 @@ const LibrarySettingsScreen = ({ navigation }: SettingsStackScreenProps<'Library
width={30}
height="100%"
>
<FlatList
<Reanimated.FlatList
data={libraries}
contentContainerStyle={tw`py-5`}
keyExtractor={(item) => item.uuid}

View file

@ -5,7 +5,10 @@ import { tw } from '~/lib/tailwind';
const PrivacySettingsScreen = () => {
return (
<ScreenContainer scrollview={false} style={tw`px-6`}>
<ScreenContainer header={{
title: 'Privacy',
navBack: true,
}} scrollview={false} style={tw`px-6`}>
<Text style={tw`text-ink`}>TODO</Text>
</ScreenContainer>
);

View file

@ -1,8 +1,8 @@
import { useBridgeQuery } from '@sd/client';
import { Image } from 'expo-image';
import { Globe } from 'phosphor-react-native';
import React from 'react';
import { Linking, Platform, Text, View } from 'react-native';
import { useBridgeQuery } from '@sd/client';
import { DiscordIcon, GitHubIcon } from '~/components/icons/Brands';
import ScreenContainer from '~/components/layout/ScreenContainer';
import { Button } from '~/components/primitive/Button';
@ -11,14 +11,16 @@ import { tw } from '~/lib/tailwind';
const AboutScreen = () => {
const buildInfo = useBridgeQuery(['buildInfo']);
return (
<ScreenContainer style={tw`justify-start gap-0 px-6`}>
<ScreenContainer header={{
title: 'About',
navBack: true,
}} style={tw`justify-start gap-0 px-6`}>
<View style={tw`flex flex-row items-center`}>
<Image
source={require('../../../../assets/icon.png')}
style={tw`mr-8 h-[88px] w-[88px] rounded-3xl`}
resizeMode="contain"
contentFit="contain"
/>
<View style={tw`flex flex-col`}>
<Text style={tw`text-2xl font-bold text-white`}>

View file

@ -1,7 +1,8 @@
import { useDebugState, useFeatureFlags } from '@sd/client';
import React from 'react';
import { Text, View } from 'react-native';
import { toggleFeatureFlag, useDebugState, useFeatureFlags } from '@sd/client';
import { Text } from 'react-native';
import Card from '~/components/layout/Card';
import ScreenContainer from '~/components/layout/ScreenContainer';
import { Button } from '~/components/primitive/Button';
import { tw } from '~/lib/tailwind';
import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack';
@ -11,15 +12,16 @@ const DebugScreen = ({ navigation }: SettingsStackScreenProps<'Debug'>) => {
const featureFlags = useFeatureFlags();
return (
<View style={tw`flex-1 p-4`}>
<Card style={tw`gap-y-4 bg-app-box`}>
<ScreenContainer style={tw`px-6`} header={{ title: 'Debug', navBack: true }}>
<Card style={tw`gap-y-4`}>
<Text style={tw`font-semibold text-ink`}>Debug</Text>
<Button onPress={() => (debugState.rspcLogger = !debugState.rspcLogger)}>
<Button variant="darkgray" onPress={() => (debugState.rspcLogger = !debugState.rspcLogger)}>
<Text style={tw`text-ink`}>Toggle rspc logger</Text>
</Button>
<Text style={tw`text-ink`}>{JSON.stringify(featureFlags)}</Text>
<Text style={tw`text-ink`}>{JSON.stringify(debugState)}</Text>
<Button
variant="darkgray"
onPress={() => {
navigation.popToTop();
navigation.replace('Settings');
@ -29,7 +31,7 @@ const DebugScreen = ({ navigation }: SettingsStackScreenProps<'Debug'>) => {
<Text style={tw`text-ink`}>Disable Debug Mode</Text>
</Button>
</Card>
</View>
</ScreenContainer>
);
};

View file

@ -1,12 +1,18 @@
import React from 'react';
import { Text, View } from 'react-native';
import { Text } from 'react-native';
import ScreenContainer from '~/components/layout/ScreenContainer';
import { tw } from '~/lib/tailwind';
const SupportScreen = () => {
return (
<View>
<ScreenContainer
style={tw`justify-start px-6 py-5`}
header={{
title: 'Support',
navBack: true,
}}>
<Text style={tw`text-ink`}>TODO</Text>
</View>
</ScreenContainer>
);
};

View file

@ -1,10 +1,10 @@
import { useLibraryMutation, useLibraryQuery, useNormalisedCache, useZodForm } from '@sd/client';
import { useQueryClient } from '@tanstack/react-query';
import { Archive, ArrowsClockwise, Trash } from 'phosphor-react-native';
import { useEffect } from 'react';
import { Controller } from 'react-hook-form';
import { Alert, Text, View } from 'react-native';
import { z } from 'zod';
import { useLibraryMutation, useLibraryQuery, useNormalisedCache, useZodForm } from '@sd/client';
import ScreenContainer from '~/components/layout/ScreenContainer';
import { AnimatedButton } from '~/components/primitive/Button';
import { Divider } from '~/components/primitive/Divider';
@ -28,10 +28,9 @@ const schema = z.object({
const EditLocationSettingsScreen = ({
route,
navigation
navigation,
}: SettingsStackScreenProps<'EditLocationSettings'>) => {
const { id } = route.params;
const queryClient = useQueryClient();
const cache = useNormalisedCache();
@ -111,7 +110,10 @@ const EditLocationSettingsScreen = ({
const fullRescan = useLibraryMutation('locations.fullRescan');
return (
<ScreenContainer style={tw`px-6`}>
<ScreenContainer header={{
title: 'Edit Location',
navBack: true,
}} scrollview style={tw`px-6`}>
{/* Inputs */}
<View>
<SettingsTitle style={tw`mb-1`}>Display Name</SettingsTitle>

View file

@ -1,8 +1,8 @@
import { useBridgeMutation, useLibraryContext, useZodForm } from '@sd/client';
import { Controller } from 'react-hook-form';
import { Text, View } from 'react-native';
import { TouchableOpacity } from 'react-native-gesture-handler';
import { z } from 'zod';
import { useBridgeMutation, useLibraryContext, useZodForm } from '@sd/client';
import ScreenContainer from '~/components/layout/ScreenContainer';
import DeleteLibraryModal from '~/components/modal/confirmModals/DeleteLibraryModal';
import { Button } from '~/components/primitive/Button';
@ -14,11 +14,10 @@ import { SettingsTitle } from '~/components/settings/SettingsContainer';
import SettingsToggle from '~/components/settings/SettingsToggle';
import { useAutoForm } from '~/hooks/useAutoForm';
import { tw } from '~/lib/tailwind';
import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack';
const schema = z.object({ name: z.string(), description: z.string() });
const LibraryGeneralSettingsScreen = (_: SettingsStackScreenProps<'LibraryGeneralSettings'>) => {
const LibraryGeneralSettingsScreen = () => {
const { library } = useLibraryContext();
const form = useZodForm({
@ -38,7 +37,10 @@ const LibraryGeneralSettingsScreen = (_: SettingsStackScreenProps<'LibraryGenera
});
return (
<ScreenContainer scrollview={false} style={tw`justify-start px-6 py-0`}>
<ScreenContainer header={{
title: 'Library Settings',
navBack: true,
}} style={tw`justify-start px-6 py-0`}>
<View style={tw`pt-5`}>
<SettingsTitle style={tw`mb-1`}>Name</SettingsTitle>
<Controller

View file

@ -1,6 +1,6 @@
import { useDiscoveredPeers } from '@sd/client';
import React from 'react';
import { Text, View } from 'react-native';
import { useDiscoveredPeers } from '@sd/client';
import ScreenContainer from '~/components/layout/ScreenContainer';
import { tw } from '~/lib/tailwind';
import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack';
@ -9,7 +9,10 @@ const NodesSettingsScreen = ({ navigation }: SettingsStackScreenProps<'NodesSett
const onlineNodes = useDiscoveredPeers();
return (
<ScreenContainer scrollview={false} style={tw`gap-0 px-6`}>
<ScreenContainer header={{
title: 'Nodes',
navBack: true,
}} scrollview={false} style={tw`gap-0 px-6`}>
<Text style={tw`text-ink`}>Pairing</Text>
{[...onlineNodes.entries()].map(([id, node]) => (