diff --git a/app/login.tsx b/app/login.tsx index 89396fb4..c0ed61d4 100644 --- a/app/login.tsx +++ b/app/login.tsx @@ -283,7 +283,8 @@ const Login: React.FC = () => { keyboardType='default' returnKeyType='done' autoCapitalize='none' - textContentType='oneTimeCode' + autoCorrect={false} + textContentType='username' clearButtonMode='while-editing' maxLength={500} extraClassName='mb-4' @@ -440,9 +441,8 @@ const Login: React.FC = () => { keyboardType='default' returnKeyType='done' autoCapitalize='none' - // Changed from username to oneTimeCode because it is a known issue in RN - // https://github.com/facebook/react-native/issues/47106#issuecomment-2521270037 - textContentType='oneTimeCode' + autoCorrect={false} + textContentType='username' clearButtonMode='while-editing' maxLength={500} /> diff --git a/components/PreviousServersList.tsx b/components/PreviousServersList.tsx index ea93f773..ca801901 100644 --- a/components/PreviousServersList.tsx +++ b/components/PreviousServersList.tsx @@ -1,14 +1,17 @@ import { Ionicons } from "@expo/vector-icons"; import type React from "react"; -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, Alert, TouchableOpacity, View } from "react-native"; +import { Swipeable } from "react-native-gesture-handler"; import { useMMKVString } from "react-native-mmkv"; import { Colors } from "@/constants/Colors"; import { deleteServerCredential, + removeServerFromList, type SavedServer, } from "@/utils/secureCredentials"; +import { Text } from "./common/Text"; import { ListGroup } from "./list/ListGroup"; import { ListItem } from "./list/ListItem"; @@ -78,41 +81,46 @@ export const PreviousServersList: React.FC = ({ ); }; + const handleRemoveServer = useCallback( + async (serverUrl: string) => { + await removeServerFromList(serverUrl); + // Update UI + const filtered = previousServers.filter((s) => s.address !== serverUrl); + setPreviousServers(JSON.stringify(filtered)); + }, + [previousServers, setPreviousServers], + ); + + const renderRightActions = useCallback( + (serverUrl: string, swipeableRef: React.RefObject) => ( + { + swipeableRef.current?.close(); + handleRemoveServer(serverUrl); + }} + className='bg-red-600 justify-center items-center px-5' + > + + + ), + [handleRemoveServer], + ); + if (!previousServers.length) return null; return ( {previousServers.map((s) => ( - handleServerPress(s)} - title={s.name || s.address} - subtitle={ - s.hasCredentials - ? `${s.username} • ${t("server.saved")}` - : s.name - ? s.address - : undefined - } - showArrow={loadingServer !== s.address} - disabled={loadingServer === s.address} - > - {loadingServer === s.address ? ( - - ) : s.hasCredentials ? ( - { - e.stopPropagation(); - handleRemoveCredential(s.address); - }} - className='p-1' - hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} - > - - - ) : null} - + onRemoveCredential={() => handleRemoveCredential(s.address)} + renderRightActions={renderRightActions} + t={t} + /> ))} { @@ -122,6 +130,71 @@ export const PreviousServersList: React.FC = ({ textColor='red' /> + + {t("server.swipe_to_remove")} + ); }; + +interface ServerItemProps { + server: SavedServer; + loadingServer: string | null; + onPress: () => void; + onRemoveCredential: () => void; + renderRightActions: ( + serverUrl: string, + swipeableRef: React.RefObject, + ) => React.ReactNode; + t: (key: string) => string; +} + +const ServerItem: React.FC = ({ + server, + loadingServer, + onPress, + onRemoveCredential, + renderRightActions, + t, +}) => { + const swipeableRef = useRef(null); + + return ( + + renderRightActions(server.address, swipeableRef) + } + overshootRight={false} + > + + {loadingServer === server.address ? ( + + ) : server.hasCredentials ? ( + { + e.stopPropagation(); + onRemoveCredential(); + }} + className='p-1' + hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} + > + + + ) : null} + + + ); +}; diff --git a/translations/en.json b/translations/en.json index 1edd9c1a..6f00ba87 100644 --- a/translations/en.json +++ b/translations/en.json @@ -29,7 +29,8 @@ "server_url_placeholder": "http(s)://your-server.com", "connect_button": "Connect", "previous_servers": "Previous Servers", - "clear_button": "Clear", + "clear_button": "Clear all", + "swipe_to_remove": "Swipe to remove", "search_for_local_servers": "Search for Local Servers", "searching": "Searching...", "servers": "Servers", diff --git a/utils/secureCredentials.ts b/utils/secureCredentials.ts index 43228f59..90a23eb3 100644 --- a/utils/secureCredentials.ts +++ b/utils/secureCredentials.ts @@ -136,6 +136,18 @@ export function getPreviousServers(): SavedServer[] { return []; } +/** + * Remove a server from the previous servers list and delete its credentials. + */ +export async function removeServerFromList(serverUrl: string): Promise { + // First delete any saved credentials + await deleteServerCredential(serverUrl); + // Then remove from the list + const previousServers = getPreviousServers(); + const filtered = previousServers.filter((s) => s.address !== serverUrl); + storage.set("previousServers", JSON.stringify(filtered)); +} + /** * Migrate existing previousServers to new format (add hasCredentials: false). * Should be called on app startup.