Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 96 additions & 10 deletions src/containers/JoinServerPrompt/index.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<ReturnType<typeof setTimeout> | null>(null);
const { nickName, gtasaPath, sampVersion, setSampVersion, recentNicknames } =
useSettings();

const settings = useMemo(() => {
if (server) {
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -326,14 +356,47 @@ const JoinServerPrompt = () => {
<Text semibold color={theme.textPrimary} size={2}>
{t("nickname")}:
</Text>
<TextInput
placeholderTextColor={theme.textPlaceholder}
placeholder={nickName}
value={perServerNickname}
onChangeText={handleNicknameChange}
// @ts-ignore
style={[styles.textInput, dynamicStyles.nicknameInput]}
/>
<View style={styles.nicknameInputWrapper}>
<TextInput
placeholderTextColor={theme.textPlaceholder}
placeholder={nickName}
value={perServerNickname}
onChangeText={handleNicknameChange}
onFocus={openDropdown}
onBlur={closeDropdown}
// @ts-ignore
style={[styles.textInput, dynamicStyles.nicknameInput]}
/>
{dropdownOpen && recentNicknames.length > 0 && (
<View
style={[
styles.dropdown,
{
backgroundColor: theme.itemBackgroundColor,
borderColor: theme.textSecondary,
},
]}
>
{recentNicknames.map((name) => (
<Pressable
key={name}
onPress={() => handleRecentNicknamePress(name)}
style={styles.dropdownItem}
>
<Text
numberOfLines={1}
style={[
styles.dropdownItemText,
{ color: theme.textPrimary },
]}
>
{name}
</Text>
</Pressable>
))}
</View>
)}
</View>
</View>
<TouchableOpacity
style={[styles.connectButton, dynamicStyles.connectButton]}
Expand Down Expand Up @@ -392,6 +455,7 @@ const styles = StyleSheet.create({
shadowRadius: 10,
alignItems: "center",
paddingVertical: sc(11),
overflow: "visible",
},
bannerContainer: {
width: "100%",
Expand Down Expand Up @@ -444,6 +508,28 @@ const styles = StyleSheet.create({
},
nicknameSection: {
marginTop: sc(10),
zIndex: 1,
},
nicknameInputWrapper: {
position: "relative",
},
dropdown: {
position: "absolute",
top: sc(43),
left: 0,
width: 300,
borderRadius: sc(5),
borderWidth: 1,
overflow: "hidden",
zIndex: 100,
},
dropdownItem: {
paddingHorizontal: sc(8),
paddingVertical: sc(7),
},
dropdownItemText: {
fontFamily: "Proxima Nova Regular",
fontSize: sc(15),
},
textInput: {
fontFamily: "Proxima Nova Regular",
Expand Down
96 changes: 86 additions & 10 deletions src/containers/NavBar.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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<ReturnType<typeof setTimeout> | 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(
() => [
{
Expand Down Expand Up @@ -88,13 +110,46 @@ const NavBar = memo(() => {
color={theme.textSecondary}
/>
</View>
<TextInput
value={nickName}
onChangeText={handleNicknameChange}
placeholder={`${t("nickname")}...`}
placeholderTextColor={theme.textSecondary}
style={dynamicStyles.nicknameInput}
/>
<View style={styles.nicknameInputWrapper}>
<TextInput
value={nickName}
onChangeText={handleNicknameChange}
onFocus={openDropdown}
onBlur={closeDropdown}
placeholder={`${t("nickname")}...`}
placeholderTextColor={theme.textSecondary}
style={dynamicStyles.nicknameInput}
/>
{dropdownOpen && recentNicknames.length > 0 && (
<View
style={[
styles.dropdown,
{
backgroundColor: theme.itemBackgroundColor,
borderColor: theme.textSecondary,
},
]}
>
{recentNicknames.map((name) => (
<Pressable
key={name}
onPress={() => handleRecentNicknamePress(name)}
style={styles.dropdownItem}
>
<Text
numberOfLines={1}
style={[
styles.dropdownItemText,
{ color: theme.textPrimary },
]}
>
{name}
</Text>
</Pressable>
))}
</View>
)}
</View>
</View>
</View>
</View>
Expand Down Expand Up @@ -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),
Expand Down
19 changes: 18 additions & 1 deletion src/states/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -28,11 +32,24 @@ const useSettings = create<SettingsPersistentState>()(
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 }),
Expand All @@ -44,7 +61,7 @@ const useSettings = create<SettingsPersistentState>()(
)
);

["setNickName"].forEach((event) =>
["setNickName", "setRecentNickname"].forEach((event) =>
listen(event, (ev) => {
if (ev.windowLabel !== appWindow.label) {
useSettings.persist.rehydrate();
Expand Down
6 changes: 4 additions & 2 deletions src/utils/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const startGame = async (
}:${nickname}:${password}`
: `connect:${await getIpAddress(server.ip)}:${server.port}:${nickname}`,
});
useSettings.getState().addRecentNickname(nickname);
return;
}

Expand Down Expand Up @@ -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,
Expand All @@ -232,6 +233,7 @@ export const startGame = async (
.then(() => {
addToRecentlyJoined(server);
setSelected(undefined);
useSettings.getState().addRecentNickname(nickname);
})
.catch((e) => {
if (e === "need_admin") {
Expand Down
Loading