Mobile design changes (#1886)

* bump sdk version

* move drawer

* black & blur is the way

* bottom tab height

* fix tests and cache
This commit is contained in:
Utku 2023-12-13 01:14:59 +03:00 committed by GitHub
parent a9da6a0093
commit 97c6c7d8ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 217 additions and 341 deletions

View file

@ -44,6 +44,7 @@
"tw\\.[^`]+`([^`]*)`", // tw.xxx`...`
"tw\\(.*?\\).*?`([^`]*)", // tw(....)`...`
"tw`([^`]*)", // tw`...` (mobile)
"twStyle(([^)]*)", // twStyle(....) (mobile)
"twStyle\\(([^)]*)\\)", // twStyle(....) (mobile)
["styled\\([^,)]+,([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] // styled(....)`...` (mobile)
],

View file

@ -30,7 +30,14 @@
}
},
"android": {
"permissions": ["android.permission.MANAGE_EXTERNAL_STORAGE"],
"permissions": [
"android.permission.MANAGE_EXTERNAL_STORAGE",
"android.permission.READ_MEDIA_AUDIO",
"android.permission.READ_MEDIA_IMAGES",
"android.permission.READ_MEDIA_VIDEO",
"android.permission.ACCESS_MEDIA_LOCATION",
"android.permission.WRITE_EXTERNAL_STORAGE"
],
"package": "com.spacedrive.app"
},
"splash": {
@ -42,6 +49,9 @@
[
"expo-build-properties",
{
"android": {
"minSdkVersion": 28
},
"ios": {
"useFrameworks": "static",
"deploymentTarget": "13.0"

View file

@ -69,7 +69,7 @@ android {
namespace "com.spacedrive.core"
defaultConfig {
minSdkVersion safeExtGet("minSdkVersion", 21)
minSdkVersion safeExtGet("minSdkVersion", 28)
targetSdkVersion safeExtGet("targetSdkVersion", 33)
versionCode 1
versionName "0.2.0"

View file

@ -12,6 +12,7 @@
"xcode": "open ios/Spacedrive.xcworkspace",
"android-studio": "open -a '/Applications/Android Studio.app' ./android",
"lint": "eslint src --cache",
"test": "cd ../.. && ./apps/mobile/scripts/run-maestro-tests ios",
"typecheck": "tsc -b"
},
"dependencies": {
@ -33,6 +34,7 @@
"dayjs": "^1.11.10",
"event-target-polyfill": "^0.0.3",
"expo": "~49.0.8",
"expo-blur": "^12.6.0",
"expo-build-properties": "~0.8.3",
"expo-linking": "~5.0.2",
"expo-media-library": "~15.4.1",

0
apps/mobile/scripts/run-maestro-tests Normal file → Executable file
View file

View file

@ -12,12 +12,15 @@ import relativeTime from 'dayjs/plugin/relativeTime';
import * as SplashScreen from 'expo-splash-screen';
import { StatusBar } from 'expo-status-bar';
import { useEffect, useRef, useState } from 'react';
import { LogBox } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { MenuProvider } from 'react-native-popup-menu';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { useSnapshot } from 'valtio';
import {
CacheProvider,
ClientContextProvider,
createCache,
initPlausible,
LibraryContextProvider,
NotificationContextProvider,
@ -39,6 +42,8 @@ import OnboardingNavigator from './navigation/OnboardingNavigator';
import { P2P } from './screens/p2p';
import { currentLibraryStore } from './utils/nav';
LogBox.ignoreLogs(['Sending `onAnimatedValueUpdate` with no listeners registered.']);
dayjs.extend(advancedFormat);
dayjs.extend(relativeTime);
dayjs.extend(duration);
@ -89,7 +94,7 @@ function AppNavigation() {
colors: {
...DefaultTheme.colors,
// Default screen background
background: tw.color('app')!
background: 'black'
}
}}
onStateChange={async () => {
@ -125,7 +130,7 @@ function AppContainer() {
const { id } = useSnapshot(currentLibraryStore);
return (
<SafeAreaProvider style={tw`flex-1 bg-app`}>
<SafeAreaProvider style={tw`flex-1 bg-black`}>
<GestureHandlerRootView style={tw`flex-1`}>
<MenuProvider>
<BottomSheetModalProvider>
@ -146,6 +151,7 @@ function AppContainer() {
}
const queryClient = new QueryClient();
const cache = createCache();
export default function App() {
useEffect(() => {
@ -154,7 +160,9 @@ export default function App() {
return (
<RspcProvider queryClient={queryClient}>
<AppContainer />
<CacheProvider cache={cache}>
<AppContainer />
</CacheProvider>
</RspcProvider>
);
}

View file

@ -1,21 +1,21 @@
import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types';
import { useNavigation } from '@react-navigation/native';
import { useRef } from 'react';
import { Pressable, Text, View } from 'react-native';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import { ModalRef } from '~/components/layout/Modal';
import { tw, twStyle } from '~/lib/tailwind';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import FolderIcon from '../icons/FolderIcon';
import CollapsibleView from '../layout/CollapsibleView';
import ImportModal from '../modal/ImportModal';
type DrawerLocationItemProps = {
type BrowseLocationItemProps = {
folderName: string;
onPress: () => void;
};
const DrawerLocationItem: React.FC<DrawerLocationItemProps> = (props) => {
const BrowseLocationItem: React.FC<BrowseLocationItemProps> = (props) => {
const { folderName, onPress } = props;
return (
@ -30,12 +30,8 @@ const DrawerLocationItem: React.FC<DrawerLocationItemProps> = (props) => {
);
};
type DrawerLocationsProp = {
stackName: string;
};
const DrawerLocations = ({ stackName }: DrawerLocationsProp) => {
const navigation = useNavigation<DrawerNavigationHelpers>();
const BrowseLocations = () => {
const navigation = useNavigation<BrowseStackScreenProps<'Browse'>['navigation']>();
const modalRef = useRef<ModalRef>(null);
@ -52,15 +48,10 @@ const DrawerLocations = ({ stackName }: DrawerLocationsProp) => {
>
<View style={tw`mt-2`}>
{locations?.map((location) => (
<DrawerLocationItem
<BrowseLocationItem
key={location.id}
folderName={location.name ?? ''}
onPress={() =>
navigation.navigate(stackName, {
screen: 'Location',
params: { id: location.id }
})
}
onPress={() => navigation.navigate('Location', { id: location.id })}
/>
))}
</View>
@ -78,4 +69,4 @@ const DrawerLocations = ({ stackName }: DrawerLocationsProp) => {
);
};
export default DrawerLocations;
export default BrowseLocations;

View file

@ -1,24 +1,24 @@
import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types';
import { useNavigation } from '@react-navigation/native';
import { useRef } from 'react';
import React, { useRef } from 'react';
import { ColorValue, Pressable, Text, View } from 'react-native';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import { ModalRef } from '~/components/layout/Modal';
import { tw, twStyle } from '~/lib/tailwind';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import CollapsibleView from '../layout/CollapsibleView';
import CreateTagModal from '../modal/tag/CreateTagModal';
type DrawerTagItemProps = {
type BrowseTagItemProps = {
tagName: string;
tagColor: ColorValue;
onPress: () => void;
};
const DrawerTagItem: React.FC<DrawerTagItemProps> = (props) => {
const BrowseTagItem: React.FC<BrowseTagItemProps> = (props) => {
const { tagName, tagColor, onPress } = props;
return (
<Pressable onPress={onPress} testID="drawer-tag">
<Pressable onPress={onPress} testID="browse-tag">
<View style={twStyle('mb-[4px] flex flex-row items-center rounded px-1 py-2')}>
<View style={twStyle('h-3.5 w-3.5 rounded-full', { backgroundColor: tagColor })} />
<Text style={twStyle('ml-2 text-sm font-medium text-gray-300')} numberOfLines={1}>
@ -29,14 +29,11 @@ const DrawerTagItem: React.FC<DrawerTagItemProps> = (props) => {
);
};
type DrawerTagsProp = {
stackName: string;
};
const DrawerTags = ({ stackName }: DrawerTagsProp) => {
const navigation = useNavigation<DrawerNavigationHelpers>();
const BrowseTags = () => {
const navigation = useNavigation<BrowseStackScreenProps<'Browse'>['navigation']>();
const tags = useLibraryQuery(['tags.list']);
useNodes(tags.data?.nodes);
const tagData = useCache(tags.data?.items);
@ -50,15 +47,10 @@ const DrawerTags = ({ stackName }: DrawerTagsProp) => {
>
<View style={tw`mt-2`}>
{tagData?.map((tag) => (
<DrawerTagItem
<BrowseTagItem
key={tag.id}
tagName={tag.name!}
onPress={() =>
navigation.navigate(stackName, {
screen: 'Tag',
params: { id: tag.id }
})
}
onPress={() => navigation.navigate('Tag', { id: tag.id })}
tagColor={tag.color as ColorValue}
/>
))}
@ -74,4 +66,4 @@ const DrawerTags = ({ stackName }: DrawerTagsProp) => {
);
};
export default DrawerTags;
export default BrowseTags;

View file

@ -1,8 +1,7 @@
import { useDrawerStatus } from '@react-navigation/drawer';
import { useNavigation } from '@react-navigation/native';
import { MotiView } from 'moti';
import { CaretRight, Gear, Lock, Plus } from 'phosphor-react-native';
import { useEffect, useRef, useState } from 'react';
import { useRef, useState } from 'react';
import { Alert, Pressable, Text, View } from 'react-native';
import { useClientContext } from '@sd/client';
import { tw, twStyle } from '~/lib/tailwind';
@ -13,14 +12,14 @@ import { ModalRef } from '../layout/Modal';
import CreateLibraryModal from '../modal/CreateLibraryModal';
import { Divider } from '../primitive/Divider';
const DrawerLibraryManager = () => {
const BrowseLibraryManager = () => {
const [dropdownClosed, setDropdownClosed] = useState(true);
// Closes the dropdown when the drawer is closed
const isDrawerOpen = useDrawerStatus() === 'open';
useEffect(() => {
if (!isDrawerOpen) setDropdownClosed(true);
}, [isDrawerOpen]);
// const isDrawerOpen = useDrawerStatus() === 'open';
// useEffect(() => {
// if (!isDrawerOpen) setDropdownClosed(true);
// }, [isDrawerOpen]);
const { library: currentLibrary, libraries } = useClientContext();
@ -113,4 +112,4 @@ const DrawerLibraryManager = () => {
);
};
export default DrawerLibraryManager;
export default BrowseLibraryManager;

View file

@ -1,73 +0,0 @@
import { DrawerContentScrollView } from '@react-navigation/drawer';
import { DrawerContentComponentProps } from '@react-navigation/drawer/lib/typescript/src/types';
import { AppLogo } from '@sd/assets/images';
import { CheckCircle, Gear } from 'phosphor-react-native';
import { useRef } from 'react';
import { Image, Platform, Pressable, Text, View } from 'react-native';
import { JobManagerContextProvider, useLibraryQuery } from '@sd/client';
import Layout from '~/constants/Layout';
import { tw, twStyle } from '~/lib/tailwind';
import { getStackNameFromState } from '~/utils/nav';
import { PulseAnimation } from '../animation/lottie';
import { ModalRef } from '../layout/Modal';
import { JobManagerModal } from '../modal/job/JobManagerModal';
import DrawerLibraryManager from './DrawerLibraryManager';
import DrawerLocations from './DrawerLocations';
import DrawerTags from './DrawerTags';
const drawerHeight = Platform.select({
ios: Layout.window.height * 0.85,
android: Layout.window.height * 0.9
});
function JobIcon() {
const { data: isActive } = useLibraryQuery(['jobs.isActive']);
return isActive ? (
<PulseAnimation style={tw`h-[24px] w-[32px]`} speed={1.5} />
) : (
<CheckCircle color="white" size={24} />
);
}
const DrawerContent = ({ navigation, state }: DrawerContentComponentProps) => {
const stackName = getStackNameFromState(state);
const modalRef = useRef<ModalRef>(null);
return (
<DrawerContentScrollView style={tw`flex-1 px-3 py-2`} scrollEnabled={false}>
<View style={twStyle('justify-between', { height: drawerHeight })}>
<View>
<View style={tw`flex flex-row items-center`}>
<Image source={AppLogo} style={tw`h-[40px] w-[40px]`} />
<Text style={tw`ml-2 text-lg font-bold text-ink`}>Spacedrive</Text>
</View>
<View style={tw`mt-6`} />
{/* Library Manager */}
<DrawerLibraryManager />
{/* Locations */}
<DrawerLocations stackName={stackName} />
{/* Tags */}
<DrawerTags stackName={stackName} />
</View>
<View style={tw`flex w-full flex-row items-center gap-x-4`}>
{/* Settings */}
<Pressable onPress={() => navigation.navigate('Settings')}>
<Gear color="white" size={24} />
</Pressable>
{/* Job Manager */}
<JobManagerContextProvider>
<Pressable onPress={() => modalRef.current?.present()}>
<JobIcon />
</Pressable>
<JobManagerModal ref={modalRef} />
</JobManagerContextProvider>
</View>
</View>
</DrawerContentScrollView>
);
};
export default DrawerContent;

View file

@ -7,7 +7,7 @@ import { isPath, type ExplorerItem } from '@sd/client';
import SortByMenu from '~/components/menu/SortByMenu';
import Layout from '~/constants/Layout';
import { tw } from '~/lib/tailwind';
import { type SharedScreenProps } from '~/navigation/SharedScreens';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import { getExplorerStore } from '~/stores/explorerStore';
import { useActionsModalStore } from '~/stores/modalStore';
@ -19,7 +19,7 @@ type ExplorerProps = {
};
const Explorer = ({ items }: ExplorerProps) => {
const navigation = useNavigation<SharedScreenProps<'Location'>['navigation']>();
const navigation = useNavigation<BrowseStackScreenProps<'Location'>['navigation']>();
const [layoutMode, setLayoutMode] = useState<'grid' | 'list'>(getExplorerStore().layoutMode);

View file

@ -1,39 +1,28 @@
import { useDrawerStatus } from '@react-navigation/drawer';
import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types';
import { useNavigation } from '@react-navigation/native';
import { MotiView } from 'moti';
import { List } from 'phosphor-react-native';
import { MagnifyingGlass } from 'phosphor-react-native';
import { Pressable, Text, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { tw, twStyle } from '~/lib/tailwind';
// Default header with search bar and button to open drawer
export default function Header() {
const navigation = useNavigation<DrawerNavigationHelpers>();
const navigation = useNavigation();
const { top } = useSafeAreaInsets();
const isDrawerOpen = useDrawerStatus() === 'open';
return (
<View
style={twStyle('mx-4 rounded border border-app-line bg-app-overlay', {
marginTop: top + 10
})}
>
<View style={tw`flex h-10 flex-row items-center`}>
<Pressable
testID="drawer-toggle"
style={tw`h-full justify-center px-3`}
onPress={() => navigation.openDrawer()}
>
<MotiView
animate={{ rotate: isDrawerOpen ? '90deg' : '0deg' }}
transition={{ type: 'timing' }}
>
<List size={20} color={tw.color('ink-faint')} weight="fill" />
</MotiView>
</Pressable>
<View style={tw`flex h-10 flex-row items-center px-3`}>
<MagnifyingGlass
size={20}
weight="light"
color={tw.color('ink-faint')}
style={tw`mr-3`}
/>
<Pressable
style={tw`h-full flex-1 justify-center`}
onPress={() => navigation.navigate('Search')}

View file

@ -12,7 +12,7 @@ type SettingsItemProps = {
export function SettingsItem(props: SettingsItemProps) {
return (
<Pressable onPress={props.onPress}>
<View style={tw`flex flex-row items-center justify-between bg-app-box px-4`}>
<View style={tw`flex flex-row items-center justify-between bg-app-darkBox px-4`}>
<View style={tw`flex flex-row items-center py-3`}>
{props.leftIcon &&
props.leftIcon({ size: 20, color: tw.color('ink'), style: tw`mr-3` })}
@ -21,7 +21,7 @@ export function SettingsItem(props: SettingsItemProps) {
{props.rightArea ? (
props.rightArea
) : (
<CaretRight size={20} color={tw.color('ink')} />
<CaretRight size={16} color={tw.color('ink')} />
)}
</View>
</Pressable>
@ -30,8 +30,8 @@ export function SettingsItem(props: SettingsItemProps) {
export function SettingsItemDivider(props: { style?: ViewStyle }) {
return (
<View style={twStyle('bg-app-overlay', props.style)}>
<View style={tw`mx-3 border-b border-b-app-line`} />
<View style={twStyle('bg-app-darkLine', props.style)}>
<View style={tw`mx-3 border-b border-b-app-darkLine`} />
</View>
);
}

View file

@ -1,42 +0,0 @@
import { createDrawerNavigator, DrawerScreenProps } from '@react-navigation/drawer';
import { CompositeScreenProps, NavigatorScreenParams } from '@react-navigation/native';
import { StackScreenProps } from '@react-navigation/stack';
import DrawerContent from '~/components/drawer/DrawerContent';
import { tw } from '~/lib/tailwind';
import type { RootStackParamList } from '.';
import type { TabParamList } from './TabNavigator';
import TabNavigator from './TabNavigator';
const Drawer = createDrawerNavigator<DrawerNavParamList>();
export default function DrawerNavigator() {
return (
<Drawer.Navigator
id="drawer"
initialRouteName="Home"
screenOptions={{
headerShown: false,
drawerStyle: {
backgroundColor: tw.color('app-darkBox'),
width: '70%'
},
overlayColor: 'transparent',
drawerType: 'slide',
swipeEdgeWidth: 50
}}
drawerContent={(props) => <DrawerContent {...(props as any)} />}
>
<Drawer.Screen name="Home" component={TabNavigator} />
</Drawer.Navigator>
);
}
export type DrawerNavParamList = {
Home: NavigatorScreenParams<TabParamList>;
};
export type HomeDrawerScreenProps<Screen extends keyof DrawerNavParamList> = CompositeScreenProps<
DrawerScreenProps<DrawerNavParamList, Screen>,
StackScreenProps<RootStackParamList, 'Root'>
>;

View file

@ -10,17 +10,7 @@ import { RootStackParamList } from '.';
*/
const linking: LinkingOptions<RootStackParamList> = {
prefixes: [Linking.createURL('/')],
config: {
screens: {
Root: {
screens: {
Home: 'home'
}
},
Settings: 'settings',
NotFound: '*'
}
}
config: { screens: { Root: { screens: {} }, Settings: 'settings', NotFound: '*' } }
};
export default linking;

View file

@ -40,9 +40,7 @@ export default function SettingsNavigator() {
<SettingsStack.Screen
name="Home"
component={SettingsScreen}
options={{
headerTitle: 'Settings'
}}
options={{ headerTitle: 'Settings' }}
/>
{/* Client */}
<SettingsStack.Screen
@ -53,52 +51,38 @@ export default function SettingsNavigator() {
<SettingsStack.Screen
name="LibrarySettings"
component={LibrarySettingsScreen}
options={{
headerTitle: 'Libraries'
}}
options={{ headerTitle: 'Libraries' }}
/>
<SettingsStack.Screen
name="AppearanceSettings"
component={AppearanceSettingsScreen}
options={{
headerTitle: 'Appearance'
}}
options={{ headerTitle: 'Appearance' }}
/>
<SettingsStack.Screen
name="PrivacySettings"
component={PrivacySettingsScreen}
options={{
headerTitle: 'Privacy'
}}
options={{ headerTitle: 'Privacy' }}
/>
<SettingsStack.Screen
name="ExtensionsSettings"
component={ExtensionsSettingsScreen}
options={{
headerTitle: 'Extensions'
}}
options={{ headerTitle: 'Extensions' }}
/>
{/* Library */}
<SettingsStack.Screen
name="LibraryGeneralSettings"
component={LibraryGeneralSettingsScreen}
options={{
headerTitle: 'Library Settings'
}}
options={{ headerTitle: 'Library Settings' }}
/>
<SettingsStack.Screen
name="LocationSettings"
component={LocationSettingsScreen}
options={{
headerTitle: 'Locations'
}}
options={{ headerTitle: 'Locations' }}
/>
<SettingsStack.Screen
name="EditLocationSettings"
component={EditLocationSettingsScreen}
options={{
headerTitle: 'Edit Location'
}}
options={{ headerTitle: 'Edit Location' }}
/>
<SettingsStack.Screen
name="NodesSettings"
@ -110,9 +94,7 @@ export default function SettingsNavigator() {
<SettingsStack.Screen
name="TagsSettings"
component={TagsSettingsScreen}
options={{
headerTitle: 'Tags'
}}
options={{ headerTitle: 'Tags' }}
/>
{/* <SettingsStack.Screen
name="KeysSettings"
@ -123,23 +105,17 @@ export default function SettingsNavigator() {
<SettingsStack.Screen
name="About"
component={AboutScreen}
options={{
headerTitle: 'About'
}}
options={{ headerTitle: 'About' }}
/>
<SettingsStack.Screen
name="Support"
component={SupportScreen}
options={{
headerTitle: 'Support'
}}
options={{ headerTitle: 'Support' }}
/>
<SettingsStack.Screen
name="Debug"
component={DebugScreen}
options={{
headerTitle: 'Debug'
}}
options={{ headerTitle: 'Debug' }}
/>
</SettingsStack.Navigator>
);

View file

@ -1,54 +0,0 @@
import { ParamListBase, StackNavigationState, TypedNavigator } from '@react-navigation/native';
import {
StackNavigationEventMap,
StackNavigationOptions,
StackScreenProps
} from '@react-navigation/stack';
import { ArrowLeft } from 'phosphor-react-native';
import { tw } from '~/lib/tailwind';
import LocationScreen from '~/screens/Location';
import TagScreen from '~/screens/Tag';
// Mounted on all the tabs, so we can navigate to it from any tab
export function SharedScreens(
Stack: TypedNavigator<
SharedScreensParamList,
StackNavigationState<ParamListBase>,
StackNavigationOptions,
StackNavigationEventMap,
any
>
) {
return (
<>
<Stack.Screen
name="Location"
component={LocationScreen}
options={{
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<Stack.Screen
name="Tag"
component={TagScreen}
options={{
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
</>
);
}
export type SharedScreensParamList = {
Location: { id: number; path?: string };
Tag: { id: number };
};
export type SharedScreenProps<Screen extends keyof SharedScreensParamList> = StackScreenProps<
SharedScreensParamList,
Screen
>;

View file

@ -1,9 +1,12 @@
import { BottomTabScreenProps, createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { CompositeScreenProps, NavigatorScreenParams } from '@react-navigation/native';
import { StackScreenProps } from '@react-navigation/stack';
import { BlurView } from 'expo-blur';
import { CirclesFour, FolderOpen, Planet } from 'phosphor-react-native';
import { StyleSheet } from 'react-native';
import { tw } from '~/lib/tailwind';
import type { HomeDrawerScreenProps } from './DrawerNavigator';
import { RootStackParamList } from '.';
import BrowseStack, { BrowseStackParamList } from './tabs/BrowseStack';
import NetworkStack, { NetworkStackParamList } from './tabs/NetworkStack';
import OverviewStack, { OverviewStackParamList } from './tabs/OverviewStack';
@ -16,13 +19,17 @@ export default function TabNavigator() {
id="tab"
initialRouteName="OverviewStack"
screenOptions={{
tabBarStyle: {
position: 'absolute',
// backgroundColor: 'transparent',
borderTopColor: tw.color('app')
},
tabBarBackground: () => (
<BlurView tint="dark" intensity={100} style={StyleSheet.absoluteFill} />
),
headerShown: false,
tabBarActiveTintColor: tw.color('accent'),
tabBarInactiveTintColor: tw.color('ink'),
tabBarStyle: {
backgroundColor: tw.color('app'),
borderTopColor: tw.color('app-shade')
}
tabBarInactiveTintColor: tw.color('ink')
}}
>
<Tab.Screen
@ -66,6 +73,7 @@ export default function TabNavigator() {
color={focused ? tw.color('accent') : tw.color('ink')}
/>
),
tabBarTestID: 'browse-tab',
tabBarLabel: 'Browse',
tabBarLabelStyle: tw`text-[10px] font-semibold`
}}
@ -82,5 +90,5 @@ export type TabParamList = {
export type TabScreenProps<Screen extends keyof TabParamList> = CompositeScreenProps<
BottomTabScreenProps<TabParamList, Screen>,
HomeDrawerScreenProps<'Home'>
StackScreenProps<RootStackParamList, 'Root'>
>;

View file

@ -4,9 +4,8 @@ import { tw } from '~/lib/tailwind';
import NotFoundScreen from '~/screens/NotFound';
import SearchScreen from '~/screens/Search';
import type { DrawerNavParamList } from './DrawerNavigator';
import DrawerNavigator from './DrawerNavigator';
import SettingsNavigator, { SettingsStackParamList } from './SettingsNavigator';
import TabNavigator, { TabParamList } from './TabNavigator';
const Stack = createStackNavigator<RootStackParamList>();
@ -14,11 +13,7 @@ const Stack = createStackNavigator<RootStackParamList>();
export default function RootNavigator() {
return (
<Stack.Navigator initialRouteName="Root">
<Stack.Screen
name="Root"
component={DrawerNavigator}
options={{ headerShown: false }}
/>
<Stack.Screen name="Root" component={TabNavigator} options={{ headerShown: false }} />
<Stack.Screen name="NotFound" component={NotFoundScreen} options={{ title: 'Oops!' }} />
<Stack.Screen
name="Search"
@ -45,7 +40,7 @@ export default function RootNavigator() {
}
export type RootStackParamList = {
Root: NavigatorScreenParams<DrawerNavParamList>;
Root: NavigatorScreenParams<TabParamList>;
NotFound: undefined;
// Modals
Search: undefined;

View file

@ -1,10 +1,12 @@
import { CompositeScreenProps } from '@react-navigation/native';
import { createStackNavigator, StackScreenProps } from '@react-navigation/stack';
import { ArrowLeft } from 'phosphor-react-native';
import Header from '~/components/header/Header';
import { tw } from '~/lib/tailwind';
import BrowseScreen from '~/screens/Browse';
import BrowseScreen from '~/screens/browse';
import LocationScreen from '~/screens/Location';
import TagScreen from '~/screens/Tag';
import { SharedScreens, SharedScreensParamList } from '../SharedScreens';
import { TabScreenProps } from '../TabNavigator';
const Stack = createStackNavigator<BrowseStackParamList>();
@ -21,14 +23,33 @@ export default function BrowseStack() {
}}
>
<Stack.Screen name="Browse" component={BrowseScreen} options={{ header: Header }} />
{SharedScreens(Stack as any)}
<Stack.Screen
name="Location"
component={LocationScreen}
options={{
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
<Stack.Screen
name="Tag"
component={TagScreen}
options={{
headerBackImage: () => (
<ArrowLeft size={23} color={tw.color('ink')} style={tw`ml-2`} />
)
}}
/>
</Stack.Navigator>
);
}
export type BrowseStackParamList = {
Browse: undefined;
} & SharedScreensParamList;
Location: { id: number; path?: string };
Tag: { id: number };
};
export type BrowseStackScreenProps<Screen extends keyof BrowseStackParamList> =
CompositeScreenProps<

View file

@ -2,9 +2,8 @@ import { CompositeScreenProps } from '@react-navigation/native';
import { createStackNavigator, StackScreenProps } from '@react-navigation/stack';
import Header from '~/components/header/Header';
import { tw } from '~/lib/tailwind';
import NetworkScreen from '~/screens/network';
import NetworkScreen from '../../screens/Network';
import { SharedScreens, SharedScreensParamList } from '../SharedScreens';
import { TabScreenProps } from '../TabNavigator';
const Stack = createStackNavigator<NetworkStackParamList>();
@ -21,14 +20,13 @@ export default function NetworkStack() {
}}
>
<Stack.Screen name="Network" component={NetworkScreen} options={{ header: Header }} />
{SharedScreens(Stack as any)}
</Stack.Navigator>
);
}
export type NetworkStackParamList = {
Network: undefined;
} & SharedScreensParamList;
};
export type NetworkStackScreenProps<Screen extends keyof NetworkStackParamList> =
CompositeScreenProps<

View file

@ -4,7 +4,6 @@ import Header from '~/components/header/Header';
import { tw } from '~/lib/tailwind';
import OverviewScreen from '../../screens/Overview';
import { SharedScreens, SharedScreensParamList } from '../SharedScreens';
import { TabScreenProps } from '../TabNavigator';
const Stack = createStackNavigator<OverviewStackParamList>();
@ -21,14 +20,13 @@ export default function OverviewStack() {
}}
>
<Stack.Screen name="Overview" component={OverviewScreen} options={{ header: Header }} />
{SharedScreens(Stack as any)}
</Stack.Navigator>
);
}
export type OverviewStackParamList = {
Overview: undefined;
} & SharedScreensParamList;
};
export type OverviewStackScreenProps<Screen extends keyof OverviewStackParamList> =
CompositeScreenProps<

View file

@ -1,6 +0,0 @@
import { View } from 'react-native';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
export default function BrowseScreen({ navigation, route }: BrowseStackScreenProps<'Browse'>) {
return <View></View>;
}

View file

@ -1,10 +1,10 @@
import React, { useEffect, useMemo } from 'react';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import Explorer from '~/components/explorer/Explorer';
import { SharedScreenProps } from '~/navigation/SharedScreens';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
import { getExplorerStore } from '~/stores/explorerStore';
export default function LocationScreen({ navigation, route }: SharedScreenProps<'Location'>) {
export default function LocationScreen({ navigation, route }: BrowseStackScreenProps<'Location'>) {
const { id, path } = route.params;
const location = useLibraryQuery(['locations.get', route.params.id]);

View file

@ -9,8 +9,8 @@ export default function NotFoundScreen({ navigation }: RootStackScreenProps<'Not
<TouchableOpacity
onPress={() =>
navigation.replace('Root', {
screen: 'Home',
params: { screen: 'OverviewStack', params: { screen: 'Overview' } }
screen: 'BrowseStack',
params: { screen: 'Browse' }
})
}
style={tw`mt-4 py-4`}

View file

@ -1,13 +1,16 @@
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { View } from 'react-native';
import VirtualizedListWrapper from '~/components/layout/VirtualizedListWrapper';
import OverviewStats from '~/components/overview/OverviewStats';
import { tw } from '~/lib/tailwind';
import { twStyle } from '~/lib/tailwind';
import { OverviewStackScreenProps } from '~/navigation/tabs/OverviewStack';
export default function OverviewScreen({ navigation }: OverviewStackScreenProps<'Overview'>) {
const height = useBottomTabBarHeight();
return (
<VirtualizedListWrapper>
<View style={tw`mt-4 px-4`}>
<View style={twStyle('mt-4 px-4', { marginBottom: height })}>
<OverviewStats />
</View>
</VirtualizedListWrapper>

View file

@ -1,9 +1,9 @@
import { useEffect } from 'react';
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
import Explorer from '~/components/explorer/Explorer';
import { SharedScreenProps } from '~/navigation/SharedScreens';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
export default function TagScreen({ navigation, route }: SharedScreenProps<'Tag'>) {
export default function TagScreen({ navigation, route }: BrowseStackScreenProps<'Tag'>) {
const { id } = route.params;
const search = useLibraryQuery([

View file

@ -0,0 +1,56 @@
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { CheckCircle, Gear } from 'phosphor-react-native';
import React, { useRef } from 'react';
import { Pressable, ScrollView, View } from 'react-native';
import { JobManagerContextProvider, useLibraryQuery } from '@sd/client';
import { PulseAnimation } from '~/components/animation/lottie';
import BrowseLocations from '~/components/browse/BrowseLocations';
import BrowseTags from '~/components/browse/BrowseTags';
import BrowseLibraryManager from '~/components/browse/DrawerLibraryManager';
import { ModalRef } from '~/components/layout/Modal';
import { JobManagerModal } from '~/components/modal/job/JobManagerModal';
import { tw, twStyle } from '~/lib/tailwind';
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
function JobIcon() {
const { data: isActive } = useLibraryQuery(['jobs.isActive']);
return isActive ? (
<PulseAnimation style={tw`h-[24px] w-[32px]`} speed={1.5} />
) : (
<CheckCircle color="white" size={24} />
);
}
export default function BrowseScreen({ navigation, route }: BrowseStackScreenProps<'Browse'>) {
const modalRef = useRef<ModalRef>(null);
const height = useBottomTabBarHeight();
return (
<ScrollView style={twStyle('flex-1 px-3', { marginBottom: height })}>
<View style={twStyle('justify-between')}>
<View style={tw`mt-6`} />
{/* Library Manager */}
<BrowseLibraryManager />
{/* Locations */}
<BrowseLocations />
{/* Tags */}
<BrowseTags />
<View style={tw`flex w-full flex-row items-center gap-x-4`}>
{/* Settings */}
<Pressable onPress={() => navigation.navigate('Settings', { screen: 'Home' })}>
<Gear color="white" size={24} />
</Pressable>
{/* Job Manager */}
<JobManagerContextProvider>
<Pressable onPress={() => modalRef.current?.present()}>
<JobIcon />
</Pressable>
<JobManagerModal ref={modalRef} />
</JobManagerContextProvider>
</View>
</View>
</ScrollView>
);
}

View file

@ -1,11 +1,14 @@
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { Text, View } from 'react-native';
import { Icon } from '~/components/icons/Icon';
import { tw } from '~/lib/tailwind';
import { tw, twStyle } from '~/lib/tailwind';
import { NetworkStackScreenProps } from '~/navigation/tabs/NetworkStack';
export default function NetworkScreen({ navigation }: NetworkStackScreenProps<'Network'>) {
const height = useBottomTabBarHeight();
return (
<View style={tw`flex-1 items-center justify-center`}>
<View style={twStyle('flex-1 items-center justify-center', { marginBottom: height })}>
<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

@ -2,11 +2,11 @@ appId: com.spacedrive.app
---
- launchApp
- tapOn:
id: 'drawer-toggle'
id: 'browse-tab'
- tapOn: 'Add Tag'
- tapOn:
id: 'create-tag-name'
- inputText: 'MyTag'
- tapOn: Create
- assertVisible:
id: 'drawer-tag'
id: 'browse-tag'

View file

@ -11,5 +11,5 @@ appId: com.spacedrive.app
id: 'share-minimal'
- tapOn: 'Continue'
- tapOn:
id: 'drawer-toggle'
id: 'browse-tab'
- assertVisible: 'TestLib'

View file

@ -379,6 +379,9 @@ importers:
expo:
specifier: ~49.0.8
version: 49.0.16(@babel/core@7.23.2)
expo-blur:
specifier: ^12.6.0
version: 12.6.0(expo@49.0.16)
expo-build-properties:
specifier: ~0.8.3
version: 0.8.3(expo@49.0.16)
@ -6100,7 +6103,7 @@ packages:
magic-string: 0.27.0
react-docgen-typescript: 2.2.2(typescript@5.2.2)
typescript: 5.2.2
vite: 4.5.0(less@4.2.0)
vite: 4.5.0(@types/node@18.17.19)
/@jridgewell/gen-mapping@0.3.3:
resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
@ -9247,7 +9250,7 @@ packages:
magic-string: 0.30.5
rollup: 3.29.4
typescript: 5.2.2
vite: 4.5.0(less@4.2.0)
vite: 4.5.0(@types/node@18.17.19)
transitivePeerDependencies:
- encoding
- supports-color
@ -9600,7 +9603,7 @@ packages:
react: 18.2.0
react-docgen: 6.0.4
react-dom: 18.2.0(react@18.2.0)
vite: 4.5.0(less@4.2.0)
vite: 4.5.0(@types/node@18.17.19)
transitivePeerDependencies:
- '@preact/preset-vite'
- encoding
@ -10836,7 +10839,7 @@ packages:
'@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.23.2)
magic-string: 0.27.0
react-refresh: 0.14.0
vite: 4.5.0(less@4.2.0)
vite: 4.5.0(@types/node@18.17.19)
transitivePeerDependencies:
- supports-color
@ -14167,6 +14170,14 @@ packages:
- supports-color
dev: false
/expo-blur@12.6.0(expo@49.0.16):
resolution: {integrity: sha512-yrZYu4mQX4ZJtSrjNVMuB9kCMB8Xerk5Zn5iES6ojmGAk+yxJ/jdyhaUVqbpaA8LBmspdKgQz2dW8+9wa8dSAg==}
peerDependencies:
expo: '*'
dependencies:
expo: 49.0.16(@babel/core@7.23.2)
dev: false
/expo-build-properties@0.8.3(expo@49.0.16):
resolution: {integrity: sha512-kEDDuAadHqJTkvCGK4fXYHVrePiJO1DjyW95AicmwuGwQvGJydYFbuoauf9ybAU+4UH4arhbce8gHI3ZpIj3Jw==}
peerDependencies:
@ -23369,7 +23380,6 @@ packages:
rollup: 3.29.4
optionalDependencies:
fsevents: 2.3.3
dev: true
/vite@4.5.0(less@4.2.0):
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}
@ -23405,6 +23415,7 @@ packages:
rollup: 3.29.4
optionalDependencies:
fsevents: 2.3.3
dev: true
/vite@4.5.0(sass@1.69.5):
resolution: {integrity: sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==}