Merge pull request #48 from djwesty/djwesty/39

Consistent Application Styling #39
This commit is contained in:
David Westgate 2025-03-02 16:53:49 -08:00 committed by GitHub
commit 93cc07c28c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 429 additions and 372 deletions

View File

@ -1,7 +1,35 @@
import AppContext, { IAppContext } from "@/util/context";
import { MaterialIcons } from "@expo/vector-icons";
import { Stack } from "expo-router"; import { Stack } from "expo-router";
import React from "react"; import React, { useMemo, useState } from "react";
const RootLayout: React.FC = () => {
const [showSettings, setShowSettings] = useState<boolean>(false);
const ctx = useMemo<IAppContext>(
() => ({
showSettings,
}),
[showSettings]
);
return (
<AppContext.Provider value={ctx}>
<Stack
screenOptions={{
headerShown: true,
title: "Poker Chips Helper",
headerRight: () => (
<MaterialIcons
name="settings"
onPress={() => setShowSettings(!showSettings)}
size={30}
/>
),
}}
/>
</AppContext.Provider>
);
};
const RootLayout: React.FC = () => (
<Stack screenOptions={{ headerShown: true, title: "Poker Chips Helper" }} />
);
export default RootLayout; export default RootLayout;

View File

@ -1,14 +1,6 @@
import React, { useState, useEffect } from "react"; import React, { useState, useEffect, useContext, useMemo } from "react";
import { import { ScrollView, Alert } from "react-native";
ScrollView, import Button from "@/containers/Button";
Text,
Alert,
Button,
View,
StyleSheet,
TouchableOpacity,
} from "react-native";
import { FontAwesome } from "@expo/vector-icons";
import PlayerSelector from "@/components/PlayerSelector"; import PlayerSelector from "@/components/PlayerSelector";
import BuyInSelector from "@/components/BuyInSelector"; import BuyInSelector from "@/components/BuyInSelector";
import ChipsSelector from "@/components/ChipsSelector"; import ChipsSelector from "@/components/ChipsSelector";
@ -20,14 +12,9 @@ import {
savePersistentState, savePersistentState,
loadPersistentState, loadPersistentState,
} from "@/components/PersistentState"; } from "@/components/PersistentState";
import styles from "@/styles/styles";
export enum COLORS { import Section from "@/containers/Section";
"white", import AppContext from "@/util/context";
"red",
"green",
"blue",
"black",
}
const IndexScreen: React.FC = () => { const IndexScreen: React.FC = () => {
const [playerCount, setPlayerCount] = useState(2); const [playerCount, setPlayerCount] = useState(2);
@ -35,7 +22,8 @@ const IndexScreen: React.FC = () => {
const [numberOfChips, setNumberOfChips] = useState<number>(5); const [numberOfChips, setNumberOfChips] = useState<number>(5);
const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]); const [totalChipsCount, setTotalChipsCount] = useState<number[]>([]);
const [selectedCurrency, setSelectedCurrency] = useState<string>("$"); const [selectedCurrency, setSelectedCurrency] = useState<string>("$");
const [isSettingsVisible, setIsSettingsVisible] = useState(false); const context = useContext(AppContext);
const isSettingsVisible = useMemo(() => context.showSettings, [context]);
useEffect(() => { useEffect(() => {
const loadPersistentData = async () => { const loadPersistentData = async () => {
@ -104,96 +92,85 @@ const IndexScreen: React.FC = () => {
}; };
return ( return (
<ScrollView contentContainerStyle={{ padding: 20, flexGrow: 1 }}> <ScrollView
<View style={styles.header}> style={styles.scrollView}
<TouchableOpacity contentContainerStyle={styles.scrollViewContent}
onPress={() => setIsSettingsVisible(!isSettingsVisible)} >
>
<Text>
<FontAwesome name="cog" size={30} color="black" />
</Text>
</TouchableOpacity>
</View>
{isSettingsVisible && ( {isSettingsVisible && (
<View style={styles.settingsContainer}> <Section title={"Select Currency"} iconName={"attach-money"}>
<CurrencySelector <CurrencySelector
selectedCurrency={selectedCurrency} selectedCurrency={selectedCurrency}
setSelectedCurrency={setSelectedCurrency} setSelectedCurrency={setSelectedCurrency}
/> />
</View> </Section>
)} )}
<PlayerSelector <Section
playerCount={playerCount} title={"Select the number of players"}
setPlayerCount={setPlayerCount} iconName={"people"}
/> orientation="row"
>
<BuyInSelector <PlayerSelector
selectedCurrency={selectedCurrency} playerCount={playerCount}
setBuyInAmount={setBuyInAmount} setPlayerCount={setPlayerCount}
/>
<ChipDetection
updateChipCount={(chipData) => {
const chipCountArray = Object.values(chipData);
setTotalChipsCount(chipCountArray);
}}
/>
<ChipsSelector
totalChipsCount={totalChipsCount}
setTotalChipsCount={setTotalChipsCount}
numberOfChips={numberOfChips}
setNumberOfChips={setNumberOfChips}
/>
<ChipDistributionSummary
playerCount={playerCount}
buyInAmount={buyInAmount}
totalChipsCount={totalChipsCount}
selectedCurrency={selectedCurrency}
/>
<View style={styles.buttonContainer}>
<Button
title="Save to Slot 1"
onPress={() => handleSave("SLOT1")}
disabled={buyInAmount === null}
/> />
<Button </Section>
title="Save to Slot 2"
onPress={() => handleSave("SLOT2")} <Section title={"Select buy-in amount"} iconName={"monetization-on"}>
disabled={buyInAmount === null} <BuyInSelector
selectedCurrency={selectedCurrency}
setBuyInAmount={setBuyInAmount}
/> />
<Button title="Load from Slot 1" onPress={() => handleLoad("SLOT1")} /> </Section>
<Button title="Load from Slot 2" onPress={() => handleLoad("SLOT2")} />
</View> <Section title={"Automatic Chip Detection"} iconName={"camera-alt"}>
<ChipDetection
updateChipCount={(chipData) => {
const chipCountArray = Object.values(chipData);
setTotalChipsCount(chipCountArray);
}}
/>
</Section>
<Section title={"Manual Chip Adjustment"} iconName={"account-balance"}>
<ChipsSelector
totalChipsCount={totalChipsCount}
setTotalChipsCount={setTotalChipsCount}
numberOfChips={numberOfChips}
setNumberOfChips={setNumberOfChips}
/>
</Section>
<Section
title={"Distribution & Denomination"}
iconName={"currency-exchange"}
>
<ChipDistributionSummary
playerCount={playerCount}
buyInAmount={buyInAmount}
totalChipsCount={totalChipsCount}
selectedCurrency={selectedCurrency}
/>
</Section>
<Section title={"Save + Load"} iconName={"save"} orientation="row">
<>
<Button
title={"Save\nSlot 1"}
onPress={() => handleSave("SLOT1")}
disabled={buyInAmount === null}
/>
<Button
title={"Save\nSlot 2"}
onPress={() => handleSave("SLOT2")}
disabled={buyInAmount === null}
/>
<Button title={"Load\nSlot 1"} onPress={() => handleLoad("SLOT1")} />
<Button title={"Load\nSlot 2"} onPress={() => handleLoad("SLOT2")} />
</>
</Section>
</ScrollView> </ScrollView>
); );
}; };
const styles = StyleSheet.create({
header: {
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "center",
marginBottom: 20,
},
settingsContainer: {
marginBottom: 20,
padding: 10,
backgroundColor: "#f5f5f5",
borderRadius: 5,
},
settingTitle: {
fontSize: 18,
fontWeight: "bold",
marginBottom: 10,
},
buttonContainer: {
marginTop: 20,
},
});
export default IndexScreen; export default IndexScreen;

View File

@ -1,12 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { import { View, Text, TextInput } from "react-native";
View, import styles, { COLORS } from "@/styles/styles";
Text, import Button from "@/containers/Button";
TextInput,
TouchableOpacity,
StyleSheet,
} from "react-native";
import { MaterialIcons } from "@expo/vector-icons";
interface BuyInSelectorProps { interface BuyInSelectorProps {
setBuyInAmount: React.Dispatch<React.SetStateAction<number>>; setBuyInAmount: React.Dispatch<React.SetStateAction<number>>;
@ -42,31 +37,19 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({
}; };
return ( return (
<View style={styles.container}> <>
<View style={styles.header}> <View style={{ ...styles.container, flexDirection: "row" }}>
<MaterialIcons name="monetization-on" size={30} color="green" />
<Text style={styles.title}>Select Buy-in Amount:</Text>
</View>
<View style={styles.optionsContainer}>
{defaultBuyInOptions.map((amount) => ( {defaultBuyInOptions.map((amount) => (
<TouchableOpacity <Button
key={amount} key={amount}
style={[ color={buyInAmount === amount ? COLORS.PRIMARY : COLORS.SECONDARY}
styles.buyInButton,
buyInAmount === amount ? styles.selectedButton : null,
]}
onPress={() => handleBuyInSelection(amount)} onPress={() => handleBuyInSelection(amount)}
> title={`${selectedCurrency} ${amount}`}
<Text style={styles.buttonText}> ></Button>
{selectedCurrency} {amount}{" "}
{/* Display the selected currency before the amount */}
</Text>
</TouchableOpacity>
))} ))}
</View> </View>
<Text style={styles.orText}>Or enter a custom amount:</Text> <Text style={styles.p}>Or enter a custom amount:</Text>
<TextInput <TextInput
style={styles.input} style={styles.input}
value={customAmount} value={customAmount}
@ -75,60 +58,12 @@ const BuyInSelector: React.FC<BuyInSelectorProps> = ({
keyboardType="numeric" keyboardType="numeric"
/> />
<Text style={styles.selectionText}> <Text style={styles.h2}>
Selected Buy-in:{" "} Selected Buy-in:{" "}
{buyInAmount !== null ? `${selectedCurrency} ${buyInAmount}` : "None"}{" "} {buyInAmount !== null ? `${selectedCurrency} ${buyInAmount}` : "None"}
{/* Display the currency here */}
</Text> </Text>
</View> </>
); );
}; };
const styles = StyleSheet.create({
container: {
padding: 20,
},
header: {
flexDirection: "row",
alignItems: "center",
marginBottom: 10,
},
title: {
fontSize: 22,
marginLeft: 10,
},
optionsContainer: {
flexDirection: "row",
justifyContent: "space-around",
marginVertical: 10,
},
buyInButton: {
backgroundColor: "#ddd",
padding: 10,
borderRadius: 5,
},
selectedButton: {
backgroundColor: "#4caf50",
},
buttonText: {
fontSize: 16,
},
orText: {
marginTop: 10,
textAlign: "center",
},
input: {
borderWidth: 1,
borderColor: "#ccc",
padding: 8,
marginVertical: 10,
borderRadius: 5,
},
selectionText: {
marginTop: 15,
fontSize: 16,
fontWeight: "bold",
},
});
export default BuyInSelector; export default BuyInSelector;

View File

@ -1,15 +1,14 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { import { Image, ActivityIndicator, Text, View } from "react-native";
View, import Button from "@/containers/Button";
Button,
Image,
ActivityIndicator,
Text,
ScrollView,
} from "react-native";
import * as ImagePicker from "expo-image-picker"; import * as ImagePicker from "expo-image-picker";
const ChipDetection = ({ updateChipCount }) => { const ChipDetection = ({
updateChipCount,
}: {
updateChipCount: () => void;
}) => {
const [imageUri, setImageUri] = useState(null); const [imageUri, setImageUri] = useState(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
@ -113,9 +112,11 @@ const ChipDetection = ({ updateChipCount }) => {
}; };
return ( return (
<ScrollView contentContainerStyle={{ padding: 20, alignItems: "center" }}> <>
<Button title="Pick an Image" onPress={pickImage} /> <View style={{ flexDirection: "row", justifyContent: "space-evenly" }}>
<Button title="Take a Photo" onPress={takePhoto} /> <Button title="Pick an Image" onPress={pickImage} />
<Button title="Take a Photo" onPress={takePhoto} />
</View>
{imageUri && ( {imageUri && (
<Image <Image
source={{ uri: imageUri }} source={{ uri: imageUri }}
@ -124,7 +125,7 @@ const ChipDetection = ({ updateChipCount }) => {
)} )}
{loading && <ActivityIndicator size="large" color="blue" />} {loading && <ActivityIndicator size="large" color="blue" />}
{error && <Text style={{ color: "red", marginTop: 10 }}>{error}</Text>} {error && <Text style={{ color: "red", marginTop: 10 }}>{error}</Text>}
</ScrollView> </>
); );
}; };

View File

@ -1,6 +1,7 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useMemo, useState } from "react";
import { View, Text, StyleSheet } from "react-native"; import { View, Text, StyleSheet } from "react-native";
import { ColorValue } from "react-native"; import { ColorValue } from "react-native";
import styles from "@/styles/styles";
interface ChipDistributionSummaryProps { interface ChipDistributionSummaryProps {
playerCount: number; playerCount: number;
@ -15,7 +16,7 @@ const ChipDistributionSummary = ({
buyInAmount, buyInAmount,
totalChipsCount, totalChipsCount,
colors = ["white", "red", "green", "blue", "black"], colors = ["white", "red", "green", "blue", "black"],
selectedCurrency, selectedCurrency = "$",
}: ChipDistributionSummaryProps) => { }: ChipDistributionSummaryProps) => {
const validDenominations: validDenomination[] = [ const validDenominations: validDenomination[] = [
0.05, 0.1, 0.25, 0.5, 1, 2, 2.5, 5, 10, 20, 50, 100, 0.05, 0.1, 0.25, 0.5, 1, 2, 2.5, 5, 10, 20, 50, 100,
@ -153,84 +154,32 @@ const ChipDistributionSummary = ({
}, [totalChipsCount, maxDenomination, buyInAmount, playerCount]); }, [totalChipsCount, maxDenomination, buyInAmount, playerCount]);
return ( return (
<View style={styles.container}> <>
<Text style={styles.title}>Distribution & Denomination</Text> <View style={styles.container}>
<Text style={styles.subTitle}>
{selectedCurrency} {potValue} Pot
</Text>
<View style={styles.chipContainer}>
{totalChipsCount.map((_, index) => ( {totalChipsCount.map((_, index) => (
<View style={styles.chipRow} key={index}> <View style={{ flexDirection: "row" }} key={index}>
<Text <Text
style={{ style={{
...styles.chipText, ...styles.h2,
color: colors[index], color: colors[index],
...(colors[index] === "white" && styles.whiteShadow), ...(colors[index] === "white" && styles.shadow),
}} }}
> >
{distributions[index]} chips: {`${distributions[index]} chips: ${selectedCurrency}${denominations[index]} each`}
</Text>
<Text
style={{
...styles.chipText,
color: colors[index],
...(colors[index] === "white" && styles.whiteShadow),
}}
>
{selectedCurrency} {denominations[index]} each
</Text> </Text>
</View> </View>
))} ))}
</View> </View>
<Text style={styles.chipText}> <View style={{ flexDirection: "row", justifyContent: "space-between" }}>
Total Value: {selectedCurrency} {totalValue} <Text style={styles.p}>
</Text> Total Value: {selectedCurrency} {totalValue}
</View> </Text>
<Text style={styles.p}>
{selectedCurrency} {potValue} Pot
</Text>
</View>
</>
); );
}; };
const styles = StyleSheet.create({
container: {
marginTop: 20,
padding: 15,
borderRadius: 10,
display: "flex",
alignItems: "center",
},
title: {
fontSize: 18,
fontWeight: "bold",
marginBottom: 10,
},
subTitle: {
fontSize: 16,
color: "gray",
fontWeight: "bold",
marginBottom: 10,
},
chipContainer: {
marginTop: 10,
},
chipRow: {
display: "flex",
flexDirection: "row",
justifyContent: "center",
gap: 10,
},
chipText: {
fontSize: 20,
marginVertical: 2,
fontWeight: "bold",
},
whiteShadow: {
textShadowColor: "black",
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: 10,
},
noDataText: {
fontSize: 16,
color: "gray",
},
});
export default ChipDistributionSummary; export default ChipDistributionSummary;

View File

@ -4,10 +4,14 @@ import {
Text, Text,
TextInput, TextInput,
StyleSheet, StyleSheet,
Button,
ColorValue, ColorValue,
Modal, Modal,
TouchableOpacity,
} from "react-native"; } from "react-native";
import Button from "@/containers/Button";
import { MaterialCommunityIcons } from "@expo/vector-icons";
import styles from "@/styles/styles";
const colors: ColorValue[] = ["white", "red", "green", "blue", "black"]; const colors: ColorValue[] = ["white", "red", "green", "blue", "black"];
const ChipInputModal = ({ const ChipInputModal = ({
@ -31,16 +35,27 @@ const ChipInputModal = ({
setValue(totalChipsCount[colorIdx]); setValue(totalChipsCount[colorIdx]);
}, [colorIdx]); }, [colorIdx]);
const shadow = useMemo(() => color === "white", [color]);
return ( return (
<Modal <Modal
visible={showModal[0]} visible={showModal[0]}
onRequestClose={() => setShowModal([false, color])} onRequestClose={() => setShowModal([false, color])}
style={styles.modal}
presentationStyle="fullScreen"
animationType="slide"
> >
{value !== undefined && ( {value !== undefined && (
<> <>
<Text>Number of {showModal[1]?.toString()} chips</Text> <Text style={styles.h2}>
Number of {showModal[1]?.toString()} chips
</Text>
<TextInput <TextInput
style={{ color: showModal[1] }} style={{
...styles.input,
color: showModal[1],
...(shadow ? styles.shadow : {}),
}}
keyboardType="numeric" keyboardType="numeric"
value={value.toString()} value={value.toString()}
onChangeText={(v) => { onChangeText={(v) => {
@ -71,14 +86,25 @@ const Chip = ({
count: number; count: number;
setShowModal: React.Dispatch<React.SetStateAction<[boolean, ColorValue]>>; setShowModal: React.Dispatch<React.SetStateAction<[boolean, ColorValue]>>;
}) => { }) => {
const shadow = useMemo(() => color === "white", [color]);
return ( return (
<Text <TouchableOpacity
key={color.toString()}
onPress={() => setShowModal([true, color])} onPress={() => setShowModal([true, color])}
style={[{ color: color }, styles.chip]} style={{ alignItems: "center" }}
> >
{count} <MaterialCommunityIcons
</Text> name="poker-chip"
size={24}
color={color}
style={shadow ? styles.shadow : {}}
/>
<Text
key={color.toString()}
style={[{ color: color }, styles.h2, shadow ? styles.shadow : {}]}
>
{count}
</Text>
</TouchableOpacity>
); );
}; };
@ -134,34 +160,31 @@ const ChipsSelector = ({
return ( return (
<> <>
<View style={styles.container}> <View style={[styles.container, { flexDirection: "row" }]}>
<Text style={styles.title}>Chips you have</Text> {colorsUsed.map((color) => (
<View style={styles.chipContainer}> <Chip
{colorsUsed.map((color) => ( key={color.toString()}
<Chip color={color}
key={color.toString()} count={totalChipsCount[colors.indexOf(color)] ?? 0}
color={color} setShowModal={setShowModal}
count={totalChipsCount[colors.indexOf(color)] ?? 0}
setShowModal={setShowModal}
/>
))}
</View>
<View style={styles.buttonContainer}>
<Button
title="-"
onPress={() => {
setNumberOfChips(Math.max(1, numberOfChips - 1));
}}
disabled={numberOfChips == 1}
/> />
<Button ))}
title="+" </View>
onPress={() => { <View style={[styles.container, { flexDirection: "row" }]}>
setNumberOfChips(Math.min(5, numberOfChips + 1)); <Button
}} title="-"
disabled={numberOfChips == 5} onPress={() => {
/> setNumberOfChips(Math.max(1, numberOfChips - 1));
</View> }}
disabled={numberOfChips == 1}
/>
<Button
title="+"
onPress={() => {
setNumberOfChips(Math.min(5, numberOfChips + 1));
}}
disabled={numberOfChips == 5}
/>
</View> </View>
<ChipInputModal <ChipInputModal
@ -174,7 +197,7 @@ const ChipsSelector = ({
); );
}; };
const styles = StyleSheet.create({ const styles1 = StyleSheet.create({
container: { container: {
marginBottom: 20, marginBottom: 20,
gap: 10, gap: 10,

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { View, StyleSheet, Text } from "react-native";
import { Picker } from "@react-native-picker/picker"; import { Picker } from "@react-native-picker/picker";
import styles from "@/styles/styles";
interface CurrencySelectorProps { interface CurrencySelectorProps {
selectedCurrency: string; selectedCurrency: string;
@ -12,8 +12,7 @@ const CurrencySelector: React.FC<CurrencySelectorProps> = ({
setSelectedCurrency, setSelectedCurrency,
}) => { }) => {
return ( return (
<View style={styles.container}> <>
<Text style={styles.title}>Select Currency:</Text>
<Picker <Picker
selectedValue={selectedCurrency} selectedValue={selectedCurrency}
onValueChange={(itemValue) => setSelectedCurrency(itemValue)} onValueChange={(itemValue) => setSelectedCurrency(itemValue)}
@ -25,22 +24,8 @@ const CurrencySelector: React.FC<CurrencySelectorProps> = ({
<Picker.Item label="Pound (£)" value="£" /> <Picker.Item label="Pound (£)" value="£" />
<Picker.Item label="INR (₹)" value="₹" /> <Picker.Item label="INR (₹)" value="₹" />
</Picker> </Picker>
</View> </>
); );
}; };
const styles = StyleSheet.create({
container: {
marginBottom: 20,
},
title: {
fontSize: 18,
marginBottom: 10,
},
picker: {
height: 50,
width: 150,
},
});
export default CurrencySelector; export default CurrencySelector;

View File

@ -1,64 +1,43 @@
import React from "react"; import React from "react";
import { View, Text, Button, Image, StyleSheet } from "react-native"; import { View, Text } from "react-native";
import Button from "@/containers/Button";
import styles 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 MAX = 8;
const PlayerSelector: React.FC<PlayerSelectorProps> = ({ const PlayerSelector: React.FC<PlayerSelectorProps> = ({
playerCount, playerCount,
setPlayerCount, setPlayerCount,
}) => { }) => {
const increasePlayers = () => { const increasePlayers = () => {
if (playerCount < 8) setPlayerCount(playerCount + 1); if (playerCount < MAX) setPlayerCount(playerCount + 1);
}; };
const decreasePlayers = () => { const decreasePlayers = () => {
if (playerCount > 2) setPlayerCount(playerCount - 1); if (playerCount > MIN) setPlayerCount(playerCount - 1);
}; };
return ( return (
<View style={styles.container}> <>
<View style={styles.header}> <Text style={styles.h1}>{playerCount}</Text>
<Image <View style={{ flexDirection: "row", gap: 10 }}>
source={{ <Button
uri: "https://static.thenounproject.com/png/3890959-200.png", title="-"
}} onPress={decreasePlayers}
style={styles.icon} disabled={playerCount <= MIN}
/>
<Button
title="+"
onPress={increasePlayers}
disabled={playerCount >= MAX}
/> />
<Text style={styles.title}>Select Number of Players:</Text>
</View> </View>
</>
<Text style={styles.playerCount}>{playerCount}</Text>
<View style={{ flexDirection: "row" }}>
<Button title="-" onPress={decreasePlayers} />
<Button title="+" onPress={increasePlayers} />
</View>
</View>
); );
}; };
const styles = StyleSheet.create({
container: {
padding: 20,
},
header: {
flexDirection: "row",
alignItems: "center",
},
title: {
fontSize: 18,
marginLeft: 10, // Spacing between icon and text
},
icon: {
width: 48, // Increased size
height: 48, // Increased size
},
playerCount: {
fontSize: 24,
marginVertical: 10,
},
});
export default PlayerSelector; export default PlayerSelector;

View File

@ -32,7 +32,6 @@ describe("BuyInSelector Component", () => {
}); });
it("renders the buy-in options and input correctly", () => { it("renders the buy-in options and input correctly", () => {
expect(getByText("Select Buy-in Amount:")).toBeTruthy();
expect(getByText("$ 10")).toBeTruthy(); expect(getByText("$ 10")).toBeTruthy();
expect(getByText("$ 25")).toBeTruthy(); expect(getByText("$ 25")).toBeTruthy();
expect(getByText("$ 50")).toBeTruthy(); expect(getByText("$ 50")).toBeTruthy();

View File

@ -12,26 +12,20 @@ describe("ChipDistributionSummary Component", () => {
const expectedDistribution = [2, 2, 1, 2, 2]; const expectedDistribution = [2, 2, 1, 2, 2];
const expectedDenominations = [0.5, 1, 2, 2.5, 5]; const expectedDenominations = [0.5, 1, 2, 2.5, 5];
const { getByText, getAllByText } = render( const { getByText } = render(
<ChipDistributionSummary <ChipDistributionSummary
playerCount={playerCount} playerCount={playerCount}
buyInAmount={buyInAmount} buyInAmount={buyInAmount}
totalChipsCount={totalChipsCount} totalChipsCount={totalChipsCount}
selectedCurrency={"$"}
/> />
); );
expect(getByText("Distribution & Denomination")).toBeTruthy();
expectedDistribution.forEach((count, index) => { expectedDistribution.forEach((count, index) => {
// Ensure "X chips:" appears correctly const regex = new RegExp(
expect(getAllByText(new RegExp(`^${count}\\s+chips:`, "i"))).toBeTruthy(); `^${count}\\s+chips:\\s+\\$${expectedDenominations[index]}\\s+each$`
);
// Ensure value format matches the rendered output expect(getByText(regex)).toBeTruthy();
expect(
getByText(
new RegExp(`^\\s*${expectedDenominations[index]}\\s+each$`, "i")
)
).toBeTruthy();
}); });
}); });
@ -40,6 +34,7 @@ describe("ChipDistributionSummary Component", () => {
<ChipDistributionSummary <ChipDistributionSummary
playerCount={0} playerCount={0}
buyInAmount={20} buyInAmount={20}
selectedCurrency={"$"}
totalChipsCount={[]} totalChipsCount={[]}
/> />
); );
@ -66,6 +61,7 @@ describe("ChipDistributionSummary Component", () => {
playerCount={playerCount} playerCount={playerCount}
buyInAmount={100} buyInAmount={100}
totalChipsCount={totalChipsCount} totalChipsCount={totalChipsCount}
selectedCurrency={"$"}
/> />
); );

View File

@ -10,6 +10,13 @@ import ChipsSelector from "@/components/ChipsSelector";
const TOTAL_CHIPS_COUNT = [100, 80, 60, 40, 20]; const TOTAL_CHIPS_COUNT = [100, 80, 60, 40, 20];
jest.mock("@expo/vector-icons", () => {
const { Text } = require("react-native");
return {
MaterialCommunityIcons: () => <Text>TestIcon</Text>,
};
});
const mocktTotalChipsCount = jest.fn(); const mocktTotalChipsCount = jest.fn();
const mockSetNumberOfChips = jest.fn(); const mockSetNumberOfChips = jest.fn();

View File

@ -13,7 +13,6 @@ describe("CurrencySelector Component", () => {
/> />
); );
expect(getByText("Select Currency:")).toBeTruthy(); // Check label exists
expect(getByTestId("currency-picker")).toBeTruthy(); // Check Picker exists expect(getByTestId("currency-picker")).toBeTruthy(); // Check Picker exists
}); });

View File

@ -9,7 +9,6 @@ describe("PlayerSelector Component", () => {
<PlayerSelector playerCount={4} setPlayerCount={setPlayerCount} /> <PlayerSelector playerCount={4} setPlayerCount={setPlayerCount} />
); );
expect(getByText("Select Number of Players:")).toBeTruthy();
expect(getByText("4")).toBeTruthy(); expect(getByText("4")).toBeTruthy();
expect(getByRole("button", { name: "-" })).toBeTruthy(); expect(getByRole("button", { name: "-" })).toBeTruthy();
expect(getByRole("button", { name: "+" })).toBeTruthy(); expect(getByRole("button", { name: "+" })).toBeTruthy();

9
containers/Button.tsx Normal file
View File

@ -0,0 +1,9 @@
import { ButtonProps, Button } from "react-native";
import { COLORS } from "@/styles/styles";
// More styling can be done, or swap this out with more flexible component like a TouchableOpacity if needed
const AppButton = (props: ButtonProps) => (
<Button color={COLORS.PRIMARY} {...props} />
);
export default AppButton;

67
containers/Section.tsx Normal file
View File

@ -0,0 +1,67 @@
import { View, Text, StyleSheet } from "react-native";
import React from "react";
import { MaterialIcons } from "@expo/vector-icons";
import globalStyles from "@/styles/styles";
const titleCase = (input: string) =>
input
.split(" ")
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(" ");
//Higher Order Component (HOC) for styling purposes
const Section = ({
title,
iconName,
children,
orientation = "column",
}: {
title: string;
iconName: string | any;
children: React.JSX.Element;
orientation?: "row" | "column";
}) => {
return (
<View style={styles.container}>
<View style={styles.header}>
<MaterialIcons
style={styles.icon}
name={iconName}
size={30}
color={"black"}
/>
<Text style={styles.title}>{titleCase(title)}</Text>
</View>
<View style={{ ...styles.content, flexDirection: orientation }}>
{children}
</View>
</View>
);
};
const styles = StyleSheet.create({
container: {
marginBottom: 20,
display: "flex",
alignContent: "center",
justifyContent: "center",
},
header: {
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 5,
marginBottom: 10,
},
icon: {},
title: {
...globalStyles.h1,
},
content: {
display: "flex",
justifyContent: "space-evenly",
gap: 5,
},
});
export default Section;

View File

@ -0,0 +1,32 @@
import { render, screen } from "@testing-library/react-native";
import Section from "../Section";
import { Text } from "react-native";
import React from "react";
jest.mock("@expo/vector-icons", () => {
const { Text } = require("react-native");
return {
MaterialIcons: () => <Text testID={"test-icon"}>TestIcon</Text>,
};
});
const TITLE = "Select the weather";
const rend = () =>
render(
<Section title={TITLE} iconName={"test-icon"}>
<Text>child</Text>
</Section>
);
describe("tests for the Section HOC Component", () => {
it("everything expected appears", () => {
rend();
const title = screen.getByText(/select the weather/i);
expect(title).toBeTruthy();
const child = screen.getByText("child");
expect(child).toBeTruthy();
const icon = screen.getByTestId("test-icon");
expect(icon).toBeTruthy();
});
});

59
styles/styles.ts Normal file
View File

@ -0,0 +1,59 @@
import { StyleSheet } from "react-native";
export const COLORS = {
PRIMARY: "#007bff",
SECONDARY: "#6c757d",
SUCCESS: "#28a745",
DANGER: "#dc3545",
WARNING: "#ffc107",
};
const lightStyles = StyleSheet.create({});
const darkStyles = StyleSheet.create({});
const GlobalStyles = StyleSheet.create({
scrollView: {},
scrollViewContent: {
padding: 15,
},
container: {
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
gap: 10,
},
h1: { fontSize: 20, fontWeight: "bold" },
h2: { fontSize: 18, fontWeight: "normal" },
p: {
fontSize: 16,
color: "#333",
},
input: {
borderWidth: 1,
borderColor: "#ccc",
padding: 8,
marginVertical: 10,
borderRadius: 5,
},
modal: {},
button: {
backgroundColor: "#007bff",
padding: 10,
borderRadius: 5,
},
shadow: {
textShadowColor: "black",
textShadowOffset: { width: 0, height: 0 },
textShadowRadius: 10,
},
picker: {
height: 50,
width: 150,
},
});
export default GlobalStyles;

13
util/context.ts Normal file
View File

@ -0,0 +1,13 @@
import React, { createContext } from "react";
export interface IAppContext {
showSettings: boolean;
}
const defaultContext: IAppContext = {
showSettings: false,
};
const AppContext = createContext<IAppContext>(defaultContext);
export default AppContext;