diff --git a/Contributing.md b/Contributing.md index e7155a43..f738a29a 100644 --- a/Contributing.md +++ b/Contributing.md @@ -9,10 +9,10 @@ For android, use JDK 17. ```zsh npx react-native-clean-project npx expo prebuild -eas build --profile development-simulator --platform ios --local -eas build --profile development-simulator --platform android --local -eas build:run -p ios # select expo build from above -eas build:run -p android # select expo build from above +npx eas build --profile development-simulator --platform ios --local +npx eas build --profile development-simulator --platform android --local +npx eas build:run -p ios # select expo build from above +npx eas build:run -p android # select expo build from above npx expo start --dev-client ``` diff --git a/redux/GamesSlice.ts b/redux/GamesSlice.ts index 8253d4d4..f59096b4 100644 --- a/redux/GamesSlice.ts +++ b/redux/GamesSlice.ts @@ -7,7 +7,7 @@ import { getPalette } from '../src/ColorPalette'; import { SortDirectionKey, SortSelectorKey } from '../src/components/ScoreLog/SortHelper'; import logger from '../src/Logger'; -import { playerAdd, selectPlayerById } from './PlayersSlice'; +import { playerAdd, selectPlayerById, updatePlayer } from './PlayersSlice'; import { setCurrentGameId } from './SettingsSlice'; import { RootState } from './store'; @@ -34,6 +34,7 @@ const initialState = gamesAdapter.getInitialState({ locked: false, sortSelectorKey: SortSelectorKey.ByIndex, sortDirectionKey: SortDirectionKey.Normal, + palette: 'original', }); const gamesSlice = createSlice({ @@ -98,7 +99,8 @@ const gamesSlice = createSlice({ playerIds: action.payload.playerIds, } }); - } + }, + } }); @@ -159,6 +161,35 @@ export const asyncRematchGame = createAsyncThunk( } ); +export const asyncSetGamePalette = createAsyncThunk( + 'games/setpalette', + async ( + { gameId, palette }: { gameId: string, palette: string; }, + { dispatch, getState } + ) => { + // Update game + dispatch(updateGame({ + id: gameId, + changes: { + palette: palette, + } + })); + // Get palette colors + const paletteColors = getPalette(palette); + + const game = selectGameById(getState() as RootState, gameId); + + // Update players + game?.playerIds.forEach((playerId) => { + const color = paletteColors[game.playerIds.indexOf(playerId) % paletteColors.length]; + dispatch(updatePlayer({ + id: playerId, + changes: { color: color } + })); + }); + } +); + export const asyncCreateGame = createAsyncThunk( 'games/create', async ( @@ -172,20 +203,24 @@ export const asyncCreateGame = createAsyncThunk( playerIds.push(Crypto.randomUUID()); } - playerIds.forEach((playerId) => { + const paletteName = initialState.palette; + const paletteColors = getPalette(paletteName); + + playerIds.forEach((playerId, index) => { + const color = paletteColors[index % paletteColors.length]; dispatch(playerAdd({ id: playerId, playerName: `Player ${playerIds.indexOf(playerId) + 1}`, scores: [0], + color: color, })); }); dispatch(gameSave({ + ...initialState, id: newGameId, title: `Game ${gameCount + 1}`, dateCreated: Date.now(), - roundCurrent: 0, - roundTotal: 1, playerIds: playerIds, })); @@ -199,22 +234,60 @@ export const asyncCreateGame = createAsyncThunk( } ); +export const addPlayer = createAsyncThunk( + 'games/addplayer', + async ( + { gameId, playerName }: { gameId: string, playerName: string; }, + { dispatch, getState } + ) => { + const playerId = Crypto.randomUUID(); + const s = getState() as RootState; + const paletteName = s.games.entities[gameId]?.palette; + const palette = getPalette(paletteName || 'original'); + const playerIndex = s.games.entities[gameId]?.playerIds.length || 0; + const paletteColor = palette[playerIndex % palette.length]; + + dispatch(playerAdd({ + id: playerId, + playerName: playerName, + scores: [0], + color: paletteColor, + })); + + dispatch(updateGame({ + id: gameId, + changes: { + playerIds: [...selectGameById(getState() as RootState, gameId)?.playerIds || [], playerId], + } + })); + + return playerId; + } +); + export const selectSortSelectorKey = (state: RootState, gameId: string) => { const key = selectGameById(state, gameId)?.sortSelectorKey; return key !== undefined ? key : SortSelectorKey.ByScore; }; -const selectPaletteName = (state: RootState, gameId: string) => state.games.entities[gameId]?.palette; -const selectPlayerIndex = (_: RootState, __: string, playerIndex: number) => playerIndex; - export const selectPlayerColors = createSelector( - [selectPaletteName, selectPlayerIndex], - (paletteName, playerIndex) => { - // TODO: Get player color if it exists + [ + (state: RootState, playerId: string) => { + const gameId = selectAllGames(state).filter((game) => game.playerIds.includes(playerId))[0].id; + const paletteName = state.games.entities[gameId]?.palette; - const palette = getPalette(paletteName || 'original'); + const playerColor = state.players.entities[playerId]?.color; + + const playerIndex = state.games.entities[gameId]?.playerIds.indexOf(playerId) || 0; + + return { paletteName, playerColor, playerIndex }; + }, + ], + ({ paletteName, playerColor, playerIndex }) => { + const palette = getPalette(paletteName || 'original') || getPalette('original'); + const paletteBG = palette[playerIndex % palette.length]; - const bg = palette[playerIndex % palette.length]; + const bg = playerColor || paletteBG; const blackContrast = getContrastRatio(bg, '#000').number; const whiteContrast = getContrastRatio(bg, '#fff').number; diff --git a/redux/SettingsSlice.ts b/redux/SettingsSlice.ts index 5ec8f2b1..3e01e8dc 100644 --- a/redux/SettingsSlice.ts +++ b/redux/SettingsSlice.ts @@ -17,6 +17,7 @@ export interface SettingsState { showColorPalettes?: boolean; interactionType: InteractionType; lastStoreReviewPrompt: number; + devMenuEnabled?: boolean; }; const initialState: SettingsState = { @@ -26,8 +27,9 @@ const initialState: SettingsState = { addendTwo: 10, currentGameId: undefined, onboarded: undefined, - showPointParticles: true, + showPointParticles: false, showPlayerIndex: false, + showColorPalettes: false, interactionType: InteractionType.SwipeVertical, lastStoreReviewPrompt: 0, }; @@ -72,6 +74,9 @@ const settingsSlice = createSlice({ setLastStoreReviewPrompt(state, action: PayloadAction) { state.lastStoreReviewPrompt = action.payload; }, + toggleDevMenuEnabled(state) { + state.devMenuEnabled = !state.devMenuEnabled; + } } }); @@ -87,6 +92,7 @@ export const { toggleShowColorPalettes, setInteractionType, setLastStoreReviewPrompt, + toggleDevMenuEnabled, } = settingsSlice.actions; export default settingsSlice.reducer; diff --git a/src/ColorPalette.ts b/src/ColorPalette.ts index 154f6388..29aa3627 100644 --- a/src/ColorPalette.ts +++ b/src/ColorPalette.ts @@ -14,6 +14,20 @@ const palettes: PaletteType = { '#755647', '#925561', ], + 'c': [ + '#88498f', + '#779fa1', + '#e0cba8', + '#ff6542', + '#564154' + ], + 'd': [ + '#f8ffe5', + '#06d6a0', + '#1b9aaa', + '#ef476f', + '#ffc43d' + ], 'pastel': [ '#f9d5e5', '#eeac99', @@ -25,13 +39,19 @@ const palettes: PaletteType = { '#f6416c', ], 'dark': [ - '#011627', '#fdfffc', '#2ec4b6', '#e71d36', '#ff9f1c', '#f3722c', ], + 'f': [ + '#fcaa67', + '#b0413e', + '#ffffc7', + '#548687', + '#473335' + ], 'grey': [ '#f8f9fa', '#e9ecef', @@ -44,52 +64,6 @@ const palettes: PaletteType = { '#212529', '#000000', ], - 'a': [ - '#114b5f', - '#456990', - '#e4fde1', - '#f45b69', - '#6b2737' - ], - 'b': [ - '#c25858', - '#01497c', - ], - 'c': [ - '#88498f', - '#779fa1', - '#e0cba8', - '#ff6542', - '#564154' - ], - 'd': [ - '#f8ffe5', - '#06d6a0', - '#1b9aaa', - '#ef476f', - '#ffc43d' - ], - 'e': [ - '#1f2041', - '#4b3f72', - '#ffc857', - '#119da4', - '#19647e' - ], - 'f': [ - '#fcaa67', - '#b0413e', - '#ffffc7', - '#548687', - '#473335' - ], - 'g': [ - '#ffa400', - '#009ffd', - '#2a2a72', - '#232528', - '#eaf6ff' - ] }; export const getPalettes = (): string[] => { diff --git a/src/components/AppInfo/RotatingIcon.tsx b/src/components/AppInfo/RotatingIcon.tsx index 06768a0a..dfd21cda 100644 --- a/src/components/AppInfo/RotatingIcon.tsx +++ b/src/components/AppInfo/RotatingIcon.tsx @@ -10,7 +10,12 @@ import Animated, { withTiming } from 'react-native-reanimated'; +import { useAppDispatch } from '../../../redux/hooks'; +import { toggleDevMenuEnabled } from '../../../redux/SettingsSlice'; + const RotatingIcon: React.FunctionComponent = ({ }) => { + const dispatch = useAppDispatch(); + const rotation = useSharedValue(0); const rotationCount = useSharedValue(1); const animatedStyles = useAnimatedStyle(() => { @@ -21,12 +26,28 @@ const RotatingIcon: React.FunctionComponent = ({ }) => { }; }); - return { - rotationCount.value = rotationCount.value + 1; - rotation.value = withTiming((rotationCount.value * 90), { duration: 1000, easing: Easing.elastic(1) }); + let holdCallback: NodeJS.Timeout; + const onPressIn = () => { + holdCallback = setTimeout(() => { + dispatch(toggleDevMenuEnabled()); + // spring expand animate the Animated.View + }, 5000); + }; + + const onPressOut = () => { + if (holdCallback == null) return; + clearTimeout(holdCallback); + }; + + return { + rotationCount.value = rotationCount.value + 1; + rotation.value = withTiming((rotationCount.value * 90), { duration: 1000, easing: Easing.elastic(1) }); - await analytics().logEvent('app_icon'); - }}> + await analytics().logEvent('app_icon'); + }}> = () => { const playerCount = playerIds.length; - const desiredAspectRatio = 0.8; + const desiredAspectRatio = 1; const layoutHandler = (e: LayoutChangeEvent) => { const { width, height } = e.nativeEvent.layout; diff --git a/src/components/Boards/FlexboxTile.tsx b/src/components/Boards/FlexboxTile.tsx index 3b890616..c7a1b3f3 100644 --- a/src/components/Boards/FlexboxTile.tsx +++ b/src/components/Boards/FlexboxTile.tsx @@ -5,7 +5,7 @@ import Animated, { Easing, FadeIn } from 'react-native-reanimated'; import { selectPlayerColors } from '../../../redux/GamesSlice'; import { useAppSelector } from '../../../redux/hooks'; -import { selectCurrentGame, selectInteractionType } from '../../../redux/selectors'; +import { selectInteractionType } from '../../../redux/selectors'; import { interactionComponents } from '../Interactions/InteractionComponents'; import { InteractionType } from '../Interactions/InteractionType'; import AdditionTile from '../PlayerTiles/AdditionTile/AdditionTile'; @@ -32,9 +32,8 @@ const FlexboxTile: React.FunctionComponent = ({ if (!(width > 0 && height > 0)) return null; if (Number.isNaN(width) || Number.isNaN(height)) return null; - const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); const playerIndexLabel = useAppSelector(state => state.settings.showPlayerIndex); - const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId || '', index || 0)); + const playerColors = useAppSelector(state => selectPlayerColors(state, playerId)); const [bg, fg] = playerColors; const widthPerc: DimensionValue = `${(100 / cols)}%`; diff --git a/src/components/ColorPalettes/ColorSelector.tsx b/src/components/ColorPalettes/ColorSelector.tsx new file mode 100644 index 00000000..89a08995 --- /dev/null +++ b/src/components/ColorPalettes/ColorSelector.tsx @@ -0,0 +1,111 @@ +import React from 'react'; + +import { StyleSheet, Text, TouchableOpacity, View } from 'react-native'; + +import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; +import { updatePlayer } from '../../../redux/PlayersSlice'; +import { selectCurrentGame } from '../../../redux/selectors'; +import { getPalette, getPalettes } from '../../ColorPalette'; + +interface ColorSelectorProps { + playerId: string; +} + +const ColorSelector: React.FC = ({ playerId }) => { + const colorPalettes = getPalettes(); + const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); + const currentPalette = useAppSelector(state => selectCurrentGame(state)?.palette); + const playerColor = useAppSelector(state => state.players.entities[playerId]?.color); + + if (!currentGameId) return null; + + const dispatch = useAppDispatch(); + + const tapColorHandler = (color: string) => { + dispatch(updatePlayer({ + id: playerId, + changes: { color: color } + })); + }; + + return ( + + + + + Current Pallete + + + {currentPalette && + + { + getPalette(currentPalette).map((color, i) => ( + tapColorHandler(color)} + > + + + )) + } + + } + + + + Other Palletes + + + {colorPalettes.map((palette, palette_index) => ( + currentPalette !== palette && ( + + { + getPalette(palette).map((color, i) => ( + tapColorHandler(color)} + > + + + )) + } + + ) + ))} + + ); +}; + +export default ColorSelector; + +const styles = StyleSheet.create({ + colorBadge: { + borderColor: '#999', + borderWidth: 1, + borderRadius: 25, + borderStyle: 'solid', + height: 25, + marginHorizontal: 5, + padding: 5, + width: 25, + }, + title: { + color: 'white', + }, + titleContainer: { + flexDirection: 'column', + justifyContent: 'space-between', + marginVertical: 10, + } +}); diff --git a/src/components/ColorPalettes/PaletteSelector.tsx b/src/components/ColorPalettes/PaletteSelector.tsx index 912485ad..1f1f588d 100644 --- a/src/components/ColorPalettes/PaletteSelector.tsx +++ b/src/components/ColorPalettes/PaletteSelector.tsx @@ -2,14 +2,14 @@ import React from 'react'; import { ScrollView, StyleSheet, TouchableOpacity } from 'react-native'; -import { updateGame } from '../../../redux/GamesSlice'; +import { asyncSetGamePalette } from '../../../redux/GamesSlice'; import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; import { selectCurrentGame } from '../../../redux/selectors'; import { getPalette, getPalettes } from '../../ColorPalette'; import PalettePreview from './PalettePreview'; -const MemoizedColorCircle = React.memo(PalettePreview); +const MemoizedPalettePreview = React.memo(PalettePreview); const PaletteSelector: React.FunctionComponent = () => { const colorPalettes = getPalettes(); @@ -21,12 +21,12 @@ const PaletteSelector: React.FunctionComponent = () => { if (!currentGameId) return null; const onSelect = (palette: string) => { - dispatch(updateGame({ - id: currentGameId, - changes: { + dispatch( + asyncSetGamePalette({ + gameId: currentGameId, palette: palette, - } - })); + }) + ); }; return ( @@ -39,7 +39,7 @@ const PaletteSelector: React.FunctionComponent = () => { ]} onPress={() => onSelect(palette)} > - + ))} diff --git a/src/components/EditGame.tsx b/src/components/EditGame.tsx index d375df9e..ef3192c0 100644 --- a/src/components/EditGame.tsx +++ b/src/components/EditGame.tsx @@ -7,6 +7,8 @@ import { updateGame } from '../../redux/GamesSlice'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { selectCurrentGame } from '../../redux/selectors'; +import PaletteSelector from './ColorPalettes/PaletteSelector'; + const UNTITLED = 'Untitled'; const EditGame = ({ }) => { @@ -48,6 +50,8 @@ const EditGame = ({ }) => { })); }; + const showColorPalettes = useAppSelector(state => state.settings.showColorPalettes); + return ( <> @@ -69,6 +73,12 @@ const EditGame = ({ }) => {   {new Date(currentGame.dateCreated).toLocaleTimeString()} + + + {showColorPalettes && + + } + ); }; diff --git a/src/components/PlayerListItem.tsx b/src/components/PlayerListItem.tsx index ed03e192..581ca685 100644 --- a/src/components/PlayerListItem.tsx +++ b/src/components/PlayerListItem.tsx @@ -31,7 +31,7 @@ const PlayerListItem: React.FunctionComponent = ({ const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); const playerIds = useAppSelector(state => selectGameById(state, currentGameId || '')?.playerIds); const player = useAppSelector(state => selectPlayerById(state, playerId)); - const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId || '', index || 0)); + const playerColors = useAppSelector(state => selectPlayerColors(state, playerId)); if (currentGameId == '' || currentGameId === undefined) return null; if (playerIds === undefined) return null; diff --git a/src/components/ScoreLog/PlayerNameColumn.tsx b/src/components/ScoreLog/PlayerNameColumn.tsx index 525f0fc8..6fa5ea6c 100644 --- a/src/components/ScoreLog/PlayerNameColumn.tsx +++ b/src/components/ScoreLog/PlayerNameColumn.tsx @@ -17,8 +17,7 @@ interface CellProps { } const PlayerNameCell: React.FunctionComponent = ({ index, playerId }) => { - const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); - const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId || '', index || 0)); + const playerColors = useAppSelector(state => selectPlayerColors(state, playerId)); const playerName = useAppSelector(state => selectPlayerById(state, playerId)?.playerName); return ( @@ -55,7 +54,6 @@ const PlayerHeaderCell: React.FunctionComponent = () => { ); }; - const PlayerNameColumn: React.FunctionComponent = () => { const sortKey = useAppSelector(state => selectCurrentGame(state)?.sortSelectorKey); diff --git a/src/components/ScoreLog/SortHelper.ts b/src/components/ScoreLog/SortHelper.ts index 202884bc..ff8eb4d3 100644 --- a/src/components/ScoreLog/SortHelper.ts +++ b/src/components/ScoreLog/SortHelper.ts @@ -35,7 +35,6 @@ export const selectPlayerIdsByScore: SortSelector = createSelector( (state: RootState) => state.settings.currentGameId ? state.games.entities[state.settings.currentGameId]?.sortDirectionKey : undefined ], (players: ScoreState[], currentGame: GameState | undefined, sortDirectionKey: SortDirectionKey | undefined) => { - console.log('sort selector'); if (!currentGame) return []; const playerIds = [...players] diff --git a/src/components/Sheets/GameSheet.tsx b/src/components/Sheets/GameSheet.tsx index f4909295..c604fc4e 100644 --- a/src/components/Sheets/GameSheet.tsx +++ b/src/components/Sheets/GameSheet.tsx @@ -13,7 +13,6 @@ import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; import { updatePlayer } from '../../../redux/PlayersSlice'; import { systemBlue } from '../../constants'; import BigButton from '../BigButtons/BigButton'; -import PaletteSelector from '../ColorPalettes/PaletteSelector'; import RematchIcon from '../Icons/RematchIcon'; import Rounds from '../Rounds'; @@ -190,8 +189,6 @@ const GameSheet: React.FunctionComponent = ({ navigation, containerHeight [] ); - const showColorPalettes = useAppSelector(state => state.settings.showColorPalettes); - return ( = ({ navigation, containerHeight - {showColorPalettes && - - } - Tap the player column to toggle sorting by total score and original order. @@ -244,7 +237,7 @@ const GameSheet: React.FunctionComponent = ({ navigation, containerHeight {!gameLocked && -