Display add button on android (#846)

This commit is contained in:
Leendert de Borst
2025-05-23 15:44:56 +02:00
parent cbe224385d
commit e6b7d1afa1
4 changed files with 81 additions and 29 deletions

View File

@@ -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' ? <AndroidHeader title="Credentials" /> : <Text>Credentials</Text>,
...defaultHeaderOptions,
}}
/>

View File

@@ -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' ? <AndroidHeader title="Credentials" headerButtons={headerButtons} /> : <Text>Credentials</Text>,
});
}, [navigation, headerButtons]);
return (
<ThemedView style={styles.container}>
<CollapsibleHeader

View File

@@ -1,9 +1,19 @@
import { View, StyleSheet } from 'react-native';
import { View, StyleSheet, TouchableOpacity } from 'react-native';
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
import { ThemedText } from '@/components/themed/ThemedText';
import { useColors } from '@/hooks/useColorScheme';
import Logo from '@/assets/images/logo.svg';
type HeaderButton = {
icon: keyof typeof MaterialIcons.glyphMap;
onPress: () => 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 (
<View style={styles.headerContainer}>
{headerButtons.find(b => b.position === 'left') && (
<TouchableOpacity
style={[styles.headerButton, styles.leftButton]}
onPress={headerButtons.find(b => b.position === 'left')?.onPress}
>
<MaterialIcons
name={headerButtons.find(b => b.position === 'left')?.icon ?? 'add'}
size={28}
color={colors.primary}
/>
</TouchableOpacity>
)}
<Logo width={40} height={40} style={styles.logo} />
<ThemedText style={styles.headerTitle}>{title}</ThemedText>
{headerButtons.find(b => b.position === 'right') && (
<TouchableOpacity
style={[styles.headerButton, styles.rightButton]}
onPress={headerButtons.find(b => b.position === 'right')?.onPress}
>
<MaterialIcons
name={headerButtons.find(b => b.position === 'right')?.icon ?? 'add'}
size={28}
color={colors.primary}
/>
</TouchableOpacity>
)}
</View>
);
}
const styles = StyleSheet.create({
headerContainer: {
alignItems: 'center',
flexDirection: 'row',
gap: 8,
verticalAlign: 'middle',
},
headerTitle: {
fontSize: 22,
fontWeight: 'bold',
},
logo: {
marginBottom: 0,
},
});
}

View File

@@ -99,7 +99,7 @@ export function CollapsibleHeader({
right: 0,
},
headerButton: {
bottom: 6,
bottom: 2,
padding: 4,
position: 'absolute',
},