mirror of
https://github.com/aliasvault/aliasvault.git
synced 2026-05-18 13:28:12 -04:00
Add android autofill instructions page (#846)
This commit is contained in:
@@ -32,6 +32,14 @@ export default function SettingsLayout(): React.ReactNode {
|
||||
...defaultHeaderOptions,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="android-autofill"
|
||||
options={{
|
||||
title: 'Android Autofill',
|
||||
headerBackTitle: 'Settings',
|
||||
...defaultHeaderOptions,
|
||||
}}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="vault-unlock"
|
||||
options={{
|
||||
|
||||
145
apps/mobile-app/app/(tabs)/settings/android-autofill.tsx
Normal file
145
apps/mobile-app/app/(tabs)/settings/android-autofill.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import { StyleSheet, View, TouchableOpacity, Linking } from 'react-native';
|
||||
import { router } from 'expo-router';
|
||||
|
||||
import { ThemedText } from '@/components/themed/ThemedText';
|
||||
import { useColors } from '@/hooks/useColorScheme';
|
||||
import { useAuth } from '@/context/AuthContext';
|
||||
import { ThemedScrollView } from '@/components/themed/ThemedScrollView';
|
||||
import { ThemedContainer } from '@/components/themed/ThemedContainer';
|
||||
|
||||
/**
|
||||
* Android autofill screen.
|
||||
*/
|
||||
export default function AndroidAutofillScreen() : React.ReactNode {
|
||||
const colors = useColors();
|
||||
const { markAndroidAutofillConfigured, shouldShowAndroidAutofillReminder } = useAuth();
|
||||
|
||||
/**
|
||||
* Handle the configure press.
|
||||
*/
|
||||
const handleConfigurePress = async () : Promise<void> => {
|
||||
await markAndroidAutofillConfigured();
|
||||
try {
|
||||
await Linking.sendIntent('android.settings.SETTINGS');
|
||||
router.back();
|
||||
} catch (err) {
|
||||
console.warn('Failed to open settings:', err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the already configured press.
|
||||
*/
|
||||
const handleAlreadyConfigured = async () : Promise<void> => {
|
||||
await markAndroidAutofillConfigured();
|
||||
router.back();
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
buttonContainer: {
|
||||
padding: 16,
|
||||
paddingBottom: 32,
|
||||
},
|
||||
configureButton: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.primary,
|
||||
borderRadius: 10,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
configureButtonText: {
|
||||
color: colors.primarySurfaceText,
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
header: {
|
||||
paddingTop: 12,
|
||||
},
|
||||
headerText: {
|
||||
color: colors.textMuted,
|
||||
fontSize: 13,
|
||||
},
|
||||
instructionContainer: {
|
||||
paddingTop: 16,
|
||||
},
|
||||
instructionStep: {
|
||||
color: colors.text,
|
||||
fontSize: 15,
|
||||
lineHeight: 22,
|
||||
marginBottom: 8,
|
||||
},
|
||||
instructionTitle: {
|
||||
color: colors.text,
|
||||
fontSize: 17,
|
||||
fontWeight: '600',
|
||||
marginBottom: 8,
|
||||
},
|
||||
secondaryButton: {
|
||||
alignItems: 'center',
|
||||
backgroundColor: colors.accentBackground,
|
||||
borderRadius: 10,
|
||||
marginTop: 12,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
secondaryButtonText: {
|
||||
color: colors.text,
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
},
|
||||
warningText: {
|
||||
color: colors.textMuted,
|
||||
fontSize: 15,
|
||||
fontStyle: 'italic',
|
||||
marginTop: 8,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<ThemedContainer>
|
||||
<ThemedScrollView>
|
||||
<View style={styles.header}>
|
||||
<ThemedText style={styles.headerText}>
|
||||
You can configure AliasVault to provide native password autofill functionality in Android. Follow the instructions below to enable it.
|
||||
</ThemedText>
|
||||
</View>
|
||||
|
||||
<View style={styles.instructionContainer}>
|
||||
<ThemedText style={styles.instructionTitle}>How to enable:</ThemedText>
|
||||
<ThemedText style={styles.instructionStep}>
|
||||
1. Open Android Settings via the button below
|
||||
</ThemedText>
|
||||
<View style={styles.buttonContainer}>
|
||||
<TouchableOpacity
|
||||
style={styles.configureButton}
|
||||
onPress={handleConfigurePress}
|
||||
>
|
||||
<ThemedText style={styles.configureButtonText}>
|
||||
Open Android Settings
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ThemedText style={styles.instructionStep}>
|
||||
2. Navigate to "Passwords, passkeys & accounts" in the settings menu
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.instructionStep}>
|
||||
3. Change the "prefered service" to "AliasVault"
|
||||
</ThemedText>
|
||||
<ThemedText style={styles.warningText}>
|
||||
Note: You'll need to authenticate with your device security method when using autofill.
|
||||
</ThemedText>
|
||||
<View style={styles.buttonContainer}>
|
||||
{shouldShowAndroidAutofillReminder && (
|
||||
<TouchableOpacity
|
||||
style={styles.secondaryButton}
|
||||
onPress={handleAlreadyConfigured}
|
||||
>
|
||||
<ThemedText style={styles.secondaryButtonText}>
|
||||
I already configured it
|
||||
</ThemedText>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</ThemedScrollView>
|
||||
</ThemedContainer>
|
||||
);
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import { ThemedContainer } from '@/components/themed/ThemedContainer';
|
||||
export default function SettingsScreen() : React.ReactNode {
|
||||
const webApi = useWebApi();
|
||||
const colors = useColors();
|
||||
const { getAuthMethodDisplay, shouldShowIosAutofillReminder } = useAuth();
|
||||
const { getAuthMethodDisplay, shouldShowIosAutofillReminder, shouldShowAndroidAutofillReminder } = useAuth();
|
||||
const { getAutoLockTimeout } = useAuth();
|
||||
const scrollY = useRef(new Animated.Value(0)).current;
|
||||
const scrollViewRef = useRef<ScrollView>(null);
|
||||
@@ -123,6 +123,13 @@ export default function SettingsScreen() : React.ReactNode {
|
||||
router.push('/(tabs)/settings/ios-autofill');
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the Android autofill press.
|
||||
*/
|
||||
const handleAndroidAutofillPress = () : void => {
|
||||
router.push('/(tabs)/settings/android-autofill');
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
scrollContent: {
|
||||
paddingBottom: 40,
|
||||
@@ -246,6 +253,28 @@ export default function SettingsScreen() : React.ReactNode {
|
||||
<View style={styles.separator} />
|
||||
</>
|
||||
)}
|
||||
{Platform.OS === 'android' && (
|
||||
<>
|
||||
<TouchableOpacity
|
||||
style={styles.settingItem}
|
||||
onPress={handleAndroidAutofillPress}
|
||||
>
|
||||
<View style={styles.settingItemIcon}>
|
||||
<Ionicons name="key-outline" size={20} color={colors.text} />
|
||||
</View>
|
||||
<View style={styles.settingItemContent}>
|
||||
<ThemedText style={styles.settingItemText}>Android Autofill</ThemedText>
|
||||
{shouldShowAndroidAutofillReminder && (
|
||||
<View style={styles.settingItemBadge}>
|
||||
<ThemedText style={styles.settingItemBadgeText}>1</ThemedText>
|
||||
</View>
|
||||
)}
|
||||
<Ionicons name="chevron-forward" size={20} color={colors.textMuted} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<View style={styles.separator} />
|
||||
</>
|
||||
)}
|
||||
<TouchableOpacity
|
||||
style={styles.settingItem}
|
||||
onPress={handleVaultUnlockPress}
|
||||
|
||||
@@ -37,12 +37,16 @@ type AuthContextType = {
|
||||
// iOS Autofill methods
|
||||
shouldShowIosAutofillReminder: boolean;
|
||||
markIosAutofillConfigured: () => Promise<void>;
|
||||
// Android Autofill methods
|
||||
shouldShowAndroidAutofillReminder: boolean;
|
||||
markAndroidAutofillConfigured: () => Promise<void>;
|
||||
// Return URL methods
|
||||
returnUrl: { path: string; params?: object } | null;
|
||||
setReturnUrl: (url: { path: string; params?: object } | null) => void;
|
||||
}
|
||||
|
||||
const IOS_AUTOFILL_CONFIGURED_KEY = 'ios_autofill_configured';
|
||||
const ANDROID_AUTOFILL_CONFIGURED_KEY = 'android_autofill_configured';
|
||||
|
||||
/**
|
||||
* Auth context.
|
||||
@@ -58,6 +62,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
const [username, setUsername] = useState<string | null>(null);
|
||||
const [globalMessage, setGlobalMessage] = useState<string | null>(null);
|
||||
const [shouldShowIosAutofillReminder, setShouldShowIosAutofillReminder] = useState(false);
|
||||
const [shouldShowAndroidAutofillReminder, setShouldShowAndroidAutofillReminder] = useState(false);
|
||||
const [returnUrl, setReturnUrl] = useState<{ path: string; params?: object } | null>(null);
|
||||
const [isOffline, setIsOffline] = useState(false);
|
||||
const appState = useRef(AppState.currentState);
|
||||
@@ -357,17 +362,45 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
setShouldShowIosAutofillReminder(false);
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Mark Android autofill as configured.
|
||||
*/
|
||||
const markAndroidAutofillConfigured = useCallback(async (): Promise<void> => {
|
||||
await AsyncStorage.setItem(ANDROID_AUTOFILL_CONFIGURED_KEY, 'true');
|
||||
setShouldShowAndroidAutofillReminder(false);
|
||||
}, []);
|
||||
|
||||
// Load iOS Autofill state on mount
|
||||
useEffect(() => {
|
||||
loadIosAutofillState();
|
||||
}, [loadIosAutofillState]);
|
||||
|
||||
// Check if autofill is configured on mount
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Check if autofill is configured for the current platform.
|
||||
* @returns {Promise<void>} A promise that resolves when the check is complete
|
||||
*/
|
||||
const checkAutofillConfiguration = async (): Promise<void> => {
|
||||
if (Platform.OS === 'ios') {
|
||||
const isConfigured = await AsyncStorage.getItem(IOS_AUTOFILL_CONFIGURED_KEY);
|
||||
setShouldShowIosAutofillReminder(!isConfigured);
|
||||
} else if (Platform.OS === 'android') {
|
||||
const isConfigured = await AsyncStorage.getItem(ANDROID_AUTOFILL_CONFIGURED_KEY);
|
||||
setShouldShowAndroidAutofillReminder(!isConfigured);
|
||||
}
|
||||
};
|
||||
|
||||
checkAutofillConfiguration();
|
||||
}, []);
|
||||
|
||||
const contextValue = useMemo(() => ({
|
||||
isLoggedIn,
|
||||
isInitialized,
|
||||
username,
|
||||
globalMessage,
|
||||
shouldShowIosAutofillReminder,
|
||||
shouldShowAndroidAutofillReminder,
|
||||
returnUrl,
|
||||
isOffline,
|
||||
getEnabledAuthMethods,
|
||||
@@ -383,6 +416,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
setAutoLockTimeout,
|
||||
getBiometricDisplayName,
|
||||
markIosAutofillConfigured,
|
||||
markAndroidAutofillConfigured,
|
||||
setReturnUrl,
|
||||
verifyPassword,
|
||||
setOfflineMode: setIsOffline,
|
||||
@@ -392,6 +426,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
username,
|
||||
globalMessage,
|
||||
shouldShowIosAutofillReminder,
|
||||
shouldShowAndroidAutofillReminder,
|
||||
returnUrl,
|
||||
isOffline,
|
||||
getEnabledAuthMethods,
|
||||
@@ -407,6 +442,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
||||
setAutoLockTimeout,
|
||||
getBiometricDisplayName,
|
||||
markIosAutofillConfigured,
|
||||
markAndroidAutofillConfigured,
|
||||
setReturnUrl,
|
||||
verifyPassword,
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user