Merge pull request #59 from djwesty/MantashaNoyela/22
Changed Styles and Screen Size for Mobile Device
This commit is contained in:
commit
e8898d8a60
@ -1,9 +1,11 @@
|
|||||||
import i18n from "@/i18n/i18n";
|
import i18n from "@/i18n/i18n";
|
||||||
|
import { COLORS } from "@/styles/styles";
|
||||||
import AppContext, { IAppContext } from "@/util/context";
|
import AppContext, { IAppContext } from "@/util/context";
|
||||||
import { MaterialIcons } from "@expo/vector-icons";
|
import { MaterialIcons } from "@expo/vector-icons";
|
||||||
import { Stack } from "expo-router";
|
import { Stack } from "expo-router";
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { I18nextProvider, useTranslation } from "react-i18next";
|
import { I18nextProvider, useTranslation } from "react-i18next";
|
||||||
|
import { useColorScheme } from "react-native";
|
||||||
|
|
||||||
const RootLayout: React.FC = () => {
|
const RootLayout: React.FC = () => {
|
||||||
const [showSettings, setShowSettings] = useState<boolean>(false);
|
const [showSettings, setShowSettings] = useState<boolean>(false);
|
||||||
@ -17,20 +19,36 @@ const RootLayout: React.FC = () => {
|
|||||||
[showSettings]
|
[showSettings]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const darkMode = useMemo(() => colorScheme === "dark", [colorScheme]);
|
||||||
|
const colors = useMemo(
|
||||||
|
() => (darkMode ? COLORS.DARK : COLORS.LIGHT),
|
||||||
|
[darkMode]
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<AppContext.Provider value={ctx}>
|
<AppContext.Provider value={ctx}>
|
||||||
<I18nextProvider i18n={i18n}>
|
<I18nextProvider i18n={i18n}>
|
||||||
<Stack
|
<Stack
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
|
contentStyle: {
|
||||||
|
backgroundColor: colors.BACKGROUND,
|
||||||
|
},
|
||||||
headerShown: true,
|
headerShown: true,
|
||||||
title: t("poker_chips_helper"),
|
title: t("poker_chips_helper"),
|
||||||
|
navigationBarColor: colors.PRIMARY,
|
||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<MaterialIcons
|
<MaterialIcons
|
||||||
name="settings"
|
name="settings"
|
||||||
onPress={() => setShowSettings(!showSettings)}
|
onPress={() => setShowSettings(!showSettings)}
|
||||||
size={30}
|
size={30}
|
||||||
|
color={colors.TEXT}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
headerStyle: {
|
||||||
|
backgroundColor: colors.PRIMARY,
|
||||||
|
},
|
||||||
|
headerTintColor: colors.TEXT,
|
||||||
|
statusBarBackgroundColor: "grey",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</I18nextProvider>
|
</I18nextProvider>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useContext, useMemo } from "react";
|
import React, { useState, useEffect, useContext, useMemo } from "react";
|
||||||
import { ScrollView, Alert } from "react-native";
|
import { ScrollView, Alert, useColorScheme, Appearance } from "react-native";
|
||||||
import Button from "@/containers/Button";
|
import Button from "@/containers/Button";
|
||||||
import PlayerSelector from "@/components/PlayerSelector";
|
import PlayerSelector from "@/components/PlayerSelector";
|
||||||
import BuyInSelector from "@/components/BuyInSelector";
|
import BuyInSelector from "@/components/BuyInSelector";
|
||||||
@ -7,16 +7,17 @@ import ChipsSelector from "@/components/ChipsSelector";
|
|||||||
import ChipDistributionSummary from "@/components/ChipDistributionSummary";
|
import ChipDistributionSummary from "@/components/ChipDistributionSummary";
|
||||||
import ChipDetection from "@/components/ChipDetection";
|
import ChipDetection from "@/components/ChipDetection";
|
||||||
import CurrencySelector from "@/components/CurrencySelector";
|
import CurrencySelector from "@/components/CurrencySelector";
|
||||||
import { saveState, loadState } from "@/components/CalculatorState";
|
import { saveState, loadState } from "@/util/CalculatorState";
|
||||||
import {
|
import {
|
||||||
savePersistentState,
|
savePersistentState,
|
||||||
loadPersistentState,
|
loadPersistentState,
|
||||||
} from "@/components/PersistentState";
|
} from "@/util/PersistentState";
|
||||||
import styles from "@/styles/styles";
|
import styles, { COLORS } from "@/styles/styles";
|
||||||
import Section from "@/containers/Section";
|
import Section from "@/containers/Section";
|
||||||
import AppContext from "@/util/context";
|
import AppContext from "@/util/context";
|
||||||
import { Picker } from "@react-native-picker/picker";
|
|
||||||
import i18n from "@/i18n/i18n";
|
import i18n from "@/i18n/i18n";
|
||||||
|
import { Picker, PickerItem } from "@/containers/Picker";
|
||||||
|
import { ItemValue } from "@react-native-picker/picker/typings/Picker";
|
||||||
|
|
||||||
const IndexScreen: React.FC = () => {
|
const IndexScreen: React.FC = () => {
|
||||||
const [playerCount, setPlayerCount] = useState(2);
|
const [playerCount, setPlayerCount] = useState(2);
|
||||||
@ -25,23 +26,25 @@ const IndexScreen: React.FC = () => {
|
|||||||
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
|
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
|
||||||
const [selectedCurrency, setSelectedCurrency] = useState<string>("$");
|
const [selectedCurrency, setSelectedCurrency] = useState<string>("$");
|
||||||
const [selectedLanguage, setSelectedLanguage] = useState<string>("en");
|
const [selectedLanguage, setSelectedLanguage] = useState<string>("en");
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const darkMode = useMemo(() => colorScheme === "dark", [colorScheme]);
|
||||||
|
const colors = useMemo(
|
||||||
|
() => (darkMode ? COLORS.DARK : COLORS.LIGHT),
|
||||||
|
[darkMode]
|
||||||
|
);
|
||||||
const context = useContext(AppContext);
|
const context = useContext(AppContext);
|
||||||
const isSettingsVisible = useMemo(() => context.showSettings, [context]);
|
const isSettingsVisible = useMemo(() => context.showSettings, [context]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadPersistentData = async () => {
|
const loadPersistentData = async () => {
|
||||||
try {
|
try {
|
||||||
console.log("Loading persistent game state...");
|
|
||||||
const savedState = await loadPersistentState();
|
const savedState = await loadPersistentState();
|
||||||
if (savedState) {
|
if (savedState) {
|
||||||
console.log("Persistent state restored:", savedState);
|
|
||||||
setPlayerCount(savedState.playerCount || 2);
|
setPlayerCount(savedState.playerCount || 2);
|
||||||
setBuyInAmount(savedState.buyInAmount || 20);
|
setBuyInAmount(savedState.buyInAmount || 20);
|
||||||
setNumberOfChips(savedState.numberOfChips || 5);
|
setNumberOfChips(savedState.numberOfChips || 5);
|
||||||
setTotalChipsCount(savedState.totalChipsCount || []);
|
setTotalChipsCount(savedState.totalChipsCount || []);
|
||||||
setSelectedCurrency(savedState.selectedCurrency || "$");
|
setSelectedCurrency(savedState.selectedCurrency || "$");
|
||||||
} else {
|
|
||||||
console.log("No persistent state found, using defaults.");
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading persistent state:", error);
|
console.error("Error loading persistent state:", error);
|
||||||
@ -62,19 +65,12 @@ const IndexScreen: React.FC = () => {
|
|||||||
totalChipsCount,
|
totalChipsCount,
|
||||||
selectedCurrency,
|
selectedCurrency,
|
||||||
};
|
};
|
||||||
try {
|
|
||||||
await saveState(slot, state);
|
await saveState(slot, state);
|
||||||
await savePersistentState(state);
|
await savePersistentState(state);
|
||||||
console.log(`Game state saved to ${slot}:`, state);
|
Alert.alert(i18n.t("success"), i18n.t("state_saved", { slot }));
|
||||||
Alert.alert(i18n.t("success"), i18n.t("state_saved", { slot })); // Fixed interpolation
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error saving state:", error);
|
|
||||||
Alert.alert(i18n.t("error"), i18n.t("failed_to_save_state"));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLoad = async (slot: "SLOT1" | "SLOT2") => {
|
const handleLoad = async (slot: "SLOT1" | "SLOT2") => {
|
||||||
try {
|
|
||||||
const loadedState = await loadState(slot);
|
const loadedState = await loadState(slot);
|
||||||
if (loadedState) {
|
if (loadedState) {
|
||||||
setPlayerCount(loadedState.playerCount);
|
setPlayerCount(loadedState.playerCount);
|
||||||
@ -83,20 +79,15 @@ const IndexScreen: React.FC = () => {
|
|||||||
setTotalChipsCount(loadedState.totalChipsCount);
|
setTotalChipsCount(loadedState.totalChipsCount);
|
||||||
setSelectedCurrency(loadedState.selectedCurrency || "$");
|
setSelectedCurrency(loadedState.selectedCurrency || "$");
|
||||||
await savePersistentState(loadedState);
|
await savePersistentState(loadedState);
|
||||||
console.log(`Game state loaded from ${slot}:`, loadedState);
|
Alert.alert(i18n.t("success"), i18n.t("state_loaded_from", { slot }));
|
||||||
Alert.alert(i18n.t("success"), i18n.t("state_loaded_from", { slot })); // Fixed interpolation
|
|
||||||
} else {
|
} else {
|
||||||
Alert.alert(i18n.t("info"), i18n.t("no_saved_state_found"));
|
Alert.alert(i18n.t("info"), i18n.t("no_saved_state_found"));
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error("Error loading state:", error);
|
|
||||||
Alert.alert(i18n.t("error"), i18n.t("failed_to_load_state"));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLanguageChange = (language: string) => {
|
const handleLanguageChange = (language: ItemValue, _: any) => {
|
||||||
setSelectedLanguage(language);
|
setSelectedLanguage(language.toString());
|
||||||
i18n.changeLanguage(language);
|
i18n.changeLanguage(language.toString());
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -105,6 +96,24 @@ const IndexScreen: React.FC = () => {
|
|||||||
contentContainerStyle={styles.scrollViewContent}
|
contentContainerStyle={styles.scrollViewContent}
|
||||||
>
|
>
|
||||||
{isSettingsVisible && (
|
{isSettingsVisible && (
|
||||||
|
<>
|
||||||
|
<Section
|
||||||
|
title={i18n.t("appearance")}
|
||||||
|
iconName={"brightness-4"}
|
||||||
|
orientation="row"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
title={
|
||||||
|
darkMode
|
||||||
|
? i18n.t("switch_to_light_mode")
|
||||||
|
: i18n.t("switch_to_dark_mode")
|
||||||
|
}
|
||||||
|
onPress={() =>
|
||||||
|
Appearance.setColorScheme(darkMode ? "light" : "dark")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Section>
|
||||||
|
|
||||||
<Section
|
<Section
|
||||||
title={i18n.t("select_language")}
|
title={i18n.t("select_language")}
|
||||||
iconName={"language"}
|
iconName={"language"}
|
||||||
@ -113,15 +122,12 @@ const IndexScreen: React.FC = () => {
|
|||||||
<Picker
|
<Picker
|
||||||
selectedValue={selectedLanguage}
|
selectedValue={selectedLanguage}
|
||||||
onValueChange={handleLanguageChange}
|
onValueChange={handleLanguageChange}
|
||||||
style={styles.picker}
|
|
||||||
>
|
>
|
||||||
<Picker.Item label={i18n.t("english")} value="en" />
|
<PickerItem label={i18n.t("english")} value="en" />
|
||||||
<Picker.Item label={i18n.t("spanish")} value="es" />
|
<PickerItem label={i18n.t("spanish")} value="es" />
|
||||||
</Picker>
|
</Picker>
|
||||||
</Section>
|
</Section>
|
||||||
)}
|
|
||||||
|
|
||||||
{isSettingsVisible && (
|
|
||||||
<Section
|
<Section
|
||||||
title={i18n.t("select_currency")}
|
title={i18n.t("select_currency")}
|
||||||
iconName={"attach-money"}
|
iconName={"attach-money"}
|
||||||
@ -132,6 +138,7 @@ const IndexScreen: React.FC = () => {
|
|||||||
setSelectedCurrency={setSelectedCurrency}
|
setSelectedCurrency={setSelectedCurrency}
|
||||||
/>
|
/>
|
||||||
</Section>
|
</Section>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Section
|
<Section
|
||||||
@ -205,19 +212,23 @@ const IndexScreen: React.FC = () => {
|
|||||||
title={i18n.t("save_slot_1")}
|
title={i18n.t("save_slot_1")}
|
||||||
onPress={() => handleSave("SLOT1")}
|
onPress={() => handleSave("SLOT1")}
|
||||||
disabled={buyInAmount === null}
|
disabled={buyInAmount === null}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
title={i18n.t("save_slot_2")}
|
title={i18n.t("save_slot_2")}
|
||||||
onPress={() => handleSave("SLOT2")}
|
onPress={() => handleSave("SLOT2")}
|
||||||
disabled={buyInAmount === null}
|
disabled={buyInAmount === null}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
title={i18n.t("load_slot_1")}
|
title={i18n.t("load_slot_1")}
|
||||||
onPress={() => handleLoad("SLOT1")}
|
onPress={() => handleLoad("SLOT1")}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
title={i18n.t("load_slot_2")}
|
title={i18n.t("load_slot_2")}
|
||||||
onPress={() => handleLoad("SLOT2")}
|
onPress={() => handleLoad("SLOT2")}
|
||||||
|
size="small"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
</Section>
|
</Section>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { View, Text, TextInput } from "react-native";
|
import { View, Text, TextInput, useColorScheme } from "react-native";
|
||||||
import styles, { COLORS } from "@/styles/styles";
|
import styles, { COLORS } from "@/styles/styles";
|
||||||
import Button from "@/containers/Button";
|
import Button from "@/containers/Button";
|
||||||
import i18n from "@/i18n/i18n";
|
import i18n from "@/i18n/i18n";
|
||||||
@ -25,6 +25,12 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [customAmount, setCustomAmount] = useState("");
|
const [customAmount, setCustomAmount] = useState("");
|
||||||
const [buyInAmount, setBuyInAmountState] = useState<number | null>(null);
|
const [buyInAmount, setBuyInAmountState] = useState<number | null>(null);
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const darkMode = useMemo(() => colorScheme === "dark", [colorScheme]);
|
||||||
|
const colors = useMemo(
|
||||||
|
() => (darkMode ? COLORS.DARK : COLORS.LIGHT),
|
||||||
|
[darkMode]
|
||||||
|
);
|
||||||
|
|
||||||
const handleCustomAmountChange = (value: string) => {
|
const handleCustomAmountChange = (value: string) => {
|
||||||
const numericValue = parseRoundClamp(value);
|
const numericValue = parseRoundClamp(value);
|
||||||
@ -51,7 +57,6 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({
|
|||||||
{defaultBuyInOptions.map((amount) => (
|
{defaultBuyInOptions.map((amount) => (
|
||||||
<Button
|
<Button
|
||||||
key={amount}
|
key={amount}
|
||||||
color={buyInAmount === amount ? COLORS.PRIMARY : COLORS.SECONDARY}
|
|
||||||
onPress={() => handleBuyInSelection(amount)}
|
onPress={() => handleBuyInSelection(amount)}
|
||||||
title={`${selectedCurrency} ${amount}`}
|
title={`${selectedCurrency} ${amount}`}
|
||||||
/>
|
/>
|
||||||
@ -59,7 +64,8 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.input}
|
style={[styles.input, { color: colors.TEXT }]}
|
||||||
|
placeholderTextColor={colors.TEXT}
|
||||||
value={customAmount}
|
value={customAmount}
|
||||||
maxLength={3}
|
maxLength={3}
|
||||||
onChangeText={handleCustomAmountChange}
|
onChangeText={handleCustomAmountChange}
|
||||||
@ -67,7 +73,7 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({
|
|||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Text style={styles.h2}>
|
<Text style={[styles.h2, { color: colors.TEXT }]}>
|
||||||
{`${i18n.t("selected_buy_in")} `}
|
{`${i18n.t("selected_buy_in")} `}
|
||||||
{buyInAmount !== null
|
{buyInAmount !== null
|
||||||
? `${selectedCurrency} ${buyInAmount}`
|
? `${selectedCurrency} ${buyInAmount}`
|
||||||
|
@ -94,7 +94,7 @@ const ChipDetection = ({
|
|||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (!response.ok || !result.choices || !result.choices[0].message) {
|
if (!response.ok || !result.choices || !result.choices[0].message) {
|
||||||
throw new Error(i18n.t("invalid_response")); // Translate invalid response error
|
throw new Error(i18n.t("invalid_response"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawContent = result.choices[0].message.content.trim();
|
const rawContent = result.choices[0].message.content.trim();
|
||||||
|
@ -32,7 +32,6 @@ const ChipInputModal = ({
|
|||||||
|
|
||||||
const [value, setValue] = useState<number | undefined>();
|
const [value, setValue] = useState<number | undefined>();
|
||||||
|
|
||||||
// Reset the color value when the specific color this modal is for changes
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setValue(totalChipsCount[colorIdx]);
|
setValue(totalChipsCount[colorIdx]);
|
||||||
}, [colorIdx, totalChipsCount]);
|
}, [colorIdx, totalChipsCount]);
|
||||||
@ -129,11 +128,10 @@ const ChipsSelector = ({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const colorsUsed = useMemo(
|
const colorsUsed = useMemo(
|
||||||
() => colors.slice(0, numberOfChips), // Only show as many colors as the `numberOfChips`
|
() => colors.slice(0, numberOfChips),
|
||||||
[numberOfChips]
|
[numberOfChips]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Callback for ChipInputModal to update the chips in the parent's state.
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
(color: ColorValue, count: number) => {
|
(color: ColorValue, count: number) => {
|
||||||
const newTotalChipsCount = totalChipsCount.slice();
|
const newTotalChipsCount = totalChipsCount.slice();
|
||||||
@ -144,7 +142,6 @@ const ChipsSelector = ({
|
|||||||
[totalChipsCount, setTotalChipsCount]
|
[totalChipsCount, setTotalChipsCount]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handling number of chips to make sure the array updates accordingly
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (numberOfChips !== totalChipsCount.length) {
|
if (numberOfChips !== totalChipsCount.length) {
|
||||||
let newTotalChipsCount = totalChipsCount.slice();
|
let newTotalChipsCount = totalChipsCount.slice();
|
||||||
@ -169,7 +166,7 @@ const ChipsSelector = ({
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
setNumberOfChips(Math.max(1, numberOfChips - 1));
|
setNumberOfChips(Math.max(1, numberOfChips - 1));
|
||||||
}}
|
}}
|
||||||
disabled={numberOfChips == 1}
|
disabled={numberOfChips === 1}
|
||||||
/>
|
/>
|
||||||
<View style={[styles.container, { flexDirection: "row" }]}>
|
<View style={[styles.container, { flexDirection: "row" }]}>
|
||||||
{colorsUsed.map((color) => {
|
{colorsUsed.map((color) => {
|
||||||
@ -189,7 +186,7 @@ const ChipsSelector = ({
|
|||||||
onPress={() => {
|
onPress={() => {
|
||||||
setNumberOfChips(Math.min(5, numberOfChips + 1));
|
setNumberOfChips(Math.min(5, numberOfChips + 1));
|
||||||
}}
|
}}
|
||||||
disabled={numberOfChips == 5}
|
disabled={numberOfChips === 5}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<ChipInputModal
|
<ChipInputModal
|
||||||
@ -202,35 +199,4 @@ const ChipsSelector = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const styles1 = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
marginBottom: 20,
|
|
||||||
gap: 10,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontWeight: "bold",
|
|
||||||
margin: "auto",
|
|
||||||
fontSize: 18,
|
|
||||||
},
|
|
||||||
chipContainer: {
|
|
||||||
padding: 20,
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "space-evenly",
|
|
||||||
backgroundColor: "#bbb",
|
|
||||||
},
|
|
||||||
chip: {
|
|
||||||
textAlign: "center",
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: "bold",
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "row",
|
|
||||||
justifyContent: "space-evenly",
|
|
||||||
},
|
|
||||||
button: {},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default ChipsSelector;
|
export default ChipsSelector;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Picker } from "@react-native-picker/picker";
|
|
||||||
import styles from "@/styles/styles";
|
|
||||||
import i18n from "@/i18n/i18n";
|
import i18n from "@/i18n/i18n";
|
||||||
|
import { Picker, PickerItem } from "@/containers/Picker";
|
||||||
|
|
||||||
interface CurrencySelectorProps {
|
interface CurrencySelectorProps {
|
||||||
selectedCurrency: string;
|
selectedCurrency: string;
|
||||||
@ -16,14 +15,13 @@ const CurrencySelector: React.FC<CurrencySelectorProps> = ({
|
|||||||
<>
|
<>
|
||||||
<Picker
|
<Picker
|
||||||
selectedValue={selectedCurrency}
|
selectedValue={selectedCurrency}
|
||||||
onValueChange={(itemValue) => setSelectedCurrency(itemValue)}
|
onValueChange={(itemValue) => setSelectedCurrency(itemValue.toString())}
|
||||||
style={styles.picker}
|
|
||||||
testID="currency-picker" // ✅ Add testID here
|
testID="currency-picker" // ✅ Add testID here
|
||||||
>
|
>
|
||||||
<Picker.Item label={i18n.t("usd")} value="$" />
|
<PickerItem label={i18n.t("usd")} value="$" />
|
||||||
<Picker.Item label={i18n.t("euro")} value="€" />
|
<PickerItem label={i18n.t("euro")} value="€" />
|
||||||
<Picker.Item label={i18n.t("pound")} value="£" />
|
<PickerItem label={i18n.t("pound")} value="£" />
|
||||||
<Picker.Item label={i18n.t("inr")} value="₹" />
|
<PickerItem label={i18n.t("inr")} value="₹" />
|
||||||
</Picker>
|
</Picker>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import { View, Text } from "react-native";
|
import { View, Text, useColorScheme } from "react-native";
|
||||||
import Button from "@/containers/Button";
|
import Button from "@/containers/Button";
|
||||||
import styles from "@/styles/styles";
|
import styles, { COLORS } from "@/styles/styles";
|
||||||
|
|
||||||
interface PlayerSelectorProps {
|
interface PlayerSelectorProps {
|
||||||
playerCount: number;
|
playerCount: number;
|
||||||
setPlayerCount: React.Dispatch<React.SetStateAction<number>>;
|
setPlayerCount: React.Dispatch<React.SetStateAction<number>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MIN = 2;
|
const MIN = 2;
|
||||||
const MAX = 8;
|
const MAX = 8;
|
||||||
|
|
||||||
const PlayerSelector: React.FC<PlayerSelectorProps> = ({
|
const PlayerSelector: React.FC<PlayerSelectorProps> = ({
|
||||||
playerCount,
|
playerCount,
|
||||||
setPlayerCount,
|
setPlayerCount,
|
||||||
@ -20,21 +22,27 @@ const PlayerSelector: React.FC<PlayerSelectorProps> = ({
|
|||||||
const decreasePlayers = () => {
|
const decreasePlayers = () => {
|
||||||
if (playerCount > MIN) setPlayerCount(playerCount - 1);
|
if (playerCount > MIN) setPlayerCount(playerCount - 1);
|
||||||
};
|
};
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const darkMode = useMemo(() => colorScheme === "dark", [colorScheme]);
|
||||||
|
const colors = useMemo(
|
||||||
|
() => (darkMode ? COLORS.DARK : COLORS.LIGHT),
|
||||||
|
[darkMode]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
|
||||||
<Button
|
<Button
|
||||||
title="-"
|
title="-"
|
||||||
onPress={decreasePlayers}
|
onPress={decreasePlayers}
|
||||||
disabled={playerCount <= MIN}
|
disabled={playerCount <= MIN}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.h1}>{playerCount}</Text>
|
<Text style={[styles.h1, { color: colors.TEXT }]}>{playerCount}</Text>
|
||||||
<Button
|
<Button
|
||||||
title="+"
|
title="+"
|
||||||
onPress={increasePlayers}
|
onPress={increasePlayers}
|
||||||
disabled={playerCount >= MAX}
|
disabled={playerCount >= MAX}
|
||||||
/>
|
/>
|
||||||
</>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -82,6 +82,7 @@ describe("tests for ChipsSelector", () => {
|
|||||||
TOTAL_CHIPS_COUNT[4],
|
TOTAL_CHIPS_COUNT[4],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// skip: There is a jest/DOM issue with the button interaction, despite working correctly in-app. Documented to resolve.
|
// skip: There is a jest/DOM issue with the button interaction, despite working correctly in-app. Documented to resolve.
|
||||||
it.skip("test dec/inc buttons", async () => {
|
it.skip("test dec/inc buttons", async () => {
|
||||||
rend();
|
rend();
|
||||||
|
@ -1,9 +1,82 @@
|
|||||||
import { ButtonProps, Button } from "react-native";
|
|
||||||
import { COLORS } from "@/styles/styles";
|
import { COLORS } from "@/styles/styles";
|
||||||
|
import React, { useMemo } from "react";
|
||||||
|
import {
|
||||||
|
Text,
|
||||||
|
TouchableOpacity,
|
||||||
|
StyleSheet,
|
||||||
|
useColorScheme,
|
||||||
|
} from "react-native";
|
||||||
|
|
||||||
// More styling can be done, or swap this out with more flexible component like a TouchableOpacity if needed
|
interface ButtonProps {
|
||||||
const AppButton = (props: ButtonProps) => (
|
title: string;
|
||||||
<Button color={COLORS.PRIMARY} {...props} />
|
onPress: () => void;
|
||||||
);
|
disabled?: boolean;
|
||||||
|
size?: "normal" | "small";
|
||||||
|
}
|
||||||
|
|
||||||
export default AppButton;
|
const Button: React.FC<ButtonProps> = ({
|
||||||
|
title,
|
||||||
|
onPress,
|
||||||
|
disabled,
|
||||||
|
size = "normal",
|
||||||
|
}) => {
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const darkMode = useMemo(() => colorScheme === "dark", [colorScheme]);
|
||||||
|
const colors = useMemo(
|
||||||
|
() => (darkMode ? COLORS.DARK : COLORS.LIGHT),
|
||||||
|
[darkMode]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={onPress}
|
||||||
|
disabled={disabled}
|
||||||
|
accessibilityRole="button"
|
||||||
|
style={[
|
||||||
|
size == "normal" ? styles.button : styles.buttonSmall,
|
||||||
|
{ backgroundColor: colors.PRIMARY },
|
||||||
|
disabled && styles.disabled,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
size == "normal" ? styles.buttonText : styles.buttonTextSmall,
|
||||||
|
{ color: colors.TEXT },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
button: {
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginHorizontal: 5,
|
||||||
|
marginVertical: 5,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
buttonSmall: {
|
||||||
|
paddingVertical: 5,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginHorizontal: 2,
|
||||||
|
marginVertical: 2,
|
||||||
|
},
|
||||||
|
buttonTextSmall: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: "bold",
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
disabled: {
|
||||||
|
opacity: 0.5,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Button;
|
||||||
|
49
containers/Picker.tsx
Normal file
49
containers/Picker.tsx
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import styles, { COLORS } from "@/styles/styles";
|
||||||
|
import { PickerItemProps, PickerProps } from "@react-native-picker/picker";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { useColorScheme, View } from "react-native";
|
||||||
|
import { Picker as RNPicker } from "@react-native-picker/picker";
|
||||||
|
|
||||||
|
export const PickerItem: React.FC<PickerItemProps> = (props) => {
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const darkMode = useMemo(() => colorScheme === "dark", [colorScheme]);
|
||||||
|
const colors = useMemo(
|
||||||
|
() => (darkMode ? COLORS.DARK : COLORS.LIGHT),
|
||||||
|
[darkMode]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RNPicker.Item
|
||||||
|
style={[
|
||||||
|
styles.pickerItem,
|
||||||
|
{ color: colors.TEXT, backgroundColor: colors.PRIMARY },
|
||||||
|
]}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Picker: React.FC<PickerProps> = (props) => {
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const darkMode = useMemo(() => colorScheme === "dark", [colorScheme]);
|
||||||
|
const colors = useMemo(
|
||||||
|
() => (darkMode ? COLORS.DARK : COLORS.LIGHT),
|
||||||
|
[darkMode]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.pickerWrapper}>
|
||||||
|
<RNPicker
|
||||||
|
style={[
|
||||||
|
styles.picker,
|
||||||
|
{
|
||||||
|
color: colors.TEXT,
|
||||||
|
backgroundColor: colors.PRIMARY,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
dropdownIconColor={colors.TEXT}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
@ -1,7 +1,7 @@
|
|||||||
import { View, Text, StyleSheet } from "react-native";
|
import { View, Text, StyleSheet, useColorScheme } from "react-native";
|
||||||
import React from "react";
|
import React, { useMemo } from "react";
|
||||||
import { MaterialIcons } from "@expo/vector-icons";
|
import { MaterialIcons } from "@expo/vector-icons";
|
||||||
import globalStyles from "@/styles/styles";
|
import globalStyles, { COLORS } from "@/styles/styles";
|
||||||
|
|
||||||
const titleCase = (input: string) =>
|
const titleCase = (input: string) =>
|
||||||
input
|
input
|
||||||
@ -23,6 +23,12 @@ const Section = ({
|
|||||||
orientation?: "row" | "column";
|
orientation?: "row" | "column";
|
||||||
contentStyle?: object;
|
contentStyle?: object;
|
||||||
}) => {
|
}) => {
|
||||||
|
const colorScheme = useColorScheme();
|
||||||
|
const darkMode = useMemo(() => colorScheme === "dark", [colorScheme]);
|
||||||
|
const colors = useMemo(
|
||||||
|
() => (darkMode ? COLORS.DARK : COLORS.LIGHT),
|
||||||
|
[darkMode]
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
@ -30,9 +36,11 @@ const Section = ({
|
|||||||
style={styles.icon}
|
style={styles.icon}
|
||||||
name={iconName}
|
name={iconName}
|
||||||
size={30}
|
size={30}
|
||||||
color={"black"}
|
color={colors.TEXT}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.title}>{titleCase(title)}</Text>
|
<Text style={[styles.title, { color: colors.TEXT }]}>
|
||||||
|
{titleCase(title)}
|
||||||
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
|
@ -46,6 +46,9 @@
|
|||||||
"load_slot_1": "Load\nSlot 1",
|
"load_slot_1": "Load\nSlot 1",
|
||||||
"load_slot_2": "Load\nSlot 2",
|
"load_slot_2": "Load\nSlot 2",
|
||||||
"please_select_valid_buyin": "Please select a valid buy-in amount",
|
"please_select_valid_buyin": "Please select a valid buy-in amount",
|
||||||
"chip_value_warn": "Be advised that the value of the distributed chips does not equal the buy-in for these inputs.\n\nHowever, results shown are fair to all players"
|
"chip_value_warn": "Be advised that the value of the distributed chips does not equal the buy-in for these inputs.\n\nHowever, results shown are fair to all players",
|
||||||
|
"appearance": "Appearance",
|
||||||
|
"switch_to_dark_mode": "Switch to Dark Mode",
|
||||||
|
"switch_to_light_mode": "Switch to Light Mode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,9 @@
|
|||||||
"load_slot_1": "Cargar\nSlot 1",
|
"load_slot_1": "Cargar\nSlot 1",
|
||||||
"load_slot_2": "Cargar\nSlot 2",
|
"load_slot_2": "Cargar\nSlot 2",
|
||||||
"please_select_valid_buyin": "Por favor seleccione una cantidad de buy-in válida",
|
"please_select_valid_buyin": "Por favor seleccione una cantidad de buy-in válida",
|
||||||
"chip_value_warn": "Tenga en cuenta que el valor de las fichas distribuidas no es igual al buy-in para estas entradas.\n\nSin embargo, los resultados que se muestran son justos para todos los jugadores."
|
"chip_value_warn": "Tenga en cuenta que el valor de las fichas distribuidas no es igual al buy-in para estas entradas.\n\nSin embargo, los resultados que se muestran son justos para todos los jugadores.",
|
||||||
|
"appearance": "Apariencia",
|
||||||
|
"switch_to_dark_mode": "Cambiar a Modo Oscuro",
|
||||||
|
"switch_to_light_mode": "Cambiar a Modo Claro"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
2
package-lock.json
generated
2
package-lock.json
generated
@ -13,7 +13,7 @@
|
|||||||
"@react-native-picker/picker": "^2.11.0",
|
"@react-native-picker/picker": "^2.11.0",
|
||||||
"@react-navigation/bottom-tabs": "7.2.0",
|
"@react-navigation/bottom-tabs": "7.2.0",
|
||||||
"@react-navigation/native": "7.0.14",
|
"@react-navigation/native": "7.0.14",
|
||||||
"expo": "52.0.37",
|
"expo": "^52.0.37",
|
||||||
"expo-blur": "14.0.3",
|
"expo-blur": "14.0.3",
|
||||||
"expo-constants": "17.0.7",
|
"expo-constants": "17.0.7",
|
||||||
"expo-file-system": "18.0.11",
|
"expo-file-system": "18.0.11",
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
"@react-native-picker/picker": "^2.11.0",
|
"@react-native-picker/picker": "^2.11.0",
|
||||||
"@react-navigation/bottom-tabs": "7.2.0",
|
"@react-navigation/bottom-tabs": "7.2.0",
|
||||||
"@react-navigation/native": "7.0.14",
|
"@react-navigation/native": "7.0.14",
|
||||||
"expo": "52.0.37",
|
"expo": "^52.0.37",
|
||||||
"expo-blur": "14.0.3",
|
"expo-blur": "14.0.3",
|
||||||
"expo-constants": "17.0.7",
|
"expo-constants": "17.0.7",
|
||||||
"expo-file-system": "18.0.11",
|
"expo-file-system": "18.0.11",
|
||||||
@ -31,6 +31,7 @@
|
|||||||
"expo-haptics": "14.0.1",
|
"expo-haptics": "14.0.1",
|
||||||
"expo-image-picker": "16.0.6",
|
"expo-image-picker": "16.0.6",
|
||||||
"expo-linking": "7.0.5",
|
"expo-linking": "7.0.5",
|
||||||
|
"expo-localization": "~16.0.1",
|
||||||
"expo-router": "4.0.17",
|
"expo-router": "4.0.17",
|
||||||
"expo-splash-screen": "0.29.22",
|
"expo-splash-screen": "0.29.22",
|
||||||
"expo-status-bar": "2.0.1",
|
"expo-status-bar": "2.0.1",
|
||||||
@ -47,8 +48,7 @@
|
|||||||
"react-native-safe-area-context": "4.12.0",
|
"react-native-safe-area-context": "4.12.0",
|
||||||
"react-native-screens": "4.4.0",
|
"react-native-screens": "4.4.0",
|
||||||
"react-native-web": "0.19.13",
|
"react-native-web": "0.19.13",
|
||||||
"react-native-webview": "13.12.5",
|
"react-native-webview": "13.12.5"
|
||||||
"expo-localization": "~16.0.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.26.9",
|
"@babel/core": "7.26.9",
|
||||||
|
@ -1,16 +1,23 @@
|
|||||||
import { StyleSheet } from "react-native";
|
import { StyleSheet } from "react-native";
|
||||||
|
|
||||||
export const COLORS = {
|
export const COLORS = {
|
||||||
PRIMARY: "#007bff",
|
|
||||||
SECONDARY: "#6c757d",
|
|
||||||
SUCCESS: "#28a745",
|
|
||||||
DANGER: "#dc3545",
|
|
||||||
WARNING: "#c79c28",
|
WARNING: "#c79c28",
|
||||||
|
LIGHT: {
|
||||||
|
TEXT: "black",
|
||||||
|
PRIMARY: "lightgrey",
|
||||||
|
SECONDARY: "azure",
|
||||||
|
BACKGROUND: "ghostwhite",
|
||||||
|
DISABLED: "gray",
|
||||||
|
},
|
||||||
|
DARK: {
|
||||||
|
TEXT: "white",
|
||||||
|
PRIMARY: "black",
|
||||||
|
SECONDARY: "teal",
|
||||||
|
BACKGROUND: "dimgrey",
|
||||||
|
DISABLED: "gray",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const lightStyles = StyleSheet.create({});
|
|
||||||
const darkStyles = StyleSheet.create({});
|
|
||||||
|
|
||||||
const GlobalStyles = StyleSheet.create({
|
const GlobalStyles = StyleSheet.create({
|
||||||
scrollView: {},
|
scrollView: {},
|
||||||
scrollViewContent: {
|
scrollViewContent: {
|
||||||
@ -25,7 +32,7 @@ const GlobalStyles = StyleSheet.create({
|
|||||||
gap: 10,
|
gap: 10,
|
||||||
},
|
},
|
||||||
|
|
||||||
h1: { fontSize: 20, fontWeight: "bold" },
|
h1: { fontSize: 19, fontWeight: "bold" },
|
||||||
h2: { fontSize: 18, fontWeight: "normal" },
|
h2: { fontSize: 18, fontWeight: "normal" },
|
||||||
p: {
|
p: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@ -51,9 +58,15 @@ const GlobalStyles = StyleSheet.create({
|
|||||||
textShadowRadius: 10,
|
textShadowRadius: 10,
|
||||||
},
|
},
|
||||||
picker: {
|
picker: {
|
||||||
height: 50,
|
borderRadius: 10,
|
||||||
|
height: 55,
|
||||||
width: 150,
|
width: 150,
|
||||||
},
|
},
|
||||||
|
pickerItem: {},
|
||||||
|
pickerWrapper: {
|
||||||
|
borderRadius: 10,
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default GlobalStyles;
|
export default GlobalStyles;
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
import { saveState, loadState, PokerState } from "../CalculatorState";
|
import { saveState, loadState, PokerState } from "@/util/CalculatorState";
|
||||||
|
|
||||||
// Mock AsyncStorage
|
// Mock AsyncStorage
|
||||||
jest.mock("@react-native-async-storage/async-storage", () =>
|
jest.mock("@react-native-async-storage/async-storage", () =>
|
||||||
require("@react-native-async-storage/async-storage/jest/async-storage-mock")
|
require("@react-native-async-storage/async-storage/jest/async-storage-mock")
|
||||||
);
|
);
|
||||||
|
|
||||||
describe("CalculatorState.tsx", () => {
|
describe("CalculatorState.ts", () => {
|
||||||
const mockState: PokerState = {
|
const mockState: PokerState = {
|
||||||
playerCount: 4,
|
playerCount: 4,
|
||||||
buyInAmount: 50,
|
buyInAmount: 50,
|
@ -3,14 +3,14 @@ import {
|
|||||||
savePersistentState,
|
savePersistentState,
|
||||||
loadPersistentState,
|
loadPersistentState,
|
||||||
PokerState,
|
PokerState,
|
||||||
} from "../PersistentState";
|
} from "@/util/PersistentState";
|
||||||
|
|
||||||
jest.mock("@react-native-async-storage/async-storage", () => ({
|
jest.mock("@react-native-async-storage/async-storage", () => ({
|
||||||
setItem: jest.fn(),
|
setItem: jest.fn(),
|
||||||
getItem: jest.fn(),
|
getItem: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe("PersistentState.tsx", () => {
|
describe("PersistentState.ts", () => {
|
||||||
const mockState: PokerState = {
|
const mockState: PokerState = {
|
||||||
playerCount: 4,
|
playerCount: 4,
|
||||||
buyInAmount: 50,
|
buyInAmount: 50,
|
Loading…
Reference in New Issue
Block a user