diff --git a/assets/images/index.ts b/assets/images/index.ts index 7aae81044..e52dc216d 100644 --- a/assets/images/index.ts +++ b/assets/images/index.ts @@ -45,8 +45,11 @@ import OpenLockIcon from './open-lock-icon.svg' import QRCodeIcon from './qr-code-icon.svg' import RepeatIcon from './repeat-icon.svg' import ShareIcon from './share-icon.svg' -import StartIconGrey from './star-icon-grey.svg' -import StartIconWhite from './star-icon-white.svg' +import StarCircleIconGreyFilled from './star-circle-icon-grey-filled.svg' +import StarCircleIconGrey from './star-circle-icon-grey.svg' +import StarIconGreyFilled from './star-icon-grey-filled.svg' +import StarIconGrey from './star-icon-grey.svg' +import StarIconWhite from './star-icon-white.svg' import TrashIcon from './trash-bin-icon.svg' import TrophyIcon from './trophy-icon.svg' import VolumeUpCircleIcon from './volume-up-circle-icon.svg' @@ -99,8 +102,11 @@ export { QRCodeIcon, RepeatIcon, ShareIcon, - StartIconGrey, - StartIconWhite, + StarCircleIconGrey, + StarCircleIconGreyFilled, + StarIconGrey, + StarIconGreyFilled, + StarIconWhite, TrashIcon, TrophyIcon, VolumeUpCircleIcon diff --git a/assets/images/star-circle-icon-grey-filled.svg b/assets/images/star-circle-icon-grey-filled.svg new file mode 100644 index 000000000..814d1651d --- /dev/null +++ b/assets/images/star-circle-icon-grey-filled.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/star-circle-icon-grey.svg b/assets/images/star-circle-icon-grey.svg new file mode 100644 index 000000000..ed2857cae --- /dev/null +++ b/assets/images/star-circle-icon-grey.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/star-icon-grey-filled.svg b/assets/images/star-icon-grey-filled.svg new file mode 100644 index 000000000..ca8713c5f --- /dev/null +++ b/assets/images/star-icon-grey-filled.svg @@ -0,0 +1,3 @@ + + + diff --git a/release-notes/unreleased/LUN-132-favorites.yml b/release-notes/unreleased/LUN-132-favorites.yml new file mode 100644 index 000000000..5f8ff2a60 --- /dev/null +++ b/release-notes/unreleased/LUN-132-favorites.yml @@ -0,0 +1,6 @@ +issue_key: LUN-132 +show_in_stores: true +platforms: + - android + - ios +de: Favoriten können nun gespeichert werden. diff --git a/src/components/AudioPlayer.tsx b/src/components/AudioPlayer.tsx index 0af8e23c3..95af7cb65 100644 --- a/src/components/AudioPlayer.tsx +++ b/src/components/AudioPlayer.tsx @@ -15,14 +15,7 @@ export interface AudioPlayerProps { submittedAlternative?: string | null } -const StyledView = styled.View` - align-items: center; - margin-bottom: ${props => props.theme.spacings.sm}; -` - const VolumeIcon = styled.TouchableOpacity<{ disabled: boolean; isActive: boolean }>` - position: absolute; - top: ${wp('-4.5%')}px; width: ${wp('9%')}px; height: ${wp('9%')}px; border-radius: 50px; @@ -107,15 +100,13 @@ const AudioPlayer = ({ document, disabled, submittedAlternative }: AudioPlayerPr } return ( - - - - - + + + ) } diff --git a/src/components/DocumentImageSection.tsx b/src/components/DocumentImageSection.tsx new file mode 100644 index 000000000..21d0766b7 --- /dev/null +++ b/src/components/DocumentImageSection.tsx @@ -0,0 +1,49 @@ +import React, { ReactElement } from 'react' +import { widthPercentageToDP as wp } from 'react-native-responsive-screen' +import styled from 'styled-components/native' + +import { Document } from '../constants/endpoints' +import AudioPlayer from './AudioPlayer' +import FavoriteButton from './FavoriteButton' +import ImageCarousel from './ImageCarousel' + +const AudioContainer = styled.View` + position: absolute; + bottom: ${wp('-4.5%')}px; + align-self: center; +` +const FavoriteContainer = styled.View` + position: absolute; + top: 0; + right: ${props => props.theme.spacings.md}; +` + +const Container = styled.View` + margin-bottom: ${props => props.theme.spacings.md}; +` + +interface Props { + document: Document + audioDisabled?: boolean + minimized?: boolean + submittedAlternative?: string | null +} + +const DocumentImageSection = ({ + document, + audioDisabled = false, + minimized = false, + submittedAlternative +}: Props): ReactElement => ( + + + + + + + + + +) + +export default DocumentImageSection diff --git a/src/components/FavoriteButton.tsx b/src/components/FavoriteButton.tsx new file mode 100644 index 000000000..e6e5779dd --- /dev/null +++ b/src/components/FavoriteButton.tsx @@ -0,0 +1,62 @@ +import { useFocusEffect } from '@react-navigation/native' +import React, { ReactElement } from 'react' +import { widthPercentageToDP as wp } from 'react-native-responsive-screen' +import styled from 'styled-components/native' + +import { StarCircleIconGrey, StarCircleIconGreyFilled } from '../../assets/images' +import { Document } from '../constants/endpoints' +import useLoadAsync from '../hooks/useLoadAsync' +import AsyncStorage from '../services/AsyncStorage' +import { reportError } from '../services/sentry' + +const Icon = styled(StarCircleIconGreyFilled)` + min-width: ${wp('9%')}px; + min-height: ${wp('9%')}px; +` +const IconOutline = styled(StarCircleIconGrey)` + min-width: ${wp('9%')}px; + min-height: ${wp('9%')}px; +` +const Button = styled.TouchableOpacity` + justify-content: center; + align-items: center; + shadow-color: ${props => props.theme.colors.shadow}; + shadow-radius: 5px; + shadow-offset: 1px 1px; + shadow-opacity: 0.5; +` + +interface Props { + document: Document + onFavoritesChanged?: () => void +} + +const FavoriteButton = ({ document, onFavoritesChanged }: Props): ReactElement | null => { + const { data: isFavorite, refresh } = useLoadAsync(AsyncStorage.isFavorite, document.id) + + useFocusEffect(refresh) + + const onPress = async () => { + if (isFavorite) { + await AsyncStorage.removeFavorite(document.id).catch(reportError) + } else { + await AsyncStorage.addFavorite(document.id).catch(reportError) + } + refresh() + if (onFavoritesChanged) { + onFavoritesChanged() + } + } + + if (isFavorite === null) { + return null + } + + return ( + + ) +} + +export default FavoriteButton diff --git a/src/components/VocabularyList.tsx b/src/components/VocabularyList.tsx index 63b7626ca..68d53fb61 100644 --- a/src/components/VocabularyList.tsx +++ b/src/components/VocabularyList.tsx @@ -17,11 +17,18 @@ const Root = styled.View` interface VocabularyListScreenProps { documents: Document[] onItemPress: (index: number) => void + onFavoritesChanged?: () => void + title: string } -const VocabularyList = ({ documents, onItemPress }: VocabularyListScreenProps): JSX.Element => { +const VocabularyList = ({ + documents, + onItemPress, + onFavoritesChanged, + title +}: VocabularyListScreenProps): JSX.Element => { const renderItem = ({ item, index }: { item: Document; index: number }): JSX.Element => ( - onItemPress(index)} /> + onItemPress(index)} onFavoritesChanged={onFavoritesChanged} /> ) return ( @@ -29,7 +36,7 @@ const VocabularyList = ({ documents, onItemPress }: VocabularyListScreenProps): } diff --git a/src/components/VocabularyListItem.tsx b/src/components/VocabularyListItem.tsx index d3caf3a46..b2baf8aff 100644 --- a/src/components/VocabularyListItem.tsx +++ b/src/components/VocabularyListItem.tsx @@ -5,6 +5,7 @@ import styled from 'styled-components/native' import { Document } from '../constants/endpoints' import { getArticleColor } from '../services/helpers' import AudioPlayer from './AudioPlayer' +import FavoriteButton from './FavoriteButton' import ListItem from './ListItem' import { ContentTextLight } from './text/Content' @@ -24,17 +25,23 @@ const StyledTitle = styled(ContentTextLight)<{ articleColor: string }>` height: ${wp('5%')}px; text-align: center; ` -const Speaker = styled.View` - padding-right: ${props => props.theme.spacings.xl}; - padding-top: ${props => props.theme.spacings.sm}; +const RightChildrenContainer = styled.View` + flex-direction: row; + justify-content: space-between; +` + +const FavButtonContainer = styled.View` + padding: ${props => `0 ${props.theme.spacings.xs} 0 ${props.theme.spacings.sm}`}; + align-self: center; ` interface VocabularyListItemProps { document: Document onPress: () => void + onFavoritesChanged?: () => void } -const VocabularyListItem = ({ document, onPress }: VocabularyListItemProps): ReactElement => { +const VocabularyListItem = ({ document, onPress, onFavoritesChanged }: VocabularyListItemProps): ReactElement => { const { article, word, document_image: documentImage } = document const title = {article.value} @@ -50,9 +57,12 @@ const VocabularyListItem = ({ document, onPress }: VocabularyListItemProps): Rea onPress={onPress} icon={icon} rightChildren={ - + - + + + + } /> ) diff --git a/src/components/__tests__/FavoriteButton.spec.tsx b/src/components/__tests__/FavoriteButton.spec.tsx new file mode 100644 index 000000000..7bf6dbc6d --- /dev/null +++ b/src/components/__tests__/FavoriteButton.spec.tsx @@ -0,0 +1,45 @@ +import { NavigationContainer } from '@react-navigation/native' +import { fireEvent, waitFor } from '@testing-library/react-native' +import React from 'react' + +import AsyncStorage from '../../services/AsyncStorage' +import DocumentBuilder from '../../testing/DocumentBuilder' +import render from '../../testing/render' +import FavoriteButton from '../FavoriteButton' + +describe('FavoriteButton', () => { + const document = new DocumentBuilder(1).build()[0] + + const renderFavoriteButton = () => + render( + + + + ) + + it('should add favorite on click', async () => { + await AsyncStorage.setFavorites([]) + await expect(AsyncStorage.isFavorite(document.id)).resolves.toBe(false) + + const { getByTestId } = renderFavoriteButton() + + await waitFor(() => expect(getByTestId('add')).toBeTruthy()) + fireEvent.press(getByTestId('add')) + + await waitFor(() => expect(getByTestId('remove')).toBeTruthy()) + await expect(AsyncStorage.isFavorite(document.id)).resolves.toBe(true) + }) + + it('should remove favorite on click', async () => { + await AsyncStorage.setFavorites([document.id]) + await expect(AsyncStorage.isFavorite(document.id)).resolves.toBe(true) + + const { getByTestId } = renderFavoriteButton() + + await waitFor(() => expect(getByTestId('remove')).toBeTruthy()) + fireEvent.press(getByTestId('remove')) + + await waitFor(() => expect(getByTestId('add')).toBeTruthy()) + await expect(AsyncStorage.isFavorite(document.id)).resolves.toBe(false) + }) +}) diff --git a/src/components/__tests__/VocabularyList.spec.tsx b/src/components/__tests__/VocabularyList.spec.tsx index 04affd344..54f53a576 100644 --- a/src/components/__tests__/VocabularyList.spec.tsx +++ b/src/components/__tests__/VocabularyList.spec.tsx @@ -7,6 +7,10 @@ import { mockUseLoadAsyncWithData } from '../../testing/mockUseLoadFromEndpoint' import render from '../../testing/render' import VocabularyList from '../VocabularyList' +jest.mock('../FavoriteButton', () => () => { + const { Text } = require('react-native') + return FavoriteButton +}) jest.mock('../AudioPlayer', () => { const Text = require('react-native').Text return () => AudioPlayer @@ -24,10 +28,10 @@ describe('VocabularyList', () => { mockUseLoadAsyncWithData(documents) const { getByText, getAllByText, getAllByTestId } = render( - + ) - expect(getByText(labels.exercises.vocabularyList.title)).toBeTruthy() + expect(getByText('Title')).toBeTruthy() expect(getByText(`2 ${labels.general.words}`)).toBeTruthy() expect(getByText('Der')).toBeTruthy() expect(getByText('Spachtel')).toBeTruthy() @@ -38,7 +42,7 @@ describe('VocabularyList', () => { }) it('should call onItemPress with correct index', () => { - const { getByText } = render() + const { getByText } = render() expect(onItemPress).toHaveBeenCalledTimes(0) diff --git a/src/components/__tests__/VocabularyListItem.spec.tsx b/src/components/__tests__/VocabularyListItem.spec.tsx index ed668dc66..18a833668 100644 --- a/src/components/__tests__/VocabularyListItem.spec.tsx +++ b/src/components/__tests__/VocabularyListItem.spec.tsx @@ -7,6 +7,10 @@ import { Document } from '../../constants/endpoints' import render from '../../testing/render' import VocabularyListItem from '../VocabularyListItem' +jest.mock('../FavoriteButton', () => () => { + const { Text } = require('react-native') + return FavoriteButton +}) jest.mock('../AudioPlayer', () => () => null) describe('VocabularyListItem', () => { diff --git a/src/constants/endpoints.ts b/src/constants/endpoints.ts index 3c7a3b92d..494f251eb 100644 --- a/src/constants/endpoints.ts +++ b/src/constants/endpoints.ts @@ -41,7 +41,8 @@ export const ENDPOINTS = { groupInfo: 'group_info', trainingSet: 'training_set', trainingSets: 'training_sets', - documents: 'documents/:id' + documents: 'documents/:id', + document: 'words' } export const ForbiddenError = 'Request failed with status code 403' diff --git a/src/constants/labels.json b/src/constants/labels.json index 36c8d93dd..ea2fcf444 100644 --- a/src/constants/labels.json +++ b/src/constants/labels.json @@ -147,5 +147,6 @@ "word": "Wort", "words": "Wörter", "customModalCancel": "Zurück" - } + }, + "favorites": "Favoriten" } diff --git a/src/hooks/useLoadDocuments.ts b/src/hooks/useLoadDocuments.ts index ee2c635e8..70b631a69 100644 --- a/src/hooks/useLoadDocuments.ts +++ b/src/hooks/useLoadDocuments.ts @@ -17,7 +17,7 @@ export interface DocumentFromServer { alternatives: AlternativeWordFromServer[] } -const formatServerResponse = (documents: DocumentFromServer[]): Document[] => +export const formatServerResponse = (documents: DocumentFromServer[]): Document[] => documents.map(document => ({ ...document, article: ARTICLES[document.article], diff --git a/src/hooks/useLoadFavorites.ts b/src/hooks/useLoadFavorites.ts new file mode 100644 index 000000000..58dce9dbe --- /dev/null +++ b/src/hooks/useLoadFavorites.ts @@ -0,0 +1,21 @@ +import { Document, ENDPOINTS } from '../constants/endpoints' +import AsyncStorage from '../services/AsyncStorage' +import { getFromEndpoint } from '../services/axios' +import useLoadAsync, { Return } from './useLoadAsync' +import { DocumentFromServer, formatServerResponse } from './useLoadDocuments' + +export const loadFavorites = async (): Promise => { + const favoriteIds = await AsyncStorage.getFavorites() + const documents = await Promise.all( + favoriteIds.map(id => { + const url = `${ENDPOINTS.document}/${id}` + return getFromEndpoint(url) + }) + ) + + return formatServerResponse(documents) +} + +const useLoadFavorites = (): Return => useLoadAsync(loadFavorites, {}) + +export default useLoadFavorites diff --git a/src/navigation/BottomTabNavigator.tsx b/src/navigation/BottomTabNavigator.tsx index 8311dc2ab..ee1eb50c9 100644 --- a/src/navigation/BottomTabNavigator.tsx +++ b/src/navigation/BottomTabNavigator.tsx @@ -4,8 +4,9 @@ import { SafeAreaView } from 'react-native' import { widthPercentageToDP as wp } from 'react-native-responsive-screen' import { useTheme } from 'styled-components/native' -import { HomeIconGrey, HomeIconWhite } from '../../assets/images' +import { HomeIconGrey, HomeIconWhite, StarIconGrey, StarIconWhite } from '../../assets/images' import labels from '../constants/labels.json' +import FavoritesScreen from '../routes/FavoritesScreen' import HomeStackNavigator from './HomeStackNavigator' import { RoutesParams } from './NavigationTypes' @@ -22,9 +23,7 @@ const BottomTabNavigator = (): JSX.Element | null => { tabBarActiveTintColor: theme.colors.background, tabBarStyle: { backgroundColor: theme.colors.primary, - height: wp('14%'), - // TODO LUN-132: Delete - display: 'none' + height: wp('14%') }, tabBarItemStyle: { height: wp('14%'), padding: wp('2%') }, tabBarLabelStyle: { fontSize: wp('3%') } @@ -42,20 +41,19 @@ const BottomTabNavigator = (): JSX.Element | null => { title: labels.general.home }} /> - {/* TODO LUN-132: Uncomment */} - {/* */} - {/* focused ? ( */} - {/* */} - {/* ) : ( */} - {/* */} - {/* ), */} - {/* title: labels.general.favorites */} - {/* }} */} - {/* /> */} + + focused ? ( + + ) : ( + + ), + title: labels.general.favorites + }} + /> {/* { documentIndex: number + disciplineId: number | null } export interface ExercisesParams extends Omit { @@ -51,7 +52,7 @@ export type RoutesParams = { discipline: Discipline initialSelection: boolean } - VocabularyDetail: DetailExerciseParams + VocabularyDetail: VocabularyDetailExerciseParams Exercises: ExercisesParams VocabularyList: ExerciseParams WordChoiceExercise: ExerciseParams @@ -66,6 +67,7 @@ export type RoutesParams = { } Imprint: undefined ManageDisciplines: undefined + Favorites: undefined } export type Route = keyof RoutesParams diff --git a/src/routes/FavoritesScreen.tsx b/src/routes/FavoritesScreen.tsx new file mode 100644 index 000000000..7af95de6e --- /dev/null +++ b/src/routes/FavoritesScreen.tsx @@ -0,0 +1,48 @@ +import { CommonActions, RouteProp, useFocusEffect } from '@react-navigation/native' +import { StackNavigationProp } from '@react-navigation/stack' +import React, { ReactElement } from 'react' + +import ServerResponseHandler from '../components/ServerResponseHandler' +import VocabularyList from '../components/VocabularyList' +import labels from '../constants/labels.json' +import useLoadFavorites from '../hooks/useLoadFavorites' +import { RoutesParams } from '../navigation/NavigationTypes' + +interface FavoritesScreenProps { + route: RouteProp + navigation: StackNavigationProp +} + +const FavoritesScreen = ({ navigation }: FavoritesScreenProps): ReactElement => { + const { data, error, refresh } = useLoadFavorites() + + useFocusEffect(refresh) + + const onItemPress = (index: number) => { + if (!data) { + return + } + navigation.navigate('VocabularyDetail', { + disciplineId: null, + disciplineTitle: labels.general.favorites, + documents: data, + documentIndex: index, + closeExerciseAction: CommonActions.goBack() + }) + } + + return ( + + {data && ( + + )} + + ) +} + +export default FavoritesScreen diff --git a/src/routes/VocabularyDetailScreen.tsx b/src/routes/VocabularyDetailScreen.tsx index e3d873359..cdb583b15 100644 --- a/src/routes/VocabularyDetailScreen.tsx +++ b/src/routes/VocabularyDetailScreen.tsx @@ -4,15 +4,18 @@ import React, { ReactElement } from 'react' import styled from 'styled-components/native' import { ArrowRightIcon } from '../../assets/images' -import AudioPlayer from '../components/AudioPlayer' import Button from '../components/Button' +import DocumentImageSection from '../components/DocumentImageSection' import ExerciseHeader from '../components/ExerciseHeader' -import ImageCarousel from '../components/ImageCarousel' import WordItem from '../components/WordItem' import { BUTTONS_THEME } from '../constants/data' import labels from '../constants/labels.json' import { RoutesParams } from '../navigation/NavigationTypes' +const Container = styled.View` + flex: 1; +` + const ItemContainer = styled.View` margin: ${props => props.theme.spacings.xl} 0; height: 10%; @@ -33,22 +36,21 @@ interface VocabularyDetailScreenProps { const VocabularyDetailScreen = ({ route, navigation }: VocabularyDetailScreenProps): ReactElement => { const { documents, documentIndex } = route.params const document = documents[documentIndex] - const { word, article, document_image: image } = document + const { word, article } = document const hasNextDocument = documentIndex + 1 < documents.length const goToNextWord = () => navigation.navigate('VocabularyDetail', { ...route.params, documentIndex: documentIndex + 1 }) return ( - <> + - - + @@ -68,7 +70,7 @@ const VocabularyDetailScreen = ({ route, navigation }: VocabularyDetailScreenPro /> )} - + ) } diff --git a/src/routes/VocabularyListScreen.tsx b/src/routes/VocabularyListScreen.tsx index 9da9bdc60..39dc8dacf 100644 --- a/src/routes/VocabularyListScreen.tsx +++ b/src/routes/VocabularyListScreen.tsx @@ -5,6 +5,7 @@ import React, { useEffect } from 'react' import ExerciseHeader from '../components/ExerciseHeader' import VocabularyList from '../components/VocabularyList' import { ExerciseKeys } from '../constants/data' +import labels from '../constants/labels.json' import { RoutesParams } from '../navigation/NavigationTypes' import AsyncStorage from '../services/AsyncStorage' import { reportError } from '../services/sentry' @@ -27,7 +28,11 @@ const VocabularyListScreen = ({ route, navigation }: VocabularyListScreenProps): return ( <> - + ) } diff --git a/src/routes/__tests__/VocabularyListScreen.spec.tsx b/src/routes/__tests__/VocabularyListScreen.spec.tsx index 56cf03483..e065c9114 100644 --- a/src/routes/__tests__/VocabularyListScreen.spec.tsx +++ b/src/routes/__tests__/VocabularyListScreen.spec.tsx @@ -10,6 +10,11 @@ import { mockUseLoadAsyncWithData } from '../../testing/mockUseLoadFromEndpoint' import render from '../../testing/render' import VocabularyListScreen from '../VocabularyListScreen' +jest.mock('../../components/FavoriteButton', () => { + const Text = require('react-native').Text + return () => FavoriteButton +}) + jest.mock('../../services/AsyncStorage', () => ({ setExerciseProgress: jest.fn(() => Promise.resolve()) })) diff --git a/src/routes/choice-exercises/__tests__/ArticleChoiceExerciseScreen.spec.tsx b/src/routes/choice-exercises/__tests__/ArticleChoiceExerciseScreen.spec.tsx index 9cd1859b0..c8942da73 100644 --- a/src/routes/choice-exercises/__tests__/ArticleChoiceExerciseScreen.spec.tsx +++ b/src/routes/choice-exercises/__tests__/ArticleChoiceExerciseScreen.spec.tsx @@ -11,6 +11,11 @@ import createNavigationMock from '../../../testing/createNavigationPropMock' import render from '../../../testing/render' import ArticleChoiceExerciseScreen from '../ArticleChoiceExerciseScreen' +jest.mock('../../../components/FavoriteButton', () => () => { + const { Text } = require('react-native') + return FavoriteButton +}) + jest.mock('../../../services/helpers', () => ({ ...jest.requireActual('../../../services/helpers'), shuffleArray: jest.fn(it => it) diff --git a/src/routes/choice-exercises/__tests__/WordChoiceExerciseScreen.spec.tsx b/src/routes/choice-exercises/__tests__/WordChoiceExerciseScreen.spec.tsx index 6ed91626c..27c2453d0 100644 --- a/src/routes/choice-exercises/__tests__/WordChoiceExerciseScreen.spec.tsx +++ b/src/routes/choice-exercises/__tests__/WordChoiceExerciseScreen.spec.tsx @@ -11,6 +11,10 @@ import WordChoiceExerciseScreen from '../WordChoiceExerciseScreen' jest.useFakeTimers() +jest.mock('../../../components/FavoriteButton', () => { + const Text = require('react-native').Text + return () => FavoriteButton +}) jest.mock('../../../services/helpers', () => ({ ...jest.requireActual('../../../services/helpers'), shuffleArray: jest.fn(it => it) diff --git a/src/routes/choice-exercises/components/SingleChoiceExercise.tsx b/src/routes/choice-exercises/components/SingleChoiceExercise.tsx index e5ab8ed30..d02b8121e 100644 --- a/src/routes/choice-exercises/components/SingleChoiceExercise.tsx +++ b/src/routes/choice-exercises/components/SingleChoiceExercise.tsx @@ -4,10 +4,9 @@ import React, { ReactElement, useEffect, useState, useCallback } from 'react' import styled from 'styled-components/native' import { ArrowRightIcon } from '../../../../assets/images' -import AudioPlayer from '../../../components/AudioPlayer' import Button from '../../../components/Button' +import DocumentImageSection from '../../../components/DocumentImageSection' import ExerciseHeader from '../../../components/ExerciseHeader' -import ImageCarousel from '../../../components/ImageCarousel' import { Answer, BUTTONS_THEME, numberOfMaxRetries, SIMPLE_RESULTS, SimpleResult } from '../../../constants/data' import { AlternativeWord, Document } from '../../../constants/endpoints' import labels from '../../../constants/labels.json' @@ -138,8 +137,7 @@ const ChoiceExerciseScreen = ({ <> - {document.document_image.length > 0 && } - + { const professionItems = selectedProfessions?.map(id => { const unselectProfessionAndRefresh = () => { - removeSelectedProfession(id).then(refreshSelectedProfessions).catch(reportError) + AsyncStorage.removeSelectedProfession(id).then(refreshSelectedProfessions).catch(reportError) } return }) const customDisciplineItems = customDisciplines?.map(apiKey => { const deleteCustomDisciplineAndRefresh = () => { - removeCustomDiscipline(apiKey).then(refreshCustomDisciplines).catch(reportError) + AsyncStorage.removeCustomDiscipline(apiKey).then(refreshCustomDisciplines).catch(reportError) } return }) diff --git a/src/routes/write-exercise/WriteExerciseScreen.tsx b/src/routes/write-exercise/WriteExerciseScreen.tsx index 72f314394..63b4c3a08 100644 --- a/src/routes/write-exercise/WriteExerciseScreen.tsx +++ b/src/routes/write-exercise/WriteExerciseScreen.tsx @@ -8,7 +8,6 @@ import styled from 'styled-components/native' import { ArrowRightIcon } from '../../../assets/images' import Button from '../../components/Button' import ExerciseHeader from '../../components/ExerciseHeader' -import ImageCarousel from '../../components/ImageCarousel' import { BUTTONS_THEME, ExerciseKeys, numberOfMaxRetries, SIMPLE_RESULTS } from '../../constants/data' import labels from '../../constants/labels.json' import { useIsKeyboardVisible } from '../../hooks/useIsKeyboardVisible' @@ -17,13 +16,8 @@ import { saveExerciseProgress } from '../../services/AsyncStorage' import { moveToEnd, shuffleArray } from '../../services/helpers' import InteractionSection from './components/InteractionSection' -const StyledContainer = styled.View` - padding-top: ${props => props.theme.spacings.md}; - padding-bottom: ${props => props.theme.spacings.lg}; +const ButtonContainer = styled.View` align-items: center; - position: relative; - width: 100%; - height: 85%; ` export interface WriteExerciseScreenProps { @@ -121,15 +115,12 @@ const WriteExerciseScreen = ({ route, navigation }: WriteExerciseScreenProps): R - - - - - + + {isAnswerSubmitted && current.result !== 'similar' ? (