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 2a4bd8d3b..ef188ebca 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' @@ -70,6 +72,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 aedbd555a..835d0740e 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/ios/Podfile.lock b/ios/Podfile.lock index 6d9acaae6..69b6909d5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -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): @@ -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`) @@ -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: @@ -624,6 +629,7 @@ SPEC CHECKSUMS: RNCAsyncStorage: d81ee5c3db1060afd49ea7045ad460eff82d2b7d RNCMaskedView: c298b644a10c0c142055b3ae24d83879ecb13ccd RNDeviceInfo: 1e3f62b9ec32f7754fac60bd06b8f8a27124e7f0 + RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: bad495418bcbd3ab47017a38d93d290ebd406f50 RNPermissions: eac721f71748c4472d6e876038270b75397d6ee7 RNReanimated: 11e6e30bbc49079b7f0c321ac5830e6d113d0224 @@ -636,6 +642,6 @@ SPEC CHECKSUMS: TextToSpeech: b3aa777ff5585705f179c0a2436bfd0926d1716e Yoga: e7dc4e71caba6472ff48ad7d234389b91dadc280 -PODFILE CHECKSUM: f0c4013aeb23d2c0b2b385496c8d12dc3493083b +PODFILE CHECKSUM: 5ce2b856afff013c4abae6206a72de31a844f8af COCOAPODS: 1.11.3 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/components/Dropdown.tsx b/src/components/Dropdown.tsx index 02cf68717..96c999260 100644 --- a/src/components/Dropdown.tsx +++ b/src/components/Dropdown.tsx @@ -78,6 +78,7 @@ const Dropdown = ({ fontFamily: theme.fonts.contentFontRegular, paddingHorizontal: wp('2%'), }} + listMode='SCROLLVIEW' /> {showErrorValidation && ( {errorMessage.length > 0 && {errorMessage}} diff --git a/src/components/ListItem.tsx b/src/components/ListItem.tsx index d47a6d5ad..2063b48ad 100644 --- a/src/components/ListItem.tsx +++ b/src/components/ListItem.tsx @@ -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; diff --git a/src/routes/add-custom-discipline/components/NotAuthorisedView.tsx b/src/components/NotAuthorisedView.tsx similarity index 65% rename from src/routes/add-custom-discipline/components/NotAuthorisedView.tsx rename to src/components/NotAuthorisedView.tsx index 312513871..47ce761c4 100644 --- a/src/routes/add-custom-discipline/components/NotAuthorisedView.tsx +++ b/src/components/NotAuthorisedView.tsx @@ -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; @@ -30,12 +30,12 @@ const NotAuthorisedView = ({ setVisible }: NotAuthorizedViewProps): ReactElement } return ( - - {getLabels().addCustomDiscipline.qrCodeScanner.noAuthorization.description} + + {getLabels().general.camera.noAuthorization.description}