CoompanionApp/mobile/src/screens/LobbyScreen.tsx

236 lines
6.8 KiB
TypeScript
Raw Normal View History

2026-02-03 13:48:56 +01:00
import React, { useEffect, useMemo, useState } from "react";
import {
FlatList,
Platform,
StyleSheet,
Text,
TextInput,
TouchableOpacity,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { useNavigation } from "@react-navigation/native";
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
import type { RootStackParamList } from "../navigation/types";
import { useSession } from "../state/session-context";
import { useI18n } from "../i18n";
import { useTheme } from "../theme";
import type { AppTheme } from "../theme";
import ExitGameButton from "../components/ExitGameButton";
export default function LobbyScreen() {
const navigation = useNavigation<NativeStackNavigationProp<RootStackParamList>>();
const manager = useSession();
const { t } = useI18n();
const theme = useTheme();
const styles = useMemo(() => createStyles(theme), [theme]);
const placeholderColor = theme.colors.placeholder;
const [dummyName, setDummyName] = useState("");
const [dummyBalance, setDummyBalance] = useState("1500");
const insets = useSafeAreaInsets();
const topInset = insets.top || (Platform.OS === "ios" ? 44 : 0);
const containerStyle = useMemo(
() => [
styles.container,
{
paddingTop: topInset + 20,
paddingBottom: insets.bottom + 20,
paddingLeft: insets.left + 20,
paddingRight: insets.right + 20,
},
],
[
styles.container,
topInset,
insets.bottom,
insets.left,
insets.right,
],
);
useEffect(() => {
if (!manager.session || !manager.me) return;
if (manager.session.status === "active") {
navigation.replace(manager.isBanker ? "BankerTabs" : "PlayerTabs");
}
}, [manager.session, manager.me, manager.isBanker, navigation]);
if (!manager.session || !manager.me) {
return (
<View style={containerStyle}>
<Text style={styles.title}>{t("common.loadingLobby")}</Text>
{manager.error ? <Text style={styles.helper}>{manager.error}</Text> : null}
<ExitGameButton mode="full" />
</View>
);
}
const canStart = manager.isBanker && manager.session.status === "lobby";
return (
<View style={containerStyle}>
<Text style={styles.title}>{t("lobby.title")}</Text>
<Text style={styles.subtitle}>{t("lobby.code", { code: manager.session.code })}</Text>
<FlatList
data={manager.session.players}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.list}
renderItem={({ item }) => (
<View style={styles.listItem}>
<View>
<Text style={styles.playerName}>{item.name}</Text>
<Text style={styles.playerMeta}>
{item.role === "banker" ? t("common.banker") : t("common.player")}{" "}
{item.isDummy ? `- ${t("common.dummy")}` : ""}
</Text>
</View>
<Text style={styles.playerMeta}>
{item.connected ? t("common.online") : t("common.offline")}
</Text>
</View>
)}
/>
{manager.isBanker && manager.session.status === "lobby" && (
<View style={styles.card}>
<Text style={styles.cardTitle}>{t("lobby.addDummyTitle")}</Text>
<Text style={styles.helper}>{t("lobby.addDummySubtitle")}</Text>
<TextInput
style={styles.input}
placeholder={t("lobby.enterDummyName")}
placeholderTextColor={placeholderColor}
value={dummyName}
onChangeText={setDummyName}
/>
<TextInput
style={styles.input}
placeholder={t("banker.tools.startingBalance")}
placeholderTextColor={placeholderColor}
value={dummyBalance}
onChangeText={setDummyBalance}
keyboardType="number-pad"
/>
<TouchableOpacity
style={styles.buttonSecondary}
onPress={() => {
manager.sendMessage({
type: "banker_create_dummy",
sessionId: manager.sessionId,
bankerId: manager.me?.id,
name: dummyName,
balance: Number(dummyBalance) || undefined,
});
setDummyName("");
setDummyBalance("1500");
}}
>
<Text style={styles.buttonSecondaryText}>{t("lobby.addDummyButton")}</Text>
</TouchableOpacity>
</View>
)}
{canStart && (
<TouchableOpacity
style={styles.button}
onPress={() =>
manager.sendMessage({
type: "banker_start",
sessionId: manager.sessionId,
bankerId: manager.me?.id,
})
}
>
<Text style={styles.buttonText}>{t("lobby.startGame")}</Text>
</TouchableOpacity>
)}
<ExitGameButton mode="full" />
</View>
);
}
const createStyles = (theme: AppTheme) =>
StyleSheet.create({
container: {
flex: 1,
paddingHorizontal: 0,
paddingBottom: 0,
gap: 12,
backgroundColor: theme.colors.background,
},
title: {
fontSize: 24,
fontWeight: "700",
color: theme.colors.text,
},
subtitle: {
color: theme.colors.textMuted,
},
list: {
gap: 10,
paddingBottom: 20,
},
listItem: {
backgroundColor: theme.colors.surface,
borderRadius: 12,
padding: 12,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
playerName: {
fontWeight: "600",
color: theme.colors.text,
},
playerMeta: {
fontSize: 12,
color: theme.colors.textMuted,
},
card: {
backgroundColor: theme.colors.surface,
borderRadius: 16,
padding: 16,
gap: 10,
borderWidth: 1,
borderColor: theme.colors.borderMuted,
},
cardTitle: {
fontWeight: "600",
color: theme.colors.text,
},
helper: {
color: theme.colors.textMuted,
fontSize: 12,
},
input: {
borderWidth: 1,
borderColor: theme.colors.border,
backgroundColor: theme.colors.inputBackground,
color: theme.colors.inputText,
borderRadius: 12,
paddingHorizontal: 12,
paddingVertical: 10,
},
button: {
backgroundColor: theme.colors.primary,
paddingVertical: 14,
borderRadius: 999,
alignItems: "center",
},
buttonText: {
color: theme.colors.primaryText,
fontWeight: "600",
},
buttonSecondary: {
backgroundColor: theme.colors.secondary,
paddingVertical: 12,
borderRadius: 999,
alignItems: "center",
},
buttonSecondaryText: {
color: theme.colors.secondaryText,
fontWeight: "600",
},
});