diff --git a/android/app/src/main/assets/fonts/INatIcon.ttf b/android/app/src/main/assets/fonts/INatIcon.ttf index 7f84059b8..d7021328c 100644 Binary files a/android/app/src/main/assets/fonts/INatIcon.ttf and b/android/app/src/main/assets/fonts/INatIcon.ttf differ diff --git a/android/link-assets-manifest.json b/android/link-assets-manifest.json index 591972043..e566f2a4d 100644 --- a/android/link-assets-manifest.json +++ b/android/link-assets-manifest.json @@ -3,7 +3,7 @@ "data": [ { "path": "assets/fonts/INatIcon.ttf", - "sha1": "b06f8d8fc228469707681cbbdde665868437d031" + "sha1": "e6311871bfa114078a3adb8900359160b001f0ba" }, { "path": "assets/fonts/Whitney-Book-Italic.otf", diff --git a/assets/fonts/INatIcon.ttf b/assets/fonts/INatIcon.ttf index 7f84059b8..d7021328c 100644 Binary files a/assets/fonts/INatIcon.ttf and b/assets/fonts/INatIcon.ttf differ diff --git a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj index 9023e443c..b37c0aff8 100644 --- a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj +++ b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 00E356F31AD99517003FC87E /* iNaturalistReactNativeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* iNaturalistReactNativeTests.m */; }; 03BE06B8FED98F5CD10273BB /* libPods-iNaturalistReactNative.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20043186B311EE211FEB259A /* libPods-iNaturalistReactNative.a */; }; + 08387561362A4099A1D58774 /* INatIcon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 121B82B7918941189871CFD5 /* INatIcon.ttf */; }; 13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; }; 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; 13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; }; @@ -40,7 +41,6 @@ BA2479FA3D7B40A7BEF7B3CD /* Whitney-Medium-Pro.otf in Resources */ = {isa = PBXBuildFile; fileRef = D09FA3A0162844FF80A5EF96 /* Whitney-Medium-Pro.otf */; }; CD4D097140634EE091BB5818 /* Whitney-Book.otf in Resources */ = {isa = PBXBuildFile; fileRef = 8EB5F71FA6C34855B03FE3FB /* Whitney-Book.otf */; }; CD871292AF3144DC9F9240BA /* Whitney-Semibold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 4A5C60A9F7DF42F49CED09FC /* Whitney-Semibold.otf */; }; - D2853A71785A4FF6AB137F4C /* INatIcon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = CC5CF9E037E74ADF9CF9710B /* INatIcon.ttf */; }; E23E0899594A7C6DF680FFDB /* libPods-iNaturalistReactNative-ShareExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A336AF0ADEAE537AB1B73F98 /* libPods-iNaturalistReactNative-ShareExtension.a */; }; /* End PBXBuildFile section */ @@ -81,6 +81,7 @@ 00E356F21AD99517003FC87E /* iNaturalistReactNativeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iNaturalistReactNativeTests.m; sourceTree = ""; }; 097C2902AB1249AA9846CED3 /* Whitney-MediumItalic-Pro.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Whitney-MediumItalic-Pro.otf"; path = "../assets/fonts/Whitney-MediumItalic-Pro.otf"; sourceTree = ""; }; 0DD877E5F25945BCB6707357 /* Whitney-SemiboldItalic-Pro.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Whitney-SemiboldItalic-Pro.otf"; path = "../assets/fonts/Whitney-SemiboldItalic-Pro.otf"; sourceTree = ""; }; + 121B82B7918941189871CFD5 /* INatIcon.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = INatIcon.ttf; path = ../assets/fonts/INatIcon.ttf; sourceTree = ""; }; 13B07F961A680F5B00A75B9A /* iNaturalistReactNative.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iNaturalistReactNative.app; sourceTree = BUILT_PRODUCTS_DIR; }; 13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = iNaturalistReactNative/AppDelegate.h; sourceTree = ""; }; 13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = iNaturalistReactNative/AppDelegate.mm; sourceTree = ""; }; @@ -117,7 +118,6 @@ B8FC28F6DD66FAD52B79E072 /* Pods-iNaturalistReactNative.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iNaturalistReactNative.debug.xcconfig"; path = "Target Support Files/Pods-iNaturalistReactNative/Pods-iNaturalistReactNative.debug.xcconfig"; sourceTree = ""; }; BA9D41ECEBFA4C38B74009B3 /* Whitney-Light-Pro.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Whitney-Light-Pro.otf"; path = "../assets/fonts/Whitney-Light-Pro.otf"; sourceTree = ""; }; C13A1C5596344F5F8830E524 /* Whitney-Light.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Whitney-Light.otf"; path = "../assets/fonts/Whitney-Light.otf"; sourceTree = ""; }; - CC5CF9E037E74ADF9CF9710B /* INatIcon.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = INatIcon.ttf; path = ../assets/fonts/INatIcon.ttf; sourceTree = ""; }; D09FA3A0162844FF80A5EF96 /* Whitney-Medium-Pro.otf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "Whitney-Medium-Pro.otf"; path = "../assets/fonts/Whitney-Medium-Pro.otf"; sourceTree = ""; }; D7AE5BDBC584A83878A04344 /* Pods-iNaturalistReactNative-ShareExtension.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iNaturalistReactNative-ShareExtension.debug.xcconfig"; path = "Target Support Files/Pods-iNaturalistReactNative-ShareExtension/Pods-iNaturalistReactNative-ShareExtension.debug.xcconfig"; sourceTree = ""; }; D8663889EABFBFC3077401E3 /* Pods-iNaturalistReactNative-ShareExtension.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iNaturalistReactNative-ShareExtension.release.xcconfig"; path = "Target Support Files/Pods-iNaturalistReactNative-ShareExtension/Pods-iNaturalistReactNative-ShareExtension.release.xcconfig"; sourceTree = ""; }; @@ -213,7 +213,6 @@ 376267522BB609590059CCF1 /* Recovered References */ = { isa = PBXGroup; children = ( - CC5CF9E037E74ADF9CF9710B /* INatIcon.ttf */, ); name = "Recovered References"; sourceTree = ""; @@ -287,6 +286,7 @@ 63C92696AD524E14B655C56A /* Whitney-Semibold-Italic.otf */, 4A5C60A9F7DF42F49CED09FC /* Whitney-Semibold.otf */, 0DD877E5F25945BCB6707357 /* Whitney-SemiboldItalic-Pro.otf */, + 121B82B7918941189871CFD5 /* INatIcon.ttf */, ); name = Resources; sourceTree = ""; @@ -418,7 +418,6 @@ BA2479FA3D7B40A7BEF7B3CD /* Whitney-Medium-Pro.otf in Resources */, 8F1AC6772BC1B610002F994B /* PrivacyInfo.xcprivacy in Resources */, 4FB3B444D46A4115B867B9CC /* inaturalisticons.ttf in Resources */, - D2853A71785A4FF6AB137F4C /* INatIcon.ttf in Resources */, B197D1A2DFD9440C9581D513 /* Whitney-Book-Pro.otf in Resources */, 42832120F7C944B980FD13BC /* Whitney-MediumItalic-Pro.otf in Resources */, B4518F59A698433CA0271ED5 /* Whitney-Semibold-Pro.otf in Resources */, @@ -432,6 +431,7 @@ A64FC9A5388A49568EC932B6 /* Whitney-Semibold-Italic.otf in Resources */, CD871292AF3144DC9F9240BA /* Whitney-Semibold.otf in Resources */, 4698CEACBD4F4A859CAB0694 /* Whitney-SemiboldItalic-Pro.otf in Resources */, + 08387561362A4099A1D58774 /* INatIcon.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/iNaturalistReactNative/Info.plist b/ios/iNaturalistReactNative/Info.plist index cfbf17a98..debc5e2f3 100644 --- a/ios/iNaturalistReactNative/Info.plist +++ b/ios/iNaturalistReactNative/Info.plist @@ -40,13 +40,12 @@ https mailto googlegmail - inbox-gmail + inbox-gmail LSRequiresIPhoneOS NSAppTransportSecurity - NSAllowsArbitraryLoads NSAllowsLocalNetworking @@ -74,7 +73,6 @@ Whitney-Medium-Pro.otf Whitney-BookItalic-Pro.otf inaturalisticons.ttf - INatIcon.ttf Whitney-Book-Pro.otf Whitney-MediumItalic-Pro.otf Whitney-Semibold-Pro.otf @@ -88,6 +86,7 @@ Whitney-Semibold-Italic.otf Whitney-Semibold.otf Whitney-SemiboldItalic-Pro.otf + INatIcon.ttf UILaunchStoryboardName LaunchScreen diff --git a/ios/link-assets-manifest.json b/ios/link-assets-manifest.json index 591972043..e566f2a4d 100644 --- a/ios/link-assets-manifest.json +++ b/ios/link-assets-manifest.json @@ -3,7 +3,7 @@ "data": [ { "path": "assets/fonts/INatIcon.ttf", - "sha1": "b06f8d8fc228469707681cbbdde665868437d031" + "sha1": "e6311871bfa114078a3adb8900359160b001f0ba" }, { "path": "assets/fonts/Whitney-Book-Italic.otf", diff --git a/package.json b/package.json index 9a7dfaf86..dfa8a5948 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "ios": "react-native run-ios", "ios:release": "npx react-native run-ios --device --mode Release", "start": "react-native start", + "clean": "./scripts/clean.sh", "clean-start": "npx react-native clean-project-auto && npx pod-install && npm start -- --reset-cache", "test": "jest", "lint": "npm run lint:eslint && npm run lint:flow", diff --git a/scripts/clean.sh b/scripts/clean.sh new file mode 100755 index 000000000..cb0697dab --- /dev/null +++ b/scripts/clean.sh @@ -0,0 +1,23 @@ +echo "iOS simulator cache" +rm -rf ~/Library/Developer/CoreSimulator/Caches + +echo "React-native cache" +rm -rf $TMPDIR/react-* + +echo "Metro bundler cache" +rm -rf $TMPDIR/metro-* + +echo "Watchman cache" +watchman watch-del-all + +echo "iOS build folder" +rm -rf ios/build + +echo "system iOS pods cache" +(cd ios && pod cache clean --all) + +echo "Android build folder" +rm -rf android/build + +echo "Android clean project" +(cd android && ./gradlew clean) diff --git a/src/components/Notifications/NotificationsContainer.js b/src/components/Notifications/NotificationsContainer.js index bfe1aa312..519b10b13 100644 --- a/src/components/Notifications/NotificationsContainer.js +++ b/src/components/Notifications/NotificationsContainer.js @@ -34,6 +34,7 @@ const NotificationsContainer = (): Node => { isOnline={isOnline} isLoading={isInitialLoading || isFetching} isError={isError} + reload={refetch} /> ); }; diff --git a/src/components/Notifications/NotificationsList.js b/src/components/Notifications/NotificationsList.js index a7ab811e5..2957d4844 100644 --- a/src/components/Notifications/NotificationsList.js +++ b/src/components/Notifications/NotificationsList.js @@ -3,7 +3,7 @@ import { FlashList } from "@shopify/flash-list"; import InfiniteScrollLoadingWheel from "components/MyObservations/InfiniteScrollLoadingWheel"; import NotificationsListItem from "components/Notifications/NotificationsListItem"; -import { Body2, ViewWrapper } from "components/SharedComponents"; +import { Body2, OfflineNotice, ViewWrapper } from "components/SharedComponents"; import { View } from "components/styledComponents"; import type { Node } from "react"; import React, { useCallback } from "react"; @@ -14,7 +14,8 @@ type Props = { isError?: boolean, isLoading?: boolean, isOnline: boolean, - onEndReached: Function + onEndReached: Function, + reload: Function }; const NotificationsList = ( { @@ -22,7 +23,8 @@ const NotificationsList = ( { isError, isLoading, isOnline, - onEndReached + onEndReached, + reload }: Props ): Node => { const { t } = useTranslation( ); @@ -43,10 +45,12 @@ const NotificationsList = ( { const renderEmptyComponent = useCallback( ( ) => { if ( isLoading ) return null; - let msg = t( "No-Notifications-Found" ); if ( !isOnline ) { - msg = t( "Offline-No-Notifications" ); - } else if ( isError ) { + return ; + } + + let msg = t( "No-Notifications-Found" ); + if ( isError ) { msg = t( "Something-went-wrong" ); } @@ -55,11 +59,20 @@ const NotificationsList = ( { isError, isLoading, isOnline, + reload, t ] ); + if ( !data || data.length === 0 ) { + return ( + + {renderEmptyComponent( )} + + ); + } + return ( - + item.id} @@ -69,7 +82,6 @@ const NotificationsList = ( { onEndReached={onEndReached} refreshing={isLoading} ListFooterComponent={renderFooter} - ListEmptyComponent={renderEmptyComponent} /> ); diff --git a/src/components/ObsDetails/DQAContainer.js b/src/components/ObsDetails/DQAContainer.js index a149ca27f..36f8193e5 100644 --- a/src/components/ObsDetails/DQAContainer.js +++ b/src/components/ObsDetails/DQAContainer.js @@ -1,4 +1,8 @@ // @flow +import { + refresh as refreshNetInfo, + useNetInfo +} from "@react-native-community/netinfo"; import { useRoute } from "@react-navigation/native"; import { useQueryClient } from "@tanstack/react-query"; import { faveObservation, unfaveObservation } from "api/observations"; @@ -17,7 +21,6 @@ import * as React from "react"; import Observation from "realmModels/Observation"; import { useAuthenticatedMutation, - useIsConnected, useLocalObservation } from "sharedHooks"; import useRemoteObservation, { @@ -26,7 +29,7 @@ import useRemoteObservation, { const DQAContainer = ( ): React.Node => { const queryClient = useQueryClient( ); - const isOnline = useIsConnected( ); + const { isInternetReachable: isOnline } = useNetInfo( ); const { params } = useRoute( ); const { observationUUID } = params; const [qualityMetrics, setQualityMetrics] = useState( undefined ); @@ -299,6 +302,8 @@ const DQAContainer = ( ): React.Node => { removeNeedsIDVote={removeNeedsIDVote} ifMajorityAgree={ifMajorityAgree} checkTest={checkTest} + isOnline={isOnline} + recheckIsOnline={refreshNetInfo} /> { } }; type Props = { - qualityMetrics: Object, + checkTest: Function, + ifMajorityAgree: Function, + isOnline?: boolean, loadingAgree: boolean, loadingDisagree: boolean, loadingMetric: string, qualityGrade: string, - ifMajorityAgree: Function, - checkTest: Function, - setMetricVote: Function, + qualityMetrics: Object, + recheckIsOnline: Function, removeMetricVote: Function, + removeNeedsIDVote: Function, + setMetricVote: Function, setNeedsIDVote: Function, - removeNeedsIDVote: Function } const DataQualityAssessment = ( { - qualityMetrics, + checkTest, + ifMajorityAgree, + isOnline, loadingAgree, loadingDisagree, loadingMetric, qualityGrade, - ifMajorityAgree, - checkTest, - setMetricVote, + qualityMetrics, + recheckIsOnline, removeMetricVote, - setNeedsIDVote, - removeNeedsIDVote + removeNeedsIDVote, + setMetricVote, + setNeedsIDVote }: Props ): Node => { const isResearchGrade = qualityGrade === "research"; const theme = useTheme( ); @@ -107,6 +113,14 @@ const DataQualityAssessment = ( { ); }; + if ( isOnline === false ) { + return ( + + recheckIsOnline( )} /> + + ); + } + return ( diff --git a/src/components/ObsDetails/PhotoContainer.js b/src/components/ObsDetails/PhotoContainer.js index 4d07ac1ac..54efa13c0 100644 --- a/src/components/ObsDetails/PhotoContainer.js +++ b/src/components/ObsDetails/PhotoContainer.js @@ -1,11 +1,10 @@ // @flow import classnames from "classnames"; -import { ActivityIndicator } from "components/SharedComponents"; +import { ActivityIndicator, OfflineNotice } from "components/SharedComponents"; import { Image, Pressable } from "components/styledComponents"; import type { Node } from "react"; import React, { useState } from "react"; -import IconMaterial from "react-native-vector-icons/MaterialIcons"; import { useTranslation } from "sharedHooks"; type Props = { @@ -79,17 +78,6 @@ const PhotoContainer = ( { photo, onPress, style }: Props ): Node => { )} /> ); - const offlineNotice = loadSuccess === false && ( - - ); return ( { > {loadingIndicator} {image} - {offlineNotice} + {loadSuccess === false && ( + setLoadSuccess( null )} + color="white" + /> + )} ); }; diff --git a/src/components/ObsDetails/SoundContainer.js b/src/components/ObsDetails/SoundContainer.js index c58d08573..bd37d8e20 100644 --- a/src/components/ObsDetails/SoundContainer.js +++ b/src/components/ObsDetails/SoundContainer.js @@ -1,5 +1,9 @@ +import { + refresh as refreshNetInfo, + useNetInfo +} from "@react-native-community/netinfo"; import { useFocusEffect } from "@react-navigation/native"; -import { Body1, INatIconButton } from "components/SharedComponents"; +import { Body1, INatIconButton, OfflineNotice } from "components/SharedComponents"; import { View } from "components/styledComponents"; import React, { useCallback, @@ -14,6 +18,7 @@ import { useTranslation } from "sharedHooks"; const logger = log.extend( "SoundContainer" ); const SoundContainer = ( { sizeClass, isVisible, sound } ) => { + const { isInternetReachable } = useNetInfo( ); const playerRef = useRef( new AudioRecorderPlayer( ) ); const player = playerRef.current; const { t } = useTranslation( ); @@ -138,6 +143,15 @@ const SoundContainer = ( { sizeClass, isVisible, sound } ) => { }, [stopSound] ) ); const playBackPercent = ( playBackState.currentPosition / ( playBackState.duration || 1 ) ) * 100; + + if ( isInternetReachable === false ) { + return ( + refreshNetInfo( )} + color="white" + /> + ); + } return ( { + const { t } = useTranslation( ); + if ( typeof ( onPress ) !== "function" ) { + throw new Error( "OfflineNotice needs an onPress function" ); + } + return ( + + + + { t( "You-are-offline-Tap-to-try-again" ) } + + + ); +}; + +export default OfflineNotice; diff --git a/src/components/SharedComponents/index.js b/src/components/SharedComponents/index.js index 438f27c52..c53263132 100644 --- a/src/components/SharedComponents/index.js +++ b/src/components/SharedComponents/index.js @@ -34,6 +34,7 @@ export { default as Mortal } from "./Mortal"; export { default as ObservationLocation } from "./ObservationLocation"; export { default as ObservationsFlashList } from "./ObservationsFlashList/ObservationsFlashList"; export { default as ObsStatus } from "./ObsStatus"; +export { default as OfflineNotice } from "./OfflineNotice"; export { default as OverviewCounts } from "./OverviewCounts"; export { default as PermissionGate } from "./PermissionGate"; export { default as PhotoCount } from "./PhotoCount"; diff --git a/src/components/UiLibrary.js b/src/components/UiLibrary.js index 957900277..4e3e43f22 100644 --- a/src/components/UiLibrary.js +++ b/src/components/UiLibrary.js @@ -480,8 +480,6 @@ const UiLibrary = (): Node => { Research - {/* TODO: refactor to not have color prop because we only need black and white */} - {/* TODO: better to access the color from theme here */} @@ -595,7 +593,6 @@ const UiLibrary = (): Node => { - IdentificationsCount @@ -645,11 +642,16 @@ const UiLibrary = (): Node => { Confidence Interval Taxon Result - + Taxon w/ photo - + Iconic taxon - + Iconic Taxon Chooser { before={