Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LUN-400: Image for own vocabulary #377

Merged
merged 12 commits into from
Oct 11, 2022
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.content.Context;
ztefanie marked this conversation as resolved.
Show resolved Hide resolved
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.rnfs.RNFSPackage;
import com.facebook.react.modules.i18nmanager.I18nUtil;
import com.reactnativecommunity.asyncstorage.AsyncStoragePackage;
import com.johnsonsu.rnsoundplayer.RNSoundPlayerPackage;
Expand Down
2 changes: 2 additions & 0 deletions android/settings.gradle
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
rootProject.name = 'lunes-app-android'
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')

apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)

Expand Down
1 change: 1 addition & 0 deletions assets/images/circle-icon-white.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions assets/images/image-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions assets/images/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import CheckCloseCircleIconBold from './check-close-circle-icon-bold.svg'
import CheckCloseCircleIcon from './check-close-circle-icon.svg'
import ChevronRight from './chevron-right.svg'
import CircleIconBlue from './circle-icon-blue.svg'
import CircleIconWhite from './circle-icon-white.svg'
ztefanie marked this conversation as resolved.
Show resolved Hide resolved
import CloseCircleIconBlue from './close-circle-icon-blue.svg'
import CloseCircleIconBold from './close-circle-icon-bold.svg'
import CloseCircleIconWhite from './close-circle-icon-white.svg'
Expand All @@ -41,6 +42,7 @@ import HomeCircleIconWhite from './home-circle-icon-white.svg'
import HomeIconGrey from './home-icon-grey.svg'
import HomeIconWhite from './home-icon-white.svg'
import ImageCircleIcon from './image-circle-icon.svg'
import ImageIcon from './image-icon.svg'
import InfoCircleIcon from './info-circle-icon.svg'
import ListIcon from './list-icon.svg'
import LockIcon from './lock-icon.svg'
Expand Down Expand Up @@ -70,6 +72,8 @@ import VolumeUpCircleIcon from './volume-up-circle-icon.svg'

export {
AddCircleIcon,
CircleIconWhite,
ImageIcon,
AddIconWhite,
ArrowLeftCircleIconBlue,
ArrowLeftCircleIconWhite,
Expand Down
2 changes: 2 additions & 0 deletions ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ target 'Lunes' do
permissions_path = '../node_modules/react-native-permissions/ios'
pod 'Permission-Camera', :path => "#{permissions_path}/Camera"

pod 'RNFS', :path => '../node_modules/react-native-fs'

target 'LunesTests' do
inherit! :complete
# Pods for testing
Expand Down
8 changes: 7 additions & 1 deletion ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,8 @@ PODS:
- React-Core
- RNDeviceInfo (9.0.2):
- React-Core
- RNFS (2.20.0):
- React-Core
- RNGestureHandler (2.5.0):
- React-Core
- RNPermissions (3.4.0):
Expand Down Expand Up @@ -450,6 +452,7 @@ DEPENDENCIES:
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- "RNCMaskedView (from `../node_modules/@react-native-masked-view/masked-view`)"
- RNDeviceInfo (from `../node_modules/react-native-device-info`)
- RNFS (from `../node_modules/react-native-fs`)
- RNGestureHandler (from `../node_modules/react-native-gesture-handler`)
- RNPermissions (from `../node_modules/react-native-permissions`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
Expand Down Expand Up @@ -552,6 +555,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-masked-view/masked-view"
RNDeviceInfo:
:path: "../node_modules/react-native-device-info"
RNFS:
:path: "../node_modules/react-native-fs"
RNGestureHandler:
:path: "../node_modules/react-native-gesture-handler"
RNPermissions:
Expand Down Expand Up @@ -624,6 +629,7 @@ SPEC CHECKSUMS:
RNCAsyncStorage: d81ee5c3db1060afd49ea7045ad460eff82d2b7d
RNCMaskedView: c298b644a10c0c142055b3ae24d83879ecb13ccd
RNDeviceInfo: 1e3f62b9ec32f7754fac60bd06b8f8a27124e7f0
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNGestureHandler: bad495418bcbd3ab47017a38d93d290ebd406f50
RNPermissions: eac721f71748c4472d6e876038270b75397d6ee7
RNReanimated: 11e6e30bbc49079b7f0c321ac5830e6d113d0224
Expand All @@ -636,6 +642,6 @@ SPEC CHECKSUMS:
TextToSpeech: b3aa777ff5585705f179c0a2436bfd0926d1716e
Yoga: e7dc4e71caba6472ff48ad7d234389b91dadc280

PODFILE CHECKSUM: f0c4013aeb23d2c0b2b385496c8d12dc3493083b
PODFILE CHECKSUM: 5ce2b856afff013c4abae6206a72de31a844f8af

COCOAPODS: 1.11.3
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"react-native-camera": "^4.2.1",
"react-native-device-info": "^9.0.2",
"react-native-dropdown-picker": "^5.4.2",
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "^2.5.0",
"react-native-image-zoom-viewer": "^3.0.1",
"react-native-keyboard-aware-scroll-view": "^0.9.5",
Expand Down
1 change: 1 addition & 0 deletions src/__mocks__/react-native-fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = { readFile: jest.fn(() => Promise.resolve('image')) }
2 changes: 1 addition & 1 deletion src/components/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ const Button = (props: ButtonProps): ReactElement => {

const getBackgroundColor = (): Color | 'transparent' => {
if (disabled) {
return theme.colors.disabled
return buttonTheme === BUTTONS_THEME.text ? 'transparent' : theme.colors.disabled
}
if (isPressed) {
if (buttonTheme === BUTTONS_THEME.text) {
Expand Down
75 changes: 75 additions & 0 deletions src/components/CameraOverlay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { ReactElement, useEffect, useRef, useState } from 'react'
ztefanie marked this conversation as resolved.
Show resolved Hide resolved
import { AppState, Modal as RNModal, Platform } from 'react-native'
import { PERMISSIONS, request, RESULTS } from 'react-native-permissions'
import { widthPercentageToDP as wp } from 'react-native-responsive-screen'
import styled from 'styled-components/native'

import { CloseCircleIconBlue, CloseCircleIconWhite } from '../../assets/images'
import { reportError } from '../services/sentry'
import NotAuthorisedView from './NotAuthorisedView'

const Container = styled.SafeAreaView`
flex: 1;
background-color: ${props => props.theme.colors.background};
`

const Icon = styled.Pressable`
align-self: flex-end;
margin: ${props => `${props.theme.spacings.xs} ${props.theme.spacings.sm}`};
width: ${wp('7%')}px;
height: ${wp('7%')}px;
`

interface Props {
setVisible: (visible: boolean) => void
children: ReactElement
}

const CameraOverlay = ({ setVisible, children }: Props): ReactElement => {
const appState = useRef(AppState.currentState)

const [isPressed, setIsPressed] = useState<boolean>(false)
const [permissionRequested, setPermissionRequested] = useState<boolean>(false)
const [permissionGranted, setPermissionGranted] = useState<boolean>(false)

// Needed when navigating back from settings, when users selected "ask every time" as camera permission option
useEffect(() => {
if (!permissionRequested) {
request(Platform.OS === 'ios' ? PERMISSIONS.IOS.CAMERA : PERMISSIONS.ANDROID.CAMERA)
.then(result => setPermissionGranted(result === RESULTS.GRANTED))
.catch(reportError)
.finally(() => setPermissionRequested(true))
}
}, [permissionRequested])

useEffect(() => {
const subscription = AppState.addEventListener('change', nextAppState => {
if ((appState.current === 'inactive' || appState.current === 'background') && nextAppState === 'active') {
setPermissionRequested(false)
}
appState.current = nextAppState
})
return subscription.remove
}, [])

return (
<RNModal visible transparent animationType='fade' onRequestClose={() => setVisible(false)}>
<Container>
<Icon
onPress={() => setVisible(false)}
onPressIn={() => setIsPressed(true)}
onPressOut={() => setIsPressed(false)}>
{isPressed ? (
<CloseCircleIconBlue testID='close-circle-icon-blue' width={wp('7%')} height={wp('7%')} />
) : (
<CloseCircleIconWhite testID='close-circle-icon-white' width={wp('7%')} height={wp('7%')} />
)}
</Icon>
{permissionGranted && children}
{permissionRequested && !permissionGranted && <NotAuthorisedView setVisible={setVisible} />}
</Container>
</RNModal>
)
}

export default CameraOverlay
1 change: 1 addition & 0 deletions src/components/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ const Dropdown = <T extends ValueType>({
fontFamily: theme.fonts.contentFontRegular,
paddingHorizontal: wp('2%'),
}}
listMode='SCROLLVIEW'
/>
{showErrorValidation && (
<ErrorContainer>{errorMessage.length > 0 && <ContentError>{errorMessage}</ContentError>}</ErrorContainer>
Expand Down
1 change: 1 addition & 0 deletions src/components/ListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const Container = styled(GenericListItemContainer)<{
feedback: EXERCISE_FEEDBACK
}>`
min-height: ${hp('12%')}px;
width: 100%;
justify-content: center;
flex-direction: column;
border-top-width: 1px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import React, { ReactElement } from 'react'
import { Linking } from 'react-native'
import styled from 'styled-components/native'

import Button from '../../../components/Button'
import { ContentSecondary } from '../../../components/text/Content'
import { BUTTONS_THEME } from '../../../constants/data'
import { getLabels } from '../../../services/helpers'
import { reportError } from '../../../services/sentry'
import { BUTTONS_THEME } from '../constants/data'
import { getLabels } from '../services/helpers'
import { reportError } from '../services/sentry'
import Button from './Button'
import { ContentSecondary } from './text/Content'

const Container = styled.View`
display: flex;
Expand All @@ -30,12 +30,12 @@ const NotAuthorisedView = ({ setVisible }: NotAuthorizedViewProps): ReactElement
}

return (
<Container>
<Description>{getLabels().addCustomDiscipline.qrCodeScanner.noAuthorization.description}</Description>
<Container testID='no-auth'>
<Description>{getLabels().general.camera.noAuthorization.description}</Description>
<Button onPress={() => setVisible(false)} label={getLabels().general.back} buttonTheme={BUTTONS_THEME.outlined} />
<Button
onPress={openSettings}
label={getLabels().addCustomDiscipline.qrCodeScanner.noAuthorization.settings}
label={getLabels().general.camera.noAuthorization.settings}
buttonTheme={BUTTONS_THEME.contained}
/>
</Container>
Expand Down
37 changes: 37 additions & 0 deletions src/components/__tests__/CameraOverlay.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { fireEvent } from '@testing-library/react-native'
ztefanie marked this conversation as resolved.
Show resolved Hide resolved
import React from 'react'
import { Text } from 'react-native'

import render from '../../testing/render'
import CameraOverlay from '../CameraOverlay'

jest.mock('react-native-permissions', () => require('react-native-permissions/mock'))

describe('CameraOverlay', () => {
const setVisible = jest.fn()

it('should show close header with correct icon', async () => {
const { getByTestId, queryByTestId, findByTestId } = render(
<CameraOverlay setVisible={setVisible}>
<Text>Children</Text>
</CameraOverlay>
)
const closeIcon = await findByTestId('close-circle-icon-white')
expect(closeIcon).toBeDefined()
fireEvent(closeIcon, 'onPressIn')
expect(getByTestId('close-circle-icon-blue')).toBeDefined()
expect(queryByTestId('close-circle-icon-white')).toBeNull()
})

it('should close overlay on icon press', async () => {
const { findByTestId } = render(
<CameraOverlay setVisible={setVisible}>
<Text>Children</Text>
</CameraOverlay>
)
const closeIcon = await findByTestId('close-circle-icon-white')
expect(closeIcon).toBeDefined()
fireEvent.press(closeIcon)
expect(setVisible).toHaveBeenCalledWith(false)
})
})
21 changes: 11 additions & 10 deletions src/constants/labels.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,6 @@
"alreadyAdded": "Dieser Code wurde bereits erfolgreich hinzufügt.",
"wrongCode": "Dieser Code existiert nicht (ungültiger Code).",
"technical": "Ein technischer Fehler ist aufgetreten. Prüfen Sie Ihre Internetverbindung."
},
"qrCodeScanner": {
"noAuthorization": {
"description": "Kamerazugriff zum Scannen von QR-Codes erforderlich. Öffnen Sie die Einstellungen, tippen Sie auf Berechtigungen und tippen Sie auf Erlauben.",
"settings": "Einstellungen"
}
}
},
"exercises": {
Expand Down Expand Up @@ -164,7 +158,13 @@
"discipline": "Modul",
"disciplines": "Module",
"word": "Wort",
"words": "Wörter"
"words": "Wörter",
"camera": {
"noAuthorization": {
"description": "Kamerazugriff erforderlich. Öffnen Sie die Einstellungen, tippen Sie auf Berechtigungen und tippen Sie auf Erlauben.",
"settings": "Einstellungen"
}
}
},
"favorites": "Favoriten",
"dictionary": {
Expand Down Expand Up @@ -199,11 +199,12 @@
"wordPlaceholder": "Wort eingeben*",
"articlePlaceholder": "Artikel auswählen*",
"saveButton": "Speichern",
"addImage": "Bild hochladen*",
"addImage": "Bild hinzufügen*",
"addAudio": "Audio aufnehmen",
"maxPictureUpload": "Du kannst bis zu 3 Bilder hochladen",
"maxPictureUpload": "Du kannst bis zu 3 Bilder hinzufügen",
"requiredFields": "*Pflichtfelder",
"errorMessage": "Dies ist ein Pflichtfeld."
"errorMessage": "Dies ist ein Pflichtfeld.",
"imageErrorMessage": "Du musst 1 bis 3 Bilder auswählen"
},
"list": {
"edit": "Bearbeiten",
Expand Down
Loading