init commit,
7
002_source/ionic_mobile/src/App.test.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders without crashing', () => {
|
||||
const { baseElement } = render(<App />);
|
||||
expect(baseElement).toBeDefined();
|
||||
});
|
199
002_source/ionic_mobile/src/App.tsx
Normal file
@@ -0,0 +1,199 @@
|
||||
import { DEBUG, DEBUG_LINK, LESSON_LINK, QUIZ_MAIN_MENU_LINK, RECORD_LINK, SETTING_LINK } from './constants';
|
||||
/* Core CSS required for Ionic components to work properly */
|
||||
|
||||
import '@ionic/react/css/core.css';
|
||||
/* Basic CSS for apps built with Ionic */
|
||||
import '@ionic/react/css/normalize.css';
|
||||
import '@ionic/react/css/structure.css';
|
||||
/* Optional CSS utils that can be commented out */
|
||||
import '@ionic/react/css/padding.css';
|
||||
import '@ionic/react/css/float-elements.css';
|
||||
import '@ionic/react/css/text-alignment.css';
|
||||
import '@ionic/react/css/text-transformation.css';
|
||||
import '@ionic/react/css/flex-utils.css';
|
||||
import '@ionic/react/css/typography.css';
|
||||
import '@ionic/react/css/display.css';
|
||||
/**
|
||||
* Ionic Dark Mode
|
||||
* -----------------------------------------------------
|
||||
* For more info, please see:
|
||||
* https://ionicframework.com/docs/theming/dark-mode
|
||||
*/
|
||||
/* import '@ionic/react/css/palettes/dark.always.css'; */
|
||||
/* import '@ionic/react/css/palettes/dark.class.css'; */
|
||||
// import '@ionic/react/css/palettes/dark.system.css';
|
||||
/* Theme variables */
|
||||
import './theme/variables.css';
|
||||
import './debug.css';
|
||||
import './style.css';
|
||||
import './google_fonts.css';
|
||||
import { App as CapacitorApp } from '@capacitor/app';
|
||||
import {
|
||||
IonApp,
|
||||
IonIcon,
|
||||
IonLabel,
|
||||
IonRouterOutlet,
|
||||
IonTabBar,
|
||||
IonTabButton,
|
||||
IonTabs,
|
||||
setupIonicReact,
|
||||
useIonRouter,
|
||||
} from '@ionic/react';
|
||||
import { IonReactRouter } from '@ionic/react-router';
|
||||
import { bookOutline, bug, heartOutline, newspaperOutline, settingsOutline } from 'ionicons/icons';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ConfirmUserExit from './components/ConfirmUserExit';
|
||||
import { DisableUserTap } from './components/DisableUserTap';
|
||||
import ContextMeta from './contexts';
|
||||
import { useAppStateContext } from './contexts/AppState';
|
||||
import { useMyIonQuizContext } from './contexts/MyIonQuiz';
|
||||
import { RouteConfig } from './RouteConfig';
|
||||
|
||||
let active_color = 'tomato';
|
||||
let inactive_color = 'gray';
|
||||
|
||||
CapacitorApp.addListener('appStateChange', ({ isActive }) => {
|
||||
console.log('App state changed. Is active?', isActive);
|
||||
});
|
||||
|
||||
CapacitorApp.addListener('backButton', ({ canGoBack }) => {
|
||||
if (!canGoBack) {
|
||||
// CapacitorApp.minimizeApp();
|
||||
// alert('blablabla');
|
||||
}
|
||||
});
|
||||
|
||||
setupIonicReact();
|
||||
|
||||
const TabButtons: React.FC = () => {
|
||||
let router = useIonRouter();
|
||||
const { t } = useTranslation();
|
||||
|
||||
let {
|
||||
url_push_after_user_confirm,
|
||||
setURLPushAfterUserConfirm,
|
||||
setShowConfirmUserExit,
|
||||
listening_practice_in_progress,
|
||||
setListeningPracticeInProgress,
|
||||
matching_frenzy_in_progress,
|
||||
connective_revision_in_progress,
|
||||
tab_active,
|
||||
setTabActive,
|
||||
} = useAppStateContext();
|
||||
const { resetListeningPracticeCorrectionList } = useMyIonQuizContext();
|
||||
|
||||
function goSwitchPage(url: string) {
|
||||
if (listening_practice_in_progress || matching_frenzy_in_progress || connective_revision_in_progress) {
|
||||
if (url != QUIZ_MAIN_MENU_LINK) {
|
||||
setURLPushAfterUserConfirm(url);
|
||||
setShowConfirmUserExit(true);
|
||||
}
|
||||
} else {
|
||||
setTabActive(url);
|
||||
router.push(url, 'none', 'replace');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonTabBar slot="bottom">
|
||||
<IonTabButton
|
||||
tab="lesson"
|
||||
onClick={() => goSwitchPage(LESSON_LINK)}
|
||||
style={{ color: tab_active == LESSON_LINK ? active_color : inactive_color }}
|
||||
>
|
||||
<IonIcon aria-hidden="true" icon={bookOutline} size="large" />
|
||||
</IonTabButton>
|
||||
{/* */}
|
||||
<IonTabButton
|
||||
tab="quiz"
|
||||
onClick={() => goSwitchPage(QUIZ_MAIN_MENU_LINK)}
|
||||
style={{ color: tab_active == QUIZ_MAIN_MENU_LINK ? active_color : inactive_color }}
|
||||
>
|
||||
<IonIcon aria-hidden="true" icon={newspaperOutline} size="large" />
|
||||
</IonTabButton>
|
||||
|
||||
<IonTabButton
|
||||
tab="quiz"
|
||||
onClick={() => goSwitchPage(QUIZ_MAIN_MENU_LINK)}
|
||||
style={{ color: tab_active == QUIZ_MAIN_MENU_LINK ? active_color : inactive_color }}
|
||||
></IonTabButton>
|
||||
|
||||
{/* */}
|
||||
<IonTabButton
|
||||
tab="record"
|
||||
onClick={() => goSwitchPage(RECORD_LINK)}
|
||||
style={{ color: tab_active == RECORD_LINK ? active_color : inactive_color }}
|
||||
>
|
||||
<IonIcon aria-hidden="true" icon={heartOutline} size="large" />
|
||||
</IonTabButton>
|
||||
{/* */}
|
||||
|
||||
{/* 003_remove_setting_screen, hide setting on bottom tabs */}
|
||||
<IonTabButton
|
||||
tab="setting"
|
||||
onClick={() => goSwitchPage(SETTING_LINK)}
|
||||
style={{ color: tab_active == SETTING_LINK ? active_color : inactive_color }}
|
||||
>
|
||||
<IonIcon aria-hidden="true" icon={settingsOutline} size="large" />
|
||||
</IonTabButton>
|
||||
|
||||
{DEBUG ? (
|
||||
<IonTabButton
|
||||
tab="debug"
|
||||
onClick={() => goSwitchPage(DEBUG_LINK)}
|
||||
// href={DEBUG_LINK}
|
||||
>
|
||||
<IonIcon aria-hidden="true" icon={bug} color="danger" />
|
||||
<IonLabel color="danger">Debug</IonLabel>
|
||||
</IonTabButton>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</IonTabBar>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const App: React.FC = () => {
|
||||
return (
|
||||
<IonApp>
|
||||
<ContextMeta>
|
||||
<DisableUserTap>
|
||||
<IonReactRouter>
|
||||
<IonTabs>
|
||||
<IonRouterOutlet>
|
||||
<RouteConfig />
|
||||
</IonRouterOutlet>
|
||||
<TabButtons />
|
||||
</IonTabs>
|
||||
<div
|
||||
style={{
|
||||
zIndex: 9999,
|
||||
backgroundColor: 'tomato',
|
||||
position: 'fixed',
|
||||
bottom: '1rem',
|
||||
height: '4rem',
|
||||
width: '4rem',
|
||||
left: 'calc( (100vw - 4rem ) / 2 )',
|
||||
borderRadius: '50%',
|
||||
//
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontWeight: 'bold', fontSize: '2rem', color: 'white' }}>AI</div>
|
||||
</div>
|
||||
{/* */}
|
||||
<ConfirmUserExit />
|
||||
{/* */}
|
||||
</IonReactRouter>
|
||||
</DisableUserTap>
|
||||
</ContextMeta>
|
||||
</IonApp>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
158
002_source/ionic_mobile/src/RouteConfig.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
import { Redirect, Route } from 'react-router-dom';
|
||||
import {
|
||||
CONNECTIVE_REVISION_LINK,
|
||||
DEBUG_LINK,
|
||||
FAVORITE_LINK,
|
||||
LESSON_LINK,
|
||||
LESSON_WORD_PAGE_LINK,
|
||||
LISTENING_PRACTICE_LINK,
|
||||
MATCHING_FRENZY_LINK,
|
||||
QUIZ_MAIN_MENU_LINK,
|
||||
RECORD_LINK,
|
||||
} from './constants';
|
||||
import ConnectiveRevisionQuizRun from './pages/ConnectiveRevision/QuizRun';
|
||||
import ConnectiveRevisionQuizResult from './pages/ConnectiveRevision/Result';
|
||||
// import CorrectionRoute from './pages/MatchingFrenzy/Route/Correction';
|
||||
// import PracticeFinish from './pages/MatchingFrenzy/Finish';
|
||||
import ConnectiveRevisionSelectCategory from './pages/ConnectiveRevision/SelectCategory';
|
||||
import DebugPage from './pages/DebugPage';
|
||||
import FavConnectivesPage from './pages/Favorite/Connectives';
|
||||
import ConnectivesWordPage from './pages/Favorite/ConnectivesWordPage';
|
||||
import FavVocabularyPage from './pages/Favorite/Vocabulary';
|
||||
import FavoriteVocabularyPage from './pages/Favorite/WordPage';
|
||||
import ConnectivesPage from './pages/Lesson/ConnectivesPage';
|
||||
import Lesson from './pages/Lesson/index';
|
||||
import LessonWordPage from './pages/Lesson/WordPage';
|
||||
//
|
||||
import ListeningPractice from './pages/ListeningPractice';
|
||||
import PracticeFinish from './pages/ListeningPractice/Finish';
|
||||
import PracticeResult from './pages/ListeningPractice/Result';
|
||||
import QuestionRoute from './pages/ListeningPractice/Route';
|
||||
import CorrectionRoute from './pages/ListeningPractice/Route/CorrectionRoute';
|
||||
import MatchingFrenzyMatchFinished from './pages/MatchingFrenzy/Finished';
|
||||
import MatchingFrenzyMatchRun from './pages/MatchingFrenzy/MatchRun';
|
||||
import MatchingFrenzyMatchResult from './pages/MatchingFrenzy/Result';
|
||||
//
|
||||
import MatchingFrenzySelectCategory from './pages/MatchingFrenzy/SelectCategory';
|
||||
import Page from './pages/Page';
|
||||
import QuizzesMainMenu from './pages/QuizzesMainMenu';
|
||||
//
|
||||
import MyAchievementPage from './pages/Record/index';
|
||||
import Setting from './pages/Setting/indx';
|
||||
import Tab1 from './pages/Tab1';
|
||||
import Tab2 from './pages/Tab2';
|
||||
import Tab3 from './pages/Tab3';
|
||||
|
||||
function RouteConfig() {
|
||||
return (
|
||||
<>
|
||||
{/* */}
|
||||
<Route exact path={QUIZ_MAIN_MENU_LINK}>
|
||||
<QuizzesMainMenu />
|
||||
</Route>
|
||||
{/* */}
|
||||
<Route exact path={RECORD_LINK}>
|
||||
<MyAchievementPage />
|
||||
</Route>
|
||||
{/* */}
|
||||
<Route exact path={`${LESSON_LINK}/a/:act_category`}>
|
||||
<Lesson />
|
||||
</Route>
|
||||
|
||||
<Route exact path={LESSON_LINK}>
|
||||
<Lesson />
|
||||
</Route>
|
||||
{/* */}
|
||||
{/* http://localhost:5173/listening_practice/c/0 */}
|
||||
<Route exact path={`${LISTENING_PRACTICE_LINK}/c/:p_route`}>
|
||||
<CorrectionRoute />
|
||||
</Route>
|
||||
|
||||
{/* http://localhost:5173/listening_practice/r/0 */}
|
||||
<Route exact path={`${LISTENING_PRACTICE_LINK}/r/:p_route`}>
|
||||
<QuestionRoute />
|
||||
</Route>
|
||||
<Route exact path={`${LISTENING_PRACTICE_LINK}/finished`}>
|
||||
<PracticeFinish />
|
||||
</Route>
|
||||
<Route exact path={`${LISTENING_PRACTICE_LINK}/result`}>
|
||||
<PracticeResult />
|
||||
</Route>
|
||||
<Route exact path={LISTENING_PRACTICE_LINK}>
|
||||
<ListeningPractice />
|
||||
</Route>
|
||||
{/* */}
|
||||
<Route exact path={`${CONNECTIVE_REVISION_LINK}/r/:p_route`}>
|
||||
<ConnectiveRevisionQuizRun />
|
||||
</Route>
|
||||
<Route exact path={`${CONNECTIVE_REVISION_LINK}/finished`}>
|
||||
<ConnectiveRevisionQuizResult />
|
||||
</Route>
|
||||
<Route exact path={CONNECTIVE_REVISION_LINK}>
|
||||
<ConnectiveRevisionSelectCategory />
|
||||
</Route>
|
||||
{/* */}
|
||||
<Route exact path={`${MATCHING_FRENZY_LINK}/r/:p_route`}>
|
||||
<MatchingFrenzyMatchRun />
|
||||
</Route>
|
||||
<Route exact path={`${MATCHING_FRENZY_LINK}/finished`}>
|
||||
<MatchingFrenzyMatchFinished />
|
||||
</Route>
|
||||
<Route exact path={`${MATCHING_FRENZY_LINK}/result`}>
|
||||
<MatchingFrenzyMatchResult />
|
||||
</Route>
|
||||
<Route exact path={MATCHING_FRENZY_LINK}>
|
||||
<MatchingFrenzySelectCategory />
|
||||
</Route>
|
||||
|
||||
<Route exact path={`${FAVORITE_LINK}/v/:lesson_idx/:cat_idx/:word_idx`}>
|
||||
<FavoriteVocabularyPage />
|
||||
</Route>
|
||||
|
||||
<Route exact path={`${FAVORITE_LINK}/v`}>
|
||||
<FavVocabularyPage />
|
||||
</Route>
|
||||
|
||||
<Route exact path={`${FAVORITE_LINK}/c/:lesson_idx/:cat_idx/:word_idx`}>
|
||||
{/* <FavoriteVocabularyPage /> */}
|
||||
{/* <FavConnectivesContentPage /> */}
|
||||
<ConnectivesWordPage />
|
||||
</Route>
|
||||
<Route exact path={`${FAVORITE_LINK}/c`}>
|
||||
<FavConnectivesPage />
|
||||
</Route>
|
||||
|
||||
<Route exact path={`${LESSON_WORD_PAGE_LINK}/v/:lesson_idx/:cat_idx/:word_idx`}>
|
||||
<LessonWordPage />
|
||||
</Route>
|
||||
<Route exact path={`${LESSON_WORD_PAGE_LINK}/c/:lesson_idx/:cat_idx/:word_idx`}>
|
||||
<ConnectivesPage />
|
||||
</Route>
|
||||
|
||||
{/* TODO: remove below */}
|
||||
<Route exact path="/tab1">
|
||||
<Tab1 />
|
||||
</Route>
|
||||
<Route exact path="/tab2">
|
||||
<Tab2 />
|
||||
</Route>
|
||||
<Route path="/tab3">
|
||||
<Tab3 />
|
||||
</Route>
|
||||
<Route path="/setting">
|
||||
<Setting />
|
||||
</Route>
|
||||
<Route path="/page/:name" exact={true}>
|
||||
<Page />
|
||||
</Route>
|
||||
<Route exact path={DEBUG_LINK}>
|
||||
<DebugPage />
|
||||
</Route>
|
||||
<Route exact path="/">
|
||||
<Redirect to={LESSON_LINK} />
|
||||
</Route>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export { RouteConfig };
|
@@ -0,0 +1,68 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import './style.css';
|
||||
|
||||
interface AudioSeekBarProps {
|
||||
value: number;
|
||||
range_list: number[];
|
||||
}
|
||||
|
||||
const AttentiveEarsProgressBar: FunctionComponent<AudioSeekBarProps> = ({ value, range_list }) => {
|
||||
let test_value = value;
|
||||
let scale = [];
|
||||
|
||||
for (let i = 0; i < range_list.length; i++) {
|
||||
let scale_full_range = range_list[i];
|
||||
let scale_range = i > 0 ? scale_full_range - range_list[i - 1] : scale_full_range;
|
||||
|
||||
let fill = 0;
|
||||
if (test_value > scale_full_range) {
|
||||
fill = scale_full_range;
|
||||
} else {
|
||||
if (i > 0) {
|
||||
fill = Math.max(0, test_value - range_list[i - 1]);
|
||||
} else {
|
||||
fill = test_value;
|
||||
}
|
||||
}
|
||||
let fill_pct = Math.min(fill / scale_range, 1);
|
||||
scale.push([fill_pct]);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{scale.map((item: any, index: number) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: `calc( 100% / ${range_list.length} )`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '0.5rem' }}>{range_list[index]}</div>
|
||||
<div
|
||||
className={`audioSeekBar playBar_t__seek`}
|
||||
style={{
|
||||
// border: '1px solid #000',
|
||||
borderTop: '1px solid #000',
|
||||
borderBottom: '1px solid #000',
|
||||
borderLeft: index == 0 ? '1px solid #000' : '',
|
||||
borderRight: index == scale.length - 1 ? '1px solid #000' : '',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: `${item * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AttentiveEarsProgressBar;
|
@@ -0,0 +1,29 @@
|
||||
.audioSeekBar {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.audioSeekBar__tick {
|
||||
background-color: rgb(56, 182, 255);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.playBar_t {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: #1c192f;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
|
||||
.playBar_t__seek {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
/* border-radius: 12px; */
|
||||
margin-right: 0px;
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
import { IonButton, IonContent, IonModal, useIonRouter } from '@ionic/react';
|
||||
import { useRef } from 'react';
|
||||
import { COLOR_TEXT } from '../../constants';
|
||||
import { useAppStateContext } from '../../contexts/AppState';
|
||||
import { useMyIonQuizContext } from '../../contexts/MyIonQuiz';
|
||||
|
||||
function ConfirmUserExit() {
|
||||
let {
|
||||
setTabActive,
|
||||
show_confirm_user_exit,
|
||||
setShowConfirmUserExit,
|
||||
url_push_after_user_confirm,
|
||||
setListeningPracticeInProgress,
|
||||
setConnectiveRevisionInProgress,
|
||||
setMatchingFrenzyInProgress,
|
||||
} = useAppStateContext();
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
const router = useIonRouter();
|
||||
const { resetListeningPracticeCorrectionList } = useMyIonQuizContext();
|
||||
return (
|
||||
<>
|
||||
<IonModal
|
||||
isOpen={show_confirm_user_exit}
|
||||
ref={modal}
|
||||
// trigger='open-modal'
|
||||
initialBreakpoint={0.25}
|
||||
breakpoints={[0, 0.25, 0.5, 0.75]}
|
||||
onDidDismiss={() => setShowConfirmUserExit(false)}
|
||||
>
|
||||
<IonContent className="ion-padding">
|
||||
<div style={{ margin: '2rem', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<div style={{ color: COLOR_TEXT }}>Are you sure want to quit ?</div>
|
||||
<div
|
||||
style={{
|
||||
margin: '2rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: '3rem',
|
||||
}}
|
||||
>
|
||||
<IonButton
|
||||
color="dark"
|
||||
shape={'round'}
|
||||
style={{ minWidth: '80px' }}
|
||||
onClick={() => setShowConfirmUserExit(false)}
|
||||
>
|
||||
No
|
||||
</IonButton>
|
||||
<IonButton
|
||||
fill="outline"
|
||||
shape={'round'}
|
||||
style={{ minWidth: '80px', color: COLOR_TEXT }}
|
||||
color="dark"
|
||||
onClick={() => {
|
||||
setShowConfirmUserExit(false);
|
||||
setListeningPracticeInProgress(false);
|
||||
setMatchingFrenzyInProgress(false);
|
||||
setConnectiveRevisionInProgress(false);
|
||||
//
|
||||
resetListeningPracticeCorrectionList();
|
||||
//
|
||||
setTabActive(url_push_after_user_confirm);
|
||||
//
|
||||
router.push(url_push_after_user_confirm, 'none', 'replace');
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
</IonButton>
|
||||
</div>
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConfirmUserExit;
|
@@ -0,0 +1,86 @@
|
||||
import { IonButton, IonContent, IonModal, useIonRouter } from '@ionic/react';
|
||||
import { Dispatch, FunctionComponent, SetStateAction, useRef } from 'react';
|
||||
import { COLOR_TEXT } from '../../constants';
|
||||
import { useAppStateContext } from '../../contexts/AppState';
|
||||
import { useMyIonQuizContext } from '../../contexts/MyIonQuiz';
|
||||
|
||||
interface AudioSeekBarProps {
|
||||
show_confirm_user_exit: boolean;
|
||||
setShowConfirmUserExit: Dispatch<SetStateAction<boolean>>;
|
||||
url_push_after_user_confirm: string;
|
||||
}
|
||||
|
||||
const ConfirmUserQuitQuiz: FunctionComponent<AudioSeekBarProps> = ({
|
||||
show_confirm_user_exit,
|
||||
setShowConfirmUserExit,
|
||||
//
|
||||
url_push_after_user_confirm,
|
||||
}) => {
|
||||
let { setListeningPracticeInProgress, setConnectiveRevisionInProgress, setMatchingFrenzyInProgress } =
|
||||
useAppStateContext();
|
||||
let { setTabActive } = useAppStateContext();
|
||||
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
const router = useIonRouter();
|
||||
const { resetListeningPracticeCorrectionList } = useMyIonQuizContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonModal
|
||||
isOpen={show_confirm_user_exit}
|
||||
ref={modal}
|
||||
// trigger='open-modal'
|
||||
initialBreakpoint={0.25}
|
||||
breakpoints={[0, 0.25, 0.5, 0.75]}
|
||||
onDidDismiss={() => setShowConfirmUserExit(false)}
|
||||
>
|
||||
<IonContent className="ion-padding">
|
||||
<div style={{ margin: '2rem', display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
|
||||
<div style={{ color: COLOR_TEXT }}>Are you sure want to quit ?</div>
|
||||
<div
|
||||
style={{
|
||||
margin: '2rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
gap: '3rem',
|
||||
}}
|
||||
>
|
||||
<IonButton
|
||||
color="dark"
|
||||
shape={'round'}
|
||||
style={{ minWidth: '80px' }}
|
||||
onClick={() => setShowConfirmUserExit(false)}
|
||||
>
|
||||
No
|
||||
</IonButton>
|
||||
<IonButton
|
||||
fill="outline"
|
||||
shape={'round'}
|
||||
style={{ minWidth: '80px', color: COLOR_TEXT }}
|
||||
color="dark"
|
||||
onClick={() => {
|
||||
setShowConfirmUserExit(false);
|
||||
setListeningPracticeInProgress(false);
|
||||
setMatchingFrenzyInProgress(false);
|
||||
setConnectiveRevisionInProgress(false);
|
||||
//
|
||||
resetListeningPracticeCorrectionList();
|
||||
//
|
||||
setTabActive(url_push_after_user_confirm);
|
||||
//
|
||||
router.push(url_push_after_user_confirm, 'none', 'replace');
|
||||
}}
|
||||
>
|
||||
Yes
|
||||
</IonButton>
|
||||
</div>
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmUserQuitQuiz;
|
@@ -0,0 +1,68 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import './style.css';
|
||||
|
||||
interface AudioSeekBarProps {
|
||||
value: number;
|
||||
range_list: number[];
|
||||
}
|
||||
|
||||
const ConnectivesConquerorProgressBar: FunctionComponent<AudioSeekBarProps> = ({ value, range_list }) => {
|
||||
let test_value = value;
|
||||
let scale = [];
|
||||
|
||||
for (let i = 0; i < range_list.length; i++) {
|
||||
let scale_full_range = range_list[i];
|
||||
let scale_range = i > 0 ? scale_full_range - range_list[i - 1] : scale_full_range;
|
||||
|
||||
let fill = 0;
|
||||
if (test_value > scale_full_range) {
|
||||
fill = scale_full_range;
|
||||
} else {
|
||||
if (i > 0) {
|
||||
fill = Math.max(0, test_value - range_list[i - 1]);
|
||||
} else {
|
||||
fill = test_value;
|
||||
}
|
||||
}
|
||||
let fill_pct = Math.min(fill / scale_range, 1);
|
||||
scale.push([fill_pct]);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{scale.map((item: any, index: number) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: `calc( 100% / ${range_list.length} )`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '0.5rem' }}>{range_list[index]}</div>
|
||||
<div
|
||||
className={`audioSeekBar playBar_t__seek`}
|
||||
style={{
|
||||
// border: '1px solid #000',
|
||||
borderTop: '1px solid #000',
|
||||
borderBottom: '1px solid #000',
|
||||
borderLeft: index == 0 ? '1px solid #000' : '',
|
||||
borderRight: index == scale.length - 1 ? '1px solid #000' : '',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: `${item * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectivesConquerorProgressBar;
|
@@ -0,0 +1,29 @@
|
||||
.audioSeekBar {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.audioSeekBar__tick {
|
||||
background-color: rgb(56, 182, 255);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.playBar_t {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: #1c192f;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
|
||||
.playBar_t__seek {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
/* border-radius: 12px; */
|
||||
margin-right: 0px;
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
import { IonToast } from '@ionic/react';
|
||||
import './style.css';
|
||||
import { CORRECT_ANSWER_MESSAGE } from '../../constants';
|
||||
import { useAppStateContext } from '../../contexts/AppState';
|
||||
|
||||
interface QuestionCardProps {
|
||||
isOpen: boolean;
|
||||
dismiss: () => void;
|
||||
}
|
||||
|
||||
const CorrectAnswerToast: React.FC<QuestionCardProps> = ({ isOpen, dismiss }) => {
|
||||
const { CORRECT_ANS_TOAST_APPEAR_TIMEOUT_S } = useAppStateContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonToast
|
||||
className="correct-answer-toast"
|
||||
isOpen={isOpen}
|
||||
message={CORRECT_ANSWER_MESSAGE}
|
||||
onDidDismiss={() => dismiss()}
|
||||
duration={CORRECT_ANS_TOAST_APPEAR_TIMEOUT_S * 1000}
|
||||
color="success"
|
||||
></IonToast>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CorrectAnswerToast;
|
@@ -0,0 +1,9 @@
|
||||
ion-toast.correct-answer-toast::part(message) {
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
ion-toast.correct-answer-toast::part(container) {
|
||||
bottom: 100px;
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
import { IonBackdrop } from '@ionic/react';
|
||||
import { useAppStateContext } from '../../contexts/AppState';
|
||||
|
||||
export function DisableUserTap({ children }: { children: React.ReactNode }) {
|
||||
const { disable_user_tap } = useAppStateContext();
|
||||
return (
|
||||
<>
|
||||
{disable_user_tap ? <IonBackdrop visible={true}></IonBackdrop> : null}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
After Width: | Height: | Size: 12 KiB |
21
002_source/ionic_mobile/src/components/ExitButton/index.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { App } from '@capacitor/app';
|
||||
import { IonButton, IonIcon } from '@ionic/react';
|
||||
import { FunctionComponent } from 'react';
|
||||
import './style.css';
|
||||
import { exitOutline } from 'ionicons/icons';
|
||||
|
||||
const ExitButton: FunctionComponent = () => {
|
||||
const handleExitOnClick = () => {
|
||||
App.exitApp();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonButton color="clear" fill="clear" shape="round" onClick={handleExitOnClick}>
|
||||
<IonIcon slot={'icon-only'} color={'dark'} size={'large'} icon={exitOutline} />
|
||||
</IonButton>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExitButton;
|
29
002_source/ionic_mobile/src/components/ExitButton/style.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.audioSeekBar {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.audioSeekBar__tick {
|
||||
background-color: rgb(56, 182, 255);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.playBar_t {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: #1c192f;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
|
||||
.playBar_t__seek {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
/* border-radius: 12px; */
|
||||
margin-right: 0px;
|
||||
}
|
24
002_source/ionic_mobile/src/components/ExploreContainer.css
Normal file
@@ -0,0 +1,24 @@
|
||||
.container {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.container strong {
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.container p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container a {
|
||||
text-decoration: none;
|
||||
}
|
43
002_source/ionic_mobile/src/components/ExploreContainer.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import './ExploreContainer.css';
|
||||
import React from 'react';
|
||||
import Markdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
interface ContainerProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
const markdown = `
|
||||
Just a link: www.nasa.gov.
|
||||
- [ ] helloworld
|
||||
__bold__
|
||||
# h1
|
||||
## h2
|
||||
### h3
|
||||
#### h4
|
||||
##### h5
|
||||
###### h6
|
||||
|
||||
| test | test | test |
|
||||
| --- | --- | --- |
|
||||
| 1 | 1 | 1 |
|
||||
`;
|
||||
|
||||
const ExploreContainer: React.FC<ContainerProps> = ({ name }) => {
|
||||
return (
|
||||
<div className="container">
|
||||
<strong>{name}</strong>
|
||||
<p>
|
||||
Explore{' '}
|
||||
<a target="_blank" rel="noopener noreferrer" href="https://ionicframework.com/docs/components">
|
||||
UI Components
|
||||
</a>
|
||||
</p>
|
||||
<div>
|
||||
<Markdown remarkPlugins={[remarkGfm]}>{markdown}</Markdown>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExploreContainer;
|
@@ -0,0 +1,109 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import './style.css';
|
||||
|
||||
interface AudioSeekBarProps {
|
||||
value: number;
|
||||
range_list: number[];
|
||||
}
|
||||
|
||||
const GeniusProgressBar: FunctionComponent<AudioSeekBarProps> = ({ value, range_list }) => {
|
||||
let test_value = value;
|
||||
let scale = [];
|
||||
|
||||
for (let i = 0; i < range_list.length; i++) {
|
||||
let scale_full_range = range_list[i];
|
||||
let scale_range = i > 0 ? scale_full_range - range_list[i - 1] : scale_full_range;
|
||||
|
||||
let fill = 0;
|
||||
if (test_value > scale_full_range) {
|
||||
fill = scale_full_range;
|
||||
} else {
|
||||
if (i > 0) {
|
||||
fill = Math.max(0, test_value - range_list[i - 1]);
|
||||
} else {
|
||||
fill = Math.max(0, test_value);
|
||||
}
|
||||
}
|
||||
let fill_pct = Math.min(fill / scale_range, 1);
|
||||
scale.push([fill_pct]);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', width: '100%' }}>
|
||||
<div style={{ width: '100%', display: 'flex', flexDirection: 'row' }}>
|
||||
{scale.map((item: any, index: number) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
width: `calc( 100% / ${range_list.length} )`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '0.5rem' }}>{range_list[index]}</div>
|
||||
<div
|
||||
className={`audioSeekBar playBar_t__seek`}
|
||||
style={{
|
||||
// border: '1px solid #000',
|
||||
borderTop: '1px solid #000',
|
||||
borderBottom: '1px solid #000',
|
||||
borderLeft: index == 0 ? '1px solid #000' : '',
|
||||
borderRight: index == scale.length - 1 ? '1px solid #000' : '',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: `${item * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ backgroundColor: 'gold' }}>
|
||||
<div>helloworld</div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{scale.map((item, index) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: `calc( 100% / ${range_list.length} )`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '0.5rem' }}>{range_list[index]}</div>
|
||||
<div
|
||||
className={`audioSeekBar playBar_t__seek`}
|
||||
style={{ borderTop: '1px solid #000', borderBottom: '1px solid #000' }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: `${item * 100}%`,
|
||||
borderLeft: index == 0 ? '1px solid #000' : '',
|
||||
borderRight: index == 6 ? '1px solid #000' : '',
|
||||
}}
|
||||
className="audioSeekBar__tick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeniusProgressBar;
|
@@ -0,0 +1,29 @@
|
||||
.audioSeekBar {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.audioSeekBar__tick {
|
||||
background-color: rgb(56, 182, 255);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.playBar_t {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: #1c192f;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
|
||||
.playBar_t__seek {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
/* border-radius: 12px; */
|
||||
margin-right: 0px;
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import './style.css';
|
||||
|
||||
interface AudioSeekBarProps {
|
||||
value: number;
|
||||
range_list: number[];
|
||||
}
|
||||
|
||||
const HardWorkerProgressBar: FunctionComponent<AudioSeekBarProps> = ({ value, range_list }) => {
|
||||
let test_value = value;
|
||||
let scale = [];
|
||||
|
||||
for (let i = 0; i < range_list.length; i++) {
|
||||
let scale_full_range = range_list[i];
|
||||
let scale_range = i > 0 ? scale_full_range - range_list[i - 1] : scale_full_range;
|
||||
|
||||
let fill = 0;
|
||||
if (test_value > scale_full_range) {
|
||||
fill = scale_full_range;
|
||||
} else {
|
||||
if (i > 0) {
|
||||
fill = Math.max(0, test_value - range_list[i - 1]);
|
||||
} else {
|
||||
fill = test_value;
|
||||
}
|
||||
}
|
||||
let fill_pct = Math.min(fill / scale_range, 1);
|
||||
scale.push([fill_pct]);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', width: '100%' }}>
|
||||
<div style={{ width: '100%', display: 'flex', flexDirection: 'row' }}>
|
||||
{scale.map((item: any, index: number) => {
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
width: `calc( 100% / ${range_list.length} )`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '0.5rem' }}>{range_list[index]}</div>
|
||||
<div
|
||||
className={`audioSeekBar playBar_t__seek`}
|
||||
style={{
|
||||
// border: '1px solid #000',
|
||||
borderTop: '1px solid #000',
|
||||
borderBottom: '1px solid #000',
|
||||
borderLeft: index == 0 ? '1px solid #000' : '',
|
||||
borderRight: index == scale.length - 1 ? '1px solid #000' : '',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: `${item * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default HardWorkerProgressBar;
|
@@ -0,0 +1,29 @@
|
||||
.audioSeekBar {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.audioSeekBar__tick {
|
||||
background-color: rgb(56, 182, 255);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.playBar_t {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: #1c192f;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
|
||||
.playBar_t__seek {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
/* border-radius: 12px; */
|
||||
margin-right: 0px;
|
||||
}
|
BIN
002_source/ionic_mobile/src/components/HelloworldImage/image.jpg
Normal file
After Width: | Height: | Size: 194 KiB |
@@ -0,0 +1,9 @@
|
||||
import ImageJpg from './image.jpg';
|
||||
|
||||
function HelloworldImage() {
|
||||
return (
|
||||
<div style={{ width: '100%', height: '100%', backgroundImage: `url(${ImageJpg})`, backgroundSize: 'cover' }}></div>
|
||||
);
|
||||
}
|
||||
|
||||
export { HelloworldImage };
|
@@ -0,0 +1,26 @@
|
||||
import { IonContent, IonPage, IonSpinner } from '@ionic/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function LoadingScreen() {
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonContent fullscreen>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
gap: '1rem',
|
||||
//
|
||||
marginTop: '33vh',
|
||||
}}
|
||||
>
|
||||
<IonSpinner></IonSpinner>
|
||||
<div>{t('Loading')}</div>
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
34
002_source/ionic_mobile/src/components/MarkRating/index.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { IonIcon } from '@ionic/react';
|
||||
import './style.css';
|
||||
import { star, starOutline } from 'ionicons/icons';
|
||||
|
||||
interface ContainerProps {
|
||||
num_rating: number;
|
||||
max_rating?: number;
|
||||
}
|
||||
|
||||
const MarkRating: React.FC<ContainerProps> = ({ num_rating, max_rating = 3 }) => {
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: '0.5rem' }}>
|
||||
{Array.from({ length: max_rating }, (_, index) => {
|
||||
if (index < num_rating) {
|
||||
return (
|
||||
<div style={{ width: '10vw', height: '10vw' }}>
|
||||
<IonIcon size="large" color="warning" icon={star}></IonIcon>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div style={{ width: '10vw', height: '10vw' }}>
|
||||
<IonIcon size="large" color="warning" style={{ opacity: '0.3' }} icon={starOutline}></IonIcon>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MarkRating;
|
@@ -0,0 +1,68 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import './style.css';
|
||||
|
||||
interface AudioSeekBarProps {
|
||||
value: number;
|
||||
range_list: number[];
|
||||
}
|
||||
|
||||
const MatchmakingProgressBar: FunctionComponent<AudioSeekBarProps> = ({ value, range_list }) => {
|
||||
let test_value = value;
|
||||
let scale = [];
|
||||
|
||||
for (let i = 0; i < range_list.length; i++) {
|
||||
let scale_full_range = range_list[i];
|
||||
let scale_range = i > 0 ? scale_full_range - range_list[i - 1] : scale_full_range;
|
||||
|
||||
let fill = 0;
|
||||
if (test_value > scale_full_range) {
|
||||
fill = scale_full_range;
|
||||
} else {
|
||||
if (i > 0) {
|
||||
fill = Math.max(0, test_value - range_list[i - 1]);
|
||||
} else {
|
||||
fill = test_value;
|
||||
}
|
||||
}
|
||||
let fill_pct = Math.min(fill / scale_range, 1);
|
||||
scale.push([fill_pct]);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
{scale.map((item: any, index: number) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: `calc( 100% / ${range_list.length} )`,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<div style={{ fontSize: '0.5rem' }}>{range_list[index]}</div>
|
||||
<div
|
||||
className={`audioSeekBar playBar_t__seek`}
|
||||
style={{
|
||||
// border: '1px solid #000',
|
||||
borderTop: '1px solid #000',
|
||||
borderBottom: '1px solid #000',
|
||||
borderLeft: index == 0 ? '1px solid #000' : '',
|
||||
borderRight: index == scale.length - 1 ? '1px solid #000' : '',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<div style={{ width: `${item * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MatchmakingProgressBar;
|
@@ -0,0 +1,29 @@
|
||||
.audioSeekBar {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.audioSeekBar__tick {
|
||||
background-color: rgb(56, 182, 255);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.playBar_t {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: #1c192f;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
|
||||
.playBar_t__seek {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
/* border-radius: 12px; */
|
||||
margin-right: 0px;
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
import { IonButton, IonIcon, IonModal } from '@ionic/react';
|
||||
import { closeOutline } from 'ionicons/icons';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { COLOR_TEXT, CONNECTIVE_CONQUEROR_STAGES, DEBUG } from '../../../../constants';
|
||||
import './style.css';
|
||||
import { useConnectivesRevisionCorrectCount } from '../../../../contexts/MyIonMetric/ConnectivesRevisionCorrectCount';
|
||||
|
||||
function CongratConnectiveConqueror({}: {}) {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
const [opened, setOpened] = useState(false);
|
||||
// const message = '';
|
||||
const [message, setMessage] = useState('');
|
||||
const [openCongratListeningProgress, setOpenCongratListeningProgress] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [count_to_prompt, setCountToPrompt] = useState(0);
|
||||
|
||||
const {
|
||||
myIonMetricGetConnectivesRevisionCorrectCount,
|
||||
myIonMetricGetConnectivesRevisionCorrectCountIgnore,
|
||||
myIonMetricSetConnectivesRevisionCorrectCountIgnore,
|
||||
} = useConnectivesRevisionCorrectCount();
|
||||
|
||||
const dismiss_modal = () => {
|
||||
myIonMetricSetConnectivesRevisionCorrectCountIgnore(count_to_prompt);
|
||||
setOpened(false);
|
||||
};
|
||||
|
||||
async function promptProgress(count_to_prompt: number) {
|
||||
if (count_to_prompt == (await myIonMetricGetConnectivesRevisionCorrectCountIgnore()).count) return;
|
||||
// setCountPrompted(count_to_prompt);
|
||||
setMessage('You scored full marks ' + count_to_prompt + ' times.');
|
||||
// setOpenedCongraFullmarkTimes(true);
|
||||
setOpened(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const prompt_list = CONNECTIVE_CONQUEROR_STAGES;
|
||||
const b_prompt_list = Array(prompt_list.length).fill(false);
|
||||
|
||||
for (let i = 0; i < prompt_list.length; i++) {
|
||||
if (progress > prompt_list[i]) {
|
||||
b_prompt_list[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
let lastTrueIndex = -1;
|
||||
for (let i = b_prompt_list.length - 1; i >= 0; i--) {
|
||||
if (b_prompt_list[i]) {
|
||||
lastTrueIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastTrueIndex == -1) {
|
||||
DEBUG ? console.log('prompt ignored') : '';
|
||||
} else {
|
||||
setCountToPrompt(prompt_list[lastTrueIndex]);
|
||||
promptProgress(prompt_list[lastTrueIndex]);
|
||||
}
|
||||
}, [progress]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let temp = await myIonMetricGetConnectivesRevisionCorrectCount();
|
||||
setProgress(temp.count);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonModal
|
||||
id="congratulation-listening-progress-modal"
|
||||
ref={modal}
|
||||
trigger="open-custom-dialog"
|
||||
onIonModalWillDismiss={() => dismiss_modal()}
|
||||
// 008_remove_achievement_announcement, hide congratulation modal
|
||||
isOpen={false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
color: COLOR_TEXT,
|
||||
}}
|
||||
>
|
||||
{DEBUG ? <span className={'debug'}>congratulation connectives conqueror</span> : <></>}
|
||||
|
||||
<div>
|
||||
<h1>Congratulation!</h1>
|
||||
</div>
|
||||
|
||||
<div>{'Genius'}</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
color="light"
|
||||
shape="round"
|
||||
style={{ position: 'absolute', top: '5px', right: '5px' }}
|
||||
onClick={() => dismiss_modal()}
|
||||
>
|
||||
<IonIcon slot="icon-only" icon={closeOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CongratConnectiveConqueror;
|
@@ -0,0 +1,21 @@
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal {
|
||||
--height: 33%;
|
||||
--width: 80%;
|
||||
--border-radius: 16px;
|
||||
--box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal::part(backdrop) {
|
||||
/* background: rgba(209, 213, 219); */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal ion-toolbar {
|
||||
/* --background: rgb(14 116 144); */
|
||||
/* --color: white; */
|
||||
--color: black;
|
||||
}
|
@@ -0,0 +1,122 @@
|
||||
import { IonButton, IonIcon, IonModal } from '@ionic/react';
|
||||
import { closeOutline } from 'ionicons/icons';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { COLOR_TEXT, DEBUG, GENIUS_STAGES } from '../../../../constants';
|
||||
import './style.css';
|
||||
import { useAppStateContext } from '../../../../contexts/AppState';
|
||||
import { useFullmarkCount } from '../../../../contexts/MyIonMetric/FullmarkCount';
|
||||
|
||||
function CongratGenius({}: {}) {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
const [opened, setOpened] = useState(false);
|
||||
// const message = '';
|
||||
const [message, setMessage] = useState('');
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [count_to_prompt, setCountToPrompt] = useState(0);
|
||||
|
||||
const { myIonMetricGetFullMarkCount, myIonMetricGetFullMarkIgnore, myIonMetricSetFullMarkIgnore } =
|
||||
useFullmarkCount();
|
||||
|
||||
const dismiss_modal = () => {
|
||||
myIonMetricSetFullMarkIgnore(count_to_prompt);
|
||||
setOpened(false);
|
||||
};
|
||||
|
||||
async function promptGeniusProgress(count_to_prompt: number) {
|
||||
if (count_to_prompt == (await myIonMetricGetFullMarkIgnore()).count) return;
|
||||
// setCountPrompted(count_to_prompt);
|
||||
setMessage('You scored full marks ' + count_to_prompt + ' times.');
|
||||
// setOpenedCongraFullmarkTimes(true);
|
||||
setOpened(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const prompt_list = GENIUS_STAGES;
|
||||
const b_prompt_list = Array(prompt_list.length).fill(false);
|
||||
|
||||
for (let i = 0; i < prompt_list.length; i++) {
|
||||
if (progress >= prompt_list[i]) {
|
||||
b_prompt_list[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
let lastTrueIndex = -1;
|
||||
for (let i = b_prompt_list.length - 1; i >= 0; i--) {
|
||||
if (b_prompt_list[i]) {
|
||||
lastTrueIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastTrueIndex == -1) {
|
||||
DEBUG ? console.log('prompt ignored') : '';
|
||||
} else {
|
||||
console.log({ progress });
|
||||
setCountToPrompt(prompt_list[lastTrueIndex]);
|
||||
promptGeniusProgress(prompt_list[lastTrueIndex]);
|
||||
}
|
||||
}, [progress]);
|
||||
|
||||
let { tab_active } = useAppStateContext();
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let temp = await myIonMetricGetFullMarkCount();
|
||||
setProgress(temp.count);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonModal
|
||||
id="congratulation-listening-progress-modal"
|
||||
ref={modal}
|
||||
trigger="open-custom-dialog"
|
||||
onIonModalWillDismiss={() => dismiss_modal()}
|
||||
// 008_remove_achievement_announcement, hide congratulation modal
|
||||
isOpen={false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
color: COLOR_TEXT,
|
||||
}}
|
||||
>
|
||||
{DEBUG ? <span className={'debug'}>congratulation genius</span> : <></>}
|
||||
<div>
|
||||
<h1>Congratulation!</h1>
|
||||
</div>
|
||||
|
||||
<div>{'Genius'}</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
color="light"
|
||||
shape="round"
|
||||
style={{ position: 'absolute', top: '5px', right: '5px' }}
|
||||
onClick={() => dismiss_modal()}
|
||||
>
|
||||
<IonIcon slot="icon-only" icon={closeOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CongratGenius;
|
@@ -0,0 +1,21 @@
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal {
|
||||
--height: 33%;
|
||||
--width: 80%;
|
||||
--border-radius: 16px;
|
||||
--box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal::part(backdrop) {
|
||||
/* background: rgba(209, 213, 219); */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal ion-toolbar {
|
||||
/* --background: rgb(14 116 144); */
|
||||
/* --color: white; */
|
||||
--color: black;
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
import { IonButton, IonIcon, IonModal } from '@ionic/react';
|
||||
import { closeOutline } from 'ionicons/icons';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { COLOR_TEXT, DEBUG, HARDWORKER_STAGES } from '../../../../constants';
|
||||
import './style.css';
|
||||
import { useAppUseTime } from '../../../../contexts/MyIonMetric/AppUseTime';
|
||||
|
||||
function CongratHardworker({}: {}) {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
const [opened, setOpened] = useState(false);
|
||||
// const message = '';
|
||||
const [message, setMessage] = useState('');
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [count_to_prompt, setCountToPrompt] = useState(0);
|
||||
|
||||
const {
|
||||
//
|
||||
myIonMetricGetAppUseTime,
|
||||
myIonMetricSetAppUseTimeIgnore,
|
||||
myIonMetricGetAppUseTimeIgnore,
|
||||
} = useAppUseTime();
|
||||
|
||||
const dismiss_modal = () => {
|
||||
myIonMetricSetAppUseTimeIgnore(count_to_prompt);
|
||||
setOpened(false);
|
||||
};
|
||||
|
||||
async function promptHardworkerProgress(count_to_prompt: number) {
|
||||
if (count_to_prompt == (await myIonMetricGetAppUseTimeIgnore()).time_spent_s) return;
|
||||
// setCountPrompted(count_to_prompt);
|
||||
setMessage('You scored full marks ' + count_to_prompt + ' times.');
|
||||
// setOpenedCongraFullmarkTimes(true);
|
||||
setOpened(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const prompt_list = HARDWORKER_STAGES;
|
||||
const b_prompt_list = Array(prompt_list.length).fill(false);
|
||||
|
||||
for (let i = 0; i < prompt_list.length; i++) {
|
||||
if (progress >= prompt_list[i]) {
|
||||
b_prompt_list[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
let lastTrueIndex = -1;
|
||||
for (let i = b_prompt_list.length - 1; i >= 0; i--) {
|
||||
if (b_prompt_list[i]) {
|
||||
lastTrueIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastTrueIndex == -1) {
|
||||
DEBUG ? console.log('prompt ignored') : '';
|
||||
} else {
|
||||
console.log({ progress });
|
||||
setCountToPrompt(prompt_list[lastTrueIndex]);
|
||||
promptHardworkerProgress(prompt_list[lastTrueIndex]);
|
||||
}
|
||||
}, [progress]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let temp = await myIonMetricGetAppUseTime();
|
||||
|
||||
setProgress(Math.floor(temp.time_spent_s / 60));
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonModal
|
||||
id="congratulation-listening-progress-modal"
|
||||
ref={modal}
|
||||
trigger="open-custom-dialog"
|
||||
onIonModalWillDismiss={() => dismiss_modal()}
|
||||
// 008_remove_achievement_announcement, hide congratulation modal
|
||||
isOpen={false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
color: COLOR_TEXT,
|
||||
}}
|
||||
>
|
||||
{DEBUG ? <span className={'debug'}>congratulation hardworker</span> : <></>}
|
||||
<div>
|
||||
<h1>Congratulation!</h1>
|
||||
</div>
|
||||
|
||||
<div>{'Genius'}</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
color="light"
|
||||
shape="round"
|
||||
style={{ position: 'absolute', top: '5px', right: '5px' }}
|
||||
onClick={() => dismiss_modal()}
|
||||
>
|
||||
<IonIcon slot="icon-only" icon={closeOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CongratHardworker;
|
@@ -0,0 +1,21 @@
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal {
|
||||
--height: 33%;
|
||||
--width: 80%;
|
||||
--border-radius: 16px;
|
||||
--box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal::part(backdrop) {
|
||||
/* background: rgba(209, 213, 219); */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal ion-toolbar {
|
||||
/* --background: rgb(14 116 144); */
|
||||
/* --color: white; */
|
||||
--color: black;
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
import { IonButton, IonIcon, IonModal } from '@ionic/react';
|
||||
import { closeOutline } from 'ionicons/icons';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { COLOR_TEXT } from '../../../../constants';
|
||||
import './style.css';
|
||||
|
||||
function CongratHelloworld({}: {}) {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
const [opened, setOpened] = useState(false);
|
||||
// const message = '';
|
||||
const [message, setMessage] = useState('');
|
||||
const [openCongratListeningProgress, setOpenCongratListeningProgress] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [count_to_prompt, setCountToPrompt] = useState(0);
|
||||
|
||||
const dismiss_modal = () => {
|
||||
setOpened(false);
|
||||
};
|
||||
|
||||
useEffect(() => {}, [progress]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonModal
|
||||
id="congratulation-listening-progress-modal"
|
||||
ref={modal}
|
||||
trigger="open-custom-dialog"
|
||||
onIonModalWillDismiss={() => dismiss_modal()}
|
||||
isOpen={opened}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
color: COLOR_TEXT,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h1>Congratulation!</h1>
|
||||
</div>
|
||||
|
||||
<div>{'Genius'}</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
color="light"
|
||||
shape="round"
|
||||
style={{ position: 'absolute', top: '5px', right: '5px' }}
|
||||
onClick={() => dismiss_modal()}
|
||||
>
|
||||
<IonIcon slot="icon-only" icon={closeOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CongratHelloworld;
|
@@ -0,0 +1,21 @@
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal {
|
||||
--height: 33%;
|
||||
--width: 80%;
|
||||
--border-radius: 16px;
|
||||
--box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal::part(backdrop) {
|
||||
/* background: rgba(209, 213, 219); */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal ion-toolbar {
|
||||
/* --background: rgb(14 116 144); */
|
||||
/* --color: white; */
|
||||
--color: black;
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
import { IonButton, IonIcon, IonModal } from '@ionic/react';
|
||||
import { closeOutline } from 'ionicons/icons';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ATTENTIVE_EARS_STAGES, COLOR_TEXT, DEBUG } from '../../../../constants';
|
||||
import { useListeningPracticeTimeSpent } from '../../../../contexts/MyIonMetric/ListeningPracticeTimeSpent';
|
||||
import './style.css';
|
||||
|
||||
function CongratListeningProgress({}: {}) {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
const [opened, setOpened] = useState(false);
|
||||
// const message = '';
|
||||
const [message, setMessage] = useState('');
|
||||
const [openCongratListeningProgress, setOpenCongratListeningProgress] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [count_to_prompt, setCountToPrompt] = useState(0);
|
||||
|
||||
const {
|
||||
myIonMetricGetListeningPracticeTimeSpent,
|
||||
myIonMetricGetListeningPracticeProgressIgnore,
|
||||
myIonMetricSetListeningPracticeProgressIgnore,
|
||||
} = useListeningPracticeTimeSpent();
|
||||
|
||||
const dismiss_modal = () => {
|
||||
myIonMetricSetListeningPracticeProgressIgnore(count_to_prompt);
|
||||
setOpened(false);
|
||||
};
|
||||
|
||||
async function promptListeningPracticeProgress(count_to_prompt: number) {
|
||||
if (count_to_prompt == (await myIonMetricGetListeningPracticeProgressIgnore()).time_spent_s) return;
|
||||
// setCountPrompted(count_to_prompt);
|
||||
setMessage('You scored full marks ' + count_to_prompt + ' times.');
|
||||
// setOpenedCongraFullmarkTimes(true);
|
||||
setOpened(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const prompt_list = ATTENTIVE_EARS_STAGES;
|
||||
const b_prompt_list = Array(prompt_list.length).fill(false);
|
||||
|
||||
for (let i = 0; i < prompt_list.length; i++) {
|
||||
if (progress > prompt_list[i]) {
|
||||
b_prompt_list[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
let lastTrueIndex = -1;
|
||||
for (let i = b_prompt_list.length - 1; i >= 0; i--) {
|
||||
if (b_prompt_list[i]) {
|
||||
lastTrueIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastTrueIndex == -1) {
|
||||
DEBUG ? console.log('prompt ignored') : '';
|
||||
} else {
|
||||
setCountToPrompt(prompt_list[lastTrueIndex]);
|
||||
promptListeningPracticeProgress(prompt_list[lastTrueIndex]);
|
||||
}
|
||||
}, [progress]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let temp = await myIonMetricGetListeningPracticeTimeSpent();
|
||||
setProgress(temp.time_spent_s / 60);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonModal
|
||||
id="congratulation-listening-progress-modal"
|
||||
ref={modal}
|
||||
trigger="open-custom-dialog"
|
||||
onIonModalWillDismiss={() => dismiss_modal()}
|
||||
// 008_remove_achievement_announcement, hide congratulation modal
|
||||
isOpen={false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
color: COLOR_TEXT,
|
||||
}}
|
||||
>
|
||||
{DEBUG ? <span className={'debug'}>congratulation listening progress</span> : <></>}
|
||||
|
||||
<div>
|
||||
<h1>Congratulation!</h1>
|
||||
</div>
|
||||
|
||||
<div>{'Genius'}</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
color="light"
|
||||
shape="round"
|
||||
style={{ position: 'absolute', top: '5px', right: '5px' }}
|
||||
onClick={() => dismiss_modal()}
|
||||
>
|
||||
<IonIcon slot="icon-only" icon={closeOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CongratListeningProgress;
|
@@ -0,0 +1,21 @@
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal {
|
||||
--height: 33%;
|
||||
--width: 80%;
|
||||
--border-radius: 16px;
|
||||
--box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal::part(backdrop) {
|
||||
/* background: rgba(209, 213, 219); */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal ion-toolbar {
|
||||
/* --background: rgb(14 116 144); */
|
||||
/* --color: white; */
|
||||
--color: black;
|
||||
}
|
@@ -0,0 +1,124 @@
|
||||
import { IonButton, IonIcon, IonModal } from '@ionic/react';
|
||||
import { closeOutline } from 'ionicons/icons';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { COLOR_TEXT, DEBUG, MATCHMAKING_STAGES } from '../../../../constants';
|
||||
import './style.css';
|
||||
import { useMatchingFrenzyCorrectCount } from '../../../../contexts/MyIonMetric/MatchingFrenzyCorrectCount';
|
||||
|
||||
function CongratMatchmaking({}: {}) {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
const [opened, setOpened] = useState(false);
|
||||
// const message = '';
|
||||
const [message, setMessage] = useState('');
|
||||
const [openCongratListeningProgress, setOpenCongratListeningProgress] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [count_to_prompt, setCountToPrompt] = useState(0);
|
||||
|
||||
const {
|
||||
myIonMetricGetMatchingFrenzyCorrectCount,
|
||||
myIonMetricGetMatchingFrenzyCorrectCountIgnore,
|
||||
myIonMetricSetMatchingFrenzyCorrectCountIgnore,
|
||||
} = useMatchingFrenzyCorrectCount();
|
||||
|
||||
const dismiss_modal = () => {
|
||||
myIonMetricSetMatchingFrenzyCorrectCountIgnore(count_to_prompt);
|
||||
setOpened(false);
|
||||
};
|
||||
|
||||
async function promptProgress(count_to_prompt: number) {
|
||||
if (count_to_prompt == (await myIonMetricGetMatchingFrenzyCorrectCountIgnore()).count) return;
|
||||
// setCountPrompted(count_to_prompt);
|
||||
setMessage('You scored full marks ' + count_to_prompt + ' times.');
|
||||
// setOpenedCongraFullmarkTimes(true);
|
||||
setOpened(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const prompt_list = MATCHMAKING_STAGES;
|
||||
const b_prompt_list = Array(prompt_list.length).fill(false);
|
||||
|
||||
for (let i = 0; i < prompt_list.length; i++) {
|
||||
if (progress > prompt_list[i]) {
|
||||
b_prompt_list[i] = true;
|
||||
}
|
||||
}
|
||||
|
||||
let lastTrueIndex = -1;
|
||||
for (let i = b_prompt_list.length - 1; i >= 0; i--) {
|
||||
if (b_prompt_list[i]) {
|
||||
lastTrueIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lastTrueIndex == -1) {
|
||||
DEBUG ? console.log('prompt ignored') : '';
|
||||
} else {
|
||||
setCountToPrompt(prompt_list[lastTrueIndex]);
|
||||
promptProgress(prompt_list[lastTrueIndex]);
|
||||
}
|
||||
}, [progress]);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let temp = await myIonMetricGetMatchingFrenzyCorrectCount();
|
||||
setProgress(temp.count);
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonModal
|
||||
id="congratulation-listening-progress-modal"
|
||||
ref={modal}
|
||||
trigger="open-custom-dialog"
|
||||
onIonModalWillDismiss={() => dismiss_modal()}
|
||||
// 008_remove_achievement_announcement, hide congratulation modal
|
||||
isOpen={false}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
color: COLOR_TEXT,
|
||||
}}
|
||||
>
|
||||
{DEBUG ? <span className={'debug'}>congratulation matchmaking</span> : <></>}
|
||||
|
||||
<div>
|
||||
<h1>Congratulation!</h1>
|
||||
</div>
|
||||
|
||||
<div>{'Genius'}</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
color="light"
|
||||
shape="round"
|
||||
style={{ position: 'absolute', top: '5px', right: '5px' }}
|
||||
onClick={() => dismiss_modal()}
|
||||
>
|
||||
<IonIcon slot="icon-only" icon={closeOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CongratMatchmaking;
|
@@ -0,0 +1,21 @@
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal {
|
||||
--height: 33%;
|
||||
--width: 80%;
|
||||
--border-radius: 16px;
|
||||
--box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal::part(backdrop) {
|
||||
/* background: rgba(209, 213, 219); */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ion-modal#congratulation-listening-progress-modal ion-toolbar {
|
||||
/* --background: rgb(14 116 144); */
|
||||
/* --color: white; */
|
||||
--color: black;
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
import { IonButton, IonIcon, IonModal } from '@ionic/react';
|
||||
import { closeOutline } from 'ionicons/icons';
|
||||
import { useRef } from 'react';
|
||||
import { COLOR_TEXT } from '../../../constants';
|
||||
import { useFullmarkCount } from '../../../contexts/MyIonMetric/FullmarkCount';
|
||||
|
||||
function CongratulationAchievementModal({
|
||||
opened,
|
||||
setOpened,
|
||||
count_prompted,
|
||||
message = 'hello message',
|
||||
}: {
|
||||
message?: string;
|
||||
opened: boolean;
|
||||
count_prompted: number;
|
||||
setOpened: (opened: boolean) => void;
|
||||
}) {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
|
||||
const { myIonMetricSetFullMarkIgnore } = useFullmarkCount();
|
||||
const dismiss_modal = () => {
|
||||
myIonMetricSetFullMarkIgnore(count_prompted);
|
||||
setOpened(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonModal
|
||||
id="example-modal"
|
||||
ref={modal}
|
||||
trigger="open-custom-dialog"
|
||||
onIonModalWillDismiss={() => dismiss_modal()}
|
||||
isOpen={opened}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
color: COLOR_TEXT,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h1>Congratulation!</h1>
|
||||
</div>
|
||||
|
||||
<div>{'Genius'}</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
color="light"
|
||||
shape="round"
|
||||
style={{ position: 'absolute', top: '5px', right: '5px' }}
|
||||
onClick={() => dismiss_modal()}
|
||||
>
|
||||
<IonIcon slot="icon-only" icon={closeOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default CongratulationAchievementModal;
|
@@ -0,0 +1,58 @@
|
||||
import { IonButton, IonIcon, IonModal } from '@ionic/react';
|
||||
import { closeOutline } from 'ionicons/icons';
|
||||
import { useRef, useState } from 'react';
|
||||
import { COLOR_TEXT } from '../../../constants';
|
||||
|
||||
function HelloworldModal() {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
const [opened, setOpened] = useState(true);
|
||||
|
||||
return (
|
||||
<>
|
||||
<IonButton onClick={() => setOpened(true)}>Open</IonButton>
|
||||
<IonModal isOpen={opened} id="example-modal" ref={modal} trigger="open-custom-dialog">
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
color: COLOR_TEXT,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h1>Congratulation!</h1>
|
||||
</div>
|
||||
|
||||
<div>{'Genius'}</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<div>You scored full</div>
|
||||
<div>marks {'1000'} times!</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
color="light"
|
||||
shape="round"
|
||||
style={{ position: 'absolute', top: '5px', right: '5px' }}
|
||||
onClick={() => setOpened(false)}
|
||||
>
|
||||
<IonIcon slot="icon-only" icon={closeOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</IonModal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default HelloworldModal;
|
@@ -0,0 +1,87 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import './style.css';
|
||||
|
||||
interface AudioSeekBarProps {
|
||||
value: number;
|
||||
full_range: number;
|
||||
}
|
||||
|
||||
const MyProgressBar: FunctionComponent<AudioSeekBarProps> = ({ value, full_range }) => {
|
||||
let test_value = value;
|
||||
|
||||
let first_scale_full_range = 10;
|
||||
let first_scale = test_value > first_scale_full_range ? first_scale_full_range : test_value;
|
||||
//
|
||||
let second_scale_full_range = 50;
|
||||
let second_scale_range = second_scale_full_range - first_scale_full_range;
|
||||
let second_scale =
|
||||
test_value >= second_scale_full_range ? second_scale_full_range : Math.max(0, test_value - first_scale_full_range);
|
||||
|
||||
let third_scale_full_range = 100;
|
||||
let third_scale_range = third_scale_full_range - second_scale_full_range;
|
||||
let third_scale =
|
||||
test_value >= third_scale_full_range ? third_scale_full_range : Math.max(0, test_value - second_scale_full_range);
|
||||
let forth_scale_full_range = 300;
|
||||
let forth_scale_range = forth_scale_full_range - third_scale_full_range;
|
||||
let forth_scale =
|
||||
test_value >= forth_scale_full_range ? forth_scale_full_range : Math.max(0, test_value - third_scale_full_range);
|
||||
let fifth_scale_full_range = 700;
|
||||
let fifth_scale_range = fifth_scale_full_range - forth_scale_full_range;
|
||||
let fifth_scale =
|
||||
test_value >= fifth_scale_full_range ? fifth_scale_full_range : Math.max(0, test_value - forth_scale_full_range);
|
||||
|
||||
let sixth_scale_full_range = 1000;
|
||||
let sixth_scale_range = sixth_scale_full_range - fifth_scale_full_range;
|
||||
let sixth_scale =
|
||||
test_value >= sixth_scale_full_range ? sixth_scale_full_range : Math.max(0, test_value - fifth_scale_full_range);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<div style={{ width: 'calc( 100% / 6 )', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
|
||||
<div style={{ fontSize: '0.5rem' }}>{first_scale_full_range}</div>
|
||||
<div className={`audioSeekBar playBar_t__seek`}>
|
||||
<div style={{ width: `${(first_scale / first_scale_full_range) * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ width: 'calc( 100% / 6 )', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
|
||||
<div style={{ fontSize: '0.5rem' }}>{second_scale_full_range}</div>
|
||||
<div className={`audioSeekBar playBar_t__seek`}>
|
||||
<div style={{ width: `${(second_scale / second_scale_range) * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ width: 'calc( 100% / 6 )', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
|
||||
<div style={{ fontSize: '0.5rem' }}>{third_scale_full_range}</div>
|
||||
<div className={`audioSeekBar playBar_t__seek`}>
|
||||
<div style={{ width: `${(third_scale / third_scale_range) * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ width: 'calc( 100% / 6 )', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
|
||||
<div style={{ fontSize: '0.5rem' }}>{forth_scale_full_range}</div>
|
||||
<div className={`audioSeekBar playBar_t__seek`}>
|
||||
<div style={{ width: `${(forth_scale / forth_scale_range) * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ width: 'calc( 100% / 6 )', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
|
||||
<div style={{ fontSize: '0.5rem' }}>{fifth_scale_full_range}</div>
|
||||
<div className={`audioSeekBar playBar_t__seek`}>
|
||||
<div style={{ width: `${(fifth_scale / fifth_scale_range) * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style={{ width: 'calc( 100% / 6 )', display: 'flex', flexDirection: 'column', alignItems: 'flex-end' }}>
|
||||
<div style={{ fontSize: '0.5rem' }}>{sixth_scale_full_range}</div>
|
||||
<div className={`audioSeekBar playBar_t__seek`}>
|
||||
<div style={{ width: `${(sixth_scale / sixth_scale_range) * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyProgressBar;
|
@@ -0,0 +1,29 @@
|
||||
.audioSeekBar {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.audioSeekBar__tick {
|
||||
background-color: rgb(56, 182, 255);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.playBar_t {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: #1c192f;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 0px 50px;
|
||||
}
|
||||
|
||||
.playBar_t__seek {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
/* border-radius: 12px; */
|
||||
margin-right: 0px;
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import './style.css';
|
||||
|
||||
interface AudioSeekBarProps {
|
||||
value: number;
|
||||
full_range: number;
|
||||
}
|
||||
|
||||
const MyProgressBar: FunctionComponent<AudioSeekBarProps> = ({ value, full_range }) => {
|
||||
return (
|
||||
<>
|
||||
<div className={`audioSeekBar playBar_t__seek`}>
|
||||
<div style={{ width: `${(value / full_range) * 100}%` }} className="audioSeekBar__tick" />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default MyProgressBar;
|
@@ -0,0 +1,29 @@
|
||||
.audioSeekBar {
|
||||
cursor: pointer;
|
||||
background-color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.audioSeekBar__tick {
|
||||
background-color: #999900;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.playBar_t {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background-color: #1c192f;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 25px 50px;
|
||||
}
|
||||
|
||||
.playBar_t__seek {
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
border-radius: 12px;
|
||||
margin-right: 10px;
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="649.3779"
|
||||
height="727.77798"
|
||||
viewBox="0 0 649.3779 727.77798"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
role="img"
|
||||
artist="Katerina Limpitsouni"
|
||||
source="https://undraw.co/"
|
||||
><path
|
||||
d="M648.20033,259.05824h-3.99878V149.51291A63.40187,63.40187,0,0,0,580.79976,86.111H348.713a63.40184,63.40184,0,0,0-63.402,63.4017V750.48713A63.40181,63.40181,0,0,0,348.71284,813.889H580.79945a63.40185,63.40185,0,0,0,63.402-63.40167V337.0345h3.99884Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
d="M583.3578,102.606h-30.295a22.49485,22.49485,0,0,1-20.82715,30.99053H399.2762a22.49484,22.49484,0,0,1-20.82715-30.99061H350.15346a47.34781,47.34781,0,0,0-47.34784,47.34774V750.04628a47.34781,47.34781,0,0,0,47.34777,47.34784H583.3578a47.34781,47.34781,0,0,0,47.34784-47.34778h0V149.95371A47.34777,47.34777,0,0,0,583.3578,102.606Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#fff"
|
||||
/><path
|
||||
id="f3818c68-126c-4685-b4e0-2450731ccc2a-761"
|
||||
data-name="a2804879-ded6-4045-b20f-1f1dde9b938b"
|
||||
d="M611.46248,573.92982h-279.619a5.184,5.184,0,0,1-5.178-5.178v-69.361a5.184,5.184,0,0,1,5.178-5.178h279.619a5.184,5.184,0,0,1,5.178,5.178v69.362A5.184,5.184,0,0,1,611.46248,573.92982Zm-279.619-77.646a3.11,3.11,0,0,0-3.107,3.107v69.362a3.11,3.11,0,0,0,3.107,3.107h279.619a3.11,3.11,0,0,0,3.107-3.107v-69.362a3.11,3.11,0,0,0-3.107-3.107Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><circle
|
||||
id="abdb74b7-e321-430b-89c2-b563f66442fc"
|
||||
data-name="b9ad11c9-d8a0-4df6-8741-900b9ec46a35"
|
||||
cx="82.77841"
|
||||
cy="447.95985"
|
||||
r="21.74799"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="addd02b1-b85b-481a-baea-1b0ba5ed9f4a-762"
|
||||
data-name="bd261eec-7ae0-49b0-bf26-57ff03972605"
|
||||
d="M418.31746,519.57286a3.625,3.625,0,0,0,0,7.249h170.878a3.625,3.625,0,0,0,.13989-7.24866l-.02087-.00033q-.05943-.001-.119,0Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="a42dc2a4-5fb2-4ea8-b2b3-ce81bc256782-763"
|
||||
data-name="e80b4447-8c34-435b-ba6c-5300a190ab24"
|
||||
d="M418.31746,541.32087a3.625,3.625,0,0,0,0,7.249h170.878a3.625,3.625,0,0,0,.119-7.249q-.05943-.00092-.119,0Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="b84687c4-e3b4-4975-8361-bf73c33c9ee5-764"
|
||||
data-name="e55fcb7d-3a3b-45d8-b167-72fb2263dd92"
|
||||
d="M611.46248,689.91981h-279.619a5.184,5.184,0,0,1-5.178-5.178v-69.361a5.184,5.184,0,0,1,5.178-5.178h279.619a5.184,5.184,0,0,1,5.178,5.178v69.362A5.184,5.184,0,0,1,611.46248,689.91981Zm-279.619-77.646a3.11,3.11,0,0,0-3.107,3.107v69.362a3.11,3.11,0,0,0,3.107,3.107h279.619a3.11,3.11,0,0,0,3.107-3.107v-69.362a3.11,3.11,0,0,0-3.107-3.107Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><circle
|
||||
id="fe3713e7-4e14-41f8-af1d-48b338e5371c"
|
||||
data-name="a50d232f-7710-43e4-8fa9-6ef0443fc454"
|
||||
cx="82.77841"
|
||||
cy="563.94987"
|
||||
r="21.74799"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="e34cf46a-1d6c-4c41-b5e1-331fa5bf8d4c-765"
|
||||
data-name="e49b4965-a9e9-4371-9134-194e44e65c31"
|
||||
d="M418.31746,635.56288a3.625,3.625,0,0,0,0,7.249h170.878a3.625,3.625,0,0,0,.119-7.249q-.05943-.001-.119,0Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="e105039f-b7a6-49c8-8f81-87505f1b0ae5-766"
|
||||
data-name="abc4586a-ac92-4255-aae6-84f53baad571"
|
||||
d="M418.31746,657.31086a3.625,3.625,0,0,0,0,7.249h170.878a3.625,3.625,0,0,0,.119-7.249q-.05943-.00092-.119,0Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="ad9187ec-89e0-4b9d-a4fb-dc654c09bafe-767"
|
||||
data-name="a9e593af-a319-4e97-9065-f0c2c04624d5"
|
||||
d="M465.76845,429.99485a98.343,98.343,0,0,1-98.384-98.30194v-.08206c0-.206,0-.423.012-.629.3-53.879,44.432-97.756,98.372-97.756a98.384,98.384,0,0,1,.0224,196.768h-.0224Zm0-194.7a96.519,96.519,0,0,0-96.3,95.749c-.011.22-.011.4-.011.564a96.325,96.325,0,1,0,96.337-96.313h-.026Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><circle cx="315.11393" cy="422.84174" r="40" fill="#fff" /><path
|
||||
d="M586.53265,526.94884c-.06861,0-.13721-.00049-.20606-.00195h-21.5a10.39761,10.39761,0,0,1-.0083-20.79248h21.51807c.10547-.00195.22021-.00195.334,0a10.39771,10.39771,0,0,1-.13769,20.79443Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#6c63ff"
|
||||
/><rect x="325.3453" y="725.34374" width="324.03261" height="2.24072" fill="#3f3d56" /><path
|
||||
d="M630.6207,621.842a6.56111,6.56111,0,0,1-.62973-1.259l-5.16339-13.82657a6.50779,6.50779,0,0,1,3.81532-8.36324l115.24228-43.03187a6.50621,6.50621,0,0,1,8.363,3.81491l5.16283,13.82751a6.49951,6.49951,0,0,1-3.81558,8.36283l-115.2412,43.03174a6.5067,6.5067,0,0,1-7.73351-2.55634Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M743.51834,551.78826l-46.86323,18.68723a6,6,0,0,0-4.093,7.1996l5.43852,21.77515a6,6,0,0,0,8.37642,3.97481l46.99241-22.1188a6.0109,6.0109,0,0,0,3.52446-7.717l-5.64883-18.27558A6.01072,6.01072,0,0,0,743.51834,551.78826Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M829.28044,687.45a6.50671,6.50671,0,0,1-6.89226-4.34039l-40.114-116.289a6.49951,6.49951,0,0,1,4.02482-8.26416l13.95295-4.81352a6.50408,6.50408,0,0,1,8.26373,4.02536l40.11465,116.2899a6.50779,6.50779,0,0,1-4.02531,8.26422l-13.95257,4.8125A6.56115,6.56115,0,0,1,829.28044,687.45Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M811.5058,555.77814l15.39577,48.04524a6,6,0,0,1-2.95088,7.73816l-20.26024,9.657a6,6,0,0,1-8.39566-3.934l-12.83035-50.328a6.0109,6.0109,0,0,1,3.71178-7.62871l17.694-7.26835A6.01071,6.01071,0,0,1,811.5058,555.77814Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M764.89068,812.11413H750.13141a6.50753,6.50753,0,0,1-6.5-6.5V682.59948a6.50753,6.50753,0,0,1,6.5-6.5h14.75927a6.50753,6.50753,0,0,1,6.5,6.5V805.61413A6.50753,6.50753,0,0,1,764.89068,812.11413Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M794.08307,812.11413H779.3233a6.50753,6.50753,0,0,1-6.5-6.5V682.59948a6.50753,6.50753,0,0,1,6.5-6.5h14.75977a6.50753,6.50753,0,0,1,6.5,6.5V805.61413A6.50753,6.50753,0,0,1,794.08307,812.11413Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M806.2467,697.06158H739.1842a7.00787,7.00787,0,0,1-7-7V581.11725a40.53125,40.53125,0,0,1,81.0625,0V690.06158A7.00786,7.00786,0,0,1,806.2467,697.06158Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#ccc"
|
||||
/><path
|
||||
d="M816.34118,723.711h-34V531.55621l.6316.17236a45.38181,45.38181,0,0,1,33.3684,43.6875Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M760.32629,723.711h-34V575.41607a45.38226,45.38226,0,0,1,33.36841-43.6875l.63159-.17236Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><circle cx="486.89704" cy="378.42061" r="53.51916" fill="#6c63ff" /><path
|
||||
d="M832.71545,452.21s3-83-36-56c0,0-22.5-75.5-77.5,37.5l-16,16s72-10,139,21Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M773.02436,491.03636c-3.30591-.09179-7.42029-.20654-10.59-2.522a8.13272,8.13272,0,0,1-3.20008-6.07275,5.47091,5.47091,0,0,1,1.86035-4.49317c1.65552-1.39892,4.073-1.727,6.67823-.96142l-2.69922-19.72559,1.98144-.27148,3.17322,23.18994-1.65466-.75928c-1.91834-.87988-4.55164-1.32764-6.188.05518a3.51513,3.51513,0,0,0-1.15271,2.8955,6.14685,6.14685,0,0,0,2.38123,4.52783c2.46667,1.80176,5.74621,2.03418,9.46582,2.13819Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><rect x="459.19183" y="372.85162" width="10.77161" height="2" fill="#2f2e41" /><rect
|
||||
x="493.19183"
|
||||
y="372.85162"
|
||||
width="10.77161"
|
||||
height="2"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M652.88209,626.94393a6.00014,6.00014,0,0,1-7.96794-2.91753l-34.511-74.38406a6,6,0,0,1,10.88547-5.0504l34.511,74.38406A6.00012,6.00012,0,0,1,652.88209,626.94393Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#3f3d56"
|
||||
/><path
|
||||
d="M617.425,546.90538a4,4,0,0,1-4-4V491.95275h-50a4,4,0,0,1,0-8h54a4,4,0,0,1,4,4v54.95263A4,4,0,0,1,617.425,546.90538Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M618.371,552.55988a48,48,0,1,1,23.34031-63.74348A48.05437,48.05437,0,0,1,618.371,552.55988Zm-35.35277-76.19831a36,36,0,1,0,47.80761,17.50523A36.04072,36.04072,0,0,0,583.01827,476.36157Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#3f3d56"
|
||||
/></svg>
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1,94 @@
|
||||
import { IonButton, IonIcon, IonModal } from '@ionic/react';
|
||||
import { closeOutline } from 'ionicons/icons';
|
||||
import { useRef } from 'react';
|
||||
import MissingSvg from './image.svg';
|
||||
import './style.css';
|
||||
import { COLOR_TEXT } from '../../constants';
|
||||
|
||||
export interface NoFavoriteVocabModalProps {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
function NoFavoriteVocabModal({ open, setOpen }: NoFavoriteVocabModalProps) {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
|
||||
function dismiss() {
|
||||
modal.current?.dismiss();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonModal
|
||||
id="no-favorite-connectives-modal"
|
||||
ref={modal}
|
||||
trigger="open-custom-dialog"
|
||||
isOpen={open}
|
||||
onIonModalDidDismiss={() => setOpen(false)}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
padding: '1rem',
|
||||
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
color: COLOR_TEXT,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '33vw',
|
||||
width: '33vw',
|
||||
backgroundImage: `url(${MissingSvg})`,
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
></div>
|
||||
<div style={{ marginTop: '1rem' }}>You have currently no favorite</div>
|
||||
<div> Vocabulary</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
color="light"
|
||||
shape="round"
|
||||
style={{
|
||||
width: '1rem',
|
||||
height: '1rem',
|
||||
|
||||
position: 'absolute',
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
}}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<IonIcon slot={'icon-only'} icon={closeOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</div>
|
||||
</IonModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoFavoriteVocabModal;
|
@@ -0,0 +1,21 @@
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-modal#no-favorite-connectives-modal {
|
||||
--height: 33%;
|
||||
--width: 80%;
|
||||
--border-radius: 16px;
|
||||
--box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
ion-modal#no-favorite-connectives-modal::part(backdrop) {
|
||||
/* background: rgba(209, 213, 219); */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ion-modal#no-favorite-connectives-modal ion-toolbar {
|
||||
/* --background: rgb(14 116 144); */
|
||||
/* --color: white; */
|
||||
--color: black;
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="649.3779"
|
||||
height="727.77798"
|
||||
viewBox="0 0 649.3779 727.77798"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
role="img"
|
||||
artist="Katerina Limpitsouni"
|
||||
source="https://undraw.co/"
|
||||
><path
|
||||
d="M648.20033,259.05824h-3.99878V149.51291A63.40187,63.40187,0,0,0,580.79976,86.111H348.713a63.40184,63.40184,0,0,0-63.402,63.4017V750.48713A63.40181,63.40181,0,0,0,348.71284,813.889H580.79945a63.40185,63.40185,0,0,0,63.402-63.40167V337.0345h3.99884Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
d="M583.3578,102.606h-30.295a22.49485,22.49485,0,0,1-20.82715,30.99053H399.2762a22.49484,22.49484,0,0,1-20.82715-30.99061H350.15346a47.34781,47.34781,0,0,0-47.34784,47.34774V750.04628a47.34781,47.34781,0,0,0,47.34777,47.34784H583.3578a47.34781,47.34781,0,0,0,47.34784-47.34778h0V149.95371A47.34777,47.34777,0,0,0,583.3578,102.606Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#fff"
|
||||
/><path
|
||||
id="f3818c68-126c-4685-b4e0-2450731ccc2a-761"
|
||||
data-name="a2804879-ded6-4045-b20f-1f1dde9b938b"
|
||||
d="M611.46248,573.92982h-279.619a5.184,5.184,0,0,1-5.178-5.178v-69.361a5.184,5.184,0,0,1,5.178-5.178h279.619a5.184,5.184,0,0,1,5.178,5.178v69.362A5.184,5.184,0,0,1,611.46248,573.92982Zm-279.619-77.646a3.11,3.11,0,0,0-3.107,3.107v69.362a3.11,3.11,0,0,0,3.107,3.107h279.619a3.11,3.11,0,0,0,3.107-3.107v-69.362a3.11,3.11,0,0,0-3.107-3.107Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><circle
|
||||
id="abdb74b7-e321-430b-89c2-b563f66442fc"
|
||||
data-name="b9ad11c9-d8a0-4df6-8741-900b9ec46a35"
|
||||
cx="82.77841"
|
||||
cy="447.95985"
|
||||
r="21.74799"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="addd02b1-b85b-481a-baea-1b0ba5ed9f4a-762"
|
||||
data-name="bd261eec-7ae0-49b0-bf26-57ff03972605"
|
||||
d="M418.31746,519.57286a3.625,3.625,0,0,0,0,7.249h170.878a3.625,3.625,0,0,0,.13989-7.24866l-.02087-.00033q-.05943-.001-.119,0Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="a42dc2a4-5fb2-4ea8-b2b3-ce81bc256782-763"
|
||||
data-name="e80b4447-8c34-435b-ba6c-5300a190ab24"
|
||||
d="M418.31746,541.32087a3.625,3.625,0,0,0,0,7.249h170.878a3.625,3.625,0,0,0,.119-7.249q-.05943-.00092-.119,0Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="b84687c4-e3b4-4975-8361-bf73c33c9ee5-764"
|
||||
data-name="e55fcb7d-3a3b-45d8-b167-72fb2263dd92"
|
||||
d="M611.46248,689.91981h-279.619a5.184,5.184,0,0,1-5.178-5.178v-69.361a5.184,5.184,0,0,1,5.178-5.178h279.619a5.184,5.184,0,0,1,5.178,5.178v69.362A5.184,5.184,0,0,1,611.46248,689.91981Zm-279.619-77.646a3.11,3.11,0,0,0-3.107,3.107v69.362a3.11,3.11,0,0,0,3.107,3.107h279.619a3.11,3.11,0,0,0,3.107-3.107v-69.362a3.11,3.11,0,0,0-3.107-3.107Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><circle
|
||||
id="fe3713e7-4e14-41f8-af1d-48b338e5371c"
|
||||
data-name="a50d232f-7710-43e4-8fa9-6ef0443fc454"
|
||||
cx="82.77841"
|
||||
cy="563.94987"
|
||||
r="21.74799"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="e34cf46a-1d6c-4c41-b5e1-331fa5bf8d4c-765"
|
||||
data-name="e49b4965-a9e9-4371-9134-194e44e65c31"
|
||||
d="M418.31746,635.56288a3.625,3.625,0,0,0,0,7.249h170.878a3.625,3.625,0,0,0,.119-7.249q-.05943-.001-.119,0Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="e105039f-b7a6-49c8-8f81-87505f1b0ae5-766"
|
||||
data-name="abc4586a-ac92-4255-aae6-84f53baad571"
|
||||
d="M418.31746,657.31086a3.625,3.625,0,0,0,0,7.249h170.878a3.625,3.625,0,0,0,.119-7.249q-.05943-.00092-.119,0Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
id="ad9187ec-89e0-4b9d-a4fb-dc654c09bafe-767"
|
||||
data-name="a9e593af-a319-4e97-9065-f0c2c04624d5"
|
||||
d="M465.76845,429.99485a98.343,98.343,0,0,1-98.384-98.30194v-.08206c0-.206,0-.423.012-.629.3-53.879,44.432-97.756,98.372-97.756a98.384,98.384,0,0,1,.0224,196.768h-.0224Zm0-194.7a96.519,96.519,0,0,0-96.3,95.749c-.011.22-.011.4-.011.564a96.325,96.325,0,1,0,96.337-96.313h-.026Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#e6e6e6"
|
||||
/><circle cx="315.11393" cy="422.84174" r="40" fill="#fff" /><path
|
||||
d="M586.53265,526.94884c-.06861,0-.13721-.00049-.20606-.00195h-21.5a10.39761,10.39761,0,0,1-.0083-20.79248h21.51807c.10547-.00195.22021-.00195.334,0a10.39771,10.39771,0,0,1-.13769,20.79443Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#6c63ff"
|
||||
/><rect x="325.3453" y="725.34374" width="324.03261" height="2.24072" fill="#3f3d56" /><path
|
||||
d="M630.6207,621.842a6.56111,6.56111,0,0,1-.62973-1.259l-5.16339-13.82657a6.50779,6.50779,0,0,1,3.81532-8.36324l115.24228-43.03187a6.50621,6.50621,0,0,1,8.363,3.81491l5.16283,13.82751a6.49951,6.49951,0,0,1-3.81558,8.36283l-115.2412,43.03174a6.5067,6.5067,0,0,1-7.73351-2.55634Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M743.51834,551.78826l-46.86323,18.68723a6,6,0,0,0-4.093,7.1996l5.43852,21.77515a6,6,0,0,0,8.37642,3.97481l46.99241-22.1188a6.0109,6.0109,0,0,0,3.52446-7.717l-5.64883-18.27558A6.01072,6.01072,0,0,0,743.51834,551.78826Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M829.28044,687.45a6.50671,6.50671,0,0,1-6.89226-4.34039l-40.114-116.289a6.49951,6.49951,0,0,1,4.02482-8.26416l13.95295-4.81352a6.50408,6.50408,0,0,1,8.26373,4.02536l40.11465,116.2899a6.50779,6.50779,0,0,1-4.02531,8.26422l-13.95257,4.8125A6.56115,6.56115,0,0,1,829.28044,687.45Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M811.5058,555.77814l15.39577,48.04524a6,6,0,0,1-2.95088,7.73816l-20.26024,9.657a6,6,0,0,1-8.39566-3.934l-12.83035-50.328a6.0109,6.0109,0,0,1,3.71178-7.62871l17.694-7.26835A6.01071,6.01071,0,0,1,811.5058,555.77814Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M764.89068,812.11413H750.13141a6.50753,6.50753,0,0,1-6.5-6.5V682.59948a6.50753,6.50753,0,0,1,6.5-6.5h14.75927a6.50753,6.50753,0,0,1,6.5,6.5V805.61413A6.50753,6.50753,0,0,1,764.89068,812.11413Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M794.08307,812.11413H779.3233a6.50753,6.50753,0,0,1-6.5-6.5V682.59948a6.50753,6.50753,0,0,1,6.5-6.5h14.75977a6.50753,6.50753,0,0,1,6.5,6.5V805.61413A6.50753,6.50753,0,0,1,794.08307,812.11413Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M806.2467,697.06158H739.1842a7.00787,7.00787,0,0,1-7-7V581.11725a40.53125,40.53125,0,0,1,81.0625,0V690.06158A7.00786,7.00786,0,0,1,806.2467,697.06158Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#ccc"
|
||||
/><path
|
||||
d="M816.34118,723.711h-34V531.55621l.6316.17236a45.38181,45.38181,0,0,1,33.3684,43.6875Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M760.32629,723.711h-34V575.41607a45.38226,45.38226,0,0,1,33.36841-43.6875l.63159-.17236Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><circle cx="486.89704" cy="378.42061" r="53.51916" fill="#6c63ff" /><path
|
||||
d="M832.71545,452.21s3-83-36-56c0,0-22.5-75.5-77.5,37.5l-16,16s72-10,139,21Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M773.02436,491.03636c-3.30591-.09179-7.42029-.20654-10.59-2.522a8.13272,8.13272,0,0,1-3.20008-6.07275,5.47091,5.47091,0,0,1,1.86035-4.49317c1.65552-1.39892,4.073-1.727,6.67823-.96142l-2.69922-19.72559,1.98144-.27148,3.17322,23.18994-1.65466-.75928c-1.91834-.87988-4.55164-1.32764-6.188.05518a3.51513,3.51513,0,0,0-1.15271,2.8955,6.14685,6.14685,0,0,0,2.38123,4.52783c2.46667,1.80176,5.74621,2.03418,9.46582,2.13819Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#2f2e41"
|
||||
/><rect x="459.19183" y="372.85162" width="10.77161" height="2" fill="#2f2e41" /><rect
|
||||
x="493.19183"
|
||||
y="372.85162"
|
||||
width="10.77161"
|
||||
height="2"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M652.88209,626.94393a6.00014,6.00014,0,0,1-7.96794-2.91753l-34.511-74.38406a6,6,0,0,1,10.88547-5.0504l34.511,74.38406A6.00012,6.00012,0,0,1,652.88209,626.94393Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#3f3d56"
|
||||
/><path
|
||||
d="M617.425,546.90538a4,4,0,0,1-4-4V491.95275h-50a4,4,0,0,1,0-8h54a4,4,0,0,1,4,4v54.95263A4,4,0,0,1,617.425,546.90538Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M618.371,552.55988a48,48,0,1,1,23.34031-63.74348A48.05437,48.05437,0,0,1,618.371,552.55988Zm-35.35277-76.19831a36,36,0,1,0,47.80761,17.50523A36.04072,36.04072,0,0,0,583.01827,476.36157Z"
|
||||
transform="translate(-285.31105 -86.11101)"
|
||||
fill="#3f3d56"
|
||||
/></svg>
|
After Width: | Height: | Size: 8.4 KiB |
@@ -0,0 +1,95 @@
|
||||
import { IonButton, IonIcon, IonModal } from '@ionic/react';
|
||||
import { closeOutline } from 'ionicons/icons';
|
||||
import { useRef } from 'react';
|
||||
import MissingSvg from './image.svg';
|
||||
import './style.css';
|
||||
import { COLOR_TEXT } from '../../constants';
|
||||
|
||||
export interface NoFavoriteVocabModalProps {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
}
|
||||
|
||||
function NoFavoriteConnectivesModal({ open, setOpen }: NoFavoriteVocabModalProps) {
|
||||
const modal = useRef<HTMLIonModalElement>(null);
|
||||
|
||||
function dismiss() {
|
||||
modal.current?.dismiss();
|
||||
}
|
||||
|
||||
return (
|
||||
<IonModal
|
||||
id="no-favorite-vocalbulary-modal"
|
||||
ref={modal}
|
||||
trigger="open-custom-dialog"
|
||||
onWillDismiss={() => setOpen(false)}
|
||||
onIonModalDidDismiss={() => setOpen(false)}
|
||||
isOpen={open}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
padding: '1rem',
|
||||
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '1rem',
|
||||
color: COLOR_TEXT,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '0.25rem',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
height: '33vw',
|
||||
width: '33vw',
|
||||
backgroundImage: `url(${MissingSvg})`,
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
></div>
|
||||
<div style={{ marginTop: '1rem' }}>You have currently no favorite</div>
|
||||
<div>Connectives</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IonButton
|
||||
color="light"
|
||||
shape="round"
|
||||
style={{
|
||||
width: '1rem',
|
||||
height: '1rem',
|
||||
|
||||
position: 'absolute',
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
}}
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<IonIcon slot={'icon-only'} icon={closeOutline}></IonIcon>
|
||||
</IonButton>
|
||||
</div>
|
||||
</IonModal>
|
||||
);
|
||||
}
|
||||
|
||||
export default NoFavoriteConnectivesModal;
|
@@ -0,0 +1,21 @@
|
||||
.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-modal#no-favorite-vocalbulary-modal {
|
||||
--height: 33%;
|
||||
--width: 80%;
|
||||
--border-radius: 16px;
|
||||
--box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
ion-modal#no-favorite-vocalbulary-modal::part(backdrop) {
|
||||
/* background: rgba(209, 213, 219); */
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
ion-modal#no-favorite-vocalbulary-modal ion-toolbar {
|
||||
/* --background: rgb(14 116 144); */
|
||||
/* --color: white; */
|
||||
--color: black;
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
import { checkmarkOutline } from 'ionicons/icons';
|
||||
import './style.css';
|
||||
import { IonButton, IonIcon } from '@ionic/react';
|
||||
|
||||
interface ContainerProps {
|
||||
num_rating: number;
|
||||
num_full_rating: number;
|
||||
}
|
||||
|
||||
const QuestionProgress: React.FC<ContainerProps> = ({ num_rating = 0, num_full_rating = 2 }) => {
|
||||
return (
|
||||
<>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: '0.15rem' }}>
|
||||
{Array.from({ length: num_rating }, (_, index) => (
|
||||
<IonButton shape="round" fill="outline" size="small" color="success">
|
||||
<IonIcon slot="icon-only" icon={checkmarkOutline}></IonIcon>
|
||||
</IonButton>
|
||||
))}
|
||||
{Array.from({ length: num_full_rating - num_rating }, (_, index) => (
|
||||
<IonButton shape="round" fill="outline" size="small" color="medium">
|
||||
<IonIcon slot="icon-only"></IonIcon>
|
||||
</IonButton>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuestionProgress;
|
@@ -0,0 +1,3 @@
|
||||
.quizzes_main_menu_container {
|
||||
text-align: center;
|
||||
}
|
@@ -0,0 +1,95 @@
|
||||
import { IonButton, useIonRouter } from '@ionic/react';
|
||||
import './QuizzesMainMenuContainer.css';
|
||||
import { COLOR_TEXT, CONNECTIVE_REVISION_LINK, LISTENING_PRACTICE_LINK, MATCHING_FRENZY_LINK } from '../constants';
|
||||
|
||||
interface ContainerProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
const QuizzesMainMenuContainer: React.FC<ContainerProps> = ({ name }) => {
|
||||
const router = useIonRouter();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="quizzes_main_menu_container"
|
||||
style={{
|
||||
display: 'column',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<div style={{ margin: '1rem 1rem' }}>
|
||||
<IonButton
|
||||
expand="block"
|
||||
// href={LISTENING_PRACTICE_LINK}
|
||||
onClick={() => {
|
||||
router.push(LISTENING_PRACTICE_LINK, undefined, 'replace');
|
||||
}}
|
||||
fill="clear"
|
||||
>
|
||||
<div style={{ padding: '1rem' }}>
|
||||
<div
|
||||
style={{
|
||||
width: '60vw',
|
||||
height: '30vw',
|
||||
backgroundImage: `url('/data/Lesson/images/quiz_listening_practice.jpg')`,
|
||||
backgroundSize: 'cover',
|
||||
borderRadius: '1rem',
|
||||
}}
|
||||
></div>
|
||||
<div style={{ marginTop: '2rem', color: COLOR_TEXT }}>Listening Practice</div>
|
||||
</div>
|
||||
</IonButton>
|
||||
</div>
|
||||
<div style={{ margin: '1rem 1rem' }}>
|
||||
<IonButton
|
||||
expand="block"
|
||||
// href={MATCHING_FRENZY_LINK}
|
||||
onClick={() => {
|
||||
router.push(MATCHING_FRENZY_LINK, undefined, 'replace');
|
||||
}}
|
||||
fill="clear"
|
||||
>
|
||||
<div style={{ padding: '1rem' }}>
|
||||
<div
|
||||
style={{
|
||||
width: '60vw',
|
||||
height: '30vw',
|
||||
backgroundImage: `url('/data/Lesson/images/quiz_matching_frenzy.jpg')`,
|
||||
backgroundSize: 'cover',
|
||||
borderRadius: '1rem',
|
||||
}}
|
||||
></div>
|
||||
<div style={{ marginTop: '0.5rem', color: COLOR_TEXT }}>Matching Frenzy</div>
|
||||
</div>
|
||||
</IonButton>
|
||||
</div>
|
||||
<div style={{ margin: '1rem 1rem' }}>
|
||||
<IonButton
|
||||
expand="block"
|
||||
// href={CONNECTIVE_REVISION_LINK}
|
||||
onClick={() => {
|
||||
router.push(CONNECTIVE_REVISION_LINK, undefined, 'replace');
|
||||
}}
|
||||
fill="clear"
|
||||
>
|
||||
<div style={{ padding: '1rem' }}>
|
||||
<div
|
||||
style={{
|
||||
width: '60vw',
|
||||
height: '30vw',
|
||||
backgroundImage: `url('/data/Lesson/images/quiz_connectives_revision.jpg')`,
|
||||
backgroundSize: 'cover',
|
||||
borderRadius: '1rem',
|
||||
}}
|
||||
></div>
|
||||
<div style={{ marginTop: '0.5rem', color: COLOR_TEXT }}>Connectives Revision</div>
|
||||
</div>
|
||||
</IonButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuizzesMainMenuContainer;
|
@@ -0,0 +1,24 @@
|
||||
import { IonToast } from '@ionic/react';
|
||||
import React from 'react';
|
||||
import './style.css';
|
||||
import { THE_WORD_REMOVED, THE_WORD_REMOVED_DISMISS_TIMEOUT } from '../../constants';
|
||||
|
||||
const RemoveFavoritePrompt: React.FC<{ open: boolean; setIsOpen: (isOpen: boolean) => void }> = ({
|
||||
open,
|
||||
setIsOpen,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<IonToast
|
||||
className="remove-favorite-prompt-toast"
|
||||
isOpen={open}
|
||||
message={THE_WORD_REMOVED}
|
||||
onDidDismiss={() => setIsOpen(false)}
|
||||
color="success"
|
||||
duration={THE_WORD_REMOVED_DISMISS_TIMEOUT}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RemoveFavoritePrompt;
|
@@ -0,0 +1,11 @@
|
||||
ion-toast.remove-favorite-prompt-toast::part(message) {
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
}
|
||||
|
||||
/*
|
||||
ion-toast.remove-favorite-prompt-toast::part(container) {
|
||||
bottom: 100px;
|
||||
}
|
||||
*/
|
24
002_source/ionic_mobile/src/components/SettingContainer.css
Normal file
@@ -0,0 +1,24 @@
|
||||
.container {
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.container strong {
|
||||
font-size: 20px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.container p {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
color: #8c8c8c;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.container a {
|
||||
text-decoration: none;
|
||||
}
|
@@ -0,0 +1,135 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="845.87074"
|
||||
height="569.98927"
|
||||
viewBox="0 0 845.87074 569.98927"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
role="img"
|
||||
artist="Katerina Limpitsouni"
|
||||
source="https://undraw.co/"
|
||||
><polygon
|
||||
points="760.114 287.832 752.997 294.857 762.452 319.527 775.643 292.177 797.249 168.007 764.02 167.081 760.114 287.832"
|
||||
fill="#9f616a"
|
||||
/><polygon
|
||||
points="705.966 547.714 687.966 547.714 673.966 388.714 708.966 388.714 705.966 547.714"
|
||||
fill="#9f616a"
|
||||
/><polygon
|
||||
points="773.966 547.714 755.966 547.714 741.966 388.714 776.966 388.714 773.966 547.714"
|
||||
fill="#9f616a"
|
||||
/><path
|
||||
d="M937.09032,733.461l-26.27966-.13983a10.60246,10.60246,0,0,1-5.18583-13.20591h0a10.60249,10.60249,0,0,1,8.18437-6.73544l4.96424-.82737,4.32555-5.50525a8.19029,8.19029,0,0,1,10.758-2.00034c4.14944,2.63357,9.90449,3.511,17,2.92223l5.52291,13.4861c1.09439,7.29591-3.90074,12.10883-11.188,13.25945h0A13.43132,13.43132,0,0,1,937.09032,733.461Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M869.09032,733.461l-26.27966-.13983a10.60246,10.60246,0,0,1-5.18583-13.20591h0a10.60249,10.60249,0,0,1,8.18437-6.73544l4.96424-.82737,4.80952-6.12121a7.45937,7.45937,0,0,1,10.01669-1.55131c4.16748,2.7633,10.01393,3.69017,17.25728,3.08916l5.52291,13.4861c1.09439,7.29591-3.90074,12.10883-11.188,13.25945h0A13.43132,13.43132,0,0,1,869.09032,733.461Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#2f2e41"
|
||||
/><circle cx="710.9656" cy="55.71363" r="27" fill="#9f616a" /><path
|
||||
d="M927.03023,272.719l-44,3c.15576-14.2391-.46558-27.13579-4-35l31-10C910.3279,243.25667,916.90486,257.50461,927.03023,272.719Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#9f616a"
|
||||
/><path
|
||||
d="M954.03023,467.719l-113-9c15.0769-50.26016,10.35876-116.07391-5-165.41577a28.4717,28.4717,0,0,1,18.28887-26.58848L883.03023,255.719l36,4,30.356,15.178a27.71585,27.71585,0,0,1,15.31608,25.30937C953.29231,345.83844,951.91774,404.51285,954.03023,467.719Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M869.03023,344.719l-51-9L822.42,301.69848a42.20258,42.20258,0,0,1,30.84455-35.34011l9.76571-2.63938Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#6c63ff"
|
||||
/><polygon
|
||||
points="661.966 292.714 667.966 300.714 658.966 325.714 645.966 294.714 642.966 168.714 675.966 172.714 661.966 292.714"
|
||||
fill="#9f616a"
|
||||
/><path
|
||||
d="M925.03023,346.719l51-9-4.38975-34.02051a42.20256,42.20256,0,0,0-30.84455-35.34011l-9.7657-2.63938Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M957.03023,558.719l-41.83858,2-12.16142-59-14.76772,55-44.23228,1-8.8417-71.06584a41.09716,41.09716,0,0,1,8.84169-30.93414v0l110,5Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#2f2e41"
|
||||
/><path
|
||||
d="M890.53023,250.219l20.19008-3.82345,12.0114-20.51949a27.28013,27.28013,0,0,0-3.19387-31.95038l0,0a27.28016,27.28016,0,0,0-24.2073-8.837l-26.16686,3.73812a17.27884,17.27884,0,0,0-14.64408,19.66833l17.62513,2.99868Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#2f2e41"
|
||||
/><ellipse cx="701.4656" cy="63.21363" rx="5" ry="6" fill="#9f616a" /><path
|
||||
d="M303.53023,177.219a11.01245,11.01245,0,0,0-11,11v201a11.01245,11.01245,0,0,0,11,11h5a11.01245,11.01245,0,0,0,11-11v-201a11.01245,11.01245,0,0,0-11-11Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
d="M303.53023,181.719a6.50753,6.50753,0,0,0-6.5,6.5v201a6.50752,6.50752,0,0,0,6.5,6.5h5a6.50753,6.50753,0,0,0,6.5-6.5v-201a6.50753,6.50753,0,0,0-6.5-6.5Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M385.53023,177.219a11.01245,11.01245,0,0,0-11,11v201a11.01245,11.01245,0,0,0,11,11h5a11.01245,11.01245,0,0,0,11-11v-201a11.01245,11.01245,0,0,0-11-11Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
d="M385.53023,181.719a6.50753,6.50753,0,0,0-6.5,6.5v201a6.50752,6.50752,0,0,0,6.5,6.5h5a6.50753,6.50753,0,0,0,6.5-6.5v-201a6.50753,6.50753,0,0,0-6.5-6.5Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M467.53023,177.219a11.01245,11.01245,0,0,0-11,11v201a11.01245,11.01245,0,0,0,11,11h5a11.01245,11.01245,0,0,0,11-11v-201a11.01245,11.01245,0,0,0-11-11Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
d="M467.53023,181.719a6.50753,6.50753,0,0,0-6.5,6.5v201a6.50752,6.50752,0,0,0,6.5,6.5h5a6.50753,6.50753,0,0,0,6.5-6.5v-201a6.50753,6.50753,0,0,0-6.5-6.5Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M549.53023,177.219a11.01245,11.01245,0,0,0-11,11v201a11.01245,11.01245,0,0,0,11,11h5a11.01245,11.01245,0,0,0,11-11v-201a11.01245,11.01245,0,0,0-11-11Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
d="M549.53023,181.719a6.50753,6.50753,0,0,0-6.5,6.5v201a6.50752,6.50752,0,0,0,6.5,6.5h5a6.50753,6.50753,0,0,0,6.5-6.5v-201a6.50753,6.50753,0,0,0-6.5-6.5Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M631.53023,177.219a11.01245,11.01245,0,0,0-11,11v201a11.01245,11.01245,0,0,0,11,11h5a11.01245,11.01245,0,0,0,11-11v-201a11.01245,11.01245,0,0,0-11-11Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
d="M631.53023,181.719a6.50753,6.50753,0,0,0-6.5,6.5v201a6.50752,6.50752,0,0,0,6.5,6.5h5a6.50753,6.50753,0,0,0,6.5-6.5v-201a6.50753,6.50753,0,0,0-6.5-6.5Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M322.2878,242.2493H290.77265a10.25379,10.25379,0,0,1-10.24242-10.24243v-1.57576a10.25379,10.25379,0,0,1,10.24242-10.24242H322.2878a10.25379,10.25379,0,0,1,10.24243,10.24242v1.57576A10.25379,10.25379,0,0,1,322.2878,242.2493Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M406.2878,275.2493H374.77265a10.25379,10.25379,0,0,1-10.24242-10.24243v-1.57576a10.25379,10.25379,0,0,1,10.24242-10.24242H406.2878a10.25379,10.25379,0,0,1,10.24243,10.24242v1.57576A10.25379,10.25379,0,0,1,406.2878,275.2493Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M486.2878,340.2493H454.77265a10.25379,10.25379,0,0,1-10.24242-10.24243v-1.57576a10.25379,10.25379,0,0,1,10.24242-10.24242H486.2878a10.25379,10.25379,0,0,1,10.24243,10.24242v1.57576A10.25379,10.25379,0,0,1,486.2878,340.2493Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M568.2878,222.2493H536.77265a10.25379,10.25379,0,0,1-10.24242-10.24243v-1.57576a10.25379,10.25379,0,0,1,10.24242-10.24242H568.2878a10.25379,10.25379,0,0,1,10.24243,10.24242v1.57576A10.25379,10.25379,0,0,1,568.2878,222.2493Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M650.2878,300.2493H618.77265a10.25379,10.25379,0,0,1-10.24242-10.24243v-1.57576a10.25379,10.25379,0,0,1,10.24242-10.24242H650.2878a10.25379,10.25379,0,0,1,10.24243,10.24242v1.57576A10.25379,10.25379,0,0,1,650.2878,300.2493Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#6c63ff"
|
||||
/><polygon
|
||||
points="845.475 569.989 578.074 569.989 578.074 567.804 845.871 567.804 845.475 569.989"
|
||||
fill="#3f3d56"
|
||||
/><circle cx="129.4656" cy="275.21363" r="13" fill="#e6e6e6" /><circle
|
||||
cx="211.4656"
|
||||
cy="275.21363"
|
||||
r="13"
|
||||
fill="#e6e6e6"
|
||||
/><circle cx="293.4656" cy="275.21363" r="13" fill="#e6e6e6" /><circle
|
||||
cx="375.4656"
|
||||
cy="275.21363"
|
||||
r="13"
|
||||
fill="#e6e6e6"
|
||||
/><circle cx="457.4656" cy="275.21363" r="13" fill="#e6e6e6" /><path
|
||||
d="M205.86092,165.00536a28.79631,28.79631,0,1,0,28.79632,28.79629A28.79629,28.79629,0,0,0,205.86092,165.00536Zm0,53.33839a24.54208,24.54208,0,1,1,24.5421-24.5421A24.54208,24.54208,0,0,1,205.86092,218.34375Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#e6e6e6"
|
||||
/><path
|
||||
d="M204.26992,203.64975a1.51846,1.51846,0,0,1-1.07424-.44513L194.99111,195a1.5192,1.5192,0,0,1,2.14848-2.14847l6.95228,6.95228,12.32319-16.80442a1.51945,1.51945,0,0,1,2.45057,1.79712l-13.37042,18.23239a1.52072,1.52072,0,0,1-1.10955.61665C204.34708,203.64856,204.3082,203.64975,204.26992,203.64975Z"
|
||||
transform="translate(-177.06463 -165.00536)"
|
||||
fill="#6c63ff"
|
||||
/></svg>
|
After Width: | Height: | Size: 8.1 KiB |
@@ -0,0 +1,50 @@
|
||||
import { IonButton, IonIcon, useIonRouter } from '@ionic/react';
|
||||
import { arrowBack } from 'ionicons/icons';
|
||||
import { LESSON_LINK, VERSIONS } from '../../constants';
|
||||
import SettingSvg from './image.svg';
|
||||
|
||||
interface ContainerProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
const SettingContainer: React.FC<ContainerProps> = ({ name }) => {
|
||||
const router = useIonRouter();
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '85%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
backgroundImage: `url(${SettingSvg})`,
|
||||
height: '33vh',
|
||||
width: '33vh',
|
||||
backgroundSize: 'contain',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center',
|
||||
}}
|
||||
></div>
|
||||
<div>
|
||||
<p>T.B.A.</p>
|
||||
</div>
|
||||
<div>{VERSIONS}</div>
|
||||
<IonButton
|
||||
onClick={() => {
|
||||
router.push(LESSON_LINK, undefined, 'replace');
|
||||
}}
|
||||
style={{ marginTop: '1rem' }}
|
||||
>
|
||||
<IonIcon slot="start" icon={arrowBack}></IonIcon>
|
||||
Back
|
||||
</IonButton>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SettingContainer;
|
BIN
002_source/ionic_mobile/src/components/SpeakerImage/image.jpg
Normal file
After Width: | Height: | Size: 12 KiB |
@@ -0,0 +1,90 @@
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="863.91732"
|
||||
height="364.20537"
|
||||
viewBox="0 0 863.91732 364.20537"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
role="img"
|
||||
artist="Katerina Limpitsouni"
|
||||
source="https://undraw.co/"
|
||||
><polygon
|
||||
points="311.959 119.745 0 119.745 0 222.156 11.817 222.156 11.817 248.941 38.601 222.156 311.959 222.156 311.959 119.745"
|
||||
fill="#f0f0f0"
|
||||
/><rect x="8.66553" y="129.71814" width="294.62811" height="81.92868" fill="#fff" /><rect
|
||||
x="34.72148"
|
||||
y="154.42552"
|
||||
width="141.85589"
|
||||
height="4.30497"
|
||||
fill="#f0f0f0"
|
||||
/><rect x="34.72148" y="169.31777" width="247.24292" height="4.30497" fill="#f0f0f0" /><rect
|
||||
x="34.72148"
|
||||
y="184.21003"
|
||||
width="247.00081"
|
||||
height="4.30497"
|
||||
fill="#f0f0f0"
|
||||
/><path
|
||||
d="M692.07274,618.96676s1.487-31.15875,31.97119-27.537"
|
||||
transform="translate(-168.04134 -267.89732)"
|
||||
fill="#f0f0f0"
|
||||
/><circle cx="515.41796" cy="306.16087" r="15.2571" fill="#f0f0f0" /><rect
|
||||
x="512.9354"
|
||||
y="331.85294"
|
||||
width="4.30672"
|
||||
height="30.14703"
|
||||
fill="#f0f0f0"
|
||||
/><circle cx="666.92952" cy="180.07338" r="123.29665" fill="#3f3d56" /><path
|
||||
d="M757.348,457.86815a32.62688,32.62688,0,0,1,50.081,0,36.26372,36.26372,0,1,0-51.27085-1.18987Q756.73918,457.28694,757.348,457.86815Z"
|
||||
transform="translate(-168.04134 -267.89732)"
|
||||
fill="#fff"
|
||||
/><path
|
||||
d="M855.26,457.86815a32.627,32.627,0,0,1,50.08092,0,36.26371,36.26371,0,1,0-51.2708-1.18987Q854.65117,457.28694,855.26,457.86815Z"
|
||||
transform="translate(-168.04134 -267.89732)"
|
||||
fill="#fff"
|
||||
/><circle cx="601.97649" cy="151.39215" r="12.47434" fill="#3f3d56" /><circle
|
||||
cx="699.88499"
|
||||
cy="151.39215"
|
||||
r="12.47434"
|
||||
fill="#3f3d56"
|
||||
/><circle cx="578.08341" cy="210.89752" r="14.50548" fill="#6c63ff" /><circle
|
||||
cx="744.8965"
|
||||
cy="210.89752"
|
||||
r="14.5055"
|
||||
fill="#6c63ff"
|
||||
/><polygon points="661.49 181.886 650.611 229.029 668.742 210.898 661.49 181.886" fill="#6c63ff" /><polygon
|
||||
points="717.39 363.205 705.038 352.839 705.326 363.205 701.49 363.205 701.183 352.244 684.507 363.205 677.526 363.205 701.059 347.737 700.147 315.258 699.466 290.728 703.293 290.623 703.984 315.258 704.894 347.708 723.354 363.205 717.39 363.205"
|
||||
fill="#3f3d56"
|
||||
/><polygon
|
||||
points="659.363 363.205 647.012 352.839 647.3 363.205 643.474 363.205 643.167 352.244 626.49 363.205 619.509 363.205 643.033 347.737 642.122 315.258 641.441 290.728 645.276 290.623 645.967 315.258 646.868 347.708 665.328 363.205 659.363 363.205"
|
||||
fill="#3f3d56"
|
||||
/><path
|
||||
d="M836.784,315.60813c-3.3831,0-6.36764,2.628-8.36294,6.66445-1.75872-6.06969-5.45374-10.29078-9.7689-10.29078a6.56326,6.56326,0,0,0-.87094.1463c-1.65871-6.4805-5.51368-11.02542-10.00816-11.02542-6.00841,0-10.8791,8.118-10.8791,18.13187s4.87073,18.13187,10.8791,18.13187a6.56119,6.56119,0,0,0,.87093-.14629c1.65871,6.4805,5.51369,11.02541,10.00817,11.02541,3.3831,0,6.36764-2.62795,8.36294-6.66444,1.75876,6.06971,5.45374,10.29077,9.7689,10.29077,6.00841,0,10.8791-8.118,10.8791-18.13187S842.79244,315.60813,836.784,315.60813Z"
|
||||
transform="translate(-168.04134 -267.89732)"
|
||||
fill="#3f3d56"
|
||||
/><path
|
||||
d="M718.72328,451.807l-67.92039-11.01653c-3.42269-.55515-6.90789-1.11141-10.34147-.6282s-6.87069,2.1737-8.62107,5.16688a8.651,8.651,0,0,0,9.14985,12.853c-3.70741-.12023-7.60411-.19978-10.894,1.51369s-5.61946,5.87559-4.01553,9.22024a8.27667,8.27667,0,0,0,1.91922,2.4289,17.60582,17.60582,0,0,0,18.52289,3.14128c-2.50047,3.58582-7.46212,4.11838-11.7541,4.94866s-9.25362,3.258-9.41312,7.62664c-.17922,4.90869,5.66264,7.51763,10.47189,8.51687A137.41687,137.41687,0,0,0,712.648,489.3171a30.98,30.98,0,0,0,7.737-3.95049,17.43266,17.43266,0,0,0-7.05356-30.96345"
|
||||
transform="translate(-168.04134 -267.89732)"
|
||||
fill="#3f3d56"
|
||||
/><path
|
||||
d="M1011.89005,507.47917a137.41884,137.41884,0,0,0-51.17256-57.63676,30.97519,30.97519,0,0,0-7.80737-3.80966,17.43272,17.43272,0,0,0-20.50879,24.24615l-5.31525-2.74921Q943.09323,497.98314,959.1,528.43656c1.61312,3.06929,3.26318,6.18918,5.71292,8.64309s5.86648,4.18514,9.31075,3.78525a8.6006,8.6006,0,0,0,6.77916-12.2999,16.64264,16.64264,0,0,0,5.752,5.05979c3.34648,1.59972,8.07321.9603,9.7823-2.33177a8.27455,8.27455,0,0,0,.78809-2.99368,17.60592,17.60592,0,0,0-8.62117-16.69248c4.36853-.1565,7.77622,3.48909,11.01912,6.42052,3.24327,2.93143,8.1652,5.43808,11.75289,2.94008C1015.40712,518.16062,1013.98161,511.92354,1011.89005,507.47917Z"
|
||||
transform="translate(-168.04134 -267.89732)"
|
||||
fill="#3f3d56"
|
||||
/><polygon
|
||||
points="55.757 0 506 0 506 147.807 488.945 147.807 488.945 186.463 450.289 147.807 55.757 147.807 55.757 0"
|
||||
fill="#cacaca"
|
||||
/><rect x="68.26381" y="14.39335" width="425.22943" height="118.24561" fill="#fff" /><rect
|
||||
x="102.45877"
|
||||
y="48.91591"
|
||||
width="204.73707"
|
||||
height="6.21326"
|
||||
fill="#6c63ff"
|
||||
/><rect x="102.45877" y="70.40954" width="356.83952" height="6.21326" fill="#6c63ff" /><rect
|
||||
x="102.45877"
|
||||
y="91.90316"
|
||||
width="356.49009"
|
||||
height="6.21326"
|
||||
fill="#6c63ff"
|
||||
/><path
|
||||
d="M1030.95866,632.10268h-381a1,1,0,0,1,0-2h381a1,1,0,0,1,0,2Z"
|
||||
transform="translate(-168.04134 -267.89732)"
|
||||
fill="#cacaca"
|
||||
/></svg>
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,18 @@
|
||||
import ImageSvg from './image.svg';
|
||||
|
||||
function SpeakerImage() {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
backgroundImage: `url(${ImageSvg})`,
|
||||
backgroundSize: 'contain',
|
||||
backgroundPosition: 'center',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
}}
|
||||
></div>
|
||||
);
|
||||
}
|
||||
|
||||
export { SpeakerImage };
|
@@ -0,0 +1,44 @@
|
||||
import { IonToast } from '@ionic/react';
|
||||
import { WRONG_ANSWER_MESSAGE } from '../../constants';
|
||||
import './style.css';
|
||||
import { useAppStateContext } from '../../contexts/AppState';
|
||||
|
||||
interface QuestionCardProps {
|
||||
isOpen: boolean;
|
||||
dismiss: () => void;
|
||||
correct_answer?: string;
|
||||
}
|
||||
|
||||
const WrongAnswerToast: React.FC<QuestionCardProps> = ({ isOpen, dismiss, correct_answer = '' }) => {
|
||||
const { WRONG_ANS_TOAST_APPEAR_TIMEOUT_S } = useAppStateContext();
|
||||
|
||||
if (correct_answer != '') {
|
||||
return (
|
||||
<>
|
||||
<IonToast
|
||||
className="wrong-answer-toast"
|
||||
isOpen={isOpen}
|
||||
message={`${WRONG_ANSWER_MESSAGE}. The correct answer is "${correct_answer}"`}
|
||||
onDidDismiss={() => dismiss()}
|
||||
duration={WRONG_ANS_TOAST_APPEAR_TIMEOUT_S * 1000}
|
||||
color="danger"
|
||||
></IonToast>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<IonToast
|
||||
className="wrong-answer-toast"
|
||||
isOpen={isOpen}
|
||||
message={`${WRONG_ANSWER_MESSAGE}`}
|
||||
onDidDismiss={() => dismiss()}
|
||||
duration={WRONG_ANS_TOAST_APPEAR_TIMEOUT_S * 1000}
|
||||
color="danger"
|
||||
></IonToast>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default WrongAnswerToast;
|
@@ -0,0 +1,10 @@
|
||||
ion-toast.wrong-answer-toast::part(message) {
|
||||
/* text-align: center; */
|
||||
font-size: 1.5rem;
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
ion-toast.wrong-answer-toast::part(container) {
|
||||
bottom: 100px;
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
import { IonToast } from '@ionic/react';
|
||||
import { WRONG_ANSWER_MESSAGE } from '../../constants';
|
||||
import './style.css';
|
||||
import { useAppStateContext } from '../../contexts/AppState';
|
||||
|
||||
interface QuestionCardProps {
|
||||
isOpen: boolean;
|
||||
dismiss: () => void;
|
||||
correct_answer: string;
|
||||
}
|
||||
|
||||
const WrongAnswerToastWithoutCorrectAnswer: React.FC<QuestionCardProps> = ({
|
||||
isOpen,
|
||||
dismiss,
|
||||
correct_answer = '',
|
||||
}) => {
|
||||
const { WRONG_ANS_TOAST_APPEAR_TIMEOUT_S } = useAppStateContext();
|
||||
|
||||
if (correct_answer != '') {
|
||||
return (
|
||||
<>
|
||||
<IonToast
|
||||
className="wrong-answer-toast"
|
||||
isOpen={isOpen}
|
||||
message={`${WRONG_ANSWER_MESSAGE}. The correct answer is "${correct_answer}"`}
|
||||
onDidDismiss={() => dismiss()}
|
||||
duration={WRONG_ANS_TOAST_APPEAR_TIMEOUT_S * 1000}
|
||||
color="danger"
|
||||
></IonToast>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<IonToast
|
||||
className="wrong-answer-toast"
|
||||
isOpen={isOpen}
|
||||
message={`${WRONG_ANSWER_MESSAGE}`}
|
||||
onDidDismiss={() => dismiss()}
|
||||
duration={WRONG_ANS_TOAST_APPEAR_TIMEOUT_S * 1000}
|
||||
color="danger"
|
||||
></IonToast>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default WrongAnswerToastWithoutCorrectAnswer;
|
@@ -0,0 +1,10 @@
|
||||
ion-toast.wrong-answer-toast::part(message) {
|
||||
/* text-align: center; */
|
||||
font-size: 1.5rem;
|
||||
color: rgba(0, 0, 0, 0.9);
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
ion-toast.wrong-answer-toast::part(container) {
|
||||
bottom: 100px;
|
||||
}
|
138
002_source/ionic_mobile/src/constants.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
// 0.0.1 - implement screen
|
||||
// 0.0.2 - implement logic
|
||||
// 0.0.3 - first demo
|
||||
// 0.0.4 - bug fix
|
||||
// 0.0.5 - misc update
|
||||
// 0.0.6 - fix scroe board problem
|
||||
// 0.0.7 - add back button for listening practice, matching frenzy, connective revision
|
||||
// 0.0.8 - add back button for listening practice card, matching frenzy card, connective revision card
|
||||
// 0.0.9 - fix ticket 26,27,28,29
|
||||
// 0.0.10 - remove debug symbol and re-route ending page
|
||||
const VERSIONS = 'v0.0.10';
|
||||
const HELLOWORLD_MP3 = '/helloworld.mp3';
|
||||
|
||||
// api
|
||||
const API_URL = 'http://localhost:8080';
|
||||
|
||||
// route
|
||||
const LISTENING_PRACTICE_LINK = '/listening_practice';
|
||||
const MATCHING_FRENZY_LINK = '/matching_frenzy';
|
||||
const FAVORITE_LINK = '/fav';
|
||||
const LESSON_WORD_PAGE_LINK = '/lesson_word_page';
|
||||
const CONNECTIVE_REVISION_LINK = '/connective_revision';
|
||||
const LESSON_LINK = '/lesson';
|
||||
const QUIZ_MAIN_MENU_LINK = '/quizzes_main_menu';
|
||||
const RECORD_LINK = '/record';
|
||||
const SETTING_LINK = '/setting';
|
||||
const DEBUG_LINK = '/debug';
|
||||
const MY_ACHIEVEMENT_LINK = '/my_achievement';
|
||||
|
||||
// achievement
|
||||
const FULLMARK_COUNT_KEY = 'full_mark_count';
|
||||
const APP_USE_TIME = 'app_use_time_s';
|
||||
const LISTENING_PRACTICE_TIME_SPENT = 'listening_practice_time_spent_s';
|
||||
const MATCHING_FRENZY_CORRECT_COUNT = 'matching_frenzy_correct_count';
|
||||
const CONNECTIVES_REVISION_CORRECT_COUNT = 'connectives_revision_correct_count';
|
||||
const CONNECTIVES_REVISION_IGNORE_COUNT = 'connectives_revision_ignore_count';
|
||||
const FULLMARK_IGNORE_COUNT = 'full_mark_ignore_count';
|
||||
const MATCH_FRENZY_SCOREBOARD_KEY = 'matching_frenzy_scoreboard';
|
||||
|
||||
const COLOR_TEXT = 'rgba(0,0,0,0.9)';
|
||||
const GOOD_JOB_BG_COLOR = 'rgba (0,255,0,1)';
|
||||
const WRONG_ANS_BG_COLOR = 'rgba (255,0,0,1)';
|
||||
const HELLOWORLD = 'HELLOWORLD';
|
||||
|
||||
//
|
||||
const GENIUS_STAGES = [10, 50, 100, 300, 700, 1000];
|
||||
const HARDWORKER_STAGES = [5, 50, 100, 500, 1000, 1500, 3000];
|
||||
const ATTENTIVE_EARS_STAGES = [1, 10, 50, 100, 300, 700, 1000];
|
||||
const MATCHMAKING_STAGES = [30, 100, 250, 500, 1500, 3000, 8000];
|
||||
const CONNECTIVE_CONQUEROR_STAGES = [1, 5, 15, 35, 60];
|
||||
|
||||
//
|
||||
const THE_WORD_REMOVED = 'The word removed';
|
||||
const THE_WORD_REMOVED_DISMISS_TIMEOUT = 1000;
|
||||
|
||||
const CORRECT_ANSWER_MESSAGE = 'Good job!';
|
||||
const CORRECT_ANSWER_DISMISS_TIMEOUT = 3000;
|
||||
//
|
||||
const WRONG_ANSWER_MESSAGE = 'Wrong answer !';
|
||||
const WRONG_ANSWER_DISMISS_TIMEOUT = 3000;
|
||||
|
||||
//
|
||||
const PRESS_START_TO_BEGIN = 'Press start to begin';
|
||||
const PRESS_START_TO_BEGIN_MESSAGE = 'Relax and Prepare yourself';
|
||||
|
||||
//
|
||||
const CORRECTION_PHASE = 'correction phase';
|
||||
|
||||
//
|
||||
const hide_setting = true;
|
||||
const DEBUG = false;
|
||||
const TEST = process.env.NODE_ENV === 'test';
|
||||
|
||||
//
|
||||
const MY_FAVORITE = 'My Favorite';
|
||||
|
||||
//
|
||||
|
||||
export {
|
||||
//
|
||||
API_URL,
|
||||
APP_USE_TIME,
|
||||
ATTENTIVE_EARS_STAGES,
|
||||
//
|
||||
COLOR_TEXT,
|
||||
CONNECTIVES_REVISION_CORRECT_COUNT,
|
||||
CONNECTIVES_REVISION_IGNORE_COUNT,
|
||||
CONNECTIVE_CONQUEROR_STAGES,
|
||||
CONNECTIVE_REVISION_LINK,
|
||||
//
|
||||
CORRECTION_PHASE,
|
||||
CORRECT_ANSWER_DISMISS_TIMEOUT,
|
||||
//
|
||||
CORRECT_ANSWER_MESSAGE,
|
||||
DEBUG,
|
||||
DEBUG_LINK,
|
||||
FAVORITE_LINK,
|
||||
//
|
||||
FULLMARK_COUNT_KEY,
|
||||
FULLMARK_IGNORE_COUNT,
|
||||
//
|
||||
GENIUS_STAGES,
|
||||
//
|
||||
GOOD_JOB_BG_COLOR,
|
||||
HARDWORKER_STAGES,
|
||||
//
|
||||
HELLOWORLD,
|
||||
HELLOWORLD_MP3,
|
||||
LESSON_LINK,
|
||||
LESSON_WORD_PAGE_LINK,
|
||||
LISTENING_PRACTICE_LINK,
|
||||
LISTENING_PRACTICE_TIME_SPENT,
|
||||
MATCHING_FRENZY_CORRECT_COUNT,
|
||||
MATCHING_FRENZY_LINK,
|
||||
MATCHMAKING_STAGES,
|
||||
MATCH_FRENZY_SCOREBOARD_KEY,
|
||||
MY_ACHIEVEMENT_LINK,
|
||||
//
|
||||
MY_FAVORITE,
|
||||
//
|
||||
PRESS_START_TO_BEGIN,
|
||||
PRESS_START_TO_BEGIN_MESSAGE,
|
||||
QUIZ_MAIN_MENU_LINK,
|
||||
RECORD_LINK,
|
||||
SETTING_LINK,
|
||||
TEST,
|
||||
//
|
||||
THE_WORD_REMOVED,
|
||||
THE_WORD_REMOVED_DISMISS_TIMEOUT,
|
||||
VERSIONS,
|
||||
WRONG_ANSWER_DISMISS_TIMEOUT,
|
||||
WRONG_ANSWER_MESSAGE,
|
||||
WRONG_ANS_BG_COLOR,
|
||||
//
|
||||
hide_setting,
|
||||
};
|
||||
|
||||
export const DEFAULT_FORWARD_TIMEOUT = 3000;
|
42
002_source/ionic_mobile/src/contexts/AppOnRecorder.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
import { useIdle } from 'react-use';
|
||||
import { useAppUseTime } from './MyIonMetric/AppUseTime';
|
||||
|
||||
const AppOnRecorderContext = createContext<MyContextProps | undefined>(undefined);
|
||||
|
||||
export const AppOnRecorderProvider: React.FC = () => {
|
||||
const [my_context, setMyContext] = useState<string>('initial value');
|
||||
const { myIonMetricIncAppUseTime } = useAppUseTime();
|
||||
|
||||
const isIdle = useIdle(3e3);
|
||||
let getCurrentTimeInSec = () => Math.floor(Date.now() / 1000);
|
||||
let [last_on_s, setLastOn_s] = useState(getCurrentTimeInSec());
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (isIdle) {
|
||||
// active -> inactive
|
||||
let current_s = getCurrentTimeInSec();
|
||||
myIonMetricIncAppUseTime(current_s - last_on_s);
|
||||
} else {
|
||||
// inactive->active
|
||||
setLastOn_s(getCurrentTimeInSec());
|
||||
}
|
||||
})();
|
||||
}, [isIdle]);
|
||||
|
||||
return <AppOnRecorderContext.Provider value={{ my_context, setMyContext }}>{/* */}</AppOnRecorderContext.Provider>;
|
||||
};
|
||||
|
||||
export const useMyContext = (): MyContextProps => {
|
||||
const context = useContext(AppOnRecorderContext);
|
||||
if (!context) {
|
||||
throw new Error('useMyContext must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface MyContextProps {
|
||||
my_context: string;
|
||||
setMyContext: React.Dispatch<React.SetStateAction<string>>;
|
||||
}
|
145
002_source/ionic_mobile/src/contexts/AppState.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react';
|
||||
import RemoveFavoritePrompt from '../components/RemoveFavoritePrompt';
|
||||
import { LESSON_LINK } from '../constants';
|
||||
|
||||
const AppStateContext = createContext<AppStateContextProps | undefined>(undefined);
|
||||
|
||||
export const AppStateProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [my_context, setMyContext] = useState<string>('initial value');
|
||||
const [tab_active, setTabActive] = useState<string>(LESSON_LINK);
|
||||
const [show_confirm_user_exit, useShowConfirmUserExit] = useState<boolean>(false);
|
||||
const [url_push_after_user_confirm, setURLPushAfterUserConfirm] = useState<string>(LESSON_LINK);
|
||||
|
||||
const [matching_frenzy_in_progress, setMatchingFrenzyInProgress] = useState<boolean>(false);
|
||||
const [connective_revision_in_progress, setConnectiveRevisionInProgress] = useState<boolean>(false);
|
||||
const [listening_practice_in_progress, setListeningPracticeInProgress] = useState<boolean>(false);
|
||||
|
||||
const [disable_user_tap, setDisableUserTap] = useState<boolean>(false);
|
||||
|
||||
const [show_remove_fav_prompt, setShowRemoceFavPrompt] = useState(false);
|
||||
|
||||
// 010_user_configurable_anwered_timeout
|
||||
const [LISTENING_PRACTICE_ANWERED_WAIT_S, setListeningPracticeAnswerWait_s] = useState<number>(1);
|
||||
const [MATCHING_FRENZY_ANWERED_WAIT_S, setMatchingFrenzyAnswerWait_s] = useState<number>(1);
|
||||
|
||||
//
|
||||
const [WRONG_ANS_TOAST_APPEAR_TIMEOUT_S, setWRONG_ANS_TOAST_APPEAR_TIMEOUT_S] = useState<number>(3);
|
||||
const [CORRECT_ANS_TOAST_APPEAR_TIMEOUT_S, setCORRECT_ANS_TOAST_APPEAR_TIMEOUT_S] = useState<number>(3);
|
||||
const [CONNECTIVES_REVISION_ANWERED_WAIT_S, setCONNECTIVES_REVISION_ANWERED_WAIT_S] = useState<number>(3);
|
||||
|
||||
// user_config.json
|
||||
// 012_put_matching_frenzy_count_down_time_to_config_file
|
||||
const [user_config_json, setUserConfigJson] = useState<any>({});
|
||||
const [MATCHING_FRENZY_COUNT_DOWN_S, setMATCHING_FRENZY_COUNT_DOWN_S] = useState<number>(120);
|
||||
useEffect(() => {
|
||||
fetch('/data/user_config.json')
|
||||
.then((res) => res.json())
|
||||
.then((res_json) => {
|
||||
setUserConfigJson(res_json);
|
||||
|
||||
setMATCHING_FRENZY_COUNT_DOWN_S(res_json['matching_frenzy_count_down_s']);
|
||||
|
||||
setListeningPracticeAnswerWait_s(res_json['listening_practice_anwered_wait_s']);
|
||||
setMatchingFrenzyAnswerWait_s(res_json['matching_frenzy_anwered_wait_s']);
|
||||
setCONNECTIVES_REVISION_ANWERED_WAIT_S(res_json['connectives_revision_anwered_wait_s']);
|
||||
// console.log({ res_json });
|
||||
|
||||
setWRONG_ANS_TOAST_APPEAR_TIMEOUT_S(res_json['WRONG_ANS_TOAST_APPEAR_TIMEOUT_S']);
|
||||
setCORRECT_ANS_TOAST_APPEAR_TIMEOUT_S(res_json['CORRECT_ANS_TOAST_APPEAR_TIMEOUT_S']);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AppStateContext.Provider
|
||||
value={{
|
||||
my_context,
|
||||
setMyContext,
|
||||
//
|
||||
tab_active,
|
||||
setTabActive,
|
||||
//
|
||||
listening_practice_in_progress,
|
||||
setListeningPracticeInProgress,
|
||||
//
|
||||
matching_frenzy_in_progress,
|
||||
setMatchingFrenzyInProgress,
|
||||
//
|
||||
connective_revision_in_progress,
|
||||
setConnectiveRevisionInProgress,
|
||||
//
|
||||
show_confirm_user_exit,
|
||||
setShowConfirmUserExit: useShowConfirmUserExit,
|
||||
//
|
||||
url_push_after_user_confirm,
|
||||
setURLPushAfterUserConfirm,
|
||||
//
|
||||
disable_user_tap,
|
||||
setDisableUserTap,
|
||||
//
|
||||
show_remove_fav_prompt,
|
||||
setShowRemoceFavPrompt,
|
||||
//
|
||||
// 012_put_matching_frenzy_count_down_time_to_config_file
|
||||
MATCHING_FRENZY_COUNT_DOWN_S,
|
||||
//
|
||||
LISTENING_PRACTICE_ANWERED_WAIT_S,
|
||||
MATCHING_FRENZY_ANWERED_WAIT_S,
|
||||
CONNECTIVES_REVISION_ANWERED_WAIT_S,
|
||||
//
|
||||
WRONG_ANS_TOAST_APPEAR_TIMEOUT_S,
|
||||
CORRECT_ANS_TOAST_APPEAR_TIMEOUT_S,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
<RemoveFavoritePrompt open={show_remove_fav_prompt} setIsOpen={setShowRemoceFavPrompt} />
|
||||
</AppStateContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAppStateContext = (): AppStateContextProps => {
|
||||
const context = useContext(AppStateContext);
|
||||
if (!context) {
|
||||
throw new Error('useMyContext must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface AppStateContextProps {
|
||||
my_context: string;
|
||||
setMyContext: React.Dispatch<React.SetStateAction<string>>;
|
||||
//
|
||||
listening_practice_in_progress: boolean;
|
||||
setListeningPracticeInProgress: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
//
|
||||
//
|
||||
matching_frenzy_in_progress: boolean;
|
||||
setMatchingFrenzyInProgress: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
//
|
||||
connective_revision_in_progress: boolean;
|
||||
setConnectiveRevisionInProgress: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
//
|
||||
tab_active: string;
|
||||
setTabActive: React.Dispatch<React.SetStateAction<string>>;
|
||||
//
|
||||
show_confirm_user_exit: boolean;
|
||||
setShowConfirmUserExit: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
//
|
||||
url_push_after_user_confirm: string;
|
||||
setURLPushAfterUserConfirm: React.Dispatch<React.SetStateAction<string>>;
|
||||
//
|
||||
disable_user_tap: boolean;
|
||||
setDisableUserTap: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
//
|
||||
show_remove_fav_prompt: boolean;
|
||||
setShowRemoceFavPrompt: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
//
|
||||
MATCHING_FRENZY_COUNT_DOWN_S: number;
|
||||
//
|
||||
LISTENING_PRACTICE_ANWERED_WAIT_S: number;
|
||||
MATCHING_FRENZY_ANWERED_WAIT_S: number;
|
||||
CONNECTIVES_REVISION_ANWERED_WAIT_S: number;
|
||||
//
|
||||
WRONG_ANS_TOAST_APPEAR_TIMEOUT_S: number;
|
||||
CORRECT_ANS_TOAST_APPEAR_TIMEOUT_S: number;
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
export interface ConnectiveRevisionAllResult {
|
||||
[quiz_name: string]: number;
|
||||
}
|
||||
|
||||
export interface ConnectiveRevisionResult {
|
||||
date: string;
|
||||
progress: number;
|
||||
}
|
91
002_source/ionic_mobile/src/contexts/Haptics.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { Haptics, ImpactStyle } from '@capacitor/haptics';
|
||||
import React, { createContext, ReactNode, useContext, useState } from 'react';
|
||||
|
||||
const MyContext = createContext<MyContextProps | undefined>(undefined);
|
||||
|
||||
const hapticsImpactMedium = async () => {
|
||||
await Haptics.impact({ style: ImpactStyle.Medium });
|
||||
};
|
||||
|
||||
const hapticsImpactLight = async () => {
|
||||
await Haptics.impact({ style: ImpactStyle.Light });
|
||||
};
|
||||
|
||||
const hapticsVibrate = async () => {
|
||||
await Haptics.vibrate();
|
||||
};
|
||||
|
||||
const hapticsSelectionStart = async () => {
|
||||
await Haptics.selectionStart();
|
||||
};
|
||||
|
||||
const hapticsSelectionChanged = async () => {
|
||||
await Haptics.selectionChanged();
|
||||
};
|
||||
|
||||
const hapticsSelectionEnd = async () => {
|
||||
await Haptics.selectionEnd();
|
||||
};
|
||||
|
||||
export const MyProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [my_context, setMyContext] = useState<string>('initial value');
|
||||
|
||||
const [test_first, setTestFirst] = useState<IFirst | undefined>(undefined);
|
||||
const [test_second, setTestSecond] = useState<ISecond | undefined>(undefined);
|
||||
const [test_third, setTestThird] = useState<IThird | undefined>(undefined);
|
||||
|
||||
return (
|
||||
<MyContext.Provider
|
||||
value={{
|
||||
my_context,
|
||||
setMyContext,
|
||||
//
|
||||
test_first,
|
||||
setTestFirst,
|
||||
test_second,
|
||||
setTestSecond,
|
||||
test_third,
|
||||
setTestThird,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MyContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMyContext = (): MyContextProps => {
|
||||
const context = useContext(MyContext);
|
||||
if (!context) {
|
||||
throw new Error('useMyContext must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface MyContextProps {
|
||||
my_context: string;
|
||||
setMyContext: React.Dispatch<React.SetStateAction<string>>;
|
||||
//
|
||||
test_first: IFirst | undefined;
|
||||
setTestFirst: React.Dispatch<React.SetStateAction<IFirst | undefined>>;
|
||||
test_second: ISecond | undefined;
|
||||
setTestSecond: React.Dispatch<React.SetStateAction<ISecond | undefined>>;
|
||||
test_third: IThird | undefined;
|
||||
setTestThird: React.Dispatch<React.SetStateAction<IThird | undefined>>;
|
||||
}
|
||||
|
||||
interface IFirst {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
}
|
||||
|
||||
interface ISecond {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
content: IFirst[] | [];
|
||||
}
|
||||
|
||||
interface IThird {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
content: ISecond[] | [];
|
||||
}
|
66
002_source/ionic_mobile/src/contexts/Helloworld.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import React, { createContext, ReactNode, useContext, useState } from 'react';
|
||||
|
||||
const MyContext = createContext<MyContextProps | undefined>(undefined);
|
||||
|
||||
export const MyProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [my_context, setMyContext] = useState<string>('initial value');
|
||||
|
||||
const [test_first, setTestFirst] = useState<IFirst | undefined>(undefined);
|
||||
const [test_second, setTestSecond] = useState<ISecond | undefined>(undefined);
|
||||
const [test_third, setTestThird] = useState<IThird | undefined>(undefined);
|
||||
|
||||
return (
|
||||
<MyContext.Provider
|
||||
value={{
|
||||
my_context,
|
||||
setMyContext,
|
||||
//
|
||||
test_first,
|
||||
setTestFirst,
|
||||
test_second,
|
||||
setTestSecond,
|
||||
test_third,
|
||||
setTestThird,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MyContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMyContext = (): MyContextProps => {
|
||||
const context = useContext(MyContext);
|
||||
if (!context) {
|
||||
throw new Error('useMyContext must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface MyContextProps {
|
||||
my_context: string;
|
||||
setMyContext: React.Dispatch<React.SetStateAction<string>>;
|
||||
//
|
||||
test_first: IFirst | undefined;
|
||||
setTestFirst: React.Dispatch<React.SetStateAction<IFirst | undefined>>;
|
||||
test_second: ISecond | undefined;
|
||||
setTestSecond: React.Dispatch<React.SetStateAction<ISecond | undefined>>;
|
||||
test_third: IThird | undefined;
|
||||
setTestThird: React.Dispatch<React.SetStateAction<IThird | undefined>>;
|
||||
}
|
||||
|
||||
interface IFirst {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
}
|
||||
|
||||
interface ISecond {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
content: IFirst[] | [];
|
||||
}
|
||||
|
||||
interface IThird {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
content: ISecond[] | [];
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
export interface MatchingFrezyRanking {
|
||||
ranking: MatchingFrenzyResult[] | [];
|
||||
}
|
||||
export interface MatchingFrenzyResult {
|
||||
date: string;
|
||||
result: number;
|
||||
}
|
138
002_source/ionic_mobile/src/contexts/MyIonFavorite.tsx
Normal file
@@ -0,0 +1,138 @@
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
import { useMyIonStore } from './MyIonStore';
|
||||
|
||||
const MyIonFavoriteContext = createContext<MyContextProps | undefined>(undefined);
|
||||
|
||||
export const MyIonFavoriteProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const { myIonStoreRead, myIonStoreWrite, user_store } = useMyIonStore();
|
||||
|
||||
async function myIonStoreAddFavorite(address_to_add: string) {
|
||||
console.log('myIonStoreAddFavorite', address_to_add);
|
||||
let current_fav = await JSON.parse(await myIonStoreRead('lesson_favorite_word'));
|
||||
if (JSON.stringify(current_fav) == '{}') current_fav = [];
|
||||
current_fav = [...current_fav, address_to_add];
|
||||
await myIonStoreWrite('lesson_favorite_word', JSON.stringify(current_fav));
|
||||
}
|
||||
|
||||
async function myIonStoreRemoveFavorite(address_to_remove: string) {
|
||||
let current_fav = await JSON.parse(await myIonStoreRead('lesson_favorite_word'));
|
||||
current_fav = current_fav.filter((address: string) => address !== address_to_remove);
|
||||
await myIonStoreWrite('lesson_favorite_word', JSON.stringify(current_fav));
|
||||
}
|
||||
|
||||
async function myIonStoreLoadFavorite() {
|
||||
let current_fav = await JSON.parse(await myIonStoreRead('lesson_favorite_word'));
|
||||
return current_fav ? current_fav : [];
|
||||
}
|
||||
|
||||
async function myIonStoreFindInFavorite(string_to_search: string) {
|
||||
const current_fav = await myIonStoreLoadFavorite();
|
||||
return current_fav.includes(string_to_search);
|
||||
}
|
||||
|
||||
//
|
||||
const KEY_FAV_VOCBULARY = 'lesson_favorite_vocabulary';
|
||||
async function myIonStoreAddFavoriteVocabulary(address_to_add: string) {
|
||||
console.log('myIonStoreAddFavorite', address_to_add);
|
||||
let current_fav = await JSON.parse(await myIonStoreRead(KEY_FAV_VOCBULARY));
|
||||
if (JSON.stringify(current_fav) == '{}') current_fav = [];
|
||||
current_fav = [...current_fav, address_to_add];
|
||||
await myIonStoreWrite(KEY_FAV_VOCBULARY, JSON.stringify(current_fav));
|
||||
}
|
||||
|
||||
async function myIonStoreRemoveFavoriteVocabulary(address_to_remove: string) {
|
||||
let current_fav = await JSON.parse(await myIonStoreRead(KEY_FAV_VOCBULARY));
|
||||
current_fav = current_fav.filter((address: string) => address !== address_to_remove);
|
||||
await myIonStoreWrite(KEY_FAV_VOCBULARY, JSON.stringify(current_fav));
|
||||
}
|
||||
|
||||
async function myIonStoreLoadFavoriteVocabulary(): Promise<string[]> {
|
||||
let result = await myIonStoreRead(KEY_FAV_VOCBULARY);
|
||||
|
||||
if (result == '{}') return [];
|
||||
let current_fav = JSON.parse(result);
|
||||
return current_fav;
|
||||
}
|
||||
|
||||
async function myIonStoreFindInFavoriteVocabulary(string_to_search: string): Promise<boolean> {
|
||||
const current_fav = await myIonStoreLoadFavoriteVocabulary();
|
||||
console.log({ current_fav });
|
||||
return current_fav.includes(string_to_search);
|
||||
}
|
||||
|
||||
//
|
||||
const KEY_FAV_CONNECTIVES = 'lesson_favorite_connectives';
|
||||
|
||||
async function myIonStoreAddFavoriteConnectives(address_to_add: string) {
|
||||
console.log('myIonStoreAddFavorite', address_to_add);
|
||||
let current_fav = await JSON.parse(await myIonStoreRead(KEY_FAV_CONNECTIVES));
|
||||
if (JSON.stringify(current_fav) == '{}') current_fav = [];
|
||||
current_fav = [...current_fav, address_to_add];
|
||||
await myIonStoreWrite(KEY_FAV_CONNECTIVES, JSON.stringify(current_fav));
|
||||
}
|
||||
|
||||
async function myIonStoreRemoveFavoriteConnectives(address_to_remove: string) {
|
||||
let current_fav = await JSON.parse(await myIonStoreRead(KEY_FAV_CONNECTIVES));
|
||||
current_fav = current_fav.filter((address: string) => address !== address_to_remove);
|
||||
await myIonStoreWrite(KEY_FAV_CONNECTIVES, JSON.stringify(current_fav));
|
||||
}
|
||||
|
||||
async function myIonStoreLoadFavoriteConnectives(): Promise<string[]> {
|
||||
let temp = await myIonStoreRead(KEY_FAV_CONNECTIVES);
|
||||
let current_fav = await JSON.parse(temp);
|
||||
return current_fav ? current_fav : [];
|
||||
}
|
||||
|
||||
async function myIonStoreFindInFavoriteConnectives(string_to_search: string) {
|
||||
const current_fav = await myIonStoreLoadFavoriteConnectives();
|
||||
return current_fav.includes(string_to_search);
|
||||
}
|
||||
|
||||
return (
|
||||
<MyIonFavoriteContext.Provider
|
||||
value={{
|
||||
myIonStoreAddFavorite,
|
||||
myIonStoreRemoveFavorite,
|
||||
myIonStoreLoadFavorite,
|
||||
myIonStoreFindInFavorite,
|
||||
//
|
||||
myIonStoreAddFavoriteVocabulary,
|
||||
myIonStoreRemoveFavoriteVocabulary,
|
||||
myIonStoreLoadFavoriteVocabulary,
|
||||
myIonStoreFindInFavoriteVocabulary,
|
||||
//
|
||||
myIonStoreAddFavoriteConnectives,
|
||||
myIonStoreRemoveFavoriteConnectives,
|
||||
myIonStoreLoadFavoriteConnectives,
|
||||
myIonStoreFindInFavoriteConnectives,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MyIonFavoriteContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMyIonFavorite = (): MyContextProps => {
|
||||
const context = useContext(MyIonFavoriteContext);
|
||||
if (!context) {
|
||||
throw new Error('useMyContext must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface MyContextProps {
|
||||
myIonStoreAddFavorite: (address_to_add: string) => Promise<void>;
|
||||
myIonStoreRemoveFavorite: (address_to_add: string) => Promise<void>;
|
||||
myIonStoreLoadFavorite: () => Promise<[]>;
|
||||
myIonStoreFindInFavorite: (string_to_search: string) => Promise<boolean>;
|
||||
//
|
||||
myIonStoreAddFavoriteVocabulary: (address_to_add: string) => Promise<void>;
|
||||
myIonStoreRemoveFavoriteVocabulary: (address_to_add: string) => Promise<void>;
|
||||
myIonStoreLoadFavoriteVocabulary: () => Promise<string[]>;
|
||||
myIonStoreFindInFavoriteVocabulary: (string_to_search: string) => Promise<boolean>;
|
||||
//
|
||||
myIonStoreAddFavoriteConnectives: (address_to_add: string) => Promise<void>;
|
||||
myIonStoreRemoveFavoriteConnectives: (address_to_add: string) => Promise<void>;
|
||||
myIonStoreLoadFavoriteConnectives: () => Promise<string[]>;
|
||||
myIonStoreFindInFavoriteConnectives: (string_to_search: string) => Promise<boolean>;
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
import { useMyIonMetric } from '.';
|
||||
import { APP_USE_TIME } from '../../constants';
|
||||
|
||||
const AppUseTimeContext = createContext<AppUseTimeContextProps | undefined>(undefined);
|
||||
|
||||
export const AppUseTimeProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const { myIonStoreRead, myIonStoreWrite } = useMyIonMetric();
|
||||
|
||||
function Helloworld() {
|
||||
console.log('helloworld');
|
||||
}
|
||||
|
||||
async function myIonMetricGetAppUseTime() {
|
||||
let result = JSON.parse(await myIonStoreRead(APP_USE_TIME, { time_spent_s: 0 }));
|
||||
return result;
|
||||
}
|
||||
|
||||
async function myIonMetricResetAppUseTime() {
|
||||
await myIonStoreWrite(APP_USE_TIME, JSON.stringify({ time_spent_s: 0 }));
|
||||
}
|
||||
|
||||
async function myIonMetricIncAppUseTime(time_spent_s: number) {
|
||||
let result = await myIonMetricGetAppUseTime();
|
||||
await myIonStoreWrite(APP_USE_TIME, JSON.stringify({ time_spent_s: result.time_spent_s + time_spent_s }));
|
||||
}
|
||||
|
||||
async function myIonMetricSetAppUseTime(time_spent_s: number) {
|
||||
await myIonStoreWrite(APP_USE_TIME, JSON.stringify({ time_spent_s: time_spent_s }));
|
||||
}
|
||||
|
||||
async function myIonMetricGetAppUseTimeIgnore() {
|
||||
let result = JSON.parse(await myIonStoreRead(APP_USE_TIME + '_ignore', { time_spent_s: 0 }));
|
||||
return result;
|
||||
}
|
||||
|
||||
async function myIonMetricSetAppUseTimeIgnore(time_spent_s: number) {
|
||||
await myIonStoreWrite(APP_USE_TIME + '_ignore', JSON.stringify({ time_spent_s: time_spent_s }));
|
||||
}
|
||||
|
||||
return (
|
||||
<AppUseTimeContext.Provider
|
||||
value={{
|
||||
//
|
||||
Helloworld,
|
||||
myIonMetricGetAppUseTime,
|
||||
myIonMetricResetAppUseTime,
|
||||
myIonMetricIncAppUseTime,
|
||||
//
|
||||
myIonMetricSetAppUseTime,
|
||||
myIonMetricGetAppUseTimeIgnore,
|
||||
myIonMetricSetAppUseTimeIgnore,
|
||||
}}
|
||||
>
|
||||
{/* */}
|
||||
{children}
|
||||
</AppUseTimeContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useAppUseTime = (): AppUseTimeContextProps => {
|
||||
const context = useContext(AppUseTimeContext);
|
||||
if (!context) {
|
||||
throw new Error('useAppUseTime must be used within a AppUseTimeProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface AppUseTimeContextProps {
|
||||
Helloworld: () => void;
|
||||
myIonMetricGetAppUseTime: () => Promise<{ time_spent_s: number }>;
|
||||
myIonMetricIncAppUseTime: (time_spent_s: number) => Promise<void>;
|
||||
myIonMetricResetAppUseTime: () => Promise<void>;
|
||||
//
|
||||
myIonMetricSetAppUseTime: (time_spent_s: number) => Promise<void>;
|
||||
myIonMetricGetAppUseTimeIgnore: () => Promise<{ time_spent_s: number }>;
|
||||
myIonMetricSetAppUseTimeIgnore: (time_spent_s: number) => Promise<void>;
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
import { useMyIonMetric } from '.';
|
||||
import { CONNECTIVES_REVISION_CORRECT_COUNT } from '../../constants';
|
||||
|
||||
const ConnectivesRevisionCorrectCount = createContext<ConnectivesRevisionCorrectCountProps | undefined>(undefined);
|
||||
|
||||
export const ConnectivesRevisionCorrectCountProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const NS_KEY_IGNORE = `${CONNECTIVES_REVISION_CORRECT_COUNT}_ignore`;
|
||||
|
||||
const { myIonStoreRead, myIonStoreWrite } = useMyIonMetric();
|
||||
|
||||
function Helloworld() {
|
||||
console.log('helloworld ConnectivesRevisionCorrectCountProvider');
|
||||
}
|
||||
|
||||
async function myIonMetricGetConnectivesRevisionCorrectCount() {
|
||||
let result = JSON.parse(await myIonStoreRead(CONNECTIVES_REVISION_CORRECT_COUNT, { count: 0 }));
|
||||
return result;
|
||||
}
|
||||
|
||||
async function myIonMetricResetConnectivesRevisionCorrectCount() {
|
||||
await myIonStoreWrite(CONNECTIVES_REVISION_CORRECT_COUNT, JSON.stringify({ count: 0 }));
|
||||
}
|
||||
|
||||
async function myIonMetricIncConnectivesRevisionCorrectCount() {
|
||||
let result = await myIonMetricGetConnectivesRevisionCorrectCount();
|
||||
await myIonStoreWrite(CONNECTIVES_REVISION_CORRECT_COUNT, JSON.stringify({ count: result.count + 1 }));
|
||||
}
|
||||
|
||||
async function myIonMetricSetConnectivesRevisionCorrectCount(count: number) {
|
||||
await myIonStoreWrite(CONNECTIVES_REVISION_CORRECT_COUNT, JSON.stringify({ count: count }));
|
||||
}
|
||||
|
||||
async function myIonMetricGetConnectivesRevisionCorrectCountIgnore() {
|
||||
let result = JSON.parse(await myIonStoreRead(NS_KEY_IGNORE, { count: 0 }));
|
||||
return result;
|
||||
}
|
||||
|
||||
async function myIonMetricSetConnectivesRevisionCorrectCountIgnore(count: number) {
|
||||
await myIonStoreWrite(NS_KEY_IGNORE, JSON.stringify({ count: count }));
|
||||
}
|
||||
|
||||
return (
|
||||
<ConnectivesRevisionCorrectCount.Provider
|
||||
value={{
|
||||
Helloworld,
|
||||
|
||||
// ConnectivesRevisionCorrect
|
||||
myIonMetricGetConnectivesRevisionCorrectCount,
|
||||
myIonMetricResetConnectivesRevisionCorrectCount,
|
||||
myIonMetricIncConnectivesRevisionCorrectCount,
|
||||
//
|
||||
myIonMetricSetConnectivesRevisionCorrectCount,
|
||||
myIonMetricGetConnectivesRevisionCorrectCountIgnore,
|
||||
myIonMetricSetConnectivesRevisionCorrectCountIgnore,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</ConnectivesRevisionCorrectCount.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useConnectivesRevisionCorrectCount = (): ConnectivesRevisionCorrectCountProps => {
|
||||
const context = useContext(ConnectivesRevisionCorrectCount);
|
||||
if (!context) {
|
||||
throw new Error('useConnectivesRevisionCorrectCount must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface ConnectivesRevisionCorrectCountProps {
|
||||
Helloworld: () => void;
|
||||
// ConnectivesRevision
|
||||
myIonMetricGetConnectivesRevisionCorrectCount: () => Promise<{ count: number }>;
|
||||
myIonMetricResetConnectivesRevisionCorrectCount: () => Promise<void>;
|
||||
myIonMetricIncConnectivesRevisionCorrectCount: () => Promise<void>;
|
||||
//
|
||||
myIonMetricSetConnectivesRevisionCorrectCount: (count: number) => Promise<void>;
|
||||
myIonMetricGetConnectivesRevisionCorrectCountIgnore: () => Promise<{ count: number }>;
|
||||
myIonMetricSetConnectivesRevisionCorrectCountIgnore: (count: number) => Promise<void>;
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
import { useMyIonMetric } from '.';
|
||||
import { FULLMARK_COUNT_KEY, FULLMARK_IGNORE_COUNT } from '../../constants';
|
||||
|
||||
const FullmarkCountContext = createContext<FullmarkCountContextProps | undefined>(undefined);
|
||||
|
||||
export const FullmarkCountProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const { myIonStoreRead, myIonStoreWrite } = useMyIonMetric();
|
||||
function Helloworld() {
|
||||
console.log('helloworld');
|
||||
}
|
||||
|
||||
async function myIonMetricGetFullMarkCount() {
|
||||
let result = JSON.parse(await myIonStoreRead(FULLMARK_COUNT_KEY, { count: 0 }));
|
||||
return result;
|
||||
}
|
||||
|
||||
async function myIonMetricResetFullMarkCount() {
|
||||
await myIonStoreWrite(FULLMARK_COUNT_KEY, JSON.stringify({ count: 0 }));
|
||||
}
|
||||
|
||||
async function myIonMetricIncFullMarkCount() {
|
||||
let result = await myIonMetricGetFullMarkCount();
|
||||
await myIonStoreWrite(FULLMARK_COUNT_KEY, JSON.stringify({ count: result.count + 1 }));
|
||||
}
|
||||
|
||||
async function myIonMetricSetFullMarkCount(num: number) {
|
||||
await myIonStoreWrite(FULLMARK_COUNT_KEY, JSON.stringify({ count: num }));
|
||||
}
|
||||
|
||||
async function myIonMetricSetFullMarkIgnore(num: number) {
|
||||
await myIonStoreWrite(FULLMARK_IGNORE_COUNT, JSON.stringify({ count: num }));
|
||||
}
|
||||
async function myIonMetricGetFullMarkIgnore() {
|
||||
let result = JSON.parse(await myIonStoreRead(FULLMARK_IGNORE_COUNT, { count: 0 }));
|
||||
return result;
|
||||
}
|
||||
|
||||
return (
|
||||
<FullmarkCountContext.Provider
|
||||
value={{
|
||||
Helloworld,
|
||||
myIonMetricGetFullMarkCount,
|
||||
myIonMetricResetFullMarkCount,
|
||||
myIonMetricIncFullMarkCount,
|
||||
//
|
||||
myIonMetricSetFullMarkCount,
|
||||
myIonMetricSetFullMarkIgnore,
|
||||
myIonMetricGetFullMarkIgnore,
|
||||
}}
|
||||
>
|
||||
{/* */}
|
||||
{children}
|
||||
</FullmarkCountContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useFullmarkCount = (): FullmarkCountContextProps => {
|
||||
const context = useContext(FullmarkCountContext);
|
||||
if (!context) {
|
||||
throw new Error('useMyContext must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface FullmarkCountContextProps {
|
||||
Helloworld: () => void;
|
||||
//
|
||||
// FullMark
|
||||
myIonMetricGetFullMarkCount: () => Promise<{ count: number }>;
|
||||
myIonMetricResetFullMarkCount: () => Promise<void>;
|
||||
myIonMetricIncFullMarkCount: () => Promise<void>;
|
||||
//
|
||||
myIonMetricSetFullMarkCount: (num: number) => Promise<void>;
|
||||
myIonMetricSetFullMarkIgnore: (num: number) => Promise<void>;
|
||||
myIonMetricGetFullMarkIgnore: () => Promise<{ count: number }>;
|
||||
}
|
@@ -0,0 +1,5 @@
|
||||
function Helloworld() {
|
||||
console.log('helloworld');
|
||||
}
|
||||
|
||||
export { Helloworld };
|
@@ -0,0 +1,88 @@
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
import { useMyIonMetric } from '.';
|
||||
import { LISTENING_PRACTICE_TIME_SPENT } from '../../constants';
|
||||
|
||||
const ListenPracticeTimeSpent = createContext<ListenPracticeTimeSpentProps | undefined>(undefined);
|
||||
|
||||
export const ListeningPracticeTimeSpentProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
// TODO create namespace for LISTENING_PRACTICE_TIME_SPENT ignore
|
||||
|
||||
const { myIonStoreRead, myIonStoreWrite } = useMyIonMetric();
|
||||
|
||||
function Helloworld() {
|
||||
console.log('helloworld ListeningPracticeTimeSpentProvider');
|
||||
}
|
||||
|
||||
async function myIonMetricGetListeningPracticeTimeSpent() {
|
||||
let result = JSON.parse(await myIonStoreRead(LISTENING_PRACTICE_TIME_SPENT, { time_spent_s: 0 }));
|
||||
return result;
|
||||
}
|
||||
|
||||
async function myIonMetricResetListeningPracticeTimeSpent() {
|
||||
await myIonStoreWrite(LISTENING_PRACTICE_TIME_SPENT, JSON.stringify({ time_spent_s: 0 }));
|
||||
}
|
||||
|
||||
async function myIonMetricIncListeningPracticeTimeSpent(time_spent_s: number) {
|
||||
let result = await myIonMetricGetListeningPracticeTimeSpent();
|
||||
await myIonStoreWrite(
|
||||
LISTENING_PRACTICE_TIME_SPENT,
|
||||
JSON.stringify({ time_spent_s: result.time_spent_s + time_spent_s }),
|
||||
);
|
||||
}
|
||||
|
||||
async function myIonMetricSetListeningPracticeProgress(num: number) {
|
||||
await myIonStoreWrite(
|
||||
//
|
||||
LISTENING_PRACTICE_TIME_SPENT,
|
||||
JSON.stringify({ time_spent_s: num }),
|
||||
);
|
||||
}
|
||||
|
||||
async function myIonMetricGetListeningPracticeProgressIgnore() {
|
||||
let result = JSON.parse(await myIonStoreRead(LISTENING_PRACTICE_TIME_SPENT + '_ignore', { time_spent_s: 0 }));
|
||||
return result;
|
||||
}
|
||||
|
||||
async function myIonMetricSetListeningPracticeProgressIgnore(time_spent_s: number) {
|
||||
await myIonStoreWrite(LISTENING_PRACTICE_TIME_SPENT + '_ignore', JSON.stringify({ time_spent_s: time_spent_s }));
|
||||
}
|
||||
|
||||
return (
|
||||
<ListenPracticeTimeSpent.Provider
|
||||
value={{
|
||||
Helloworld,
|
||||
myIonMetricGetListeningPracticeTimeSpent,
|
||||
myIonMetricIncListeningPracticeTimeSpent,
|
||||
myIonMetricResetListeningPracticeTimeSpent,
|
||||
//
|
||||
myIonMetricSetListeningPracticeProgress,
|
||||
myIonMetricGetListeningPracticeProgressIgnore,
|
||||
myIonMetricSetListeningPracticeProgressIgnore,
|
||||
}}
|
||||
>
|
||||
{/* */}
|
||||
{children}
|
||||
</ListenPracticeTimeSpent.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useListeningPracticeTimeSpent = (): ListenPracticeTimeSpentProps => {
|
||||
const context = useContext(ListenPracticeTimeSpent);
|
||||
if (!context) {
|
||||
throw new Error('useListenPracticeTimeSpent must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface ListenPracticeTimeSpentProps {
|
||||
Helloworld: () => void;
|
||||
//
|
||||
// ListeningPractice
|
||||
myIonMetricGetListeningPracticeTimeSpent: () => Promise<{ time_spent_s: number }>;
|
||||
myIonMetricIncListeningPracticeTimeSpent: (time_spent_s: number) => Promise<void>;
|
||||
myIonMetricResetListeningPracticeTimeSpent: () => Promise<void>;
|
||||
//
|
||||
myIonMetricSetListeningPracticeProgress: (time_spent_s: number) => Promise<void>;
|
||||
myIonMetricGetListeningPracticeProgressIgnore: () => Promise<{ time_spent_s: number }>;
|
||||
myIonMetricSetListeningPracticeProgressIgnore: (time_spent_s: number) => Promise<void>;
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
import React, { createContext, ReactNode, useContext } from 'react';
|
||||
import { useMyIonMetric } from '.';
|
||||
import { MATCHING_FRENZY_CORRECT_COUNT } from '../../constants';
|
||||
|
||||
const MatchingFrenzyCorrectCountContext = createContext<MatchingFrenzyCorrectCountContextProps | undefined>(undefined);
|
||||
|
||||
export const MatchingFrenzyCorrectCountProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const { myIonStoreRead, myIonStoreWrite } = useMyIonMetric();
|
||||
|
||||
function Helloworld() {
|
||||
console.log('helloworld MatchingFrenzyCorrectCountProvider');
|
||||
}
|
||||
|
||||
async function myIonMetricGetMatchingFrenzyCorrectCount() {
|
||||
let result = JSON.parse(await myIonStoreRead(MATCHING_FRENZY_CORRECT_COUNT, { count: 0 }));
|
||||
return result;
|
||||
}
|
||||
|
||||
async function myIonMetricResetMatchingFrenzyCorrectCount() {
|
||||
await myIonStoreWrite(MATCHING_FRENZY_CORRECT_COUNT, JSON.stringify({ count: 0 }));
|
||||
}
|
||||
|
||||
async function myIonMetricIncMatchingFrenzyCorrectCount() {
|
||||
let result = await myIonMetricGetMatchingFrenzyCorrectCount();
|
||||
await myIonStoreWrite(MATCHING_FRENZY_CORRECT_COUNT, JSON.stringify({ count: result.count + 1 }));
|
||||
}
|
||||
|
||||
async function myIonMetricSetMatchingFrenzyCorrectCount(count: number) {
|
||||
await myIonStoreWrite(MATCHING_FRENZY_CORRECT_COUNT, JSON.stringify({ count: count }));
|
||||
}
|
||||
|
||||
async function myIonMetricGetMatchingFrenzyCorrectCountIgnore() {
|
||||
let result = JSON.parse(await myIonStoreRead(`${MATCHING_FRENZY_CORRECT_COUNT}_ignore`, { count: 0 }));
|
||||
return result;
|
||||
}
|
||||
|
||||
async function myIonMetricSetMatchingFrenzyCorrectCountIgnore(count: number) {
|
||||
await myIonStoreWrite(`${MATCHING_FRENZY_CORRECT_COUNT}_ignore`, JSON.stringify({ count: count }));
|
||||
}
|
||||
|
||||
return (
|
||||
<MatchingFrenzyCorrectCountContext.Provider
|
||||
value={{
|
||||
Helloworld,
|
||||
|
||||
// MatchingFrenzyCorrect
|
||||
myIonMetricGetMatchingFrenzyCorrectCount,
|
||||
myIonMetricResetMatchingFrenzyCorrectCount,
|
||||
myIonMetricIncMatchingFrenzyCorrectCount,
|
||||
//
|
||||
myIonMetricSetMatchingFrenzyCorrectCount,
|
||||
myIonMetricGetMatchingFrenzyCorrectCountIgnore,
|
||||
myIonMetricSetMatchingFrenzyCorrectCountIgnore,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MatchingFrenzyCorrectCountContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMatchingFrenzyCorrectCount = (): MatchingFrenzyCorrectCountContextProps => {
|
||||
const context = useContext(MatchingFrenzyCorrectCountContext);
|
||||
if (!context) {
|
||||
throw new Error('useMatchingFrenzyCorrectCountContext must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface MatchingFrenzyCorrectCountContextProps {
|
||||
Helloworld: () => void;
|
||||
// MatchingFrenzyCorrect
|
||||
myIonMetricGetMatchingFrenzyCorrectCount: () => Promise<{ count: number }>;
|
||||
myIonMetricResetMatchingFrenzyCorrectCount: () => Promise<void>;
|
||||
myIonMetricIncMatchingFrenzyCorrectCount: () => Promise<void>;
|
||||
//
|
||||
myIonMetricSetMatchingFrenzyCorrectCount: (count: number) => Promise<void>;
|
||||
myIonMetricGetMatchingFrenzyCorrectCountIgnore: () => Promise<{ count: number }>;
|
||||
myIonMetricSetMatchingFrenzyCorrectCountIgnore: (count: number) => Promise<void>;
|
||||
}
|
75
002_source/ionic_mobile/src/contexts/MyIonMetric/index.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import { Drivers, Storage } from '@ionic/storage';
|
||||
import React, { createContext, ReactNode, useContext, useEffect } from 'react';
|
||||
import { AppOnRecorderProvider } from '../AppOnRecorder';
|
||||
import { AppUseTimeProvider } from './AppUseTime';
|
||||
import { ConnectivesRevisionCorrectCountProvider } from './ConnectivesRevisionCorrectCount';
|
||||
import { FullmarkCountProvider } from './FullmarkCount';
|
||||
import { Helloworld } from './Helloworld';
|
||||
import { ListeningPracticeTimeSpentProvider } from './ListeningPracticeTimeSpent';
|
||||
import { MatchingFrenzyCorrectCountProvider } from './MatchingFrenzyCorrectCount';
|
||||
|
||||
const metric_store = new Storage({
|
||||
name: 'app_metrics',
|
||||
driverOrder: [Drivers.IndexedDB, Drivers.LocalStorage],
|
||||
});
|
||||
|
||||
const MyContext = createContext<MyContextProps | undefined>(undefined);
|
||||
|
||||
export const MyIonMetricProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
async function myIonStoreRead(key: string, not_found_output: any = {}) {
|
||||
const output = (await metric_store.get(key)) || JSON.stringify(not_found_output);
|
||||
return output;
|
||||
}
|
||||
|
||||
async function myIonStoreWrite(key: string, value: string) {
|
||||
await metric_store.set(key, value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
await metric_store.create();
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MyContext.Provider
|
||||
value={{
|
||||
myIonStoreRead,
|
||||
myIonStoreWrite,
|
||||
//
|
||||
Helloworld,
|
||||
}}
|
||||
>
|
||||
<AppUseTimeProvider>
|
||||
<AppOnRecorderProvider />
|
||||
<FullmarkCountProvider>
|
||||
<ListeningPracticeTimeSpentProvider>
|
||||
<MatchingFrenzyCorrectCountProvider>
|
||||
<ConnectivesRevisionCorrectCountProvider>
|
||||
{/* */}
|
||||
{children}
|
||||
</ConnectivesRevisionCorrectCountProvider>
|
||||
</MatchingFrenzyCorrectCountProvider>
|
||||
</ListeningPracticeTimeSpentProvider>
|
||||
</FullmarkCountProvider>
|
||||
</AppUseTimeProvider>
|
||||
</MyContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMyIonMetric = (): MyContextProps => {
|
||||
const context = useContext(MyContext);
|
||||
if (!context) {
|
||||
throw new Error('useMyContext must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface MyContextProps {
|
||||
myIonStoreRead: (key: string, not_found_output: any) => Promise<any>;
|
||||
myIonStoreWrite: (key: string, value: string) => Promise<void>;
|
||||
//
|
||||
|
||||
//
|
||||
Helloworld: () => void;
|
||||
}
|
156
002_source/ionic_mobile/src/contexts/MyIonQuiz.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
import React, { createContext, ReactNode, useContext, useState } from 'react';
|
||||
import { MATCH_FRENZY_SCOREBOARD_KEY } from '../constants';
|
||||
import IListeningPracticeQuestion from '../interfaces/IListeningPracticeQuestion';
|
||||
import { ConnectiveRevisionAllResult } from './ConnectiveRevisionRanking';
|
||||
import { MatchingFrenzyResult, MatchingFrezyRanking } from './MatchingFrezyRanking';
|
||||
import { useMyIonStore } from './MyIonStore';
|
||||
|
||||
const MyIonQuizContext = createContext<MyContextProps | undefined>(undefined);
|
||||
|
||||
export const MyIonQuizProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [my_context, setMyContext] = useState<string>('initial value');
|
||||
const { myIonStoreRead, myIonStoreWrite } = useMyIonStore();
|
||||
|
||||
function Helloworld() {
|
||||
listening_practice_correction_list;
|
||||
}
|
||||
const [listening_practice_result, setListeningPracticeResult] = useState<number>(0);
|
||||
const [listening_practice_current_test, setListeningPracticeCurrentTest] = useState<number>(0);
|
||||
const [listening_practice_correction_list, setListeningPracticeCorrectionList] = useState<any[] | []>([]);
|
||||
//
|
||||
function appendToListeningPracticeCorrectionList(question: IListeningPracticeQuestion) {
|
||||
setListeningPracticeCorrectionList([...listening_practice_correction_list, question]);
|
||||
}
|
||||
//
|
||||
function resetListeningPracticeCorrectionList() {
|
||||
setListeningPracticeCorrectionList([]);
|
||||
}
|
||||
//
|
||||
const [matching_frenzy_result, setMatchingFrenzyResult] = useState<number>(0);
|
||||
const [matching_frenzy_current_test, setMatchingFrenzyCurrentTest] = useState<number>(0);
|
||||
//
|
||||
const getCategoryKey = (category: string) => `${MATCH_FRENZY_SCOREBOARD_KEY}/${category}`;
|
||||
//
|
||||
async function loadMatchingFrenzyScoreBoard(cat_name: string): Promise<MatchingFrezyRanking> {
|
||||
let category_key = getCategoryKey(cat_name);
|
||||
|
||||
let current_result = JSON.parse(await myIonStoreRead(category_key));
|
||||
if (!current_result || JSON.stringify(current_result) == '{}') {
|
||||
current_result = { ranking: [] };
|
||||
}
|
||||
return current_result;
|
||||
}
|
||||
|
||||
async function saveMatchingFrenzyResultToScoreBoard(cat_name: string, result: MatchingFrenzyResult) {
|
||||
let category_key = getCategoryKey(cat_name);
|
||||
let current_result: MatchingFrezyRanking = await loadMatchingFrenzyScoreBoard(cat_name);
|
||||
current_result['ranking'] = [...current_result['ranking'], result];
|
||||
current_result['ranking'].sort((a: MatchingFrenzyResult, b: MatchingFrenzyResult) => b.result - a.result).splice(3);
|
||||
myIonStoreWrite(category_key, JSON.stringify(current_result));
|
||||
}
|
||||
//
|
||||
const [connective_revision_progress, setConnectiveRevisionProgress] = useState<number>(0);
|
||||
const [connective_revision_current_test, setConnectiveRevisionCurrentTest] = useState<number>(0);
|
||||
//
|
||||
const CONNECT_REVISION_SCOREBOARD_KEY = 'connective_revision_scoreboard';
|
||||
async function loadConnectiveRevisionScoreBoard(): Promise<ConnectiveRevisionAllResult> {
|
||||
let current_result = JSON.parse(await myIonStoreRead(CONNECT_REVISION_SCOREBOARD_KEY));
|
||||
if (!current_result || JSON.stringify(current_result) == '{}') {
|
||||
current_result = {};
|
||||
}
|
||||
return current_result;
|
||||
}
|
||||
|
||||
const [connective_revision_score, setConnectiveRevisionScore] = useState<number>(0);
|
||||
|
||||
async function saveConnectiveRevisionResultToScoreBoard(quiz_index: string, progress: number) {
|
||||
let current_result: ConnectiveRevisionAllResult = await loadConnectiveRevisionScoreBoard();
|
||||
current_result = { ...current_result, [quiz_index]: progress };
|
||||
myIonStoreWrite(CONNECT_REVISION_SCOREBOARD_KEY, JSON.stringify(current_result));
|
||||
}
|
||||
|
||||
return (
|
||||
<MyIonQuizContext.Provider
|
||||
value={{
|
||||
my_context,
|
||||
setMyContext,
|
||||
//
|
||||
listening_practice_current_test,
|
||||
setListeningPracticeCurrentTest,
|
||||
//
|
||||
listening_practice_result,
|
||||
setListeningPracticeResult,
|
||||
//
|
||||
listening_practice_correction_list,
|
||||
setListeningPracticeCorrectionList,
|
||||
appendToListeningPracticeCorrectionList,
|
||||
resetListeningPracticeCorrectionList,
|
||||
//
|
||||
matching_frenzy_current_test,
|
||||
setMatchingFrenzyCurrentTest,
|
||||
loadMatchingFrenzyScoreBoard,
|
||||
saveMatchingFrenzyResultToScoreBoard,
|
||||
matching_frenzy_result,
|
||||
setMatchingFrenzyResult,
|
||||
//
|
||||
connective_revision_progress,
|
||||
setConnectiveRevisionProgress,
|
||||
//
|
||||
connective_revision_score,
|
||||
setConnectiveRevisionScore,
|
||||
connective_revision_current_test,
|
||||
setConnectiveRevisionCurrentTest,
|
||||
loadConnectiveRevisionScoreBoard,
|
||||
saveConnectiveRevisionResultToScoreBoard,
|
||||
//
|
||||
Helloworld,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MyIonQuizContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMyIonQuizContext = (): MyContextProps => {
|
||||
const context = useContext(MyIonQuizContext);
|
||||
if (!context) {
|
||||
throw new Error('useMyContext must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface MyContextProps {
|
||||
my_context: string;
|
||||
setMyContext: React.Dispatch<React.SetStateAction<string>>;
|
||||
//
|
||||
//
|
||||
listening_practice_result: number;
|
||||
setListeningPracticeResult: React.Dispatch<React.SetStateAction<number>>;
|
||||
//
|
||||
listening_practice_current_test: number;
|
||||
setListeningPracticeCurrentTest: React.Dispatch<React.SetStateAction<number>>;
|
||||
//
|
||||
listening_practice_correction_list: IListeningPracticeQuestion[];
|
||||
setListeningPracticeCorrectionList: React.Dispatch<React.SetStateAction<IListeningPracticeQuestion[]>>;
|
||||
appendToListeningPracticeCorrectionList: (question: IListeningPracticeQuestion) => void;
|
||||
resetListeningPracticeCorrectionList: () => void;
|
||||
//
|
||||
matching_frenzy_result: number;
|
||||
setMatchingFrenzyResult: React.Dispatch<React.SetStateAction<number>>;
|
||||
matching_frenzy_current_test: number;
|
||||
setMatchingFrenzyCurrentTest: React.Dispatch<React.SetStateAction<number>>;
|
||||
loadMatchingFrenzyScoreBoard: (cat_name: string) => Promise<MatchingFrezyRanking>;
|
||||
saveMatchingFrenzyResultToScoreBoard: (cat_name: string, result: MatchingFrenzyResult) => Promise<void>;
|
||||
//
|
||||
connective_revision_progress: number;
|
||||
setConnectiveRevisionProgress: React.Dispatch<React.SetStateAction<number>>;
|
||||
//
|
||||
connective_revision_score: number;
|
||||
setConnectiveRevisionScore: React.Dispatch<React.SetStateAction<number>>;
|
||||
connective_revision_current_test: number;
|
||||
setConnectiveRevisionCurrentTest: React.Dispatch<React.SetStateAction<number>>;
|
||||
loadConnectiveRevisionScoreBoard: () => Promise<ConnectiveRevisionAllResult>;
|
||||
saveConnectiveRevisionResultToScoreBoard: (quiz_index: string, progress: number) => Promise<void>;
|
||||
//
|
||||
Helloworld: () => void;
|
||||
}
|
113
002_source/ionic_mobile/src/contexts/MyIonStore.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
import { Drivers, Storage } from '@ionic/storage';
|
||||
import React, { createContext, ReactNode, useContext, useEffect, useState } from 'react';
|
||||
import ILesson from '../interfaces/ILesson';
|
||||
import { listLessonCategories } from '../public_data/listLessonCategories';
|
||||
|
||||
const user_store = new Storage({
|
||||
name: '__mydb',
|
||||
driverOrder: [Drivers.IndexedDB, Drivers.LocalStorage],
|
||||
});
|
||||
|
||||
const MyContext = createContext<MyContextProps | undefined>(undefined);
|
||||
|
||||
export const MyIonStoreProvider: React.FC<{ children: ReactNode }> = ({ children }) => {
|
||||
const [db, setDB] = useState<[]>([]);
|
||||
const [lesson_content, setLessonContent] = useState<ILesson[] | []>([]);
|
||||
|
||||
const [my_context, setMyContext] = useState<string>('initial value');
|
||||
const [test_first, setTestFirst] = useState<IFirst | undefined>(undefined);
|
||||
const [test_second, setTestSecond] = useState<ISecond | undefined>(undefined);
|
||||
const [test_third, setTestThird] = useState<IThird | undefined>(undefined);
|
||||
|
||||
async function myIonStoreRead(key: string, not_found_output: string = '{}'): Promise<string> {
|
||||
const output = (await user_store.get(key)) || not_found_output;
|
||||
return output;
|
||||
}
|
||||
|
||||
async function myIonStoreWrite(key: string, value: string): Promise<void> {
|
||||
await user_store.set(key, value);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
let cats = await listLessonCategories();
|
||||
setLessonContent(cats);
|
||||
})();
|
||||
|
||||
(async () => {
|
||||
await user_store.create();
|
||||
})();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<MyContext.Provider
|
||||
value={{
|
||||
db,
|
||||
setDB,
|
||||
lesson_contents: lesson_content,
|
||||
setLessonContent,
|
||||
user_store,
|
||||
//
|
||||
myIonStoreRead,
|
||||
myIonStoreWrite,
|
||||
//
|
||||
my_context,
|
||||
setMyContext,
|
||||
test_first,
|
||||
setTestFirst,
|
||||
test_second,
|
||||
setTestSecond,
|
||||
test_third,
|
||||
setTestThird,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</MyContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useMyIonStore = (): MyContextProps => {
|
||||
const context = useContext(MyContext);
|
||||
if (!context) {
|
||||
throw new Error('useMyContext must be used within a MyProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
interface MyContextProps {
|
||||
//
|
||||
db: [];
|
||||
setDB: React.Dispatch<React.SetStateAction<[]>>;
|
||||
lesson_contents: ILesson[] | [];
|
||||
setLessonContent: React.Dispatch<React.SetStateAction<ILesson[] | []>>;
|
||||
user_store: Storage;
|
||||
//
|
||||
myIonStoreRead: (key: string, not_found_output?: any) => Promise<string>;
|
||||
myIonStoreWrite: (key: string, value: string) => Promise<void>;
|
||||
//
|
||||
my_context: string;
|
||||
setMyContext: React.Dispatch<React.SetStateAction<string>>;
|
||||
test_first: IFirst | undefined;
|
||||
setTestFirst: React.Dispatch<React.SetStateAction<IFirst | undefined>>;
|
||||
test_second: ISecond | undefined;
|
||||
setTestSecond: React.Dispatch<React.SetStateAction<ISecond | undefined>>;
|
||||
test_third: IThird | undefined;
|
||||
setTestThird: React.Dispatch<React.SetStateAction<IThird | undefined>>;
|
||||
}
|
||||
|
||||
interface IFirst {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
}
|
||||
|
||||
interface ISecond {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
content: IFirst[] | [];
|
||||
}
|
||||
|
||||
interface IThird {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
content: ISecond[] | [];
|
||||
}
|
26
002_source/ionic_mobile/src/contexts/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { AppStateProvider } from './AppState';
|
||||
import { MyIonFavoriteProvider } from './MyIonFavorite';
|
||||
import { MyIonMetricProvider } from './MyIonMetric';
|
||||
import { MyIonQuizProvider } from './MyIonQuiz';
|
||||
import { MyIonStoreProvider } from './MyIonStore';
|
||||
|
||||
const ContextMeta = ({ children }: { children: React.ReactNode }) => {
|
||||
return (
|
||||
<>
|
||||
<AppStateProvider>
|
||||
<MyIonStoreProvider>
|
||||
<MyIonFavoriteProvider>
|
||||
<MyIonQuizProvider>
|
||||
<MyIonMetricProvider>
|
||||
{/* */}
|
||||
{children}
|
||||
</MyIonMetricProvider>
|
||||
</MyIonQuizProvider>
|
||||
</MyIonFavoriteProvider>
|
||||
</MyIonStoreProvider>
|
||||
</AppStateProvider>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContextMeta;
|
3
002_source/ionic_mobile/src/debug.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.debug {
|
||||
background-color: gold;
|
||||
}
|
7
002_source/ionic_mobile/src/google_fonts.css
Normal file
@@ -0,0 +1,7 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@100..900&family=Noto+Sans+TC:wght@100..900&display=swap');
|
||||
|
||||
* {
|
||||
font-family: 'Noto Sans TC', sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-style: normal;
|
||||
}
|
106
002_source/ionic_mobile/src/i18n.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
// the translations
|
||||
// (tip move them in a JSON file and import them)
|
||||
const resources = {
|
||||
en: {
|
||||
translation: {
|
||||
'Lesson': 'Lesson',
|
||||
'Quiz': 'Quiz',
|
||||
'Favorite': 'Favorite',
|
||||
'Loading': 'Loading',
|
||||
'menu.link-home': 'Home',
|
||||
'menu.link-stats': 'Stats',
|
||||
'home.title': 'Remove duplicate songs from your Spotify library.',
|
||||
'home.description':
|
||||
"Spotify Dedup cleans up your playlists and liked songs from your Spotify account. It's easy and fast.",
|
||||
'home.review':
|
||||
'Read what {{-supportersCount}} supporters think about Spotify Dedup on {{- linkOpen}}Buy Me a Coffee{{- linkClose}}',
|
||||
'home.login-button': 'Log in with Spotify',
|
||||
'meta.title': 'Spotify Dedup - Remove duplicate songs from your Spotify library',
|
||||
'meta.description':
|
||||
'Delete repeated songs from your Spotify playlists and liked songs automatically. Fix your music library. Quickly and easy.',
|
||||
'features.find-remove.header': 'Find & remove',
|
||||
'features.find-remove.body':
|
||||
'Dedup checks your playlists and liked songs in {{- strongOpen}}your Spotify library{{- strongClose}}. Once Dedup finds duplicates you can remove them on a per-playlist basis.',
|
||||
'features.safer.header': 'Safer',
|
||||
'features.safer.body':
|
||||
'Dedup will only remove {{- strongOpen}}duplicate songs{{- strongClose}}, leaving the rest of the playlist and liked songs untouched.',
|
||||
'features.open-source.header': 'Open Source',
|
||||
'features.open-source.body':
|
||||
"You might want to have a look at the {{- linkGithubOpen}}source code on GitHub{{- linkGithubClose}}. This web app uses the {{- linkWebApiOpen}}Spotify Web API{{- linkWebApiClose}} to manage user's playlists and liked songs.",
|
||||
'reviews.title': 'This is what users are saying',
|
||||
'footer.author': 'Made with ♥ by {{- linkOpen}}JMPerez 👨💻{{- linkClose}}',
|
||||
'footer.github': 'Check out the {{- linkOpen}}code on GitHub 📃{{- linkClose}}',
|
||||
'footer.bmc': 'Support the project {{- linkOpen}}buying a coffee ☕{{- linkClose}}',
|
||||
'bmc.button': 'Would you buy me a coffee?',
|
||||
'result.duplicate.reason-same-id': 'Duplicate',
|
||||
'result.duplicate.reason-same-data': 'Duplicate (same name, artist and duration)',
|
||||
'result.duplicate.track': '<0>{{trackName}}</0> <2>by</2> <4>{{trackArtistName}}</4>',
|
||||
'process.status.finding': 'Finding duplicates in your playlists and liked songs…',
|
||||
'process.status.complete': 'Processing complete!',
|
||||
'process.status.complete.body': 'Your playlists and liked songs have been processed!',
|
||||
'process.status.complete.dups.body':
|
||||
'Click on the {{- strongOpen}}Remove duplicates{{- strongClose}} button to get rid of duplicates in that playlist or liked songs collection.',
|
||||
'process.status.complete.nodups.body': "Congrats! You don't have duplicates in your playlists nor liked songs.",
|
||||
'process.reading-library': 'Going through your library, finding the playlists you own and your liked songs…',
|
||||
'process.processing_one': 'Searching for duplicate songs, wait a sec. Still to process {{count}} playlist…',
|
||||
'process.processing_other': 'Searching for duplicate songs, wait a sec. Still to process {{count}} playlists…',
|
||||
'process.saved.title': 'liked songs in your library',
|
||||
'process.saved.duplicates_one': 'This collection has {{count}} duplicate song',
|
||||
'process.saved.duplicates_other': 'This collection has {{count}} duplicate songs',
|
||||
'process.saved.remove-button': 'Remove duplicates from your liked songs',
|
||||
'process.playlist.duplicates_one': 'This playlist has {{count}} duplicate song',
|
||||
'process.playlist.duplicates_other': 'This playlist has {{count}} duplicate songs',
|
||||
'process.playlist.remove-button': 'Remove duplicates from this playlist',
|
||||
'process.items.removed': 'Duplicates removed',
|
||||
'faq.section-title': 'Frequently asked questions',
|
||||
'faq.question-1': 'What does this web application do?',
|
||||
'faq.answer-1':
|
||||
'Spotify Dedup helps you clean up your music libraries on Spotify by identifying and deleting duplicate songs across playlists and liked songs.',
|
||||
'faq.question-2': 'How does it find duplicates?',
|
||||
'faq.answer-2':
|
||||
"Dedup finds duplicates based on the songs identifier, title, artist, and duration similarity. It identifies duplicates that Spotify's application does not catch.",
|
||||
'faq.question-3': "How is Dedup better than Spotify's built-in duplicate detection?",
|
||||
'faq.answer-3':
|
||||
"Spotify's applications only warn about duplicates when adding a song to a playlit or liked songs with the exact same song identifier. However, the same song can have multiple identifiers on Spotify that both in the same release or in several ones. Dedup detects duplicates based on title, artist, and duration similarity.",
|
||||
'faq.question-4': 'When duplicates are found, which songs are removed?',
|
||||
'faq.answer-4': 'Dedup will keep the first song within a group of duplicate songs, and will remove the rest.',
|
||||
'faq.question-5': 'Is my data safe with this web application?',
|
||||
'faq.answer-5':
|
||||
'Yes, this web application does not store any user data on its servers. It only requests the minimum set of permissions necessary to process your library.',
|
||||
'faq.question-6': 'What permissions does this web application require?',
|
||||
'faq.answer-6':
|
||||
"This web application uses Spotify's authentication service to access your liked songs and playlists in your library.",
|
||||
'faq.question-7': 'How has this tool been tested?',
|
||||
'faq.answer-7':
|
||||
'This tool has been battle-tested by thousands of users who have used it to identify duplicates in millions of playlists since 2014.',
|
||||
'faq.question-8': 'Can this tool delete duplicates across multiple playlists?',
|
||||
'faq.answer-8':
|
||||
"This tool can identify and delete duplicates on all playlists in a library, but doesn't detect duplicates of a song across multiple playlists.",
|
||||
'faq.question-9': 'How can I revoke the permissions granted to this web application?',
|
||||
'faq.answer-9':
|
||||
"You can revoke the permissions granted to this web application at any time on your Spotify account, under the 'Apps' section.",
|
||||
'faq.question-10': 'Does this tool work with other music streaming services?',
|
||||
'faq.answer-10': "No, this tool only works with Spotify through Spotify's Web API.",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(initReactI18next) // passes i18n down to react-i18next
|
||||
.init({
|
||||
// the translations
|
||||
// (tip move them in a JSON file and import them,
|
||||
// or even better, manage them via a UI: https://react.i18next.com/guides/multiple-translation-files#manage-your-translations-with-a-management-gui)
|
||||
resources,
|
||||
lng: 'en', // if you're using a language detector, do not define the lng option
|
||||
fallbackLng: 'en',
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false, // react already safes from xss => https://www.i18next.com/translation-function/interpolation#unescape
|
||||
},
|
||||
});
|
||||
|
||||
export default i18n;
|
5
002_source/ionic_mobile/src/interfaces/Helloworld.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
interface ContainerProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default ContainerProps;
|
@@ -0,0 +1,11 @@
|
||||
import IConnectivesRevisionQuestion from './IConnectivesRevisionQuestion';
|
||||
|
||||
interface IConnectivesRevisionCategory {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
cat_info: string;
|
||||
cat_name: string;
|
||||
content: IConnectivesRevisionQuestion[] | [];
|
||||
}
|
||||
|
||||
export default IConnectivesRevisionCategory;
|
@@ -0,0 +1,12 @@
|
||||
interface IConnectivesRevisionQuestion {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
image: string;
|
||||
sound: string;
|
||||
word_c: string;
|
||||
word: string;
|
||||
sample_e: string;
|
||||
sample_c: string;
|
||||
}
|
||||
|
||||
export default IConnectivesRevisionQuestion;
|
11
002_source/ionic_mobile/src/interfaces/ILesson.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import ILessonCategory from './ILessonCategory';
|
||||
|
||||
interface ILesson {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
name: string;
|
||||
path: string;
|
||||
content: ILessonCategory[] | [];
|
||||
}
|
||||
|
||||
export default ILesson;
|
11
002_source/ionic_mobile/src/interfaces/ILessonCategory.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import IWordCard from './IWordCard';
|
||||
|
||||
interface ILessonCategory {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
cat_info: string;
|
||||
cat_name: string;
|
||||
content: IWordCard[] | [];
|
||||
}
|
||||
|
||||
export default ILessonCategory;
|
@@ -0,0 +1,11 @@
|
||||
import IListeningPracticeQuestion from './IListeningPracticeQuestion';
|
||||
|
||||
interface IListeningPracticeCategory {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
cat_info: string;
|
||||
cat_name: string;
|
||||
content: IListeningPracticeQuestion[] | [];
|
||||
}
|
||||
|
||||
export default IListeningPracticeCategory;
|
@@ -0,0 +1,12 @@
|
||||
interface IListeningPracticeQuestion {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
image: string;
|
||||
sound: string;
|
||||
word_c: string;
|
||||
word: string;
|
||||
sample_e: string;
|
||||
sample_c: string;
|
||||
}
|
||||
|
||||
export default IListeningPracticeQuestion;
|
@@ -0,0 +1,11 @@
|
||||
import IMatchingFrenzyQuestion from './IMatchingFrenzyQuestion';
|
||||
|
||||
interface IMatchingFrenzyCategory {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
cat_info: string;
|
||||
cat_name: string;
|
||||
content: IMatchingFrenzyQuestion[] | [];
|
||||
}
|
||||
|
||||
export default IMatchingFrenzyCategory;
|
@@ -0,0 +1,12 @@
|
||||
interface IMatchingFrenzyQuestion {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
image: string;
|
||||
sound: string;
|
||||
word_c: string;
|
||||
word: string;
|
||||
sample_e: string;
|
||||
sample_c: string;
|
||||
}
|
||||
|
||||
export default IMatchingFrenzyQuestion;
|
14
002_source/ionic_mobile/src/interfaces/IWordCard.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
interface IWordCard {
|
||||
test_s: string;
|
||||
test_i: number;
|
||||
image: string;
|
||||
sound: string;
|
||||
word_c: string;
|
||||
word: string;
|
||||
sample_e: string;
|
||||
sample_c: string;
|
||||
image_url: string;
|
||||
sound_url: string;
|
||||
}
|
||||
|
||||
export default IWordCard;
|
@@ -0,0 +1,5 @@
|
||||
interface LessonCategoriesProps {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export default LessonCategoriesProps;
|