From 69664796ce68388d7e1679dd1f0dcaf9083ee79a Mon Sep 17 00:00:00 2001 From: Halit Aksoy Date: Thu, 16 Jul 2026 02:00:25 +0300 Subject: [PATCH] feat(mobile): implement "Rejoin Game" functionality and fix storage bugs --- apps/frontend/src/storage/index.ts | 6 ++++++ apps/mobile/src/screens/GameScreen.tsx | 19 ++++++++++++++++--- apps/mobile/src/screens/HomeScreen.tsx | 21 +++++++++++++++++++++ apps/mobile/src/screens/LobyScreen.tsx | 1 + apps/mobile/src/storage/index.ts | 6 ++++++ apps/mobile/src/store/GameStore.ts | 2 +- packages/core/src/localization/en.ts | 4 +++- packages/core/src/localization/tr.ts | 4 +++- packages/core/src/storage/storage.ts | 2 ++ 9 files changed, 59 insertions(+), 6 deletions(-) diff --git a/apps/frontend/src/storage/index.ts b/apps/frontend/src/storage/index.ts index f2e0a08..800c932 100644 --- a/apps/frontend/src/storage/index.ts +++ b/apps/frontend/src/storage/index.ts @@ -4,4 +4,10 @@ export class LocalStorage implements Storage { getString(key: string): string | undefined { return localStorage.getItem(key) as string; } + setString(key: string, value: string): void { + localStorage.setItem(key, value); + } + deleteString(key: string): void { + localStorage.removeItem(key); + } } \ No newline at end of file diff --git a/apps/mobile/src/screens/GameScreen.tsx b/apps/mobile/src/screens/GameScreen.tsx index bd61373..b7cfa02 100644 --- a/apps/mobile/src/screens/GameScreen.tsx +++ b/apps/mobile/src/screens/GameScreen.tsx @@ -55,6 +55,9 @@ export function GameScreen({ navigation, route }: GameScreenProps) { pitAnimator.setUpdatedGame(newGame); setHasOngoingAction(false); setGameUsersConnectionInfo(newGame.gameUsersConnectionInfo); + if (newGame.mancalaGame.state === 'ended') { + context.storage.deleteString('lastGameId'); + } } const isUserOnline = (userId: string) => { @@ -81,6 +84,7 @@ export function GameScreen({ navigation, route }: GameScreenProps) { const userKeyWhoLeave = message; setUserKeyWhoLeave(userKeyWhoLeave); setHasOngoingAction(false); + context.storage.deleteString('lastGameId'); }; const onUserConnectionChange = (message: any) => { const gameUsersConnectionInfo = message as GameUsersConnectionInfo; @@ -131,6 +135,7 @@ export function GameScreen({ navigation, route }: GameScreenProps) { text: t('Yes'), onPress: () => { context.rtmt.sendMessage(channel_leave_game, {}); + context.storage.deleteString('lastGameId'); updateHeaderButton(); }, style: 'default', @@ -144,6 +149,7 @@ export function GameScreen({ navigation, route }: GameScreenProps) { const onNewGameClick = () => { if (Util.checkConnectionAndMaybeAlert(context, t("ConnectionLost"))) return; + context.storage.deleteString('lastGameId'); navigation.replace("Loby", { context }) }; @@ -193,12 +199,16 @@ export function GameScreen({ navigation, route }: GameScreenProps) { setLoadingStateGame(LoadingState.Loading()) context.gameStore.get(gameId!!).then((game) => { if (game) { + if (game.mancalaGame.state !== 'ended') { + context.storage.setString('lastGameId', game.id); + } pitAnimator = new PitAnimator(context.themeManager, updateBoardViewModel); setPitAnimator(pitAnimator); onGameUpdate(pitAnimator, game); unlistenMessages = listenMessages(game, pitAnimator); setLoadingStateGame(LoadingState.Loaded({ value: game })) } else { + context.storage.deleteString('lastGameId'); setLoadingStateGame(LoadingState.Error({ errorMessage: t('GameNotFound') })) } }) @@ -210,6 +220,7 @@ export function GameScreen({ navigation, route }: GameScreenProps) { const updateHeaderButton = () => { const isGameEnded = game?.mancalaGame.state === "ended" || leftPlayer; + const isError = gameLoadingState.isError(); navigation.setOptions({ headerRight: () => ( { - if (isGameEnded) { + if (isError) { + navigation.replace("Home", { context }); + } else if (isGameEnded) { onNewGameClick(); } else { onLeaveGameClick(); } }}> - {isGameEnded ? t('NewGame') : t('Leave')}} /> + {isError ? t('Home') : (isGameEnded ? t('NewGame') : t('Leave'))}} /> { navigation.push("Help", { context }) @@ -240,7 +253,7 @@ export function GameScreen({ navigation, route }: GameScreenProps) { React.useEffect(() => { updateHeaderButton(); - }, [navigation, game, userKeyWhoLeave]); + }, [navigation, game, userKeyWhoLeave, gameLoadingState]); const textColorOnAppBar = getColorByBrightness( context.themeManager.theme.appBarBgColor, diff --git a/apps/mobile/src/screens/HomeScreen.tsx b/apps/mobile/src/screens/HomeScreen.tsx index fc74379..aa7757b 100644 --- a/apps/mobile/src/screens/HomeScreen.tsx +++ b/apps/mobile/src/screens/HomeScreen.tsx @@ -11,6 +11,7 @@ export function HomeScreen({ navigation, route }: HomeScreenProps) { const { context } = route.params; const { t } = useTranslation(); + const [lastGameId, setLastGameId] = React.useState(undefined); const textColorOnAppBar = getColorByBrightness( context.themeManager.theme.appBarBgColor, @@ -32,6 +33,10 @@ export function HomeScreen({ navigation, route }: HomeScreenProps) { React.useEffect(() => { updateHeaderButton(); + const savedGameId = context.storage.getString('lastGameId'); + if (savedGameId) { + setLastGameId(savedGameId); + } }, []); return ( @@ -39,6 +44,22 @@ export function HomeScreen({ navigation, route }: HomeScreenProps) { navigation.replace('Loby', { context })}> {t('NewGame')}} /> + {lastGameId && ( + <> + + { + try { + const userKey = await context.userKeyStore.getUserKey(); + navigation.replace('Game', { context, gameId: lastGameId, userKey }); + } catch (error) { + context.storage.deleteString('lastGameId'); + setLastGameId(undefined); + } + }}> + {t('RejoinGame')}} /> + + + )} ); } diff --git a/apps/mobile/src/screens/LobyScreen.tsx b/apps/mobile/src/screens/LobyScreen.tsx index 83faa6c..7c5bf78 100644 --- a/apps/mobile/src/screens/LobyScreen.tsx +++ b/apps/mobile/src/screens/LobyScreen.tsx @@ -17,6 +17,7 @@ export default function LobyScreen({ navigation, route }: LobyScreenProps) { const onGameStart = async (message: Object) => { const newGame: CommonMancalaGame = message as CommonMancalaGame; + context.storage.setString('lastGameId', newGame.id); const userKey = await context.userKeyStore.getUserKey(); navigation.replace("Game", { context, gameId: newGame.id, userKey }) } diff --git a/apps/mobile/src/storage/index.ts b/apps/mobile/src/storage/index.ts index dab606c..b266919 100644 --- a/apps/mobile/src/storage/index.ts +++ b/apps/mobile/src/storage/index.ts @@ -8,4 +8,10 @@ export class LocalStorage implements Storage { getString(key: string): string | undefined { return storage.getString(key); } + setString(key: string, value: string): void { + storage.set(key, value); + } + deleteString(key: string): void { + storage.remove(key); + } } diff --git a/apps/mobile/src/store/GameStore.ts b/apps/mobile/src/store/GameStore.ts index d4e62b0..32282ce 100644 --- a/apps/mobile/src/store/GameStore.ts +++ b/apps/mobile/src/store/GameStore.ts @@ -21,7 +21,7 @@ export class GameStoreImpl implements GameStore { return game; } catch (error) { // todo check error - Promise.resolve(undefined); + return undefined; } } diff --git a/packages/core/src/localization/en.ts b/packages/core/src/localization/en.ts index e60a6e6..cce07cd 100644 --- a/packages/core/src/localization/en.ts +++ b/packages/core/src/localization/en.ts @@ -36,5 +36,7 @@ export const en = { Cancel: "Cancel", Help: "Help", WebApp: "Goto WebApp", - Privacy: "Open Privacy Policy" + Privacy: "Open Privacy Policy", + Home: "Home", + RejoinGame: "Rejoin Game" } \ No newline at end of file diff --git a/packages/core/src/localization/tr.ts b/packages/core/src/localization/tr.ts index 8769fe9..5b8ce70 100644 --- a/packages/core/src/localization/tr.ts +++ b/packages/core/src/localization/tr.ts @@ -36,5 +36,7 @@ export const tr = { Cancel: "İptal", Help: "Yardım", WebApp: "Web Uygulamasına Git", - Privacy: "Gizlilik Politikasını Göster" + Privacy: "Gizlilik Politikasını Göster", + Home: "Ana Sayfa", + RejoinGame: "Oyuna Geri Dön" }; \ No newline at end of file diff --git a/packages/core/src/storage/storage.ts b/packages/core/src/storage/storage.ts index 992e069..25547f5 100644 --- a/packages/core/src/storage/storage.ts +++ b/packages/core/src/storage/storage.ts @@ -1,4 +1,6 @@ export interface Storage { getString(key: string): string | undefined; + setString(key: string, value: string): void; + deleteString(key: string): void; } \ No newline at end of file