Obs grid view updates (#1241)

* Adjust iconic icon size, dark gradient size and breaks for ObsGridItem, hide noevidence when photos
* Layout consistency for upload status
This commit is contained in:
Ken-ichi
2024-03-01 09:45:10 -08:00
committed by GitHub
parent 71a154b432
commit 0f67a10fe4
14 changed files with 453 additions and 217 deletions

View File

@@ -10,35 +10,43 @@ import colors from "styles/tailwindColors";
type Props = {
iconicTaxonName: string,
imageClassName?: string,
style?: Object,
isBackground?: boolean,
size?: number,
white?: boolean
}
const IconicTaxonIcon = ( {
iconicTaxonName = "unknown",
imageClassName,
style = {},
isBackground = false,
size = 30,
white = false
}: Props ): Node => (
<View
className={classnames(
imageClassName,
"justify-center items-center",
{
"bg-darkGray": white,
"border border-lightGray": !white
}
)}
testID="IconicTaxonName.iconicTaxonIcon"
>
<INatIcon
name={iconicTaxonName && `iconic-${iconicTaxonName?.toLowerCase( )}`}
size={style?.width > 80
? 30
: 22}
color={white && colors.white}
/>
</View>
);
}: Props ): Node => {
let color = null;
if ( white ) {
color = isBackground
? colors.mediumGrayGhost
: colors.white;
}
return (
<View
className={classnames(
imageClassName,
"flex-1 justify-center items-center",
{
"bg-darkGray": white,
"border border-lightGray": !white
}
)}
testID="IconicTaxonName.iconicTaxonIcon"
>
<INatIcon
name={iconicTaxonName && `iconic-${iconicTaxonName?.toLowerCase( )}`}
size={size}
color={color}
/>
</View>
);
};
export default IconicTaxonIcon;

View File

@@ -16,14 +16,16 @@ type Props = {
height?: string,
style?: Object,
uploadSingleObservation?: Function,
uploadState: Object,
uploadState: {
uploadProgress: Number
},
explore: boolean
};
const ObsGridItem = ( {
observation,
width = "w-full",
height,
width = "w-[200px]",
height = "w-[200px]",
style,
uploadSingleObservation,
uploadState,

View File

@@ -10,11 +10,13 @@ type URI = {
};
type Props = {
uri?: URI,
opaque?: boolean,
iconicTaxonIconSize?: number,
iconicTaxonName?: string,
white?: boolean,
imageClassName: string
imageClassName: string,
isBackground?: boolean,
opaque?: boolean,
uri?: URI,
white?: boolean
};
const CLASS_NAMES = [
@@ -23,11 +25,13 @@ const CLASS_NAMES = [
];
const ObsImage = ( {
uri,
opaque = false,
iconicTaxonName = "unknown",
imageClassName,
isBackground = false,
opaque = false,
uri,
white = false,
imageClassName
iconicTaxonIconSize
}: Props ): Node => {
const noImg = !uri?.uri;
@@ -41,6 +45,8 @@ const ObsImage = ( {
]}
iconicTaxonName={iconicTaxonName}
white={white}
isBackground={isBackground}
size={iconicTaxonIconSize}
/>
);
}

View File

@@ -75,13 +75,18 @@ const ObsImagePreview = ( {
"p-2": !isSmall
} )}
>
{ !( isSmall && obsPhotosCount === 0 )
{ !hasSound && !( isSmall && obsPhotosCount === 0 )
&& <PhotoCount count={obsPhotosCount} /> }
</View>
);
}
return null;
}, [isMultiplePhotosTop, isSmall, obsPhotosCount] );
}, [
hasSound,
isMultiplePhotosTop,
isSmall,
obsPhotosCount
] );
const renderSelectable = useCallback( ( ) => {
if ( selectable ) {
@@ -114,6 +119,8 @@ const ObsImagePreview = ( {
<LinearGradient
colors={["rgba(0, 0, 0, 0)", "rgba(0, 0, 0, 0.5) 100%)"]}
className="absolute w-full h-full"
start={{ x: 0, y: 0 }}
end={{ x: 0, y: 0.75 }}
/>
);
}
@@ -148,6 +155,12 @@ const ObsImagePreview = ( {
imageClassName={imageClassName}
iconicTaxonName={iconicTaxonName}
white={white}
isBackground
iconicTaxonIconSize={
isSmall
? 22
: 100
}
/>
{renderGradient( )}
{renderSelectable( )}

View File

@@ -1,4 +1,5 @@
// @flow
import classnames from "classnames";
import {
DateDisplay, DisplayTaxonName, ObservationLocation, ObsStatus
} from "components/SharedComponents";
@@ -53,7 +54,12 @@ const ObsListItem = ( {
classNameMargin="mt-1"
/>
</View>
<View className="items-center ml-auto justify-center">
<View
className={classnames(
"flex-0 justify-start flex-row",
{ "justify-center": uploadState }
)}
>
{explore
? (
<ObsStatus

View File

@@ -27,36 +27,32 @@ const ObsUploadStatus = ( {
const theme = useTheme( );
const whiteColor = white && theme.colors.onPrimary;
const displayUploadStatus = ( ) => {
const obsStatus = (
<ObsStatus
observation={observation}
layout={layout}
white={white}
classNameMargin={classNameMargin}
testID={`ObsStatus.${observation.uuid}`}
/>
);
const obsStatus = (
<ObsStatus
observation={observation}
layout={layout}
white={white}
classNameMargin={classNameMargin}
testID={`ObsStatus.${observation.uuid}`}
/>
);
if ( !showUploadStatus ) {
return obsStatus;
}
if ( !showUploadStatus ) {
return obsStatus;
}
return (
<UploadStatus
progress={progress}
uploadObservation={uploadSingleObservation}
color={whiteColor}
completeColor={whiteColor}
layout={layout}
uuid={observation.uuid}
>
{obsStatus}
</UploadStatus>
);
};
return displayUploadStatus( );
return (
<UploadStatus
progress={progress}
uploadObservation={uploadSingleObservation}
color={whiteColor}
completeColor={whiteColor}
layout={layout}
uuid={observation.uuid}
>
{obsStatus}
</UploadStatus>
);
};
export default ObsUploadStatus;

View File

@@ -16,7 +16,9 @@ type Props = {
};
const PhotoCount = ( {
count, size, shadow
count,
size,
shadow
}: Props ): Node => {
const theme = useTheme( );

View File

@@ -13,33 +13,35 @@ import { useTaxon, useTranslation } from "sharedHooks";
import ConfidenceInterval from "./ConfidenceInterval";
type Props = {
taxon: Object,
handlePress: Function,
showCheckmark?: boolean,
handleCheckmarkPress: Function,
testID: string,
activeColor?: string,
clearBackground?: boolean,
confidence?: number,
white?: boolean,
activeColor?: string,
confidencePosition?: string,
fetchRemote?: boolean,
first?: boolean,
fetchRemote?: boolean
fromLocal?: boolean,
handleCheckmarkPress: Function,
handlePress: Function,
showCheckmark?: boolean,
taxon: Object,
testID: string,
white?: boolean
};
const TaxonResult = ( {
activeColor,
clearBackground,
confidence,
confidencePosition = "photo",
fetchRemote = true,
first = false,
fromLocal = true,
handleCheckmarkPress,
handlePress,
showCheckmark = true,
handleCheckmarkPress,
taxon: taxonResult,
testID,
white = false,
activeColor,
confidencePosition = "photo",
first = false,
fetchRemote = true
white = false
}: Props ): Node => {
const { t } = useTranslation( );
const navigation = useNavigation( );
@@ -48,7 +50,10 @@ const TaxonResult = ( {
// network requests for useTaxon instead of making individual API calls.
// right now, this fetches a single taxon at a time on AR camera &
// a short list of taxa from offline Suggestions
const taxon = useTaxon( taxonResult, fetchRemote );
const localTaxon = useTaxon( taxonResult, fetchRemote );
const taxon = fromLocal
? localTaxon
: taxonResult;
const taxonImage = { uri: taxon?.default_photo?.url };
const navToTaxonDetails = () => navigation.navigate( "TaxonDetails", { id: taxon.id } );

View File

@@ -128,17 +128,24 @@ const UploadStatus = ( {
/>
);
const iconClasses = [
"items-center",
"justify-center",
"w-[44px]",
"h-[44px]"
];
const uploadTappedIcon = (
<>
<View className={classnames( iconClasses )}>
{showProgressArrow( )}
<AnimatedView style={rotate}>
<INatIcon name="circle-dots" color={color || defaultColor} size={33} />
</AnimatedView>
</>
</View>
);
const uploadInProgressIcon = (
<>
<View className={classnames( iconClasses )}>
{showProgressArrow( )}
<CircularProgressBase
testID="UploadStatus.CircularProgress"
@@ -154,15 +161,11 @@ const UploadStatus = ( {
inActiveStrokeOpacity={0}
activeStrokeWidth={2}
/>
</>
</View>
);
const uploadCompleteIcon = (
<AnimatedView
className="absolute"
entering={keyframe.duration( 2000 )}
testID={`UploadIcon.complete.${uuid}`}
>
<View className={classnames( iconClasses )}>
<INatIcon
size={28}
name="upload-complete"
@@ -172,51 +175,85 @@ const UploadStatus = ( {
: theme.colors.onSecondary
}
/>
</View>
);
const fadeOutUploadCompleteIcon = (
<AnimatedView
entering={keyframe.duration( 2000 )}
testID={`UploadIcon.complete.${uuid}`}
>
{ uploadCompleteIcon }
</AnimatedView>
);
const fadeInObsStatusComponent = (
<AnimatedView
entering={FadeIn.duration( 1000 ).delay( 2000 )}
className={classnames( {
"justify-end": layout === "horizontal",
absolute: layout === "vertical"
} )}
>
<AnimatedView entering={FadeIn.duration( 1000 ).delay( 2000 )}>
{children}
</AnimatedView>
);
const iconViewClassName = classnames( {
"items-center justify-center w-[49px] h-[67px]": layout === "vertical"
const iconWraperClasses = classnames( {
"items-center justify-center w-[49px]": layout === "vertical"
} );
const displayUploadStatus = ( ) => {
if ( progress === 0 ) {
return (
<View className={iconViewClassName}>
<View className={iconWraperClasses}>
{startUploadIcon}
</View>
);
}
if ( progress <= 0.05 ) {
return (
<View className={iconViewClassName}>
<View className={iconWraperClasses}>
{uploadTappedIcon}
</View>
);
}
if ( progress > 0.05 && progress < 1 ) {
return (
<View className={iconViewClassName}>
<View className={iconWraperClasses}>
{uploadInProgressIcon}
</View>
);
}
// Test of end state before animation
if ( progress === 10 ) {
return (
<View className={iconWraperClasses}>
{uploadCompleteIcon}
</View>
);
}
// Test of end state with all elements overlayed
if ( progress === 11 ) {
return (
<View className="justify-center">
<View
className={classnames( "absolute", {
"bottom-0": layout === "horizontal"
} )}
>
<View className={iconWraperClasses}>
{uploadCompleteIcon}
</View>
</View>
{children}
</View>
);
}
return (
<>
<View className={iconViewClassName}>
{uploadCompleteIcon}
<View
className={classnames( "absolute", {
"bottom-0": layout === "horizontal"
} )}
>
<View className={iconWraperClasses}>
{fadeOutUploadCompleteIcon}
</View>
</View>
{fadeInObsStatusComponent}
</>
@@ -227,6 +264,10 @@ const UploadStatus = ( {
<View
accessible
accessibilityLabel={accessibilityLabelText( )}
className={classnames( {
"h-[44px] justify-end": layout === "horizontal",
"justify-center": layout !== "horizontal"
} )}
>
{displayUploadStatus( )}
</View>

View File

@@ -46,6 +46,8 @@ import {
} from "components/SharedComponents";
import AddObsButton from "components/SharedComponents/Buttons/AddObsButton";
import glyphmap from "components/SharedComponents/INatIcon/glyphmap.json";
import ObsGridItem from "components/SharedComponents/ObservationsFlashList/ObsGridItem";
import ObsListItem from "components/SharedComponents/ObservationsFlashList/ObsListItem";
import { fontMonoClass, ScrollView, View } from "components/styledComponents";
import { RealmContext } from "providers/contexts";
import type { Node } from "react";
@@ -96,8 +98,16 @@ const UiLibrary = (): Node => {
vision: false
};
const taxonWithPhoto = realm.objects( "Taxon" ).filtered( "defaultPhoto.url != nil" )[0];
const iconicTaxon = realm.objects( "Taxon" ).filtered( "isIconic == true" )[0];
const aves = {
id: 1,
name: "Aves",
preferred_common_name: "Birds",
rank: "family",
rank_level: 60,
iconic_taxon_name: "Aves",
isIconic: true
};
const taxonWithPhoto = realm.objects( "Taxon" ).filtered( "defaultPhoto.url != nil" )[0] || aves;
const species = realm.objects( "Taxon" )
.filtered( "preferred_common_name != nil AND rank = 'species'" )[0];
@@ -612,20 +622,11 @@ const UiLibrary = (): Node => {
<Heading2 className="my-2">Confidence Interval</Heading2>
<ConfidenceInterval confidence={3} activeColor="bg-inatGreen" />
<Heading2 className="my-2">Taxon Result</Heading2>
<TaxonResult
taxon={{
id: 1,
name: "Aves",
preferred_common_name: "Birds",
rank: "family",
rank_level: 60,
iconic_taxon_name: "Aves"
}}
/>
<TaxonResult taxon={aves} />
<Heading3>Taxon w/ photo</Heading3>
<TaxonResult taxon={taxonWithPhoto} />
<Heading3>Iconic taxon</Heading3>
<TaxonResult taxon={iconicTaxon} />
<TaxonResult taxon={aves} fetchRemote={false} fromLocal={false} />
<Heading2 className="my-2">Iconic Taxon Chooser</Heading2>
<IconicTaxonChooser
taxon={{
@@ -675,6 +676,66 @@ const UiLibrary = (): Node => {
description="This is a description"
/>
<Heading1 className="my-2">ObsGridItem</Heading1>
<Heading2 className="my-2">Synced</Heading2>
<ObsGridItem
observation={{ uuid: "the-uuid", _synced_at: new Date( ) }}
uploadState={false}
/>
<Heading2 className="my-2">Upload needed</Heading2>
<ObsGridItem observation={{ uuid: "the-uuid" }} />
<Heading2 className="my-2">Upload in progress</Heading2>
<ObsGridItem
observation={{ uuid: "the-uuid" }}
uploadState={{ uploadProgress: { "the-uuid": 0.4 } }}
/>
<Heading2 className="my-2">Upload complete, w/ animation</Heading2>
<ObsGridItem
observation={{
uuid: "the-uuid"
}}
uploadState={{ uploadProgress: { "the-uuid": 1 } }}
/>
<Heading2 className="my-2">Upload complete, before animation</Heading2>
<ObsGridItem
observation={{ uuid: "the-uuid" }}
uploadState={{ uploadProgress: { "the-uuid": 10 } }}
/>
<Heading2 className="my-2">Upload complete, overlay of animated elements</Heading2>
<ObsGridItem
observation={{ uuid: "the-uuid" }}
uploadState={{ uploadProgress: { "the-uuid": 11 } }}
/>
<Heading1 className="my-2">ObsListItem</Heading1>
<Heading2 className="my-2">Synced</Heading2>
<ObsListItem
observation={{ uuid: "the-uuid", _synced_at: new Date( ) }}
uploadState={false}
/>
<Heading2 className="my-2">Upload needed</Heading2>
<ObsListItem observation={{ uuid: "the-uuid" }} />
<Heading2 className="my-2">Upload in progress</Heading2>
<ObsListItem
observation={{ uuid: "the-uuid" }}
uploadState={{ uploadProgress: { "the-uuid": 0.4 } }}
/>
<Heading2 className="my-2">Upload complete, w/ animation</Heading2>
<ObsListItem
observation={{ uuid: "the-uuid" }}
uploadState={{ uploadProgress: { "the-uuid": 1 } }}
/>
<Heading2 className="my-2">Upload complete, before animation</Heading2>
<ObsListItem
observation={{ uuid: "the-uuid" }}
uploadState={{ uploadProgress: { "the-uuid": 10 } }}
/>
<Heading2 className="my-2">Upload complete, overlay of animated elements</Heading2>
<ObsListItem
observation={{ uuid: "the-uuid" }}
uploadState={{ uploadProgress: { "the-uuid": 11 } }}
/>
<Heading1 className="my-2">More Stuff!</Heading1>
<Body1 className="h-[400px]">
Useless spacer at the end because height in NativeWind is confusing.

View File

@@ -103,6 +103,7 @@ module.exports = {
inatGreenDisabledDark: "#3A5600",
lightGray: "#E8E8E8",
mediumGray: "#BFBFBF",
mediumGrayGhost: "#BFBFBF33",
warningRed: "#9B1010",
warningRedDisabled: "#b06365",
warningYellow: "#E6A939",
@@ -115,7 +116,8 @@ module.exports = {
blue: "#0000FF",
deepPink: "#FF1493",
deeppink: "#FF1493",
DeepPink: "#FF1493"
DeepPink: "#FF1493",
orange: "#FFA500"
},
screens: {
sm: "240px",

View File

@@ -21,10 +21,10 @@ exports[`ObsGridItem for an observation with a photo should render 1`] = `
"borderTopRightRadius": 15,
},
{
"height": 62,
"width": 200,
},
{
"width": "100%",
"width": 200,
},
],
]
@@ -61,14 +61,14 @@ exports[`ObsGridItem for an observation with a photo should render 1`] = `
}
endPoint={
{
"x": 0.5,
"y": 1,
"x": 0,
"y": 0.75,
}
}
locations={null}
startPoint={
{
"x": 0.5,
"x": 0,
"y": 0,
}
}
@@ -117,6 +117,18 @@ exports[`ObsGridItem for an observation with a photo should render 1`] = `
<View
accessibilityLabel="Saved observation, in queue to upload"
accessible={true}
style={
[
[
{
"height": 44,
},
{
"justifyContent": "flex-end",
},
],
]
}
>
<View>
<View
@@ -258,10 +270,10 @@ exports[`ObsGridItem for an observation without a photo should render 1`] = `
"borderTopRightRadius": 15,
},
{
"height": 62,
"width": 200,
},
{
"width": "100%",
"width": 200,
},
],
]
@@ -294,14 +306,19 @@ exports[`ObsGridItem for an observation without a photo should render 1`] = `
"borderTopRightRadius": 15,
},
{
"height": 62,
"width": 200,
},
{
"width": "100%",
"width": 200,
},
{
"backgroundColor": "#454545",
},
{
"flexBasis": "0%",
"flexGrow": 1,
"flexShrink": 1,
},
{
"justifyContent": "center",
},
@@ -322,8 +339,8 @@ exports[`ObsGridItem for an observation without a photo should render 1`] = `
style={
[
{
"color": "#ffffff",
"fontSize": 22,
"color": "#BFBFBF33",
"fontSize": 100,
},
undefined,
{
@@ -347,14 +364,14 @@ exports[`ObsGridItem for an observation without a photo should render 1`] = `
}
endPoint={
{
"x": 0.5,
"y": 1,
"x": 0,
"y": 0.75,
}
}
locations={null}
startPoint={
{
"x": 0.5,
"x": 0,
"y": 0,
}
}
@@ -454,6 +471,18 @@ exports[`ObsGridItem for an observation without a photo should render 1`] = `
<View
accessibilityLabel="Saved observation, in queue to upload"
accessible={true}
style={
[
[
{
"height": 44,
},
{
"justifyContent": "flex-end",
},
],
]
}
>
<View>
<View

View File

@@ -4,53 +4,83 @@ exports[`UploadStatus displays progress bar when progress is greater than 5% cor
<View
accessibilityLabel="Upload 50 percent complete"
accessible={true}
style={
[
[
{
"justifyContent": "center",
},
],
]
}
>
<View>
<View
accessibilityLabel="Upload in progress"
style={
[
[
{
"position": "absolute",
"alignItems": "center",
},
{
"justifyContent": "center",
},
{
"width": 44,
},
{
"height": 44,
},
],
]
}
testID="UploadIcon.progress.undefined"
>
<Text
allowFontScaling={false}
selectable={false}
<View
accessibilityLabel="Upload in progress"
style={
[
{
"color": "#454545",
"fontSize": 15,
},
undefined,
{
"fontFamily": "INatIcon",
"fontStyle": "normal",
"fontWeight": "normal",
},
{},
[
{
"position": "absolute",
},
],
]
}
testID="UploadIcon.progress.undefined"
>
</Text>
<Text
allowFontScaling={false}
selectable={false}
style={
[
{
"color": "#454545",
"fontSize": 15,
},
undefined,
{
"fontFamily": "INatIcon",
"fontStyle": "normal",
"fontWeight": "normal",
},
{},
]
}
>
</Text>
</View>
<CircularProgress
activeStrokeColor="#454545"
activeStrokeWidth={2}
inActiveStrokeOpacity={0}
maxValue={1}
radius={18}
showProgressValue={false}
testID="UploadStatus.CircularProgress"
value={0.5}
/>
</View>
<CircularProgress
activeStrokeColor="#454545"
activeStrokeWidth={2}
inActiveStrokeOpacity={0}
maxValue={1}
radius={18}
showProgressValue={false}
testID="UploadStatus.CircularProgress"
value={0.5}
/>
</View>
</View>
`;
@@ -59,89 +89,119 @@ exports[`UploadStatus displays progress bar when progress is less than 5% correc
<View
accessibilityLabel="Saved observation, in queue to upload"
accessible={true}
style={
[
[
{
"justifyContent": "center",
},
],
]
}
>
<View>
<View
accessibilityLabel="Upload in progress"
style={
[
[
{
"position": "absolute",
"alignItems": "center",
},
{
"justifyContent": "center",
},
{
"width": 44,
},
{
"height": 44,
},
],
]
}
testID="UploadIcon.progress.undefined"
>
<Text
allowFontScaling={false}
selectable={false}
<View
accessibilityLabel="Upload in progress"
style={
[
{
"color": "#454545",
"fontSize": 15,
},
undefined,
{
"fontFamily": "INatIcon",
"fontStyle": "normal",
"fontWeight": "normal",
},
{},
]
}
>
</Text>
</View>
<View
animatedStyle={
{
"value": {
"transform": [
[
{
"rotateZ": "0deg",
"position": "absolute",
},
],
},
]
}
}
collapsable={false}
style={
[
testID="UploadIcon.progress.undefined"
>
<Text
allowFontScaling={false}
selectable={false}
style={
[
{
"color": "#454545",
"fontSize": 15,
},
undefined,
{
"fontFamily": "INatIcon",
"fontStyle": "normal",
"fontWeight": "normal",
},
{},
]
}
>
</Text>
</View>
<View
animatedStyle={
{
"transform": [
{
"rotateZ": "0deg",
},
],
},
]
}
>
<Text
allowFontScaling={false}
selectable={false}
"value": {
"transform": [
{
"rotateZ": "0deg",
},
],
},
}
}
collapsable={false}
style={
[
{
"color": "#454545",
"fontSize": 33,
"transform": [
{
"rotateZ": "0deg",
},
],
},
undefined,
{
"fontFamily": "INatIcon",
"fontStyle": "normal",
"fontWeight": "normal",
},
{},
]
}
>
</Text>
<Text
allowFontScaling={false}
selectable={false}
style={
[
{
"color": "#454545",
"fontSize": 33,
},
undefined,
{
"fontFamily": "INatIcon",
"fontStyle": "normal",
"fontWeight": "normal",
},
{},
]
}
>
</Text>
</View>
</View>
</View>
</View>

View File

@@ -155,6 +155,11 @@ exports[`TaxonResult should render correctly 1`] = `
{
"width": 62,
},
{
"flexBasis": "0%",
"flexGrow": 1,
"flexShrink": 1,
},
{
"justifyContent": "center",
},