From b96051cb0203e17bbcb1ab4616baa65ca176373e Mon Sep 17 00:00:00 2001 From: budowski Date: Fri, 23 Aug 2024 00:12:37 +0200 Subject: [PATCH] Internal feedback form (#1992) Adds internal feedback form that posts to our logging infrastructure. Also, * Adds maxLength prop to TextInputSheet w/ UI to show characters remaining * Adds optional description to TextInputSheet * Adjusts TextInputSheet input height to fit area left by the keyboard Closes #1844 --------- Co-authored-by: Ken-ichi Ueda --- .../app/src/main/assets/fonts/INatIcon.ttf | Bin 29188 -> 29340 bytes android/link-assets-manifest.json | 2 +- assets/fonts/INatIcon.ttf | Bin 29188 -> 29340 bytes .../project.pbxproj | 8 +- ios/link-assets-manifest.json | 2 +- .../SharedComponents/INatIcon/glyphmap.json | 73 +++++++------ .../SharedComponents/Sheets/BottomSheet.js | 3 + .../SharedComponents/Sheets/TextInputSheet.js | 103 +++++++++++++++--- src/i18n/l10n/en.ftl | 9 ++ src/i18n/l10n/en.ftl.json | 11 ++ src/i18n/strings.ftl | 9 ++ src/images/icons/feedback.svg | 4 + src/navigation/CustomDrawerContent.tsx | 48 +++++++- .../__snapshots__/CustomTabBar.test.js.snap | 2 +- .../__snapshots__/ActivityCount.test.js.snap | 2 +- .../__snapshots__/CommentsCount.test.js.snap | 6 +- .../__snapshots__/INatIconButton.test.js.snap | 2 +- .../__snapshots__/ObsGridItem.test.js.snap | 4 +- .../__snapshots__/UploadStatus.test.js.snap | 6 +- .../__snapshots__/Checkbox.test.js.snap | 2 +- .../__snapshots__/TaxonResult.test.js.snap | 2 +- .../__snapshots__/INatIcon.test.js.snap | 2 +- 22 files changed, 227 insertions(+), 73 deletions(-) create mode 100644 src/images/icons/feedback.svg diff --git a/android/app/src/main/assets/fonts/INatIcon.ttf b/android/app/src/main/assets/fonts/INatIcon.ttf index 370a9fb6f0ff1e9ed02e1189ff428d30d59521df..4c8358a605b392912a42e90a315162c2d03b5b47 100644 GIT binary patch delta 533 zcmZp9!Z_zC;{@gUc?=AUJq!#C3CX#M1-m!1b1*P4A7Ef$-<6(InP#*%v7Ui}Cxd|@ zHX|c7F(qQn0w;$s+0I^C&Zb?Ol#Q%#x^WFgQm7M(KM3=kK{}>p! zYk=}^auX{G7$-0a0_7xte1*Kk+|&RyDTAL344D@g7?{Kg@{3DuO}AlVV90*Kz`(X? z;vQw*QU*q#S|FCLNm@VgjUi*{WI4u-j3t{dFvc-Xwqq6n;)$A8U{wr^CJfAAmJX2S z0vc(>z`_Xh4+9Gj0?qhXGWiGdccA8XEWC`I4L}JHDA~-ymd48Su>_<5#AjfboWZ@w zOX-txn(`JEDU}eF2`YC~EmU8q38}@Xb*b%8`=x$H{hx-MMuCFZ6Es7V?8WMIeyDq<2V$S*GO$d$EXV91VPU|?H5 zagQ=@2?HZgEf9Zxa(T+cH-?NQljRsUG8S*Xz!=9kIgeRnvK#xZItepHjKv@RX5)cUl#hba< z(pV?gaWC*v)>5uezNaFg5~8v|<&Wwb)oW@BYJO@pYWvhDs2@^)q#>c zIn6fB4Vpi++_Xxx7HQAYzNh1)Q>3#^S4xjVuY!SLa|TbY>t^4eJ(55VaOJ$%{3T}& K^X8caOiTc*e`<9A diff --git a/android/link-assets-manifest.json b/android/link-assets-manifest.json index 6576f012d..8e7de39f7 100644 --- a/android/link-assets-manifest.json +++ b/android/link-assets-manifest.json @@ -3,7 +3,7 @@ "data": [ { "path": "assets/fonts/INatIcon.ttf", - "sha1": "a668337350732e29a114b5357b5a3a22c17bb7a6" + "sha1": "a9202f961997013c4c384d9ecbb8b192671bb606" }, { "path": "assets/fonts/Lato-Bold.ttf", diff --git a/assets/fonts/INatIcon.ttf b/assets/fonts/INatIcon.ttf index 370a9fb6f0ff1e9ed02e1189ff428d30d59521df..4c8358a605b392912a42e90a315162c2d03b5b47 100644 GIT binary patch delta 533 zcmZp9!Z_zC;{@gUc?=AUJq!#C3CX#M1-m!1b1*P4A7Ef$-<6(InP#*%v7Ui}Cxd|@ zHX|c7F(qQn0w;$s+0I^C&Zb?Ol#Q%#x^WFgQm7M(KM3=kK{}>p! zYk=}^auX{G7$-0a0_7xte1*Kk+|&RyDTAL344D@g7?{Kg@{3DuO}AlVV90*Kz`(X? z;vQw*QU*q#S|FCLNm@VgjUi*{WI4u-j3t{dFvc-Xwqq6n;)$A8U{wr^CJfAAmJX2S z0vc(>z`_Xh4+9Gj0?qhXGWiGdccA8XEWC`I4L}JHDA~-ymd48Su>_<5#AjfboWZ@w zOX-txn(`JEDU}eF2`YC~EmU8q38}@Xb*b%8`=x$H{hx-MMuCFZ6Es7V?8WMIeyDq<2V$S*GO$d$EXV91VPU|?H5 zagQ=@2?HZgEf9Zxa(T+cH-?NQljRsUG8S*Xz!=9kIgeRnvK#xZItepHjKv@RX5)cUl#hba< z(pV?gaWC*v)>5uezNaFg5~8v|<&Wwb)oW@BYJO@pYWvhDs2@^)q#>c zIn6fB4Vpi++_Xxx7HQAYzNh1)Q>3#^S4xjVuY!SLa|TbY>t^4eJ(55VaOJ$%{3T}& K^X8caOiTc*e`<9A diff --git a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj index 334808ef3..37fdc3ab5 100644 --- a/ios/iNaturalistReactNative.xcodeproj/project.pbxproj +++ b/ios/iNaturalistReactNative.xcodeproj/project.pbxproj @@ -10,7 +10,6 @@ 00E356F31AD99517003FC87E /* iNaturalistReactNativeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* iNaturalistReactNativeTests.m */; }; 03BE06B8FED98F5CD10273BB /* libPods-iNaturalistReactNative.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 20043186B311EE211FEB259A /* libPods-iNaturalistReactNative.a */; }; 085DD3205807404CAFC32228 /* Lato-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 3A9BAF07FCF24F668E2EF5AB /* Lato-Medium.ttf */; }; - 0928C25974EC489BA89D9E45 /* INatIcon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E5BA7B171CE84F61B07BAAF3 /* 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 */; }; @@ -31,6 +30,7 @@ AE4DC81B3A87484CB3FD6750 /* Lato-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 4B0AEEF6CA584BCF9880EB35 /* Lato-Regular.ttf */; }; E23E0899594A7C6DF680FFDB /* libPods-iNaturalistReactNative-ShareExtension.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A336AF0ADEAE537AB1B73F98 /* libPods-iNaturalistReactNative-ShareExtension.a */; }; E5DFC1C6FBFA45739CE91C69 /* Lato-MediumItalic.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 69DF855D92EA4ADFB73B47F1 /* Lato-MediumItalic.ttf */; }; + 9E1FFD6E542A4EFD98DB22E0 /* INatIcon.ttf in Resources */ = {isa = PBXBuildFile; fileRef = B72E364FDB0D4ED9A160B613 /* INatIcon.ttf */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -100,8 +100,8 @@ 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 = ""; }; 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 = ""; }; - E5BA7B171CE84F61B07BAAF3 /* INatIcon.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = INatIcon.ttf; path = ../assets/fonts/INatIcon.ttf; sourceTree = ""; }; ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; }; + B72E364FDB0D4ED9A160B613 /* INatIcon.ttf */ = {isa = PBXFileReference; name = "INatIcon.ttf"; path = "../assets/fonts/INatIcon.ttf"; sourceTree = ""; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -253,7 +253,7 @@ 3A9BAF07FCF24F668E2EF5AB /* Lato-Medium.ttf */, 69DF855D92EA4ADFB73B47F1 /* Lato-MediumItalic.ttf */, 4B0AEEF6CA584BCF9880EB35 /* Lato-Regular.ttf */, - E5BA7B171CE84F61B07BAAF3 /* INatIcon.ttf */, + B72E364FDB0D4ED9A160B613 /* INatIcon.ttf */, ); name = Resources; sourceTree = ""; @@ -387,7 +387,7 @@ 085DD3205807404CAFC32228 /* Lato-Medium.ttf in Resources */, E5DFC1C6FBFA45739CE91C69 /* Lato-MediumItalic.ttf in Resources */, AE4DC81B3A87484CB3FD6750 /* Lato-Regular.ttf in Resources */, - 0928C25974EC489BA89D9E45 /* INatIcon.ttf in Resources */, + 9E1FFD6E542A4EFD98DB22E0 /* INatIcon.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ios/link-assets-manifest.json b/ios/link-assets-manifest.json index 6576f012d..8e7de39f7 100644 --- a/ios/link-assets-manifest.json +++ b/ios/link-assets-manifest.json @@ -3,7 +3,7 @@ "data": [ { "path": "assets/fonts/INatIcon.ttf", - "sha1": "a668337350732e29a114b5357b5a3a22c17bb7a6" + "sha1": "a9202f961997013c4c384d9ecbb8b192671bb606" }, { "path": "assets/fonts/Lato-Bold.ttf", diff --git a/src/components/SharedComponents/INatIcon/glyphmap.json b/src/components/SharedComponents/INatIcon/glyphmap.json index f37b1726a..ee0ed2857 100644 --- a/src/components/SharedComponents/INatIcon/glyphmap.json +++ b/src/components/SharedComponents/INatIcon/glyphmap.json @@ -78,40 +78,41 @@ "flash-on": 61773, "flash-off": 61774, "flag": 61775, - "edit-comment": 61776, - "door-exit": 61777, - "currentlocation": 61778, - "crop": 61779, - "creative-commons": 61780, - "copyright": 61781, - "copy": 61782, - "compass-rose-outline": 61783, - "comments": 61784, - "comments-outline": 61785, - "close": 61786, - "close-bold": 61787, - "clock-outline": 61788, - "circle-dots": 61789, - "chevron-right-circle": 61790, - "chevron-left": 61791, - "chevron-left-circle": 61792, - "checkmark-circle": 61793, - "checkmark-circle-outline": 61794, - "check": 61795, - "caret": 61796, - "camera": 61797, - "briefcase": 61798, - "book": 61799, - "binoculars": 61800, - "ban": 61801, - "arrow-up": 61802, - "arrow-up-small": 61803, - "arrow-up-circle-dots": 61804, - "arrow-up-bold-circle": 61805, - "arrow-up-bold-circle-outline": 61806, - "arrow-turn-down-right": 61807, - "arrow-down-bold-circle": 61808, - "arrow-down-bold-circle-outline": 61809, - "arcamera": 61810, - "add-comment-outline": 61811 + "feedback": 61776, + "edit-comment": 61777, + "door-exit": 61778, + "currentlocation": 61779, + "crop": 61780, + "creative-commons": 61781, + "copyright": 61782, + "copy": 61783, + "compass-rose-outline": 61784, + "comments": 61785, + "comments-outline": 61786, + "close": 61787, + "close-bold": 61788, + "clock-outline": 61789, + "circle-dots": 61790, + "chevron-right-circle": 61791, + "chevron-left": 61792, + "chevron-left-circle": 61793, + "checkmark-circle": 61794, + "checkmark-circle-outline": 61795, + "check": 61796, + "caret": 61797, + "camera": 61798, + "briefcase": 61799, + "book": 61800, + "binoculars": 61801, + "ban": 61802, + "arrow-up": 61803, + "arrow-up-small": 61804, + "arrow-up-circle-dots": 61805, + "arrow-up-bold-circle": 61806, + "arrow-up-bold-circle-outline": 61807, + "arrow-turn-down-right": 61808, + "arrow-down-bold-circle": 61809, + "arrow-down-bold-circle-outline": 61810, + "arcamera": 61811, + "add-comment-outline": 61812 } \ No newline at end of file diff --git a/src/components/SharedComponents/Sheets/BottomSheet.js b/src/components/SharedComponents/Sheets/BottomSheet.js index db8228bb5..5f4281501 100644 --- a/src/components/SharedComponents/Sheets/BottomSheet.js +++ b/src/components/SharedComponents/Sheets/BottomSheet.js @@ -23,6 +23,7 @@ type Props = { handleClose?: Function, hideCloseButton?: boolean, headerText?: string, + onLayout?: Function, snapPoints?: Array, insideModal?: boolean, keyboardShouldPersistTaps: string @@ -37,6 +38,7 @@ const StandardBottomSheet = ( { handleClose, hideCloseButton = false, headerText, + onLayout, snapPoints, insideModal, keyboardShouldPersistTaps = "never" @@ -116,6 +118,7 @@ const StandardBottomSheet = ( { ? "pb-7" : null )} + onLayout={onLayout} > {headerText} diff --git a/src/components/SharedComponents/Sheets/TextInputSheet.js b/src/components/SharedComponents/Sheets/TextInputSheet.js index a3e87a239..a71073286 100644 --- a/src/components/SharedComponents/Sheets/TextInputSheet.js +++ b/src/components/SharedComponents/Sheets/TextInputSheet.js @@ -2,32 +2,64 @@ import { BottomSheetTextInput } from "@gorhom/bottom-sheet"; import { fontRegular } from "appConstants/fontFamilies.ts"; +import classnames from "classnames"; import { Body3, BottomSheet, Button } from "components/SharedComponents"; -import { View } from "components/styledComponents"; +import { Pressable, View } from "components/styledComponents"; import type { Node } from "react"; import React, { useMemo, useRef, useState } from "react"; import { Keyboard } from "react-native"; import { useTheme } from "react-native-paper"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import useKeyboardInfo from "sharedHooks/useKeyboardInfo"; import useTranslation from "sharedHooks/useTranslation"; +// Optimized to maximize input size while minimizing post-render height +// adjustments for for iPhone 13 and taller screens. Shorter screens +// (e.g. iPhone SE) will jerk around a bit to avoid the top inset +const TARGET_INPUT_HEIGHT = 220; + type Props = { buttonText: string, confirm: Function, + description?: string, handleClose: Function, headerText: string, - initialInput?: ?string, + initialInput?: string, + maxLength?: number, placeholder: string, textInputStyle?: Object } +const CharLimit = ( { current = 0, limit = 1 } ) => { + let currentColor = "text-darkGrayDisabled"; + if ( current / limit >= 1 ) { + currentColor = "text-warningRed"; + } else if ( current / limit > 0.9 ) { + currentColor = "text-warningRedDisabled"; + } + return ( + + + { current } + + + { " / " } + { limit } + + + ); +}; + const TextInputSheet = ( { buttonText, confirm, + description, handleClose, headerText, - initialInput = null, + initialInput, + maxLength, placeholder, textInputStyle }: Props ): Node => { @@ -36,18 +68,29 @@ const TextInputSheet = ( { const [input, setInput] = useState( initialInput ); const [hasChanged, setHasChanged] = useState( false ); const { t } = useTranslation( ); + const { nonKeyboardHeight } = useKeyboardInfo( TARGET_INPUT_HEIGHT ); + const { top: topInset } = useSafeAreaInsets( ); + const [sheetHeight, setSheetHeight] = useState( 0 ); // disable if user hasn't changed existing text const confirmButtonDisabled = initialInput === input && !hasChanged; const inputStyle = useMemo( ( ) => ( { - height: 223, + height: Math.min( + TARGET_INPUT_HEIGHT - ( sheetHeight - nonKeyboardHeight ) - topInset, + TARGET_INPUT_HEIGHT + ), fontFamily: fontRegular, fontSize: 14, lineHeight: 17, color: theme.colors.primary, textAlignVertical: "top" - } ), [theme] ); + } ), [ + nonKeyboardHeight, + sheetHeight, + theme, + topInset + ] ); const dismissKeyboardAndClose = ( ) => { Keyboard.dismiss( ); @@ -59,13 +102,26 @@ const TextInputSheet = ( { handleClose={dismissKeyboardAndClose} headerText={headerText} keyboardShouldPersistTaps="always" + onLayout={event => { + const { height } = event.nativeEvent.layout; + // Only do this once. Device height isn't going to change + if ( sheetHeight === 0 ) { + setSheetHeight( height ); + } + }} > - + + { description && description.length > 0 && ( + + { description } + + ) } { if ( !hasChanged ) { @@ -79,14 +135,27 @@ const TextInputSheet = ( { autoFocus defaultValue={input} /> - { - textInputRef?.current?.clear(); - }} + - {t( "Clear" )} - + { maxLength && ( + + ) } + { + textInputRef?.current?.clear(); + }} + accessibilityHint={t( "Deletes-entered-text" )} + accessibilityRole="button" + > + {t( "Clear" )} + +