[mobile] feature: add context, http service, store, storage
This commit is contained in:
parent
c330c9d4f5
commit
341fe0e083
@ -13,11 +13,14 @@
|
|||||||
"@react-navigation/native": "^6.1.17",
|
"@react-navigation/native": "^6.1.17",
|
||||||
"@react-navigation/native-stack": "^6.9.26",
|
"@react-navigation/native-stack": "^6.9.26",
|
||||||
"i18next": "^23.10.1",
|
"i18next": "^23.10.1",
|
||||||
|
"mancala.js": "^0.0.2-beta.3",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-i18next": "^14.1.0",
|
"react-i18next": "^14.1.0",
|
||||||
"react-native": "0.73.6",
|
"react-native": "0.73.6",
|
||||||
|
"react-native-mmkv": "^2.12.2",
|
||||||
"react-native-safe-area-context": "^4.9.0",
|
"react-native-safe-area-context": "^4.9.0",
|
||||||
"react-native-screens": "^3.29.0",
|
"react-native-screens": "^3.29.0",
|
||||||
|
"react-native-snackbar": "^2.6.2",
|
||||||
"tiny-emitter": "^2.1.0"
|
"tiny-emitter": "^2.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@ -1,19 +1,80 @@
|
|||||||
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
import { NavigationContainer, Theme } from '@react-navigation/native';
|
||||||
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
||||||
import { RootStackParamList } from './types';
|
import { RootStackParamList } from './types';
|
||||||
import { HomeScreen } from './screens/HomeScreen';
|
import { HomeScreen } from './screens/HomeScreen';
|
||||||
import LobyScreen from './screens/LobyScreen';
|
import LobyScreen from './screens/LobyScreen';
|
||||||
|
import { initContext } from './context/context';
|
||||||
|
import { GameScreen } from './screens/GameScreen';
|
||||||
|
import { RTMTWS } from './rtmt/rtmt_websocket';
|
||||||
|
import { ConnectionState } from './rtmt/rtmt';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import Snackbar from 'react-native-snackbar';
|
||||||
|
|
||||||
const Stack = createNativeStackNavigator<RootStackParamList>();
|
const Stack = createNativeStackNavigator<RootStackParamList>();
|
||||||
|
|
||||||
|
const context = initContext();
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const [userKey, setUserKey] = React.useState<string | undefined>(undefined);
|
||||||
|
|
||||||
|
const [connectionState, setConnetionState] = React.useState<ConnectionState>("connecting");
|
||||||
|
|
||||||
|
//@ts-ignore
|
||||||
|
const [theme, setTheme] = React.useState<Theme>(context.themeManager.theme);
|
||||||
|
|
||||||
|
const onConnectionError = (event: Event) => console.error("(RTMT) Connection Error: ", event);
|
||||||
|
|
||||||
|
const onConnectionChange = (_connectionState: ConnectionState) => setConnetionState(_connectionState);
|
||||||
|
|
||||||
|
const onThemeChange = (theme: Theme) => setTheme(theme);
|
||||||
|
|
||||||
|
const connectRTMT = (userKey: string) => {
|
||||||
|
const rtmt = context.rtmt as RTMTWS;
|
||||||
|
rtmt.on("error", onConnectionError);
|
||||||
|
rtmt.on("connectionchange", onConnectionChange)
|
||||||
|
rtmt.connectWebSocket(userKey)
|
||||||
|
return rtmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadUserKeyAndConnectServer = () => {
|
||||||
|
context.userKeyStore.getUserKey().then((userKey: string) => {
|
||||||
|
setUserKey(userKey);
|
||||||
|
connectRTMT(userKey);
|
||||||
|
}).catch((error) => {
|
||||||
|
//TODO: check if it is network error!
|
||||||
|
Snackbar.show({text: t("ErrorWhenRetrievingInformation")})
|
||||||
|
console.error(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const disposeApp = () => {
|
||||||
|
context.rtmt?.dispose();
|
||||||
|
context.themeManager.off("themechange", onThemeChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
loadUserKeyAndConnectServer();
|
||||||
|
context.themeManager.on("themechange", onThemeChange);
|
||||||
|
return () => disposeApp();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
//const textColorOnBoard = getColorByBrightness(
|
||||||
|
// context.themeManager.theme.boardColor,
|
||||||
|
// context.themeManager.theme.textColor,
|
||||||
|
// context.themeManager.theme.textLightColor
|
||||||
|
//);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavigationContainer>
|
<NavigationContainer>
|
||||||
<Stack.Navigator>
|
<Stack.Navigator>
|
||||||
<Stack.Screen name="Home" component={HomeScreen} />
|
<Stack.Screen name="Home" component={HomeScreen} initialParams={{context}} />
|
||||||
<Stack.Screen name="Loby" component={LobyScreen} />
|
<Stack.Screen name="Loby" component={LobyScreen} />
|
||||||
|
<Stack.Screen name="Game" component={GameScreen} />
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
28
mobile/src/context/context.tsx
Normal file
28
mobile/src/context/context.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { server } from "../const/config";
|
||||||
|
import { RTMT } from "../rtmt/rtmt";
|
||||||
|
import { RTMTWS } from "../rtmt/rtmt_websocket";
|
||||||
|
import { HttpServiceImpl } from "../service/HttpService";
|
||||||
|
import { GameStore, GameStoreImpl } from "../store/GameStore";
|
||||||
|
import { UserKeyStore, UserKeyStoreImpl } from "../store/KeyStore";
|
||||||
|
import ThemeManager from "../theme/ThemeManager";
|
||||||
|
|
||||||
|
export type Context = {
|
||||||
|
rtmt: RTMT;
|
||||||
|
userKeyStore: UserKeyStore;
|
||||||
|
themeManager: ThemeManager;
|
||||||
|
gameStore: GameStore;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const initContext = (): Context => {
|
||||||
|
const rtmt = new RTMTWS();
|
||||||
|
const httpService = new HttpServiceImpl(server.serverAdress);
|
||||||
|
const userKeyStore = new UserKeyStoreImpl({ httpService });
|
||||||
|
const gameStore = new GameStoreImpl({ httpService });
|
||||||
|
const themeManager = new ThemeManager();
|
||||||
|
return {
|
||||||
|
rtmt: rtmt,
|
||||||
|
userKeyStore: userKeyStore,
|
||||||
|
themeManager,
|
||||||
|
gameStore
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -1,6 +1,39 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
newGame: "New Game",
|
Mancala: "Mancala",
|
||||||
searchingOppenent: 'Please wait, searching opponent...',
|
Leave: "Leave The Game",
|
||||||
|
NewGame: "New Game",
|
||||||
|
YourTurn: "Your Turn",
|
||||||
|
OpponentTurn: "Opponent Turn",
|
||||||
|
GameEnded: "Game Ended",
|
||||||
|
InternalErrorOccurred: "An internal error has occurred",
|
||||||
|
YouWon: "You Won",
|
||||||
|
Won: "Won",
|
||||||
|
YouLost: "You Lost",
|
||||||
|
Connecting: "Connecting",
|
||||||
|
Connected: "Connected",
|
||||||
|
CannotConnect: "Can't Connect",
|
||||||
|
ConnectionLost: "Network Connection Lost",
|
||||||
|
ConnectingAgain: "Connecting Again",
|
||||||
|
ServerError: "Server Error",
|
||||||
|
SearchingOpponet: "Searching Opponet",
|
||||||
|
OpponentLeftTheGame: "Opponent Leaves The Game",
|
||||||
|
YouLeftTheGame: "You Left The Game",
|
||||||
|
UserLeftTheGame: "Left The Game",
|
||||||
|
SearchingOpponent: "Searching Opponent",
|
||||||
|
PleaseWait: "Please Wait",
|
||||||
|
GameDraw: "Draw",
|
||||||
|
Anonymous: "Anonymous",
|
||||||
|
GameNotFound: "Game Not Found",
|
||||||
|
Loading: "Loading",
|
||||||
|
Playing: "Playing",
|
||||||
|
Error: "Error",
|
||||||
|
ErrorWhenRetrievingInformation: "An error occured when retrieving information!",
|
||||||
|
UCanOnlyPlayYourOwnPits: "You can only play your own pits",
|
||||||
|
UMustWaitUntilCurrentMoveComplete: "You must wait until the current move is complete",
|
||||||
|
UCanNotPlayEmptyPit: "You can not play empty pit",
|
||||||
|
AreYouSureToLeaveGame: "Are you sure to leave game?",
|
||||||
|
Yes: "Yes",
|
||||||
|
Cancel: "Cancel",
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -1,6 +1,39 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
newGame: "Yeni Oyun",
|
Mancala: "Köçürme",
|
||||||
searchingOppenent: 'Lütfen bekleyin, rakip bulunuyor...',
|
Leave: "Oyundan Ayrıl",
|
||||||
|
NewGame: "Yeni Oyun",
|
||||||
|
YourTurn: "Sıra Sende",
|
||||||
|
OpponentTurn: "Sıra Rakipte",
|
||||||
|
GameEnded: "Oyun Bitti",
|
||||||
|
InternalErrorOccurred: "Dahili bir hata oluştu",
|
||||||
|
YouWon: "Kazandın",
|
||||||
|
Won: "Kazandı",
|
||||||
|
YouLost: "Kaybettin",
|
||||||
|
Connecting: "Bağlanılıyor",
|
||||||
|
Connected: "Bağlandı",
|
||||||
|
CannotConnect: "Bağlanılamadı",
|
||||||
|
ConnectionLost: "Ağ Bağlantısı Koptu",
|
||||||
|
ConnectingAgain: "Tekrar Bağlanılıyor",
|
||||||
|
ServerError: "Sunucu Hatası",
|
||||||
|
SearchingOpponet: "Rakip Aranıyor",
|
||||||
|
OpponentLeftTheGame: "Rakip Oyundan Ayrıldı",
|
||||||
|
YouLeftTheGame: "Sen Oyundan Ayrıldın",
|
||||||
|
UserLeftTheGame: "Oyundan Ayrıldı",
|
||||||
|
SearchingOpponent: "Rakip Aranıyor",
|
||||||
|
PleaseWait: "Lütfen Bekleyin",
|
||||||
|
GameDraw: "Berabere",
|
||||||
|
Anonymous: "Anonim",
|
||||||
|
GameNotFound: "Oyun Bulunamadı",
|
||||||
|
Loading: "Yükleniyor",
|
||||||
|
Playing: "Oynuyor",
|
||||||
|
Error: "Hata",
|
||||||
|
ErrorWhenRetrievingInformation: "Bilgiler toplanırken bir hata oluştu!",
|
||||||
|
UCanOnlyPlayYourOwnPits: "Sadece sana ait olan kuyular ile oynayabilirsin",
|
||||||
|
UMustWaitUntilCurrentMoveComplete: "Devam eden haraketin bitmesini beklemelisin",
|
||||||
|
UCanNotPlayEmptyPit: "Boş kuyu ile oynayamazsın",
|
||||||
|
AreYouSureToLeaveGame: "Oyundan ayrılmak istediğine emin misin?",
|
||||||
|
Yes: "Evet",
|
||||||
|
Cancel: "İptal"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
13
mobile/src/models/Game.ts
Normal file
13
mobile/src/models/Game.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { MancalaGame } from "mancala.js";
|
||||||
|
import { UserConnectionInfo } from "./UserConnectionInfo";
|
||||||
|
|
||||||
|
export interface Game {
|
||||||
|
id: string;
|
||||||
|
mancalaGame: MancalaGame;
|
||||||
|
gameUsersConnectionInfo: GameUsersConnectionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameUsersConnectionInfo {
|
||||||
|
user1ConnectionInfo: UserConnectionInfo;
|
||||||
|
user2ConnectionInfo: UserConnectionInfo;
|
||||||
|
}
|
||||||
3
mobile/src/models/GameMove.ts
Normal file
3
mobile/src/models/GameMove.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface GameMove {
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
46
mobile/src/models/LoadingState.tsx
Normal file
46
mobile/src/models/LoadingState.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
export type LoadingStateType = "unset" | "loading" | "loaded" | "error";
|
||||||
|
|
||||||
|
export class LoadingState<T> {
|
||||||
|
state: LoadingStateType;
|
||||||
|
|
||||||
|
errorMessage?: string;
|
||||||
|
|
||||||
|
value?: T;
|
||||||
|
|
||||||
|
constructor(props: { state?: LoadingStateType, errorMessage?: string, value?: T }) {
|
||||||
|
this.state = props.state ? props.state : "unset";
|
||||||
|
this.errorMessage = props.errorMessage;
|
||||||
|
this.value = props.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Unset<T>() {
|
||||||
|
return new LoadingState<T>({ state: "unset" });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Loading<T>() {
|
||||||
|
return new LoadingState<T>({ state: "loading" });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Error<T>(props: { errorMessage: string }) {
|
||||||
|
const { errorMessage } = props;
|
||||||
|
return new LoadingState<T>({ state: "error", errorMessage });
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Loaded<T>(props: { value?: T }) {
|
||||||
|
const { value } = props;
|
||||||
|
return new LoadingState<T>({ state: "loaded", value });
|
||||||
|
}
|
||||||
|
|
||||||
|
public isUnset() : boolean {
|
||||||
|
return this.state === "unset";
|
||||||
|
}
|
||||||
|
public isLoading() : boolean {
|
||||||
|
return this.state === "loading";
|
||||||
|
}
|
||||||
|
public isError() : boolean {
|
||||||
|
return this.state === "error";
|
||||||
|
}
|
||||||
|
public isLoaded() : boolean {
|
||||||
|
return this.state === "loaded";
|
||||||
|
}
|
||||||
|
}
|
||||||
6
mobile/src/models/User.ts
Normal file
6
mobile/src/models/User.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export interface User {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isOnline: boolean;
|
||||||
|
isAnonymous: boolean;
|
||||||
|
}
|
||||||
4
mobile/src/models/UserConnectionInfo.ts
Normal file
4
mobile/src/models/UserConnectionInfo.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface UserConnectionInfo {
|
||||||
|
userId: string;
|
||||||
|
isOnline: boolean;
|
||||||
|
}
|
||||||
@ -109,7 +109,7 @@ export class RTMTWS extends TinyEmitter implements RTMT {
|
|||||||
this.emit(MESSAGE_CHANNEL_PREFIX + channel, message);
|
this.emit(MESSAGE_CHANNEL_PREFIX + channel, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public on(event: RtmtEventTypes, callback: (...value: any[]) => void): Listener | this {
|
public on(event: RtmtEventTypes, callback: (...value: any[]) => void): this {
|
||||||
return super.on(event, callback);
|
return super.on(event, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { View, Button } from 'react-native';
|
import { View, Button, Text } from 'react-native';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { GameScreenProps } from '../types';
|
import { GameScreenProps } from '../types';
|
||||||
|
|
||||||
export function GameScreen({ navigation, route }: GameScreenProps) {
|
export function GameScreen({ navigation, route }: GameScreenProps) {
|
||||||
|
|
||||||
|
const { context, gameId } = route.params;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||||
|
<Text>{gameId}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,13 +4,15 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { HomeScreenProps } from '../types';
|
import { HomeScreenProps } from '../types';
|
||||||
|
|
||||||
export function HomeScreen({ navigation, route }: HomeScreenProps) {
|
export function HomeScreen({ navigation, route }: HomeScreenProps) {
|
||||||
|
|
||||||
|
const { context } = route.params;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<Button
|
<Button
|
||||||
title={t('newGame')}
|
title={t('NewGame')}
|
||||||
onPress={() => navigation.navigate('Loby')}></Button>
|
onPress={() => navigation.navigate('Loby', { context })}></Button>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,13 +2,31 @@ import * as React from 'react';
|
|||||||
import { View, Text, Button } from 'react-native';
|
import { View, Text, Button } from 'react-native';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { LobyScreenProps } from '../types';
|
import { LobyScreenProps } from '../types';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { CommonMancalaGame } from 'mancala.js';
|
||||||
|
import { channel_on_game_start } from '../const/channel_names';
|
||||||
|
|
||||||
export default function LobyScreen({ navigation, route }: LobyScreenProps) {
|
export default function LobyScreen({ navigation, route }: LobyScreenProps) {
|
||||||
|
|
||||||
|
const { context } = route.params;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const onGameStart = (message: Object) => {
|
||||||
|
const newGame: CommonMancalaGame = message as CommonMancalaGame;
|
||||||
|
navigation.navigate("Game", { context, gameId: newGame.id })
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
context.rtmt.addMessageListener(channel_on_game_start, onGameStart);
|
||||||
|
context.rtmt.sendMessage("new_game", {});
|
||||||
|
return () => {
|
||||||
|
context.rtmt.removeMessageListener(channel_on_game_start, onGameStart);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||||
<Text>{t('searchingOppenent')}</Text>
|
<Text>{t('SearchingOpponent')}</Text>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
23
mobile/src/service/HttpService.ts
Normal file
23
mobile/src/service/HttpService.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { server } from "../const/config";
|
||||||
|
|
||||||
|
export interface HttpService {
|
||||||
|
get: (route: string) => Promise<Response>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HttpServiceImpl implements HttpService {
|
||||||
|
public serverAdress: string;
|
||||||
|
|
||||||
|
constructor(serverAdress: string) {
|
||||||
|
this.serverAdress = serverAdress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async get(route: string): Promise<Response> {
|
||||||
|
const url = server.serverAdress + route;
|
||||||
|
const requestOptions = {
|
||||||
|
method: 'GET',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
};
|
||||||
|
const response = await fetch(url, requestOptions);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
}
|
||||||
3
mobile/src/storage/index.ts
Normal file
3
mobile/src/storage/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { MMKV } from 'react-native-mmkv'
|
||||||
|
|
||||||
|
export const storage = new MMKV()
|
||||||
28
mobile/src/store/GameStore.ts
Normal file
28
mobile/src/store/GameStore.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { CommonMancalaGame, MancalaGame } from "mancala.js";
|
||||||
|
import { Game } from "../models/Game";
|
||||||
|
import { HttpService } from "../service/HttpService";
|
||||||
|
|
||||||
|
export interface GameStore {
|
||||||
|
get(id: string): Promise<Game | undefined>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GameStoreImpl implements GameStore {
|
||||||
|
httpService: HttpService;
|
||||||
|
constructor(props: { httpService: HttpService }) {
|
||||||
|
this.httpService = props.httpService;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(id: string): Promise<Game | undefined> {
|
||||||
|
try {
|
||||||
|
const response = await this.httpService.get(`/game/${id}`);
|
||||||
|
const json = await response.json();
|
||||||
|
const game: Game = json as Game;
|
||||||
|
game.mancalaGame = MancalaGame.createFromMancalaGame(game.mancalaGame);
|
||||||
|
return game;
|
||||||
|
} catch (error) {
|
||||||
|
// todo check error
|
||||||
|
Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
54
mobile/src/store/KeyStore.ts
Normal file
54
mobile/src/store/KeyStore.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { HttpService } from "../service/HttpService"
|
||||||
|
import { storage } from "../storage";
|
||||||
|
|
||||||
|
const user_key = "user_key"
|
||||||
|
|
||||||
|
export interface UserKeyStore {
|
||||||
|
getUserKey: () => Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserKeyStoreImpl implements UserKeyStore {
|
||||||
|
private httpService: HttpService;
|
||||||
|
private keyStoreHttp: UserKeyStore;
|
||||||
|
private keyStoreLocalStorage = new UserKeyStoreLocalStorage()
|
||||||
|
|
||||||
|
constructor(props: { httpService: HttpService }) {
|
||||||
|
this.httpService = props.httpService;
|
||||||
|
this.keyStoreHttp = new UserKeyStoreLocalHttp({ httpService: this.httpService });
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getUserKey(): Promise<string> {
|
||||||
|
const maybeUserKey = await this.keyStoreLocalStorage.getUserKey();
|
||||||
|
if (maybeUserKey === undefined) {
|
||||||
|
const userKey = await this.keyStoreHttp.getUserKey()
|
||||||
|
this.keyStoreLocalStorage.storeUserKey(userKey)
|
||||||
|
return Promise.resolve(userKey);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(maybeUserKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserKeyStoreLocalHttp implements UserKeyStore {
|
||||||
|
httpService: HttpService;
|
||||||
|
|
||||||
|
constructor(params: { httpService: HttpService }) {
|
||||||
|
this.httpService = params.httpService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getUserKey(): Promise<string> {
|
||||||
|
const response = await this.httpService.get("/register/")
|
||||||
|
return response.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UserKeyStoreLocalStorage {
|
||||||
|
public getUserKey(): Promise<string | undefined> {
|
||||||
|
const userKey = storage.getString(user_key)
|
||||||
|
return Promise.resolve(userKey === null ? undefined : userKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
public storeUserKey(userKey: string): void {
|
||||||
|
storage.set(user_key, userKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
33
mobile/src/theme/DarkTheme.ts
Normal file
33
mobile/src/theme/DarkTheme.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Theme } from "./Theme";
|
||||||
|
|
||||||
|
// https://colorhunt.co/palette/525252414141313131ec625f
|
||||||
|
const colors = {
|
||||||
|
primary: "#414141",
|
||||||
|
secondary: "#313131",
|
||||||
|
tertiary: "#606060",
|
||||||
|
quaternary: "#808080",
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorSpecial = "#337a44";
|
||||||
|
|
||||||
|
const darkTheme: Theme = {
|
||||||
|
id: "2",
|
||||||
|
name: "Dark Theme",
|
||||||
|
themePreviewColor: colors.primary,
|
||||||
|
background: colors.primary,
|
||||||
|
appBarBgColor: colors.secondary,
|
||||||
|
textColor: colors.primary,
|
||||||
|
textLightColor: "#AAAAAA",
|
||||||
|
playerTurnColor: colors.tertiary,
|
||||||
|
boardColor: colors.secondary,
|
||||||
|
pitColor: colors.tertiary,
|
||||||
|
pitSelectedColor: colors.secondary,
|
||||||
|
stoneColor: "#252525",
|
||||||
|
stoneLightColor: "#252525",
|
||||||
|
pitGameMoveAnimateColor: colors.quaternary,
|
||||||
|
pitEmptyPitAnimateColor: colorSpecial,
|
||||||
|
pitLastStoneInBankPitAnimateColor: colorSpecial,
|
||||||
|
pitGetRivalStonePitAnimateColor: colorSpecial,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default darkTheme;
|
||||||
23
mobile/src/theme/GreyTheme.ts
Normal file
23
mobile/src/theme/GreyTheme.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Theme } from "./Theme";
|
||||||
|
|
||||||
|
const greyTheme: Theme = {
|
||||||
|
id: "1",
|
||||||
|
name: "Grey Theme",
|
||||||
|
themePreviewColor: "#4D606E",
|
||||||
|
background: "#EEEEEE",
|
||||||
|
appBarBgColor: "#e4e4e4",
|
||||||
|
textColor: "#4D606E",
|
||||||
|
textLightColor: "#EEEEEE",
|
||||||
|
playerTurnColor: "#84b8a6",
|
||||||
|
boardColor: "#4D606E",
|
||||||
|
pitColor: "#D3D4D8",
|
||||||
|
pitSelectedColor: "#8837fa",
|
||||||
|
stoneColor: "#393E46",
|
||||||
|
stoneLightColor: "#EEEEEE",
|
||||||
|
pitGameMoveAnimateColor: "#c9b43c",
|
||||||
|
pitEmptyPitAnimateColor: "#5d7322",
|
||||||
|
pitLastStoneInBankPitAnimateColor: "#9463f7",
|
||||||
|
pitGetRivalStonePitAnimateColor: "#ff3d44",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default greyTheme;
|
||||||
25
mobile/src/theme/LightTheme.ts
Normal file
25
mobile/src/theme/LightTheme.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { Theme } from "./Theme";
|
||||||
|
|
||||||
|
const colorSpecial = "#8B8B8B";
|
||||||
|
|
||||||
|
const lightTheme: Theme = {
|
||||||
|
id: "1",
|
||||||
|
name: "Light Theme",
|
||||||
|
themePreviewColor: "#9B9B9B",
|
||||||
|
background: "#BBBBBB",
|
||||||
|
appBarBgColor: "#7B7B7B",
|
||||||
|
textColor: "#5B5B5B",
|
||||||
|
textLightColor: "#EBEBEB",
|
||||||
|
playerTurnColor: "#6B6B6B",
|
||||||
|
boardColor: "#9B9B9B",
|
||||||
|
pitColor: "#B8B8B8",
|
||||||
|
pitSelectedColor: "#9B9B9B",
|
||||||
|
stoneColor: "#5B5B5B",
|
||||||
|
stoneLightColor: "#3B3B3B",
|
||||||
|
pitGameMoveAnimateColor: "#ABABAB",
|
||||||
|
pitEmptyPitAnimateColor: colorSpecial,
|
||||||
|
pitLastStoneInBankPitAnimateColor: colorSpecial,
|
||||||
|
pitGetRivalStonePitAnimateColor: colorSpecial,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default lightTheme;
|
||||||
19
mobile/src/theme/Theme.ts
Normal file
19
mobile/src/theme/Theme.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
export type Theme = {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
themePreviewColor: string; // for theme switch menu
|
||||||
|
textColor: string;
|
||||||
|
textLightColor: string;
|
||||||
|
background: string;
|
||||||
|
appBarBgColor: string;
|
||||||
|
playerTurnColor: string;
|
||||||
|
boardColor: string;
|
||||||
|
pitColor: string;
|
||||||
|
pitSelectedColor: string;
|
||||||
|
stoneColor: string;
|
||||||
|
stoneLightColor: string;
|
||||||
|
pitGameMoveAnimateColor: string;
|
||||||
|
pitEmptyPitAnimateColor: string;
|
||||||
|
pitLastStoneInBankPitAnimateColor: string;
|
||||||
|
pitGetRivalStonePitAnimateColor: string;
|
||||||
|
};
|
||||||
51
mobile/src/theme/ThemeManager.ts
Normal file
51
mobile/src/theme/ThemeManager.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import lightTheme from "./LightTheme";
|
||||||
|
import greyTheme from "./GreyTheme";
|
||||||
|
import { Theme } from "./Theme";
|
||||||
|
import darkTheme from "./DarkTheme";
|
||||||
|
import { TinyEmitter } from "tiny-emitter";
|
||||||
|
import { storage } from "../storage";
|
||||||
|
|
||||||
|
export const themes = [lightTheme, darkTheme, greyTheme];
|
||||||
|
|
||||||
|
const THEME_ID = "theme_id";
|
||||||
|
|
||||||
|
export type ThemeManagerEvents = "themechange";
|
||||||
|
|
||||||
|
export default class ThemeManager extends TinyEmitter {
|
||||||
|
private _theme: Theme;
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this._theme = this.readFromLocalStorage() || lightTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get theme() {
|
||||||
|
return this._theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set theme(value: Theme) {
|
||||||
|
this._theme = value;
|
||||||
|
this.emit("themechange", value);
|
||||||
|
this.writetToLocalStorage(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private writetToLocalStorage(value: Theme) {
|
||||||
|
storage.set(THEME_ID, value.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readFromLocalStorage(): Theme | undefined {
|
||||||
|
const themeID = storage.getString(THEME_ID);
|
||||||
|
const theme = themes.find((eachTheme: Theme) => themeID === eachTheme.id);
|
||||||
|
return theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get themes(): Theme[] {
|
||||||
|
return themes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public on(event: ThemeManagerEvents, callback: (...value: any[]) => void): this {
|
||||||
|
return super.on(event, callback);
|
||||||
|
}
|
||||||
|
public off(event: ThemeManagerEvents, callback: (...value: any[]) => void): this {
|
||||||
|
return super.off(event, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,10 @@
|
|||||||
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||||
|
import { Context } from '../context/context';
|
||||||
|
|
||||||
export type RootStackParamList = {
|
export type RootStackParamList = {
|
||||||
Home: undefined,
|
Home: { context: Context },
|
||||||
Loby: undefined,
|
Loby: { context: Context },
|
||||||
Game: { gameId: string }
|
Game: { context: Context, gameId: string }
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HomeScreenProps = NativeStackScreenProps<RootStackParamList, 'Home'>;
|
export type HomeScreenProps = NativeStackScreenProps<RootStackParamList, 'Home'>;
|
||||||
|
|||||||
@ -4861,6 +4861,11 @@ makeerror@1.0.12:
|
|||||||
dependencies:
|
dependencies:
|
||||||
tmpl "1.0.5"
|
tmpl "1.0.5"
|
||||||
|
|
||||||
|
mancala.js@^0.0.2-beta.3:
|
||||||
|
version "0.0.2-beta.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/mancala.js/-/mancala.js-0.0.2-beta.3.tgz#78edfa220e1a7172351a07f255eb81180845226a"
|
||||||
|
integrity sha512-LPmQ/VT4/JWFdp/YSB7k63zK7GyflApyh4M26t23a9uXFRSpBcWSePtNFpHU/xY2+1gVjlbOwQjup2QW3Tue7w==
|
||||||
|
|
||||||
marky@^1.2.2:
|
marky@^1.2.2:
|
||||||
version "1.2.5"
|
version "1.2.5"
|
||||||
resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0"
|
resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0"
|
||||||
@ -5639,6 +5644,11 @@ react-is@^17.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
|
||||||
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
|
||||||
|
|
||||||
|
react-native-mmkv@^2.12.2:
|
||||||
|
version "2.12.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-mmkv/-/react-native-mmkv-2.12.2.tgz#4bba0f5f04e2cf222494cce3a9794ba6a4894dee"
|
||||||
|
integrity sha512-6058Aq0p57chPrUutLGe9fYoiDVDNMU2PKV+lLFUJ3GhoHvUrLdsS1PDSCLr00yqzL4WJQ7TTzH+V8cpyrNcfg==
|
||||||
|
|
||||||
react-native-safe-area-context@^4.9.0:
|
react-native-safe-area-context@^4.9.0:
|
||||||
version "4.9.0"
|
version "4.9.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.9.0.tgz#21a570ca3594cb4259ba65f93befaa60d91bcbd0"
|
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.9.0.tgz#21a570ca3594cb4259ba65f93befaa60d91bcbd0"
|
||||||
@ -5652,6 +5662,11 @@ react-native-screens@^3.29.0:
|
|||||||
react-freeze "^1.0.0"
|
react-freeze "^1.0.0"
|
||||||
warn-once "^0.1.0"
|
warn-once "^0.1.0"
|
||||||
|
|
||||||
|
react-native-snackbar@^2.6.2:
|
||||||
|
version "2.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-native-snackbar/-/react-native-snackbar-2.6.2.tgz#7c0d0d93bfb5822fb1f41f00d29383f522c02185"
|
||||||
|
integrity sha512-edAubZJiBowwQUXJV5oXbMqitQ9vw7JzWUCczeTPVo6lRa+FzsUjiCQBHdWBbCw/N8/Q7jgKg4juNXU/bXZdXg==
|
||||||
|
|
||||||
react-native@0.73.6:
|
react-native@0.73.6:
|
||||||
version "0.73.6"
|
version "0.73.6"
|
||||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.73.6.tgz#ed4c675e205a34bd62c4ce8b9bd1ca5c85126d5b"
|
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.73.6.tgz#ed4c675e205a34bd62c4ce8b9bd1ca5c85126d5b"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user