From 360346213a5c609b51241e6b1562c81fd04a402d Mon Sep 17 00:00:00 2001 From: Lopez <272230284+LopezDev7@users.noreply.github.com> Date: Mon, 10 Aug 2026 17:17:37 -0500 Subject: [PATCH 1/3] Remember recently used nicknames --- src/containers/NavBar.tsx | 96 +++++++++++++++++++++++++++++++++++---- src/states/settings.ts | 19 +++++++- src/utils/game.ts | 6 ++- 3 files changed, 108 insertions(+), 13 deletions(-) diff --git a/src/containers/NavBar.tsx b/src/containers/NavBar.tsx index d132aaf3..7fa2209b 100644 --- a/src/containers/NavBar.tsx +++ b/src/containers/NavBar.tsx @@ -1,6 +1,6 @@ -import { memo, useCallback, useMemo } from "react"; +import { memo, useCallback, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; // ✅ use this instead -import { StyleSheet, TextInput, View } from "react-native"; +import { Pressable, StyleSheet, Text, TextInput, View } from "react-native"; import Icon from "../components/Icon"; import TabBar from "../components/TabBar"; import { images } from "../constants/images"; @@ -13,9 +13,31 @@ import { ListType } from "../utils/types"; const NavBar = memo(() => { const { t, i18n } = useTranslation(); const { theme } = useTheme(); - const { nickName, setNickName } = useSettings(); + const { nickName, setNickName, recentNicknames } = useSettings(); const { setListType, listType } = useGenericTempState(); + const [dropdownOpen, setDropdownOpen] = useState(false); + const hideTimer = useRef | null>(null); + + const openDropdown = useCallback(() => { + if (hideTimer.current) clearTimeout(hideTimer.current); + setDropdownOpen(true); + }, []); + + const closeDropdown = useCallback(() => { + if (hideTimer.current) clearTimeout(hideTimer.current); + hideTimer.current = setTimeout(() => setDropdownOpen(false), 150); + }, []); + + const handleRecentNicknamePress = useCallback( + (name: string) => { + setNickName(name); + if (hideTimer.current) clearTimeout(hideTimer.current); + setDropdownOpen(false); + }, + [setNickName] + ); + const tabList = useMemo( () => [ { @@ -88,13 +110,46 @@ const NavBar = memo(() => { color={theme.textSecondary} /> - + + + {dropdownOpen && recentNicknames.length > 0 && ( + + {recentNicknames.map((name) => ( + handleRecentNicknamePress(name)} + style={styles.dropdownItem} + > + + {name} + + + ))} + + )} + @@ -128,6 +183,27 @@ const styles = StyleSheet.create({ flexDirection: "row", alignItems: "center", }, + nicknameInputWrapper: { + position: "relative", + }, + dropdown: { + position: "absolute", + top: sc(36), + left: 0, + width: sc(160), + borderRadius: sc(5), + borderWidth: 1, + overflow: "hidden", + zIndex: 100, + }, + dropdownItem: { + paddingHorizontal: sc(8), + paddingVertical: sc(7), + }, + dropdownItemText: { + fontFamily: "Proxima Nova Regular", + fontSize: sc(15), + }, nicknameIconContainer: { height: sc(35), width: sc(35), diff --git a/src/states/settings.ts b/src/states/settings.ts index 7cd7784a..bf126112 100644 --- a/src/states/settings.ts +++ b/src/states/settings.ts @@ -5,13 +5,17 @@ import { createJSONStorage, persist } from "zustand/middleware"; import { stateStorage } from "../utils/stateStorage"; import { SAMPDLLVersions } from "../utils/types"; +const MAX_RECENT_NICKNAMES = 5; + interface SettingsPersistentState { nickName: string; gtasaPath: string; customGameExe: string; sampVersion: SAMPDLLVersions; dataMerged: boolean; + recentNicknames: string[]; setNickName: (name: string) => void; + addRecentNickname: (name: string) => void; setGTASAPath: (path: string) => void; setCustomGameExe: (fileName: string) => void; setSampVersion: (version: SAMPDLLVersions) => void; @@ -28,11 +32,24 @@ const useSettings = create()( customGameExe: "", sampVersion: "custom", dataMerged: false, + recentNicknames: [], setNickName: (name) => set(() => { emitWithDelay("setNickName", name); return { nickName: name }; }), + addRecentNickname: (name) => + set((state) => { + const trimmed = name.trim(); + if (!trimmed) return state; + emitWithDelay("setRecentNickname", trimmed); + const rest = state.recentNicknames.filter( + (n) => n.toLowerCase() !== trimmed.toLowerCase() + ); + return { + recentNicknames: [trimmed, ...rest].slice(0, MAX_RECENT_NICKNAMES), + }; + }), setGTASAPath: (path) => set({ gtasaPath: path }), setCustomGameExe: (fileName) => set({ customGameExe: fileName }), setSampVersion: (version) => set({ sampVersion: version }), @@ -44,7 +61,7 @@ const useSettings = create()( ) ); -["setNickName"].forEach((event) => +["setNickName", "setRecentNickname"].forEach((event) => listen(event, (ev) => { if (ev.windowLabel !== appWindow.label) { useSettings.persist.rehydrate(); diff --git a/src/utils/game.ts b/src/utils/game.ts index a0bb3442..0469bce8 100644 --- a/src/utils/game.ts +++ b/src/utils/game.ts @@ -82,6 +82,7 @@ export const startGame = async ( }:${nickname}:${password}` : `connect:${await getIpAddress(server.ip)}:${server.port}:${nickname}`, }); + useSettings.getState().addRecentNickname(nickname); return; } @@ -216,8 +217,8 @@ export const startGame = async ( sampVersion === "custom" ? idealSAMPDllPath : file - ? await getLocalPath(file.path, file.name) - : idealSAMPDllPath; + ? await getLocalPath(file.path, file.name) + : idealSAMPDllPath; invoke("inject", { name: nickname, @@ -232,6 +233,7 @@ export const startGame = async ( .then(() => { addToRecentlyJoined(server); setSelected(undefined); + useSettings.getState().addRecentNickname(nickname); }) .catch((e) => { if (e === "need_admin") { From 81b88aa8ba409e81c79018a7d767a1b9d0ee9aaa Mon Sep 17 00:00:00 2001 From: Lopez <272230284+LopezDev7@users.noreply.github.com> Date: Fri, 14 Aug 2026 18:32:01 -0500 Subject: [PATCH 2/3] Show recent nicknames in the join prompt nickname input --- src/containers/JoinServerPrompt/index.tsx | 104 +++++++++++++++++++--- 1 file changed, 94 insertions(+), 10 deletions(-) diff --git a/src/containers/JoinServerPrompt/index.tsx b/src/containers/JoinServerPrompt/index.tsx index 1fca04ed..49621627 100644 --- a/src/containers/JoinServerPrompt/index.tsx +++ b/src/containers/JoinServerPrompt/index.tsx @@ -1,8 +1,9 @@ import { fs } from "@tauri-apps/api"; import { t } from "i18next"; -import { useCallback, useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Image, + Pressable, StyleSheet, TextInput, TouchableOpacity, @@ -46,7 +47,10 @@ const JoinServerPrompt = () => { SAMPDLLVersions | undefined >(); const [perServerNickname, setPerServerNickname] = useState(""); - const { nickName, gtasaPath, sampVersion, setSampVersion } = useSettings(); + const [dropdownOpen, setDropdownOpen] = useState(false); + const hideTimer = useRef | null>(null); + const { nickName, gtasaPath, sampVersion, setSampVersion, recentNicknames } = + useSettings(); const settings = useMemo(() => { if (server) { @@ -192,6 +196,32 @@ const JoinServerPrompt = () => { [server, settings, setServerSettings] ); + const openDropdown = useCallback(() => { + if (hideTimer.current) clearTimeout(hideTimer.current); + setDropdownOpen(true); + }, []); + + const closeDropdown = useCallback(() => { + if (hideTimer.current) clearTimeout(hideTimer.current); + hideTimer.current = setTimeout(() => setDropdownOpen(false), 150); + }, []); + + const handleRecentNicknamePress = useCallback( + (name: string) => { + setPerServerNickname(name); + if (server) { + if (settings) { + setServerSettings(server, name, settings.sampVersion); + } else { + setServerSettings(server, name, undefined); + } + } + if (hideTimer.current) clearTimeout(hideTimer.current); + setDropdownOpen(false); + }, + [server, settings, setServerSettings] + ); + const handleConnect = useCallback(() => { if (server) { if (server.hasPassword && password.length) { @@ -326,14 +356,47 @@ const JoinServerPrompt = () => { {t("nickname")}: - + + + {dropdownOpen && recentNicknames.length > 0 && ( + + {recentNicknames.map((name) => ( + handleRecentNicknamePress(name)} + style={styles.dropdownItem} + > + + {name} + + + ))} + + )} + Date: Sun, 16 Aug 2026 16:49:33 -0500 Subject: [PATCH 3/3] fix: recent nicknames dropdown now shows above connect button and version select in join prompt --- src/containers/JoinServerPrompt/index.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/containers/JoinServerPrompt/index.tsx b/src/containers/JoinServerPrompt/index.tsx index 49621627..29e92688 100644 --- a/src/containers/JoinServerPrompt/index.tsx +++ b/src/containers/JoinServerPrompt/index.tsx @@ -455,6 +455,7 @@ const styles = StyleSheet.create({ shadowRadius: 10, alignItems: "center", paddingVertical: sc(11), + overflow: "visible", }, bannerContainer: { width: "100%", @@ -507,6 +508,7 @@ const styles = StyleSheet.create({ }, nicknameSection: { marginTop: sc(10), + zIndex: 1, }, nicknameInputWrapper: { position: "relative",