diff --git a/apps/mobile-app/app/(tabs)/credentials/_layout.tsx b/apps/mobile-app/app/(tabs)/credentials/_layout.tsx index 1bfdf8963..20efd4a48 100644 --- a/apps/mobile-app/app/(tabs)/credentials/_layout.tsx +++ b/apps/mobile-app/app/(tabs)/credentials/_layout.tsx @@ -1,5 +1,6 @@ import { Stack } from 'expo-router'; import { Platform, Text } from 'react-native'; +import { useRouter } from 'expo-router'; import { defaultHeaderOptions } from '@/components/themed/ThemedHeader'; import { AndroidHeader } from '@/components/ui/AndroidHeader'; @@ -16,12 +17,6 @@ export default function CredentialsLayout(): React.ReactNode { options={{ title: 'Credentials', headerShown: Platform.OS === 'android', - /** - * On Android, we use a custom header component that includes the AliasVault logo. - * On iOS, we don't show the header as a custom collapsible header is used. - * @returns {React.ReactNode} The header component - */ - headerTitle: (): React.ReactNode => Platform.OS === 'android' ? : Credentials, ...defaultHeaderOptions, }} /> diff --git a/apps/mobile-app/app/(tabs)/credentials/index.tsx b/apps/mobile-app/app/(tabs)/credentials/index.tsx index de6b52c30..59c6d65c4 100644 --- a/apps/mobile-app/app/(tabs)/credentials/index.tsx +++ b/apps/mobile-app/app/(tabs)/credentials/index.tsx @@ -1,5 +1,5 @@ import { StyleSheet, Text, FlatList, TouchableOpacity, TextInput, RefreshControl, Platform, Animated, Alert } from 'react-native'; -import { useState, useEffect, useCallback, useRef } from 'react'; +import { useState, useEffect, useCallback, useRef, useMemo } from 'react'; import { useNavigation } from '@react-navigation/native'; import { useRouter } from 'expo-router'; import Toast from 'react-native-toast-message'; @@ -18,6 +18,7 @@ import { CredentialCard } from '@/components/credentials/CredentialCard'; import { TitleContainer } from '@/components/ui/TitleContainer'; import { CollapsibleHeader } from '@/components/ui/CollapsibleHeader'; import { SkeletonLoader } from '@/components/ui/SkeletonLoader'; +import { AndroidHeader } from '@/components/ui/AndroidHeader'; import emitter from '@/utils/EventEmitter'; import { useMinDurationLoading } from '@/hooks/useMinDurationLoading'; import { useWebApi } from '@/context/WebApiContext'; @@ -65,14 +66,14 @@ export default function CredentialsScreen() : React.ReactNode { } }, [dbContext.sqliteClient, setIsLoadingCredentials]); - const headerButtons = [{ + const headerButtons = useMemo(() => [{ icon: 'add' as const, position: 'right' as const, /** * Add credential. */ onPress: () : void => router.push('/(tabs)/credentials/add-edit') - }]; + }], [router]); useEffect(() => { const unsubscribeFocus = navigation.addListener('focus', () => { @@ -261,6 +262,17 @@ export default function CredentialsScreen() : React.ReactNode { }, }); + // Set header buttons + useEffect(() => { + navigation.setOptions({ + /** + * Define custom header which is shown on Android. iOS displays the custom CollapsibleHeader component instead. + * @returns + */ + headerTitle: (): React.ReactNode => Platform.OS === 'android' ? : Credentials, + }); + }, [navigation, headerButtons]); + return ( void; + position: 'left' | 'right'; +} + interface IAndroidHeaderProps { title: string; + headerButtons?: HeaderButton[]; } /** @@ -11,27 +21,62 @@ interface IAndroidHeaderProps { * @param {IAndroidHeaderProps} props - The component props * @returns {React.ReactNode} The Android header component */ -export function AndroidHeader({ title }: IAndroidHeaderProps): React.ReactNode { +export function AndroidHeader({ title, headerButtons = [] }: IAndroidHeaderProps): React.ReactNode { + const colors = useColors(); + + const styles = StyleSheet.create({ + headerButton: { + padding: 4, + }, + headerContainer: { + alignItems: 'center', + flexDirection: 'row', + gap: 8, + verticalAlign: 'middle', + }, + headerTitle: { + fontSize: 22, + fontWeight: 'bold', + }, + leftButton: { + marginRight: 'auto', + }, + logo: { + marginBottom: 0, + }, + rightButton: { + marginLeft: 'auto', + }, + }); + return ( + {headerButtons.find(b => b.position === 'left') && ( + b.position === 'left')?.onPress} + > + b.position === 'left')?.icon ?? 'add'} + size={28} + color={colors.primary} + /> + + )} {title} + {headerButtons.find(b => b.position === 'right') && ( + b.position === 'right')?.onPress} + > + b.position === 'right')?.icon ?? 'add'} + size={28} + color={colors.primary} + /> + + )} ); -} - -const styles = StyleSheet.create({ - headerContainer: { - alignItems: 'center', - flexDirection: 'row', - gap: 8, - verticalAlign: 'middle', - }, - headerTitle: { - fontSize: 22, - fontWeight: 'bold', - }, - logo: { - marginBottom: 0, - }, -}); \ No newline at end of file +} \ No newline at end of file diff --git a/apps/mobile-app/components/ui/CollapsibleHeader.tsx b/apps/mobile-app/components/ui/CollapsibleHeader.tsx index 1ea217cb7..42f89c094 100644 --- a/apps/mobile-app/components/ui/CollapsibleHeader.tsx +++ b/apps/mobile-app/components/ui/CollapsibleHeader.tsx @@ -99,7 +99,7 @@ export function CollapsibleHeader({ right: 0, }, headerButton: { - bottom: 6, + bottom: 2, padding: 4, position: 'absolute', },