Design refresh with new navigator and colors (#418)

* Refactor design with navigator and colors

* remove childList test data

* Update React Navigation and related packages

* Add `initials` to peopleHelpers

* remove unused tests

* change initials to first 2 letters of name

* update color scheme

* add mock for react-native-reanimated

* fix absence translation

* update design à la modern/rounded

* Add headerLargeTitle everywhere
This commit is contained in:
Jonathan Edenström 2021-06-16 14:10:06 +02:00
parent 49328b44f9
commit 25f4ebc0f7
40 changed files with 1397 additions and 1224 deletions

View File

@ -1,22 +1,21 @@
import React from 'react'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import * as eva from '@eva-design/eva'
import AsyncStorage from '@react-native-async-storage/async-storage'
import CookieManager from '@react-native-community/cookies'
import { ApiProvider } from '@skolplattformen/api-hooks'
import init from '@skolplattformen/embedded-api'
import { ApplicationProvider, IconRegistry } from '@ui-kitten/components'
import { EvaIconsPack } from '@ui-kitten/eva-icons'
import * as eva from '@eva-design/eva'
import darkTheme from './design/dark.json'
import lightTheme from './design/light.json'
import { AppNavigator } from './components/navigation.component'
import init from '@skolplattformen/embedded-api'
import { ApiProvider } from '@skolplattformen/api-hooks'
import CookieManager from '@react-native-community/cookies'
import AsyncStorage from '@react-native-async-storage/async-storage'
import React from 'react'
import { StatusBar } from 'react-native'
import { useBackgroundBlur } from './utils/blur'
import { LanguageProvider } from './context/language/languageContext'
import { translations } from './utils/translation'
import { AppearanceProvider, useColorScheme } from 'react-native-appearance'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { AppNavigator } from './components/navigation.component'
import { LanguageProvider } from './context/language/languageContext'
import { default as customMapping } from './design/mapping.json'
import { darkTheme, lightTheme } from './design/themes'
import { useBackgroundBlur } from './utils/blur'
import { translations } from './utils/translation'
const api = init(fetch, CookieManager)
import { default as customMapping } from './design/mapping.json';
const reporter = __DEV__
? {
@ -45,10 +44,7 @@ export default () => {
<ApplicationProvider
{...eva}
customMapping={customMapping}
theme={{
...(colorScheme === 'dark' ? eva.dark : eva.light),
...(colorScheme === 'dark' ? darkTheme : lightTheme),
}}
theme={colorScheme === 'dark' ? darkTheme : lightTheme}
>
<LanguageProvider cache={true} data={translations}>
<AppNavigator />

View File

@ -37,12 +37,6 @@ beforeEach(() => {
AsyncStorage.clear()
})
test('renders title', () => {
const screen = setup()
expect(screen.getByText('Anmäl frånvaro')).toBeTruthy()
})
test('can fill out the form with full day absence', async () => {
const screen = setup()

View File

@ -1,8 +1,7 @@
import { useApi, useNewsDetails } from '@skolplattformen/api-hooks'
import React from 'react'
import { render } from '../../utils/testHelpers'
import { NewsItem } from '../newsItem.component'
import { fireEvent } from '@testing-library/react-native'
import { useNewsDetails, useApi } from '@skolplattformen/api-hooks'
jest.mock('@skolplattformen/api-hooks')
@ -89,11 +88,3 @@ test('renders an article without modified date if date is invalid', () => {
expect(screen.getByText('Publicerad: 15 feb 2021 10:13')).toBeTruthy()
expect(screen.queryByText('Uppdaterad: Invalid DateTime')).toBeFalsy()
})
test('handles navigating back to child view', () => {
const screen = setup()
fireEvent.press(screen.getByTestId('topNavBackToChild'))
expect(navigation.goBack).toHaveBeenCalled()
})

View File

@ -1,35 +1,29 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import { RouteProp, useRoute } from '@react-navigation/native'
import {
Button,
CheckBox,
Divider,
Input,
Layout,
StyleService,
Text,
TopNavigation,
TopNavigationAction,
useStyleSheet,
} from '@ui-kitten/components'
import { Formik } from 'formik'
import moment from 'moment'
import Personnummer from 'personnummer'
import React from 'react'
import React, { useCallback } from 'react'
import { View } from 'react-native'
import DateTimePickerModal from 'react-native-modal-datetime-picker'
import { NativeStackNavigationOptions } from 'react-native-screens/native-stack'
import * as Yup from 'yup'
import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
import { SafeAreaView } from '../ui/safeAreaView.component'
import { studentName } from '../utils/peopleHelpers'
import { useSMS } from '../utils/SMS'
import { translate } from '../utils/translation'
import { BackIcon, AlertIcon } from './icon.component'
import { AlertIcon } from './icon.component'
import { RootStackParamList } from './navigation.component'
import { SafeAreaViewContainer } from '../ui/safeAreaViewContainer.component'
import { NavigationTitle } from './navigationTitle.component'
type AbsenceNavigationProp = StackNavigationProp<RootStackParamList, 'Absence'>
type AbsenceRouteProps = RouteProp<RootStackParamList, 'Absence'>
interface AbsenceFormValues {
@ -41,7 +35,21 @@ interface AbsenceFormValues {
endTime: moment.Moment
}
const Alert = (props: any) => <AlertIcon {...props} />
export const absenceRouteOptions = ({
route,
}: {
route: RouteProp<RootStackParamList, 'Absence'>
}): NativeStackNavigationOptions => {
const child = route.params.child
return {
headerCenter: () => (
<NavigationTitle
title={translate('abscense.title')}
subtitle={studentName(child?.name)}
/>
),
}
}
const Absence = () => {
const AbsenceSchema = Yup.object().shape({
@ -52,7 +60,7 @@ const Absence = () => {
),
isFullDay: Yup.bool().required(),
})
const navigation = useNavigation<AbsenceNavigationProp>()
const route = useRoute<AbsenceRouteProps>()
const { sendSMS } = useSMS()
const { child } = route.params
@ -61,6 +69,28 @@ const Absence = () => {
const maximumDate = moment().hours(17).minute(0)
const styles = useStyleSheet(themedStyles)
const submit = useCallback(
async (values: AbsenceFormValues) => {
const ssn = Personnummer.parse(values.socialSecurityNumber).format()
if (values.isFullDay) {
sendSMS(ssn)
} else {
sendSMS(
`${ssn} ${moment(values.startTime).format('HHmm')}-${moment(
values.endTime
).format('HHmm')}`
)
}
await AsyncStorage.setItem(
`@childssn.${child.id}`,
values.socialSecurityNumber
)
},
[child.id, sendSMS]
)
React.useEffect(() => {
const getSocialSecurityNumber = async () => {
const ssn = await AsyncStorage.getItem(`@childssn.${child.id}`)
@ -80,210 +110,154 @@ const Absence = () => {
}
return (
<SafeAreaView>
<SafeAreaViewContainer>
<TopNavigation
accessoryLeft={() => (
<TopNavigationAction
icon={BackIcon}
onPress={() => navigation.goBack()}
/>
)}
alignment="center"
style={styles.topBar}
title={() => (
<Text maxFontSizeMultiplier={1.5} style={styles.topNavigationTitle}>
{translate('abscense.title')}
</Text>
)}
subtitle={() => (
<Text
maxFontSizeMultiplier={1.5}
style={styles.topNavigationSubtitle}
>
{studentName(child.name)}
</Text>
)}
/>
<Divider />
<Layout style={styles.wrap}>
<Formik
enableReinitialize
validationSchema={AbsenceSchema}
initialValues={initialValues}
onSubmit={async (values) => {
const ssn = Personnummer.parse(
values.socialSecurityNumber
).format()
<Formik
enableReinitialize
validationSchema={AbsenceSchema}
initialValues={initialValues}
onSubmit={submit}
>
{({
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
values,
touched,
errors,
}) => {
const hasError = (field: keyof typeof values) =>
errors[field] && touched[field]
if (values.isFullDay) {
sendSMS(ssn)
} else {
sendSMS(
`${ssn} ${moment(values.startTime).format('HHmm')}-${moment(
values.endTime
).format('HHmm')}`
)
}
await AsyncStorage.setItem(
`@childssn.${child.id}`,
values.socialSecurityNumber
)
}}
>
{({
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
values,
touched,
errors,
}) => {
const hasError = (field: keyof typeof values) =>
errors[field] && touched[field]
return (
<View>
<View style={styles.field}>
<Text style={styles.label}>
{translate('general.socialSecurityNumber')}
</Text>
<Input
testID="socialSecurityNumberInput"
keyboardType="number-pad"
onChangeText={handleChange('socialSecurityNumber')}
onBlur={handleBlur('socialSecurityNumber')}
status={
hasError('socialSecurityNumber') ? 'danger' : 'basic'
}
value={values.socialSecurityNumber}
accessoryRight={
errors.socialSecurityNumber ? Alert : undefined
}
/>
{hasError('socialSecurityNumber') && (
<Text style={styles.error}>
{errors.socialSecurityNumber}
</Text>
)}
</View>
<View style={styles.field}>
<CheckBox
checked={values.isFullDay}
onChange={(checked) =>
setFieldValue('isFullDay', checked)
}
>
{translate('abscense.entireDay')}
</CheckBox>
</View>
{!values.isFullDay && (
<View style={styles.partOfDay}>
<View style={styles.inputHalf}>
<Text style={styles.label}>
{translate('abscense.startTime')}
</Text>
<Button
status="basic"
onPress={() =>
setFieldValue('displayStartTimePicker', true)
}
>
{moment(values.startTime).format('LT')}
</Button>
<DateTimePickerModal
cancelTextIOS={translate('general.cancel')}
confirmTextIOS={translate('general.confirm')}
date={moment(values.startTime).toDate()}
isVisible={values.displayStartTimePicker}
headerTextIOS={translate(
'abscense.selectAbscenseStartTime'
)}
locale="sv-SE"
maximumDate={maximumDate.toDate()}
minimumDate={minumumDate.toDate()}
minuteInterval={10}
mode="time"
onConfirm={(date) => {
setFieldValue('startTime', date)
setFieldValue('displayStartTimePicker', false)
}}
onCancel={() =>
setFieldValue('displayStartTimePicker', false)
}
/>
</View>
<View style={styles.spacer} />
<View style={styles.inputHalf}>
<Text style={styles.label}>
{translate('abscense.endTime')}
</Text>
<Button
status="basic"
onPress={() =>
setFieldValue('displayEndTimePicker', true)
}
>
{moment(values.endTime).format('LT')}
</Button>
<DateTimePickerModal
cancelTextIOS={translate('general.cancel')}
confirmTextIOS={translate('general.confirm')}
date={moment(values.endTime).toDate()}
isVisible={values.displayEndTimePicker}
headerTextIOS={translate(
'abscense.selectAbscenseEndTime'
)}
// Todo fix this
locale="sv-SE"
maximumDate={maximumDate.toDate()}
minimumDate={minumumDate.toDate()}
minuteInterval={10}
mode="time"
onConfirm={(date) => {
setFieldValue('endTime', date)
setFieldValue('displayEndTimePicker', false)
}}
onCancel={() =>
setFieldValue('displayEndTimePicker', false)
}
/>
</View>
</View>
)}
<Button onPress={handleSubmit} status="primary">
{translate('general.send')}
return (
<View style={styles.wrap}>
<View style={styles.field}>
<Text style={styles.label}>
{translate('general.socialSecurityNumber')}
</Text>
<Input
testID="socialSecurityNumberInput"
keyboardType="number-pad"
onChangeText={handleChange('socialSecurityNumber')}
onBlur={handleBlur('socialSecurityNumber')}
status={hasError('socialSecurityNumber') ? 'danger' : 'basic'}
value={values.socialSecurityNumber}
style={styles.input}
accessoryRight={
hasError('socialSecurityNumber') ? AlertIcon : undefined
}
/>
{hasError('socialSecurityNumber') && (
<Text style={styles.error}>{errors.socialSecurityNumber}</Text>
)}
</View>
<View style={styles.field}>
<CheckBox
checked={values.isFullDay}
onChange={(checked) => setFieldValue('isFullDay', checked)}
>
{translate('abscense.entireDay')}
</CheckBox>
</View>
{!values.isFullDay && (
<View style={styles.partOfDay}>
<View style={styles.inputHalf}>
<Text style={styles.label}>
{translate('abscense.startTime')}
</Text>
<Button
status="basic"
style={styles.pickerButton}
onPress={() =>
setFieldValue('displayStartTimePicker', true)
}
>
{moment(values.startTime).format('LT')}
</Button>
<DateTimePickerModal
cancelTextIOS={translate('general.cancel')}
confirmTextIOS={translate('general.confirm')}
date={moment(values.startTime).toDate()}
isVisible={values.displayStartTimePicker}
headerTextIOS={translate(
'abscense.selectAbscenseStartTime'
)}
locale="sv-SE"
maximumDate={maximumDate.toDate()}
minimumDate={minumumDate.toDate()}
minuteInterval={10}
mode="time"
onConfirm={(date) => {
setFieldValue('startTime', date)
setFieldValue('displayStartTimePicker', false)
}}
onCancel={() =>
setFieldValue('displayStartTimePicker', false)
}
/>
</View>
)
}}
</Formik>
</Layout>
</SafeAreaViewContainer>
</SafeAreaView>
<View style={styles.spacer} />
<View style={styles.inputHalf}>
<Text style={styles.label}>
{translate('abscense.endTime')}
</Text>
<Button
status="basic"
style={styles.pickerButton}
onPress={() => setFieldValue('displayEndTimePicker', true)}
>
{moment(values.endTime).format('LT')}
</Button>
<DateTimePickerModal
cancelTextIOS={translate('general.cancel')}
confirmTextIOS={translate('general.confirm')}
date={moment(values.endTime).toDate()}
isVisible={values.displayEndTimePicker}
headerTextIOS={translate('abscense.selectAbscenseEndTime')}
// Todo fix this
locale="sv-SE"
maximumDate={maximumDate.toDate()}
minimumDate={minumumDate.toDate()}
minuteInterval={10}
mode="time"
onConfirm={(date) => {
setFieldValue('endTime', date)
setFieldValue('displayEndTimePicker', false)
}}
onCancel={() =>
setFieldValue('displayEndTimePicker', false)
}
/>
</View>
</View>
)}
<Button onPress={handleSubmit} status="primary">
{translate('general.send')}
</Button>
</View>
)
}}
</Formik>
)
}
export default Absence
const themedStyles = StyleService.create({
safeArea: {
...LayoutStyle.flex.full,
backgroundColor: 'background-basic-color-1',
},
topBar: {
backgroundColor: 'background-basic-color-1',
},
wrap: {
...LayoutStyle.flex.full,
padding: Sizing.t5,
padding: Sizing.t4,
},
field: { marginBottom: Sizing.t4 },
partOfDay: { ...LayoutStyle.flex.row, marginBottom: Sizing.t4 },
spacer: { width: Sizing.t2 },
inputHalf: { ...LayoutStyle.flex.full },
input: {
backgroundColor: 'background-basic-color-1',
},
// TODO: Refactor to use mapping.json in eva design
pickerButton: {
backgroundColor: 'background-basic-color-1',
},
label: {
...Typography.fontSize.xs,
...Typography.fontWeight.bold,
@ -293,11 +267,4 @@ const themedStyles = StyleService.create({
error: {
color: 'color-primary-600',
},
topNavigationTitle: {
...Typography.fontWeight.semibold,
},
topNavigationSubtitle: {
...Typography.fontWeight.regular,
...Typography.fontSize.sm,
},
})

View File

@ -1,23 +1,30 @@
import { StackNavigationProp } from '@react-navigation/stack'
import {
Layout,
Text,
TopNavigation,
TopNavigationAction,
StyleService,
Text,
useStyleSheet,
useTheme,
} from '@ui-kitten/components'
import React from 'react'
import { Keyboard, TouchableWithoutFeedback, View, Image } from 'react-native'
import { Login } from './login.component'
import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
import { SafeAreaViewContainer } from '../ui/safeAreaViewContainer.component'
import { translate, languages } from '../utils/translation'
import { GlobeIcon } from './icon.component'
import { StackNavigationProp } from '@react-navigation/stack'
import { RootStackParamList } from './navigation.component'
import { SafeAreaView } from '../ui/safeAreaView.component'
import { KeyboardAvoidingView } from '../ui/keyboardAvoidingView.component'
import {
Image,
ImageStyle,
Keyboard,
TouchableWithoutFeedback,
View,
} from 'react-native'
import { TouchableOpacity } from 'react-native-gesture-handler'
import { NativeStackNavigationOptions } from 'react-native-screens/native-stack'
import { LanguageService } from '../services/languageService'
import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
import { fontSize } from '../styles/typography'
import { KeyboardAvoidingView } from '../ui/keyboardAvoidingView.component'
import { SafeAreaView } from '../ui/safeAreaView.component'
import { SafeAreaViewContainer } from '../ui/safeAreaViewContainer.component'
import { languages, translate } from '../utils/translation'
import { GlobeIcon } from './icon.component'
import { Login } from './login.component'
import { RootStackParamList } from './navigation.component'
const randomWord = () => {
const words = translate('auth.words')
@ -34,8 +41,17 @@ interface AuthProps {
navigation: StackNavigationProp<RootStackParamList, 'Login'>
}
export const authRouteOptions = (): NativeStackNavigationOptions => {
return {
headerShown: false,
replaceAnimation: 'push',
stackAnimation: 'fade',
}
}
export const Auth: React.FC<AuthProps> = ({ navigation }) => {
const styles = useStyleSheet(themeStyles)
const colors = useTheme()
const currentLanguage = LanguageService.getLanguageCode()
const currentLanguageName = languages.find(
@ -43,56 +59,66 @@ export const Auth: React.FC<AuthProps> = ({ navigation }) => {
)?.languageLocalName
return (
<KeyboardAvoidingView>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<SafeAreaView>
<SafeAreaViewContainer>
<TopNavigation
alignment="start"
subtitle={currentLanguageName}
accessoryLeft={() => (
<TopNavigationAction
accessibilityHint={translate(
'auth.a11y_navigate_to_change_language',
{
defaultValue: 'Navigerar till vyn för att byta språk',
}
)}
accessibilityLabel={translate('auth.a11y_change_language', {
defaultValue: 'Byt språk',
})}
accessible={true}
icon={GlobeIcon}
onPress={() => navigation.navigate('SetLanguage')}
/>
<SafeAreaView>
<SafeAreaViewContainer>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={LayoutStyle.flex.full}>
<TouchableOpacity
onPress={() => navigation.navigate('SetLanguage')}
accessibilityHint={translate(
'auth.a11y_navigate_to_change_language',
{
defaultValue: 'Navigerar till vyn för att byta språk',
}
)}
/>
<View style={styles.content}>
<Image
source={require('../assets/boys.png')}
// @ts-expect-error Don't know why this occurs
style={styles.image}
accessibilityHint={translate('login.a11y_image_two_boys', {
defaultValue: 'Bild på två personer som kollar i mobilen',
})}
accessibilityIgnoresInvertColors={false}
accessibilityLabel={translate('auth.a11y_change_language', {
defaultValue: 'Byt språk',
})}
>
<View style={styles.language}>
<GlobeIcon
height={24}
width={24}
fill={colors['color-primary-500']}
/>
<Layout style={styles.container}>
<Text category="h1" style={styles.header} adjustsFontSizeToFit numberOfLines={2}>
{translate('general.title')}
</Text>
<Login />
<Text category="c2" style={styles.subtitle}>
{translate('auth.subtitle', {
word: randomWord(),
})}
</Text>
</Layout>
</View>
</SafeAreaViewContainer>
</SafeAreaView>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
<Text style={styles.languageText}>{currentLanguageName}</Text>
</View>
</TouchableOpacity>
<KeyboardAvoidingView>
<View style={styles.content}>
<View style={styles.imageWrapper}>
<Image
source={require('../assets/boys.png')}
style={styles.image as ImageStyle}
accessibilityHint={translate('login.a11y_image_two_boys', {
defaultValue: 'Bild på två personer som kollar i mobilen',
})}
resizeMode="contain"
accessibilityIgnoresInvertColors={false}
/>
</View>
<View style={styles.container}>
<Text
category="h1"
style={styles.header}
adjustsFontSizeToFit
numberOfLines={2}
>
{translate('general.title')}
</Text>
<Login />
<Text category="c2" style={styles.subtitle}>
{translate('auth.subtitle', {
word: randomWord(),
})}
</Text>
</View>
</View>
</KeyboardAvoidingView>
</View>
</TouchableWithoutFeedback>
</SafeAreaViewContainer>
</SafeAreaView>
)
}
@ -102,9 +128,12 @@ const themeStyles = StyleService.create({
...LayoutStyle.crossAxis.flexEnd,
padding: Sizing.t6,
},
imageWrapper: {
flex: 1,
justifyContent: 'flex-end',
},
image: {
...Sizing.aspectRatio(1.7, Sizing.Ratio['4:3']),
marginLeft: '-17%',
...Sizing.aspectRatio(1.5, Sizing.Ratio['4:3']),
},
content: {
...LayoutStyle.flex.full,
@ -112,12 +141,22 @@ const themeStyles = StyleService.create({
header: {
width: '60%',
marginBottom: Sizing.t5,
fontFamily: 'Poppins-Black',
fontWeight: '900',
},
subtitle: {
width: '100%',
textAlign: 'center',
...Typography.fontSize.xs,
color: 'color-basic-800',
marginTop: Sizing.t5,
},
language: {
flexDirection: 'row',
alignItems: 'center',
paddingLeft: Sizing.t4,
},
languageText: {
...fontSize.xs,
marginLeft: Sizing.t1,
},
})

View File

@ -65,12 +65,12 @@ export const Calendar = () => {
const themedStyles = StyleService.create({
container: {
backgroundColor: 'background-basic-color-2',
backgroundColor: 'background-basic-color-1',
height: '100%',
width: '100%',
},
description: {
...Typography.fontSize.xs,
color: 'color-basic-600',
color: 'text-hint-color',
},
})

View File

@ -1,182 +1,126 @@
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import {
BottomTabBarOptions,
BottomTabBarProps,
createBottomTabNavigator,
} from '@react-navigation/bottom-tabs'
import { RouteProp, useNavigation, useRoute } from '@react-navigation/native'
getFocusedRouteNameFromRoute,
RouteProp,
useRoute,
} from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import {
BottomNavigation,
BottomNavigationTab,
Layout,
StyleService,
Text,
TopNavigation,
TopNavigationAction,
useStyleSheet,
} from '@ui-kitten/components'
import { Icon } from '@ui-kitten/components'
import React from 'react'
import { StyleProp, TextProps } from 'react-native'
import { studentName } from '../utils/peopleHelpers'
import { NativeStackNavigationOptions } from 'react-native-screens/native-stack'
import { defaultStackStyling } from '../design/navigationThemes'
import { translate } from '../utils/translation'
import { Calendar } from './calendar.component'
import { ChildProvider } from './childContext.component'
import { Menu } from './menu.component'
import {
BackIcon,
CalendarOutlineIcon,
MenuIcon,
NewsIcon,
NotificationsIcon,
} from './icon.component'
import { RootStackParamList } from './navigation.component'
import { NewsList } from './newsList.component'
import { NotificationsList } from './notificationsList.component'
import { translate } from '../utils/translation'
import { SafeAreaView } from '../ui/safeAreaView.component'
import { SafeAreaViewContainer } from '../ui/safeAreaViewContainer.component'
import { Typography } from '../styles'
type ChildNavigationProp = StackNavigationProp<RootStackParamList, 'Child'>
type ChildRouteProps = RouteProp<RootStackParamList, 'Child'>
export type ChildTabParamList = {
News: undefined
Notifications: undefined
Calendar: undefined
Menu: undefined
}
interface TabTitleProps {
children: string
style?: StyleProp<TextProps>
}
const { Navigator, Screen } = createBottomTabNavigator()
const { Navigator, Screen } = createBottomTabNavigator<ChildTabParamList>()
const NewsScreen = () => {
return (
<Layout>
<NewsList />
</Layout>
)
}
const NewsScreen = () => <NewsList />
const NotificationsScreen = () => <NotificationsList />
const CalendarScreen = () => <Calendar />
const MenuScreen = () => <Menu />
const NotificationsScreen = () => {
return (
<Layout>
<NotificationsList />
</Layout>
)
}
const CalendarScreen = () => {
return (
<Layout>
<Calendar />
</Layout>
)
}
const MenuScreen = () => {
return (
<Layout>
<Menu />
</Layout>
)
}
const TabTitle = ({ style, children }: TabTitleProps) => (
<Text
maxFontSizeMultiplier={1.5}
adjustsFontSizeToFit
numberOfLines={1}
style={style}
>
{children}
</Text>
)
const BottomTabBar = ({
navigation,
state,
}: BottomTabBarProps<BottomTabBarOptions>) => (
<BottomNavigation
accessibilityRole="menu"
selectedIndex={state.index}
onSelect={(index) => navigation.navigate(state.routeNames[index])}
>
<BottomNavigationTab
accessibilityRole="menuitem"
title={(props) => (
<TabTitle {...props}>{translate('navigation.news')}</TabTitle>
)}
icon={NewsIcon}
/>
<BottomNavigationTab
accessibilityRole="menuitem"
title={(props) => (
<TabTitle {...props}>{translate('navigation.notifications')}</TabTitle>
)}
icon={NotificationsIcon}
/>
<BottomNavigationTab
accessibilityRole="menuitem"
title={(props) => (
<TabTitle {...props}>{translate('navigation.calender')}</TabTitle>
)}
icon={CalendarOutlineIcon}
/>
<BottomNavigationTab
accessibilityRole="menuitem"
title={(props) => (
<TabTitle {...props}>{translate('navigation.menu')}</TabTitle>
)}
icon={MenuIcon}
/>
</BottomNavigation>
)
const TabNavigator = ({ initialRouteName = 'Nyheter' }) => (
const TabNavigator = ({
initialRouteName = 'News',
}: {
initialRouteName: keyof ChildTabParamList
}) => (
<Navigator
initialRouteName={initialRouteName}
tabBar={(props) => <BottomTabBar {...props} />}
screenOptions={({ route }) => {
return {
tabBarIcon: ({ focused, color }) => {
let iconName = 'news'
if (route.name === 'News')
iconName = focused ? 'book-open' : 'book-open-outline'
else if (route.name === 'Notifications')
iconName = focused ? 'alert-circle' : 'alert-circle-outline'
else if (route.name === 'Calendar')
iconName = focused ? 'calendar' : 'calendar-outline'
else if (route.name === 'Menu')
iconName = focused ? 'clipboard' : 'clipboard-outline'
return <Icon name={iconName} fill={color} height={24} width={24} />
},
}
}}
>
<Screen name={'navigation.news'} component={NewsScreen} />
<Screen name={'navigation.notifications'} component={NotificationsScreen} />
<Screen name={'navigation.calender'} component={CalendarScreen} />
<Screen name={'navigation.menu'} component={MenuScreen} />
<Screen
name="News"
component={NewsScreen}
options={{ title: translate('navigation.news') }}
/>
<Screen
name="Notifications"
component={NotificationsScreen}
options={{ title: translate('navigation.notifications') }}
/>
<Screen
name="Calendar"
component={CalendarScreen}
options={{ title: translate('navigation.calender') }}
/>
<Screen
name="Menu"
component={MenuScreen}
options={{ title: translate('navigation.menu') }}
/>
</Navigator>
)
export const Child = () => {
const navigation = useNavigation<ChildNavigationProp>()
const route = useRoute<ChildRouteProps>()
const { child, initialRouteName } = route.params
const styles = useStyleSheet(themedStyles)
const getHeaderTitle = (route: any) => {
const routeName = getFocusedRouteNameFromRoute(route) ?? 'News'
const BackAction = () => (
<TopNavigationAction icon={BackIcon} onPress={navigateBack} />
)
const navigateBack = () => {
navigation.goBack()
switch (routeName) {
case 'News':
return translate('navigation.news')
case 'Notifications':
return translate('navigation.notifications')
case 'Calendar':
return translate('navigation.calender')
case 'Menu':
return translate('navigation.menu')
}
return (
<SafeAreaView>
<SafeAreaViewContainer>
<ChildProvider child={child}>
<TopNavigation
title={() => (
<Text maxFontSizeMultiplier={2} style={styles.topNavigationTitle}>
{studentName(child.name)}
</Text>
)}
alignment="center"
accessoryLeft={BackAction}
/>
<TabNavigator initialRouteName={initialRouteName} />
</ChildProvider>
</SafeAreaViewContainer>
</SafeAreaView>
)
}
const themedStyles = StyleService.create({
topNavigationTitle: {
...Typography.fontWeight.semibold,
},
})
export const childRouteOptions = ({
route,
}: {
route: RouteProp<RootStackParamList, 'Child'>
}): NativeStackNavigationOptions => {
return {
...defaultStackStyling,
title: getHeaderTitle(route),
}
}
export const Child = () => {
const route = useRoute<ChildRouteProps>()
const { child, initialRouteName } = route.params
return (
<ChildProvider child={child}>
<TabNavigator initialRouteName={initialRouteName as any} />
</ChildProvider>
)
}

View File

@ -9,28 +9,28 @@ import {
useSchedule,
} from '@skolplattformen/api-hooks'
import { Child } from '@skolplattformen/embedded-api'
import {
Avatar,
Button,
Card,
IconProps,
StyleService,
Text,
useStyleSheet,
useTheme,
} from '@ui-kitten/components'
import moment from 'moment'
import React from 'react'
import { View } from 'react-native'
import { TouchableOpacity, View } from 'react-native'
import { Layout, Sizing } from '../styles'
import { studentName } from '../utils/peopleHelpers'
import { translate } from '../utils/translation'
import {
BookOpenIcon,
CalendarOutlineIcon,
MenuIcon,
NewsIcon,
ClipboardIcon,
NotificationsIcon,
} from './icon.component'
import { RootStackParamList } from './navigation.component'
import { StudentAvatar } from './studentAvatar.component'
interface ChildListItemProps {
child: Child
@ -40,10 +40,12 @@ type ChildListItemNavigationProp = StackNavigationProp<
RootStackParamList,
'Children'
>
export const ChildListItem = ({ child, color }: ChildListItemProps) => {
// Forces rerender when child.id changes
React.useEffect(() => {}, [child.id])
const colors = useTheme()
const navigation = useNavigation<ChildListItemNavigationProp>()
const { data: notifications, status: notificationsStatus } = useNotifications(
child
@ -113,181 +115,204 @@ export const ChildListItem = ({ child, color }: ChildListItemProps) => {
pending: 'basic',
}
const buttonAppearance: string = 'ghost'
const Footer = () => {
return (
<View style={styles.itemFooter}>
<Button
style={styles.item}
accessible={true}
accessibilityLabel={`${child.name}, ${translate('navigation.news')}`}
accessibilityRole="button"
size="small"
appearance={buttonAppearance}
status={statusColors[newsStatus]}
onPress={() =>
navigation.navigate('Child', {
child,
color,
initialRouteName: 'navigation.news',
})
}
accessoryLeft={NewsIcon}
>
{`${(news || []).length}`}
</Button>
<Button
style={styles.item}
accessible={true}
accessibilityLabel={`${child.name}, ${translate(
'navigation.notifications'
)}`}
accessibilityRole="button"
size="small"
appearance={buttonAppearance}
status={statusColors[notificationsStatus]}
onPress={() =>
navigation.navigate('Child', {
child,
color,
initialRouteName: 'navigation.notifications',
})
}
accessoryLeft={NotificationsIcon}
>
{`${(notifications || []).length}`}
</Button>
<Button
style={styles.item}
accessible={true}
accessibilityLabel={`${child.name}, ${translate(
'navigation.calender'
)}`}
accessibilityRole="button"
size="small"
appearance={buttonAppearance}
status={statusColors[calendarStatus]}
onPress={() =>
navigation.navigate('Child', {
child,
color,
initialRouteName: 'navigation.calender',
})
}
accessoryLeft={CalendarOutlineIcon}
>
{`${(calendar || []).length}`}
</Button>
<Button
style={styles.item}
accessible={true}
accessibilityLabel={`${child.name}, ${translate('navigation.menu')}`}
accessibilityRole="button"
size="small"
appearance={buttonAppearance}
status={statusColors[menuStatus]}
onPress={() =>
navigation.navigate('Child', {
child,
color,
initialRouteName: 'navigation.menu',
})
}
accessoryLeft={MenuIcon}
/>
</View>
)
}
const buttonAppearance = 'basic'
return (
<Card
style={styles.card}
appearance="filled"
status={color}
header={(props) => (
<View {...props} style={styles.cardHeader}>
<View style={styles.cardAvatar}>
<Avatar source={require('../assets/avatar.png')} shape="square" />
</View>
<View style={styles.cardHeaderText}>
<Text category="h6">{studentName(child.name)}</Text>
{className ? <Text category="s1">{className}</Text> : null}
</View>
</View>
)}
footer={Footer}
<TouchableOpacity
onPress={() => navigation.navigate('Child', { child, color })}
>
{scheduleAndCalendarThisWeek.slice(0, 3).map((calendarItem, i) => (
<Text category="p1" key={i}>
{`${calendarItem.title} (${displayDate(calendarItem.startDate)})`}
</Text>
))}
{notificationsThisWeek.slice(0, 3).map((notification, i) => (
<Text category="p1" key={i}>
{translate('notifications.notificationTitle', {
message: notification.message,
dateCreated: displayDate(notification.dateCreated),
})}
</Text>
))}
{newsThisWeek.slice(0, 3).map((newsItem, i) => (
<Text category="p1" key={i}>
{translate('news.notificationTitle', {
header: newsItem.header,
published: displayDate(newsItem.published),
})}
</Text>
))}
{scheduleAndCalendarThisWeek.length ||
notificationsThisWeek.length ||
newsThisWeek.length ? null : (
<Text category="p1" style={styles.noNewNewsItemsText}>
{translate('news.noNewNewsItemsThisWeek')}
</Text>
)}
<View style={styles.itemFooterAbsence}>
<Button
accessible={true}
accessibilityRole="button"
accessibilityLabel={`${child.name}, ${translate('abscense.title')}`}
size="small"
status="primary"
onPress={() => navigation.navigate('Absence', { child })}
>
{translate('abscense.title')}
</Button>
<View style={styles.card}>
<View style={styles.cardHeader}>
<View style={styles.cardHeaderLeft}>
<StudentAvatar name={studentName(child.name)} color={color} />
<View style={styles.cardHeaderText}>
<Text category="h6">{studentName(child.name)}</Text>
{className ? <Text category="s1">{className}</Text> : null}
</View>
</View>
</View>
{scheduleAndCalendarThisWeek.slice(0, 3).map((calendarItem, i) => (
<Text category="p1" key={i}>
{`${calendarItem.title} (${displayDate(calendarItem.startDate)})`}
</Text>
))}
{notificationsThisWeek.slice(0, 3).map((notification, i) => (
<Text category="p1" key={i}>
{translate('notifications.notificationTitle', {
message: notification.message,
dateCreated: displayDate(notification.dateCreated),
})}
</Text>
))}
{newsThisWeek.slice(0, 3).map((newsItem, i) => (
<Text category="p1" key={i}>
{translate('news.notificationTitle', {
header: newsItem.header,
published: displayDate(newsItem.published),
})}
</Text>
))}
{scheduleAndCalendarThisWeek.length ||
notificationsThisWeek.length ||
newsThisWeek.length ? null : (
<Text category="p1" style={styles.noNewNewsItemsText}>
{translate('news.noNewNewsItemsThisWeek')}
</Text>
)}
<View style={styles.itemFooterAbsence}>
<Button
accessible
accessibilityRole="button"
accessibilityLabel={`${child.name}, ${translate('abscense.title')}`}
size="small"
status="primary"
onPress={() => navigation.navigate('Absence', { child })}
>
{translate('abscense.title')}
</Button>
</View>
<View style={styles.itemFooter}>
<Button
style={styles.item}
size="small"
accessible={true}
accessibilityLabel={`${child.name}, ${translate(
'navigation.news'
)}`}
accessibilityRole="button"
appearance={buttonAppearance}
status={statusColors[newsStatus]}
onPress={() =>
navigation.navigate('Child', {
child,
color,
initialRouteName: translate('navigation.news'),
})
}
accessoryLeft={createFooterIcon(
BookOpenIcon,
colors['color-basic-text']
)}
>
{`${(news || []).length}`}
</Button>
<Button
style={styles.item}
size="small"
accessible={true}
accessibilityLabel={`${child.name}, ${translate(
'navigation.notifications'
)}`}
accessibilityRole="button"
appearance={buttonAppearance}
status={statusColors[notificationsStatus]}
onPress={() =>
navigation.navigate('Child', {
child,
color,
initialRouteName: translate('navigation.notifications'),
})
}
accessoryLeft={createFooterIcon(
NotificationsIcon,
colors['color-basic-text']
)}
>
{`${(notifications || []).length}`}
</Button>
<Button
style={styles.item}
size="small"
accessible={true}
accessibilityLabel={`${child.name}, ${translate(
'navigation.calender'
)}`}
accessibilityRole="button"
appearance={buttonAppearance}
status={statusColors[calendarStatus]}
onPress={() =>
navigation.navigate('Child', {
child,
color,
initialRouteName: translate('navigation.calender'),
})
}
accessoryLeft={createFooterIcon(
CalendarOutlineIcon,
colors['color-basic-text']
)}
>
{`${(calendar || []).length}`}
</Button>
<Button
style={styles.item}
size="small"
accessible
accessibilityLabel={`${child.name}, ${translate(
'navigation.menu'
)}`}
accessibilityRole="button"
appearance={buttonAppearance}
status={statusColors[menuStatus]}
onPress={() =>
navigation.navigate('Child', {
child,
color,
initialRouteName: translate('navigation.menu'),
})
}
accessoryLeft={createFooterIcon(
ClipboardIcon,
colors['color-basic-text']
)}
/>
</View>
</View>
</Card>
</TouchableOpacity>
)
}
const createFooterIcon = (Icon: typeof BookOpenIcon, color: string) => (
props: IconProps
) => {
return <Icon {...props} fill={color} />
}
const themeStyles = StyleService.create({
card: {
marginBottom: Sizing.t5,
borderRadius: 25,
padding: Sizing.t5,
marginBottom: Sizing.t4,
backgroundColor: 'background-basic-color-1',
},
cardHeader: {
...Layout.flex.row,
...Layout.mainAxis.center,
...Layout.crossAxis.spaceBetween,
marginBottom: Sizing.t4,
},
cardHeaderLeft: {
...Layout.flex.row,
...Layout.mainAxis.center,
flex: 1,
},
cardHeaderText: {
marginHorizontal: Sizing.t4,
flex: 1,
},
cardAvatar: { margin: Sizing.t5, marginRight: 0 },
cardHeaderText: { margin: Sizing.t5, flex: 1 },
itemFooter: {
...Layout.flex.row,
...Layout.crossAxis.evenly,
paddingVertical: Sizing.t2,
borderRadius: 5,
margin: 0,
marginTop: Sizing.t4,
},
itemFooterAbsence: {
...Layout.mainAxis.flexStart,
marginTop: Sizing.t4,
},
item: {
paddingHorizontal: 0,
},
noNewNewsItemsText: {
color: 'color-basic-600',
marginRight: 12,
paddingHorizontal: 2,
paddingVertical: 0,
marginBottom: 0,
},
noNewNewsItemsText: {},
})

View File

@ -1,28 +1,27 @@
import AsyncStorage from '@react-native-async-storage/async-storage'
import { useNavigation } from '@react-navigation/core'
import { useApi, useChildList } from '@skolplattformen/api-hooks'
import { Child } from '@skolplattformen/embedded-api'
import {
Button,
Divider,
Layout,
List,
Spinner,
StyleService,
Text,
TopNavigation,
TopNavigationAction,
useTheme,
useStyleSheet,
} from '@ui-kitten/components'
import React from 'react'
import React, { useCallback, useEffect, useMemo } from 'react'
import {
Image,
ListRenderItemInfo,
StyleSheet,
View,
ImageStyle,
Linking,
ListRenderItemInfo,
View,
} from 'react-native'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { NativeStackNavigationOptions } from 'react-native-screens/native-stack'
import ActionSheet from 'rn-actionsheet-module'
import { defaultStackStyling } from '../design/navigationThemes'
import { Colors, Layout as LayoutStyle, Sizing, Typography } from '../styles'
import { translate } from '../utils/translation'
import { ChildListItem } from './childListItem.component'
@ -30,25 +29,43 @@ import { SettingsIcon } from './icon.component'
const colors = ['primary', 'success', 'info', 'warning', 'danger']
export const childenRouteOptions = (): NativeStackNavigationOptions => {
return {
...defaultStackStyling,
title: translate('children.title'),
headerLargeTitle: true,
headerLargeTitleHideShadow: true,
}
}
export const Children = () => {
const settingsOptions = [
translate('general.logout'),
translate('general.cancel'),
]
const theme = useTheme()
const styles = useStyleSheet(themedStyles)
const navigation = useNavigation()
const { api } = useApi()
const { data: childList, status, reload } = useChildList()
const insets = useSafeAreaInsets()
const handleSettingSelection = (index: number) => {
switch (index) {
case 0:
logout()
break
}
let { data: childList, status, reload } = useChildList()
const reloadChildren = () => {
reload()
}
const settings = () => {
const logout = useCallback(() => {
api.logout()
AsyncStorage.clear()
}, [api])
const settingsOptions = useMemo(() => {
return [translate('general.logout'), translate('general.cancel')]
}, [])
const handleSettingSelection = useCallback(
(index: number) => {
if (index === 0) logout()
},
[logout]
)
const settings = useCallback(() => {
const options = {
cancelButtonIndex: 1,
title: translate('general.settings'),
@ -58,126 +75,93 @@ export const Children = () => {
}
ActionSheet(options, handleSettingSelection)
}
}, [handleSettingSelection, settingsOptions])
const reloadChildren = () => {
reload()
}
const logout = () => {
api.logout()
AsyncStorage.clear()
}
useEffect(() => {
navigation.setOptions({
headerRight: () => {
return <TopNavigationAction icon={SettingsIcon} onPress={settings} />
},
})
}, [navigation, settings])
// We need to skip safe area view here, due to the reason that it's adding a white border
// when this view is actually lightgrey. Taking the padding top value from the use inset hook.
return (
<View
style={[
{
...styles.topContainer,
paddingTop: insets.top,
},
{ backgroundColor: theme['background-basic-color-1'] },
]}
>
<>
{status === 'loaded' ? (
<>
<TopNavigation
title={() => (
<Text
maxFontSizeMultiplier={2.5}
style={styles.topNavigationTitle}
<>
{status === 'loaded' ? (
<List
contentContainerStyle={styles.childListContainer}
data={childList}
style={styles.childList}
ListEmptyComponent={
<View style={styles.emptyState}>
<Text category="h2">{translate('children.noKids_title')}</Text>
<Text style={styles.emptyStateDescription}>
{translate('children.noKids_description')}
</Text>
<Image
accessibilityIgnoresInvertColors={false}
source={require('../assets/children.png')}
style={styles.emptyStateImage as ImageStyle}
/>
</View>
}
renderItem={({ item: child, index }: ListRenderItemInfo<Child>) => (
<ChildListItem
child={child}
color={colors[index % colors.length]}
key={child.id}
/>
)}
/>
) : (
<View style={styles.loading}>
<Image
accessibilityIgnoresInvertColors={false}
source={require('../assets/girls.png')}
style={styles.loadingImage as ImageStyle}
/>
{status === 'error' ? (
<View style={styles.errorMessage}>
<Text category="h5">
{translate('children.loadingErrorHeading')}
</Text>
<Text style={{ fontSize: Sizing.t4 }}>
{translate('children.loadingErrorInformationText')}
</Text>
<View style={styles.errorButtons}>
<Button status="success" onPress={() => reloadChildren()}>
{translate('children.tryAgain')}
</Button>
<Button
status="basic"
onPress={() =>
Linking.openURL('https://skolplattformen.org/status')
}
>
{translate('children.title')}
</Text>
)}
alignment="center"
accessoryRight={() => (
<TopNavigationAction icon={SettingsIcon} onPress={settings} />
)}
/>
<Divider />
<List
contentContainerStyle={styles.childListContainer}
data={childList}
style={styles.childList}
ListEmptyComponent={
<View style={styles.emptyState}>
<Text category="h2">
{translate('children.noKids_title')}
</Text>
<Text style={styles.emptyStateDescription}>
{translate('children.noKids_description')}
</Text>
<Image
accessibilityIgnoresInvertColors={false}
source={require('../assets/children.png')}
style={styles.emptyStateImage}
/>
</View>
}
renderItem={({
item: child,
index,
}: ListRenderItemInfo<Child>) => (
<ChildListItem
child={child}
color={colors[index % colors.length]}
key={child.id}
/>
)}
/>
</>
) : (
<Layout style={styles.loading}>
<Image
accessibilityIgnoresInvertColors={false}
source={require('../assets/girls.png')}
style={styles.loadingImage}
/>
{status === 'error' ? (
<View style={styles.errorMessage}>
<Text category="h5">
{translate('children.loadingErrorHeading')}
</Text>
<Text style={{ fontSize: Sizing.t5 }}>
{translate('children.loadingErrorInformationText')}
</Text>
<View style={styles.errorButtons}>
<Button status="success" onPress={() => reloadChildren()}>
{translate('children.tryAgain')}
</Button>
<Button
status="basic"
onPress={() =>
Linking.openURL('https://skolplattformen.org/status')
}
>
{translate('children.viewStatus')}
</Button>
<Button onPress={() => logout()}>
{translate('general.logout')}
</Button>
</View>
{translate('children.viewStatus')}
</Button>
<Button onPress={() => logout()}>
{translate('general.logout')}
</Button>
</View>
) : (
<View style={styles.loadingMessage}>
<Spinner size="large" status="warning" />
<Text category="h1" style={styles.loadingText}>
{translate('general.loading')}
</Text>
</View>
)}
</Layout>
)}
</>
</View>
</View>
) : (
<View style={styles.loadingMessage}>
<Spinner size="large" status="primary" />
<Text category="h1" style={styles.loadingText}>
{translate('general.loading')}
</Text>
</View>
)}
</View>
)}
</>
)
}
const styles = StyleSheet.create({
const themedStyles = StyleService.create({
topContainer: {
...LayoutStyle.flex.full,
paddingBottom: 0,
@ -216,7 +200,8 @@ const styles = StyleSheet.create({
...LayoutStyle.flex.full,
},
childListContainer: {
padding: Sizing.t5,
paddingVertical: Sizing.t4,
paddingHorizontal: Sizing.t3,
},
emptyState: {
...LayoutStyle.center,

View File

@ -1,7 +1,9 @@
import { Icon } from '@ui-kitten/components'
import { Icon, IconProps } from '@ui-kitten/components'
import React from 'react'
const uiIcon = (name: string) => (props: any) => <Icon {...props} name={name} />
const uiIcon = (name: string) => (props: IconProps) => (
<Icon {...props} name={name} />
)
export const AlertIcon = uiIcon('alert-circle-outline')
export const BackIcon = uiIcon('arrow-back')
@ -26,3 +28,4 @@ export const SearchIcon = uiIcon('search-outline')
export const BookOpenIcon = uiIcon('book-open-outline')
export const GlobeIcon = uiIcon('globe-outline')
export const ExternalLinkIcon = uiIcon('external-link-outline')
export const ClipboardIcon = uiIcon('clipboard-outline')

View File

@ -1,16 +1,22 @@
import { StyleService, useStyleSheet } from '@ui-kitten/components'
import React from 'react'
import { ActivityIndicator, View, StyleSheet } from 'react-native'
import { ActivityIndicator, View } from 'react-native'
export const LoadingComponent = () => (
<View style={[styles.container, styles.horizontal]}>
<ActivityIndicator size="large" />
</View>
)
export const LoadingComponent = () => {
const styles = useStyleSheet(themedStyles)
const styles = StyleSheet.create({
return (
<View style={[styles.container, styles.horizontal]}>
<ActivityIndicator size="large" />
</View>
)
}
const themedStyles = StyleService.create({
container: {
flex: 1,
justifyContent: 'center',
backgroundColor: 'background-basic-color-2',
},
horizontal: {
flexDirection: 'row',

View File

@ -32,10 +32,11 @@ import {
SelectIcon,
} from './icon.component'
const BankId = (style) => (
const BankId = () => (
<Image
style={themedStyles.icon}
source={require('../assets/bankid_low_rgb.png')}
accessibilityIgnoresInvertColors
/>
)
@ -60,7 +61,9 @@ export const Login = () => {
translate('auth.bankid.OpenOnThisDevice'),
translate('auth.bankid.OpenOnAnotherDevice'),
translate('auth.loginAsTestUser'),
translate('general.cancel'),
]
useEffect(() => {
if (loginMethodIndex !== parseInt(cachedLoginMethodIndex, 10)) {
setCachedLoginMethodIndex(loginMethodIndex.toString())
@ -180,7 +183,7 @@ export const Login = () => {
placeholder={translate('auth.placeholder_SocialSecurityNumber')}
/>
)}
<ButtonGroup style={styles.loginButtonGroup} status="info">
<ButtonGroup style={styles.loginButtonGroup} status="primary">
<Button
accessible={true}
onPress={() => startLogin(socialSecurityNumber)}

View File

@ -1,15 +1,22 @@
import { useMenu } from '@skolplattformen/api-hooks'
import { MenuItem } from '@skolplattformen/embedded-api'
import { List, Text } from '@ui-kitten/components'
import {
Divider,
List,
StyleService,
Text,
useStyleSheet,
} from '@ui-kitten/components'
import 'moment/locale/sv'
import React from 'react'
import { Image, ListRenderItemInfo, StyleSheet, View } from 'react-native'
import { Sizing, Layout as LayoutStyle, Typography } from '../styles'
import { Image, ImageStyle, ListRenderItemInfo, View } from 'react-native'
import { Layout as LayoutStyle, Sizing, Typography } from '../styles'
import { translate } from '../utils/translation'
import { useChild } from './childContext.component'
import { MenuListItem } from './menuListItem.component'
export const Menu = () => {
const styles = useStyleSheet(themedStyles)
const child = useChild()
const { data } = useMenu(child)
@ -17,6 +24,7 @@ export const Menu = () => {
<List
contentContainerStyle={styles.contentContainer}
data={data}
ItemSeparatorComponent={Divider}
ListEmptyComponent={
<View style={styles.emptyState}>
<Text category="h4">{translate('menu.emptyHeadline')}</Text>
@ -26,7 +34,7 @@ export const Menu = () => {
<Image
accessibilityIgnoresInvertColors={false}
source={require('../assets/children.png')}
style={styles.emptyStateImage}
style={styles.emptyStateImage as ImageStyle}
/>
</View>
}
@ -38,23 +46,26 @@ export const Menu = () => {
)
}
const styles = StyleSheet.create({
const themedStyles = StyleService.create({
container: {
height: '100%',
width: '100%',
padding: Sizing.t3,
},
contentContainer: {
padding: Sizing.t3,
paddingHorizontal: Sizing.t5,
paddingVertical: Sizing.t2,
backgroundColor: 'background-basic-color-1',
borderRadius: 25,
},
emptyState: {
...LayoutStyle.center,
...LayoutStyle.flex.full,
paddingHorizontal: Sizing.t5,
paddingTop: 25,
},
emptyStateDescription: {
...Typography.align.center,
lineHeight: 21,
paddingHorizontal: Sizing.t3,
marginTop: Sizing.t3,
},
emptyStateImage: {

View File

@ -1,7 +1,7 @@
import { Text, Card, StyleService, useStyleSheet } from '@ui-kitten/components'
import { MenuItem } from '@skolplattformen/embedded-api'
import { StyleService, Text, useStyleSheet } from '@ui-kitten/components'
import React from 'react'
import { View } from 'react-native'
import { MenuItem } from '@skolplattformen/embedded-api'
import { Sizing, Typography } from '../styles'
interface MenuListItemProps {
@ -12,16 +12,8 @@ export const MenuListItem = ({ item }: MenuListItemProps) => {
const styles = useStyleSheet(themedStyles)
return (
<View style={styles.container}>
<Card
header={(props) => (
<View {...props}>
<Text style={styles.title}>{item.title}</Text>
</View>
)}
style={styles.contentContainer}
>
<Text category="p1">{item.description}</Text>
</Card>
<Text style={styles.title}>{item.title}</Text>
<Text category="p1">{item.description}</Text>
</View>
)
}
@ -29,10 +21,7 @@ export const MenuListItem = ({ item }: MenuListItemProps) => {
const themedStyles = StyleService.create({
container: {
width: '100%',
},
contentContainer: {
marginBottom: Sizing.t2,
justifyContent: 'flex-start',
paddingVertical: Sizing.t3,
},
topContainer: {
margin: Sizing.t1,
@ -41,6 +30,6 @@ const themedStyles = StyleService.create({
},
title: {
...Typography.header,
color: 'color-basic-800',
marginBottom: Sizing.t1,
},
})

View File

@ -88,7 +88,7 @@ export const ModalWebView = ({
const themedStyles = StyleService.create({
container: {
flex: 1,
backgroundColor: 'background-basic-color-1',
backgroundColor: 'background-basic-color-2',
},
headerWrapper: {
marginTop: Sizing.t1,
@ -96,7 +96,7 @@ const themedStyles = StyleService.create({
borderRadius: 2,
borderColor: 'basic-color-200',
borderBottomWidth: 1,
backgroundColor: 'background-basic-color-1',
backgroundColor: 'background-basic-color-2',
},
backdrop: {
backgroundColor: 'color-basic-transparent-600',
@ -111,7 +111,7 @@ const themedStyles = StyleService.create({
...Layout.mainAxis.center,
paddingHorizontal: Sizing.t3,
paddingVertical: Sizing.t1,
backgroundColor: 'background-basic-color-1',
backgroundColor: 'background-basic-color-2',
},
shareIcon: {
width: 24,

View File

@ -1,20 +1,25 @@
import { useApi } from '@skolplattformen/api-hooks'
import { NavigationContainer } from '@react-navigation/native'
import { createStackNavigator } from '@react-navigation/stack'
import React, { useEffect } from 'react'
import { StatusBar } from 'react-native'
import { schema } from '../app.json'
import Absence from './absence.component'
import { Child } from './child.component'
import { Children } from './children.component'
import { Auth } from './auth.component'
import { SetLanguage } from './setLanguage.component'
import { NewsItem } from './newsItem.component'
import { useApi } from '@skolplattformen/api-hooks'
import {
Child as ChildType,
NewsItem as NewsItemType,
} from '@skolplattformen/embedded-api'
import { useTheme } from '@ui-kitten/components'
import React, { useEffect } from 'react'
import { StatusBar, useColorScheme } from 'react-native'
import { createNativeStackNavigator } from 'react-native-screens/native-stack'
import { schema } from '../app.json'
import {
darkNavigationTheme,
lightNavigationTheme,
} from '../design/navigationThemes'
import { useAppState } from '../hooks/useAppState'
import Absence, { absenceRouteOptions } from './absence.component'
import { Auth, authRouteOptions } from './auth.component'
import { Child, childRouteOptions } from './child.component'
import { childenRouteOptions, Children } from './children.component'
import { NewsItem, newsItemRouteOptions } from './newsItem.component'
import { SetLanguage, setLanguageRouteOptions } from './setLanguage.component'
export type RootStackParamList = {
Login: undefined
@ -29,7 +34,7 @@ export type RootStackParamList = {
SetLanguage: undefined
}
const { Navigator, Screen } = createStackNavigator()
const { Navigator, Screen } = createNativeStackNavigator<RootStackParamList>()
const linking = {
prefixes: [schema],
@ -43,6 +48,9 @@ const linking = {
export const AppNavigator = () => {
const { isLoggedIn, api } = useApi()
const colorScheme = useColorScheme()
const colors = useTheme()
const currentAppState = useAppState()
useEffect(() => {
@ -59,20 +67,56 @@ export const AppNavigator = () => {
}, [currentAppState, isLoggedIn, api])
return (
<NavigationContainer linking={linking}>
<NavigationContainer
linking={linking}
theme={
colorScheme === 'dark' ? darkNavigationTheme : lightNavigationTheme
}
>
<StatusBar />
<Navigator headerMode="none">
<Navigator
screenOptions={() => ({
headerLargeTitle: true,
headerLargeTitleHideShadow: true,
headerStyle: {
backgroundColor: colors['background-basic-color-2'],
},
headerLargeTitleStyle: {
fontFamily: 'Poppins-ExtraBold',
},
})}
>
{isLoggedIn ? (
<>
<Screen name="Children" component={Children} />
<Screen name="Child" component={Child} />
<Screen name="NewsItem" component={NewsItem} />
<Screen name="Absence" component={Absence} />
<Screen
name="Children"
component={Children}
options={childenRouteOptions}
/>
<Screen
name="Child"
component={Child}
options={childRouteOptions}
/>
<Screen
name="NewsItem"
component={NewsItem}
options={newsItemRouteOptions}
/>
<Screen
name="Absence"
component={Absence}
options={absenceRouteOptions}
/>
</>
) : (
<>
<Screen name="Login" component={Auth} />
<Screen name="SetLanguage" component={SetLanguage} />
<Screen name="Login" component={Auth} options={authRouteOptions} />
<Screen
name="SetLanguage"
component={SetLanguage}
options={setLanguageRouteOptions}
/>
</>
)}
</Navigator>

View File

@ -0,0 +1,32 @@
import { Text } from '@ui-kitten/components'
import React from 'react'
import { StyleSheet, View } from 'react-native'
import { Layout } from '../styles'
import { fontSize } from '../styles/typography'
interface NavigationTitleProps {
title?: string
subtitle?: string
}
/**
* Navigation Title with a smaller subtitle.
*/
export const NavigationTitle = ({ title, subtitle }: NavigationTitleProps) => {
return (
<View style={styles.container}>
<Text style={styles.title}>{title}</Text>
<Text style={styles.subtitle}>{subtitle}</Text>
</View>
)
}
const styles = StyleSheet.create({
container: {
...Layout.center,
},
title: {
...fontSize.base,
fontWeight: '500',
},
subtitle: { ...fontSize.xs },
})

View File

@ -1,26 +1,18 @@
import { RouteProp } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import { useNewsDetails } from '@skolplattformen/api-hooks'
import {
Divider,
Text,
TopNavigation,
TopNavigationAction,
StyleService,
useStyleSheet,
} from '@ui-kitten/components'
import { StyleService, Text, useStyleSheet } from '@ui-kitten/components'
import moment from 'moment'
import 'moment/locale/sv'
import React from 'react'
import { ScrollView, View } from 'react-native'
import { Colors, Layout, Sizing, Typography } from '../styles'
import { NativeStackNavigationOptions } from 'react-native-screens/native-stack'
import { defaultStackStyling } from '../design/navigationThemes'
import { Layout, Sizing, Typography } from '../styles'
import { translate } from '../utils/translation'
import { BackIcon } from './icon.component'
import { Image } from './image.component'
import { Markdown } from './markdown.component'
import { RootStackParamList } from './navigation.component'
import { SafeAreaViewContainer } from '../ui/safeAreaViewContainer.component'
import { SafeAreaView } from '../ui/safeAreaView.component'
interface NewsItemProps {
navigation: StackNavigationProp<RootStackParamList, 'NewsItem'>
@ -32,111 +24,90 @@ const displayDate = (date: string | undefined) => moment(date).format('lll')
const dateIsValid = (date: string | undefined) =>
moment(date, moment.ISO_8601).isValid()
export const NewsItem = ({ navigation, route }: NewsItemProps) => {
export const newsItemRouteOptions = ({
route,
}: {
route: RouteProp<RootStackParamList, 'NewsItem'>
}): NativeStackNavigationOptions => {
const newsItem = route.params.newsItem
return {
...defaultStackStyling,
title: newsItem.header,
headerLargeTitle: true,
headerStyle: {
// TODO: This color must come from theme for dark mode
backgroundColor: '#fff',
},
}
}
export const NewsItem = ({ route }: NewsItemProps) => {
const { newsItem, child } = route.params
const { data } = useNewsDetails(child, newsItem)
const styles = useStyleSheet(themedStyles)
const stylesMarkdown = useStyleSheet(themedStylesMarkdown)
const navigateBack = () => {
navigation.goBack()
}
const BackAction = () => (
<TopNavigationAction
testID="topNavBackToChild"
accessibilityHint={translate('news.backToChild')}
icon={BackIcon}
onPress={navigateBack}
/>
)
return (
<SafeAreaView>
<SafeAreaViewContainer>
<TopNavigation
title={() => (
<Text maxFontSizeMultiplier={1.5} style={styles.topNavigationTitle}>
{translate('news.title')}
</Text>
)}
alignment="center"
accessoryLeft={BackAction}
/>
<Divider />
<ScrollView
contentContainerStyle={styles.article}
style={styles.scrollView}
<ScrollView
contentContainerStyle={styles.article}
style={styles.scrollView}
>
{dateIsValid(newsItem.published) && (
<Text
maxFontSizeMultiplier={2}
style={[styles.subtitle, styles.published]}
>
<Text maxFontSizeMultiplier={2} style={styles.title}>
{newsItem.header}
</Text>
{dateIsValid(newsItem.published) && (
<Text
maxFontSizeMultiplier={2}
style={[styles.subtitle, styles.published]}
>
<Text style={styles.strong}>{translate('news.published')}:</Text>{' '}
{displayDate(newsItem.published)}
</Text>
)}
{dateIsValid(newsItem.modified) && (
<Text maxFontSizeMultiplier={2} style={styles.subtitle}>
<Text style={styles.strong}>{translate('news.updated')}:</Text>{' '}
{displayDate(newsItem.modified)}
</Text>
)}
<View style={styles.body}>
<Markdown style={stylesMarkdown}>{data.body}</Markdown>
{newsItem.fullImageUrl && (
<Image
accessibilityIgnoresInvertColors={false}
src={newsItem.fullImageUrl}
// @ts-expect-error Fix later on
style={styles.image}
/>
)}
</View>
</ScrollView>
</SafeAreaViewContainer>
</SafeAreaView>
<Text style={styles.strong}>{translate('news.published')}:</Text>{' '}
{displayDate(newsItem.published)}
</Text>
)}
{dateIsValid(newsItem.modified) && (
<Text maxFontSizeMultiplier={2} style={styles.subtitle}>
<Text style={styles.strong}>{translate('news.updated')}:</Text>{' '}
{displayDate(newsItem.modified)}
</Text>
)}
<View style={styles.body}>
<Markdown style={stylesMarkdown}>{data.body}</Markdown>
{newsItem.fullImageUrl && (
<Image
accessibilityIgnoresInvertColors={false}
src={newsItem.fullImageUrl}
// @ts-expect-error Fix later on
style={styles.image}
/>
)}
</View>
</ScrollView>
)
}
const themedStylesMarkdown = StyleService.create({
body: {
...Typography.fontSize.base,
color: 'color-basic-800',
color: 'text-basic-color',
lineHeight: 26,
},
heading1: {
...Typography.fontSize.xl,
color: 'color-basic-600',
color: 'text-basic-color',
},
heading2: {
...Typography.fontSize.lg,
color: 'color-basic-800',
color: 'text-basic-color',
},
code_block: {
color: 'color-basic-800',
color: 'text-basic-color',
backgroundColor: 'background-basic-color-1',
borderColor: 'color-basic-400',
},
})
const themedStyles = StyleService.create({
safeArea: {
...Layout.flex.full,
backgroundColor: Colors.neutral.white,
},
topContainer: {
...Layout.flex.row,
...Layout.crossAxis.spaceBetween,
},
article: {
padding: Sizing.t5,
backgroundColor: 'background-basic-color-2',
backgroundColor: 'background-basic-color-1',
},
scrollView: {
...Layout.flex.full,
@ -145,6 +116,7 @@ const themedStyles = StyleService.create({
width: '100%',
minHeight: 300,
marginTop: Sizing.t4,
borderRadius: 15,
},
title: {
...Typography.fontWeight.bold,
@ -153,12 +125,12 @@ const themedStyles = StyleService.create({
},
subtitle: {
...Typography.fontSize.xs,
color: 'color-basic-600',
color: 'text-hint-color',
},
strong: {
...Typography.fontSize.xs,
...Typography.fontWeight.bold,
color: 'color-basic-600',
color: 'text-hint-color',
},
published: {
marginBottom: Sizing.t1,

View File

@ -1,18 +1,19 @@
import React, { useState, useMemo } from 'react'
import { useNews } from '@skolplattformen/api-hooks'
import { List, Input } from '@ui-kitten/components'
import { StyleSheet, TouchableWithoutFeedback } from 'react-native'
import { Input, List, StyleService, useStyleSheet } from '@ui-kitten/components'
import React, { useMemo, useState } from 'react'
import { TouchableOpacity, View } from 'react-native'
import { Sizing } from '../styles'
import { useChild } from './childContext.component'
import { NewsListItem } from './newsListItem.component'
import { translate } from '../utils/translation'
import {
useNewsListSearchResults,
renderSearchResultPreview,
useNewsListSearchResults,
} from '../utils/search'
import { SearchIcon, CloseOutlineIcon } from './icon.component'
import { translate } from '../utils/translation'
import { useChild } from './childContext.component'
import { CloseOutlineIcon, SearchIcon } from './icon.component'
import { NewsListItem } from './newsListItem.component'
export const NewsList = () => {
const styles = useStyleSheet(themedStyles)
const child = useChild()
const { data } = useNews(child)
@ -33,14 +34,19 @@ export const NewsList = () => {
accessoryLeft={SearchIcon}
onChangeText={setSearchQuery}
value={searchQuery}
accessoryRight={(props) => (
<TouchableWithoutFeedback onPress={() => setSearchQuery('')}>
<CloseOutlineIcon {...props} />
</TouchableWithoutFeedback>
)}
style={styles.search}
accessoryRight={(props) =>
searchQuery.length > 0 ? (
<TouchableOpacity onPress={() => setSearchQuery('')}>
<CloseOutlineIcon {...props} />
</TouchableOpacity>
) : (
<View />
)
}
/>
),
[searchQuery]
[searchQuery, styles.search]
)
if (searchQuery) {
@ -72,12 +78,18 @@ export const NewsList = () => {
)
}
const styles = StyleSheet.create({
const themedStyles = StyleService.create({
container: {
height: '100%',
width: '100%',
},
contentContainer: {
padding: Sizing.t3,
paddingVertical: Sizing.t3,
paddingHorizontal: Sizing.t3,
},
search: {
backgroundColor: 'background-basic-color-1',
borderRadius: 40,
marginBottom: Sizing.t2,
},
})

View File

@ -1,15 +1,15 @@
import { useNavigation } from '@react-navigation/native'
import { StackNavigationProp } from '@react-navigation/stack'
import { NewsItem } from '@skolplattformen/embedded-api'
import React, { ReactNode } from 'react'
import { StyleService, useStyleSheet } from '@ui-kitten/components'
import moment from 'moment'
import React, { ReactNode } from 'react'
import { Dimensions, Text, View } from 'react-native'
import { TouchableOpacity } from 'react-native-gesture-handler'
import { Layout, Sizing, Typography } from '../styles'
import { useChild } from './childContext.component'
import { Image } from './image.component'
import { RootStackParamList } from './navigation.component'
import { StyleService, useStyleSheet } from '@ui-kitten/components'
interface NewsListItemProps {
item: NewsItem
@ -66,15 +66,11 @@ const themedStyles = StyleService.create({
card: {
...Layout.flex.full,
...Layout.flex.row,
borderRadius: 2,
borderWidth: 1,
padding: Sizing.t5,
marginBottom: Sizing.t2,
borderRadius: 15,
paddingVertical: Sizing.t4,
paddingHorizontal: Sizing.t4,
marginBottom: Sizing.t3,
backgroundColor: 'background-basic-color-1',
borderColor: 'border-basic-color-3',
},
text: {
...Layout.flex.full,
@ -86,7 +82,6 @@ const themedStyles = StyleService.create({
},
subtitle: {
...Typography.fontSize.xs,
marginBottom: Sizing.t2,
color: 'text-hint-color',
},
@ -95,9 +90,9 @@ const themedStyles = StyleService.create({
color: 'text-basic-color',
},
image: {
borderRadius: 3,
width: 80,
height: 80,
marginRight: Sizing.t5,
borderRadius: 50,
width: 50,
height: 50,
marginRight: Sizing.t3,
},
})

View File

@ -1,10 +1,10 @@
import { Notification as NotificationType } from '@skolplattformen/embedded-api'
import { Card, StyleService, Text, useStyleSheet } from '@ui-kitten/components'
import { StyleService, Text, useStyleSheet } from '@ui-kitten/components'
import moment from 'moment'
import React from 'react'
import { View } from 'react-native'
import { TouchableOpacity, View } from 'react-native'
import { Layout, Sizing, Typography } from '../styles'
import { ModalWebView } from './modalWebView.component'
import moment from 'moment'
interface NotificationProps {
item: NotificationType
@ -26,11 +26,9 @@ export const Notification = ({ item }: NotificationProps) => {
return (
<>
<Card
style={styles.card}
onPress={open}
header={(headerProps) => (
<View {...headerProps}>
<TouchableOpacity onPress={open}>
<View style={styles.card}>
<View>
<Text style={styles.title}>{item.sender}</Text>
<Text style={styles.subtitle}>
{item.category ? item.category : ''}
@ -38,10 +36,9 @@ export const Notification = ({ item }: NotificationProps) => {
{displayDate ? displayDate : ''}
</Text>
</View>
)}
>
<Text>{item.message}</Text>
</Card>
<Text>{item.message}</Text>
</View>
</TouchableOpacity>
{isOpen && (
<ModalWebView
url={item.url}
@ -56,13 +53,11 @@ export const Notification = ({ item }: NotificationProps) => {
const themedStyles = StyleService.create({
card: {
...Layout.flex.full,
borderRadius: 2,
borderWidth: 1,
marginBottom: Sizing.t2,
borderRadius: 15,
paddingVertical: Sizing.t4,
paddingHorizontal: Sizing.t4,
marginBottom: Sizing.t3,
backgroundColor: 'background-basic-color-1',
borderColor: 'border-basic-color-3',
},
title: {
...Typography.header,
@ -71,5 +66,6 @@ const themedStyles = StyleService.create({
subtitle: {
...Typography.fontSize.xs,
color: 'text-hint-color',
marginBottom: Sizing.t2,
},
})

View File

@ -1,14 +1,15 @@
import { useNotifications } from '@skolplattformen/api-hooks'
import { List } from '@ui-kitten/components'
import { List, StyleService, useStyleSheet } from '@ui-kitten/components'
import React from 'react'
import { StyleSheet } from 'react-native'
import { Sizing } from '../styles'
import { useChild } from './childContext.component'
import { Notification } from './notification.component'
export const NotificationsList = () => {
const styles = useStyleSheet(themedStyles)
const child = useChild()
const { data } = useNotifications(child)
return (
<List
style={styles.container}
@ -21,12 +22,13 @@ export const NotificationsList = () => {
)
}
const styles = StyleSheet.create({
const themedStyles = StyleService.create({
container: {
height: '100%',
width: '100%',
},
contentContainer: {
padding: Sizing.t3,
paddingHorizontal: Sizing.t3,
paddingVertical: Sizing.t3,
},
})

View File

@ -1,26 +1,32 @@
import { useNavigation } from '@react-navigation/native'
import {
Layout,
Text,
Button,
ButtonGroup,
TopNavigationAction,
TopNavigation,
StyleService,
Text,
useStyleSheet,
useTheme,
} from '@ui-kitten/components'
import React, { useState } from 'react'
import { StyleSheet, View } from 'react-native'
import { View } from 'react-native'
import { ScrollView, TouchableOpacity } from 'react-native-gesture-handler'
import RNRestart from 'react-native-restart'
import { NativeStackNavigationOptions } from 'react-native-screens/native-stack'
import { useLanguage } from '../hooks/useLanguage'
import { isRTL, LanguageService } from '../services/languageService'
import { Layout as LayoutStyle, Sizing } from '../styles'
import { translate, languages } from '../utils/translation'
import { BackIcon } from './icon.component'
import { SafeAreaViewContainer } from '../ui/safeAreaViewContainer.component'
import RNRestart from 'react-native-restart'
import { SafeAreaView } from '../ui/safeAreaView.component'
import { fontSize } from '../styles/typography'
import { languages, translate } from '../utils/translation'
import { CheckIcon } from './icon.component'
export const setLanguageRouteOptions = (): NativeStackNavigationOptions => ({
title: translate('language.changeLanguage'),
})
export const SetLanguage = () => {
const navigation = useNavigation()
const styles = useStyleSheet(themedStyles)
const colors = useTheme()
const currentLanguage = LanguageService.getLanguageCode()
@ -56,76 +62,63 @@ export const SetLanguage = () => {
const activeLanguages = languages.filter((language) => language.active)
return (
<SafeAreaView>
<SafeAreaViewContainer>
<TopNavigation
accessoryLeft={() => (
<TopNavigationAction icon={BackIcon} onPress={() => goBack()} />
)}
alignment="center"
title={translate('language.changeLanguage')}
/>
<View style={styles.content}>
<ScrollView>
<Layout style={styles.container}>
<View style={styles.languageList}>
{activeLanguages.map((language) => (
<TouchableOpacity
key={language.langCode}
style={styles.languageButton}
onPress={() => setSelectedLanguage(language.langCode)}
>
<Text style={styles.check}>
{isSelected(language.langCode) ? '✓' : ''}
</Text>
<Text>{language.languageName}</Text>
<Text style={styles.languageButtonSubtitle}>
{language.languageLocalName}
</Text>
</TouchableOpacity>
))}
</View>
</Layout>
</ScrollView>
<ButtonGroup style={styles.buttonGroup}>
<Button
onPress={() => saveLanguage()}
appearance="ghost"
status="primary"
disabled={currentLanguage === selectedLanguage}
style={styles.button}
size="medium"
<View style={styles.content}>
<ScrollView>
<View style={styles.languageList}>
{activeLanguages.map((language) => (
<TouchableOpacity
key={language.langCode}
style={styles.languageButton}
onPress={() => setSelectedLanguage(language.langCode)}
>
{translate('language.changeLanguageButton')}
</Button>
</ButtonGroup>
<View>
<Text style={styles.languageButtonTitle}>
{language.languageLocalName}
</Text>
<Text style={styles.languageButtonSubtitle}>
{language.languageName}
</Text>
</View>
{isSelected(language.langCode) ? (
<CheckIcon
height={24}
width={24}
fill={colors['color-success-600']}
/>
) : null}
</TouchableOpacity>
))}
</View>
</SafeAreaViewContainer>
</SafeAreaView>
</ScrollView>
<ButtonGroup style={styles.buttonGroup}>
<Button
onPress={() => saveLanguage()}
appearance="ghost"
status="primary"
disabled={currentLanguage === selectedLanguage}
style={styles.button}
size="medium"
>
{translate('language.changeLanguageButton')}
</Button>
</ButtonGroup>
</View>
)
}
const styles = StyleSheet.create({
const themedStyles = StyleService.create({
languageList: {
flex: 1,
alignSelf: 'stretch',
flexDirection: 'column',
marginTop: 40,
marginTop: 8,
},
icon: {
width: 30,
height: 30,
},
check: {
position: 'absolute',
left: -20,
color: 'green',
},
container: {
...LayoutStyle.mainAxis.center,
...LayoutStyle.crossAxis.flexEnd,
padding: Sizing.t5,
},
content: {
@ -141,8 +134,15 @@ const styles = StyleSheet.create({
languageButton: {
minHeight: 45,
marginBottom: 10,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
languageButtonTitle: {
...fontSize.lg,
},
languageButtonSubtitle: {
...fontSize.sm,
opacity: 0.4,
},
button: { ...LayoutStyle.flex.full },

View File

@ -0,0 +1,37 @@
import { Text, useTheme } from '@ui-kitten/components'
import React from 'react'
import { StyleSheet, View } from 'react-native'
import { fontSize } from '../styles/typography'
import { initials } from '../utils/peopleHelpers'
export type StudentAvatarProps = {
name?: string
color: string
}
export const StudentAvatar = ({ name, color }: StudentAvatarProps) => {
const colors = useTheme()
const bgColor = colors[`color-${color}-100`]
const textColor = colors[`color-${color}-900`]
return (
<View style={{ ...styles.container, backgroundColor: bgColor }}>
<Text style={{ ...styles.text, color: textColor }}>{initials(name)}</Text>
</View>
)
}
export const styles = StyleSheet.create({
container: {
height: 44,
width: 44,
borderRadius: 300,
alignItems: 'center',
justifyContent: 'center',
},
text: {
...fontSize.lg,
fontFamily: 'Poppins-Medium',
fontWeight: '500',
},
})

View File

@ -1,22 +1,22 @@
import { useMenu, useTimetable } from '@skolplattformen/api-hooks'
import { Child, MenuItem, TimetableEntry } from '@skolplattformen/embedded-api'
import {
List,
ListItem,
ViewPager,
Text,
TabBar,
Tab,
StyleService,
Tab,
TabBar,
Text,
useStyleSheet,
ViewPager,
} from '@ui-kitten/components'
import React, { useEffect, useState } from 'react'
import moment from 'moment'
import React, { useEffect, useState } from 'react'
import { View } from 'react-native'
import { useMenu, useTimetable } from '@skolplattformen/api-hooks'
import { TimetableEntry, Child, MenuItem } from '@skolplattformen/embedded-api'
import { LanguageService } from '../services/languageService'
import { Sizing, Typography } from '../styles'
import { translate } from '../utils/translation'
import { TransitionView } from './transitionView.component'
import { Typography, Sizing } from '../styles'
interface WeekProps {
child: Child
@ -174,7 +174,7 @@ export const Week = ({ child }: WeekProps) => {
const themedStyles = StyleService.create({
view: {
backgroundColor: 'background-basic-color-2',
backgroundColor: 'background-basic-color-1',
},
part: {
backgroundColor: 'transparent',
@ -186,7 +186,7 @@ const themedStyles = StyleService.create({
},
item: {
height: 45,
backgroundColor: 'background-basic-color-1',
backgroundColor: 'background-basic-color-2',
paddingHorizontal: 0,
borderRadius: 2,
margin: 2,

View File

@ -1,13 +1,14 @@
{
"color-primary-100": "#FDD3D4",
"color-primary-200": "#FBA7B3",
"color-primary-300": "#F47A97",
"color-primary-400": "#E95789",
"color-primary-500": "#DB2575",
"color-primary-600": "#BC1B72",
"color-primary-700": "#9D126B",
"color-primary-800": "#7F0B60",
"color-primary-900": "#690759",
"color-primary-50": "#FFECF9",
"color-primary-100": "#FEC9EF",
"color-primary-200": "#FD82DA",
"color-primary-300": "#FB3CC6",
"color-primary-400": "#EC04AB",
"color-primary-500": "#A60378",
"color-primary-600": "#880262",
"color-primary-700": "#6A024D",
"color-primary-800": "#4C0137",
"color-primary-900": "#2E0121",
"color-primary-transparent-100": "rgba(219, 37, 117, 0.08)",
"color-primary-transparent-200": "rgba(219, 37, 117, 0.16)",
"color-primary-transparent-300": "rgba(219, 37, 117, 0.24)",
@ -44,15 +45,6 @@
"color-info-transparent-400": "rgba(24, 75, 186, 0.32)",
"color-info-transparent-500": "rgba(24, 75, 186, 0.4)",
"color-info-transparent-600": "rgba(24, 75, 186, 0.48)",
"color-warning-100": "#FCF1CA",
"color-warning-200": "#F9E097",
"color-warning-300": "#EFC662",
"color-warning-400": "#E0A93B",
"color-warning-500": "#CC8204",
"color-warning-600": "#AF6902",
"color-warning-700": "#925202",
"color-warning-800": "#763D01",
"color-warning-900": "#612F00",
"color-warning-transparent-100": "rgba(204, 130, 4, 0.08)",
"color-warning-transparent-200": "rgba(204, 130, 4, 0.16)",
"color-warning-transparent-300": "rgba(204, 130, 4, 0.24)",
@ -74,14 +66,12 @@
"color-danger-transparent-400": "rgba(186, 50, 127, 0.32)",
"color-danger-transparent-500": "rgba(186, 50, 127, 0.4)",
"color-danger-transparent-600": "rgba(186, 50, 127, 0.48)",
"background-basic-color-1": "#2E3137",
"background-basic-color-2": "#202225",
"background-basic-color-1": "#150A12",
"background-basic-color-2": "#030200",
"color-control-default": "#E5E7EB",
/* text colors */
"color-basic-800": "#DCDDDE",
"color-basic-600": "#DCDDDE",
"color-basic-500": "#8E9297",
/* basic button colors */
"color-basic-300": "#202020",
"color-basic-400": "gray"
"color-basic-default": "$color-primary-800",
"color-basic-focus": "$color-primary-700",
"color-basic-hover": "$color-primary-700",
"color-basic-active": "$color-primary-700",
"color-basic-text": "$color-primary-100"
}

View File

@ -1,81 +1,86 @@
{
"color-primary-100": "#FDD3D4",
"color-primary-200": "#FBA7B3",
"color-primary-300": "#F47A97",
"color-primary-400": "#E95789",
"color-primary-500": "#DB2575",
"color-primary-600": "#BC1B72",
"color-primary-700": "#9D126B",
"color-primary-800": "#7F0B60",
"color-primary-900": "#690759",
"color-primary-50": "#FFECF9",
"color-primary-100": "#FEC9EF",
"color-primary-200": "#FD82DA",
"color-primary-300": "#FB3CC6",
"color-primary-400": "#EC04AB",
"color-primary-500": "#A60378",
"color-primary-600": "#880262",
"color-primary-700": "#6A024D",
"color-primary-800": "#4C0137",
"color-primary-900": "#2E0121",
"color-primary-transparent-100": "rgba(219, 37, 117, 0.08)",
"color-primary-transparent-200": "rgba(219, 37, 117, 0.16)",
"color-primary-transparent-300": "rgba(219, 37, 117, 0.24)",
"color-primary-transparent-400": "rgba(219, 37, 117, 0.32)",
"color-primary-transparent-500": "rgba(219, 37, 117, 0.4)",
"color-primary-transparent-600": "rgba(219, 37, 117, 0.48)",
"color-success-100": "#DEF8D1",
"color-success-200": "#B8F2A6",
"color-success-300": "#82D973",
"color-success-400": "#50B44A",
"color-success-500": "#1C821F",
"color-success-600": "#146F1F",
"color-success-700": "#0E5D1E",
"color-success-800": "#084B1C",
"color-success-900": "#053E1B",
"color-success-transparent-100": "rgba(28, 130, 31, 0.08)",
"color-success-transparent-200": "rgba(28, 130, 31, 0.16)",
"color-success-transparent-300": "rgba(28, 130, 31, 0.24)",
"color-success-transparent-400": "rgba(28, 130, 31, 0.32)",
"color-success-transparent-500": "rgba(28, 130, 31, 0.4)",
"color-success-transparent-600": "rgba(28, 130, 31, 0.48)",
"color-info-100": "#CFE3FB",
"color-info-200": "#A1C5F8",
"color-info-300": "#6F9EEA",
"color-info-400": "#4A7AD5",
"color-info-500": "#184BBA",
"color-info-600": "#11399F",
"color-info-700": "#0C2A85",
"color-info-800": "#071D6B",
"color-info-900": "#041459",
"color-info-transparent-100": "rgba(24, 75, 186, 0.08)",
"color-info-transparent-200": "rgba(24, 75, 186, 0.16)",
"color-info-transparent-300": "rgba(24, 75, 186, 0.24)",
"color-info-transparent-400": "rgba(24, 75, 186, 0.32)",
"color-info-transparent-500": "rgba(24, 75, 186, 0.4)",
"color-info-transparent-600": "rgba(24, 75, 186, 0.48)",
"color-warning-100": "#FCEDC9",
"color-warning-200": "#F9D696",
"color-warning-300": "#EDB55F",
"color-warning-400": "#DC9338",
"color-warning-500": "#C66401",
"color-warning-600": "#AA4D00",
"color-warning-700": "#8E3900",
"color-warning-800": "#722800",
"color-warning-900": "#5F1C00",
"color-warning-transparent-100": "rgba(198, 100, 1, 0.08)",
"color-warning-transparent-200": "rgba(198, 100, 1, 0.16)",
"color-warning-transparent-300": "rgba(198, 100, 1, 0.24)",
"color-warning-transparent-400": "rgba(198, 100, 1, 0.32)",
"color-warning-transparent-500": "rgba(198, 100, 1, 0.4)",
"color-warning-transparent-600": "rgba(198, 100, 1, 0.48)",
"color-danger-100": "#F9D6D0",
"color-danger-200": "#F4A7A3",
"color-danger-300": "#DE6F73",
"color-danger-400": "#BE4757",
"color-danger-500": "#931935",
"color-danger-600": "#7E1235",
"color-danger-700": "#690C33",
"color-danger-800": "#55072F",
"color-danger-900": "#46042D",
"color-danger-transparent-100": "rgba(147, 25, 53, 0.08)",
"color-danger-transparent-200": "rgba(147, 25, 53, 0.16)",
"color-danger-transparent-300": "rgba(147, 25, 53, 0.24)",
"color-danger-transparent-400": "rgba(147, 25, 53, 0.32)",
"color-danger-transparent-500": "rgba(147, 25, 53, 0.4)",
"color-danger-transparent-600": "rgba(147, 25, 53, 0.48)",
"color-basic-800": "#1F2937",
"color-basic-600": "#4B5563",
"color-basic-500": "#6B7280",
"color-basic-400": "#E4E9F2"
"color-success-100": "#EEFACF",
"color-success-200": "#DAF6A1",
"color-success-300": "#B8E36F",
"color-success-400": "#93C948",
"color-success-500": "#64A518",
"color-success-600": "#4E8D11",
"color-success-700": "#3B760C",
"color-success-800": "#2A5F07",
"color-success-900": "#1E4F04",
"color-success-transparent-100": "rgba(100, 165, 24, 0.08)",
"color-success-transparent-200": "rgba(100, 165, 24, 0.16)",
"color-success-transparent-300": "rgba(100, 165, 24, 0.24)",
"color-success-transparent-400": "rgba(100, 165, 24, 0.32)",
"color-success-transparent-500": "rgba(100, 165, 24, 0.4)",
"color-success-transparent-600": "rgba(100, 165, 24, 0.48)",
"color-info-100": "#C6EEF8",
"color-info-200": "#90D9F1",
"color-info-300": "#55AED5",
"color-info-400": "#2B7DAC",
"color-info-500": "#004475",
"color-info-600": "#003464",
"color-info-700": "#002754",
"color-info-800": "#001B43",
"color-info-900": "#001338",
"color-info-transparent-100": "rgba(0, 68, 117, 0.08)",
"color-info-transparent-200": "rgba(0, 68, 117, 0.16)",
"color-info-transparent-300": "rgba(0, 68, 117, 0.24)",
"color-info-transparent-400": "rgba(0, 68, 117, 0.32)",
"color-info-transparent-500": "rgba(0, 68, 117, 0.4)",
"color-info-transparent-600": "rgba(0, 68, 117, 0.48)",
"color-warning-100": "#FCF1CA",
"color-warning-200": "#F9E097",
"color-warning-300": "#EFC662",
"color-warning-400": "#E0A93B",
"color-warning-500": "#CC8204",
"color-warning-600": "#AF6902",
"color-warning-700": "#925202",
"color-warning-800": "#763D01",
"color-warning-900": "#612F00",
"color-warning-transparent-100": "rgba(204, 130, 4, 0.08)",
"color-warning-transparent-200": "rgba(204, 130, 4, 0.16)",
"color-warning-transparent-300": "rgba(204, 130, 4, 0.24)",
"color-warning-transparent-400": "rgba(204, 130, 4, 0.32)",
"color-warning-transparent-500": "rgba(204, 130, 4, 0.4)",
"color-warning-transparent-600": "rgba(204, 130, 4, 0.48)",
"color-danger-100": "#FBD6DC",
"color-danger-200": "#F8AFC2",
"color-danger-300": "#EA83A9",
"color-danger-400": "#D56097",
"color-danger-500": "#BA327F",
"color-danger-600": "#9F2476",
"color-danger-700": "#85196C",
"color-danger-800": "#6B0F5F",
"color-danger-900": "#590956",
"color-danger-transparent-100": "rgba(186, 50, 127, 0.08)",
"color-danger-transparent-200": "rgba(186, 50, 127, 0.16)",
"color-danger-transparent-300": "rgba(186, 50, 127, 0.24)",
"color-danger-transparent-400": "rgba(186, 50, 127, 0.32)",
"color-danger-transparent-500": "rgba(186, 50, 127, 0.4)",
"color-danger-transparent-600": "rgba(186, 50, 127, 0.48)",
"background-basic-color-1": "#fff",
"background-basic-color-2": "$color-basic-200",
"text-hint-color": "#4B5466",
"color-basic-default": "$color-primary-50",
"color-basic-focus": "$color-primary-100",
"color-basic-hover": "$color-primary-100",
"color-basic-active": "$color-primary-100",
"color-basic-text": "$color-primary-800"
}

View File

@ -1,6 +1,7 @@
{
"strict": {
"text-font-family": "Poppins"
"text-font-family": "Poppins",
"border-radius": 10
},
"components": {
"Button": {
@ -10,12 +11,13 @@
"mapping": {},
"variantGroups": {
"status": {
"primary": {
"basic": {
"textColor": "$color-basic-text"
}
}
}
}
}
}
}
}
}
}

View File

@ -0,0 +1,35 @@
import { DarkTheme, DefaultTheme, Theme } from '@react-navigation/native'
import { NativeStackNavigationOptions } from 'react-native-screens/native-stack'
import { darkTheme, lightTheme } from './themes'
export const darkNavigationTheme: Theme = {
...DarkTheme,
colors: {
...DarkTheme.colors,
background: darkTheme['background-basic-color-2'],
border: darkTheme['background-basic-color-1'],
card: darkTheme['background-basic-color-1'],
primary: darkTheme['color-primary-400'],
text: '#ddd',
},
}
export const lightNavigationTheme: Theme = {
...DefaultTheme,
colors: {
...DefaultTheme.colors,
background: lightTheme['background-basic-color-2'],
border: lightTheme['background-basic-color-1'],
card: lightTheme['background-basic-color-1'],
primary: lightTheme['color-primary-500'],
},
}
export const defaultStackStyling: NativeStackNavigationOptions = {
headerTitleStyle: {
fontFamily: 'Poppins-Medium',
},
headerBackTitleStyle: {
fontFamily: 'Poppins-Regular',
},
}

View File

@ -0,0 +1,12 @@
import * as eva from '@eva-design/eva'
import darkJsonTheme from './dark.json'
import lightJsonTheme from './light.json'
export const darkTheme = {
...eva.dark,
...darkJsonTheme,
}
export const lightTheme = {
...eva.light,
...lightJsonTheme,
}

View File

@ -1,9 +1,10 @@
/**
* @format
*/
import 'react-native-gesture-handler'
import { AppRegistry } from 'react-native'
import App from './App'
import { name as appName } from './app.json'
AppRegistry.registerComponent(appName, () => App)

View File

@ -351,7 +351,7 @@ PODS:
- React
- RNCAsyncStorage (1.15.2):
- React-Core
- RNCMaskedView (0.1.10):
- RNCMaskedView (0.1.11):
- React
- RNDateTimePicker (3.4.3):
- React-Core
@ -359,8 +359,38 @@ PODS:
- React-Core
- RNLocalize (2.0.3):
- React-Core
- RNScreens (2.18.1):
- RNReanimated (2.2.0):
- DoubleConversion
- FBLazyVector
- FBReactNativeSpec
- glog
- RCT-Folly
- RCTRequired
- RCTTypeSafety
- React
- React-callinvoker
- React-Core
- React-Core/DevSupport
- React-Core/RCTWebSocket
- React-CoreModules
- React-cxxreact
- React-jsi
- React-jsiexecutor
- React-jsinspector
- React-RCTActionSheet
- React-RCTAnimation
- React-RCTBlob
- React-RCTImage
- React-RCTLinking
- React-RCTNetwork
- React-RCTSettings
- React-RCTText
- React-RCTVibration
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (3.3.0):
- React-Core
- React-RCTImage
- RNSVG (12.1.0):
- React
- Toast (4.0.0)
@ -433,6 +463,7 @@ DEPENDENCIES:
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNLocalize (from `../node_modules/react-native-localize`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNSVG (from `../node_modules/react-native-svg`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
@ -535,6 +566,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-gesture-handler"
RNLocalize:
:path: "../node_modules/react-native-localize"
RNReanimated:
:path: "../node_modules/react-native-reanimated"
RNScreens:
:path: "../node_modules/react-native-screens"
RNSVG:
@ -547,7 +580,7 @@ SPEC CHECKSUMS:
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de
FBLazyVector: 7b423f9e248eae65987838148c36eec1dbfe0b53
FBReactNativeSpec: 313b66e17294041415f72c26996e9670a9f07a59
FBReactNativeSpec: f792ea65d58b0e9219f7d4b52d887ee1141647ff
Flipper: d3da1aa199aad94455ae725e9f3aa43f3ec17021
Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41
Flipper-Folly: 755929a4f851b2fb2c347d533a23f191b008554c
@ -591,11 +624,12 @@ SPEC CHECKSUMS:
ReactCommon: bedc99ed4dae329c4fcf128d0c31b9115e5365ca
RNCalendarEvents: 7e65eb4a94f53c1744d1e275f7fafcfaa619f7a3
RNCAsyncStorage: 9b7605e899f9acb2fba33e87952c529731265453
RNCMaskedView: 5a8ec07677aa885546a0d98da336457e2bea557f
RNCMaskedView: 0e1bc4bfa8365eba5fbbb71e07fbdc0555249489
RNDateTimePicker: d943800c936fb01c352fcfb70439550d2cb57092
RNGestureHandler: a479ebd5ed4221a810967000735517df0d2db211
RNLocalize: 99e59cad311ca1b6872b1764514009416ccba03d
RNScreens: f7ad633b2e0190b77b6a7aab7f914fad6f198d8d
RNReanimated: 9c13c86454bfd54dab7505c1a054470bfecd2563
RNScreens: bf59f17fbf001f1025243eeed5f19419d3c11ef2
RNSVG: ce9d996113475209013317e48b05c21ee988d42e
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
Yoga: a7de31c64fe738607e7a3803e3f591a4b1df7393

View File

@ -8,36 +8,36 @@
/* Begin PBXBuildFile section */
00E356F31AD99517003FC87E /* appTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* appTests.m */; };
04B5A89762F9430A902416B4 /* Poppins-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3CBFE8DBE8994C1FAF939519 /* Poppins-MediumItalic.ttf */; };
13B07FBC1A68108700A75B9A /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
230E5925A9044F679B2F7C39 /* Poppins-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AF5677EA52D44EE97668E7A /* Poppins-Medium.ttf */; };
2D02E4BC1E0B4A80006451C7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.m */; };
2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
2D02E4BF1E0B4AB3006451C7 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
2DC4DBF14540481ABF14857A /* Poppins-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9A9C27037F0F4AF2A4CBAE78 /* Poppins-ExtraBold.ttf */; };
2DCD954D1E0B4F2C00145EB5 /* appTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* appTests.m */; };
417D579C3DC24A9684D24EDD /* Poppins-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 00FDE4C2522044DAAD866D5F /* Poppins-BlackItalic.ttf */; };
41DE97CF96674977BC052462 /* Poppins-SemiBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A9CB7679337840D3BD256A13 /* Poppins-SemiBoldItalic.ttf */; };
4E01D1C8DD5749C8953D3BE5 /* Poppins-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 661AE05493ED4FE780C32EF6 /* Poppins-Bold.ttf */; };
4F931CB3182F40F580F357FF /* Poppins-ThinItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E251C7BB03F74441B4357419 /* Poppins-ThinItalic.ttf */; };
50AAEBD19BD24B129801C393 /* Poppins-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 977D264ECA05485EA89A2F13 /* Poppins-Italic.ttf */; };
5257E7B8420740A2862BEEB0 /* Poppins-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8E2BDC4163F1467E94E5D34C /* Poppins-BoldItalic.ttf */; };
6D069850985349ABB76BDD0A /* Poppins-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B1A304CF782943E19F93DFD6 /* Poppins-Black.ttf */; };
71436C48CDF348BE904103E9 /* Poppins-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 231B760691854EBCA7ECD80E /* Poppins-Light.ttf */; };
7B94D8768C6340958F2EEFFD /* Poppins-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C9AC377D6B7744ECBBEA0A8A /* Poppins-ExtraLight.ttf */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
836E1F3164D8497F8C51847A /* Poppins-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7CEBAD12E8E5471AA962262A /* Poppins-LightItalic.ttf */; };
8F8E6DB2A0AB4DF3A5ED51E7 /* Poppins-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 920F53F06E0B433DB798AC45 /* Poppins-SemiBold.ttf */; };
9FA2A398A0264369AD50123F /* Poppins-ExtraBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9F1539AF1544412D9662D264 /* Poppins-ExtraBoldItalic.ttf */; };
9FC2C87C76F74B609BA3709E /* Poppins-ExtraLightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9420B3EA5C884AFFADCAFB4C /* Poppins-ExtraLightItalic.ttf */; };
9FDEB3BC9BEB0D9B20C40FFA /* libPods-app-appTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 198E5E1047B7B7C1FE9480E3 /* libPods-app-appTests.a */; };
A74B9EB17CB0D9069DBDEC11 /* libPods-app.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A849643D470B360BB9710A2 /* libPods-app.a */; };
B9CA13426DF0582117265C94 /* libPods-app-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 209E1A9D464732CA3D5436BA /* libPods-app-tvOS.a */; };
F8EEF49F61D796445B439296 /* libPods-app-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD654FF33034F72E23F45E0 /* libPods-app-tvOSTests.a */; };
6D069850985349ABB76BDD0A /* Poppins-Black.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B1A304CF782943E19F93DFD6 /* Poppins-Black.ttf */; };
417D579C3DC24A9684D24EDD /* Poppins-BlackItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 00FDE4C2522044DAAD866D5F /* Poppins-BlackItalic.ttf */; };
4E01D1C8DD5749C8953D3BE5 /* Poppins-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 661AE05493ED4FE780C32EF6 /* Poppins-Bold.ttf */; };
5257E7B8420740A2862BEEB0 /* Poppins-BoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8E2BDC4163F1467E94E5D34C /* Poppins-BoldItalic.ttf */; };
2DC4DBF14540481ABF14857A /* Poppins-ExtraBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9A9C27037F0F4AF2A4CBAE78 /* Poppins-ExtraBold.ttf */; };
9FA2A398A0264369AD50123F /* Poppins-ExtraBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9F1539AF1544412D9662D264 /* Poppins-ExtraBoldItalic.ttf */; };
7B94D8768C6340958F2EEFFD /* Poppins-ExtraLight.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C9AC377D6B7744ECBBEA0A8A /* Poppins-ExtraLight.ttf */; };
9FC2C87C76F74B609BA3709E /* Poppins-ExtraLightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 9420B3EA5C884AFFADCAFB4C /* Poppins-ExtraLightItalic.ttf */; };
50AAEBD19BD24B129801C393 /* Poppins-Italic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 977D264ECA05485EA89A2F13 /* Poppins-Italic.ttf */; };
71436C48CDF348BE904103E9 /* Poppins-Light.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 231B760691854EBCA7ECD80E /* Poppins-Light.ttf */; };
836E1F3164D8497F8C51847A /* Poppins-LightItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7CEBAD12E8E5471AA962262A /* Poppins-LightItalic.ttf */; };
230E5925A9044F679B2F7C39 /* Poppins-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 7AF5677EA52D44EE97668E7A /* Poppins-Medium.ttf */; };
04B5A89762F9430A902416B4 /* Poppins-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3CBFE8DBE8994C1FAF939519 /* Poppins-MediumItalic.ttf */; };
BB34E03413EB4CDFA59766B1 /* Poppins-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EB6EB5293E4C48098CBD53C2 /* Poppins-Regular.ttf */; };
8F8E6DB2A0AB4DF3A5ED51E7 /* Poppins-SemiBold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 920F53F06E0B433DB798AC45 /* Poppins-SemiBold.ttf */; };
41DE97CF96674977BC052462 /* Poppins-SemiBoldItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A9CB7679337840D3BD256A13 /* Poppins-SemiBoldItalic.ttf */; };
AC5E863985C04D72B5806B58 /* Poppins-Thin.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 02B78F6C72354B2D8171239D /* Poppins-Thin.ttf */; };
4F931CB3182F40F580F357FF /* Poppins-ThinItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E251C7BB03F74441B4357419 /* Poppins-ThinItalic.ttf */; };
B9CA13426DF0582117265C94 /* libPods-app-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 209E1A9D464732CA3D5436BA /* libPods-app-tvOS.a */; };
BB34E03413EB4CDFA59766B1 /* Poppins-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = EB6EB5293E4C48098CBD53C2 /* Poppins-Regular.ttf */; };
F8EEF49F61D796445B439296 /* libPods-app-tvOSTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DAD654FF33034F72E23F45E0 /* libPods-app-tvOSTests.a */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -62,6 +62,8 @@
00E356EE1AD99517003FC87E /* appTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = appTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
00E356F21AD99517003FC87E /* appTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = appTests.m; sourceTree = "<group>"; };
00FDE4C2522044DAAD866D5F /* Poppins-BlackItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-BlackItalic.ttf"; path = "../assets/fonts/Poppins-BlackItalic.ttf"; sourceTree = "<group>"; };
02B78F6C72354B2D8171239D /* Poppins-Thin.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-Thin.ttf"; path = "../assets/fonts/Poppins-Thin.ttf"; sourceTree = "<group>"; };
13B07F961A680F5B00A75B9A /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = app/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AppDelegate.m; path = app/AppDelegate.m; sourceTree = "<group>"; };
@ -71,38 +73,36 @@
198E5E1047B7B7C1FE9480E3 /* libPods-app-appTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app-appTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
1A849643D470B360BB9710A2 /* libPods-app.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app.a"; sourceTree = BUILT_PRODUCTS_DIR; };
209E1A9D464732CA3D5436BA /* libPods-app-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
231B760691854EBCA7ECD80E /* Poppins-Light.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-Light.ttf"; path = "../assets/fonts/Poppins-Light.ttf"; sourceTree = "<group>"; };
2D02E47B1E0B4A5D006451C7 /* app-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "app-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; };
2D02E4901E0B4A5D006451C7 /* app-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "app-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
3CBFE8DBE8994C1FAF939519 /* Poppins-MediumItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-MediumItalic.ttf"; path = "../assets/fonts/Poppins-MediumItalic.ttf"; sourceTree = "<group>"; };
4F9213F90B49965FDCF003FA /* Pods-app-appTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.release.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.release.xcconfig"; sourceTree = "<group>"; };
55AC07BDE5542A03F6AD2FE2 /* Pods-app.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.release.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.release.xcconfig"; sourceTree = "<group>"; };
64A4BC0C882E097B17B89CAB /* Pods-app-tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-tvOSTests.debug.xcconfig"; path = "Target Support Files/Pods-app-tvOSTests/Pods-app-tvOSTests.debug.xcconfig"; sourceTree = "<group>"; };
661AE05493ED4FE780C32EF6 /* Poppins-Bold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-Bold.ttf"; path = "../assets/fonts/Poppins-Bold.ttf"; sourceTree = "<group>"; };
78E0CED54136BD6995951328 /* Pods-app-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-tvOS.release.xcconfig"; path = "Target Support Files/Pods-app-tvOS/Pods-app-tvOS.release.xcconfig"; sourceTree = "<group>"; };
7AF5677EA52D44EE97668E7A /* Poppins-Medium.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-Medium.ttf"; path = "../assets/fonts/Poppins-Medium.ttf"; sourceTree = "<group>"; };
7CEBAD12E8E5471AA962262A /* Poppins-LightItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-LightItalic.ttf"; path = "../assets/fonts/Poppins-LightItalic.ttf"; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = app/LaunchScreen.storyboard; sourceTree = "<group>"; };
866817BF8B562CD61F42398B /* Pods-app-tvOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-tvOSTests.release.xcconfig"; path = "Target Support Files/Pods-app-tvOSTests/Pods-app-tvOSTests.release.xcconfig"; sourceTree = "<group>"; };
8E2BDC4163F1467E94E5D34C /* Poppins-BoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-BoldItalic.ttf"; path = "../assets/fonts/Poppins-BoldItalic.ttf"; sourceTree = "<group>"; };
920F53F06E0B433DB798AC45 /* Poppins-SemiBold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-SemiBold.ttf"; path = "../assets/fonts/Poppins-SemiBold.ttf"; sourceTree = "<group>"; };
9420B3EA5C884AFFADCAFB4C /* Poppins-ExtraLightItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-ExtraLightItalic.ttf"; path = "../assets/fonts/Poppins-ExtraLightItalic.ttf"; sourceTree = "<group>"; };
977D264ECA05485EA89A2F13 /* Poppins-Italic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-Italic.ttf"; path = "../assets/fonts/Poppins-Italic.ttf"; sourceTree = "<group>"; };
9A9C27037F0F4AF2A4CBAE78 /* Poppins-ExtraBold.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-ExtraBold.ttf"; path = "../assets/fonts/Poppins-ExtraBold.ttf"; sourceTree = "<group>"; };
9F1539AF1544412D9662D264 /* Poppins-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-ExtraBoldItalic.ttf"; path = "../assets/fonts/Poppins-ExtraBoldItalic.ttf"; sourceTree = "<group>"; };
A9CB7679337840D3BD256A13 /* Poppins-SemiBoldItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-SemiBoldItalic.ttf"; path = "../assets/fonts/Poppins-SemiBoldItalic.ttf"; sourceTree = "<group>"; };
AB8B9EC2CC8BC79836F11661 /* Pods-app-tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-tvOS.debug.xcconfig"; path = "Target Support Files/Pods-app-tvOS/Pods-app-tvOS.debug.xcconfig"; sourceTree = "<group>"; };
B1A304CF782943E19F93DFD6 /* Poppins-Black.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-Black.ttf"; path = "../assets/fonts/Poppins-Black.ttf"; sourceTree = "<group>"; };
C9AC377D6B7744ECBBEA0A8A /* Poppins-ExtraLight.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-ExtraLight.ttf"; path = "../assets/fonts/Poppins-ExtraLight.ttf"; sourceTree = "<group>"; };
D9ABD588946E114F0E0A649D /* Pods-app.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app.debug.xcconfig"; path = "Target Support Files/Pods-app/Pods-app.debug.xcconfig"; sourceTree = "<group>"; };
DAD654FF33034F72E23F45E0 /* libPods-app-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-app-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
DE49AE0B25BD2EF3496BCE37 /* Pods-app-appTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-app-appTests.debug.xcconfig"; path = "Target Support Files/Pods-app-appTests/Pods-app-appTests.debug.xcconfig"; sourceTree = "<group>"; };
E251C7BB03F74441B4357419 /* Poppins-ThinItalic.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-ThinItalic.ttf"; path = "../assets/fonts/Poppins-ThinItalic.ttf"; sourceTree = "<group>"; };
EB6EB5293E4C48098CBD53C2 /* Poppins-Regular.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = "Poppins-Regular.ttf"; path = "../assets/fonts/Poppins-Regular.ttf"; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
ED2971642150620600B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS12.0.sdk/System/Library/Frameworks/JavaScriptCore.framework; sourceTree = DEVELOPER_DIR; };
B1A304CF782943E19F93DFD6 /* Poppins-Black.ttf */ = {isa = PBXFileReference; name = "Poppins-Black.ttf"; path = "../assets/fonts/Poppins-Black.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
00FDE4C2522044DAAD866D5F /* Poppins-BlackItalic.ttf */ = {isa = PBXFileReference; name = "Poppins-BlackItalic.ttf"; path = "../assets/fonts/Poppins-BlackItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
661AE05493ED4FE780C32EF6 /* Poppins-Bold.ttf */ = {isa = PBXFileReference; name = "Poppins-Bold.ttf"; path = "../assets/fonts/Poppins-Bold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
8E2BDC4163F1467E94E5D34C /* Poppins-BoldItalic.ttf */ = {isa = PBXFileReference; name = "Poppins-BoldItalic.ttf"; path = "../assets/fonts/Poppins-BoldItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
9A9C27037F0F4AF2A4CBAE78 /* Poppins-ExtraBold.ttf */ = {isa = PBXFileReference; name = "Poppins-ExtraBold.ttf"; path = "../assets/fonts/Poppins-ExtraBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
9F1539AF1544412D9662D264 /* Poppins-ExtraBoldItalic.ttf */ = {isa = PBXFileReference; name = "Poppins-ExtraBoldItalic.ttf"; path = "../assets/fonts/Poppins-ExtraBoldItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
C9AC377D6B7744ECBBEA0A8A /* Poppins-ExtraLight.ttf */ = {isa = PBXFileReference; name = "Poppins-ExtraLight.ttf"; path = "../assets/fonts/Poppins-ExtraLight.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
9420B3EA5C884AFFADCAFB4C /* Poppins-ExtraLightItalic.ttf */ = {isa = PBXFileReference; name = "Poppins-ExtraLightItalic.ttf"; path = "../assets/fonts/Poppins-ExtraLightItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
977D264ECA05485EA89A2F13 /* Poppins-Italic.ttf */ = {isa = PBXFileReference; name = "Poppins-Italic.ttf"; path = "../assets/fonts/Poppins-Italic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
231B760691854EBCA7ECD80E /* Poppins-Light.ttf */ = {isa = PBXFileReference; name = "Poppins-Light.ttf"; path = "../assets/fonts/Poppins-Light.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
7CEBAD12E8E5471AA962262A /* Poppins-LightItalic.ttf */ = {isa = PBXFileReference; name = "Poppins-LightItalic.ttf"; path = "../assets/fonts/Poppins-LightItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
7AF5677EA52D44EE97668E7A /* Poppins-Medium.ttf */ = {isa = PBXFileReference; name = "Poppins-Medium.ttf"; path = "../assets/fonts/Poppins-Medium.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
3CBFE8DBE8994C1FAF939519 /* Poppins-MediumItalic.ttf */ = {isa = PBXFileReference; name = "Poppins-MediumItalic.ttf"; path = "../assets/fonts/Poppins-MediumItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
EB6EB5293E4C48098CBD53C2 /* Poppins-Regular.ttf */ = {isa = PBXFileReference; name = "Poppins-Regular.ttf"; path = "../assets/fonts/Poppins-Regular.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
920F53F06E0B433DB798AC45 /* Poppins-SemiBold.ttf */ = {isa = PBXFileReference; name = "Poppins-SemiBold.ttf"; path = "../assets/fonts/Poppins-SemiBold.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
A9CB7679337840D3BD256A13 /* Poppins-SemiBoldItalic.ttf */ = {isa = PBXFileReference; name = "Poppins-SemiBoldItalic.ttf"; path = "../assets/fonts/Poppins-SemiBoldItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
02B78F6C72354B2D8171239D /* Poppins-Thin.ttf */ = {isa = PBXFileReference; name = "Poppins-Thin.ttf"; path = "../assets/fonts/Poppins-Thin.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
E251C7BB03F74441B4357419 /* Poppins-ThinItalic.ttf */ = {isa = PBXFileReference; name = "Poppins-ThinItalic.ttf"; path = "../assets/fonts/Poppins-ThinItalic.ttf"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -235,7 +235,7 @@
sourceTree = "<group>";
};
86DE8D5EB35F4504B427A63A /* Resources */ = {
isa = "PBXGroup";
isa = PBXGroup;
children = (
B1A304CF782943E19F93DFD6 /* Poppins-Black.ttf */,
00FDE4C2522044DAAD866D5F /* Poppins-BlackItalic.ttf */,
@ -257,8 +257,8 @@
E251C7BB03F74441B4357419 /* Poppins-ThinItalic.ttf */,
);
name = Resources;
sourceTree = "<group>";
path = "";
sourceTree = "<group>";
};
/* End PBXGroup section */
@ -975,7 +975,7 @@
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 i386";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
@ -1036,7 +1036,7 @@
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386;
"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 i386";
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

View File

@ -22,7 +22,7 @@
"@react-native-community/blur": "3.6.0",
"@react-native-community/cookies": "5.0.1",
"@react-native-community/datetimepicker": "3.4.3",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/masked-view": "^0.1.11",
"@react-navigation/bottom-tabs": "5.11.9",
"@react-navigation/native": "5.9.4",
"@react-navigation/stack": "5.14.4",
@ -47,13 +47,14 @@
"react-native-appearance": "^0.3.4",
"react-native-calendar-events": "2.2.0",
"react-native-fix-image": "2.1.0",
"react-native-gesture-handler": "1.10.3",
"react-native-gesture-handler": "^1.10.3",
"react-native-localize": "^2.0.2",
"react-native-markdown-display": "7.0.0-alpha.2",
"react-native-modal-datetime-picker": "9.2.0",
"react-native-reanimated": "^2.2.0",
"react-native-restart": "^0.0.22",
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "2.18.1",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^3.3.0",
"react-native-simple-toast": "1.1.3",
"react-native-svg": "12.1.0",
"react-native-svg-transformer": "0.14.3",
@ -70,8 +71,8 @@
"@babel/runtime": "^7.13.10",
"@react-native-community/eslint-config": "^2.0.0",
"@testing-library/jest-native": "^4.0.1",
"@testing-library/react-hooks": "^5.1.1",
"@testing-library/react-native": "7.1.0",
"@testing-library/react-hooks": "^7.0.0",
"@testing-library/react-native": "7.2.0",
"@types/i18n-js": "^3.8.0",
"@types/jest": "^26.0.22",
"@types/jsuri": "^1.3.30",

View File

@ -1,7 +1,7 @@
import 'react-native-gesture-handler/jestSetup'
import mockAsyncStorage from '@react-native-async-storage/async-storage/jest/async-storage-mock'
import moment from 'moment'
import 'moment/locale/sv'
import 'react-native-gesture-handler/jestSetup'
moment.locale('sv')
@ -9,3 +9,13 @@ jest.mock('@react-native-async-storage/async-storage', () => mockAsyncStorage)
// Silence useNativeDriver error
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper')
jest.mock('react-native-reanimated', () => {
const Reanimated = require('react-native-reanimated/mock')
// The mock for `call` immediately calls the callback which is incorrect
// So we override it with a no-op
Reanimated.default.call = () => {}
return Reanimated
})

View File

@ -1,6 +1,7 @@
import { StyleService, useStyleSheet } from '@ui-kitten/components'
import React from 'react'
import { SafeAreaView as RNSafeAreaView, ViewProps } from 'react-native'
import { ViewProps } from 'react-native'
import { SafeAreaView as RNSafeAreaView } from 'react-native-safe-area-context'
import { Layout } from '../styles'
export const SafeAreaView: React.FC<ViewProps> = ({ children }) => {
@ -12,6 +13,6 @@ export const SafeAreaView: React.FC<ViewProps> = ({ children }) => {
const themedStyles = StyleService.create({
safeArea: {
...Layout.flex.full,
backgroundColor: 'background-basic-color-1',
backgroundColor: 'background-basic-color-2',
},
})

View File

@ -1,6 +1,7 @@
import {
fullName,
guardians,
initials,
sortByFirstName,
studentName,
} from '../peopleHelpers'
@ -72,3 +73,13 @@ describe('#guardians', () => {
).toEqual('Loras Eriksson, Margaery Eriksson')
})
})
describe('#initials', () => {
test('should extract initials from name', () => {
expect(initials('Namn Namnsson')).toEqual('Na')
expect(initials('Nisse Namnsson')).toEqual('Ni')
})
test('handles undefined name', () => {
expect(initials(undefined)).toBeUndefined()
})
})

View File

@ -1,9 +0,0 @@
export const studentName = (name) => name?.replace(/\s?\(\w+\)$/, '')
export const sortByFirstName = (data) =>
data.sort((a, b) => a.firstname.localeCompare(b.firstname))
export const guardians = (data) =>
sortByFirstName(data).map(fullName).join(', ')
export const fullName = (person) => `${person.firstname} ${person.lastname}`

View File

@ -0,0 +1,16 @@
import { Guardian } from '@skolplattformen/embedded-api'
export const studentName = (name?: string) => name?.replace(/\s?\(\w+\)$/, '')
export const sortByFirstName = (data: Guardian[]) =>
data.sort((a, b) => a.firstname.localeCompare(b.firstname))
export const guardians = (data: Guardian[]) =>
sortByFirstName(data).map(fullName).join(', ')
export const fullName = (person: Guardian) =>
`${person.firstname} ${person.lastname}`
export const initials = (name?: string) => {
return name?.slice(0, 2)
}

View File

@ -173,6 +173,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz#806526ce125aed03373bc416a828321e3a6a33af"
integrity sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==
"@babel/helper-plugin-utils@^7.14.5":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9"
integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==
"@babel/helper-remap-async-to-generator@^7.13.0":
version "7.13.0"
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz#376a760d9f7b4b2077a9dd05aa9c3927cadb2209"
@ -546,6 +551,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.12.13"
"@babel/plugin-transform-object-assign@^7.10.4":
version "7.14.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-assign/-/plugin-transform-object-assign-7.14.5.tgz#62537d54b6d85de04f4df48bfdba2eebff17b760"
integrity sha512-lvhjk4UN9xJJYB1mI5KC0/o1D5EcJXdbhVe+4fSk08D6ZN+iuAIs7LJC+71h8av9Ew4+uRq9452v9R93SFmQlQ==
dependencies:
"@babel/helper-plugin-utils" "^7.14.5"
"@babel/plugin-transform-object-super@^7.0.0":
version "7.12.13"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz#b4416a2d63b8f7be314f3d349bd55a9c1b5171f7"
@ -1360,10 +1372,10 @@
resolved "https://registry.yarnpkg.com/@react-native-community/eslint-plugin/-/eslint-plugin-1.1.0.tgz#e42b1bef12d2415411519fd528e64b593b1363dc"
integrity sha512-W/J0fNYVO01tioHjvYWQ9m6RgndVtbElzYozBq1ZPrHO/iCzlqoySHl4gO/fpCl9QEFjvJfjPgtPMTMlsoq5DQ==
"@react-native-community/masked-view@0.1.10":
version "0.1.10"
resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.10.tgz#5dda643e19e587793bc2034dd9bf7398ad43d401"
integrity sha512-rk4sWFsmtOw8oyx8SD3KSvawwaK7gRBSEIy2TAwURyGt+3TizssXP1r8nx3zY+R7v2vYYHXZ+k2/GULAT/bcaQ==
"@react-native-community/masked-view@^0.1.11":
version "0.1.11"
resolved "https://registry.yarnpkg.com/@react-native-community/masked-view/-/masked-view-0.1.11.tgz#2f4c6e10bee0786abff4604e39a37ded6f3980ce"
integrity sha512-rQfMIGSR/1r/SyN87+VD8xHHzDYeHaJq6elOSCAD+0iLagXkSI2pfA0LmSXP21uw5i3em7GkkRjfJ8wpqWXZNw==
"@react-native/assets@1.0.0":
version "1.0.0"
@ -1583,22 +1595,21 @@
ramda "^0.26.1"
redent "^2.0.0"
"@testing-library/react-hooks@^5.1.1":
version "5.1.2"
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-5.1.2.tgz#36e359d992bb652a9885c6fa9aa394639cbe8dd3"
integrity sha512-jwhtDYZ5gQUIX8cmVCVdtwNvuF5EiCOWjokRlTV+o/V0GdtRZDykUllL1OXq5PS4+J33wGLNQeeWzEHcWrH7tg==
"@testing-library/react-hooks@^7.0.0":
version "7.0.0"
resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-7.0.0.tgz#dd6d37a7e018f147a3b9153137f10e013be8472b"
integrity sha512-WFBGH8DWdIGGBHt6PBtQPe2v4Kbj9vQ1sQ9qLBTmwn1PNggngint4MTE/IiWCYhPbyTW3oc/7X62DObMn/AjQQ==
dependencies:
"@babel/runtime" "^7.12.5"
"@types/react" ">=16.9.0"
"@types/react-dom" ">=16.9.0"
"@types/react-test-renderer" ">=16.9.0"
filter-console "^0.1.1"
react-error-boundary "^3.1.0"
"@testing-library/react-native@7.1.0":
version "7.1.0"
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-7.1.0.tgz#6b168aac21522c8a5175461b350336fd79612ac9"
integrity sha512-ljVM9KZqG7BT/NFN6CHzdF6MNmM28+k7MEybFJ7FW1wVrhpiY4+hU9ypZ+hboO+MG3KpE2aFBzP4od3gu+Zzdg==
"@testing-library/react-native@7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@testing-library/react-native/-/react-native-7.2.0.tgz#e5ec5b0974e4e5f525f8057563417d1e9f820d96"
integrity sha512-rDKzJjAAeGgyoJT0gFQiMsIL09chdWcwZyYx6WZHMgm2c5NDqY52hUuyTkzhqddMYWmSRklFphSg7B2HX+246Q==
dependencies:
pretty-format "^26.0.1"
@ -4007,11 +4018,6 @@ fill-range@^7.0.1:
dependencies:
to-regex-range "^5.0.1"
filter-console@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/filter-console/-/filter-console-0.1.1.tgz#6242be28982bba7415bcc6db74a79f4a294fa67c"
integrity sha512-zrXoV1Uaz52DqPs+qEwNJWJFAWZpYJ47UNmpN9q4j+/EYsz85uV0DC9k8tRND5kYmoVzL0W+Y75q4Rg8sRJCdg==
filter-obj@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b"
@ -6671,7 +6677,7 @@ mkdirp@^0.5.1, mkdirp@~0.5.1:
dependencies:
minimist "^1.2.5"
mockdate@^3.0.5:
mockdate@^3.0.2, mockdate@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb"
integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==
@ -7594,7 +7600,7 @@ react-native-fix-image@2.1.0:
resolved "https://registry.yarnpkg.com/react-native-fix-image/-/react-native-fix-image-2.1.0.tgz#a4677b91529be926544775faf736df6af8c4743c"
integrity sha512-qn4xItNSKfwlSkMHgxH9cusUQuk5Lhag9aHqL9ESxnCDUDwqS6rsWuOYt/JlncaKhsCc9cANFomCLlY8+Ffuaw==
react-native-gesture-handler@1.10.3:
react-native-gesture-handler@^1.10.3:
version "1.10.3"
resolved "https://registry.yarnpkg.com/react-native-gesture-handler/-/react-native-gesture-handler-1.10.3.tgz#942bbf2963bbf49fa79593600ee9d7b5dab3cfc0"
integrity sha512-cBGMi1IEsIVMgoox4RvMx7V2r6bNKw0uR1Mu1o7NbuHS6BRSVLq0dP34l2ecnPlC+jpWd3le6Yg1nrdCjby2Mw==
@ -7632,20 +7638,30 @@ react-native-modal-datetime-picker@9.2.0:
dependencies:
prop-types "^15.7.2"
react-native-reanimated@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-2.2.0.tgz#a6412c56b4e591d1f00fac949f62d0c72c357c78"
integrity sha512-lOJDd+5w1gY6DHGXG2jD1dsjzQmXQ2699HUc3IztvI2WP4zUT+UAA+zSG+5JiBS5DUnTL8YhhkmUQmr1KNGO5w==
dependencies:
"@babel/plugin-transform-object-assign" "^7.10.4"
fbjs "^3.0.0"
mockdate "^3.0.2"
string-hash-64 "^1.0.3"
react-native-restart@^0.0.22:
version "0.0.22"
resolved "https://registry.yarnpkg.com/react-native-restart/-/react-native-restart-0.0.22.tgz#81fcb7f31e35951d85410c68b9556acf3ab88705"
integrity sha512-XwCqAMAKsO8yCM3xACPFKvkDQZe41lcavOuO0gUEu803IuTLtciualCq/qs83ryRDCDh1jkXYRqFjsGjLMCN3Q==
react-native-safe-area-context@3.2.0:
react-native-safe-area-context@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-3.2.0.tgz#06113c6b208f982d68ab5c3cebd199ca93db6941"
integrity sha512-k2Nty4PwSnrg9HwrYeeE+EYqViYJoOFwEy9LxL5RIRfoqxAq/uQXNGwpUg2/u4gnKpBbEPa9eRh15KKMe/VHkA==
react-native-screens@2.18.1:
version "2.18.1"
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-2.18.1.tgz#47b9991c6f762d00d0ed3233e5283d523e859885"
integrity sha512-r5WZLpmx2hHjC1RgMdPq5YpSU9tEhBpUaZ5M1SUtNIONyiLqQVxabhRCINdebIk4depJiIl7yw2Q85zJyeX6fw==
react-native-screens@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-3.3.0.tgz#d4464a96620b85d09e46bd6865b5f48456c244f0"
integrity sha512-ni11jC6I9cFVXdLIDwkgafDHw/STXUNzkR5Fx3w8Wikdzi8gfTEan2kiOm7aS42d2F/LXddZ6i74Z2em0L6LPQ==
react-native-simple-toast@1.1.3:
version "1.1.3"
@ -8596,6 +8612,11 @@ strict-uri-encode@^2.0.0:
resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546"
integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY=
string-hash-64@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/string-hash-64/-/string-hash-64-1.0.3.tgz#0deb56df58678640db5c479ccbbb597aaa0de322"
integrity sha512-D5OKWKvDhyVWWn2x5Y9b+37NUllks34q1dCDhk/vYcso9fmhs+Tl3KR/gE4v5UNj2UA35cnX4KdVVGkG1deKqw==
string-length@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837"