From 6d508c9f80d735c6dd473e9ebd7f697d0ad7a7da Mon Sep 17 00:00:00 2001 From: steffi Date: Tue, 4 Oct 2022 09:31:21 +0200 Subject: [PATCH 1/8] LUN-400: Image for own vocabulary --- .../lunes}/MainActivity.java | 0 .../lunes}/MainApplication.java | 1 + android/settings.gradle | 2 + assets/images/circle-icon-white.svg | 1 + assets/images/image-icon.svg | 1 + assets/images/index.ts | 4 + ios/Podfile | 2 + package.json | 1 + src/__mocks__/react-native-fs.js | 1 + src/components/Button.tsx | 2 +- src/components/CameraOverlay.tsx | 75 ++++++++++++++ .../components/NotAuthorisedView.tsx | 10 +- .../__tests__/CameraOverlay.spec.tsx | 37 +++++++ src/constants/labels.json | 3 +- .../components/QRCodeReaderOverlay.tsx | 65 +----------- .../__tests__/QRCodeReaderOverlay.spec.tsx | 19 ---- .../UserVocabularyProcessScreen.tsx | 99 ++++++++++++++++--- .../UserVocabularyProcessScreen.spec.tsx | 34 +++++++ .../components/ImageSelectionOverlay.tsx | 81 +++++++++++++++ .../components/Thumbnail.tsx | 38 +++++++ .../__tests__/ImageSelectionOverlay.spec.tsx | 23 +++++ src/services/AsyncStorage.ts | 28 +++++- src/services/helpers.ts | 18 +++- src/testing/DocumentBuilder.ts | 4 +- yarn.lock | 31 ++++++ 25 files changed, 470 insertions(+), 110 deletions(-) rename android/app/src/main/java/{com/lunesappui => app/lunes}/MainActivity.java (100%) rename android/app/src/main/java/{com/lunesappui => app/lunes}/MainApplication.java (99%) create mode 100644 assets/images/circle-icon-white.svg create mode 100644 assets/images/image-icon.svg create mode 100644 src/__mocks__/react-native-fs.js create mode 100644 src/components/CameraOverlay.tsx rename src/{routes/add-custom-discipline => }/components/NotAuthorisedView.tsx (82%) create mode 100644 src/components/__tests__/CameraOverlay.spec.tsx create mode 100644 src/routes/process-user-vocabulary/__tests__/UserVocabularyProcessScreen.spec.tsx create mode 100644 src/routes/process-user-vocabulary/components/ImageSelectionOverlay.tsx create mode 100644 src/routes/process-user-vocabulary/components/Thumbnail.tsx create mode 100644 src/routes/process-user-vocabulary/components/__tests__/ImageSelectionOverlay.spec.tsx diff --git a/android/app/src/main/java/com/lunesappui/MainActivity.java b/android/app/src/main/java/app/lunes/MainActivity.java similarity index 100% rename from android/app/src/main/java/com/lunesappui/MainActivity.java rename to android/app/src/main/java/app/lunes/MainActivity.java diff --git a/android/app/src/main/java/com/lunesappui/MainApplication.java b/android/app/src/main/java/app/lunes/MainApplication.java similarity index 99% rename from android/app/src/main/java/com/lunesappui/MainApplication.java rename to android/app/src/main/java/app/lunes/MainApplication.java index 1f57ddeb2..77b1e028c 100644 --- a/android/app/src/main/java/com/lunesappui/MainApplication.java +++ b/android/app/src/main/java/app/lunes/MainApplication.java @@ -4,6 +4,7 @@ import android.content.Context; 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; diff --git a/android/settings.gradle b/android/settings.gradle index 8df3f753f..8e018b658 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -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) diff --git a/assets/images/circle-icon-white.svg b/assets/images/circle-icon-white.svg new file mode 100644 index 000000000..90217c461 --- /dev/null +++ b/assets/images/circle-icon-white.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/image-icon.svg b/assets/images/image-icon.svg new file mode 100644 index 000000000..1bf2bc4f1 --- /dev/null +++ b/assets/images/image-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/index.ts b/assets/images/index.ts index 96f9c8fb5..fd525776a 100644 --- a/assets/images/index.ts +++ b/assets/images/index.ts @@ -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' import CloseCircleIconBlue from './close-circle-icon-blue.svg' import CloseCircleIconBold from './close-circle-icon-bold.svg' import CloseCircleIconWhite from './close-circle-icon-white.svg' @@ -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' @@ -69,6 +71,8 @@ import VolumeUpCircleIcon from './volume-up-circle-icon.svg' export { AddCircleIcon, + CircleIconWhite, + ImageIcon, AddIconWhite, ArrowLeftCircleIconBlue, ArrowLeftCircleIconWhite, diff --git a/ios/Podfile b/ios/Podfile index 9b321de04..f2f77b3d8 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -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 diff --git a/package.json b/package.json index 5fb95dc08..420896c47 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/__mocks__/react-native-fs.js b/src/__mocks__/react-native-fs.js new file mode 100644 index 000000000..37b8960b1 --- /dev/null +++ b/src/__mocks__/react-native-fs.js @@ -0,0 +1 @@ +module.exports = { readFile: jest.fn(() => Promise.resolve('image')) } diff --git a/src/components/Button.tsx b/src/components/Button.tsx index dcae8b0d7..876abdd77 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -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) { diff --git a/src/components/CameraOverlay.tsx b/src/components/CameraOverlay.tsx new file mode 100644 index 000000000..9adfefa15 --- /dev/null +++ b/src/components/CameraOverlay.tsx @@ -0,0 +1,75 @@ +import React, { ReactElement, useEffect, useRef, useState } from 'react' +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(false) + const [permissionRequested, setPermissionRequested] = useState(false) + const [permissionGranted, setPermissionGranted] = useState(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 ( + setVisible(false)}> + + setVisible(false)} + onPressIn={() => setIsPressed(true)} + onPressOut={() => setIsPressed(false)}> + {isPressed ? ( + + ) : ( + + )} + + {permissionGranted && children} + {permissionRequested && !permissionGranted && } + + + ) +} + +export default CameraOverlay diff --git a/src/routes/add-custom-discipline/components/NotAuthorisedView.tsx b/src/components/NotAuthorisedView.tsx similarity index 82% rename from src/routes/add-custom-discipline/components/NotAuthorisedView.tsx rename to src/components/NotAuthorisedView.tsx index 33e3d9ce6..78ee673b4 100644 --- a/src/routes/add-custom-discipline/components/NotAuthorisedView.tsx +++ b/src/components/NotAuthorisedView.tsx @@ -2,10 +2,10 @@ 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 { BUTTONS_THEME } from '../constants/data' +import { getLabels } from '../services/helpers' +import Button from './Button' +import { ContentSecondary } from './text/Content' const Container = styled.View` display: flex; @@ -29,7 +29,7 @@ const NotAuthorisedView = ({ setVisible }: Props): ReactElement => { } return ( - + {getLabels().addCustomDiscipline.qrCodeScanner.noAuthorization.description}