From 7d20f6aa81297c77ee2a6fcbc64c1b7796fa68eb Mon Sep 17 00:00:00 2001 From: Amanda Bullington <35536439+albullington@users.noreply.github.com> Date: Fri, 13 Oct 2023 14:46:05 -0700 Subject: [PATCH] Suggestions (using server data) (#821) * UI overhaul for Suggestions * Updates to suggestions * Update permissions library and Podfile * Get tests passing * Add tests for suggestions * Update snapshot * Add comment prompt & box to TaxonSearch * Add empty state * Nav and loading fixes * Add more tests to Suggestions flow * Fix tests * Fix tests --- .../app/src/main/assets/fonts/INatIcon.ttf | Bin 27048 -> 27312 bytes android/link-assets-manifest.json | 2 +- assets/fonts/INatIcon.ttf | Bin 27048 -> 27312 bytes ios/Podfile | 37 ++++ ios/Podfile.lock | 10 +- .../project.pbxproj | 8 +- ios/link-assets-manifest.json | 2 +- package-lock.json | 161 ++---------------- package.json | 4 +- src/api/computerVision.js | 26 +++ src/components/AddID/AddID.js | 88 ---------- src/components/AddID/AddIDContainer.js | 136 --------------- src/components/AddID/TaxonSearch.js | 76 --------- .../Camera/StandardCamera/PhotoCarousel.js | 6 +- .../Camera/StandardCamera/PhotoPreview.js | 4 +- src/components/MediaViewer/MediaViewer.js | 14 +- .../ObsDetails/ActivityTab/FloatingButtons.js | 6 +- src/components/ObsDetails/ObsDetails.js | 6 +- .../ObsDetails/ObsDetailsContainer.js | 31 ++-- src/components/ObsEdit/EvidenceList.js | 22 ++- src/components/ObsEdit/EvidenceSection.js | 10 +- .../ObsEdit/EvidenceSectionContainer.js | 39 ++--- .../ObsEdit/IdentificationSection.js | 14 +- .../SharedComponents/ConfidenceInterval.js | 4 +- .../SharedComponents/INatIcon/glyphmap.json | 145 ++++++++-------- .../ObservationsFlashList/ObsImagePreview.js | 6 +- src/components/SharedComponents/SearchBar.js | 2 +- .../SharedComponents/TaxonResult.js | 53 +++--- src/components/SharedComponents/index.js | 1 + .../Suggestions/AddCommentPrompt.js | 53 ++++++ src/components/Suggestions/Attribution.js | 58 +++++++ src/components/Suggestions/CommentBox.js | 38 +++++ .../Suggestions/PhotoSelectionList.js | 62 +++++++ src/components/Suggestions/Suggestions.js | 80 +++++++++ .../Suggestions/SuggestionsContainer.js | 74 ++++++++ src/components/Suggestions/SuggestionsList.js | 104 +++++++++++ src/components/Suggestions/TaxonSearch.js | 83 +++++++++ src/components/TaxonDetails/TaxonDetails.js | 21 +-- src/i18n/l10n/en.ftl | 40 +++-- src/i18n/l10n/en.ftl.json | 38 +++-- src/i18n/strings.ftl | 40 +++-- src/images/icons/edit-comment.svg | 7 + .../ObservationsStackNavigator.js | 6 - .../StackNavigators/SharedStackScreens.js | 37 +++- src/providers/ObsEditProvider.js | 122 +++++++++++-- src/sharedHelpers/flattenUploadParams.js | 52 ++++++ .../ObsEditWithoutProvider.test.js | 4 +- .../components/ObsDetails/ObsDetails.test.js | 45 ++++- .../components/ObsEdit/EvidenceList.test.js | 52 +++--- .../IdentificationsCount.test.js.snap | 140 +++++++-------- .../__snapshots__/InlineUser.test.js.snap | 4 +- .../__snapshots__/ObsGridItem.test.js.snap | 22 ++- .../__snapshots__/TaxonResult.test.js.snap | 38 +++-- .../Suggestions/Attribution.test.js | 48 ++++++ .../Suggestions/Suggestions.test.js | 110 ++++++++++++ .../Suggestions/SuggestionsList.test.js | 93 ++++++++++ .../TaxonSearch.test.js} | 71 +++----- 57 files changed, 1590 insertions(+), 865 deletions(-) create mode 100644 src/api/computerVision.js delete mode 100644 src/components/AddID/AddID.js delete mode 100644 src/components/AddID/AddIDContainer.js delete mode 100644 src/components/AddID/TaxonSearch.js create mode 100644 src/components/Suggestions/AddCommentPrompt.js create mode 100644 src/components/Suggestions/Attribution.js create mode 100644 src/components/Suggestions/CommentBox.js create mode 100644 src/components/Suggestions/PhotoSelectionList.js create mode 100644 src/components/Suggestions/Suggestions.js create mode 100644 src/components/Suggestions/SuggestionsContainer.js create mode 100644 src/components/Suggestions/SuggestionsList.js create mode 100644 src/components/Suggestions/TaxonSearch.js create mode 100644 src/images/icons/edit-comment.svg create mode 100644 src/sharedHelpers/flattenUploadParams.js create mode 100644 tests/unit/components/Suggestions/Attribution.test.js create mode 100644 tests/unit/components/Suggestions/Suggestions.test.js create mode 100644 tests/unit/components/Suggestions/SuggestionsList.test.js rename tests/unit/components/{AddID/AddID.test.js => Suggestions/TaxonSearch.test.js} (68%) diff --git a/android/app/src/main/assets/fonts/INatIcon.ttf b/android/app/src/main/assets/fonts/INatIcon.ttf index 53d4358ad3fa5b8042be70e5a976c4d927f4f78a..11ec47639e7040a0b07e1a3d8c376040e8a5d8fd 100644 GIT binary patch delta 719 zcmX|8T}V@57=FLA^E3Bzj&sMex!m75$GIJUn&xkY>B2IQ5R2GF7p`sU+KwA(Vo=yc zpooNWC?w1*B1F0{79la{=Sm_%Xi7vE5mXXhApJI2*8~A4u54`y>wRv76NLx^Gkx8`9cqa$kFeDD5Rh7x7qAILYjL>JMqy z#fkzTsjVAMPmCFJCf0P)v}RV!+w2Nw=Th7wZmYPjc+V2H)xc-7W8P^3Izs+STj2=E}LgyBpkN?rl$tXTg*A^4>A;3sDiL#a~jlG$nnO1vwA)ZB&SV=QtEo#PeQ!~z5@|0uZ zj@0V42{fW@vV&qcPHGj!)-``uZ&x7De6z z2Z(nD%k2tB>K)~zOe38$*b;OmuwVXg&>O<9ckdLC3R&~VNp;0Ph_FZKJ@;-6D{h?T>v$CR03?7_Et>RW&}G?Hu3{%O#@e;Y74XO{Y>QTaAH%djSJO z=!T5c#FU6N3!H#z9{{mg22g;hfUzFP-UGxc8M!4DHPQdC0nK{@#A|Z$lM{mi0Qm}eiMgo(YElM285j}+7#Ntu3i69fyytiAV_->pi$}gzpEx0e=$zJ%J{HCxQt==Y+LHSVYc=o)e1^Un8+ka+Xwtw3_rinFX=}vXf-* z$Z^Pd$Q8*QkY|!Fkw2oKp^%`kK#@t&O>vu&m{N_>6=f0S4CO`2=Tu}=LR6Ym7O0$2 z`KCHg%|xwC?S;CQ`Xu##0vaV6XEb{>pJ+*FC1}mky0Li+H>=p@g-TBxfj(|axwbhj k^%3J{-*jO{eu2#7{JhL$-Q4_~oYLaV&3PG4OiT<604BeM0ssI2 diff --git a/android/link-assets-manifest.json b/android/link-assets-manifest.json index 80f6513dc..4e7253084 100644 --- a/android/link-assets-manifest.json +++ b/android/link-assets-manifest.json @@ -3,7 +3,7 @@ "data": [ { "path": "assets/fonts/INatIcon.ttf", - "sha1": "62da083366bb9e2e214e271c04fc5823a467b396" + "sha1": "d3d24b237a7ef666ef81aff93cde57e534a676a3" }, { "path": "assets/fonts/Whitney-BookItalic-Pro.otf", diff --git a/assets/fonts/INatIcon.ttf b/assets/fonts/INatIcon.ttf index c3b74bd0c77ce07223bff3aaece202a5537e5625..11ec47639e7040a0b07e1a3d8c376040e8a5d8fd 100644 GIT binary patch delta 699 zcmX|8T}V@57=GWg^E3B%oI9S)<^Ik&&h2P6&EE{ug=HWi7O{&iT-!|BrlUj*3cCmt zkx&kWgqcNzNEgN;BnJIlNkj-uiRdDNO2Ui05$Zzw)Bh8-z)>@<^i~)7gJaJt#2b60LEtk=jH|y(Rk#^Vwh;Z5Ht>u0hPfU zq|Om21L={m>l6E%#BZBmB{dw2-VaaC0T@0K{!=4a(3teUqh%e;e6@sIe;;@;w2Ys8wjg>4JMSZP=ci|Y<=S)J43 ze0Sh(d9O=w-E_0=9``kO&i&m}?-})Md7HiS-n>unjrv|lsx&42lDp(d`LiM_VP!^H zQ+6waiuQ_2{u2L^|7Yd-%7p+c1TF`*g6%4(5p`P4S6QmaYh@RWjM&8h?C)bWR)ZaC z;RqZDLfIq>mI|T=IhJcYhIa9AT^rWdAXndpb#4?aC`*+rC)HpRp&Oe>){-Zk<9Fm1 zpIu~;wJT1B<9WG7l3G>+ojskwU}wN$mJXSX9EDU-32QGqqc?J#QQx39=*ev$B^jXL z3Y9xlp4L0dX@y0dE7Tlv4JxgY)Y{c5Q7T0jaXQ){*h*^4n8Ko-`Jd=Qn|QTaAH%djSJO z=!T5c#FU6N3!H#z9{{mg22g;hfUzFP-UGxc8M!4DHPQdC0nK{@#A|Z$lM{mi0Qm}eiMgo(YElM285j}+7#Ntu3i69fzD+q*%D|AE!N9;~ zJ8_RPZ#DxXP%RKE@AxS+@r@y4_GCH6jf`27FEDP~+{L8IsHp~4!N91%zzk;T0BJ6u zacT@K3}1kHS%46z|6|tV8_Z8P?`Pp*XX#}322mPoYCyje4-_xm7q0C>&E6S+^k}o7b-n*1p2rw<=W=B Q)JKe)ebbYfnSl-m0CNU@D*ylh diff --git a/ios/Podfile b/ios/Podfile index c7d15287b..a874fee11 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,11 +1,48 @@ # frozen_string_literal: true +# setup instructions from https://www.npmjs.com/package/react-native-permissions +def node_require(script) + # Resolve script with node to allow for hoisting + require Pod::Executable.execute_command('node', ['-p', + "require.resolve( + '#{script}', + {paths: [process.argv[1]]}, + )", __dir__]).strip +end + +node_require('react-native/scripts/react_native_pods.rb') +node_require('react-native-permissions/scripts/setup.rb') + require_relative "../node_modules/react-native/scripts/react_native_pods" require_relative "../node_modules/@react-native-community/cli-platform-ios/native_modules" +require_relative '../node_modules/react-native-permissions/scripts/setup' platform :ios, min_ios_version_supported prepare_react_native_project! +# ⬇️ uncomment wanted permissions +setup_permissions([ + # 'AppTrackingTransparency', + # 'BluetoothPeripheral', + # 'Calendars', + 'Camera', + # 'Contacts', + # 'FaceID', + 'LocationAccuracy', + 'LocationAlways', + 'LocationWhenInUse', + 'MediaLibrary', + 'Microphone', + # 'Motion', + # 'Notifications', + 'PhotoLibrary', + 'PhotoLibraryAddOnly', + # 'Reminders', + # 'Siri', + # 'SpeechRecognition', + # 'StoreKit', +]) + # If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set. # because `react-native-flipper` depends on (FlipperKit,...) that will be excluded # diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 9152ef7ec..5709c2531 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -418,7 +418,7 @@ PODS: - React-Core - RNLocalize (2.2.6): - React-Core - - RNPermissions (3.8.0): + - RNPermissions (3.10.0): - React-Core - RNReanimated (2.17.0): - DoubleConversion @@ -447,7 +447,7 @@ PODS: - React-RCTText - ReactCommon/turbomodule/core - Yoga - - RNScreens (3.20.0): + - RNScreens (3.21.1): - React-Core - React-RCTImage - RNShareMenu (6.0.0): @@ -764,9 +764,9 @@ SPEC CHECKSUMS: RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 6e4dc6b7ab3a385386d4e36228bd065e5a611394 RNLocalize: d4b8af4e442d4bcca54e68fc687a2129b4d71a81 - RNPermissions: 46f97a9fb7a4ce9a3fbf3a9dde0e6f83eb7bd170 + RNPermissions: 0332875c444efe864dd97071dc848529bd7cc692 RNReanimated: f186e85d9f28c9383d05ca39e11dd194f59093ec - RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f + RNScreens: d3675ab2878704de70c9dae57fa5d024802404cc RNShareMenu: cb9dac548c8bf147d06f0bf07296ad51ea9f5fc3 RNSVG: 53c661b76829783cdaf9b7a57258f3d3b4c28315 RNVectorIcons: fcc2f6cb32f5735b586e66d14103a74ce6ad61f8 @@ -774,6 +774,6 @@ SPEC CHECKSUMS: VisionCameraPluginInatVision: 79bb258db75218889c74d0897ecba676492c4def Yoga: 39310a10944fc864a7550700de349183450f8aaa -PODFILE CHECKSUM: 12011c0d1d148a45ad35fd9c47bc095139411027 +PODFILE CHECKSUM: 77ed9526d4011b245ce5afa1ea331dea4c67d753 COCOAPODS: 1.13.0 diff --git a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj index 4e52a73c3..d76339de8 100644 --- a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj +++ b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj @@ -22,9 +22,9 @@ 8B65ED3129F575C10054CCEF /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8B65ED2F29F575C10054CCEF /* MainInterface.storyboard */; }; 8B65ED3529F575C10054CCEF /* iNaturalistReactNative-ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 8B65ED2B29F575C10054CCEF /* iNaturalistReactNative-ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 8B65ED3B29F575FE0054CCEF /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8B65ED3A29F575FE0054CCEF /* ShareViewController.swift */; }; - 8C27575C16AD4AB88AFBEAD7 /* INatIcon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 046B62AFE69547678922E815 /* INatIcon.ttf */; }; A019DB3A4661689827F5BB56 /* libPods-iNaturalistReactNative.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 486ED9661FEC89EDDBE3DA02 /* libPods-iNaturalistReactNative.a */; }; A252B2AEA64E47C9AC1D20E8 /* Whitney-Light-Pro.otf in Resources */ = {isa = PBXBuildFile; fileRef = BA9D41ECEBFA4C38B74009B3 /* Whitney-Light-Pro.otf */; }; + B7727B0D0C414FFF99814FDF /* INatIcon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 68976D05824745ECA696C47C /* INatIcon.ttf */; }; BA2479FA3D7B40A7BEF7B3CD /* Whitney-Medium-Pro.otf in Resources */ = {isa = PBXBuildFile; fileRef = D09FA3A0162844FF80A5EF96 /* Whitney-Medium-Pro.otf */; }; /* End PBXBuildFile section */ @@ -63,7 +63,6 @@ 00E356EE1AD99517003FC87E /* iNaturalistReactNativeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iNaturalistReactNativeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 00E356F11AD99517003FC87E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 00E356F21AD99517003FC87E /* iNaturalistReactNativeTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = iNaturalistReactNativeTests.m; sourceTree = ""; }; - 046B62AFE69547678922E815 /* 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 = ""; }; @@ -77,6 +76,7 @@ 374CB22E29943E63005885ED /* Whitney-BookItalic-Pro.otf */ = {isa = PBXFileReference; lastKnownFileType = file; name = "Whitney-BookItalic-Pro.otf"; path = "../assets/fonts/Whitney-BookItalic-Pro.otf"; sourceTree = ""; }; 486ED9661FEC89EDDBE3DA02 /* libPods-iNaturalistReactNative.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-iNaturalistReactNative.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 6836402D214AF8075A93F130 /* Pods-iNaturalistReactNative.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iNaturalistReactNative.release.xcconfig"; path = "Target Support Files/Pods-iNaturalistReactNative/Pods-iNaturalistReactNative.release.xcconfig"; sourceTree = ""; }; + 68976D05824745ECA696C47C /* INatIcon.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = INatIcon.ttf; path = ../assets/fonts/INatIcon.ttf; sourceTree = ""; }; 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = iNaturalistReactNative/LaunchScreen.storyboard; sourceTree = ""; }; 8B65ED2B29F575C10054CCEF /* iNaturalistReactNative-ShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "iNaturalistReactNative-ShareExtension.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 8B65ED3029F575C10054CCEF /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; @@ -232,7 +232,7 @@ D09FA3A0162844FF80A5EF96 /* Whitney-Medium-Pro.otf */, 374CB22E29943E63005885ED /* Whitney-BookItalic-Pro.otf */, EE004FD2EC174086A7AB2908 /* inaturalisticons.ttf */, - 046B62AFE69547678922E815 /* INatIcon.ttf */, + 68976D05824745ECA696C47C /* INatIcon.ttf */, ); name = Resources; sourceTree = ""; @@ -363,7 +363,7 @@ A252B2AEA64E47C9AC1D20E8 /* Whitney-Light-Pro.otf in Resources */, BA2479FA3D7B40A7BEF7B3CD /* Whitney-Medium-Pro.otf in Resources */, 4FB3B444D46A4115B867B9CC /* inaturalisticons.ttf in Resources */, - 8C27575C16AD4AB88AFBEAD7 /* INatIcon.ttf in Resources */, + B7727B0D0C414FFF99814FDF /* INatIcon.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/link-assets-manifest.json b/ios/link-assets-manifest.json index 80f6513dc..4e7253084 100644 --- a/ios/link-assets-manifest.json +++ b/ios/link-assets-manifest.json @@ -3,7 +3,7 @@ "data": [ { "path": "assets/fonts/INatIcon.ttf", - "sha1": "62da083366bb9e2e214e271c04fc5823a467b396" + "sha1": "d3d24b237a7ef666ef81aff93cde57e534a676a3" }, { "path": "assets/fonts/Whitney-BookItalic-Pro.otf", diff --git a/package-lock.json b/package-lock.json index 10d8155bd..12683f8e2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -82,13 +82,13 @@ "react-native-open-maps": "^0.4.3", "react-native-orientation-locker": "github:wonday/react-native-orientation-locker", "react-native-paper": "^5.10.5", - "react-native-permissions": "3.8.0", + "react-native-permissions": "^3.10.0", "react-native-picker-select": "8.0.4", "react-native-reanimated": "^2.17.0", "react-native-reanimated-carousel": "^3.4.0", "react-native-render-html": "^6.3.4", "react-native-safe-area-context": "^4.7.2", - "react-native-screens": "3.20.0", + "react-native-screens": "^3.21.1", "react-native-sensitive-info": "^6.0.0-alpha.9", "react-native-share-menu": "^6.0.0", "react-native-svg": "13.9.0", @@ -23448,13 +23448,9 @@ } }, "node_modules/react-native-permissions": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-3.8.0.tgz", - "integrity": "sha512-BfZ7ksgdpGchHZH8M/kxCGZbWeACANbnPmb3hNjVOMDQusc4PWlPpobX3eBqYMSKbpi7bMECeV9BVU4QuwAf9A==", - "dependencies": { - "picocolors": "^1.0.0", - "pkg-dir": "^5.0.0" - }, + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-3.10.0.tgz", + "integrity": "sha512-6SB6JInfC0u54Wco8M1QsRoOGThnVjrhaks5IDWyYgwfi6JpXfTIi+F9fZZmRycyyPCgU8vGtht/5gF4VWEB/A==", "peerDependencies": { "react": ">=16.13.1", "react-native": ">=0.63.3", @@ -23466,82 +23462,6 @@ } } }, - "node_modules/react-native-permissions/node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-native-permissions/node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-native-permissions/node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-native-permissions/node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/react-native-permissions/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native-permissions/node_modules/pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "dependencies": { - "find-up": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/react-native-picker-select": { "version": "8.0.4", "resolved": "https://registry.npmjs.org/react-native-picker-select/-/react-native-picker-select-8.0.4.tgz", @@ -23626,9 +23546,9 @@ } }, "node_modules/react-native-screens": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.20.0.tgz", - "integrity": "sha512-joWUKWAVHxymP3mL9gYApFHAsbd9L6ZcmpoZa6Sl3W/82bvvNVMqcfP7MeNqVCg73qZ8yL4fW+J/syusHleUgg==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.21.1.tgz", + "integrity": "sha512-asyqTA/0Ij9Sa16zB9YCUmZku5bf8RVKS0FKKhS2jfmF98BFt5XV6ldvM+Ze9cwgssSyRz4jYOIDSK1Wr5ZaYQ==", "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" @@ -44801,61 +44721,10 @@ } }, "react-native-permissions": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-3.8.0.tgz", - "integrity": "sha512-BfZ7ksgdpGchHZH8M/kxCGZbWeACANbnPmb3hNjVOMDQusc4PWlPpobX3eBqYMSKbpi7bMECeV9BVU4QuwAf9A==", - "requires": { - "picocolors": "^1.0.0", - "pkg-dir": "^5.0.0" - }, - "dependencies": { - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "requires": { - "p-limit": "^3.0.2" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "pkg-dir": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", - "requires": { - "find-up": "^5.0.0" - } - } - } + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-3.10.0.tgz", + "integrity": "sha512-6SB6JInfC0u54Wco8M1QsRoOGThnVjrhaks5IDWyYgwfi6JpXfTIi+F9fZZmRycyyPCgU8vGtht/5gF4VWEB/A==", + "requires": {} }, "react-native-picker-select": { "version": "8.0.4", @@ -44918,9 +44787,9 @@ "requires": {} }, "react-native-screens": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.20.0.tgz", - "integrity": "sha512-joWUKWAVHxymP3mL9gYApFHAsbd9L6ZcmpoZa6Sl3W/82bvvNVMqcfP7MeNqVCg73qZ8yL4fW+J/syusHleUgg==", + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-3.21.1.tgz", + "integrity": "sha512-asyqTA/0Ij9Sa16zB9YCUmZku5bf8RVKS0FKKhS2jfmF98BFt5XV6ldvM+Ze9cwgssSyRz4jYOIDSK1Wr5ZaYQ==", "requires": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" diff --git a/package.json b/package.json index fc3feed60..7726080a9 100644 --- a/package.json +++ b/package.json @@ -103,13 +103,13 @@ "react-native-open-maps": "^0.4.3", "react-native-orientation-locker": "github:wonday/react-native-orientation-locker", "react-native-paper": "^5.10.5", - "react-native-permissions": "3.8.0", + "react-native-permissions": "^3.10.0", "react-native-picker-select": "8.0.4", "react-native-reanimated": "^2.17.0", "react-native-reanimated-carousel": "^3.4.0", "react-native-render-html": "^6.3.4", "react-native-safe-area-context": "^4.7.2", - "react-native-screens": "3.20.0", + "react-native-screens": "^3.21.1", "react-native-sensitive-info": "^6.0.0-alpha.9", "react-native-share-menu": "^6.0.0", "react-native-svg": "13.9.0", diff --git a/src/api/computerVision.js b/src/api/computerVision.js new file mode 100644 index 000000000..d0b4352ca --- /dev/null +++ b/src/api/computerVision.js @@ -0,0 +1,26 @@ +// @flow + +import inatjs from "inaturalistjs"; +import Taxon from "realmModels/Taxon"; + +import handleError from "./error"; + +const PARAMS = { + fields: { + taxon: Taxon.TAXON_FIELDS + } +}; + +const scoreImage = async ( + params: Object = {}, + opts: Object = {} +): Promise => { + try { + const { results } = await inatjs.computervision.score_image( { ...PARAMS, ...params }, opts ); + return results; + } catch ( e ) { + return handleError( e ); + } +}; + +export default scoreImage; diff --git a/src/components/AddID/AddID.js b/src/components/AddID/AddID.js deleted file mode 100644 index 15a1af40d..000000000 --- a/src/components/AddID/AddID.js +++ /dev/null @@ -1,88 +0,0 @@ -// @flow - -import { useNavigation } from "@react-navigation/native"; -import { - Body3, INatIcon, INatIconButton, - TextInputSheet, ViewWrapper -} from "components/SharedComponents"; -import { View } from "components/styledComponents"; -import type { Node } from "react"; -import React, { useEffect, useState } from "react"; -import { - ActivityIndicator -} from "react-native-paper"; -import { useTranslation } from "sharedHooks"; - -import TaxonSearch from "./TaxonSearch"; - -type Props = { - setComment: Function, - comment: string, - taxonQuery: string, - setTaxonQuery: Function, - createId: Function, - loading: boolean -}; - -const AddID = ( { - setComment, comment, taxonQuery, setTaxonQuery, createId, loading -}: Props ): Node => { - const { t } = useTranslation( ); - const [showAddCommentSheet, setShowAddCommentSheet] = useState( false ); - - const navigation = useNavigation(); - - useEffect( ( ) => { - const addCommentIcon = ( ) => ( - setShowAddCommentSheet( true )} - accessibilityLabel={t( "Add-comment" )} - size={25} - /> - ); - - navigation.setOptions( { - headerRight: addCommentIcon - } ); - }, [navigation, t] ); - - return ( - - {showAddCommentSheet && ( - setShowAddCommentSheet( false )} - headerText={t( "ADD-OPTIONAL-COMMENT" )} - snapPoints={[416]} - confirm={textInput => setComment( textInput )} - /> - )} - - { comment && comment.length > 0 && ( - - - { comment } - - ) } - {loading && ( - - - - )} - - - - ); -}; - -export default AddID; diff --git a/src/components/AddID/AddIDContainer.js b/src/components/AddID/AddIDContainer.js deleted file mode 100644 index 9a3076e09..000000000 --- a/src/components/AddID/AddIDContainer.js +++ /dev/null @@ -1,136 +0,0 @@ -// @flow - -import { useNavigation } from "@react-navigation/native"; -import { createIdentification } from "api/identifications"; -import { ObsEditContext, RealmContext } from "providers/contexts"; -import type { Node } from "react"; -import React, { useContext, useEffect, useState } from "react"; -import { Alert } from "react-native"; -import uuid from "react-native-uuid"; -import { - useAuthenticatedMutation, - useCurrentUser, - useLocalObservation, - useTranslation -} from "sharedHooks"; - -import AddID from "./AddID"; - -const { useRealm } = RealmContext; - -type Props = { - route: { - params: { - observationUUID?: string, - createRemoteIdentification?: boolean, - belongsToCurrentUser?: boolean - }, - }, -}; - -const AddIDContainer = ( { route }: Props ): Node => { - const [comment, setComment] = useState( "" ); - const [loading, setLoading] = useState( false ); - const [taxonQuery, setTaxonQuery] = useState( "" ); - const { - updateObservationKeys - } = useContext( ObsEditContext ); - const currentUser = useCurrentUser( ); - const realm = useRealm( ); - const observationUUID = route?.params?.observationUUID; - const createRemoteIdentification = route?.params?.createRemoteIdentification; - const belongsToCurrentUser = route?.params?.belongsToCurrentUser; - const localObservation = useLocalObservation( observationUUID ); - const { t } = useTranslation( ); - - const navigation = useNavigation( ); - - const showErrorAlert = error => Alert.alert( "Error", error, [{ text: t( "OK" ) }], { - cancelable: true - } ); - - const createIdentificationMutation = useAuthenticatedMutation( - ( idParams, optsWithAuth ) => createIdentification( idParams, optsWithAuth ), - { - onSuccess: data => { - if ( belongsToCurrentUser ) { - realm?.write( ( ) => { - const localIdentifications = localObservation?.identifications; - const newIdentification = data[0]; - newIdentification.user = currentUser; - newIdentification.taxon = realm?.objectForPrimaryKey( - "Taxon", - newIdentification.taxon.id - ) || newIdentification.taxon; - const realmIdentification = realm?.create( "Identification", newIdentification ); - localIdentifications.push( realmIdentification ); - } ); - } - // navigate back to ObsDetails - navigation.goBack( ); - }, - onError: e => { - let error = null; - if ( e ) { - error = t( "Couldnt-create-identification-error", { error: e.message } ); - } else { - error = t( "Couldnt-create-identification-unknown-error" ); - } - showErrorAlert( error ); - } - } - ); - - const formatIdentification = taxon => { - const newIdent = { - uuid: uuid.v4(), - body: comment, - taxon - }; - - return newIdent; - }; - - const createId = identification => { - setLoading( true ); - const newIdentification = formatIdentification( identification ); - if ( createRemoteIdentification ) { - createIdentificationMutation.mutate( { - identification: { - observation_id: observationUUID, - taxon_id: newIdentification.taxon.id, - body: newIdentification.body - } - } ); - } else { - updateObservationKeys( { - taxon: newIdentification.taxon - } ); - navigation.goBack( ); - } - }; - - useEffect( - ( ) => { - navigation.addListener( "blur", ( ) => { - setTaxonQuery( "" ); - setComment( "" ); - setLoading( false ); - } ); - }, - [navigation] - ); - - return ( - - ); -}; - -export default AddIDContainer; diff --git a/src/components/AddID/TaxonSearch.js b/src/components/AddID/TaxonSearch.js deleted file mode 100644 index 9993aba2c..000000000 --- a/src/components/AddID/TaxonSearch.js +++ /dev/null @@ -1,76 +0,0 @@ -// @flow - -import fetchSearchResults from "api/search"; -import { - Body2, - SearchBar, - TaxonResult -} from "components/SharedComponents"; -import { View } from "components/styledComponents"; -import type { Node } from "react"; -import React from "react"; -import { FlatList } from "react-native"; -import Taxon from "realmModels/Taxon"; -import { useTranslation } from "sharedHooks"; -import useAuthenticatedQuery from "sharedHooks/useAuthenticatedQuery"; - -type Props = { - taxonQuery: string, - setTaxonQuery: Function, - onTaxonChosen: Function -}; - -const TaxonSearch = ( { - taxonQuery, - setTaxonQuery, - onTaxonChosen -}: Props ): Node => { - const { t } = useTranslation( ); - const { data: taxonList } = useAuthenticatedQuery( - ["fetchSearchResults", taxonQuery], - optsWithAuth => fetchSearchResults( - { - q: taxonQuery, - sources: "taxa", - fields: { - taxon: Taxon.TAXON_FIELDS - } - }, - optsWithAuth - ) - ); - - const renderEmptyComponent = ( ) => ( - - {t( "Search-for-a-taxon-to-add-an-identification" )} - - ); - - return ( - <> - - - - ( - onTaxonChosen( item )} - testID={`Search.taxa.${item.id}`} - /> - )} - keyExtractor={item => item.id} - ListEmptyComponent={renderEmptyComponent} - /> - - ); -}; - -export default TaxonSearch; diff --git a/src/components/Camera/StandardCamera/PhotoCarousel.js b/src/components/Camera/StandardCamera/PhotoCarousel.js index 25160000e..f328bedef 100644 --- a/src/components/Camera/StandardCamera/PhotoCarousel.js +++ b/src/components/Camera/StandardCamera/PhotoCarousel.js @@ -29,7 +29,7 @@ type Props = { rotation?: { value: number }, - setMediaViewerUris: Function, + setPhotoEvidenceUris: Function, photoUris: Array, setSelectedPhotoIndex: Function } @@ -60,7 +60,7 @@ const PhotoCarousel = ( { isLargeScreen, isTablet, rotation, - setMediaViewerUris, + setPhotoEvidenceUris, photoUris, setSelectedPhotoIndex }: Props ): Node => { @@ -136,7 +136,7 @@ const PhotoCarousel = ( { deletePhoto( item ); } else { setSelectedPhotoIndex( index ); - setMediaViewerUris( [...photoUris] ); + setPhotoEvidenceUris( [...photoUris] ); navigation.navigate( "MediaViewer" ); } }} diff --git a/src/components/Camera/StandardCamera/PhotoPreview.js b/src/components/Camera/StandardCamera/PhotoPreview.js index 78aba5532..1a281e45d 100644 --- a/src/components/Camera/StandardCamera/PhotoPreview.js +++ b/src/components/Camera/StandardCamera/PhotoPreview.js @@ -33,7 +33,7 @@ const PhotoPreview = ( { const { cameraPreviewUris: photoUris, deletePhotoFromObservation, - setMediaViewerUris, + setPhotoEvidenceUris, setSelectedPhotoIndex } = useContext( ObsEditContext ); const wrapperDim = isLargeScreen @@ -99,7 +99,7 @@ const PhotoPreview = ( { deletePhoto={deletePhoto} photoUris={photoUris} rotation={rotation} - setMediaViewerUris={setMediaViewerUris} + setPhotoEvidenceUris={setPhotoEvidenceUris} takingPhoto={takingPhoto} isLargeScreen={isLargeScreen} isTablet={isTablet} diff --git a/src/components/MediaViewer/MediaViewer.js b/src/components/MediaViewer/MediaViewer.js index 7e23040f8..382949dca 100644 --- a/src/components/MediaViewer/MediaViewer.js +++ b/src/components/MediaViewer/MediaViewer.js @@ -25,13 +25,13 @@ const MediaViewer = ( ): Node => { const [warningSheet, setWarningSheet] = useState( false ); const { deletePhotoFromObservation, - mediaViewerUris, + photoEvidenceUris, selectedPhotoIndex, setSelectedPhotoIndex } = useContext( ObsEditContext ); const atFirstPhoto = selectedPhotoIndex === 0; - const atLastPhoto = selectedPhotoIndex === mediaViewerUris.length - 1; + const atLastPhoto = selectedPhotoIndex === photoEvidenceUris.length - 1; const handleScrollLeft = index => { if ( atFirstPhoto ) { return; } @@ -48,15 +48,15 @@ const MediaViewer = ( ): Node => { const { isLandscapeMode, screenWidth } = useDeviceOrientation( ); const isLargeScreen = screenWidth > BREAKPOINTS.md; - const numOfPhotos = mediaViewerUris.length; + const numOfPhotos = photoEvidenceUris.length; const showWarningSheet = ( ) => setWarningSheet( true ); const hideWarningSheet = ( ) => setWarningSheet( false ); const deletePhoto = ( ) => { - deletePhotoFromObservation( mediaViewerUris[selectedPhotoIndex] ); + deletePhotoFromObservation( photoEvidenceUris[selectedPhotoIndex] ); hideWarningSheet( ); - if ( mediaViewerUris.length === 0 ) { + if ( photoEvidenceUris.length === 0 ) { navigation.goBack( ); } else if ( selectedPhotoIndex !== 0 ) { setSelectedPhotoIndex( selectedPhotoIndex - 1 ); @@ -115,13 +115,13 @@ const MediaViewer = ( ): Node => { /> )} { @@ -47,7 +47,7 @@ const FloatingButtons = ( { />