Skip to content

Commit

Permalink
Android Menu
Browse files Browse the repository at this point in the history
  • Loading branch information
wyne committed Dec 25, 2023
1 parent 67c8ca2 commit 801e900
Show file tree
Hide file tree
Showing 7 changed files with 284 additions and 137 deletions.
21 changes: 12 additions & 9 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import analytics from '@react-native-firebase/analytics';
import { StatusBar } from 'expo-status-bar';
import { View } from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { MenuProvider } from 'react-native-popup-menu';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';

import { store, persistor } from './redux/store';
import { persistor, store } from './redux/store';
import { AddendModalContextProvider } from './src/components/Sheets/AddendModalContext';
import { GameSheetContextProvider } from './src/components/Sheets/GameSheetContext';
import { Navigation } from './src/Navigation';
Expand All @@ -21,14 +22,16 @@ export default function App() {
<View style={{ flex: 1, backgroundColor: '#fff' }}>
<GestureHandlerRootView style={{ flex: 1 }}>
<GameSheetContextProvider>
<AddendModalContextProvider>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<StatusBar />
<Navigation />
</PersistGate>
</Provider>
</AddendModalContextProvider>
<MenuProvider>
<AddendModalContextProvider>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<StatusBar />
<Navigation />
</PersistGate>
</Provider>
</AddendModalContextProvider>
</MenuProvider>
</GameSheetContextProvider>
</GestureHandlerRootView>
</View>
Expand Down
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
"react-native-onboarding-swiper": "^1.2.0",
"react-native-pager-view": "6.2.0",
"react-native-picker-select": "^8.0.4",
"react-native-popup-menu": "^0.16.1",
"react-native-reanimated": "~3.3.0",
"react-native-safe-area-context": "4.6.3",
"react-native-screens": "~3.22.0",
Expand Down
142 changes: 14 additions & 128 deletions src/components/GameListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import React from 'react';

import analytics from '@react-native-firebase/analytics';
import { MenuView, MenuAction, NativeActionEvent } from '@react-native-menu/menu';
import { ParamListBase } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit';
import Moment from 'react-moment';
import { Text, StyleSheet, Alert, Platform } from 'react-native';
import { ListItem, Icon } from 'react-native-elements';
import { StyleSheet, Text } from 'react-native';
import { Icon, ListItem } from 'react-native-elements';
import Animated, { FadeInUp, SlideOutLeft } from 'react-native-reanimated';

import { selectGameById, gameDelete } from '../../redux/GamesSlice';
import { selectGameById } from '../../redux/GamesSlice';
import { useAppDispatch, useAppSelector } from '../../redux/hooks';
import { setCurrentGameId } from '../../redux/SettingsSlice';


import GameListItemPlayerName from './GameListItemPlayerName';
import AbstractPopupMenu from './PopupMenu/AbstractPopupMenu';

export type Props = {
navigation: NativeStackNavigationProp<ParamListBase, string, undefined>;
Expand Down Expand Up @@ -55,132 +55,18 @@ const GameListItem: React.FunctionComponent<Props> = ({ navigation, gameId, inde
});
};

/**
* Share Game
*/
const shareGameHandler = async () => {
asyncSetCurrentGame(dispatch).then(() => {
navigation.navigate("Share");
});

await analytics().logEvent('menu_share', {
round_count: roundTotal,
player_count: playerIds.length,
});
};

/**
* Edit Game
*/
const editGameHandler = async () => {
asyncSetCurrentGame(dispatch).then(() => {
navigation.navigate("Settings", { reason: 'edit_game' });
});

await analytics().logEvent('menu_edit', {
round_count: roundTotal,
player_count: playerIds.length,
});
};

/**
* Delete Game
*/
const deleteGameHandler = async () => {
Alert.alert(
'Delete Game',
`Are you sure you want to delete ${gameTitle}?`,
[
{
text: 'Cancel',
onPress: () => { },
style: 'cancel',
},
{
text: 'OK',
onPress: () => {
dispatch(gameDelete(gameId));
}
},
],
{ cancelable: false },
);

await analytics().logEvent('delete_game', {
index: index,
round_count: roundTotal,
player_count: playerIds.length,
});
};

/**
* Menu Actions for long press
*/
const actions: MenuAction[] = [
{
id: 'edit',
title: 'Edit',
image: Platform.select({
ios: 'pencil',
android: 'ic_menu_edit',
}),
},
{
id: 'share',
title: 'Share',
image: Platform.select({
ios: 'square.and.arrow.up',
android: 'ic_menu_share',
}),
},
{
id: 'delete',
title: `Delete`,
attributes: {
destructive: true,
},
image: Platform.select({
ios: 'trash',
android: 'ic_menu_delete',
}),
},
];

// If platform is android, remove the action with id 'share'
if (Platform.OS === 'android') {
actions.splice(1, 1);
}

type MenuActionHandler = (eativeEvent: NativeActionEvent) => void;

/**
* Menu Action Handler - handles long press actions for games
* @param nativeEvent
* @returns void
*/
const menuActionHandler: MenuActionHandler = async ({ nativeEvent }) => {
switch (nativeEvent.event) {
case 'edit':
editGameHandler();
break;
case 'share':
shareGameHandler();
break;
case 'delete':
deleteGameHandler();
break;
}
};

return (
<Animated.View entering={FadeInUp.duration(200).delay(100 + index * 100)}
exiting={SlideOutLeft.duration(200)}>
<MenuView
title={gameTitle}
shouldOpenOnLongPress={true}
onPressAction={menuActionHandler}
actions={actions}>
<ListItem key={gameId} bottomDivider onPress={chooseGameHandler}>
<AbstractPopupMenu
gameId={gameId}
asyncSetCurrentGame={asyncSetCurrentGame}
chooseGameHandler={chooseGameHandler}
navigation={navigation}
index={index}
>
{/* <ListItem key={gameId} bottomDivider onPress={chooseGameHandler}> */}
<ListItem key={gameId} bottomDivider>
<ListItem.Content>
<ListItem.Title style={{ alignItems: 'center' }}>
{gameTitle}
Expand All @@ -203,7 +89,7 @@ const GameListItem: React.FunctionComponent<Props> = ({ navigation, gameId, inde
</Text>
<ListItem.Chevron />
</ListItem>
</MenuView>
</AbstractPopupMenu>
</Animated.View>
);
};
Expand Down
116 changes: 116 additions & 0 deletions src/components/PopupMenu/AbstractPopupMenu.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import React from 'react';

import analytics from '@react-native-firebase/analytics';
import { ParamListBase } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit';
import { Alert, Platform } from 'react-native';

import { gameDelete, selectGameById } from '../../../redux/GamesSlice';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';

import AndroidPopupMenu from './AndroidPopupMenu';
import IOSPopupMenu from './IOSPopupMenu';

interface Props {
children: React.ReactNode;
gameId: string;
navigation: NativeStackNavigationProp<ParamListBase, string, undefined>;
asyncSetCurrentGame: (dispatch: ThunkDispatch<unknown, undefined, AnyAction>) => Promise<void>;
chooseGameHandler: () => void;
index: number;
}

const AbstractPopupMenu: React.FC<Props> = (props) => {
const dispatch = useAppDispatch();

if (props.gameId == null) { return null; }
const gameTitle = useAppSelector(state => selectGameById(state, props.gameId)?.title);
const roundTotal = useAppSelector(state => selectGameById(state, props.gameId)?.roundTotal);
const playerIds = useAppSelector(state => selectGameById(state, props.gameId)?.playerIds);
if (roundTotal == null || playerIds == null) { return null; }

/**
* Share Game
*/
const shareGameHandler = async () => {
props.asyncSetCurrentGame(dispatch).then(() => {
props.navigation.navigate("Share");
});

await analytics().logEvent('menu_share', {
round_count: roundTotal,
player_count: playerIds.length,
});
};

/**
* Edit Game
*/
const editGameHandler = async () => {
props.asyncSetCurrentGame(dispatch).then(() => {
props.navigation.navigate("Settings", { reason: 'edit_game' });
});

await analytics().logEvent('menu_edit', {
round_count: roundTotal,
player_count: playerIds.length,
});
};

/**
* Delete Game
*/
const deleteGameHandler = async () => {
Alert.alert(
'Delete Game',
`Are you sure you want to delete ${gameTitle}?`,
[
{
text: 'Cancel',
onPress: () => { },
style: 'cancel',
},
{
text: 'OK',
onPress: () => {
dispatch(gameDelete(props.gameId));
}
},
],
{ cancelable: false },
);

await analytics().logEvent('delete_game', {
index: props.index,
round_count: roundTotal,
player_count: playerIds.length,
});
};

return Platform.select({
ios: (
<IOSPopupMenu
gameTitle={gameTitle}
editGameHandler={editGameHandler}
shareGameHandler={shareGameHandler}
deleteGameHandler={deleteGameHandler}
>
{props.children}
</IOSPopupMenu>
),
android: (
<AndroidPopupMenu
gameTitle={gameTitle}
chooseGameHandler={props.chooseGameHandler}
editGameHandler={editGameHandler}
shareGameHandler={shareGameHandler}
deleteGameHandler={deleteGameHandler}
>
{props.children}
</AndroidPopupMenu>
),
});
};

export default AbstractPopupMenu;
Loading

0 comments on commit 801e900

Please sign in to comment.