[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-stack": "^6.9.26",
|
||||
"i18next": "^23.10.1",
|
||||
"mancala.js": "^0.0.2-beta.3",
|
||||
"react": "18.2.0",
|
||||
"react-i18next": "^14.1.0",
|
||||
"react-native": "0.73.6",
|
||||
"react-native-mmkv": "^2.12.2",
|
||||
"react-native-safe-area-context": "^4.9.0",
|
||||
"react-native-screens": "^3.29.0",
|
||||
"react-native-snackbar": "^2.6.2",
|
||||
"tiny-emitter": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@ -1,19 +1,80 @@
|
||||
|
||||
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 { RootStackParamList } from './types';
|
||||
import { HomeScreen } from './screens/HomeScreen';
|
||||
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 context = initContext();
|
||||
|
||||
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 (
|
||||
<NavigationContainer>
|
||||
<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="Game" component={GameScreen} />
|
||||
</Stack.Navigator>
|
||||
</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 {
|
||||
translation: {
|
||||
newGame: "New Game",
|
||||
searchingOppenent: 'Please wait, searching opponent...',
|
||||
Mancala: "Mancala",
|
||||
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 {
|
||||
translation: {
|
||||
newGame: "Yeni Oyun",
|
||||
searchingOppenent: 'Lütfen bekleyin, rakip bulunuyor...',
|
||||
Mancala: "Köçürme",
|
||||
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);
|
||||
}
|
||||
|
||||
public on(event: RtmtEventTypes, callback: (...value: any[]) => void): Listener | this {
|
||||
public on(event: RtmtEventTypes, callback: (...value: any[]) => void): this {
|
||||
return super.on(event, callback);
|
||||
}
|
||||
|
||||
|
||||
@ -1,13 +1,16 @@
|
||||
import * as React from 'react';
|
||||
import { View, Button } from 'react-native';
|
||||
import { View, Button, Text } from 'react-native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { GameScreenProps } from '../types';
|
||||
|
||||
export function GameScreen({ navigation, route }: GameScreenProps) {
|
||||
|
||||
const { context, gameId } = route.params;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text>{gameId}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@ -4,13 +4,15 @@ import { useTranslation } from 'react-i18next';
|
||||
import { HomeScreenProps } from '../types';
|
||||
|
||||
export function HomeScreen({ navigation, route }: HomeScreenProps) {
|
||||
|
||||
const { context } = route.params;
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Button
|
||||
title={t('newGame')}
|
||||
onPress={() => navigation.navigate('Loby')}></Button>
|
||||
title={t('NewGame')}
|
||||
onPress={() => navigation.navigate('Loby', { context })}></Button>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,13 +2,31 @@ import * as React from 'react';
|
||||
import { View, Text, Button } from 'react-native';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
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) {
|
||||
|
||||
const { context } = route.params;
|
||||
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 (
|
||||
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Text>{t('searchingOppenent')}</Text>
|
||||
<Text>{t('SearchingOpponent')}</Text>
|
||||
</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 { Context } from '../context/context';
|
||||
|
||||
export type RootStackParamList = {
|
||||
Home: undefined,
|
||||
Loby: undefined,
|
||||
Game: { gameId: string }
|
||||
Home: { context: Context },
|
||||
Loby: { context: Context },
|
||||
Game: { context: Context, gameId: string }
|
||||
};
|
||||
|
||||
export type HomeScreenProps = NativeStackScreenProps<RootStackParamList, 'Home'>;
|
||||
|
||||
@ -4861,6 +4861,11 @@ makeerror@1.0.12:
|
||||
dependencies:
|
||||
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:
|
||||
version "1.2.5"
|
||||
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"
|
||||
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:
|
||||
version "4.9.0"
|
||||
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"
|
||||
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:
|
||||
version "0.73.6"
|
||||
resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.73.6.tgz#ed4c675e205a34bd62c4ce8b9bd1ca5c85126d5b"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user