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 ;
};
diff --git a/src/frontend/screens/ObservationFields/QuestionLabel.tsx b/src/frontend/screens/ObservationFields/QuestionLabel.tsx
index 21c5e440a..fc5c7a5dd 100644
--- a/src/frontend/screens/ObservationFields/QuestionLabel.tsx
+++ b/src/frontend/screens/ObservationFields/QuestionLabel.tsx
@@ -1,21 +1,19 @@
import * as React from 'react';
import {View, StyleSheet} from 'react-native';
import {FormattedFieldProp} from '../../sharedComponents/FormattedData';
-import {Text} from '../../sharedComponents/Text';
import {Field} from '@comapeo/schema';
-
+import {HeaderText} from '../../sharedComponents/Text/HeaderText';
interface Props {
field: Field;
}
export const QuestionLabel = ({field}: Props) => {
- const hint = ;
return (
-
+
-
- {hint ? {hint} : null}
+
+ {{field.helperText}}
);
};
@@ -27,11 +25,6 @@ const styles = StyleSheet.create({
borderBottomWidth: 2,
borderColor: '#F3F3F3',
},
- label: {
- fontSize: 20,
- color: 'black',
- fontWeight: '700',
- },
hint: {
fontSize: 16,
color: '#666666',
From b0a72b0a24a81b7cca7eb8982f6e355b472e332b Mon Sep 17 00:00:00 2001
From: cimigree
Date: Thu, 21 Nov 2024 13:26:15 -0500
Subject: [PATCH 12/18] fix: project leaving inconsistencies (#853)
* Uses a state variable and use effect in the bottom sheet modal related to project invitation so the success modal shows properly.
---
.../BottomSheetModal/Content.tsx | 2 +-
.../ProjectInviteBottomSheet/index.tsx | 32 +++++++++++++++----
2 files changed, 26 insertions(+), 8 deletions(-)
diff --git a/src/frontend/sharedComponents/BottomSheetModal/Content.tsx b/src/frontend/sharedComponents/BottomSheetModal/Content.tsx
index e7340d92c..0debb2c76 100644
--- a/src/frontend/sharedComponents/BottomSheetModal/Content.tsx
+++ b/src/frontend/sharedComponents/BottomSheetModal/Content.tsx
@@ -60,7 +60,7 @@ export const Content = ({
{icon ? {icon} : null}
diff --git a/src/frontend/sharedComponents/ProjectInviteBottomSheet/index.tsx b/src/frontend/sharedComponents/ProjectInviteBottomSheet/index.tsx
index 7fdca26cf..f6604037d 100644
--- a/src/frontend/sharedComponents/ProjectInviteBottomSheet/index.tsx
+++ b/src/frontend/sharedComponents/ProjectInviteBottomSheet/index.tsx
@@ -51,6 +51,7 @@ export const ProjectInviteBottomSheet = ({
const [leaveModalState, setLeaveModalState] =
React.useState('AlreadyOnProj');
+ const [inviteModalVisible, setInviteModalVisible] = React.useState(false);
const invite = invites[0];
@@ -64,9 +65,23 @@ export const ProjectInviteBottomSheet = ({
const accept = useAcceptInvite();
const reject = useRejectInvite();
- if (invite && !inviteIsOpen && enabledForCurrentScreen) {
- openInviteSheet();
- }
+ React.useEffect(() => {
+ if (
+ (invite || acceptedInvite) &&
+ !inviteModalVisible &&
+ enabledForCurrentScreen
+ ) {
+ setInviteModalVisible(true);
+ }
+ }, [invite, acceptedInvite, inviteModalVisible, enabledForCurrentScreen]);
+
+ React.useEffect(() => {
+ if (inviteModalVisible && !inviteIsOpen) {
+ openInviteSheet();
+ } else if (!inviteModalVisible && inviteIsOpen) {
+ closeInviteSheet();
+ }
+ }, [inviteModalVisible, inviteIsOpen, openInviteSheet, closeInviteSheet]);
if (currentInviteCanceled && leaveIsOpen) {
closeLeaveSheet();
@@ -77,20 +92,20 @@ export const ProjectInviteBottomSheet = ({
reject.mutate(invite, {
onSuccess: () => {
if (invites.length <= 1) {
- closeInviteSheet();
+ setInviteModalVisible(false);
}
},
});
}
if (invites.length <= 1) {
- closeInviteSheet();
+ setInviteModalVisible(false);
}
}
function handleCanceledInvite() {
resetCacheAndClearCanceled();
if (invites.length <= 1) {
- closeInviteSheet();
+ setInviteModalVisible(false);
}
}
@@ -115,6 +130,7 @@ export const ProjectInviteBottomSheet = ({
accept.reset();
reject.reset();
acceptedInvite?.remove();
+ setInviteModalVisible(false);
}}>
{currentInviteCanceled ? (
) : acceptedInvite ? (
{
+ setInviteModalVisible(false);
+ }}
projectName={acceptedInvite.value.projectName}
/>
) : (
From b6fc3f366c525aa1ea7e2e1eabd3e27fb5e535b2 Mon Sep 17 00:00:00 2001
From: cimigree
Date: Thu, 21 Nov 2024 13:26:39 -0500
Subject: [PATCH 13/18] Fixes crash when permission is taken away. (#851)
---
src/frontend/hooks/useFormattedTimeSince.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/frontend/hooks/useFormattedTimeSince.ts b/src/frontend/hooks/useFormattedTimeSince.ts
index 363baf2ff..8c394dc07 100644
--- a/src/frontend/hooks/useFormattedTimeSince.ts
+++ b/src/frontend/hooks/useFormattedTimeSince.ts
@@ -3,7 +3,7 @@ import {Duration} from 'luxon';
export const useFormattedTimeSince = (start: Date | null, interval: number) => {
const [currentTime, setCurrentTime] = useState(new Date());
- let startDate = start ? start : new Date();
+ let startDate = start ? new Date(start) : new Date();
useEffect(() => {
setCurrentTime(new Date());
From 7678384ab4eb7d11a3c6506bde96215130dd269f Mon Sep 17 00:00:00 2001
From: ErikSin <67773827+ErikSin@users.noreply.github.com>
Date: Thu, 7 Nov 2024 12:48:17 -0800
Subject: [PATCH 14/18] chore: Implement type ramp (#818)
* chore: added rubik and updated text component to use it
* chore: update header
* chore: update description
* chore: update button to use rubik
* chore: update drawer header to rubik
* chore: update text to use variant flag
* chore: update some headers
* chore: revert text and add deprecated tag
* chore: remove all unneccesary fonts
* chore: create header component
* chore: created body text
* chore: update jsdoc comments
* chore:update bodyText to not have line height
* chore: update data and privacy screen to use new text components
* chore:update buttons
* chore: update invite screen
* chore: invite accpeted
* chore: waiting for invite to accept screen
* chore: update drawer text
* chore: text align
* chore: update sync screen
* chore: usefontSize map
* chore: type 24 converted to 14
---
app.json | 2 +-
package-lock.json | 6 ++
package.json | 1 +
src/frontend/Navigation/Drawer.tsx | 5 +-
src/frontend/Navigation/Stack/index.tsx | 1 +
.../screens/Onboarding/DataPrivacy.tsx | 32 ++++-----
.../YourTeam/InviteAccepted.tsx | 6 +-
.../WaitingForInviteAccept.tsx | 11 +++-
.../ProjectSettings/YourTeam/index.tsx | 19 +++---
.../screens/Sync/ProjectSyncDisplay.tsx | 61 ++++++++---------
src/frontend/sharedComponents/Button.tsx | 10 +--
.../sharedComponents/DeviceNameWithIcon.tsx | 21 +++---
.../sharedComponents/RoleWithIcon.tsx | 7 +-
src/frontend/sharedComponents/Text.tsx | 4 ++
.../sharedComponents/Text/BodyText.tsx | 48 ++++++++++++++
.../sharedComponents/Text/HeaderText.tsx | 65 +++++++++++++++++++
16 files changed, 213 insertions(+), 86 deletions(-)
create mode 100644 src/frontend/sharedComponents/Text/BodyText.tsx
create mode 100644 src/frontend/sharedComponents/Text/HeaderText.tsx
diff --git a/app.json b/app.json
index 7cbdfe579..3150bfe3e 100644
--- a/app.json
+++ b/app.json
@@ -46,7 +46,7 @@
[
"expo-font",
{
- "fonts": ["./public/Rubik-Regular.ttf", "./public/Rubik-Medium.ttf"]
+ "fonts": ["node_modules/@expo-google-fonts/rubik/Rubik_400Regular.ttf", "node_modules/@expo-google-fonts/rubik/Rubik_500Medium.ttf"]
}
],
[
diff --git a/package-lock.json b/package-lock.json
index d975c2e88..d25ea1fe8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,6 +12,7 @@
"@bam.tech/react-native-image-resizer": "^3.0.7",
"@comapeo/ipc": "2.0.2",
"@dev-plugins/react-navigation": "^0.0.6",
+ "@expo-google-fonts/rubik": "^0.2.3",
"@formatjs/intl-getcanonicallocales": "^2.3.0",
"@formatjs/intl-locale": "^3.3.2",
"@formatjs/intl-pluralrules": "^5.2.4",
@@ -2761,6 +2762,11 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
+ "node_modules/@expo-google-fonts/rubik": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@expo-google-fonts/rubik/-/rubik-0.2.3.tgz",
+ "integrity": "sha512-6qqK3qgBDtjqjfDfgzh7mcduRLp5AuKCkOfafde+VaA+q0S/6levqCLg27st5vNWGGcsRLgT4xWtm2+Fv+agJQ=="
+ },
"node_modules/@expo/bunyan": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@expo/bunyan/-/bunyan-4.0.1.tgz",
diff --git a/package.json b/package.json
index 4f2df6c91..24187b3dd 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
"@bam.tech/react-native-image-resizer": "^3.0.7",
"@comapeo/ipc": "2.0.2",
"@dev-plugins/react-navigation": "^0.0.6",
+ "@expo-google-fonts/rubik": "^0.2.3",
"@formatjs/intl-getcanonicallocales": "^2.3.0",
"@formatjs/intl-locale": "^3.3.2",
"@formatjs/intl-pluralrules": "^5.2.4",
diff --git a/src/frontend/Navigation/Drawer.tsx b/src/frontend/Navigation/Drawer.tsx
index 2d2f0ac7f..e1eddd9f1 100644
--- a/src/frontend/Navigation/Drawer.tsx
+++ b/src/frontend/Navigation/Drawer.tsx
@@ -8,8 +8,7 @@ import {defineMessages, useIntl} from 'react-intl';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import EntypoIcon from 'react-native-vector-icons/Entypo';
import {NavigatorScreenParams} from '@react-navigation/native';
-import {View} from 'react-native';
-import {Text} from '../sharedComponents/Text';
+import {View, Text} from 'react-native';
import {VERY_LIGHT_GREY, WHITE} from '../lib/styles';
import {useProjectSettings} from '../hooks/server/projects';
import {AppStackParamsList} from '../sharedTypes/navigation';
@@ -121,6 +120,7 @@ const DrawerContent = ({navigation}: DrawerContentComponentProps) => {
style={{alignSelf: 'flex-end', marginRight: 20}}
onPress={navigation.closeDrawer}
/>
+ {/* This text component is one of the exceptions that does not use the shared text components as requested by Sabella */}
{
textAlign: 'center',
paddingHorizontal: 40,
fontSize: 18,
+ fontFamily: 'Rubik_400Regular',
}}>
{data?.name
? formatMessage(m.projName, {projectName: data.name})
diff --git a/src/frontend/Navigation/Stack/index.tsx b/src/frontend/Navigation/Stack/index.tsx
index fe5576134..90157d3ea 100644
--- a/src/frontend/Navigation/Stack/index.tsx
+++ b/src/frontend/Navigation/Stack/index.tsx
@@ -103,6 +103,7 @@ export const NavigatorScreenOptions: NativeStackNavigationOptions = {
presentation: 'card',
contentStyle: {backgroundColor: WHITE},
headerStyle: {backgroundColor: WHITE},
+ headerTitleStyle: {fontFamily: 'Rubik_500Medium'},
headerLeft: props => ,
// This only hides the DEFAULT back button. We render a custom one in headerLeft, so the default one should always be hidden.
// This **might** cause a problem for IOS
diff --git a/src/frontend/screens/Onboarding/DataPrivacy.tsx b/src/frontend/screens/Onboarding/DataPrivacy.tsx
index 53d74c92f..5432bb8fb 100644
--- a/src/frontend/screens/Onboarding/DataPrivacy.tsx
+++ b/src/frontend/screens/Onboarding/DataPrivacy.tsx
@@ -1,5 +1,5 @@
import * as React from 'react';
-import {View, Text, StyleSheet, ScrollView} from 'react-native';
+import {View, StyleSheet, ScrollView} from 'react-native';
import {Button} from '../../sharedComponents/Button';
import {useIntl} from 'react-intl';
import {NativeStackScreenProps} from '@react-navigation/native-stack';
@@ -8,6 +8,8 @@ import CoMapeoShield from '../../images/CoMapeoShield.svg';
import MaterialIcons from 'react-native-vector-icons/MaterialIcons';
import {NEW_DARK_GREY, BLUE_GREY} from '../../lib/styles';
import {m} from './DataPrivacyMessages';
+import {HeaderText} from '../../sharedComponents/Text/HeaderText';
+import {BodyText} from '../../sharedComponents/Text/BodyText';
export const DataPrivacy = ({
navigation,
@@ -17,11 +19,13 @@ export const DataPrivacy = ({
return (
- {formatMessage(m.dataPrivacyTitle)}
+
+ {formatMessage(m.dataPrivacyTitle)}
+
-
+
{formatMessage(m.dataPrivacyDescription)}
-
+
-
+
{formatMessage(m.dataPrivacyStays)}
-
+
-
+
{formatMessage(m.dataPrivacyEncrypted)}
-
+
-
+
{formatMessage(m.dataPrivacyManageAndControl)}
-
+
-
+
{formatMessage(m.dataPrivacyDiagnostic)}
-
+
@@ -104,8 +108,6 @@ const styles = StyleSheet.create({
marginBottom: GAP,
},
title: {
- fontSize: 32,
- fontWeight: 'bold',
marginBottom: GAP,
},
descriptionContainer: {
@@ -114,7 +116,6 @@ const styles = StyleSheet.create({
gap: GAP,
},
description: {
- fontSize: 16,
textAlign: 'left',
},
bulletPoints: {
@@ -131,7 +132,6 @@ const styles = StyleSheet.create({
},
bulletPointText: {
flex: 1,
- fontSize: 14,
color: NEW_DARK_GREY,
},
buttonContainer: {
diff --git a/src/frontend/screens/Settings/ProjectSettings/YourTeam/InviteAccepted.tsx b/src/frontend/screens/Settings/ProjectSettings/YourTeam/InviteAccepted.tsx
index acc6690e6..366fe7193 100644
--- a/src/frontend/screens/Settings/ProjectSettings/YourTeam/InviteAccepted.tsx
+++ b/src/frontend/screens/Settings/ProjectSettings/YourTeam/InviteAccepted.tsx
@@ -1,6 +1,5 @@
import {BackHandler, StyleSheet, View} from 'react-native';
import GreenCheck from '../../../../images/GreenCheck.svg';
-import {Text} from '../../../../sharedComponents/Text';
import {defineMessages, useIntl} from 'react-intl';
import React from 'react';
import {NativeRootNavigationProps} from '../../../../sharedTypes/navigation';
@@ -9,6 +8,7 @@ import {RoleWithIcon} from '../../../../sharedComponents/RoleWithIcon';
import {Button} from '../../../../sharedComponents/Button';
import {useFocusEffect} from '@react-navigation/native';
import {COORDINATOR_ROLE_ID} from '../../../../sharedTypes';
+import {HeaderText} from '../../../../sharedComponents/Text/HeaderText';
const m = defineMessages({
inviteAccepted: {
@@ -47,9 +47,9 @@ export const InviteAccepted = ({
-
+
{t(m.inviteAccepted)}
-
+
- {t(m.waitingMessage)}
- {t(m.timerMessage, {seconds: time})}
+
+ {t(m.waitingMessage)}
+
+
+ {t(m.timerMessage, {seconds: time})}
+
);
diff --git a/src/frontend/screens/Settings/ProjectSettings/YourTeam/index.tsx b/src/frontend/screens/Settings/ProjectSettings/YourTeam/index.tsx
index 1a20eb13b..9d16191c4 100644
--- a/src/frontend/screens/Settings/ProjectSettings/YourTeam/index.tsx
+++ b/src/frontend/screens/Settings/ProjectSettings/YourTeam/index.tsx
@@ -10,7 +10,6 @@ import type {NativeNavigationComponent} from '../../../../sharedTypes/navigation
import {ScrollView, StyleSheet, View} from 'react-native';
import {Button} from '../../../../sharedComponents/Button';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
-import {Text} from '../../../../sharedComponents/Text';
import {BLACK} from '../../../../lib/styles';
import {DeviceCard} from '../../../../sharedComponents/DeviceCard';
import {
@@ -22,6 +21,8 @@ import {UIActivityIndicator} from 'react-native-indicators';
import {useDeviceInfo} from '../../../../hooks/server/deviceInfo';
import {CenteredView} from '../../../../sharedComponents/CenteredView';
import {NotOnProject} from './NotOnProject';
+import {HeaderText} from '../../../../sharedComponents/Text/HeaderText';
+import {BodyText} from '../../../../sharedComponents/Text/BodyText';
const m = defineMessages({
title: {
@@ -120,9 +121,7 @@ export const YourTeam: NativeNavigationComponent<'YourTeam'> = ({
name="person-add"
style={{marginRight: 10}}
/>
-
- {t(m.inviteDevice)}
-
+ {t(m.inviteDevice)}
)}
@@ -131,7 +130,7 @@ export const YourTeam: NativeNavigationComponent<'YourTeam'> = ({
messageDescriptor={m.coordinators}
style={{marginTop: 20}}
/>
- {t(m.coordinatorDescription)}
+ {t(m.coordinatorDescription)}
= ({
alignItems: 'center',
marginTop: 20,
}}>
- {t(m.deviceName)}
- {t(m.dateAdded)}
+ {t(m.deviceName)}
+ {t(m.dateAdded)}
{membersQuery.isLoading && }
@@ -163,7 +162,7 @@ export const YourTeam: NativeNavigationComponent<'YourTeam'> = ({
messageDescriptor={m.participants}
style={{marginTop: 20}}
/>
- {t(m.participantDescription)}
+ {t(m.participantDescription)}
{membersQuery.isLoading && }
@@ -210,9 +209,7 @@ const IconHeader = ({
name={iconName}
style={{marginRight: 10}}
/>
-
- {t(messageDescriptor)}
-
+ {t(messageDescriptor)}
);
};
diff --git a/src/frontend/screens/Sync/ProjectSyncDisplay.tsx b/src/frontend/screens/Sync/ProjectSyncDisplay.tsx
index e691d4f45..6f6919916 100644
--- a/src/frontend/screens/Sync/ProjectSyncDisplay.tsx
+++ b/src/frontend/screens/Sync/ProjectSyncDisplay.tsx
@@ -2,7 +2,7 @@ import * as React from 'react';
import {useFocusEffect} from '@react-navigation/native';
import {useQueryClient} from '@tanstack/react-query';
import {defineMessages, useIntl} from 'react-intl';
-import {StyleSheet, View} from 'react-native';
+import {StyleSheet, View, Text} from 'react-native';
import {Bar as ProgressBar} from 'react-native-progress';
import {useActiveProject} from '../../contexts/ActiveProjectContext';
@@ -35,7 +35,8 @@ import {
WifiIcon,
} from '../../sharedComponents/icons';
import {ScreenContentWithDock} from '../../sharedComponents/ScreenContentWithDock';
-import {Text} from '../../sharedComponents/Text';
+import {HeaderText} from '../../sharedComponents/Text/HeaderText';
+import {BodyText} from '../../sharedComponents/Text/BodyText';
const m = defineMessages({
devicesFound: {
@@ -102,7 +103,7 @@ const m = defineMessages({
defaultMessage: "You're all caught up!",
},
});
-
+// This component has headers that vary from the design system by request of sabella. The headers are manually set to rubik 400 at size 32
export const ProjectSyncDisplay = ({
syncState,
projectName,
@@ -179,7 +180,9 @@ export const ProjectSyncDisplay = ({
}}>
- {t(m.startSync)}
+
+ {t(m.startSync)}
+
);
@@ -204,7 +207,7 @@ export const ProjectSyncDisplay = ({
}}>
- {t(m.stop)}
+ {t(m.stop)}
);
@@ -229,7 +232,7 @@ export const ProjectSyncDisplay = ({
}}>
- {t(m.stop)}
+ {t(m.stop)}
);
@@ -258,7 +261,7 @@ export const ProjectSyncDisplay = ({
}}>
- {t(m.stop)}
+ {t(m.stop)}
);
@@ -285,12 +288,12 @@ export const ProjectSyncDisplay = ({
}}>
- {t(m.stop)}
+ {t(m.stop)}
) : (
);
@@ -298,7 +301,9 @@ export const ProjectSyncDisplay = ({
<>
{t(m.syncingFullyComplete)}
- {t(m.allDataSynced)}
+
+ {t(m.allDataSynced)}
+
>
@@ -321,11 +326,11 @@ export const ProjectSyncDisplay = ({
{projectName && (
- {projectName}
+ {projectName}
)}
- {t(m.devicesFound)}
+ {t(m.devicesFound)}
{syncInfoContent}
@@ -378,13 +383,14 @@ function SyncProgress({
) : (
)}
-
+
{progressLabel}
-
+
{stage.name !== 'waiting' && (
-
+
{t(m.progressSyncPercentage, {
value: Math.round(stage.progress * 100),
})}
-
+
)}
);
@@ -422,16 +428,12 @@ const styles = StyleSheet.create({
flexDirection: 'row',
gap: 8,
},
- projectNameText: {
- fontSize: 24,
- fontWeight: 'bold',
- },
titleText: {
- fontSize: 40,
+ fontSize: 32,
textAlign: 'center',
+ fontFamily: 'Rubik_400Regular',
},
subtitleText: {
- fontSize: 24,
textAlign: 'center',
},
buttonContentContainer: {
@@ -440,14 +442,8 @@ const styles = StyleSheet.create({
gap: 12,
},
buttonTextPrimary: {
- fontWeight: 'bold',
- fontSize: 20,
color: WHITE,
},
- buttonTextSecondary: {
- fontWeight: 'bold',
- fontSize: 20,
- },
syncProgressContainer: {
gap: 12,
},
@@ -457,7 +453,6 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
syncProgressLabel: {
- fontSize: 20,
color: COMAPEO_BLUE,
},
syncProgressText: {
diff --git a/src/frontend/sharedComponents/Button.tsx b/src/frontend/sharedComponents/Button.tsx
index eedd9c21e..180e3ff04 100644
--- a/src/frontend/sharedComponents/Button.tsx
+++ b/src/frontend/sharedComponents/Button.tsx
@@ -2,10 +2,10 @@ import * as React from 'react';
import {GestureResponderEvent, StyleSheet, View, ViewStyle} from 'react-native';
import {BLACK, COMAPEO_BLUE, VERY_LIGHT_BLUE} from '../lib/styles';
-import {Text} from './Text';
import {TouchableNativeFeedback} from 'react-native-gesture-handler';
import {ViewStyleProp} from '../sharedTypes';
+import {HeaderText} from './Text/HeaderText';
type ColorScheme = 'dark' | 'light' | 'ComapeoBlue';
type Variant = 'contained' | 'outlined' | 'text';
@@ -54,7 +54,11 @@ export const Button = ({
{
typeof children === 'string' ? (
- {children}
+
+ {children}
+
) : (
children
)
@@ -152,9 +156,7 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
textBase: {
- fontWeight: '700',
letterSpacing: 0.5,
- fontSize: 16,
color: '#FFFFFF',
},
textOutlinedLight: {
diff --git a/src/frontend/sharedComponents/DeviceNameWithIcon.tsx b/src/frontend/sharedComponents/DeviceNameWithIcon.tsx
index 217d66b57..736f8eaa9 100644
--- a/src/frontend/sharedComponents/DeviceNameWithIcon.tsx
+++ b/src/frontend/sharedComponents/DeviceNameWithIcon.tsx
@@ -8,10 +8,11 @@ import type {
DeviceType,
} from '../sharedTypes';
import {defineMessages, useIntl} from 'react-intl';
-import {Text} from './Text';
import {MEDIUM_GREY} from '../lib/styles';
import {ExhaustivenessError} from '../lib/ExhaustivenessError';
import Caution from '../images/caution.svg';
+import {HeaderText} from './Text/HeaderText';
+import {BodyText} from './Text/BodyText';
const m = defineMessages({
thisDevice: {
@@ -66,28 +67,29 @@ export const DeviceNameWithIcon = ({
)}
- {name}
+ {name}
{deviceId && (
-
{`${deviceId.slice(0, 12)}...`}
-
+
)}
{thisDevice && (
-
{formatMessage(m.thisDevice)}
-
+
)}
{isDisconnected && (
-
+
{formatMessage(m.disconnected)}
-
+
)}
@@ -102,7 +104,6 @@ const styles = StyleSheet.create({
},
deviceStatusText: {
flex: 1,
- fontSize: 12,
color: MEDIUM_GREY,
marginLeft: 5,
},
diff --git a/src/frontend/sharedComponents/RoleWithIcon.tsx b/src/frontend/sharedComponents/RoleWithIcon.tsx
index 5ca28c80d..cf7f18b44 100644
--- a/src/frontend/sharedComponents/RoleWithIcon.tsx
+++ b/src/frontend/sharedComponents/RoleWithIcon.tsx
@@ -1,10 +1,11 @@
+import * as React from 'react';
import {StyleSheet, View} from 'react-native';
import MaterialIcon from 'react-native-vector-icons/MaterialIcons';
import MaterialCommunity from 'react-native-vector-icons/MaterialCommunityIcons';
import {defineMessages, useIntl} from 'react-intl';
-import {Text} from './Text';
import {BLACK} from '../lib/styles';
import {ViewStyleProp} from '../sharedTypes';
+import {HeaderText} from './Text/HeaderText';
const m = defineMessages({
coordinator: {
@@ -31,9 +32,9 @@ export const RoleWithIcon = ({role, style}: RoleWithIconProps) => {
) : (
)}
-
+
{formatMessage(role === 'coordinator' ? m.coordinator : m.participant)}
-
+
);
};
diff --git a/src/frontend/sharedComponents/Text.tsx b/src/frontend/sharedComponents/Text.tsx
index bf37364c8..2f9f752da 100644
--- a/src/frontend/sharedComponents/Text.tsx
+++ b/src/frontend/sharedComponents/Text.tsx
@@ -2,6 +2,10 @@ import * as React from 'react';
import {Text as RNText, TextProps} from 'react-native';
import {BLACK} from '../lib/styles';
+/**
+ *
+ * @deprecated use `sharedComponent/Text/HeaderText` or `sharedComponent/Text/BodyText` instead
+ */
export const Text = ({
children,
style,
diff --git a/src/frontend/sharedComponents/Text/BodyText.tsx b/src/frontend/sharedComponents/Text/BodyText.tsx
new file mode 100644
index 000000000..d868f3139
--- /dev/null
+++ b/src/frontend/sharedComponents/Text/BodyText.tsx
@@ -0,0 +1,48 @@
+import * as React from 'react';
+import {Text as RNText, TextProps, TextStyle} from 'react-native';
+import {BLACK} from '../../lib/styles';
+
+type Variant = 'large' | 'regular' | 'smallMeta' | 'tinyMeta';
+
+interface BodyProps extends Omit {
+ style?: Omit;
+ variant?: Variant;
+}
+
+/**
+ * Body text uses system font and opinated font sizes and font weight. Should be used for most text.
+ *
+ * Different `variant` types (default to 'regular'):
+ *
+ * large = `{fontSize:20}`
+ *
+ * regular = `{fontSize:16}`
+ *
+ * smallMeta = `{fontSize:14}`
+ *
+ * tinyMeta = `{fontSize:12}`
+ */
+export const BodyText = ({
+ children,
+ style,
+ variant,
+ ...otherTextProps
+}: React.PropsWithChildren) => {
+ return (
+
+ {children}
+
+ );
+};
+
+const fontSizeMap: {[key in Variant]: number} = {
+ large: 20,
+ regular: 16,
+ smallMeta: 14,
+ tinyMeta: 12,
+};
diff --git a/src/frontend/sharedComponents/Text/HeaderText.tsx b/src/frontend/sharedComponents/Text/HeaderText.tsx
new file mode 100644
index 000000000..dbb97de4c
--- /dev/null
+++ b/src/frontend/sharedComponents/Text/HeaderText.tsx
@@ -0,0 +1,65 @@
+import * as React from 'react';
+import {Text as RNText, TextProps, TextStyle} from 'react-native';
+import {BLACK} from '../../lib/styles';
+
+type Variant =
+ | 'header1'
+ | 'header2'
+ | 'header3'
+ | 'header4'
+ | 'header5'
+ | 'header6';
+
+interface HeaderProps extends Omit {
+ style?: Omit;
+ variant?: Variant;
+}
+
+/**
+ * HeaderText should be used for all headers, form labels, and buttons. HeaderText uses rubik font at font weight 500
+ *
+ * Different `variant` types (default to `header1`):
+ *
+ * header1 = `{fontSize:32}`
+ *
+ * header2 = `{fontSize:24}`
+ *
+ * header3 = `{fontSize:20}`
+ *
+ * header4 = `{fontSize:18}`
+ *
+ * header5 = `{fontSize:16}`
+ *
+ * header6 = `{fontSize:14}`
+ *
+ */
+export const HeaderText = ({
+ children,
+ style,
+ variant,
+ ...otherTextProps
+}: React.PropsWithChildren) => {
+ return (
+
+ {children}
+
+ );
+};
+
+const fontSizeMap: {[key in Variant]: number} = {
+ header1: 32,
+ header2: 24,
+ header3: 20,
+ header4: 18,
+ header5: 16,
+ header6: 14,
+};
From 1909ea707b3595136a5868160f3547f924df8d39 Mon Sep 17 00:00:00 2001
From: ErikSin <67773827+ErikSin@users.noreply.github.com>
Date: Thu, 7 Nov 2024 12:50:08 -0800
Subject: [PATCH 15/18] chore: update inputs (#830)
* chore: create checkbox using svgs
* chore: update metrics to use new checkbox
* chore: update input color and font
---
.../images/checkbox/CheckboxError.svg | 3 ++
.../images/checkbox/CheckboxSelected.svg | 4 ++
.../images/checkbox/CheckboxUnselected.svg | 3 ++
src/frontend/sharedComponents/Checkbox.tsx | 39 +++++++++++++++++++
.../sharedComponents/HookFormTextInput.tsx | 13 +++++--
.../MetricsDiagnosticsPermissionToggle.tsx | 27 ++++---------
6 files changed, 66 insertions(+), 23 deletions(-)
create mode 100644 src/frontend/images/checkbox/CheckboxError.svg
create mode 100644 src/frontend/images/checkbox/CheckboxSelected.svg
create mode 100644 src/frontend/images/checkbox/CheckboxUnselected.svg
create mode 100644 src/frontend/sharedComponents/Checkbox.tsx
diff --git a/src/frontend/images/checkbox/CheckboxError.svg b/src/frontend/images/checkbox/CheckboxError.svg
new file mode 100644
index 000000000..08eb882a2
--- /dev/null
+++ b/src/frontend/images/checkbox/CheckboxError.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/frontend/images/checkbox/CheckboxSelected.svg b/src/frontend/images/checkbox/CheckboxSelected.svg
new file mode 100644
index 000000000..f050fd508
--- /dev/null
+++ b/src/frontend/images/checkbox/CheckboxSelected.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/frontend/images/checkbox/CheckboxUnselected.svg b/src/frontend/images/checkbox/CheckboxUnselected.svg
new file mode 100644
index 000000000..970cf7c52
--- /dev/null
+++ b/src/frontend/images/checkbox/CheckboxUnselected.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/frontend/sharedComponents/Checkbox.tsx b/src/frontend/sharedComponents/Checkbox.tsx
new file mode 100644
index 000000000..ee00592a0
--- /dev/null
+++ b/src/frontend/sharedComponents/Checkbox.tsx
@@ -0,0 +1,39 @@
+import * as React from 'react';
+import CheckBoxSelected from '../images/checkbox/CheckboxSelected.svg';
+import CheckBoxUnSelected from '../images/checkbox/CheckboxUnselected.svg';
+import CheckBoxError from '../images/checkbox/CheckboxError.svg';
+import {TouchableOpacity} from 'react-native';
+
+type CheckboxProps = {
+ value: boolean;
+ onPress: () => void;
+ testID?: string;
+ disabled?: boolean;
+ error?: boolean;
+ hitSlop: React.ComponentProps['hitSlop'];
+};
+
+export const Checkbox = ({
+ value,
+ onPress,
+ testID,
+ disabled,
+ error,
+ hitSlop,
+}: CheckboxProps) => {
+ return (
+
+ {value ? (
+
+ ) : error ? (
+
+ ) : (
+
+ )}
+
+ );
+};
diff --git a/src/frontend/sharedComponents/HookFormTextInput.tsx b/src/frontend/sharedComponents/HookFormTextInput.tsx
index 05975673d..97985609b 100644
--- a/src/frontend/sharedComponents/HookFormTextInput.tsx
+++ b/src/frontend/sharedComponents/HookFormTextInput.tsx
@@ -10,7 +10,7 @@ import {
UseControllerProps,
} from 'react-hook-form';
import {TextInput as RNTextInput, StyleSheet, View} from 'react-native';
-import {BLACK, LIGHT_GREY, RED} from '../lib/styles';
+import {BLACK, LIGHT_GREY, NEW_DARK_GREY, RED} from '../lib/styles';
import {ErrorIcon} from './icons';
import {ViewStyleProp} from '../sharedTypes';
import {Text} from './Text';
@@ -62,7 +62,14 @@ export const HookFormTextInput = ({
render={({field: {value, onChange, onBlur}}) => (
{
{formatMessage(m.shareDiagnostics)}
-
- {isEnabled && }
-
+ />
);
};
@@ -44,17 +44,4 @@ const styles = StyleSheet.create({
color: BLACK,
flex: 1,
},
- checkBox: {
- width: 20,
- height: 20,
- borderWidth: 1,
- borderColor: BLUE_GREY,
- borderRadius: 2,
- alignItems: 'center',
- justifyContent: 'center',
- },
- checkBoxChecked: {
- backgroundColor: COMAPEO_BLUE,
- borderColor: COMAPEO_BLUE,
- },
});
From 7a832ac0e2146a400abe3d196dd44c7133694272 Mon Sep 17 00:00:00 2001
From: ErikSin <67773827+ErikSin@users.noreply.github.com>
Date: Tue, 3 Dec 2024 20:28:29 +0000
Subject: [PATCH 16/18] fix: audio permission modal spacing hiding button
(#865) (#873)
* Fixes full sheet on bottom sheet modal back
* Undoes last change. Takes out padding so permission audio fits.
Co-authored-by: cimigree
---
.../PermissionAudioBottomSheetContent.tsx | 36 +++++++++----------
1 file changed, 17 insertions(+), 19 deletions(-)
diff --git a/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx b/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx
index d66aad3c5..2d470ff6e 100644
--- a/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx
+++ b/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx
@@ -103,24 +103,22 @@ export const PermissionAudioBottomSheetContent: FC<
: t(m.allowButtonText);
return (
-
- }
- title={t(m.title)}
- description={t(m.description)}
- buttonConfigs={[
- {
- variation: 'outlined',
- onPress: closeSheet,
- text: t(m.notNowButtonText),
- },
- {
- variation: 'filled',
- onPress: onPressActionButton,
- text: actionButtonText,
- },
- ]}
- />
-
+ }
+ title={t(m.title)}
+ description={t(m.description)}
+ buttonConfigs={[
+ {
+ variation: 'outlined',
+ onPress: closeSheet,
+ text: t(m.notNowButtonText),
+ },
+ {
+ variation: 'filled',
+ onPress: onPressActionButton,
+ text: actionButtonText,
+ },
+ ]}
+ />
);
};
From 75b11ad91f97a42b2dff2679f41fefb947faf1ef Mon Sep 17 00:00:00 2001
From: ErikSin <67773827+ErikSin@users.noreply.github.com>
Date: Tue, 10 Dec 2024 10:45:31 -0800
Subject: [PATCH 17/18] Adds snappoints prop to bottom sheet modal to help
sizing on full screen modals. (#878) (#889)
Co-authored-by: cimigree
---
.../PermissionAudioBottomSheetContent.tsx | 36 ++++++++++---------
.../BottomSheetModal/Content.tsx | 2 +-
.../BottomSheetModal/index.tsx | 1 +
3 files changed, 21 insertions(+), 18 deletions(-)
diff --git a/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx b/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx
index 2d470ff6e..d66aad3c5 100644
--- a/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx
+++ b/src/frontend/screens/Audio/PermissionAudioBottomSheetContent.tsx
@@ -103,22 +103,24 @@ export const PermissionAudioBottomSheetContent: FC<
: t(m.allowButtonText);
return (
- }
- title={t(m.title)}
- description={t(m.description)}
- buttonConfigs={[
- {
- variation: 'outlined',
- onPress: closeSheet,
- text: t(m.notNowButtonText),
- },
- {
- variation: 'filled',
- onPress: onPressActionButton,
- text: actionButtonText,
- },
- ]}
- />
+
+ }
+ title={t(m.title)}
+ description={t(m.description)}
+ buttonConfigs={[
+ {
+ variation: 'outlined',
+ onPress: closeSheet,
+ text: t(m.notNowButtonText),
+ },
+ {
+ variation: 'filled',
+ onPress: onPressActionButton,
+ text: actionButtonText,
+ },
+ ]}
+ />
+
);
};
diff --git a/src/frontend/sharedComponents/BottomSheetModal/Content.tsx b/src/frontend/sharedComponents/BottomSheetModal/Content.tsx
index 0debb2c76..e7340d92c 100644
--- a/src/frontend/sharedComponents/BottomSheetModal/Content.tsx
+++ b/src/frontend/sharedComponents/BottomSheetModal/Content.tsx
@@ -60,7 +60,7 @@ export const Content = ({
{icon ? {icon} : null}
diff --git a/src/frontend/sharedComponents/BottomSheetModal/index.tsx b/src/frontend/sharedComponents/BottomSheetModal/index.tsx
index 46ddf47a0..eb1f47a96 100644
--- a/src/frontend/sharedComponents/BottomSheetModal/index.tsx
+++ b/src/frontend/sharedComponents/BottomSheetModal/index.tsx
@@ -92,6 +92,7 @@ export const BottomSheetModal = React.forwardRef(
return (
Date: Tue, 10 Dec 2024 11:50:11 -0800
Subject: [PATCH 18/18] feat: list tracks in observation (#890)
* feat: show linked track in observation (#839)
* chore: create reuseable accordian
* chore: reuseable track
* chore: remove component
* chore: plural of observation
* chore:translations
* chore: create track list
* chore: translations
* chore: fix styling
* chore: update text to use customtext
* chore: rename track list to track accordian
* chore: change nav to popTo from navigate
* chore: remove flex:1
* chore: refactor track list in observation with maintainable architecture (#883)
* chore: find associated track with test
* chore: refactor TrackAccordian
* chore: integrate new tracsk accordian
* chore: pr review
* chore: change to mts file
* chore: remove mock data and testing for ci
---
messages/en.json | 6 ++
package-lock.json | 7 +-
.../screens/Observation/FieldDetails.tsx | 38 +++-----
.../screens/Observation/PresetHeader.tsx | 25 +++--
.../screens/Observation/TrackAccordian.tsx | 60 ++++++++++++
.../Observation/findAssociatedTrack.ts | 13 +++
src/frontend/screens/Observation/index.tsx | 43 +++++----
.../screens/Track/ObservationList.tsx | 92 +++++++------------
src/frontend/screens/Track/index.tsx | 4 +-
src/frontend/sharedComponents/Accordian.tsx | 53 +++++++++++
10 files changed, 227 insertions(+), 114 deletions(-)
create mode 100644 src/frontend/screens/Observation/TrackAccordian.tsx
create mode 100644 src/frontend/screens/Observation/findAssociatedTrack.ts
create mode 100644 src/frontend/sharedComponents/Accordian.tsx
diff --git a/messages/en.json b/messages/en.json
index df2f35e19..43491fbbd 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -824,6 +824,9 @@
"description": "Button to share an observation",
"message": "Share"
},
+ "screens.Observation.TrackList.track": {
+ "message": "Track"
+ },
"screens.Observation.cancel": {
"description": "Button to cancel delete of observation",
"message": "Cancel"
@@ -1451,6 +1454,9 @@
"screens.Sync.ProjectSyncDisplay.waitingForDevices": {
"message": "Waiting for devices"
},
+ "screens.Track.ObservationList.observation": {
+ "message": "Observation"
+ },
"screens.Track.ObservationList.observations": {
"message": "Observations"
},
diff --git a/package-lock.json b/package-lock.json
index d25ea1fe8..6a4305df8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -26498,9 +26498,10 @@
}
},
"node_modules/type-fest": {
- "version": "4.26.0",
- "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.26.0.tgz",
- "integrity": "sha512-OduNjVJsFbifKb57UqZ2EMP1i4u64Xwow3NYXUtBbD4vIwJdQd4+xl8YDou1dlm4DVrtwT/7Ky8z8WyCULVfxw==",
+ "version": "4.30.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.30.0.tgz",
+ "integrity": "sha512-G6zXWS1dLj6eagy6sVhOMQiLtJdxQBHIA9Z6HFUNLOlr6MFOgzV8wvmidtPONfPtEUv0uZsy77XJNzTAfwPDaA==",
+ "license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=16"
},
diff --git a/src/frontend/screens/Observation/FieldDetails.tsx b/src/frontend/screens/Observation/FieldDetails.tsx
index 706e5462d..548cf9344 100644
--- a/src/frontend/screens/Observation/FieldDetails.tsx
+++ b/src/frontend/screens/Observation/FieldDetails.tsx
@@ -1,37 +1,37 @@
import * as React from 'react';
-import {View, Text, StyleSheet} from 'react-native';
-import {MEDIUM_GREY, DARK_GREY, BLACK, LIGHT_GREY} from '../../lib/styles';
+import {View, StyleSheet} from 'react-native';
+import {MEDIUM_GREY} from '../../lib/styles';
import {
FormattedFieldProp,
FormattedFieldValue,
} from '../../sharedComponents/FormattedData';
import {Field, Observation} from '@comapeo/schema';
+import {ViewStyleProp} from '../../sharedTypes';
+import {HeaderText} from '../../sharedComponents/Text/HeaderText';
+import {BodyText} from '../../sharedComponents/Text/BodyText';
export const FieldDetails = ({
fields,
observation,
+ style,
}: {
fields: Field[];
observation: Observation;
+ style?: ViewStyleProp;
}) => {
return (
{fields.map(field => {
const value = observation.tags[field.tagKey];
return (
-
-
+
+
-
-
+
+
-
+
);
})}
@@ -40,23 +40,11 @@ export const FieldDetails = ({
};
const styles = StyleSheet.create({
- fieldAnswer: {
- fontSize: 20,
- fontWeight: '100',
- },
fieldTitle: {
- color: BLACK,
- fontSize: 14,
- fontWeight: '700',
marginBottom: 10,
},
section: {
flex: 1,
- marginHorizontal: 15,
paddingVertical: 15,
},
- optionalSection: {
- borderTopColor: LIGHT_GREY,
- borderTopWidth: 1,
- },
});
diff --git a/src/frontend/screens/Observation/PresetHeader.tsx b/src/frontend/screens/Observation/PresetHeader.tsx
index edd8840a0..e2f544bd1 100644
--- a/src/frontend/screens/Observation/PresetHeader.tsx
+++ b/src/frontend/screens/Observation/PresetHeader.tsx
@@ -1,21 +1,31 @@
import React from 'react';
-import {View, Text, StyleSheet} from 'react-native';
-import {BLACK} from '../../lib/styles';
+import {View, StyleSheet} from 'react-native';
import {FormattedPresetName} from '../../sharedComponents/FormattedData';
import {PresetCircleIcon} from '../../sharedComponents/icons/PresetIcon';
import {Preset} from '@comapeo/schema';
+import {ViewStyleProp} from '../../sharedTypes';
+import {HeaderText} from '../../sharedComponents/Text/HeaderText';
-export const PresetHeader = ({preset}: {preset?: Preset}) => {
+export const PresetHeader = ({
+ preset,
+ style,
+}: {
+ preset?: Preset;
+ style?: ViewStyleProp;
+}) => {
return (
-
+
-
+
-
+
);
};
@@ -26,9 +36,6 @@ const styles = StyleSheet.create({
flexDirection: 'row',
},
categoryLabel: {
- color: BLACK,
- fontWeight: 'bold',
- fontSize: 20,
marginLeft: 10,
},
});
diff --git a/src/frontend/screens/Observation/TrackAccordian.tsx b/src/frontend/screens/Observation/TrackAccordian.tsx
new file mode 100644
index 000000000..ee11faf68
--- /dev/null
+++ b/src/frontend/screens/Observation/TrackAccordian.tsx
@@ -0,0 +1,60 @@
+import React from 'react';
+import ChainIcon from '../../images/Chain.svg';
+import {useNavigationFromRoot} from '../../hooks/useNavigationWithTypes.ts';
+import {defineMessages, useIntl} from 'react-intl';
+import {Accordian} from '../../sharedComponents/Accordian.tsx';
+import {HeaderText} from '../../sharedComponents/Text/HeaderText.tsx';
+import {TrackListItem} from '../ObservationsList/TrackListItem.tsx';
+import {useTracks} from '../../hooks/server/track.ts';
+import {View} from 'react-native';
+import {LIGHT_GREY} from '../../lib/styles.ts';
+import {findAssociatedTrack} from './findAssociatedTrack.ts';
+
+const m = defineMessages({
+ track: {
+ id: 'screens.Observation.TrackList.track',
+ defaultMessage: 'Track',
+ },
+});
+
+export function TrackAccordian({observationId}: {observationId: string}) {
+ const navigation = useNavigationFromRoot();
+ const {data: allTracks} = useTracks();
+ const track =
+ allTracks === undefined
+ ? undefined
+ : findAssociatedTrack({tracks: allTracks, observationId});
+ const {formatMessage} = useIntl();
+
+ if (!track) return null;
+
+ return (
+
+
+ {1}
+
+ {formatMessage(m.track)}
+ >
+ }
+ innerAccordianDetails={
+ {
+ navigation.push('Track', {trackId: track.docId});
+ }}
+ testID={`trackListItem:${track.docId}`}
+ />
+ }
+ />
+
+ );
+}
diff --git a/src/frontend/screens/Observation/findAssociatedTrack.ts b/src/frontend/screens/Observation/findAssociatedTrack.ts
new file mode 100644
index 000000000..259d3b834
--- /dev/null
+++ b/src/frontend/screens/Observation/findAssociatedTrack.ts
@@ -0,0 +1,13 @@
+import {type Track} from '@comapeo/schema';
+
+export function findAssociatedTrack({
+ tracks,
+ observationId,
+}: {
+ tracks: Track[];
+ observationId: string;
+}) {
+ return tracks.find(trackData =>
+ trackData.observationRefs.some(ref => ref.docId === observationId),
+ );
+}
diff --git a/src/frontend/screens/Observation/index.tsx b/src/frontend/screens/Observation/index.tsx
index 2dba46db3..bf7b8cb71 100644
--- a/src/frontend/screens/Observation/index.tsx
+++ b/src/frontend/screens/Observation/index.tsx
@@ -1,8 +1,8 @@
import * as React from 'react';
-import {Text, View, ScrollView, StyleSheet} from 'react-native';
+import {View, ScrollView, StyleSheet} from 'react-native';
import {defineMessages} from 'react-intl';
-import {BLACK, WHITE, DARK_GREY, LIGHT_GREY} from '../../lib/styles';
+import {WHITE, DARK_GREY, LIGHT_GREY, BLUE_GREY} from '../../lib/styles';
import {UIActivityIndicator} from 'react-native-indicators';
import {FormattedObservationDate} from '../../sharedComponents/FormattedData';
@@ -22,6 +22,10 @@ import {SavedPhoto} from '../../contexts/PhotoPromiseContext/types.ts';
import {ButtonFields} from './Buttons.tsx';
import {AudioAttachment} from '../../sharedTypes/audio.ts';
import {isSavedPhoto, isAudioAttachment} from '../../lib/attachmentTypeChecks';
+import {TrackAccordian} from './TrackAccordian.tsx';
+import {Divider} from '../../sharedComponents/Divider.tsx';
+import {BodyText} from '../../sharedComponents/Text/BodyText.tsx';
+import {HeaderText} from '../../sharedComponents/Text/HeaderText.tsx';
const m = defineMessages({
deleteTitle: {
@@ -89,20 +93,23 @@ export const ObservationScreen: NativeNavigationComponent<'Observation'> = ({
{/* check lat and lon are not null or undefined */}
{lat != null && lon != null && }
-
+
-
+
-
-
+
+
+ }>
+
+
{typeof observation.tags.notes === 'string' ? (
-
- {observation.tags.notes}
-
+
+ {observation.tags.notes}
+
) : null}
{attachments.length > 0 && (
= ({
)}
{fields.length > 0 && (
-
+ <>
+
+
+ >
)}
{isDeviceInfoPending || isDeviceIdPending ? (
@@ -134,29 +148,24 @@ ObservationScreen.navTitle = m.title;
const styles = StyleSheet.create({
root: {
backgroundColor: WHITE,
- flex: 1,
flexDirection: 'column',
},
scrollContent: {minHeight: '100%'},
divider: {
- backgroundColor: LIGHT_GREY,
+ backgroundColor: BLUE_GREY,
paddingVertical: 15,
},
section: {
flex: 1,
- marginHorizontal: 15,
paddingVertical: 15,
},
textNotes: {
- fontSize: 22,
color: DARK_GREY,
fontWeight: '100',
- marginLeft: 10,
+ padding: 20,
},
time: {
- color: BLACK,
backgroundColor: LIGHT_GREY,
- fontSize: 14,
paddingVertical: 10,
textAlign: 'center',
},
diff --git a/src/frontend/screens/Track/ObservationList.tsx b/src/frontend/screens/Track/ObservationList.tsx
index 8dc869134..9a7f05e83 100644
--- a/src/frontend/screens/Track/ObservationList.tsx
+++ b/src/frontend/screens/Track/ObservationList.tsx
@@ -1,19 +1,12 @@
-import React, {useState} from 'react';
-import {Pressable, StyleSheet, View} from 'react-native';
-import Animated, {
- Easing,
- FadeInUp,
- FadeOutUp,
- LinearTransition,
-} from 'react-native-reanimated';
-import {Text} from '../../sharedComponents/Text.tsx';
+import React from 'react';
import ChainIcon from '../../images/Chain.svg';
-import Chevrondown from '../../images/chevrondown.svg';
-import ChevrondownDefault from '../../images/chevrondown-expanded.svg';
+
import {Observation} from '@comapeo/schema';
import {ObservationListItem} from '../ObservationsList/ObservationListItem.tsx';
import {useNavigationFromRoot} from '../../hooks/useNavigationWithTypes.ts';
import {defineMessages, useIntl} from 'react-intl';
+import {Accordian} from '../../sharedComponents/Accordian.tsx';
+import {HeaderText} from '../../sharedComponents/Text/HeaderText.tsx';
interface TrackObservation {
observations: Observation[];
@@ -24,60 +17,43 @@ const m = defineMessages({
id: 'screens.Track.ObservationList.observations',
defaultMessage: 'Observations',
},
+ observation: {
+ id: 'screens.Track.ObservationList.observation',
+ defaultMessage: 'Observation',
+ },
});
export function ObservationList({observations}: TrackObservation) {
- const [expanded, setExpanded] = useState(false);
const navigation = useNavigationFromRoot();
const {formatMessage} = useIntl();
- const Icon = expanded ? Chevrondown : ChevrondownDefault;
+ const numberOfObservations = observations.length;
return (
-
- {
- setExpanded(prev => !prev);
- }}
- style={[styles.wrapper, styles.elementWrapper]}>
-
- {observations.length}
+
+ {numberOfObservations}
- {formatMessage(m.observations)}
-
-
-
-
- {expanded &&
- observations.map((observation, index) => (
- {
- navigation.navigate('Observation', {
- observationId: observation.docId,
- });
- }}
- testID={'id' + index}
- />
- ))}
-
-
+
+ {formatMessage(
+ numberOfObservations === 1 ? m.observation : m.observations,
+ )}
+
+ >
+ }
+ innerAccordianDetails={observations.map((observation, index) => (
+ {
+ navigation.push('Observation', {
+ observationId: observation.docId,
+ });
+ }}
+ testID={'id' + index}
+ />
+ ))}
+ />
);
}
-
-const styles = StyleSheet.create({
- wrapper: {
- flexDirection: 'row',
- alignItems: 'center',
- },
- elementWrapper: {
- justifyContent: 'space-between',
- paddingVertical: 10,
- paddingHorizontal: 15,
- },
- text: {
- fontSize: 16,
- fontWeight: '700',
- },
-});
diff --git a/src/frontend/screens/Track/index.tsx b/src/frontend/screens/Track/index.tsx
index 12b1588ce..6b66dc242 100644
--- a/src/frontend/screens/Track/index.tsx
+++ b/src/frontend/screens/Track/index.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import {StyleSheet, View, Text, SafeAreaView} from 'react-native';
-import {BLACK, DARK_GREY} from '../../lib/styles.ts';
+import {BLACK, BLUE_GREY, DARK_GREY} from '../../lib/styles.ts';
import TrackIcon from '../../images/Track.svg';
import {FormattedMessage, MessageDescriptor, defineMessages} from 'react-intl';
@@ -124,7 +124,7 @@ export const styles = StyleSheet.create({
flexDirection: 'column',
justifyContent: 'space-between',
},
- divider: {borderBottomColor: '#CCCCD6', borderBottomWidth: 1},
+ divider: {borderBottomColor: BLUE_GREY, borderBottomWidth: 1},
trackTitleWrapper: {
marginVertical: 10,
marginHorizontal: 15,
diff --git a/src/frontend/sharedComponents/Accordian.tsx b/src/frontend/sharedComponents/Accordian.tsx
new file mode 100644
index 000000000..1d785ac55
--- /dev/null
+++ b/src/frontend/sharedComponents/Accordian.tsx
@@ -0,0 +1,53 @@
+import React, {useState} from 'react';
+import {Pressable, StyleSheet, View} from 'react-native';
+import Animated, {
+ Easing,
+ FadeInUp,
+ FadeOutUp,
+ LinearTransition,
+} from 'react-native-reanimated';
+import Chevrondown from '../images/chevrondown.svg';
+import ChevrondownDefault from '../images/chevrondown-expanded.svg';
+import {ViewStyleProp} from '../sharedTypes';
+
+interface AccordianProps {
+ title: React.ReactNode;
+ innerAccordianDetails: React.ReactNode;
+ style?: ViewStyleProp;
+}
+
+export function Accordian({
+ title,
+ innerAccordianDetails,
+ style,
+}: AccordianProps) {
+ const [expanded, setExpanded] = useState(false);
+ const Icon = expanded ? Chevrondown : ChevrondownDefault;
+
+ return (
+
+ {
+ setExpanded(prev => !prev);
+ }}
+ style={[styles.wrapper, styles.elementWrapper, style]}>
+ {title}
+
+
+
+ {expanded && innerAccordianDetails}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ wrapper: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ elementWrapper: {
+ justifyContent: 'space-between',
+ },
+});