From 7eba7930c0305b9e5b844a8e6665315a212432c5 Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Wed, 9 Sep 2026 20:50:12 -0400 Subject: [PATCH 1/3] fix: polish android interactions --- src/components/Buttons/GameOptionsButton.tsx | 63 +++++++++++++------- src/components/Buttons/HeaderButton.tsx | 26 +++++++- src/components/GameListItem.tsx | 23 +++++-- src/components/ScoreLogTable.tsx | 1 + src/components/Sheets/ChooseWinnersSheet.tsx | 2 +- src/components/Sheets/GameSheet.tsx | 2 +- src/components/Sheets/GestureInfoSheet.tsx | 2 +- src/components/Sheets/PointValuesSheet.tsx | 6 +- src/screens/AppSettingsScreen.tsx | 6 +- src/screens/ListScreen.tsx | 1 + 10 files changed, 95 insertions(+), 37 deletions(-) diff --git a/src/components/Buttons/GameOptionsButton.tsx b/src/components/Buttons/GameOptionsButton.tsx index 45fcfd41..3746db39 100644 --- a/src/components/Buttons/GameOptionsButton.tsx +++ b/src/components/Buttons/GameOptionsButton.tsx @@ -168,30 +168,14 @@ const GameOptionsButton: React.FunctionComponent = () => { testID="game-options-menu" > - - - {addendOne} - {addendTwo} - - - {Platform.OS === 'ios' ? ( - - ) : ( - - )} + {Platform.OS === 'android' ? ( + + {showDialDot && ( { }} /> )} - + ) : ( + + + {addendOne} + {addendTwo} + + + + {showDialDot && ( + + )} + + + )} ); @@ -209,7 +220,13 @@ const GameOptionsButton: React.FunctionComponent = () => { const styles = StyleSheet.create({ button: { - padding: 8, + padding: Platform.OS === 'android' ? 0 : 8, + }, + androidOverflowButton: { + width: 48, + height: 48, + alignItems: 'center', + justifyContent: 'center', }, content: { flexDirection: 'row', diff --git a/src/components/Buttons/HeaderButton.tsx b/src/components/Buttons/HeaderButton.tsx index 339747c6..b6852d60 100644 --- a/src/components/Buttons/HeaderButton.tsx +++ b/src/components/Buttons/HeaderButton.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { TouchableOpacity, StyleSheet } from 'react-native'; +import { Platform, Pressable, StyleSheet } from 'react-native'; interface Props { children: React.ReactNode; @@ -10,9 +10,18 @@ interface Props { const HeaderButton: React.FunctionComponent = (props) => { return ( - + [ + styles.headerButton, + Platform.OS === 'android' && styles.androidHeaderButton, + Platform.OS === 'ios' && pressed && styles.iosPressed, + ]} + > {props.children} - + ); }; @@ -22,6 +31,17 @@ const styles = StyleSheet.create({ padding: 10, paddingHorizontal: 15, }, + androidHeaderButton: { + width: 48, + height: 48, + alignItems: 'center', + justifyContent: 'center', + padding: 0, + paddingHorizontal: 0, + }, + iosPressed: { + opacity: 0.45, + }, }); export default HeaderButton; diff --git a/src/components/GameListItem.tsx b/src/components/GameListItem.tsx index 75fee6a3..2348d698 100644 --- a/src/components/GameListItem.tsx +++ b/src/components/GameListItem.tsx @@ -179,12 +179,18 @@ const GameListItem: React.FunctionComponent = ({ navigation, gameId, inde {formatCreated(dateCreated)} - - {playerIds.length} - - - {roundCount} - + + + {playerIds.length} + + + + + + {roundCount} + + + @@ -221,6 +227,11 @@ const styles = StyleSheet.create({ badge: { fontSize: 15, }, + badgeGroup: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, }); export default memo(GameListItem); diff --git a/src/components/ScoreLogTable.tsx b/src/components/ScoreLogTable.tsx index 4f8c3d0a..68b90476 100644 --- a/src/components/ScoreLogTable.tsx +++ b/src/components/ScoreLogTable.tsx @@ -90,6 +90,7 @@ const ScoreLogTable: React.FunctionComponent = ({ showScores = true }) => {roundsIterator.map((item, round) => ( onLayoutHandler(e, round)}> diff --git a/src/components/Sheets/ChooseWinnersSheet.tsx b/src/components/Sheets/ChooseWinnersSheet.tsx index bed93a30..e8dec31b 100644 --- a/src/components/Sheets/ChooseWinnersSheet.tsx +++ b/src/components/Sheets/ChooseWinnersSheet.tsx @@ -171,7 +171,7 @@ const ChooseWinnersSheet: React.FunctionComponent = () => { /> - + Select winners (optional) to end the game. diff --git a/src/components/Sheets/GameSheet.tsx b/src/components/Sheets/GameSheet.tsx index 216bd431..1a646434 100644 --- a/src/components/Sheets/GameSheet.tsx +++ b/src/components/Sheets/GameSheet.tsx @@ -247,7 +247,7 @@ const GameSheet: React.FunctionComponent = () => { accessible={false} accessibilityViewIsModal={false} > - + sheetTitlePress()}> diff --git a/src/components/Sheets/GestureInfoSheet.tsx b/src/components/Sheets/GestureInfoSheet.tsx index 5e9c53f6..2618beca 100644 --- a/src/components/Sheets/GestureInfoSheet.tsx +++ b/src/components/Sheets/GestureInfoSheet.tsx @@ -35,7 +35,7 @@ const GestureInfoSheet: React.FunctionComponent = () => { backgroundStyle={{ backgroundColor: theme.sheetBackground }} handleIndicatorStyle={{ backgroundColor: theme.sheetHandle }} > - + Tap Gesture Tap the top half of a player's tile to increase their score, or the bottom half diff --git a/src/components/Sheets/PointValuesSheet.tsx b/src/components/Sheets/PointValuesSheet.tsx index 88439cbb..1b05a6b7 100644 --- a/src/components/Sheets/PointValuesSheet.tsx +++ b/src/components/Sheets/PointValuesSheet.tsx @@ -124,7 +124,11 @@ const PointValuesSheet: React.FunctionComponent = () => { backdropComponent={renderBackdrop} backgroundStyle={{ backgroundColor: theme.sheetBackground }} handleIndicatorStyle={{ backgroundColor: theme.sheetHandle }}> - + Point Values diff --git a/src/screens/AppSettingsScreen.tsx b/src/screens/AppSettingsScreen.tsx index 1e6e1264..604f39be 100644 --- a/src/screens/AppSettingsScreen.tsx +++ b/src/screens/AppSettingsScreen.tsx @@ -163,7 +163,11 @@ const AppSettingsScreen: React.FunctionComponent = ({ navigation }) => { }; return ( - + diff --git a/src/screens/ListScreen.tsx b/src/screens/ListScreen.tsx index 45e65a48..66fe602a 100644 --- a/src/screens/ListScreen.tsx +++ b/src/screens/ListScreen.tsx @@ -101,6 +101,7 @@ const ListScreen: React.FunctionComponent = ({ navigation }) => { Tap the + button to create a new game. } + overScrollMode="always" style={[styles.list, { backgroundColor: theme.backgroundSecondary }]} data={gameIds} renderItem={({ item, index }) => From 835cb67ce5e7d6a3cfcf0042a24b9369037e4836 Mon Sep 17 00:00:00 2001 From: Justin Wyne <1986068+wyne@users.noreply.github.com> Date: Wed, 9 Sep 2026 21:30:13 -0400 Subject: [PATCH 2/3] fix: enable edit player list overscroll --- src/screens/EditGameScreen.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/screens/EditGameScreen.tsx b/src/screens/EditGameScreen.tsx index 605ed850..ea49023e 100644 --- a/src/screens/EditGameScreen.tsx +++ b/src/screens/EditGameScreen.tsx @@ -119,6 +119,7 @@ const EditGameScreen: React.FunctionComponent = ({ navigation, route }) = containerStyle={{ flex: 1 }} ListFooterComponent={ListFooter} data={playerIds} + overScrollMode="always" renderItem={({ item: playerId, getIndex, drag, isActive }) => ( Date: Wed, 9 Sep 2026 21:37:21 -0400 Subject: [PATCH 3/3] fix: cycle game sheet from handle tap --- src/components/Sheets/GameSheet.test.tsx | 29 ++++++++++++++++++++++++ src/components/Sheets/GameSheet.tsx | 20 ++++++++++++---- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/src/components/Sheets/GameSheet.test.tsx b/src/components/Sheets/GameSheet.test.tsx index 12a4a674..a3829fff 100644 --- a/src/components/Sheets/GameSheet.test.tsx +++ b/src/components/Sheets/GameSheet.test.tsx @@ -29,6 +29,7 @@ jest.mock('@gorhom/bottom-sheet', () => { index: number; backdropComponent?: React.ComponentType; backgroundStyle?: object; + handleComponent?: React.ComponentType; handleIndicatorStyle?: object; animatedPosition?: object; enablePanDownToClose?: boolean; @@ -42,6 +43,7 @@ jest.mock('@gorhom/bottom-sheet', () => { return ( + {props.handleComponent ? : null} {props.children} ); @@ -63,6 +65,7 @@ jest.mock('@gorhom/bottom-sheet', () => { return { __esModule: true, default: MockBottomSheet, + BottomSheetHandle: () => , BottomSheetScrollView: MockBottomSheetScrollView, BottomSheetBackdrop: MockBottomSheetBackdrop, }; @@ -292,6 +295,32 @@ describe('GameSheet', () => { expect(getByTestId('score-log-table')).toBeTruthy(); }); + it('renders a tappable sheet handle', () => { + const store = createMockStore({ + settings: { + currentGameId: 'game-1', + }, + games: { + entities: { + 'game-1': mockGame, + }, + ids: ['game-1'], + }, + players: { + entities: mockPlayers, + ids: ['player-1', 'player-2'], + }, + }); + + const { getByTestId } = render( + + + + ); + + expect(getByTestId('game-sheet-handle')).toBeTruthy(); + }); + it('should allow full expansion up to the top safe area', () => { const store = createMockStore({ settings: { diff --git a/src/components/Sheets/GameSheet.tsx b/src/components/Sheets/GameSheet.tsx index 1a646434..71cc1690 100644 --- a/src/components/Sheets/GameSheet.tsx +++ b/src/components/Sheets/GameSheet.tsx @@ -1,9 +1,9 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import BottomSheet, { BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetScrollView } from '@gorhom/bottom-sheet'; +import BottomSheet, { BottomSheetBackdrop, BottomSheetBackdropProps, BottomSheetHandle, BottomSheetHandleProps, BottomSheetScrollView } from '@gorhom/bottom-sheet'; import { ParamListBase, useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { Alert, StyleSheet, Text, TouchableWithoutFeedback, View, useWindowDimensions } from 'react-native'; +import { Alert, StyleSheet, Text, TouchableOpacity, TouchableWithoutFeedback, View, useWindowDimensions } from 'react-native'; import { Button } from 'react-native-elements'; import Animated, { Extrapolate, FadeIn, interpolate, Layout, useAnimatedStyle, useSharedValue } from 'react-native-reanimated'; import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -180,13 +180,13 @@ const GameSheet: React.FunctionComponent = () => { /** * Function to snap to the next point when the handle is pressed */ - const sheetTitlePress = () => { + const sheetTitlePress = useCallback(() => { setSnapPointIndex((prevIndex) => { const nextIndex = (prevIndex + 1) % snapPoints.length; gameSheetRef?.current?.snapToIndex(nextIndex); return nextIndex; }); - }; + }, [gameSheetRef, snapPoints.length]); /** * Animated position of the bottom sheet @@ -228,6 +228,17 @@ const GameSheet: React.FunctionComponent = () => { [] ); + const renderHandle = useCallback( + (props: BottomSheetHandleProps) => ( + + + + + + ), + [sheetTitlePress, theme.sheetHandle] + ); + if (currentGameId == undefined) return null; return ( @@ -239,6 +250,7 @@ const GameSheet: React.FunctionComponent = () => { snapPoints={snapPoints} backdropComponent={renderBackdrop} backgroundStyle={{ backgroundColor: theme.sheetBackground }} + handleComponent={renderHandle} handleIndicatorStyle={{ backgroundColor: theme.sheetHandle }} animatedPosition={animatedPosition} enablePanDownToClose={false}