From faaa004e6870d5331841c7334022903ef09ca543 Mon Sep 17 00:00:00 2001 From: ErikSin <67773827+ErikSin@users.noreply.github.com> Date: Thu, 31 Oct 2024 15:28:19 -0700 Subject: [PATCH 01/18] Release v1.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5302d0233..64476f426 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "comapeo-mobile", - "version": "1.1.0-pre", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "comapeo-mobile", - "version": "1.1.0-pre", + "version": "1.1.0", "hasInstallScript": true, "dependencies": { "@bam.tech/react-native-image-resizer": "^3.0.7", diff --git a/package.json b/package.json index b6e1f67e5..9321a4749 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "comapeo-mobile", - "version": "1.1.0-pre", + "version": "1.1.0", "private": true, "main": "index.js", "scripts": { From c1ef946a02b0e9d9d28ad261e49f581d1b11cb61 Mon Sep 17 00:00:00 2001 From: ErikSin <67773827+ErikSin@users.noreply.github.com> Date: Tue, 12 Nov 2024 17:08:50 -0800 Subject: [PATCH 02/18] fix: observations disappearing when tracking starts (#837) (#838) --- src/frontend/screens/MapScreen/ObservationMapLayer.tsx | 8 +------- src/frontend/screens/MapScreen/index.tsx | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/frontend/screens/MapScreen/ObservationMapLayer.tsx b/src/frontend/screens/MapScreen/ObservationMapLayer.tsx index 6f64a48c4..44074f511 100644 --- a/src/frontend/screens/MapScreen/ObservationMapLayer.tsx +++ b/src/frontend/screens/MapScreen/ObservationMapLayer.tsx @@ -1,7 +1,6 @@ import React from 'react'; import MapboxGL from '@rnmapbox/maps'; -import {usePersistedTrack} from '../../hooks/persistedState/usePersistedTrack'; import {useObservations} from '../../hooks/server/observations'; import {usePresetsQuery} from '../../hooks/server/presets'; import {useNavigationFromHomeTabs} from '../../hooks/useNavigationWithTypes'; @@ -13,7 +12,6 @@ import { export const ObservationMapLayer = () => { const {data: observations} = useObservations(); const {navigate} = useNavigationFromHomeTabs(); - const isTracking = usePersistedTrack(state => state.isTracking); const {data: presets} = usePresetsQuery(); @@ -38,11 +36,7 @@ export const ObservationMapLayer = () => { }} id="observations-source" shape={displayedFeatures}> - + ); }; diff --git a/src/frontend/screens/MapScreen/index.tsx b/src/frontend/screens/MapScreen/index.tsx index 56016b979..aefe75497 100644 --- a/src/frontend/screens/MapScreen/index.tsx +++ b/src/frontend/screens/MapScreen/index.tsx @@ -112,9 +112,9 @@ export const MapScreen = () => { )} - {isFinishedLoading && } {isFinishedLoading && ( <> + From 96249995cb71684c81357b8241d2bfbd1739cf5e Mon Sep 17 00:00:00 2001 From: cimigree Date: Wed, 13 Nov 2024 17:46:14 -0500 Subject: [PATCH 03/18] fix: Bottom Sheet Opens when animations disabled. (#840) (#841) --- package-lock.json | 50 ++++++++++++++++++----------------------------- package.json | 6 +++--- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 64476f426..0ff079ed0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "@formatjs/intl-locale": "^3.3.2", "@formatjs/intl-pluralrules": "^5.2.4", "@formatjs/intl-relativetimeformat": "^11.2.4", - "@gorhom/bottom-sheet": "^4.5.1", + "@gorhom/bottom-sheet": "^5.0.5", "@osm_borders/maritime_10000m": "^1.1.0", "@react-native-community/hooks": "^2.8.0", "@react-native-community/netinfo": "11.1.0", @@ -69,12 +69,12 @@ "react-native-android-open-settings": "^1.3.0", "react-native-confirmation-code-field": "^7.3.1", "react-native-device-info": "^10.14.0", - "react-native-gesture-handler": "~2.14.0", + "react-native-gesture-handler": "~2.16.0", "react-native-indicators": "^0.17.0", "react-native-linear-gradient": "^2.8.3", "react-native-mmkv": "^2.12.1", "react-native-progress": "^5.0.1", - "react-native-reanimated": "~3.6.2", + "react-native-reanimated": "~3.10.1", "react-native-restart": "^0.0.27", "react-native-safe-area-context": "4.8.2", "react-native-scale-bar": "^1.0.6", @@ -1650,19 +1650,6 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-object-assign": { - "version": "7.22.5", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/plugin-transform-object-rest-spread": { "version": "7.22.11", "license": "MIT", @@ -4818,7 +4805,9 @@ "license": "MIT" }, "node_modules/@gorhom/bottom-sheet": { - "version": "4.5.1", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-5.0.5.tgz", + "integrity": "sha512-OPMbwrU/sx/o8AOHe4Bmthp0oR/a1jsTYmdjeGlnWmpnwAVa13QEALsgCm8WaDKA39qeoD7rT38e4/GYeL4myA==", "license": "MIT", "dependencies": { "@gorhom/portal": "1.0.14", @@ -4829,8 +4818,8 @@ "@types/react-native": "*", "react": "*", "react-native": "*", - "react-native-gesture-handler": ">=1.10.1", - "react-native-reanimated": ">=2.2.0" + "react-native-gesture-handler": ">=2.16.1", + "react-native-reanimated": ">=3.10.1" }, "peerDependenciesMeta": { "@types/react": { @@ -23072,9 +23061,9 @@ } }, "node_modules/react-native-gesture-handler": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.14.1.tgz", - "integrity": "sha512-YiM1BApV4aKeuwsM6O4C2ufwewYEKk6VMXOt0YqEZFMwABBFWhXLySFZYjBSNRU2USGppJbfHP1q1DfFQpKhdA==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.16.2.tgz", + "integrity": "sha512-vGFlrDKlmyI+BT+FemqVxmvO7nqxU33cgXVsn6IKAFishvlG3oV2Ds67D5nPkHMea8T+s1IcuMm0bF8ntZtAyg==", "license": "MIT", "dependencies": { "@egjs/hammerjs": "^2.0.17", @@ -23132,23 +23121,22 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.6.3", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.6.3.tgz", - "integrity": "sha512-2KkkPozoIvDbJcHuf8qeyoLROXQxizSi+2CTCkuNVkVZOxxY4B0Omvgq61aOQhSZUh/649x1YHoAaTyGMGDJUw==", + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.10.1.tgz", + "integrity": "sha512-sfxg6vYphrDc/g4jf/7iJ7NRi+26z2+BszPmvmk0Vnrz6FL7HYljJqTf531F1x6tFmsf+FEAmuCtTUIXFLVo9w==", "license": "MIT", "dependencies": { - "@babel/plugin-transform-object-assign": "^7.16.7", + "@babel/plugin-transform-arrow-functions": "^7.0.0-0", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", + "@babel/plugin-transform-optional-chaining": "^7.0.0-0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", + "@babel/plugin-transform-template-literals": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "invariant": "^2.2.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0-0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0-0", - "@babel/plugin-transform-arrow-functions": "^7.0.0-0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", - "@babel/plugin-transform-template-literals": "^7.0.0-0", "react": "*", "react-native": "*" } diff --git a/package.json b/package.json index 9321a4749..8edf57c53 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@formatjs/intl-locale": "^3.3.2", "@formatjs/intl-pluralrules": "^5.2.4", "@formatjs/intl-relativetimeformat": "^11.2.4", - "@gorhom/bottom-sheet": "^4.5.1", + "@gorhom/bottom-sheet": "^5.0.5", "@osm_borders/maritime_10000m": "^1.1.0", "@react-native-community/hooks": "^2.8.0", "@react-native-community/netinfo": "11.1.0", @@ -87,12 +87,12 @@ "react-native-android-open-settings": "^1.3.0", "react-native-confirmation-code-field": "^7.3.1", "react-native-device-info": "^10.14.0", - "react-native-gesture-handler": "~2.14.0", + "react-native-gesture-handler": "~2.16.0", "react-native-indicators": "^0.17.0", "react-native-linear-gradient": "^2.8.3", "react-native-mmkv": "^2.12.1", "react-native-progress": "^5.0.1", - "react-native-reanimated": "~3.6.2", + "react-native-reanimated": "~3.10.1", "react-native-restart": "^0.0.27", "react-native-safe-area-context": "4.8.2", "react-native-scale-bar": "^1.0.6", From 6a69b5912a0649ffea7737d34d4d360daa6d6b08 Mon Sep 17 00:00:00 2001 From: ErikSin <67773827+ErikSin@users.noreply.github.com> Date: Wed, 13 Nov 2024 14:16:18 -0800 Subject: [PATCH 04/18] fix: drawer not closing when animations disabled (#842) --- package-lock.json | 23 +++++++++++------------ package.json | 4 ++-- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ff079ed0..d975c2e88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,12 +69,12 @@ "react-native-android-open-settings": "^1.3.0", "react-native-confirmation-code-field": "^7.3.1", "react-native-device-info": "^10.14.0", - "react-native-gesture-handler": "~2.16.0", + "react-native-gesture-handler": "^2.20.2", "react-native-indicators": "^0.17.0", "react-native-linear-gradient": "^2.8.3", "react-native-mmkv": "^2.12.1", "react-native-progress": "^5.0.1", - "react-native-reanimated": "~3.10.1", + "react-native-reanimated": "^3.16.1", "react-native-restart": "^0.0.27", "react-native-safe-area-context": "4.8.2", "react-native-scale-bar": "^1.0.6", @@ -4808,7 +4808,6 @@ "version": "5.0.5", "resolved": "https://registry.npmjs.org/@gorhom/bottom-sheet/-/bottom-sheet-5.0.5.tgz", "integrity": "sha512-OPMbwrU/sx/o8AOHe4Bmthp0oR/a1jsTYmdjeGlnWmpnwAVa13QEALsgCm8WaDKA39qeoD7rT38e4/GYeL4myA==", - "license": "MIT", "dependencies": { "@gorhom/portal": "1.0.14", "invariant": "^2.2.4" @@ -23061,15 +23060,13 @@ } }, "node_modules/react-native-gesture-handler": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.16.2.tgz", - "integrity": "sha512-vGFlrDKlmyI+BT+FemqVxmvO7nqxU33cgXVsn6IKAFishvlG3oV2Ds67D5nPkHMea8T+s1IcuMm0bF8ntZtAyg==", - "license": "MIT", + "version": "2.20.2", + "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.20.2.tgz", + "integrity": "sha512-HqzFpFczV4qCnwKlvSAvpzEXisL+Z9fsR08YV5LfJDkzuArMhBu2sOoSPUF/K62PCoAb+ObGlTC83TKHfUd0vg==", "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4", - "lodash": "^4.17.21", "prop-types": "^15.7.2" }, "peerDependencies": { @@ -23121,16 +23118,18 @@ } }, "node_modules/react-native-reanimated": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.10.1.tgz", - "integrity": "sha512-sfxg6vYphrDc/g4jf/7iJ7NRi+26z2+BszPmvmk0Vnrz6FL7HYljJqTf531F1x6tFmsf+FEAmuCtTUIXFLVo9w==", - "license": "MIT", + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-3.16.1.tgz", + "integrity": "sha512-Wnbo7toHZ6kPLAD8JWKoKCTfNoqYOMW5vUEP76Rr4RBmJCrdXj6oauYP0aZnZq8NCbiP5bwwu7+RECcWtoetnQ==", "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", + "@babel/plugin-transform-class-properties": "^7.0.0-0", + "@babel/plugin-transform-classes": "^7.0.0-0", "@babel/plugin-transform-nullish-coalescing-operator": "^7.0.0-0", "@babel/plugin-transform-optional-chaining": "^7.0.0-0", "@babel/plugin-transform-shorthand-properties": "^7.0.0-0", "@babel/plugin-transform-template-literals": "^7.0.0-0", + "@babel/plugin-transform-unicode-regex": "^7.0.0-0", "@babel/preset-typescript": "^7.16.7", "convert-source-map": "^2.0.0", "invariant": "^2.2.4" diff --git a/package.json b/package.json index 8edf57c53..4f2df6c91 100644 --- a/package.json +++ b/package.json @@ -87,12 +87,12 @@ "react-native-android-open-settings": "^1.3.0", "react-native-confirmation-code-field": "^7.3.1", "react-native-device-info": "^10.14.0", - "react-native-gesture-handler": "~2.16.0", + "react-native-gesture-handler": "^2.20.2", "react-native-indicators": "^0.17.0", "react-native-linear-gradient": "^2.8.3", "react-native-mmkv": "^2.12.1", "react-native-progress": "^5.0.1", - "react-native-reanimated": "~3.10.1", + "react-native-reanimated": "^3.16.1", "react-native-restart": "^0.0.27", "react-native-safe-area-context": "4.8.2", "react-native-scale-bar": "^1.0.6", From 9b9d7c9b60b9b08bde5548e68ccf24ad938f9733 Mon Sep 17 00:00:00 2001 From: cimigree Date: Mon, 18 Nov 2024 09:50:46 -0500 Subject: [PATCH 05/18] fix: microphone permission modal has correct text (#843) * Makes sure text in permissions modal is correct. Removes some old console logs. --- .../PermissionAudioBottomSheetContent.tsx | 62 ++++++++++++++----- src/frontend/screens/Audio/index.tsx | 1 - src/frontend/sharedComponents/ActionRow.tsx | 5 ++ .../MediaScrollView/AudioThumbnail.tsx | 4 -- 4 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx b/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx index eb2919fb2..d66aad3c5 100644 --- a/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx +++ b/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx @@ -1,10 +1,9 @@ -import React, {FC, useState} from 'react'; -import {Linking, View} from 'react-native'; +import React, {FC, useEffect, useRef} from 'react'; +import {Linking, View, AppState, AppStateStatus} from 'react-native'; import {defineMessages, useIntl} from 'react-intl'; import AudioPermission from '../../images/observationEdit/AudioPermission.svg'; import {BottomSheetModalContent} from '../../sharedComponents/BottomSheetModal'; import {Audio} from 'expo-av'; -import {PermissionResponse} from 'expo-modules-core'; const m = defineMessages({ title: { @@ -39,14 +38,42 @@ const m = defineMessages({ interface PermissionAudioBottomSheetContentProps { closeSheet: () => void; setShouldNavigateToAudioTrue: () => void; + permissionStatus: Audio.PermissionStatus | null; + isOpen: boolean; } export const PermissionAudioBottomSheetContent: FC< PermissionAudioBottomSheetContentProps -> = ({closeSheet, setShouldNavigateToAudioTrue}) => { +> = ({closeSheet, setShouldNavigateToAudioTrue, permissionStatus, isOpen}) => { const {formatMessage: t} = useIntl(); - const [permissionResponse, setPermissionResponse] = - useState(null); + const appState = useRef(AppState.currentState); + + useEffect(() => { + if (!isOpen) return; + + const handleAppStateChange = async (nextAppState: AppStateStatus) => { + if ( + appState.current.match(/inactive|background/) && + nextAppState === 'active' + ) { + const {status} = await Audio.getPermissionsAsync(); + if (status === 'granted') { + closeSheet(); + setShouldNavigateToAudioTrue(); + } + } + appState.current = nextAppState; + }; + + const subscription = AppState.addEventListener( + 'change', + handleAppStateChange, + ); + + return () => { + subscription.remove(); + }; + }, [isOpen, closeSheet, setShouldNavigateToAudioTrue]); const handleOpenSettings = () => { Linking.openSettings(); @@ -55,7 +82,6 @@ export const PermissionAudioBottomSheetContent: FC< const handleRequestPermission = async () => { const response = await Audio.requestPermissionsAsync(); closeSheet(); - setPermissionResponse(response); if (response.status === 'granted') { setShouldNavigateToAudioTrue(); } else if (response.status === 'denied' && !response.canAskAgain) { @@ -63,16 +89,18 @@ export const PermissionAudioBottomSheetContent: FC< } }; - const onPressActionButton = !permissionResponse - ? handleRequestPermission - : permissionResponse.status === 'denied' - ? handleOpenSettings - : handleRequestPermission; - const actionButtonText = !permissionResponse - ? t(m.allowButtonText) - : permissionResponse.status === 'denied' - ? t(m.goToSettingsButtonText) - : t(m.allowButtonText); + const onPressActionButton = + !permissionStatus || permissionStatus === 'undetermined' + ? handleRequestPermission + : permissionStatus === 'denied' + ? handleOpenSettings + : handleRequestPermission; + const actionButtonText = + !permissionStatus || permissionStatus === 'undetermined' + ? t(m.allowButtonText) + : permissionStatus === 'denied' + ? t(m.goToSettingsButtonText) + : t(m.allowButtonText); return ( diff --git a/src/frontend/screens/Audio/index.tsx b/src/frontend/screens/Audio/index.tsx index bc3a74fca..b9e8ec87b 100644 --- a/src/frontend/screens/Audio/index.tsx +++ b/src/frontend/screens/Audio/index.tsx @@ -13,7 +13,6 @@ export function Audio({route}: NativeRootNavigationProps<'Audio'>) { const {deleteAudio} = useDraftObservation(); const {isEditing, uri, isSavedUri = false} = route.params; - console.log({isEditing}); return ( <> {uri ? ( diff --git a/src/frontend/sharedComponents/ActionRow.tsx b/src/frontend/sharedComponents/ActionRow.tsx index 37093b6e0..2b821b37a 100644 --- a/src/frontend/sharedComponents/ActionRow.tsx +++ b/src/frontend/sharedComponents/ActionRow.tsx @@ -55,6 +55,8 @@ export const ActionsRow = ({fieldRefs}: ActionButtonsProps) => { }); const [shouldNavigateToAudio, setShouldNavigateToAudio] = useState(false); + const [audioPermissionStatus, setAudioPermissionStatus] = + useState(null); const handleCameraPress = () => { navigation.navigate('AddPhoto'); @@ -65,6 +67,7 @@ export const ActionsRow = ({fieldRefs}: ActionButtonsProps) => { const handleAudioPress = useCallback(async () => { const {status} = await Audio.getPermissionsAsync(); + setAudioPermissionStatus(status); if (status === 'granted') { navigation.navigate('Audio', { isEditing: currentRoute?.name === 'ObservationEdit', @@ -118,6 +121,8 @@ export const ActionsRow = ({fieldRefs}: ActionButtonsProps) => { setShouldNavigateToAudio(true)} + permissionStatus={audioPermissionStatus} + isOpen={isAudioPermissionSheetOpen} /> diff --git a/src/frontend/sharedComponents/MediaScrollView/AudioThumbnail.tsx b/src/frontend/sharedComponents/MediaScrollView/AudioThumbnail.tsx index 6356e0704..f50374e95 100644 --- a/src/frontend/sharedComponents/MediaScrollView/AudioThumbnail.tsx +++ b/src/frontend/sharedComponents/MediaScrollView/AudioThumbnail.tsx @@ -31,14 +31,10 @@ export const AudioThumbnail: FC = ({ const {projectApi} = useActiveProject(); const [loading, setLoading] = React.useState(false); - console.log({currentRoute}); - if ('deleted' in audioAttachment && audioAttachment.deleted === true) { return null; } - console.log(); - const handlePress = async () => { setLoading(true); let uri: string | undefined; From 359f513fd2cf00d067cda71ae8334e124ef47e3d Mon Sep 17 00:00:00 2001 From: cimigree Date: Tue, 19 Nov 2024 10:06:23 -0500 Subject: [PATCH 06/18] fix: delete map shows confirmation twice (#845) * Adds on success handler to the hook so that the modal is only closed after the refresh is done. * Adds body text and header text to the background map files so that they line up with figma designs. Adds warning red so that the red color is as design specifies * Uses Full screen menu list for map management menu screen. --- src/frontend/lib/styles.ts | 1 + .../Settings/MapManagement/BackgroundMaps.tsx | 36 ++++++++------- .../Settings/MapManagement/ChooseMapFile.tsx | 24 +++++----- .../MapManagement/CustomMapDetails.tsx | 44 +++++++++++-------- .../screens/Settings/MapManagement/index.tsx | 28 ++++++------ 5 files changed, 72 insertions(+), 61 deletions(-) diff --git a/src/frontend/lib/styles.ts b/src/frontend/lib/styles.ts index bca8a9a31..ee9ec2ba2 100644 --- a/src/frontend/lib/styles.ts +++ b/src/frontend/lib/styles.ts @@ -24,3 +24,4 @@ export const DARK_ORANGE = '#E86826'; export const SYNC_BACKGROUND = '#2348B2'; export const GPS_MODAL_TEXT = 'rgb(40,40,40)'; export const DARK_GREEN = '#59A553'; +export const WARNING_RED = '#D92222'; diff --git a/src/frontend/screens/Settings/MapManagement/BackgroundMaps.tsx b/src/frontend/screens/Settings/MapManagement/BackgroundMaps.tsx index 883484c38..d860c5770 100644 --- a/src/frontend/screens/Settings/MapManagement/BackgroundMaps.tsx +++ b/src/frontend/screens/Settings/MapManagement/BackgroundMaps.tsx @@ -14,7 +14,7 @@ import { import ErrorSvg from '../../../images/Error.svg'; import GreenCheckSvg from '../../../images/GreenCheck.svg'; import noop from '../../../lib/noop'; -import {DARK_GREY, RED, WHITE} from '../../../lib/styles'; +import {RED, WHITE} from '../../../lib/styles'; import { BottomSheetModal, BottomSheetModalContent, @@ -23,7 +23,8 @@ import { import {Button} from '../../../sharedComponents/Button'; import {ErrorBottomSheet} from '../../../sharedComponents/ErrorBottomSheet'; import {Loading} from '../../../sharedComponents/Loading'; -import {Text} from '../../../sharedComponents/Text'; +import {HeaderText} from '../../../sharedComponents/Text/HeaderText'; +import {BodyText} from '../../../sharedComponents/Text/BodyText'; import {type NativeRootNavigationProps} from '../../../sharedTypes/navigation'; import {ChooseMapFile} from './ChooseMapFile'; import {CustomMapDetails} from './CustomMapDetails'; @@ -130,10 +131,12 @@ export function BackgroundMapsScreen() { return ( <> - {t(m.about)} + + {t(m.about)} + - {t(m.description1)} - {t(m.description2)} + {t(m.description1)} + {t(m.description2)} - + {t(m.customMapInfoLoadError)} - + )} @@ -206,8 +211,11 @@ export function BackgroundMapsScreen() { text: t(m.deleteMapButtonText), icon: , onPress: () => { - removeCustomMapMutation.mutate(); - removeMapBottomSheet.closeSheet(); + removeCustomMapMutation.mutate(undefined, { + onSuccess: () => { + removeMapBottomSheet.closeSheet(); + }, + }); }, }, { @@ -322,22 +330,16 @@ const styles = StyleSheet.create({ }, aboutText: { textAlign: 'center', - fontWeight: 'bold', - fontSize: 36, - color: DARK_GREY, }, infoLoadErrorText: { textAlign: 'center', color: RED, - fontSize: 20, }, removeMapFileButton: { backgroundColor: RED, }, removeMapFileButtonText: { - fontWeight: '700', letterSpacing: 0.5, - fontSize: 18, color: RED, }, }); diff --git a/src/frontend/screens/Settings/MapManagement/ChooseMapFile.tsx b/src/frontend/screens/Settings/MapManagement/ChooseMapFile.tsx index 5cb284431..e71197ba1 100644 --- a/src/frontend/screens/Settings/MapManagement/ChooseMapFile.tsx +++ b/src/frontend/screens/Settings/MapManagement/ChooseMapFile.tsx @@ -2,10 +2,11 @@ import React from 'react'; import {defineMessages, useIntl} from 'react-intl'; import {StyleSheet, View} from 'react-native'; -import {MEDIUM_GREY, RED} from '../../../lib/styles'; +import {NEW_DARK_GREY, RED} from '../../../lib/styles'; import {Button} from '../../../sharedComponents/Button'; -import {Text} from '../../../sharedComponents/Text'; import {DownloadIcon} from '../../../sharedComponents/icons'; +import {HeaderText} from '../../../sharedComponents/Text/HeaderText'; +import {BodyText} from '../../../sharedComponents/Text/BodyText'; const m = defineMessages({ chooseFile: { @@ -27,14 +28,19 @@ export function ChooseMapFile({onChooseFile}: {onChooseFile: () => void}) { - + {t(m.chooseFile)} - * - + + {' '} + * + + - {t(m.acceptedFileTypes)} + + {t(m.acceptedFileTypes)} + ); } @@ -49,17 +55,13 @@ const styles = StyleSheet.create({ gap: 12, }, buttonTextBase: { - fontWeight: '700', letterSpacing: 0.5, - fontSize: 18, }, asteriskText: { - fontSize: 18, color: RED, }, fileTypeText: { - color: MEDIUM_GREY, - fontSize: 14, textAlign: 'center', + color: NEW_DARK_GREY, }, }); diff --git a/src/frontend/screens/Settings/MapManagement/CustomMapDetails.tsx b/src/frontend/screens/Settings/MapManagement/CustomMapDetails.tsx index 3eb5edfd1..0dff655b4 100644 --- a/src/frontend/screens/Settings/MapManagement/CustomMapDetails.tsx +++ b/src/frontend/screens/Settings/MapManagement/CustomMapDetails.tsx @@ -4,9 +4,15 @@ import {StyleSheet, View} from 'react-native'; import {TouchableOpacity} from 'react-native-gesture-handler'; import {bytesToMegabytes} from '../../../lib/bytesToMegabytes'; -import {BLACK, MEDIUM_GREY, RED, VERY_LIGHT_GREY} from '../../../lib/styles'; +import { + BLACK, + NEW_DARK_GREY, + WARNING_RED, + VERY_LIGHT_GREY, +} from '../../../lib/styles'; import {Loading} from '../../../sharedComponents/Loading'; -import {Text} from '../../../sharedComponents/Text'; +import {BodyText} from '../../../sharedComponents/Text/BodyText'; +import {HeaderText} from '../../../sharedComponents/Text/HeaderText'; const m = defineMessages({ mapNameColumn: { @@ -53,34 +59,40 @@ export function CustomMapDetails({ return ( - {t(m.mapNameColumn)} - {t(m.dateAddedColumn)} + + {t(m.mapNameColumn)} + + + {t(m.dateAddedColumn)} + - {name} - + {name} + {displayedSize !== undefined && t(m.sizeInMegabytes, { value: displayedSize, })} - + - + - + - {t(m.removeMap)} + + {t(m.removeMap)} + {loading && } @@ -119,21 +131,17 @@ const styles = StyleSheet.create({ justifyContent: 'space-between', }, columnTitleText: { - color: MEDIUM_GREY, + color: NEW_DARK_GREY, }, dateAddedText: { - color: MEDIUM_GREY, + color: NEW_DARK_GREY, }, sizeText: { - color: MEDIUM_GREY, + color: NEW_DARK_GREY, }, removeMapText: { fontWeight: 'bold', - color: RED, - }, - nameText: { - fontWeight: 'bold', - fontSize: 18, + color: WARNING_RED, }, columnLeft: { flex: 1, diff --git a/src/frontend/screens/Settings/MapManagement/index.tsx b/src/frontend/screens/Settings/MapManagement/index.tsx index cad00b4f8..70f610a6b 100644 --- a/src/frontend/screens/Settings/MapManagement/index.tsx +++ b/src/frontend/screens/Settings/MapManagement/index.tsx @@ -1,10 +1,9 @@ import React from 'react'; import {type NativeStackNavigationOptions} from '@react-navigation/native-stack'; import {defineMessages, useIntl, type MessageDescriptor} from 'react-intl'; -import {ScrollView} from 'react-native'; - -import {List, ListItem, ListItemText} from '../../../sharedComponents/List'; import {type NativeRootNavigationProps} from '../../../sharedTypes/navigation'; +import {FullScreenMenuList} from '../../../sharedComponents/MenuList/FullScreenMenuList'; +import {MenuListItemType} from '../../../sharedComponents/MenuList/MenuListItem'; const m = defineMessages({ screenTitle: { @@ -21,18 +20,17 @@ export function MapManagementScreen({ navigation, }: NativeRootNavigationProps<'MapManagement'>) { const {formatMessage: t} = useIntl(); - return ( - - - { - navigation.navigate('BackgroundMaps'); - }}> - - - - - ); + + const menuItems: MenuListItemType[] = [ + { + onPress: () => { + navigation.navigate('BackgroundMaps'); + }, + primaryText: t(m.backgroundMaps), + }, + ]; + + return ; } export function createNavigationOptions({ From 5c36adc1a6131970a57fd054aa6c721479305d0b Mon Sep 17 00:00:00 2001 From: cimigree Date: Tue, 19 Nov 2024 12:52:46 -0500 Subject: [PATCH 07/18] Fix: share button sometimes not working after first share (#847) * Fetches a new url for attachments each time the share button is pressed. Uses new BodyText component instead of text. --- src/frontend/lib/attachmentTypeChecks.ts | 4 ++ src/frontend/screens/Observation/Buttons.tsx | 66 ++++++++++---------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/frontend/lib/attachmentTypeChecks.ts b/src/frontend/lib/attachmentTypeChecks.ts index 4d3d1485e..6efff4b90 100644 --- a/src/frontend/lib/attachmentTypeChecks.ts +++ b/src/frontend/lib/attachmentTypeChecks.ts @@ -81,3 +81,7 @@ export function isDraftPhoto(attachment: any): attachment is DraftPhoto { export function isPhoto(attachment: any): attachment is Photo { return isSavedPhoto(attachment) || isDraftPhoto(attachment); } + +export function isPhotoOrAudio(attachment: any): attachment is Photo | Audio { + return isSavedPhoto(attachment) || isAudioAttachment(attachment); +} diff --git a/src/frontend/screens/Observation/Buttons.tsx b/src/frontend/screens/Observation/Buttons.tsx index 248783d74..14b21e3c4 100644 --- a/src/frontend/screens/Observation/Buttons.tsx +++ b/src/frontend/screens/Observation/Buttons.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {useState} from 'react'; import {Alert, StyleSheet, TouchableOpacity, View} from 'react-native'; import {Field, Observation} from '@comapeo/schema'; import {DARK_GREY} from '../../lib/styles'; @@ -6,18 +6,18 @@ import MaterialIcons from 'react-native-vector-icons/MaterialIcons'; import {defineMessages, useIntl} from 'react-intl'; import {useNavigationFromRoot} from '../../hooks/useNavigationWithTypes'; import {useDeleteObservation} from '../../hooks/server/observations'; -import {Text} from '../../sharedComponents/Text'; import Share from 'react-native-share'; -import {useAttachmentUrlQueries} from '../../hooks/server/media.ts'; import {useObservationWithPreset} from '../../hooks/useObservationWithPreset.ts'; import {formatCoords} from '../../lib/utils.ts'; import {UIActivityIndicator} from 'react-native-indicators'; import {convertUrlToBase64} from '../../utils/base64.ts'; -import {useState} from 'react'; import {usePersistedSettings} from '../../hooks/persistedState/usePersistedSettings.ts'; import * as Sentry from '@sentry/react-native'; import {CoordinateFormat} from '../../sharedTypes/index.ts'; import {getValueLabel} from '../../sharedComponents/FormattedData.tsx'; +import {useActiveProject} from '../../contexts/ActiveProjectContext'; +import {BodyText} from '../../sharedComponents/Text/BodyText.tsx'; +import {isPhotoOrAudio} from '../../lib/attachmentTypeChecks.ts'; const m = defineMessages({ delete: { @@ -95,11 +95,8 @@ export const ButtonFields = ({ const deleteObservationMutation = useDeleteObservation(); const {observation, preset} = useObservationWithPreset(observationId); const format = usePersistedSettings(store => store.coordinateFormat); - const attachmentUrlQueries = useAttachmentUrlQueries( - observation.attachments, - 'original', - ); const [isShareButtonLoading, setShareButtonLoading] = useState(false); + const {projectApi} = useActiveProject(); function handlePressDelete() { Alert.alert(t(m.deleteTitle), undefined, [ @@ -117,36 +114,36 @@ export const ButtonFields = ({ ]); } - async function handlePressShare() { + async function fetchFreshUrls() { const {attachments} = observation; - setShareButtonLoading(true); - const getValidUrls = (queries: typeof attachmentUrlQueries) => { - const urls = queries - .map(query => query.data?.url) - .filter((url): url is string => url !== undefined && url !== null); - return urls; - }; - - let urls: string[] = []; + if (!attachments || attachments.length === 0) { + return []; + } - if (attachments.length > 0) { - urls = getValidUrls(attachmentUrlQueries); + return await Promise.all( + attachments.filter(isPhotoOrAudio).map(async attachment => { + return projectApi.$blobs.getUrl({ + driveId: attachment.driveDiscoveryId, + name: attachment.name, + type: attachment.type as 'photo' | 'audio', + variant: 'original', + }); + }), + ); + } - if (urls.length === 0) { - setShareButtonLoading(false); - Alert.alert('Error', 'Unable to share this observation.'); - return; - } - } + async function handlePressShare() { + setShareButtonLoading(true); try { - const base64Urls = await Promise.all( - urls.map(url => convertUrlToBase64(url)), - ); + const urls = await fetchFreshUrls(); + const base64Urls = + urls.length > 0 + ? await Promise.all(urls.map(url => convertUrlToBase64(url))) + : []; const completedFields: Array<{label: string; value: string}> = []; - for (const field of fields) { const value = observation.tags[field.tagKey]; @@ -155,9 +152,7 @@ export const ButtonFields = ({ } const displayedValue = (Array.isArray(value) ? value : [value]) - .map(v => { - return getValueLabel(v, field).trim(); - }) + .map(v => getValueLabel(v, field).trim()) .join(', '); completedFields.push({label: field.label, value: displayedValue}); @@ -177,6 +172,7 @@ export const ButtonFields = ({ timestamp: formatDate(observation.createdAt, {format: 'long'}), titleText: t(m.shareMessageTitle), }), + failOnCancel: false, }); } catch (err) { Sentry.captureException(err); @@ -224,7 +220,9 @@ const Button = ({onPress, isLoading, iconName, title}: ButtonProps) => ( style={styles.buttonIcon} /> )} - {title} + + {title} + ); From bfd5d01b7792fa81574f2f19614f4c27c6ad93fb Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Wed, 20 Nov 2024 15:26:35 -0500 Subject: [PATCH 08/18] chore: remove unused useClearAllPendingInvites hook (#852) --- src/frontend/hooks/server/invites.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/frontend/hooks/server/invites.ts b/src/frontend/hooks/server/invites.ts index d5b382489..066c4122d 100644 --- a/src/frontend/hooks/server/invites.ts +++ b/src/frontend/hooks/server/invites.ts @@ -58,24 +58,6 @@ export function useRejectInvite() { }); } -export function useClearAllPendingInvites() { - const queryClient = useQueryClient(); - const mapeoApi = useApi(); - - return useMutation({ - mutationFn: ({inviteIds}: {inviteIds: Array}) => { - return Promise.all( - inviteIds.map(id => mapeoApi.invite.reject({inviteId: id})), - ); - }, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: [INVITE_KEY], - }); - }, - }); -} - export function useSendInvite() { const queryClient = useQueryClient(); const {projectApi} = useActiveProject(); From 8ec6f4ae111c0be90b662939c7a4e5a43b275cd6 Mon Sep 17 00:00:00 2001 From: cimigree Date: Thu, 21 Nov 2024 09:11:08 -0500 Subject: [PATCH 09/18] Removes audio files from sharing an observation. Removes type check that is no longer needed. (#854) --- src/frontend/lib/attachmentTypeChecks.ts | 4 ---- src/frontend/screens/Observation/Buttons.tsx | 10 +++++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/frontend/lib/attachmentTypeChecks.ts b/src/frontend/lib/attachmentTypeChecks.ts index 6efff4b90..4d3d1485e 100644 --- a/src/frontend/lib/attachmentTypeChecks.ts +++ b/src/frontend/lib/attachmentTypeChecks.ts @@ -81,7 +81,3 @@ export function isDraftPhoto(attachment: any): attachment is DraftPhoto { export function isPhoto(attachment: any): attachment is Photo { return isSavedPhoto(attachment) || isDraftPhoto(attachment); } - -export function isPhotoOrAudio(attachment: any): attachment is Photo | Audio { - return isSavedPhoto(attachment) || isAudioAttachment(attachment); -} diff --git a/src/frontend/screens/Observation/Buttons.tsx b/src/frontend/screens/Observation/Buttons.tsx index 14b21e3c4..0fc97d115 100644 --- a/src/frontend/screens/Observation/Buttons.tsx +++ b/src/frontend/screens/Observation/Buttons.tsx @@ -17,7 +17,7 @@ import {CoordinateFormat} from '../../sharedTypes/index.ts'; import {getValueLabel} from '../../sharedComponents/FormattedData.tsx'; import {useActiveProject} from '../../contexts/ActiveProjectContext'; import {BodyText} from '../../sharedComponents/Text/BodyText.tsx'; -import {isPhotoOrAudio} from '../../lib/attachmentTypeChecks.ts'; +import {isSavedPhoto} from '../../lib/attachmentTypeChecks.ts'; const m = defineMessages({ delete: { @@ -120,13 +120,17 @@ export const ButtonFields = ({ if (!attachments || attachments.length === 0) { return []; } + const photoAttachments = attachments.filter(isSavedPhoto); + if (photoAttachments.length === 0) { + return []; + } return await Promise.all( - attachments.filter(isPhotoOrAudio).map(async attachment => { + photoAttachments.map(async attachment => { return projectApi.$blobs.getUrl({ driveId: attachment.driveDiscoveryId, name: attachment.name, - type: attachment.type as 'photo' | 'audio', + type: 'photo', variant: 'original', }); }), From f63d3abfaa65591492921262146ba7fda5df0686 Mon Sep 17 00:00:00 2001 From: Andrew Chou Date: Thu, 21 Nov 2024 10:53:05 -0500 Subject: [PATCH 10/18] fix: simplify technical implementation of importing custom map file (#855) --- src/frontend/hooks/server/maps.ts | 2 +- src/frontend/lib/file-system.ts | 29 +++++++++---------- .../Settings/MapManagement/BackgroundMaps.tsx | 7 ----- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/frontend/hooks/server/maps.ts b/src/frontend/hooks/server/maps.ts index a9efb8fd8..040c9bedb 100644 --- a/src/frontend/hooks/server/maps.ts +++ b/src/frontend/hooks/server/maps.ts @@ -47,7 +47,7 @@ export function useImportCustomMapFile() { return useMutation({ mutationFn: async (opts: {uri: string}) => { - await FileSystem.moveAsync({ + await FileSystem.copyAsync({ from: opts.uri, to: DEFAULT_CUSTOM_MAP_FILE_PATH, }); diff --git a/src/frontend/lib/file-system.ts b/src/frontend/lib/file-system.ts index d23307e9e..a319aa1a0 100644 --- a/src/frontend/lib/file-system.ts +++ b/src/frontend/lib/file-system.ts @@ -9,14 +9,16 @@ export function convertFileUriToPosixPath(fileUri: string) { } // TODO: Some overlap with selectFile() from lib/utils but fixes some usage limitations. Ideally use this for everything -export async function selectFile(opts: { - copyToCache?: boolean; +export async function selectFile({ + mimeFilters, + extensionFilters, +}: { mimeFilters?: Array; extensionFilters?: Array; -}) { +} = {}) { const documentResult = await DocumentPicker.getDocumentAsync({ - type: opts.mimeFilters, - copyToCacheDirectory: opts.copyToCache, + type: mimeFilters, + copyToCacheDirectory: false, multiple: false, }); @@ -28,17 +30,14 @@ export async function selectFile(opts: { throw new Error(); } - const hasValidExtension = opts.extensionFilters - ? opts.extensionFilters.some(extension => - asset.uri.endsWith(`.${extension}`), - ) - : true; - - if (!hasValidExtension) { - FileSystem.deleteAsync(asset.uri).catch(err => { - console.log(err); + if (extensionFilters) { + const hasValidExtension = extensionFilters.some(extension => { + return asset.name.endsWith(`.${extension}`); }); - throw new Error('Invalid extension'); + + if (!hasValidExtension) { + throw new Error('Invalid extension'); + } } return asset; diff --git a/src/frontend/screens/Settings/MapManagement/BackgroundMaps.tsx b/src/frontend/screens/Settings/MapManagement/BackgroundMaps.tsx index d860c5770..9a1ff39a0 100644 --- a/src/frontend/screens/Settings/MapManagement/BackgroundMaps.tsx +++ b/src/frontend/screens/Settings/MapManagement/BackgroundMaps.tsx @@ -1,5 +1,4 @@ import {type NativeStackNavigationOptions} from '@react-navigation/native-stack'; -import * as FileSystem from 'expo-file-system'; import React from 'react'; import {defineMessages, useIntl, type MessageDescriptor} from 'react-intl'; import {ScrollView, StyleSheet, View} from 'react-native'; @@ -13,7 +12,6 @@ import { } from '../../../hooks/server/maps'; import ErrorSvg from '../../../images/Error.svg'; import GreenCheckSvg from '../../../images/GreenCheck.svg'; -import noop from '../../../lib/noop'; import {RED, WHITE} from '../../../lib/styles'; import { BottomSheetModal, @@ -154,11 +152,6 @@ export function BackgroundMapsScreen() { uri: asset.uri, }, { - onError: () => { - FileSystem.deleteAsync(asset.uri, { - idempotent: true, - }).catch(noop); - }, onSuccess: () => { mapAddedBottomSheet.openSheet(); }, From 1b7bd791989c5c5326fd31080c14ae48cf5c44ed Mon Sep 17 00:00:00 2001 From: ErikSin <67773827+ErikSin@users.noreply.github.com> Date: Thu, 21 Nov 2024 18:08:42 +0000 Subject: [PATCH 11/18] feat: add number as field (#849) * feat: add number as field * chore: remove unnecessary code * chore: allow negative and decimals * chore: returned Question Component to original state * chore: only allow one decimal --- .../screens/ObservationFields/Number.tsx | 51 +++++++++++++++++++ .../screens/ObservationFields/Question.tsx | 5 ++ .../ObservationFields/QuestionLabel.tsx | 15 ++---- 3 files changed, 60 insertions(+), 11 deletions(-) create mode 100644 src/frontend/screens/ObservationFields/Number.tsx diff --git a/src/frontend/screens/ObservationFields/Number.tsx b/src/frontend/screens/ObservationFields/Number.tsx new file mode 100644 index 000000000..522ca7f80 --- /dev/null +++ b/src/frontend/screens/ObservationFields/Number.tsx @@ -0,0 +1,51 @@ +import * as React from 'react'; +import {StyleSheet, TextInput} from 'react-native'; +import {QuestionLabel} from './QuestionLabel'; +import {Field} from '@comapeo/schema'; +import {usePersistedDraftObservation} from '../../hooks/persistedState/usePersistedDraftObservation'; +import {useDraftObservation} from '../../hooks/useDraftObservation'; + +export const Number = React.memo<{field: Field}>(({field}) => { + const tags = usePersistedDraftObservation(store => store.value?.tags); + const {updateTags} = useDraftObservation(); + const value = tags ? tags[field.tagKey] : ''; + return ( + + + + updateTags( + field.tagKey, + newVal + .replace(/[^0-9.-]/g, '') // Allow digits, decimal, and negative sign + .replace(/(?!^)-/g, '') // Remove any minus sign that is not at the start + .replace(/(\..*?)\./g, '$1'), // Remove additional decimal points + ) + } + keyboardType="numeric" + style={styles.textInput} + underlineColorAndroid="transparent" + multiline + scrollEnabled={false} + textContentType="none" + autoFocus + /> + + ); +}); + +const styles = StyleSheet.create({ + textInput: { + flex: 1, + minHeight: 150, + fontSize: 20, + padding: 20, + marginBottom: 20, + color: 'black', + alignItems: 'flex-start', + justifyContent: 'flex-start', + textAlignVertical: 'top', + }, +}); diff --git a/src/frontend/screens/ObservationFields/Question.tsx b/src/frontend/screens/ObservationFields/Question.tsx index 1085e9bf7..fa81bf5ab 100644 --- a/src/frontend/screens/ObservationFields/Question.tsx +++ b/src/frontend/screens/ObservationFields/Question.tsx @@ -3,6 +3,7 @@ import React from 'react'; import {SelectOne} from './SelectOne'; import {SelectMultiple} from './SelectMultiple'; import {TextArea} from './TextArea'; +import {Number} from './Number'; import {Field} from '@comapeo/schema'; import { SelectMultipleField, @@ -22,5 +23,9 @@ export const Question = ({field}: QuestionProps) => { return ; } + if (field.type === 'number') { + return ; + } + return