diff --git a/redux/GamesSlice.ts b/redux/GamesSlice.ts index f59096b4..dc902159 100644 --- a/redux/GamesSlice.ts +++ b/redux/GamesSlice.ts @@ -270,20 +270,27 @@ export const selectSortSelectorKey = (state: RootState, gameId: string) => { return key !== undefined ? key : SortSelectorKey.ByScore; }; -export const selectPlayerColors = createSelector( +export const makeSelectPlayerColors = () => createSelector( [ - (state: RootState, playerId: string) => { - const gameId = selectAllGames(state).filter((game) => game.playerIds.includes(playerId))[0].id; - const paletteName = state.games.entities[gameId]?.palette; + (state: RootState, gameId: string | undefined): GameState | undefined => { + if (!gameId) { + return undefined; + } + return state.games.entities[gameId]; + }, + (state: RootState, gameId: string | undefined, playerId: string): string => playerId, + (state: RootState, gameId: string | undefined, playerId: string): string | undefined => state.players.entities[playerId]?.color, + ], + (game, playerId, playerColor) => { + if (!game || !playerId) { + return ['#000000', '#FFFFFF']; + } - const playerColor = state.players.entities[playerId]?.color; + const paletteName = game.palette; + const playerIds = game.playerIds; - const playerIndex = state.games.entities[gameId]?.playerIds.indexOf(playerId) || 0; + const playerIndex = playerIds.indexOf(playerId); - return { paletteName, playerColor, playerIndex }; - }, - ], - ({ paletteName, playerColor, playerIndex }) => { const palette = getPalette(paletteName || 'original') || getPalette('original'); const paletteBG = palette[playerIndex % palette.length]; @@ -299,6 +306,7 @@ export const selectPlayerColors = createSelector( } ); + export const { updateGame, roundNext, diff --git a/redux/PlayersSlice.ts b/redux/PlayersSlice.ts index 237fc932..f3d68d9e 100644 --- a/redux/PlayersSlice.ts +++ b/redux/PlayersSlice.ts @@ -1,5 +1,7 @@ import crashlytics from '@react-native-firebase/crashlytics'; -import { PayloadAction, createEntityAdapter, createSlice } from '@reduxjs/toolkit'; +import { PayloadAction, createEntityAdapter, createSelector, createSlice } from '@reduxjs/toolkit'; + +import { RootState } from './store'; type RoundIndex = number; @@ -72,3 +74,17 @@ export const { selectIds: selectPlayerIds, // Pass in a selector that returns the posts slice of state } = playersAdapter.getSelectors((state: PlayersSlice) => state.players); + +export const selectPlayerNameById = createSelector( + [ + (state: RootState, playerId: string) => state.players.entities[playerId] + ], + (player) => player?.playerName +); + +export const selectPlayerScoreByRound = createSelector( + [ + (state: RootState, playerId: string, round: number) => state.players.entities[playerId]?.scores[round] + ], + (score) => score || 0 +); diff --git a/src/components/Boards/FlexboxTile.tsx b/src/components/Boards/FlexboxTile.tsx index c7a1b3f3..fe14be57 100644 --- a/src/components/Boards/FlexboxTile.tsx +++ b/src/components/Boards/FlexboxTile.tsx @@ -3,9 +3,9 @@ import React from 'react'; import { DimensionValue, StyleSheet } from 'react-native'; import Animated, { Easing, FadeIn } from 'react-native-reanimated'; -import { selectPlayerColors } from '../../../redux/GamesSlice'; +import { makeSelectPlayerColors } from '../../../redux/GamesSlice'; import { useAppSelector } from '../../../redux/hooks'; -import { selectInteractionType } from '../../../redux/selectors'; +import { selectCurrentGame, selectInteractionType } from '../../../redux/selectors'; import { interactionComponents } from '../Interactions/InteractionComponents'; import { InteractionType } from '../Interactions/InteractionType'; import AdditionTile from '../PlayerTiles/AdditionTile/AdditionTile'; @@ -20,7 +20,7 @@ interface Props { height: number; } -const FlexboxTile: React.FunctionComponent = ({ +const FlexboxTile: React.FunctionComponent = React.memo(({ index, width, height, @@ -32,8 +32,10 @@ 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, playerId)); + const selectPlayerColors = makeSelectPlayerColors(); + const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId, playerId)); const [bg, fg] = playerColors; const widthPerc: DimensionValue = `${(100 / cols)}%`; @@ -66,7 +68,7 @@ const FlexboxTile: React.FunctionComponent = ({ ); -}; +}); const styles = StyleSheet.create({ playerCard: { diff --git a/src/components/PlayerListItem.tsx b/src/components/PlayerListItem.tsx index 581ca685..931fab8e 100644 --- a/src/components/PlayerListItem.tsx +++ b/src/components/PlayerListItem.tsx @@ -6,7 +6,7 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { Alert, StyleSheet, Text, TouchableOpacity, View } from 'react-native'; import { Icon, ListItem } from 'react-native-elements'; -import { selectGameById, selectPlayerColors, updateGame } from '../../redux/GamesSlice'; +import { makeSelectPlayerColors, selectGameById, updateGame } from '../../redux/GamesSlice'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import { removePlayer, selectPlayerById } from '../../redux/PlayersSlice'; import { selectCurrentGame } from '../../redux/selectors'; @@ -31,7 +31,8 @@ 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, playerId)); + const selectPlayerColors = makeSelectPlayerColors(); + const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId, playerId)); if (currentGameId == '' || currentGameId === undefined) return null; if (playerIds === undefined) return null; diff --git a/src/components/Rounds.tsx b/src/components/Rounds.tsx index 437f9199..e65448b1 100644 --- a/src/components/Rounds.tsx +++ b/src/components/Rounds.tsx @@ -5,12 +5,12 @@ import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { LayoutChangeEvent, Platform, ScrollView, StyleSheet, View } from 'react-native'; import { TouchableOpacity } from 'react-native-gesture-handler'; -import { selectGameById, selectSortSelectorKey, setSortSelector } from '../../redux/GamesSlice'; +import { selectGameById, setSortSelector } from '../../redux/GamesSlice'; import { useAppDispatch, useAppSelector } from '../../redux/hooks'; import PlayerNameColumn from './ScoreLog/PlayerNameColumn'; import RoundScoreColumn from './ScoreLog/RoundScoreColumn'; -import { SortSelectorKey, sortSelectors } from './ScoreLog/SortHelper'; +import { SortSelectorKey } from './ScoreLog/SortHelper'; import TotalScoreColumn from './ScoreLog/TotalScoreColumn'; interface Props { @@ -61,9 +61,6 @@ const Rounds: React.FunctionComponent = ({ }) => { const dispatch = useAppDispatch(); - const sortSelectorKey = useAppSelector(state => selectSortSelectorKey(state, currentGameId)); - const sortSelector = sortSelectors[sortSelectorKey]; - const sortByPlayerIndex = () => { dispatch(setSortSelector({ gameId: currentGameId, sortSelector: SortSelectorKey.ByIndex })); }; @@ -88,10 +85,10 @@ const Rounds: React.FunctionComponent = ({ }) => { {roundsIterator.map((item, round) => ( onLayoutHandler(e, round)}> + isCurrentRound={round == roundCurrent} + /> ))} diff --git a/src/components/ScoreLog/PlayerNameColumn.tsx b/src/components/ScoreLog/PlayerNameColumn.tsx index 6fa5ea6c..e6785d06 100644 --- a/src/components/ScoreLog/PlayerNameColumn.tsx +++ b/src/components/ScoreLog/PlayerNameColumn.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { StyleSheet, Text, View } from 'react-native'; import { Icon } from 'react-native-elements/dist/icons/Icon'; -import { selectPlayerColors } from '../../../redux/GamesSlice'; +import { makeSelectPlayerColors } from '../../../redux/GamesSlice'; import { useAppSelector } from '../../../redux/hooks'; import { selectPlayerById } from '../../../redux/PlayersSlice'; import { selectCurrentGame } from '../../../redux/selectors'; @@ -17,7 +17,9 @@ interface CellProps { } const PlayerNameCell: React.FunctionComponent = ({ index, playerId }) => { - const playerColors = useAppSelector(state => selectPlayerColors(state, playerId)); + const selectPlayerColors = makeSelectPlayerColors(); + const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); + const playerColors = useAppSelector(state => selectPlayerColors(state, currentGameId, playerId)); const playerName = useAppSelector(state => selectPlayerById(state, playerId)?.playerName); return ( @@ -54,9 +56,10 @@ const PlayerHeaderCell: React.FunctionComponent = () => { ); }; +const MemoizedPlayerNameCell = React.memo(PlayerNameCell); + const PlayerNameColumn: React.FunctionComponent = () => { const sortKey = useAppSelector(state => selectCurrentGame(state)?.sortSelectorKey); - const sortSelector = sortSelectors[sortKey || SortSelectorKey.ByIndex]; const sortedPlayerIds = useAppSelector(sortSelector); @@ -65,7 +68,7 @@ const PlayerNameColumn: React.FunctionComponent = () => { {sortedPlayerIds.map((playerId, index) => ( - playerId && + playerId && ))} ); diff --git a/src/components/ScoreLog/RoundScoreCell.tsx b/src/components/ScoreLog/RoundScoreCell.tsx index f942835f..898a93ea 100644 --- a/src/components/ScoreLog/RoundScoreCell.tsx +++ b/src/components/ScoreLog/RoundScoreCell.tsx @@ -1,9 +1,9 @@ import React, { memo } from 'react'; -import { Text, StyleSheet } from 'react-native'; +import { StyleSheet, Text } from 'react-native'; import { useAppSelector } from '../../../redux/hooks'; -import { selectPlayerById } from '../../../redux/PlayersSlice'; +import { selectPlayerScoreByRound } from '../../../redux/PlayersSlice'; interface Props { playerId: string; @@ -12,13 +12,7 @@ interface Props { } const RoundScoreCell: React.FunctionComponent = ({ playerId, round, playerIndex }) => { - const scores = useAppSelector(state => - selectPlayerById(state, playerId)?.scores - ); - - if (typeof scores == 'undefined') return null; - - const scoreRound = scores[round] || 0; + const scoreRound = useAppSelector(state => selectPlayerScoreByRound(state, playerId, round)); return ( string[]; onLayout?: (event: LayoutChangeEvent, round: number) => void; } @@ -21,13 +21,13 @@ const RoundScoreColumn: React.FunctionComponent = ({ round, isCurrentRound, disabled = false, - sortSelector, onLayout, }) => { const dispatch = useAppDispatch(); - const currentGameId = useAppSelector(state => state.settings.currentGameId); - + const currentGameId = useAppSelector(state => selectCurrentGame(state)?.id); + const sortKey = useAppSelector(state => selectCurrentGame(state)?.sortSelectorKey); + const sortSelector = sortSelectors[sortKey || SortSelectorKey.ByIndex]; const sortedPlayerIds = useAppSelector(sortSelector); const onPressHandler = useCallback(async () => { diff --git a/src/components/ScoreLog/SortHelper.ts b/src/components/ScoreLog/SortHelper.ts index ff8eb4d3..bed4a3f9 100644 --- a/src/components/ScoreLog/SortHelper.ts +++ b/src/components/ScoreLog/SortHelper.ts @@ -4,23 +4,22 @@ import { GameState } from '../../../redux/GamesSlice'; import { ScoreState, selectAllPlayers } from '../../../redux/PlayersSlice'; import { RootState } from '../../../redux/store'; -export const selectPlayerIdsByIndex: SortSelector = createSelector( +export const selectSortedPlayerIdsByIndex = createSelector( [ - selectAllPlayers, (state: RootState) => state.settings.currentGameId ? state.games.entities[state.settings.currentGameId] : undefined, (state: RootState) => state.settings.currentGameId ? state.games.entities[state.settings.currentGameId]?.sortDirectionKey : undefined ], - (players: ScoreState[], currentGame: GameState | undefined) => { - if (!currentGame) return []; + (game: GameState | undefined, sortDirectionKey: SortDirectionKey | undefined) => { + if (!game) return []; - const playerIds = players.filter(player => currentGame.playerIds?.includes(player.id)) + const playerIds = [...game.playerIds] .sort((a, b) => { - if (currentGame?.playerIds == undefined) return 0; - return currentGame.playerIds.indexOf(a.id) - currentGame.playerIds.indexOf(b.id); + if (game?.playerIds == undefined) return 0; + return game.playerIds.indexOf(a) - game.playerIds.indexOf(b); }) - .map(player => player.id); + .map(player => player); - if (SortDirectionKey.Normal === currentGame.sortDirectionKey) { + if (SortDirectionKey.Normal === sortDirectionKey) { return playerIds; } else { return playerIds.reverse(); @@ -28,7 +27,7 @@ export const selectPlayerIdsByIndex: SortSelector = createSelector( } ); -export const selectPlayerIdsByScore: SortSelector = createSelector( +export const selectSortedPlayerIdsByScore: SortSelector = createSelector( [ selectAllPlayers, (state: RootState) => state.settings.currentGameId ? state.games.entities[state.settings.currentGameId] : undefined, @@ -78,6 +77,6 @@ export enum SortDirectionKey { } export const sortSelectors = { - [SortSelectorKey.ByScore]: selectPlayerIdsByScore, - [SortSelectorKey.ByIndex]: selectPlayerIdsByIndex, + [SortSelectorKey.ByScore]: selectSortedPlayerIdsByScore, + [SortSelectorKey.ByIndex]: selectSortedPlayerIdsByIndex, }; diff --git a/src/screens/ShareScreen.tsx b/src/screens/ShareScreen.tsx index 70c2dca4..82e0757c 100644 --- a/src/screens/ShareScreen.tsx +++ b/src/screens/ShareScreen.tsx @@ -9,12 +9,11 @@ import { Button, Icon } from 'react-native-elements'; import { SafeAreaView } from 'react-native-safe-area-context'; import { captureRef } from 'react-native-view-shot'; -import { selectGameById, selectSortSelectorKey } from '../../redux/GamesSlice'; +import { selectGameById } from '../../redux/GamesSlice'; import { useAppSelector } from '../../redux/hooks'; import { selectCurrentGame } from '../../redux/selectors'; import PlayerNameColumn from '../components/ScoreLog/PlayerNameColumn'; import RoundScoreColumn from '../components/ScoreLog/RoundScoreColumn'; -import { sortSelectors } from '../components/ScoreLog/SortHelper'; import TotalScoreColumn from '../components/ScoreLog/TotalScoreColumn'; import { systemBlue } from '../constants'; @@ -54,9 +53,6 @@ const ShareScreen: React.FunctionComponent = ({ navigation }) => { await analytics().logEvent('share_image'); }; - const sortSelectorKey = useAppSelector(state => selectSortSelectorKey(state, currentGameId)); - const sortSelector = sortSelectors[sortSelectorKey]; - return ( @@ -98,12 +94,11 @@ const ShareScreen: React.FunctionComponent = ({ navigation }) => { - - + + {roundsIterator.map((item, round) => (