/** * Copyright (c) 2020 INESC TEC <https://www.inesctec.pt> * * This Source Code Form is subject to the terms of the European Union * Public License, v. 1.2. If a copy of the EUPL was not distributed with * this file, You can obtain one at https://opensource.org/licenses/EUPL-1.2. * * SPDX-License-Identifier: EUPL-1.2 */ import React, { useMemo } from 'react'; import { View, StyleSheet, Platform, ImageBackground } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import PropTypes from 'prop-types'; import { useTheme } from '@app/contexts/Theme'; import TopComponent from '@app/common/components/TopComponent'; import Layout from '@app/common/components/Layout'; import Icon from '@app/common/components/Icon'; import Button from '@app/common/components/Button'; import ButtonWrapper from '@app/common/components/ButtonWrapper'; import Text from '@app/common/components/FormattedText'; import { images } from '@app/common/assets/images'; import SupportIcon from '@app/common/components/SupportIcon'; import { colors as commonColors, sizes, iconSizes } from '@app/common/theme'; import i18n from '@app/services/i18n'; import { INFECTION_STATUS } from '@app/services/tracing'; const styles = (colors, insets) => StyleSheet.create({ imageContainer: { height: 300, resizeMode: "cover", }, contentContainer: { flex: 1, paddingBottom: sizes.size48, }, updateContainer: { alignItems: 'flex-end', marginRight: sizes.size8, marginBottom: sizes.size8, }, settingsButton: { alignSelf: 'flex-end', marginTop: Platform.select({ android: sizes.size24, ios: insets.top + sizes.size24, }), marginRight: sizes.size24, borderRadius: sizes.size48, padding: sizes.size8, backgroundColor: colors.iconMainBackgroundColor, }, shareButton: { alignSelf: 'flex-end', marginTop: sizes.size16, marginRight: sizes.size24 + sizes.size6, borderRadius: sizes.size48, padding: sizes.size8, backgroundColor: colors.iconAltBackgroundColor, }, header: { marginTop: -100, }, backgroundPanel: { opacity: 0.93, position: 'absolute', width: '100%', height: '100%', borderRadius: sizes.size8, shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, paddingHorizontal: sizes.size24, paddingVertical: sizes.size24, }, panel: { backgroundColor: colors.transparent, borderRadius: sizes.size8, shadowOffset: { width: 0, height: 2, }, shadowOpacity: 0.25, shadowRadius: 3.84, elevation: 5, }, panelContainer: { paddingHorizontal: sizes.size24, paddingTop: sizes.size24, paddingBottom: sizes.size24 + iconSizes.size30, }, supportContainer: { marginTop: -sizes.size10 - (iconSizes.size30 / 2), marginHorizontal: sizes.size24, }, descriptionsContent: { marginTop: sizes.size24, }, messageContainer: { marginBottom: sizes.size24, }, message: { }, submessage: { marginTop: sizes.size8, }, titleContainer: { flexDirection: 'row', justifyContent: 'center', alignContent: 'center', marginBottom: sizes.size16, }, iconTitle: { alignSelf: 'center', marginLeft: sizes.size12, }, errorPanel: { marginBottom: sizes.size24 + sizes.size24, }, errorPanelContainer: { flexDirection: 'column', justifyContent: 'center', alignItems: 'flex-start', paddingHorizontal: sizes.size24, paddingVertical: sizes.size24, }, settingsButtonContainer: { top: Platform.select({ android: sizes.size24, ios: 0, }), position: 'absolute', zIndex: 100, width: '100%', height: '100%', }, backdropContainer: { top: 0, position: 'absolute', zIndex: 50, width: '100%', height: '100%', backgroundColor: colors.backdropColor, opacity: 0.8, }, errorsContainer: { position: 'absolute', zIndex: 100, marginTop: insets.top + sizes.size24 + iconSizes.size32 + sizes.size8 * 2 + sizes.size16 + iconSizes.size20 + sizes.size8 * 2 + sizes.size16, width: '100%', height: '100%', }, errorsLayout: { flex: 0, backgroundColor: colors.transparent, }, errorButton: { alignSelf: 'center', width: '100%', marginBottom: -sizes.size16, }, errorAlternativeButton: { marginTop: sizes.size16 + sizes.size8, marginBottom: -sizes.size16 * 2, alignSelf: 'center', width: '100%', }, }); function renderError(...args) { const [ error, colors, style, ] = args; return ( <> <View style={style.backdropContainer} /> <View style={style.errorsContainer}> <Layout padding='horizontal' style={style.errorsLayout} > <View style={style.content}> <View> <View style={{ ...style.backgroundPanel, backgroundColor: colors.panelWhiteBackgroundColor, }} /> <View style={style.panel}> <View style={style.errorPanel}> <View style={style.errorPanelContainer}> <View style={style.titleContainer}> { error.icon } <Text size='large' weight='bold' textColor={colors.panelWhiteTextColor} style={style.iconTitle}>{error.title}</Text> </View> <View style={style.messageContainer}> <Text textColor={colors.panelWhiteTextColor} style={style.message}>{error.message}</Text> { error.submessage && <Text size='small' textColor={colors.panelWhiteTextColor} style={style.submessage}>{error.submessage}</Text> } </View> <Button title={error.main.label} accessibilityLabel={error.main.accessibility.label} accessibilityHint={error.main.accessibility.hint} containerStyle={style.errorButton} onPress={error.main.onPress} /> { error.alternative && <Button alternative title={error.alternative.label} accessibilityLabel={error.alternative.accessibility.label} accessibilityHint={error.alternative.accessibility.hint} containerStyle={style.errorAlternativeButton} onPress={error.alternative.onPress} /> } </View> </View> </View> </View> <View style={style.supportContainer}> <SupportIcon /> </View> </View> </Layout> </View> </> ); } export default function Template (props) { const { header, description, image, panelBackgroundColor, panelTextColor, lastSync, onPressSettings, onLongPressSettings, onPressShare, error, infectionStatus, } = props; const showUpdatedAt = infectionStatus !== INFECTION_STATUS.INFECTED; const hasUpdated = lastSync !== 0; const insets = useSafeAreaInsets(); const { name, colors } = useTheme(); const memoizedStyle = useMemo(() => styles(colors, insets), [name, insets]); return ( <TopComponent> <View style={memoizedStyle.settingsButtonContainer} pointerEvents='box-none'> <ButtonWrapper onPress={onPressSettings} onLongPress={onLongPressSettings} style={memoizedStyle.settingsButton} accessibilityLabel={i18n.translate('screens.home.actions.settings.accessibility.label')} accessibilityHint={i18n.translate('screens.home.actions.settings.accessibility.hint')} > <Icon name='settings' width={iconSizes.size32} height={iconSizes.size32} /> </ButtonWrapper> <ButtonWrapper onPress={onPressShare} style={memoizedStyle.shareButton} accessibilityLabel={i18n.translate('screens.home.actions.share.accessibility.label')} accessibilityHint={i18n.translate('screens.home.actions.share.accessibility.hint')} > <Icon name='share' width={iconSizes.size20} height={iconSizes.size20} /> </ButtonWrapper> </View> { error.status && renderError(error, colors, memoizedStyle) } <View style={memoizedStyle.homeContainer}> <ImageBackground testID="home_image_background" source={image} style={memoizedStyle.imageContainer} /> <Layout padding='horizontal' style={memoizedStyle.contentContainer} > <View style={memoizedStyle.header}> <View> <View style={{ ...memoizedStyle.backgroundPanel, backgroundColor: panelBackgroundColor, }} /> <View style={memoizedStyle.panel}> <View style={memoizedStyle.panelContainer}> <Text size='xlarge' weight='bold' textColor={panelTextColor}>{header}</Text> </View> </View> </View> <View style={memoizedStyle.supportContainer}> { showUpdatedAt && hasUpdated && <SupportIcon label={i18n.translate('screens.home.last_updated')} content={lastSync.format('L')} borderColor={panelBackgroundColor} /> } { showUpdatedAt && !hasUpdated && <SupportIcon content={i18n.translate('screens.home.never_updated')} borderColor={panelBackgroundColor} /> } { ! showUpdatedAt && <SupportIcon /> } </View> </View> <Text style={memoizedStyle.descriptionsContent}> {description} </Text> </Layout> </View> </TopComponent> ); } Template.defaultProps = { header: '', description: [], lastSync: 0, onPressSettings: () => {}, onPressShare: () => {}, onLongPressSettings: () => {}, panelBackgroundColor: '', panelTextColor: '', error: { status: false, title: '', message: '', icon: undefined, main: { accessibility: { label: '', hint: '', }, onPress: () => {}, }, alternative: { accessibility: { label: '', hint: '', }, onPress: () => {}, }, }, infectionStatus: 0, }; Template.propTypes = { header: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), description: PropTypes.oneOfType([PropTypes.array, PropTypes.string]), onPressSettings: PropTypes.func, onPressShare: PropTypes.func, onLongPressSettings: PropTypes.func, panelBackgroundColor: PropTypes.oneOf(['', ...commonColors]), panelTextColor: PropTypes.oneOf(['', ...commonColors]), lastSync: PropTypes.oneOfType([PropTypes.object, PropTypes.number]), image: PropTypes.oneOf(Object.values(images)).isRequired, error: PropTypes.shape({ status: PropTypes.bool, title: PropTypes.string, message: PropTypes.string, submessage: PropTypes.string, icon: PropTypes.element, main: PropTypes.shape({ label: PropTypes.string, accessibility: PropTypes.object, onPress: PropTypes.func, }), alternative: PropTypes.shape({ label: PropTypes.string, accessibility: PropTypes.object, onPress: PropTypes.func, }), }), infectionStatus: PropTypes.number, };