Skip to content

Commit

Permalink
Merge pull request #301 from digitalfabrik/LUN-328-implement-new-desi…
Browse files Browse the repository at this point in the history
…gn-for-home-screen

LUN-328: Implement new design for home screen
  • Loading branch information
f1sh1918 authored Jun 7, 2022
2 parents 6a394dd + 19467ac commit 6456724
Show file tree
Hide file tree
Showing 19 changed files with 301 additions and 115 deletions.
6 changes: 6 additions & 0 deletions assets/images/arrow-right-circle-icon-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions assets/images/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import AddCircleIcon from './add-circle-icon.svg'
import ArrowLeftCircleIconBlue from './arrow-left-circle-icon-blue.svg'
import ArrowLeftCircleIconWhite from './arrow-left-circle-icon-white.svg'
import ArrowRightCircleIconWhite from './arrow-right-circle-icon-white.svg'
import ArrowRightIcon from './arrow-right-icon.svg'
import BannerGreen from './banner-green.png'
import BannerRed from './banner-red.png'
Expand Down Expand Up @@ -55,6 +56,7 @@ export {
ArrowLeftCircleIconBlue,
ArrowLeftCircleIconWhite,
ArrowRightIcon,
ArrowRightCircleIconWhite,
BannerGreen,
BannerRed,
BannerYellow,
Expand Down
6 changes: 6 additions & 0 deletions src/constants/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SvgProps } from 'react-native-svg'

import { CheckCloseCircleIcon, CheckCircleIcon, CloseCircleIcon } from '../../assets/images'
import { RoutesParams } from '../navigation/NavigationTypes'
import { Document } from './endpoints'
import labels from './labels.json'

export const ExerciseKeys = {
Expand Down Expand Up @@ -61,6 +62,11 @@ export interface NextExercise {
exerciseKey: number
}

export type NextExerciseData = NextExercise & {
documents: Document[]
title: string
}

export const BUTTONS_THEME = {
outlined: 'outlined',
contained: 'contained',
Expand Down
1 change: 1 addition & 0 deletions src/constants/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const ENDPOINTS = {
disciplinesByGroup: 'disciplines_by_group',
groupInfo: 'group_info',
trainingSet: 'training_set',
trainingSets: 'training_sets',
documents: 'documents/:id'
}

Expand Down
3 changes: 2 additions & 1 deletion src/constants/labels.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"haveFun": "Viel Spaß beim Lernen",
"progressDescription": "Modulen erledigt",
"start": "Beginnen",
"continue": "Weitermachen",
"continue": "Fortsetzen",
"viewModules": "Module ansehen",
"addCustomDiscipline": "Bereich hinzufügen",
"customDisciplineSection": "Individueller Lernbereich",
"customDisciplineExplanation": "Hier kannst du eigene Lernbereiche hinzufügen",
Expand Down
22 changes: 22 additions & 0 deletions src/hooks/useLoadNextExercise.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { NextExerciseData } from '../constants/data'
import { Discipline } from '../constants/endpoints'
import { getNextExercise, loadTrainingsSet } from '../services/helpers'
import { formatDiscipline } from './helpers'
import useLoadAsync, { Return } from './useLoadAsync'
import { loadDocuments } from './useLoadDocuments'

export const loadNextExercise = async (profession: Discipline): Promise<NextExerciseData> => {
const nextExercise = await getNextExercise(profession)
const trainingSet = await loadTrainingsSet(nextExercise.disciplineId)
const documents = await loadDocuments({ disciplineId: nextExercise.disciplineId })
return {
documents,
title: formatDiscipline(trainingSet, { parent: null }).title,
exerciseKey: nextExercise.exerciseKey,
disciplineId: nextExercise.disciplineId
}
}

const useLoadNextExercise = (loadData: Discipline): Return<NextExerciseData> => useLoadAsync(loadNextExercise, loadData)

export default useLoadNextExercise
3 changes: 1 addition & 2 deletions src/hooks/useReadNextExercise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { Discipline } from '../constants/endpoints'
import { getNextExercise } from '../services/helpers'
import useLoadAsync, { Return } from './useLoadAsync'

const useReadNextExercise = (profession: Discipline | null): Return<NextExercise | null> =>
useLoadAsync(getNextExercise, profession)
const useReadNextExercise = (profession: Discipline): Return<NextExercise> => useLoadAsync(getNextExercise, profession)

export default useReadNextExercise
28 changes: 26 additions & 2 deletions src/routes/home/HomeScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { useFocusEffect } from '@react-navigation/native'
import { CommonActions, useFocusEffect } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import React from 'react'
import { View } from 'react-native'
import styled from 'styled-components/native'

import { ContentSecondary } from '../../components/text/Content'
import { Heading } from '../../components/text/Heading'
import { EXERCISES, NextExerciseData } from '../../constants/data'
import { Discipline } from '../../constants/endpoints'
import labels from '../../constants/labels.json'
import useReadCustomDisciplines from '../../hooks/useReadCustomDisciplines'
import useReadSelectedProfessions from '../../hooks/useReadSelectedProfessions'
Expand Down Expand Up @@ -48,16 +50,38 @@ const HomeScreen = ({ navigation }: HomeScreenProps): JSX.Element => {
navigation.navigate('AddCustomDiscipline')
}

const navigateToDiscipline = (discipline: Discipline): void => {
navigation.navigate('DisciplineSelection', {
discipline
})
}

const navigateToNextExercise = (nextExerciseData: NextExerciseData) => {
const { exerciseKey, disciplineId, title: disciplineTitle, documents } = nextExerciseData
navigation.navigate(EXERCISES[exerciseKey].screen, {
disciplineId,
disciplineTitle,
documents,
closeExerciseAction: CommonActions.navigate('Home')
})
}

const customDisciplineItems = customDisciplines?.map(customDiscipline => (
<DisciplineCard
key={customDiscipline}
identifier={{ apiKey: customDiscipline }}
refresh={refreshCustomDisciplines}
navigateToDiscipline={navigateToDiscipline}
/>
))

const selectedProfessionItems = selectedProfessions?.map(profession => (
<DisciplineCard key={profession} identifier={{ disciplineId: profession }} />
<DisciplineCard
key={profession}
identifier={{ disciplineId: profession }}
navigateToDiscipline={navigateToDiscipline}
navigateToNextExercise={navigateToNextExercise}
/>
))

return (
Expand Down
7 changes: 4 additions & 3 deletions src/routes/home/__tests__/HomeScreen.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import labels from '../../../constants/labels.json'
import { useLoadDiscipline } from '../../../hooks/useLoadDiscipline'
import { useLoadDisciplines } from '../../../hooks/useLoadDisciplines'
import useReadCustomDisciplines from '../../../hooks/useReadCustomDisciplines'
import useReadProgress from '../../../hooks/useReadProgress'
import useReadSelectedProfessions from '../../../hooks/useReadSelectedProfessions'
import AsyncStorageService from '../../../services/AsyncStorage'
import createNavigationMock from '../../../testing/createNavigationPropMock'
Expand All @@ -15,8 +16,10 @@ import { mockDisciplines } from '../../../testing/mockDiscipline'
import render from '../../../testing/render'
import HomeScreen from '../HomeScreen'

jest.mock('../../../services/helpers')
jest.mock('@react-navigation/native')
jest.mock('../../../hooks/useReadCustomDisciplines')
jest.mock('../../../hooks/useReadProgress')
jest.mock('../../../hooks/useReadSelectedProfessions')
jest.mock('../../../hooks/useLoadDisciplines')
jest.mock('../../../hooks/useLoadDiscipline')
Expand All @@ -34,16 +37,14 @@ describe('HomeScreen', () => {
mocked(useLoadDiscipline)
.mockReturnValueOnce(getReturnOf(mockDisciplines()[0]))
.mockReturnValueOnce(getReturnOf(mockDisciplines()[1]))

mocked(useReadProgress).mockReturnValue(getReturnOf(0))
const { getByText } = render(<HomeScreen navigation={navigation} />)
const firstDiscipline = getByText('First Discipline')
const secondDiscipline = getByText('Second Discipline')
expect(firstDiscipline).toBeDefined()
expect(secondDiscipline).toBeDefined()
})

// TODO LUN-328 add test for navigate to child disciplines

it('should show custom discipline', async () => {
await AsyncStorageService.setCustomDisciplines(['test'])
mocked(useLoadDisciplines).mockReturnValueOnce(getReturnOf(mockDisciplines()))
Expand Down
2 changes: 1 addition & 1 deletion src/routes/home/components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const Box = styled.Pressable`
justify-content: space-between;
margin: ${props => props.theme.spacings.sm};
padding: 0 ${props => props.theme.spacings.sm};
height: ${hp('28%')}px;
min-height: ${hp('28%')}px;
`

const BoxHeading = styled.View`
Expand Down
40 changes: 16 additions & 24 deletions src/routes/home/components/CustomDisciplineDetails.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import React from 'react'
import styled from 'styled-components/native'

import Button from '../../../components/Button'
import { BUTTONS_THEME } from '../../../constants/data'
import { Discipline } from '../../../constants/endpoints'
import labels from '../../../constants/labels.json'
import { RoutesParams } from '../../../navigation/NavigationTypes'
import { childrenLabel } from '../../../services/helpers'
import { ButtonContainer, NumberText, UnitText } from './DisciplineCard'

Expand All @@ -20,28 +17,23 @@ const TextContainer = styled.View`

interface PropsType {
discipline: Discipline
navigateToDiscipline: (discipline: Discipline) => void
}

const CustomDisciplineDetails = ({ discipline }: PropsType): JSX.Element => {
const navigation = useNavigation<StackNavigationProp<RoutesParams, 'Home'>>()

const navigate = (): void => {
navigation.navigate('DisciplineSelection', {
discipline
})
}

return (
<>
<TextContainer>
<NumberText>{discipline.numberOfChildren}</NumberText>
<UnitText>{childrenLabel(discipline)}</UnitText>
</TextContainer>
<ButtonContainer>
<Button onPress={navigate} label={labels.home.start} buttonTheme={BUTTONS_THEME.outlined} />
</ButtonContainer>
</>
)
}
const CustomDisciplineDetails = ({ discipline, navigateToDiscipline }: PropsType): JSX.Element => (
<>
<TextContainer>
<NumberText>{discipline.numberOfChildren}</NumberText>
<UnitText>{childrenLabel(discipline)}</UnitText>
</TextContainer>
<ButtonContainer>
<Button
onPress={() => navigateToDiscipline(discipline)}
label={labels.home.start}
buttonTheme={BUTTONS_THEME.outlined}
/>
</ButtonContainer>
</>
)

export default CustomDisciplineDetails
25 changes: 19 additions & 6 deletions src/routes/home/components/DisciplineCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import ErrorMessage from '../../../components/ErrorMessage'
import Loading from '../../../components/Loading'
import { ContentSecondary, ContentSecondaryLight } from '../../../components/text/Content'
import { Subheading } from '../../../components/text/Subheading'
import { BUTTONS_THEME } from '../../../constants/data'
import { ForbiddenError } from '../../../constants/endpoints'
import { BUTTONS_THEME, NextExerciseData } from '../../../constants/data'
import { Discipline, ForbiddenError } from '../../../constants/endpoints'
import labels from '../../../constants/labels.json'
import { isTypeLoadProtected } from '../../../hooks/helpers'
import { RequestParams, useLoadDiscipline } from '../../../hooks/useLoadDiscipline'
Expand Down Expand Up @@ -40,9 +40,16 @@ export const ButtonContainer = styled.View`
interface PropsType {
identifier: RequestParams
refresh?: () => void
navigateToDiscipline: (discipline: Discipline) => void
navigateToNextExercise?: (nextExerciseData: NextExerciseData) => void
}

const DisciplineCard = ({ identifier, refresh: refreshHome }: PropsType): JSX.Element => {
const DisciplineCard = ({
identifier,
refresh: refreshHome,
navigateToDiscipline,
navigateToNextExercise
}: PropsType): JSX.Element => {
const { data: discipline, loading, error, refresh } = useLoadDiscipline(identifier)

if (loading) {
Expand Down Expand Up @@ -79,11 +86,17 @@ const DisciplineCard = ({ identifier, refresh: refreshHome }: PropsType): JSX.El
}

return (
<Card heading={discipline.title} icon={discipline.icon}>
<Card heading={discipline.title} icon={discipline.icon} onPress={() => navigateToDiscipline(discipline)}>
{isTypeLoadProtected(identifier) ? (
<CustomDisciplineDetails discipline={discipline} />
<CustomDisciplineDetails discipline={discipline} navigateToDiscipline={navigateToDiscipline} />
) : (
<ProfessionDetails discipline={discipline} />
navigateToNextExercise && (
<ProfessionDetails
discipline={discipline}
navigateToDiscipline={navigateToDiscipline}
navigateToNextExercise={navigateToNextExercise}
/>
)
)}
</Card>
)
Expand Down
79 changes: 79 additions & 0 deletions src/routes/home/components/NextExerciseCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { ReactElement } from 'react'
import { Pressable, TouchableOpacity } from 'react-native'
import { widthPercentageToDP as wp } from 'react-native-responsive-screen'
import styled from 'styled-components/native'

import { ArrowRightCircleIconWhite } from '../../../../assets/images'
import { ContentTextLight } from '../../../components/text/Content'
import { SubheadingPrimary, SubheadingText } from '../../../components/text/Subheading'

const Container = styled.View`
flex-direction: row;
background-color: ${props => props.theme.colors.background};
padding: ${props => props.theme.spacings.sm};
margin: ${props => props.theme.spacings.sm} 0;
shadow-color: ${props => props.theme.colors.shadow};
elevation: 8;
shadow-radius: 1px;
shadow-offset: 1px 1px;
shadow-opacity: 0.5;
`

const ExerciseDetail = styled.View`
padding: 0 ${props => props.theme.spacings.sm};
align-self: center;
`

const ActionContainer = styled.View`
padding-top: ${props => props.theme.spacings.xs};
flex-direction: row;
align-items: flex-start;
`

const Label = styled(SubheadingPrimary)`
letter-spacing: ${props => props.theme.fonts.capsLetterSpacing};
text-transform: uppercase;
padding-right: ${props => props.theme.spacings.xs};
align-self: center;
`

const Thumbnail = styled.Image`
height: ${wp('19%')}px;
width: ${wp('18%')}px;
align-self: center;
`

const Heading = styled(SubheadingText)`
font-size: ${props => props.theme.fonts.smallFontSize};
`
const Subheading = styled(ContentTextLight)`
font-size: ${props => props.theme.fonts.smallFontSize};
`

interface PropsType {
thumbnail: string
heading: string
subheading: string
buttonLabel: string
onPress: () => void
}

const NextExerciseCard = ({ thumbnail, onPress, heading, subheading, buttonLabel }: PropsType): ReactElement => (
<Pressable onPress={onPress}>
<Container>
<Thumbnail source={{ uri: thumbnail }} />
<ExerciseDetail>
<Heading>{heading}</Heading>
<Subheading>{subheading}</Subheading>
<ActionContainer>
<TouchableOpacity style={{ flexDirection: 'row' }} onPress={onPress}>
<Label>{buttonLabel}</Label>
<ArrowRightCircleIconWhite width={wp('8%')} height={wp('8%')} />
</TouchableOpacity>
</ActionContainer>
</ExerciseDetail>
</Container>
</Pressable>
)

export default NextExerciseCard
Loading

0 comments on commit 6456724

Please sign in to comment.