feat(mobile): implement "Rejoin Game" functionality and fix storage bugs

This commit is contained in:
Halit Aksoy 2026-07-16 02:00:25 +03:00
parent 8a485e2db3
commit 69664796ce
9 changed files with 59 additions and 6 deletions

View File

@ -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);
}
}

View File

@ -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: () => (
<View style={{
@ -219,13 +230,15 @@ export function GameScreen({ navigation, route }: GameScreenProps) {
gap: 5
}}>
<Pressable onPress={() => {
if (isGameEnded) {
if (isError) {
navigation.replace("Home", { context });
} else if (isGameEnded) {
onNewGameClick();
} else {
onLeaveGameClick();
}
}}>
<CircularPanel color={context.themeManager.theme?.background} children={<Text style={{ color: context.themeManager.theme.textColor }}>{isGameEnded ? t('NewGame') : t('Leave')}</Text>} />
<CircularPanel color={context.themeManager.theme?.background} children={<Text style={{ color: context.themeManager.theme.textColor }}>{isError ? t('Home') : (isGameEnded ? t('NewGame') : t('Leave'))}</Text>} />
</Pressable>
<Pressable onPress={() => {
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,

View File

@ -11,6 +11,7 @@ export function HomeScreen({ navigation, route }: HomeScreenProps) {
const { context } = route.params;
const { t } = useTranslation();
const [lastGameId, setLastGameId] = React.useState<string | undefined>(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) {
<Pressable onPress={() => navigation.replace('Loby', { context })}>
<CircularPanel color={context.themeManager.theme?.boardColor} children={<Text style={{ color: context.themeManager.theme.textColor }}>{t('NewGame')}</Text>} />
</Pressable>
{lastGameId && (
<>
<View style={{ height: 20 }} />
<Pressable onPress={async () => {
try {
const userKey = await context.userKeyStore.getUserKey();
navigation.replace('Game', { context, gameId: lastGameId, userKey });
} catch (error) {
context.storage.deleteString('lastGameId');
setLastGameId(undefined);
}
}}>
<CircularPanel color={context.themeManager.theme?.boardColor} children={<Text style={{ color: context.themeManager.theme.textColor }}>{t('RejoinGame')}</Text>} />
</Pressable>
</>
)}
</View>
);
}

View File

@ -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 })
}

View File

@ -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);
}
}

View File

@ -21,7 +21,7 @@ export class GameStoreImpl implements GameStore {
return game;
} catch (error) {
// todo check error
Promise.resolve(undefined);
return undefined;
}
}

View File

@ -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"
}

View File

@ -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"
};

View File

@ -1,4 +1,6 @@
export interface Storage {
getString(key: string): string | undefined;
setString(key: string, value: string): void;
deleteString(key: string): void;
}