Start using Tailwind CSS (#185)

Starts changing styles over to tailwind via nativewind.

* Update node to 16.17.0
* Use styled() to ignore flow errors about className or tw props when styling components with nativewind
* Upgrade realm to make test suite run; set failing test in Explore as todo
* Add workaround for getting pods to run with XCode 14
* Fix for loading remote obs with infinite scroll
* Add styling section to README
* Use IconButton from rn-paper to make buttons more responsive to press
* Add caret next to camera roll album picker
* Fixed broken addition of gallery photos to existing observation
* Removed flatlist from scrollview on ProjectDetails (apparently not allowed?)
* Moved border style from Image to container in PhotoCarousel (border color
  not allowed for images?)

Co-authored-by: Ken-ichi Ueda <kenichi.ueda@gmail.com>
This commit is contained in:
Amanda Bullington
2022-10-19 17:15:55 -07:00
committed by GitHub
parent e81894d406
commit c740a06224
105 changed files with 5118 additions and 2151 deletions

View File

@@ -87,6 +87,9 @@ git add src/i18n/l10n/*
git commit -a -m "Updated translations"
```
## Styling
We're using Nativewind, a styling system for React Native based on Tailwind CSS. Check the [Nativewind documentation](https://www.nativewind.dev/) to see what styles can be used in RN.
## Troubleshooting
1. Run `react-native clean-project`. This will give you options to clean caches, clean builds, reinstall pods, and reinstall node_modules. Using this eliminates a lot of hard-to-diagnose build issues.

View File

@@ -3,6 +3,7 @@ module.exports = {
plugins: [
"react-native-reanimated/plugin",
"transform-inline-environment-variables",
"nativewind/babel",
["module-resolver", {
alias: {
api: "./src/api",

View File

@@ -26,7 +26,19 @@ target 'iNaturalistReactNative' do
# you should disable the next line.
# use_flipper!()
# code below appears to be necessary for building pods with XCode 14: https://github.com/facebook/react-native/issues/34673#issuecomment-1252114414
post_install do |installer|
react_native_post_install(installer)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
# Add these lines for Xcode 14 builds
installer.generated_projects.each do |project|
project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings["DEVELOPMENT_TEAM"] = "iNaturalist, LLC"
end
end
end
# End of added lines
# https://github.com/Agontuk/react-native-geolocation-service/issues/287#issuecomment-980772489
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
@@ -34,5 +46,4 @@ target 'iNaturalistReactNative' do
end
end
end
end

View File

@@ -618,6 +618,6 @@ SPEC CHECKSUMS:
VisionCamera: c1c171fcdbf18c438987847f785829c5638f3a4c
Yoga: 99652481fcd320aefa4a7ef90095b95acd181952
PODFILE CHECKSUM: d1bc9d7f55b5f7449c3ceb6a7622454bc4f8b972
PODFILE CHECKSUM: 75b1e337bfba8ed4c2d8a23c444fe54409246c63
COCOAPODS: 1.11.3

View File

@@ -499,8 +499,8 @@
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = YES;
@@ -557,8 +557,8 @@
"$(inherited)",
);
LIBRARY_SEARCH_PATHS = (
"$(SDKROOT)/usr/lib/swift",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
"\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
"\"$(inherited)\"",
);
MTL_ENABLE_DEBUG_INFO = NO;

4463
package-lock.json generated
View File

File diff suppressed because it is too large Load Diff

View File

@@ -42,6 +42,7 @@
"inaturalistjs": "github:inaturalist/inaturalistjs",
"intl-pluralrules": "^1.3.1",
"lodash": "^4.17.21",
"nativewind": "^2.0.10",
"radio-buttons-react-native": "^1.0.4",
"react": "17.0.2",
"react-dom": "17.0.2",
@@ -114,6 +115,7 @@
"react-native-clean-project": "^4.0.1",
"react-native-codegen": "^0.0.7",
"react-test-renderer": "17.0.2",
"tailwindcss": "^3.1.8",
"yargs": "^17.3.1"
},
"jest": {

View File

@@ -1,12 +1,12 @@
// @flow
import { useNavigation } from "@react-navigation/native";
import TranslatedText from "components/SharedComponents/TranslatedText";
import { Text, View } from "components/styledComponents";
import { t } from "i18next";
import { ObsEditContext } from "providers/contexts";
import * as React from "react";
import { Pressable, View } from "react-native";
import { Avatar } from "react-native-paper";
import { textStyles, viewStyles } from "styles/sharedComponents/modal";
import { IconButton } from "react-native-paper";
import colors from "styles/colors";
type Props = {
closeModal: ( ) => void
@@ -38,36 +38,43 @@ const CameraOptionsModal = ( { closeModal }: Props ): React.Node => {
navAndCloseModal( "ObsEdit" );
};
const bulletedText = [
t( "Take-a-photo-with-your-camera" ),
t( "Upload-a-photo-from-your-gallery" ),
t( "Record-a-sound" )
];
const renderIconButton = ( icon, className, onPress, size = 30 ) => (
<IconButton
size={size}
mode="contained"
icon={icon}
containerColor={colors.inatGreen}
iconColor={colors.white}
className={`absolute ${className}`}
onPress={onPress}
/>
);
return (
<View>
<TranslatedText style={textStyles.whiteText} text="CREATE-AN-OBSERVATION" />
<View style={viewStyles.whiteModal}>
<TranslatedText text="STEP-1-EVIDENCE" />
<TranslatedText text="The-first-thing-you-need-is-evidence" />
<TranslatedText text="Take-a-photo-with-your-camera" />
<TranslatedText text="Upload-a-photo-from-your-gallery" />
<TranslatedText text="Record-a-sound" />
<TranslatedText text="Submit-without-evidence" />
<>
<View className="bg-white rounded-xl p-5">
<Text className="text-2xl">{t( "Evidence" )}</Text>
<Text className="color-grayText my-2">{t( "Add-evidence-of-an-organism" )}</Text>
<Text className="color-grayText my-2">{t( "You-can" )}</Text>
{bulletedText.map( string => (
<Text className="color-grayText" key={string}>
{`\u2022 ${string}`}
</Text>
) )}
</View>
<Pressable onPress={navToStandardCamera}>
<Avatar.Icon size={40} icon="camera" />
</Pressable>
{!currentObs && (
<Pressable onPress={navToPhotoGallery}>
<Avatar.Icon size={40} icon="folder-multiple-image" />
</Pressable>
)}
{!hasSound && (
<Pressable onPress={navToSoundRecorder}>
<Avatar.Icon size={40} icon="microphone" />
</Pressable>
)}
{!currentObs && (
<Pressable onPress={navToObsEdit}>
<Avatar.Icon size={40} icon="square-edit-outline" />
</Pressable>
)}
</View>
{renderIconButton( "plus", "bottom-0 left-1/3 px-2", ( ) => { }, 80 )}
{!currentObs && renderIconButton( "square-edit-outline", "bottom-6 left-10", navToObsEdit )}
{renderIconButton( "camera", "bottom-24 left-20", navToStandardCamera )}
{!currentObs
&& renderIconButton( "folder-multiple-image", "bottom-24 right-20", navToPhotoGallery )}
{!hasSound && renderIconButton( "microphone", "bottom-6 right-10", navToSoundRecorder )}
</>
);
};

View File

@@ -3,8 +3,6 @@
import type { Node } from "react";
import React, { useEffect, useRef } from "react";
import { Animated } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import viewStyles from "styles/camera/fadeInOutView";
type Props = {
savingPhoto: boolean
@@ -19,8 +17,6 @@ const fade = value => ( {
const FadeInOutView = ( { savingPhoto }: Props ): Node => {
const fadeAnimation = useRef( new Animated.Value( 0 ) ).current;
const insets = useSafeAreaInsets( );
useEffect( ( ) => {
if ( savingPhoto ) {
Animated.sequence( [
@@ -30,20 +26,11 @@ const FadeInOutView = ( { savingPhoto }: Props ): Node => {
}
}, [savingPhoto, fadeAnimation] );
// $FlowIgnore
const bottomOfPhotoPreview = viewStyles.bottomOfPhotoPreview.height + insets.top;
return (
<Animated.View
style={[
viewStyles.fadingContainer,
{
top: bottomOfPhotoPreview,
// $FlowIgnore
height: viewStyles.fadingContainer.height - bottomOfPhotoPreview,
opacity: fadeAnimation
}
]}
style={{
opacity: fadeAnimation
}}
/>
);
};

View File

@@ -3,7 +3,6 @@
import type { Node } from "react";
import React, { useEffect } from "react";
import { Animated } from "react-native";
import { viewStyles } from "styles/camera/standardCamera";
type Props = {
tappedCoordinates: Object,
@@ -27,12 +26,14 @@ const FocusSquare = ( { tappedCoordinates, tapToFocusAnimation }: Props ): Node
if ( !tappedCoordinates ) { return null; }
return (
// $FlowIgnore
<Animated.View
className="w-16 h-16 absolute border border-white rounded-lg"
style={[{
left: tappedCoordinates.x,
top: tappedCoordinates.y,
opacity: tapToFocusAnimation
}, viewStyles.tapToFocusSquare
}
]}
/>
);

View File

@@ -4,12 +4,10 @@ import MediaViewer from "components/MediaViewer/MediaViewer";
import MediaViewerModal from "components/MediaViewer/MediaViewerModal";
import DeletePhotoDialog from "components/SharedComponents/DeletePhotoDialog";
import PhotoCarousel from "components/SharedComponents/PhotoCarousel";
import { Text, View } from "components/styledComponents";
import type { Node } from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Text, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { textStyles, viewStyles } from "styles/camera/photoPreview";
type Props = {
photoUris: Array<string>,
@@ -24,8 +22,6 @@ const PhotoPreview = ( { photoUris, setPhotoUris, savingPhoto }: Props ): Node =
const [initialPhotoSelected, setInitialPhotoSelected] = useState( null );
const [mediaViewerVisible, setMediaViewerVisible] = useState( false );
const insets = useSafeAreaInsets( );
const showModal = ( ) => setMediaViewerVisible( true );
const hideModal = ( ) => setMediaViewerVisible( false );
@@ -47,12 +43,7 @@ const PhotoPreview = ( { photoUris, setPhotoUris, savingPhoto }: Props ): Node =
};
const emptyDescription = ( ) => (
<Text style={[
textStyles.topPhotoText, {
// $FlowIgnore
bottom: textStyles.topPhotoText.bottom + insets.top
}]}
>
<Text className="text-white text-xl mt-20 ml-3">
{t( "Photos-you-take-will-appear-here" )}
</Text>
);
@@ -77,13 +68,7 @@ const PhotoPreview = ( { photoUris, setPhotoUris, savingPhoto }: Props ): Node =
hideModal={hideModal}
/>
</MediaViewerModal>
<View style={[
viewStyles.photoPreviewContainer, {
// $FlowIgnore
height: viewStyles.photoPreviewContainer.height + insets.top
}
]}
>
<View className="bg-black h-32">
<PhotoCarousel
photoUris={photoUris}
emptyComponent={emptyDescription}

View File

@@ -1,18 +1,16 @@
// @flow
import { useNavigation, useRoute } from "@react-navigation/native";
import { Pressable, Text, View } from "components/styledComponents";
import { t } from "i18next";
import { ObsEditContext, RealmContext } from "providers/contexts";
import type { Node } from "react";
import React, {
useContext, useEffect, useRef, useState
} from "react";
import { Pressable, Text, View } from "react-native";
import { Avatar, Snackbar, useTheme } from "react-native-paper";
import { Camera, useCameraDevices } from "react-native-vision-camera";
import { viewStyles } from "styles/camera/standardCamera";
import colors from "styles/colors";
import { textStyles } from "styles/obsDetails/obsDetails";
import Photo from "../../models/Photo";
import CameraView from "./CameraView";
@@ -25,7 +23,6 @@ export const MAX_PHOTOS_ALLOWED = 20;
const StandardCamera = ( ): Node => {
const { colors: themeColors } = useTheme( );
// TODO: figure out if there's a way to write location to photo metadata with RN
const { addPhotos } = useContext( ObsEditContext );
const navigation = useNavigation( );
const { params } = useRoute( );
@@ -99,46 +96,32 @@ const StandardCamera = ( ): Node => {
);
return (
<View style={viewStyles.container}>
<View className="flex-1 bg-black">
{device && <CameraView device={device} camera={camera} />}
<PhotoPreview photoUris={photoUris} setPhotoUris={setPhotoUris} savingPhoto={savingPhoto} />
<FadeInOutView savingPhoto={savingPhoto} />
<View style={viewStyles.bottomButtons}>
<View style={viewStyles.cameraSettingsRow}>
<Pressable
style={viewStyles.flashButton}
onPress={toggleFlash}
>
<View className="absolute bottom-0">
<View className="flex-row justify-between w-screen mb-4 px-4">
<Pressable onPress={toggleFlash}>
{renderCameraButton( "flash" )}
</Pressable>
<Pressable
style={viewStyles.cameraFlipButton}
onPress={flipCamera}
>
<Pressable onPress={flipCamera}>
{renderCameraButton( "camera-flip" )}
</Pressable>
<View />
</View>
<View style={viewStyles.cameraCaptureRow}>
<Pressable
style={viewStyles.captureButton}
onPress={takePhoto}
>
<View className="bg-black w-screen h-32 flex-row justify-between items-center px-4">
<View className="w-1/3" />
<Pressable onPress={takePhoto}>
{renderCameraButton( "circle-outline", disallowAddingPhotos )}
</Pressable>
<Pressable
style={viewStyles.nextButton}
onPress={navToObsEdit}
>
<Text style={textStyles.whiteText}>{t( "Next" )}</Text>
</Pressable>
<Text className="text-white text-xl w-1/3 text-center" onPress={navToObsEdit}>
{t( "Next" )}
</Text>
</View>
</View>
<Snackbar
visible={showAlert}
onDismiss={() => setShowAlert( false )}
onDismiss={( ) => setShowAlert( false )}
>
{t( "You-can-only-upload-20-media" )}
</Snackbar>

View File

@@ -18,8 +18,7 @@ const Explore = ( ): Node => {
const {
exploreList,
loadingExplore,
exploreFilters,
totalObservations
exploreFilters
} = useContext( ExploreContext );
const taxonId = exploreFilters ? exploreFilters.taxon_id : null;
@@ -36,7 +35,6 @@ const Explore = ( ): Node => {
taxonId={taxonId}
testID="Explore.observations"
mapHeight={mapHeight}
totalObservations={totalObservations}
/>
)}
<BottomCard />

View File

@@ -3,29 +3,26 @@
import { useNavigation } from "@react-navigation/native";
import { useQueryClient } from "@tanstack/react-query";
import Button from "components/SharedComponents/Buttons/Button";
import {
Image, KeyboardAvoidingView, Pressable,
SafeAreaView,
ScrollView, View
} from "components/styledComponents";
import { RealmContext } from "providers/contexts";
import type { Node } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import {
Image,
KeyboardAvoidingView,
Linking,
Platform,
Pressable,
SafeAreaView,
ScrollView,
TouchableOpacity,
View
TouchableOpacity
} from "react-native";
import {
Dialog, Paragraph, Portal, Text, TextInput
} from "react-native-paper";
import IconMaterial from "react-native-vector-icons/MaterialIcons";
import colors from "styles/colors";
import {
closeButton, imageStyles, textStyles, viewStyles
} from "styles/login/login";
import viewStyles from "styles/login/login";
import {
authenticateUser,
@@ -123,16 +120,18 @@ const Login = ( ): Node => {
testID="Login.signOutButton"
text={t( "Cancel" )}
/>
<Button level="primary" onPress={onSignOut} text={t( "Sign-out" )} />
</Dialog.Actions>
</Dialog>
</Portal>
<View style={viewStyles.logoutForm}>
{/* TODO: figure out how to account for safe area views with h-screen,
maybe something along these lines: https://github.com/mvllow/tailwindcss-safe-area/blob/70dbef61557b07e26b07a6167e13a377ba3c4625/index.js
*/}
<View className="self-center justify-center h-screen">
<Text testID="Login.loggedInAs">{t( "Logged-in-as", { username } )}</Text>
<Button
level="primary"
style={viewStyles.button}
onPress={showDialog}
testID="Login.signOutButton"
text="Sign-out"
@@ -144,16 +143,16 @@ const Login = ( ): Node => {
const loginForm = (
<>
<Image
style={imageStyles.logo}
className="self-center w-32 h-32"
resizeMode="contain"
source={require( "images/inat_logo.png" )}
/>
<Text style={textStyles.header}>{t( "Login-header" )}</Text>
<Text style={textStyles.subtitle}>{t( "Login-sub-title" )}</Text>
<Text style={textStyles.fieldText}>{t( "Username-or-Email" )}</Text>
<Text className="text-2xl self-center mt-5">{t( "Login-header" )}</Text>
<Text className="text-xl self-center text-center mt-5 mb-5">{t( "Login-sub-title" )}</Text>
<Text className="text-base mb-1">{t( "Username-or-Email" )}</Text>
<TextInput
style={viewStyles.input}
className="h-10 bg-tertiary"
onChangeText={text => {
setError( null );
setEmail( text );
@@ -165,9 +164,9 @@ const Login = ( ): Node => {
keyboardType="email-address"
selectionColor={colors.black}
/>
<Text style={textStyles.fieldText}>{t( "Password" )}</Text>
<Text className="text-base mb-1 mt-5">{t( "Password" )}</Text>
<TextInput
style={viewStyles.input}
className="h-10 bg-tertiary"
onChangeText={text => {
setError( null );
setPassword( text );
@@ -178,9 +177,9 @@ const Login = ( ): Node => {
selectionColor={colors.black}
/>
<TouchableOpacity onPress={forgotPassword}>
<Text style={textStyles.forgotPassword}>{t( "Forgot-Password" )}</Text>
<Text className="underline mt-4 self-end">{t( "Forgot-Password" )}</Text>
</TouchableOpacity>
{error && <Text style={textStyles.error}>{error}</Text>}
{error && <Text className="text-red self-center mt-5">{error}</Text>}
<Button
level="primary"
text="Log-in"
@@ -196,15 +195,13 @@ const Login = ( ): Node => {
return (
<KeyboardAvoidingView
behavior={Platform.OS === "ios" ? "padding" : "height"}
style={viewStyles.container}
className="flex-1"
>
<SafeAreaView style={[viewStyles.container]}>
<ScrollView
contentContainerStyle={viewStyles.paddedContainer}
>
<SafeAreaView className="flex-1">
<ScrollView className="flex-1 p-10">
<Pressable
onPress={() => navigation.goBack()}
style={closeButton.close}
className="absolute top-0 right-0"
>
<IconMaterial name="close" size={35} />
</Pressable>

View File

@@ -3,10 +3,8 @@
import { t } from "i18next";
import type { Node } from "react";
import React, { useState } from "react";
import {
Button, Text, TextInput, View
} from "react-native";
import { textStyles, viewStyles } from "styles/login/login";
import { View } from "react-native";
import { Button, Text, TextInput } from "react-native-paper";
import { registerUser } from "./AuthenticationService";
@@ -27,26 +25,26 @@ const SignUp = (): Node => {
return (
<View>
<Text style={textStyles.text}>{t( "Sign-Up" )}</Text>
<Text className="text-base mb-1">{t( "Sign-Up" )}</Text>
<Text style={textStyles.text}>{t( "Email" )}</Text>
<Text className="text-base mb-1">{t( "Email" )}</Text>
<TextInput
style={viewStyles.input}
className="h-10 bg-tertiary"
onChangeText={setEmail}
value={email}
autoComplete="email"
/>
<Text style={textStyles.text}>{t( "Username" )}</Text>
<Text className="text-base mb-1">{t( "Username" )}</Text>
<TextInput
style={viewStyles.input}
className="h-10 bg-tertiary"
onChangeText={setUsername}
value={username}
/>
<Text style={textStyles.text}>{t( "Password" )}</Text>
<Text className="text-base mb-1">{t( "Password" )}</Text>
<TextInput
style={viewStyles.input}
className="h-10 bg-tertiary"
onChangeText={setPassword}
value={password}
secureTextEntry

View File

@@ -1,16 +1,16 @@
// @flow
import { Image, View } from "components/styledComponents";
import type { Node } from "react";
import React, { useRef } from "react";
import { Dimensions, Image, View } from "react-native";
import { Dimensions } from "react-native";
import ImageZoom from "react-native-image-pan-zoom";
import { imageStyles, viewStyles } from "styles/mediaViewer/mediaViewer";
// lifted from this issue: https://github.com/ascoders/react-native-image-zoom/issues/42#issuecomment-734209924
const { width } = Dimensions.get( "screen" );
const { width, height } = Dimensions.get( "screen" );
// $FlowIgnore
const selectedImageHeight = imageStyles.selectedPhoto.height;
const SELECTED_IMAGE_HEIGHT = height - 350;
type Props = {
source: Object
@@ -26,9 +26,9 @@ const ImageViewer = ( { source }: Props ): Node => {
return (
<ImageZoom
cropWidth={width}
cropHeight={selectedImageHeight}
cropHeight={SELECTED_IMAGE_HEIGHT}
imageWidth={width}
imageHeight={selectedImageHeight}
imageHeight={SELECTED_IMAGE_HEIGHT}
minScale={1}
onStartShouldSetPanResponder={e => {
const pinching = e.nativeEvent.touches.length === 2;
@@ -38,12 +38,12 @@ const ImageViewer = ( { source }: Props ): Node => {
onMove={handleMove}
>
<View
style={viewStyles.fullSize}
className="w-full h-full"
onStartShouldSetResponder={e => e.nativeEvent.touches.length < 2 && scaleValue.current <= 1}
>
<Image
source={source}
style={imageStyles.fullSize}
className="w-full h-full"
/>
</View>
</ImageZoom>

View File

@@ -1,12 +1,11 @@
// @flow
import { SafeAreaView, Text } from "components/styledComponents";
import type { Node } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { SafeAreaView } from "react-native";
import { Appbar, Button, IconButton } from "react-native-paper";
import { Button, IconButton } from "react-native-paper";
import colors from "styles/colors";
import { textStyles, viewStyles } from "styles/mediaViewer/mediaViewer";
import HorizontalScroll from "./HorizontalScroll";
@@ -35,13 +34,10 @@ const MediaViewer = ( {
}, [numOfPhotos, hideModal] );
return (
<SafeAreaView style={viewStyles.container}>
<Appbar.Header style={viewStyles.container}>
<Appbar.Content
title={t( "X-Photos", { photoCount: numOfPhotos } )}
titleStyle={textStyles.whiteText}
/>
</Appbar.Header>
<SafeAreaView>
<Text className="text-2xl text-white self-center mb-2">
{t( "X-Photos", { photoCount: numOfPhotos } )}
</Text>
<HorizontalScroll
photoUris={photoUris}
initialPhotoSelected={initialPhotoSelected}
@@ -55,7 +51,7 @@ const MediaViewer = ( {
iconColor={colors.white}
/>
<Button
style={viewStyles.alignRight}
className="flex-row justify-end"
onPress={showDialog}
>
{t( "Remove-Photo" )}

View File

@@ -1,15 +1,14 @@
// @flow
import { HeaderBackButton } from "@react-navigation/elements";
import { useNavigation } from "@react-navigation/native";
import CustomHeader from "components/SharedComponents/CustomHeader";
import type { Node } from "react";
import React from "react";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { Button, Headline } from "react-native-paper";
import { Button } from "react-native-paper";
import useCurrentUser from "sharedHooks/useCurrentUser";
import colors from "styles/colors";
import { viewStyles } from "styles/obsDetails/obsDetailsHeader";
type Props = {
observation: ?Object
@@ -25,15 +24,14 @@ const ObsDetailsHeader = ( { observation }: Props ): Node => {
const navToObsEdit = ( ) => navigation.navigate( "ObsEdit", { uuid: observation?.uuid } );
return (
<View style={viewStyles.headerRow}>
<HeaderBackButton onPress={( ) => navigation.goBack( )} />
<Headline>{t( "Observation" )}</Headline>
{
<CustomHeader
headerText={t( "Observation" )}
rightIcon={
( obsCreatedLocally || obsOwnedByCurrentUser )
? <Button icon="pencil" onPress={navToObsEdit} textColor={colors.gray} />
: <View />
}
</View>
/>
);
};

View File

@@ -8,6 +8,7 @@ import {
import { useNavigation } from "@react-navigation/native";
import fetchSearchResults from "api/search";
import ViewNoFooter from "components/SharedComponents/ViewNoFooter";
import { Text } from "components/styledComponents";
import * as React from "react";
import { useRef, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -20,7 +21,7 @@ import {
View
} from "react-native";
import {
Button, Headline, Text, TextInput
Button, Headline, TextInput
} from "react-native-paper";
import uuid from "react-native-uuid";
import IconMaterial from "react-native-vector-icons/MaterialIcons";
@@ -165,7 +166,9 @@ const AddID = ( { route }: Props ): React.Node => {
</View>
</View>
)}
<Text>{t( "Search-Taxon-ID" )}</Text>
<Text className="color-grayText">
{t( "Search-for-a-taxon-to-add-an-identification" )}
</Text>
<TextInput
testID="SearchTaxon"
left={SearchTaxonIcon}

View File

@@ -2,13 +2,12 @@
import { HeaderBackButton } from "@react-navigation/elements";
import { useNavigation } from "@react-navigation/native";
import { Pressable, Text, View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { useTranslation } from "react-i18next";
import { Pressable, View } from "react-native";
import { Headline } from "react-native-paper";
import IconMaterial from "react-native-vector-icons/MaterialIcons";
import { viewStyles } from "styles/obsDetails/addID";
import colors from "styles/colors";
type Props = {
showEditComment: boolean,
@@ -20,9 +19,9 @@ const AddIDHeader = ( { showEditComment, onEditCommentPressed }: Props ): Node =
const navigation = useNavigation( );
return (
<View style={viewStyles.headerRow}>
<HeaderBackButton onPress={( ) => navigation.goBack( )} />
<Headline>{t( "Add-ID-Header" )}</Headline>
<View className="flex-row justify-between">
<HeaderBackButton onPress={( ) => navigation.goBack( )} tintColor={colors.black} />
<Text className="text-2xl self-center">{t( "Add-an-ID" )}</Text>
{showEditComment
? (
<Pressable
@@ -32,7 +31,7 @@ const AddIDHeader = ( { showEditComment, onEditCommentPressed }: Props ): Node =
<IconMaterial name="textsms" size={25} />
</Pressable>
)
: <View />}
: <View className="w-16" />}
</View>
);
};

View File

@@ -6,7 +6,6 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Pressable, Text } from "react-native";
import { displayDateTimeObsEdit } from "sharedHelpers/dateAndTime";
import { textStyles } from "styles/obsEdit/obsEdit";
type Props = {
handleDatePicked: ( Date ) => void,
@@ -46,7 +45,7 @@ const DatePicker = ( { handleDatePicked, currentObs }: Props ): Node => {
<Pressable
onPress={openModal}
>
<Text style={textStyles.text} testID="ObsEdit.time">
<Text testID="ObsEdit.time">
{displayDate( ) || t( "Add-Date-Time" )}
</Text>
</Pressable>

View File

@@ -1,13 +1,12 @@
// @flow
import PhotoCarousel from "components/SharedComponents/PhotoCarousel";
import { Text, View } from "components/styledComponents";
import { ObsEditContext } from "providers/contexts";
import type { Node } from "react";
import React, { useContext } from "react";
import { useTranslation } from "react-i18next";
import { Text } from "react-native";
import { createObservedOnStringForUpload } from "sharedHelpers/dateAndTime";
import { textStyles } from "styles/obsEdit/obsEdit";
import DatePicker from "./DatePicker";
@@ -90,7 +89,7 @@ const EvidenceSection = ( {
};
return (
<>
<View className="mx-5">
{/* TODO: allow user to tap into bigger version of photo (crop screen) */}
<PhotoCarousel
photoUris={photoUris}
@@ -115,14 +114,10 @@ const EvidenceSection = ( {
</Text>
</Pressable>
*/}
<Text style={textStyles.text}>
{currentObs.place_guess}
</Text>
<Text style={textStyles.text}>
{displayLocation( ) || t( "No-Location" )}
</Text>
<Text>{currentObs.place_guess}</Text>
<Text>{displayLocation( ) || t( "No-Location" )}</Text>
<DatePicker currentObs={currentObs} handleDatePicked={handleDatePicked} />
</>
</View>
);
};

View File

@@ -1,16 +1,14 @@
// @flow
import { useNavigation } from "@react-navigation/native";
import PlaceholderText from "components/PlaceholderText";
import Button from "components/SharedComponents/Buttons/Button";
import { Text, View } from "components/styledComponents";
import { iconicTaxaIds, iconicTaxaNames } from "dictionaries/iconicTaxaIds";
import { ObsEditContext } from "providers/contexts";
import type { Node } from "react";
import React, { useContext } from "react";
import { useTranslation } from "react-i18next";
import {
FlatList, Pressable, Text, View
} from "react-native";
import { FlatList } from "react-native";
import { Avatar, useTheme } from "react-native-paper";
import { textStyles, viewStyles } from "styles/obsEdit/obsEdit";
@@ -40,40 +38,27 @@ const IdentificationSection = ( ): Node => {
const label = t( iconicTaxaNames[id] );
const selected = identification && id === identification.id;
return (
<Pressable
<Avatar.Icon
size={50}
onPress={( ) => updateIdentification( { id, preferred_common_name: label } )}
>
<Avatar.Text
size={54}
label={label}
labelStyle={textStyles.smallLabel}
style={[
{ backgroundColor: colors.tertiary },
viewStyles.iconicTaxaButtons,
selected && viewStyles.selected
]}
/>
</Pressable>
className="mx-3 my-3"
style={[
{ backgroundColor: colors.tertiary },
selected && viewStyles.selected
]}
/>
);
};
const displayIdentification = ( ) => {
if ( identification ) {
return (
<View style={viewStyles.row}>
<View className="flex-row justify-between mx-5 mt-2">
<View>
<Text style={textStyles.text}>
{identification.preferred_common_name}
</Text>
<Text style={textStyles.text}>
{identification.name}
</Text>
<Text>{identification.preferred_common_name}</Text>
<Text>{identification.name}</Text>
</View>
<Pressable
onPress={navToAddID}
>
<PlaceholderText text="edit" style={[textStyles.text]} />
</Pressable>
<Avatar.Icon icon="pencil" onPress={navToAddID} size={35} style={viewStyles.editIcon} />
</View>
);
}
@@ -82,7 +67,7 @@ const IdentificationSection = ( ): Node => {
<Button
level="primary"
onPress={navToAddID}
text="View Identification Suggestions"
text="Add-an-Identification"
style={viewStyles.button}
testID="ObsEdit.Suggestions"
/>

View File

@@ -7,7 +7,6 @@ import { Keyboard, useWindowDimensions } from "react-native";
import { TextInput } from "react-native-paper";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import colors from "styles/colors";
import { textStyles } from "styles/obsEdit/notes";
type Props = {
addNotes: Function,
@@ -48,7 +47,8 @@ const Notes = ( { addNotes, description }: Props ): Node => {
onBlur={( ) => addNotes( localDescription )}
value={localDescription}
placeholder={t( "Add-optional-notes" )}
style={[textStyles.notes, keyboardOffset > 0 && offset]}
className="pl-3 bg-white"
style={[keyboardOffset > 0 && offset]}
testID="ObsEdit.notes"
underlineColor={colors.white}
/>

View File

@@ -14,17 +14,18 @@ import Button from "components/SharedComponents/Buttons/Button";
import EvidenceButton from "components/SharedComponents/Buttons/EvidenceButton";
import KebabMenu from "components/SharedComponents/KebabMenu";
import ScrollNoFooter from "components/SharedComponents/ScrollNoFooter";
import { Pressable, Text, View } from "components/styledComponents";
import { ObsEditContext, RealmContext } from "providers/contexts";
import type { Node } from "react";
import React, {
useContext, useEffect, useRef, useState
} from "react";
import { useTranslation } from "react-i18next";
import { Pressable, Text, View } from "react-native";
import { Headline, Menu } from "react-native-paper";
import { Menu } from "react-native-paper";
import Icon from "react-native-vector-icons/MaterialIcons";
import fetchUserLocation from "sharedHelpers/fetchUserLocation";
import useLoggedIn from "sharedHooks/useLoggedIn";
import colors from "styles/colors";
import { textStyles, viewStyles } from "styles/obsEdit/obsEdit";
import Photo from "../../models/Photo";
@@ -104,17 +105,17 @@ const ObsEdit = ( ): Node => {
);
const renderHeader = ( ) => (
<View style={viewStyles.headerRow}>
<HeaderBackButton onPress={handleBackButtonPress} />
<View className="flex-row justify-between">
<HeaderBackButton onPress={handleBackButtonPress} tintColor={colors.black} />
{observations.length === 1
? <Headline>{t( "New-Observation" )}</Headline>
? <Text className="text-2xl">{t( "New-Observation" )}</Text>
: (
<View style={viewStyles.multipleObsRow}>
<Pressable onPress={showPrevObservation} style={viewStyles.caret}>
<View className="flex-row items-center">
<Pressable onPress={showPrevObservation} className="w-16">
{currentObsIndex !== 0 && <Icon name="keyboard-arrow-left" size={30} />}
</Pressable>
<Text>{`${currentObsIndex + 1} of ${observations.length}`}</Text>
<Pressable onPress={showNextObservation} style={viewStyles.caret}>
<Text className="text-2xl">{`${currentObsIndex + 1} of ${observations.length}`}</Text>
<Pressable onPress={showNextObservation} className="w-16">
{( currentObsIndex !== observations.length - 1 )
&& <Icon name="keyboard-arrow-right" size={30} />}
</Pressable>
@@ -283,15 +284,15 @@ const ObsEdit = ( ): Node => {
</MediaViewerModal>
<ScrollNoFooter style={mediaViewerVisible && viewStyles.mediaViewerSafeAreaView}>
{renderHeader( )}
<Headline style={textStyles.headerText}>{t( "Evidence" )}</Headline>
<Text className="text-2xl ml-4">{t( "Evidence" )}</Text>
<EvidenceSection
handleSelection={handleSelection}
photoUris={photoUris}
handleAddEvidence={addEvidence}
/>
<Headline style={textStyles.headerText}>{t( "Identification" )}</Headline>
<Text className="text-2xl ml-4 mt-4">{t( "Identification" )}</Text>
<IdentificationSection />
<Headline style={textStyles.headerText}>{t( "Other-Data" )}</Headline>
<Text className="text-2xl ml-4">{t( "Other-Data" )}</Text>
<OtherDataSection />
<View style={viewStyles.buttonRow}>
<Button
@@ -319,7 +320,7 @@ const ObsEdit = ( ): Node => {
backdropComponent={renderBackdrop}
>
<View
style={viewStyles.addEvidenceBottomSheet}
className="items-center p-10"
onLayout={( {
nativeEvent: {
layout: { height }
@@ -328,14 +329,14 @@ const ObsEdit = ( ): Node => {
setSnapPoint( height + 50 );
}}
>
<Headline>{t( "Add-evidence" )}</Headline>
<Text className="text-2xl ml-4 mb-4">{t( "Add-evidence" )}</Text>
{disableAddingMoreEvidence
&& (
<Text style={textStyles.evidenceWarning}>
{t( "You-can-only-upload-20-media" )}
</Text>
)}
<View style={viewStyles.evidenceButtonsContainer}>
<View className="flex-row w-full justify-around">
<EvidenceButton
icon="perm-media"
handlePress={onImportPhoto}
@@ -353,7 +354,7 @@ const ObsEdit = ( ): Node => {
/>
</View>
<Text
style={textStyles.evidenceCancel}
className="underline mt-5"
onPress={( () => bottomSheetModalRef.current?.dismiss() )}
>
{t( "Cancel" )}

View File

@@ -1,14 +1,13 @@
// @flow
import { View } from "components/styledComponents";
import { t } from "i18next";
import { ObsEditContext } from "providers/contexts";
import type { Node } from "react";
import React, { useContext } from "react";
import { View } from "react-native";
import { Button } from "react-native-paper";
import RNPickerSelect from "react-native-picker-select";
import colors from "styles/colors";
import { pickerSelectStyles, viewStyles } from "styles/obsEdit/obsEdit";
import Notes from "./Notes";
@@ -53,12 +52,11 @@ const OtherDataSection = ( ): Node => {
return (
<>
<View style={viewStyles.row}>
<View className="flex-row ml-3">
<RNPickerSelect
onValueChange={updateGeoprivacyStatus}
items={geoprivacyOptions}
useNativeAndroidPickerStyle={false}
style={pickerSelectStyles}
value={currentObs.geoprivacy}
>
<Button
@@ -73,12 +71,11 @@ const OtherDataSection = ( ): Node => {
</Button>
</RNPickerSelect>
</View>
<View style={viewStyles.row}>
<View className="flex-row ml-3">
<RNPickerSelect
onValueChange={updateCaptiveStatus}
items={captiveOptions}
useNativeAndroidPickerStyle={false}
style={pickerSelectStyles}
value={currentObs.captive_flag}
>
<Button

View File

@@ -23,7 +23,7 @@ const useRemoteObservations = ( ): Object => {
const fetchNextObservations = useCallback( numOfObs => {
const nextPageToFetch = numOfObs > 0
? Math.ceil( numOfObs / 6 )
? Math.ceil( numOfObs / 5 )
: 1;
setPage( nextPageToFetch );
setFetchFromServer( true );

View File

@@ -1,11 +1,10 @@
// @flow
import { Image, Pressable, View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { Image, Pressable } from "react-native";
import IconMaterial from "react-native-vector-icons/MaterialIcons";
import colors from "styles/colors";
import { imageStyles, viewStyles } from "styles/photoLibrary/photoGallery";
type Props = {
item: Object,
@@ -31,29 +30,34 @@ const GroupPhotoImage = ( {
const filterIconName = item.photos.length > 9 ? "filter-9-plus" : `filter-${item.photos.length}`;
const unselectedIcon = ( ) => (
<IconMaterial
name="radio-button-off"
color={colors.white}
size={35}
style={viewStyles.selectionIcon}
/>
<View className="absolute top-2 right-2">
<IconMaterial
name="radio-button-off"
color={colors.white}
size={35}
/>
</View>
);
const selectedIcon = ( ) => (
<IconMaterial
name="check-circle"
color={colors.inatGreen}
size={35}
style={viewStyles.selectionIcon}
/>
<View className="absolute top-2 right-2">
<IconMaterial
name="check-circle"
color={colors.inatGreen}
size={35}
/>
</View>
);
const numberOfPhotosIcon = ( ) => (
<IconMaterial
// $FlowIgnore
name={filterIconName}
color={colors.white}
size={35}
style={viewStyles.numOfPhotosIcon}
/>
<View className="absolute bottom-2 right-2">
<IconMaterial
// $FlowIgnore
name={filterIconName}
color={colors.white}
size={35}
/>
</View>
);
const renderIcon = isSelected ? selectedIcon : unselectedIcon;
@@ -67,7 +71,7 @@ const GroupPhotoImage = ( {
<Image
testID="GroupPhotos.photo"
source={imageUri}
style={imageStyles.imagesForGrouping}
className="h-44 w-44 mx-1"
/>
{selectionMode && renderIcon( )}
{hasMultiplePhotos && numberOfPhotosIcon( )}

View File

@@ -6,7 +6,6 @@ import { ObsEditContext, RealmContext } from "providers/contexts";
import type { Node } from "react";
import React, { useContext, useState } from "react";
import { ActivityIndicator, FlatList } from "react-native";
import { viewStyles } from "styles/photoLibrary/photoGallery";
import Observation from "../../models/Observation";
import GroupPhotoImage from "./GroupPhotoImage";
@@ -168,7 +167,6 @@ const GroupPhotos = ( ): Node => {
observations={groupedPhotos.length}
/>
<FlatList
contentContainerStyle={viewStyles.centerImages}
data={groupedPhotos}
initialNumToRender={4}
keyExtractor={extractKey}

View File

@@ -3,12 +3,11 @@
import Button from "components/SharedComponents/Buttons/Button";
import SecondaryCTAButton from "components/SharedComponents/Buttons/SecondaryCTAButton";
import KebabMenu from "components/SharedComponents/KebabMenu";
import { Text, View } from "components/styledComponents";
import { t } from "i18next";
import type { Node } from "react";
import React from "react";
import { Text, View } from "react-native";
import { Menu } from "react-native-paper";
import { viewStyles } from "styles/photoLibrary/photoGalleryFooter";
type Props = {
combinePhotos: Function,
@@ -36,50 +35,45 @@ const GroupPhotosFooter = ( {
const obsWithMultiplePhotosSelected = selectedObservations?.[0]?.photos?.length > 1;
const renderSelectionModeFooter = ( ) => (
<>
<View style={viewStyles.selectionButtons}>
<KebabMenu>
<Menu.Item
onPress={combinePhotos}
disabled={noObsSelected || oneObsSelected}
title={t( "Combine-Photos" )}
/>
<Menu.Item
onPress={separatePhotos}
disabled={!obsWithMultiplePhotosSelected}
title={t( "Separate-Photos" )}
/>
<Menu.Item
onPress={removePhotos}
disabled={noObsSelected}
title={t( "Remove-Photos" )}
/>
</KebabMenu>
<SecondaryCTAButton
onPress={( ) => {
setSelectionMode( false );
clearSelection( );
}}
>
<Text>{t( "Cancel" )}</Text>
</SecondaryCTAButton>
</View>
<View style={viewStyles.nextButton} />
</>
<View className="flex-row">
<KebabMenu>
<Menu.Item
onPress={combinePhotos}
disabled={noObsSelected || oneObsSelected}
title={t( "Combine-Photos" )}
/>
<Menu.Item
onPress={separatePhotos}
disabled={!obsWithMultiplePhotosSelected}
title={t( "Separate-Photos" )}
/>
<Menu.Item
onPress={removePhotos}
disabled={noObsSelected}
title={t( "Remove-Photos" )}
/>
</KebabMenu>
<SecondaryCTAButton
onPress={( ) => {
setSelectionMode( false );
clearSelection( );
}}
>
<Text>{t( "Cancel" )}</Text>
</SecondaryCTAButton>
</View>
);
const renderFooter = ( ) => (
<>
<View style={viewStyles.selectionButtons}>
<SecondaryCTAButton
onPress={( ) => setSelectionMode( true )}
>
<Text>{t( "Select" )}</Text>
</SecondaryCTAButton>
</View>
<View style={viewStyles.nextButton}>
<SecondaryCTAButton
onPress={( ) => setSelectionMode( true )}
>
<Text>{t( "Select" )}</Text>
</SecondaryCTAButton>
<View className="w-28">
<Button
level="primary"
level="secondary"
text="Next"
onPress={navToObsEdit}
testID="GroupPhotos.next"
@@ -89,7 +83,7 @@ const GroupPhotosFooter = ( {
);
return (
<View style={viewStyles.footer}>
<View className="h-16 flex-row justify-between mx-2">
{selectionMode ? renderSelectionModeFooter( ) : renderFooter( )}
</View>
);

View File

@@ -2,11 +2,11 @@
import { HeaderBackButton } from "@react-navigation/elements";
import { useNavigation } from "@react-navigation/native";
import { Text, View } from "components/styledComponents";
import { t } from "i18next";
import type { Node } from "react";
import React from "react";
import { Text, View } from "react-native";
import { textStyles, viewStyles } from "styles/photoLibrary/photoGalleryHeader";
import colors from "styles/colors";
type Props = {
photos: number,
@@ -19,16 +19,16 @@ const GroupPhotosHeader = ( { photos, observations }: Props ): Node => {
const navBack = ( ) => navigation.goBack( );
return (
<>
<View style={viewStyles.header}>
<HeaderBackButton onPress={navBack} />
<Text style={textStyles.header}>{t( "Group-Photos" )}</Text>
<View className="h-32">
<View className="flex-row">
<HeaderBackButton onPress={navBack} tintColor={colors.black} />
<Text className="text-xl mt-3">{t( "Group-Photos" )}</Text>
</View>
<Text style={textStyles.header}>
<Text className="text-lg ml-10">
{t( "X-photos-X-observations", { photoCount: photos, observationCount: observations } )}
</Text>
<Text style={textStyles.text}>{t( "Combine-photos-onboarding" )}</Text>
</>
<Text className="mx-3 mt-5">{t( "Combine-photos-onboarding" )}</Text>
</View>
);
};

View File

@@ -3,6 +3,7 @@
import { useNavigation, useRoute } from "@react-navigation/native";
import Button from "components/SharedComponents/Buttons/Button";
import ViewNoFooter from "components/SharedComponents/ViewNoFooter";
import { Text, View } from "components/styledComponents";
import { t } from "i18next";
import { ObsEditContext, RealmContext } from "providers/contexts";
import type { Node } from "react";
@@ -10,10 +11,9 @@ import React, {
useCallback, useContext, useEffect, useState
} from "react";
import {
ActivityIndicator, FlatList, Text, View
ActivityIndicator, FlatList
} from "react-native";
import { Snackbar } from "react-native-paper";
import { viewStyles } from "styles/photoLibrary/photoGallery";
import Observation from "../../models/Observation";
import useCameraRollPhotos from "./hooks/useCameraRollPhotos";
@@ -186,7 +186,7 @@ const PhotoGallery = ( ): Node => {
const navToNextScreen = async ( ) => {
if ( !selectedPhotos ) return;
if ( skipGroupPhotos ) {
addPhotos( selectedPhotos );
addPhotos( selectedPhotos.map( galleryPhoto => galleryPhoto.image.uri ) );
navigation.navigate( "ObsEdit", { lastScreen: "PhotoGallery" } );
return;
}
@@ -226,9 +226,9 @@ const PhotoGallery = ( ): Node => {
ListEmptyComponent={renderEmptyList( )}
/>
{ selectedPhotos.length > 0 && (
<View style={viewStyles.createObsButton}>
<View className="h-16 mt-2 mx-4">
<Button
level="primary"
level="secondary"
text="Import-X-photos"
count={totalSelected || 0}
onPress={navToNextScreen}

View File

@@ -2,11 +2,11 @@
import { HeaderBackButton } from "@react-navigation/elements";
import { useNavigation } from "@react-navigation/native";
import { Text, View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { View } from "react-native";
import RNPickerSelect from "react-native-picker-select";
import { viewStyles } from "styles/photoLibrary/photoGalleryHeader";
import colors from "styles/colors";
import usePhotoAlbums from "./hooks/usePhotoAlbums";
@@ -14,6 +14,13 @@ type Props = {
updateAlbum: Function
}
const FONT_SIZE = 20;
const pickerTextStyle = {
inputIOS: { fontSize: FONT_SIZE },
inputAndroid: { fontSize: FONT_SIZE }
};
const PhotoGalleryHeader = ( { updateAlbum }: Props ): Node => {
const navigation = useNavigation( );
@@ -30,17 +37,21 @@ const PhotoGalleryHeader = ( { updateAlbum }: Props ): Node => {
const navBack = ( ) => navigation.goBack( );
return (
<View style={viewStyles.header}>
<HeaderBackButton onPress={navBack} />
<RNPickerSelect
hideIcon
items={albums}
onValueChange={changeAlbum}
placeholder={placeholder}
useNativeAndroidPickerStyle={false}
disabled={albums.length <= 1}
style={viewStyles}
/>
<View className="flex-row h-16">
<HeaderBackButton onPress={navBack} tintColor={colors.black} />
<View className="mt-5">
<RNPickerSelect
hideIcon
items={albums}
onValueChange={changeAlbum}
placeholder={placeholder}
useNativeAndroidPickerStyle={false}
disabled={albums.length <= 1}
style={pickerTextStyle}
/>
</View>
{/* eslint-disable-next-line i18next/no-literal-string */}
<Text className="text-xl mt-3 ml-2">&#x2304;</Text>
</View>
);
};

View File

@@ -1,11 +1,10 @@
// @flow
import { Image, Pressable, View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { Image, Pressable } from "react-native";
import IconMaterial from "react-native-vector-icons/MaterialIcons";
import colors from "styles/colors";
import { imageStyles } from "styles/photoLibrary/photoGallery";
type Props = {
uri: string,
@@ -25,16 +24,17 @@ const PhotoGalleryImage = ( {
<Image
testID="PhotoGallery.photo"
source={{ uri }}
style={imageStyles.galleryImage}
className="h-24 w-24"
/>
{isSelected && (
<IconMaterial
name="check-circle"
size={30}
style={imageStyles.selectedIcon}
color={colors.inatGreen}
/>
)}
<View className="absolute top-0 right-0">
{isSelected && (
<IconMaterial
name="check-circle"
size={30}
color={colors.inatGreen}
/>
)}
</View>
</Pressable>
);

View File

@@ -2,7 +2,7 @@
import { useRoute } from "@react-navigation/native";
import { fetchProjects } from "api/projects";
import ScrollWithFooter from "components/SharedComponents/ScrollWithFooter";
import ScrollNoFooter from "components/SharedComponents/ScrollNoFooter";
import * as React from "react";
import {
Image, ImageBackground, Text
@@ -10,7 +10,7 @@ import {
import useAuthenticatedQuery from "sharedHooks/useAuthenticatedQuery";
import { imageStyles, textStyles } from "styles/projects/projectDetails";
import ProjectObservations from "./ProjectObservations";
// import ProjectObservations from "./ProjectObservations";
const ProjectDetails = ( ): React.Node => {
const { params } = useRoute( );
@@ -26,7 +26,7 @@ const ProjectDetails = ( ): React.Node => {
if ( !project ) { return null; }
return (
<ScrollWithFooter>
<ScrollNoFooter>
<ImageBackground
source={{ uri: project.header_image_url }}
// $FlowFixMe
@@ -42,8 +42,9 @@ const ProjectDetails = ( ): React.Node => {
<Text style={textStyles.descriptionText}>{project.title}</Text>
<Text style={textStyles.descriptionText}>{project.description}</Text>
{/* TODO: support joining or leaving projects once oauth is set up */}
<ProjectObservations id={id} />
</ScrollWithFooter>
{/* TODO: replace below. FlatList is not supposed to be used inside a scrollview (?) */}
{/* <ProjectObservations id={id} /> */}
</ScrollNoFooter>
);
};

View File

@@ -3,7 +3,6 @@
import TranslatedText from "components/SharedComponents/TranslatedText";
import * as React from "react";
import { Button as ButtonRNP } from "react-native-paper";
import { textStyles, viewStyles } from "styles/sharedComponents/buttons/buttonVariants";
type ButtonProps = {
text: string,
@@ -14,45 +13,50 @@ type ButtonProps = {
testID?: string,
loading?: boolean,
style?: any,
className?: string
}
const setStyles = ( {
level,
disabled
disabled,
className
} ) => {
const buttonContainer = [viewStyles.containerDefault];
const buttonText = [textStyles.textDefault];
let buttonClass = "rounded-3xl h-13";
if ( className ) {
buttonClass += ` ${className}`;
}
if ( level === "warning" ) {
buttonContainer.push( viewStyles.containerWarning );
buttonClass += buttonClass.concat( " ", "bg-buttonWarning" );
} else if ( level === "primary" ) {
buttonContainer.push( viewStyles.containerPrimary );
buttonClass += buttonClass.concat( " ", "bg-buttonPrimary" );
} else {
buttonContainer.push( viewStyles.containerNeutral );
buttonClass += buttonClass.concat( " ", "bg-buttonNeutral" );
}
if ( disabled ) {
if ( level === "warning" ) {
buttonContainer.push( viewStyles.containerWarningDisabled );
buttonClass += buttonClass.concat( " ", "bg-buttonWarningDisabled" );
} else if ( level === "primary" ) {
buttonContainer.push( viewStyles.containerPrimaryDisabled );
buttonClass += buttonClass.concat( " ", "bg-buttonPrimaryDisabled" );
} else {
buttonContainer.push( viewStyles.containerNeutralDisabled );
buttonClass += buttonClass.concat( " ", "bg-buttonNeutralDisabled" );
}
}
return { buttonText, buttonContainer };
return { buttonClass };
};
const Button = ( {
text, onPress, disabled, testID, count, level, loading, style
text, onPress, disabled, testID, count, level, loading, style, className
}: ButtonProps ): React.Node => {
const { buttonText, buttonContainer } = setStyles( { disabled, level } );
const { buttonClass } = setStyles( { disabled, level, className } );
return (
<ButtonRNP
onPress={onPress}
contentStyle={buttonContainer}
className={buttonClass}
style={style}
disabled={disabled}
testID={testID}
@@ -60,7 +64,7 @@ const Button = ( {
>
<TranslatedText
count={count}
style={buttonText}
className="text-lg text-white font-semibold"
text={text}
/>
</ButtonRNP>

View File

@@ -3,15 +3,10 @@
import CameraOptionsModal from "components/Camera/CameraOptionsModal";
import Modal from "components/SharedComponents/Modal";
import * as React from "react";
import { Pressable, View } from "react-native";
import { Pressable } from "react-native";
import IconMaterial from "react-native-vector-icons/MaterialIcons";
import { viewStyles } from "styles/obsEdit/obsEdit";
type Props = {
buttonType?: string
}
const CameraOptionsButton = ( { buttonType }: Props ): React.Node => {
const CameraOptionsButton = ( ): React.Node => {
const [showModal, setModal] = React.useState( false );
const openModal = React.useCallback( ( ) => setModal( true ), [] );
@@ -26,16 +21,9 @@ const CameraOptionsButton = ( { buttonType }: Props ): React.Node => {
closeModal={closeModal}
modal={<CameraOptionsModal closeModal={closeModal} />}
/>
{buttonType === "footer"
? (
<Pressable onPress={navToCameraOptions} accessibilityRole="link">
<IconMaterial name="add-circle" size={35} />
</Pressable>
) : (
<Pressable onPress={navToCameraOptions}>
<View style={viewStyles.evidenceButton} />
</Pressable>
)}
<Pressable onPress={navToCameraOptions} accessibilityRole="link">
<IconMaterial name="add-circle" size={30} />
</Pressable>
</>
);
};

View File

@@ -2,23 +2,28 @@
import { HeaderBackButton } from "@react-navigation/elements";
import { useNavigation } from "@react-navigation/native";
import { Text, View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { Text, View } from "react-native";
import { textStyles, viewStyles } from "styles/sharedComponents/customHeader";
import colors from "styles/colors";
type Props = {
headerText: string
headerText?: string,
hideRightIcon?: boolean,
rightIcon?: any
}
const CustomHeader = ( { headerText }: Props ): Node => {
const CustomHeader = ( {
headerText, hideRightIcon, rightIcon
}: Props ): Node => {
const navigation = useNavigation( );
const navBack = ( ) => navigation.goBack( );
return (
<View style={viewStyles.row}>
<HeaderBackButton onPress={( ) => navigation.goBack( )} style={viewStyles.element} />
<Text style={[viewStyles.element, textStyles.text]}>{headerText}</Text>
<View style={viewStyles.element} />
<View className="flex-row justify-between items-center">
<HeaderBackButton onPress={navBack} tintColor={colors.black} />
{headerText && <Text className="text-xl">{headerText}</Text>}
{!hideRightIcon && rightIcon}
</View>
);
};

View File

@@ -1,8 +1,8 @@
// @flow
import { useNavigation } from "@react-navigation/native";
import { Pressable, View } from "components/styledComponents";
import * as React from "react";
import { Pressable, View } from "react-native";
import IconMaterial from "react-native-vector-icons/MaterialIcons";
import { viewStyles } from "styles/sharedComponents/footer";
@@ -16,14 +16,17 @@ const Footer = ( ): React.Node => {
const navToNotifications = ( ) => navigation.navigate( "MainStack", { screen: "Messages" } );
return (
<View style={[viewStyles.row, viewStyles.shadow]}>
<View
className="flex-row h-24 absolute bottom-0 bg-white w-screen justify-evenly pt-2"
style={viewStyles.shadow}
>
<Pressable onPress={toggleSideMenu} accessibilityRole="link">
<IconMaterial name="menu" size={30} />
</Pressable>
<Pressable onPress={navToExplore} accessibilityRole="link">
<IconMaterial name="language" size={30} />
</Pressable>
<CameraOptionsButton buttonType="footer" />
<CameraOptionsButton />
<Pressable onPress={navToObsList} accessibilityRole="link">
<IconMaterial name="person" size={30} />
</Pressable>

View File

@@ -1,9 +1,9 @@
// @flow
import { TextInput } from "components/styledComponents";
import * as React from "react";
import { Platform, TextInput } from "react-native";
import { Platform } from "react-native";
import colors from "styles/colors";
import textStyles from "styles/sharedComponents/inputField";
type Props = {
handleTextChange: Function,
@@ -48,7 +48,7 @@ const InputField = ( {
placeholderTextColor={colors.black}
secureTextEntry={type === "password"}
selectTextOnFocus={Platform.OS === "android"}
style={textStyles.inputField}
className="border border-border h-8 mx-5 rounded-xl pl-3"
textContentType={type} // iOS only
value={text}
testID={testID}

View File

@@ -4,7 +4,7 @@ import type { Node } from "react";
import React, { useState } from "react";
import { Button, Menu } from "react-native-paper";
import colors from "styles/colors";
import { viewStyles } from "styles/sharedComponents/kebabMenu";
import viewStyles from "styles/sharedComponents/kebabMenu";
type Props = {
children: any

View File

@@ -1,16 +1,13 @@
// @flow
import { Text } from "components/styledComponents";
import { t } from "i18next";
import type { Node } from "react";
import React from "react";
import { Text, View } from "react-native";
import { textStyles, viewStyles } from "styles/sharedComponents/observationViews/obsCard";
const EmptyList = ( ): Node => (
<View style={viewStyles.center}>
<Text style={textStyles.text} testID="ObsList.emptyList">
{t( "iNaturalist-is-a-community-of-naturalists" )}
</Text>
</View>
<Text testID="ObsList.emptyList" className="self-center pt-48">
{t( "iNaturalist-is-a-community-of-naturalists" )}
</Text>
);
export default EmptyList;

View File

@@ -1,14 +1,12 @@
// @flow
import {
Image, Pressable, View
} from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import {
Image, Pressable, Text, View
} from "react-native";
import {
imageStyles,
viewStyles
} from "styles/sharedComponents/observationViews/gridItem";
import FilterIcon from "react-native-vector-icons/MaterialIcons";
import colors from "styles/colors";
import Observation from "../../../models/Observation";
import Photo from "../../../models/Photo";
@@ -16,45 +14,77 @@ import ObsCardDetails from "./ObsCardDetails";
import ObsCardStats from "./ObsCardStats";
type Props = {
// position of this item in a list of items; not ideal, but it allows us to
// style grids appropriately
index?: number,
item: Object,
handlePress: Function,
// Number of columns in the grid; we need this to set the margins correctly
numColumns?: number,
uri?: string
}
const GridItem = ( { item, handlePress, uri }: Props ): Node => {
const GridItem = ( {
handlePress,
index,
item,
numColumns,
uri
}: Props ): Node => {
const onPress = ( ) => handlePress( item );
const photo = item?.observationPhotos?.[0]?.photo;
const totalObsPhotos = item?.observationPhotos?.length;
const hasMultiplePhotos = totalObsPhotos > 1;
const filterIconName = totalObsPhotos > 9
? "filter-9-plus"
: `filter-${totalObsPhotos}`;
// TODO: add fallback image when there is no uri
const imageUri = uri === "project"
? Observation.projectUri( item )
: { uri: Photo.displayLocalOrRemoteMediumPhoto( photo ) };
const totalObsPhotos = item.observationPhotos && item.observationPhotos.length;
return (
<Pressable
onPress={onPress}
style={viewStyles.gridItem}
className={`w-1/2 px-4 py-2 ${( index || 0 ) % ( numColumns || 2 ) === 0 ? "pr-2" : "pl-2"}`}
testID={`ObsList.gridItem.${item.uuid}`}
accessibilityRole="link"
accessibilityLabel="Navigate to observation details screen"
>
{totalObsPhotos > 1 && (
<View style={viewStyles.totalObsPhotos}>
<Text>{totalObsPhotos}</Text>
</View>
)}
<Image
source={imageUri}
style={imageStyles.gridImage}
testID="ObsList.photo"
/>
<ObsCardStats item={item} />
<ObsCardDetails item={item} />
<View>
{
imageUri && imageUri.uri
? (
<Image
source={imageUri}
className="grow aspect-square"
testID="ObsList.photo"
/>
)
: <View className="bg-black/50 grow aspect-square" />
}
{hasMultiplePhotos && (
<View className="z-100 absolute top-2 right-2">
<FilterIcon
// $FlowIgnore
name={filterIconName}
color={colors.white}
size={22}
/>
</View>
)}
<ObsCardStats item={item} view="grid" />
</View>
<ObsCardDetails item={item} view="grid" />
</Pressable>
);
};
GridItem.defaultProps = {
numColumns: 2
};
export default GridItem;

View File

@@ -1,12 +1,12 @@
// @flow
import { View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { ActivityIndicator, View } from "react-native";
import { viewStyles } from "styles/sharedComponents/observationViews/infiniteScroll";
import { ActivityIndicator } from "react-native";
const InfiniteScrollFooter = ( ): Node => (
<View style={viewStyles.infiniteScroll}>
<View className="h-32 border border-border pt-10">
<ActivityIndicator />
</View>
);

View File

@@ -1,12 +1,10 @@
// @flow
import { useNavigation } from "@react-navigation/native";
import { Pressable, Text } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { useTranslation } from "react-i18next";
import { Pressable } from "react-native";
import { Text } from "react-native-paper";
import { textStyles, viewStyles } from "styles/observations/loggedOutCard";
type Props = {
numOfUnuploadedObs: number
@@ -18,13 +16,12 @@ const LoggedOutCard = ( { numOfUnuploadedObs }: Props ): Node => {
return (
<Pressable
style={viewStyles.loggedOutCard}
onPress={( ) => navigation.navigate( "login" )}
accessibilityRole="link"
accessibilityLabel={t( "Navigate-to-login-screen" )}
>
<Text variant="titleLarge" style={textStyles.centerText}>{t( "Log-in-to-iNaturalist" )}</Text>
<Text variant="bodyLarge" style={textStyles.centerText}>
<Text className="self-center color-white text-2xl">{t( "Log-in-to-iNaturalist" )}</Text>
<Text className="self-center color-white text-base">
{t( "X-unuploaded-observations", { observationCount: numOfUnuploadedObs } )}
</Text>
</Pressable>

View File

@@ -6,7 +6,6 @@ import { t } from "i18next";
import type { Node } from "react";
import React from "react";
import { Text } from "react-native";
import viewStyles from "styles/upload/uploadPrompt";
const LoginPrompt = ( ): Node => {
const navigation = useNavigation( );
@@ -17,7 +16,7 @@ const LoginPrompt = ( ): Node => {
<Button
level="neutral"
text={t( "LOG-IN-TO-INATURALIST" )}
style={viewStyles.button}
className="mt-5"
onPress={( ) => navigation.navigate( "login" )}
/>
</>

View File

@@ -1,11 +1,10 @@
// @flow
import { Image, Pressable, View } from "components/styledComponents";
import { RealmContext } from "providers/contexts";
import type { Node } from "react";
import React, { useEffect, useState } from "react";
import { Image, Pressable, View } from "react-native";
import { Avatar } from "react-native-paper";
import { viewStyles } from "styles/sharedComponents/observationViews/obsCard";
import Observation from "../../../models/Observation";
import Photo from "../../../models/Photo";
@@ -37,23 +36,26 @@ const ObsCard = ( { item, handlePress }: Props ): Node => {
return (
<Pressable
onPress={onPress}
style={viewStyles.row}
className="flex-row my-2 mx-3 justify-between"
testID={`ObsList.obsCard.${item.uuid}`}
accessibilityRole="link"
accessibilityLabel="Navigate to observation details screen"
>
<Image
source={{ uri: Photo.displayLocalOrRemoteSquarePhoto( photo ) }}
style={viewStyles.imageBackground}
testID="ObsList.photo"
/>
<View style={viewStyles.obsDetailsColumn}>
{/* TODO: fill in with actual empty states */}
<ObsCardDetails item={item} />
<View className="flex-row shrink">
<Image
source={{ uri: Photo.displayLocalOrRemoteSquarePhoto( photo ) }}
className="w-16 h-16 rounded-md mr-2"
testID="ObsList.photo"
/>
<View className="shrink">
<ObsCardDetails item={item} />
</View>
</View>
<View className="flex-row items-center justify-items-center ml-2">
{needsUpload
? <Avatar.Icon size={40} icon="arrow-up-circle-outline" />
: <ObsCardStats item={item} type="list" />}
</View>
{needsUpload
? <Avatar.Icon size={40} icon="arrow-up-circle-outline" />
: <ObsCardStats item={item} type="list" />}
</Pressable>
);
};

View File

@@ -1,18 +1,18 @@
// @flow
import checkCamelAndSnakeCase from "components/ObsDetails/helpers/checkCamelAndSnakeCase";
import { Text, View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { Text } from "react-native";
import IconMaterial from "react-native-vector-icons/MaterialIcons";
import { formatObsListTime } from "sharedHelpers/dateAndTime";
import { textStyles } from "styles/sharedComponents/observationViews/obsCard";
type Props = {
item: Object
item: Object,
view?: string
}
const ObsCardDetails = ( { item }: Props ): Node => {
const ObsCardDetails = ( { item, view }: Props ): Node => {
const placeGuess = checkCamelAndSnakeCase( item, "placeGuess" );
const displayTime = ( ) => {
@@ -28,17 +28,17 @@ const ObsCardDetails = ( { item }: Props ): Node => {
);
return (
<>
<Text style={textStyles.text} numberOfLines={1}>{displayName( )}</Text>
<Text style={textStyles.text} numberOfLines={1}>
<View className={view === "grid" && "border border-border p-2"}>
<Text numberOfLines={1}>{displayName( )}</Text>
<Text numberOfLines={1}>
<IconMaterial name="location-pin" size={15} />
{placeGuess || "no place guess"}
</Text>
<Text style={textStyles.text} numberOfLines={1}>
<Text numberOfLines={1}>
<IconMaterial name="watch-later" size={15} />
{displayTime( )}
</Text>
</>
</View>
);
};

View File

@@ -1,25 +1,33 @@
// @flow
import checkCamelAndSnakeCase from "components/ObsDetails/helpers/checkCamelAndSnakeCase";
import { Text, View } from "components/styledComponents";
import { t } from "i18next";
import type { Node } from "react";
import React from "react";
import { Text, View } from "react-native";
import Icon from "react-native-vector-icons/MaterialIcons";
import colors from "styles/colors";
import { textStyles, viewStyles } from "styles/sharedComponents/observationViews/obsCard";
type Props = {
item: Object,
type?: string
type?: string,
view?: string
}
const ObsCardStats = ( { item, type }: Props ): Node => {
const ObsCardStats = ( { item, type, view }: Props ): Node => {
const numOfIds = item.identifications?.length || 0;
const numOfComments = item.comments?.length || 0;
const qualityGrade = checkCamelAndSnakeCase( item, "qualityGrade" );
const iconColor = item.viewed === false ? colors.red : colors.black;
const setIconColor = ( ) => {
if ( item.viewed === false ) {
return colors.red;
}
if ( view === "grid" ) {
return colors.white;
}
return colors.black;
};
const qualityGradeText = {
needs_id: t( "NI" ),
@@ -28,23 +36,27 @@ const ObsCardStats = ( { item, type }: Props ): Node => {
};
const renderIdRow = ( ) => (
<View style={viewStyles.iconRow}>
<Icon name="shield" color={iconColor} size={14} style={viewStyles.icon} />
<Text style={[textStyles.text, { color: iconColor }]}>{numOfIds || 0}</Text>
<View className="flex-row items-center mr-3">
<Icon name="shield" color={setIconColor( )} size={14} />
<Text className="mx-1" style={{ color: setIconColor( ) }}>{numOfIds || 0}</Text>
</View>
);
const renderCommentRow = ( ) => (
<View style={viewStyles.iconRow}>
<Icon name="comment" color={iconColor} size={14} style={viewStyles.icon} />
<Text style={[textStyles.text, { color: iconColor }]} testID="ObsList.obsCard.commentCount">
<View className="flex-row items-center">
<Icon name="comment" color={setIconColor( )} size={14} />
<Text
className="mx-1"
style={{ color: setIconColor( ) }}
testID="ObsList.obsCard.commentCount"
>
{numOfComments || 0}
</Text>
</View>
);
const renderQualityGrade = ( ) => (
<Text style={textStyles.text}>
<Text style={{ color: setIconColor( ) }}>
{qualityGrade ? qualityGradeText[qualityGrade] : "?"}
</Text>
);
@@ -58,9 +70,11 @@ const ObsCardStats = ( { item, type }: Props ): Node => {
);
const renderRow = ( ) => (
<View style={viewStyles.photoStatRow}>
{renderIdRow( )}
{renderCommentRow( )}
<View className="flex-row absolute bottom-1 justify-between w-full px-2">
<View className="flex-row">
{renderIdRow( )}
{renderCommentRow( )}
</View>
{renderQualityGrade( )}
</View>
);

View File

@@ -1,9 +1,9 @@
// @flow
import { View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { Animated, View } from "react-native";
import viewStyles from "styles/observations/header";
import { Animated } from "react-native";
import LoggedOutCard from "./LoggedOutCard";
import Toolbar from "./Toolbar";
@@ -14,44 +14,31 @@ type Props = {
isLoggedIn: ?boolean,
translateY: any,
isExplore: boolean,
headerHeight: number,
syncObservations: Function,
setView: Function
}
const ObsListHeader = ( {
numOfUnuploadedObs, isLoggedIn, translateY, isExplore, headerHeight, syncObservations, setView
numOfUnuploadedObs, isLoggedIn, translateY, isExplore, syncObservations, setView
}: Props ): Node => {
if ( isLoggedIn === null ) {
return <View style={viewStyles.header} />;
return <View className="rounded-bl-3xl rounded-br-3xl bg-primary h-24" />;
}
const renderToolbar = ( ) => (
<View style={[
viewStyles.toolbar,
isExplore && viewStyles.exploreButtons,
{ paddingTop: headerHeight }
]}
>
return (
// $FlowIgnore
<Animated.View style={[{ transform: [{ translateY }] }]}>
<View className="rounded-bl-3xl rounded-br-3xl bg-primary h-24 justify-center">
{isLoggedIn
? <UserCard />
: <LoggedOutCard numOfUnuploadedObs={numOfUnuploadedObs} />}
</View>
<Toolbar
isExplore={isExplore}
isLoggedIn={isLoggedIn}
syncObservations={syncObservations}
setView={setView}
numOfUnuploadedObs={numOfUnuploadedObs}
/>
</View>
);
return (
// $FlowIgnore
<Animated.View style={[{ transform: [{ translateY }] }]}>
<View style={viewStyles.header}>
{isLoggedIn
? <UserCard />
: <LoggedOutCard numOfUnuploadedObs={numOfUnuploadedObs} />}
</View>
{renderToolbar( )}
</Animated.View>
);
};

View File

@@ -3,14 +3,14 @@
import { useNavigation, useRoute } from "@react-navigation/native";
import BottomSheet from "components/SharedComponents/BottomSheet";
import Map from "components/SharedComponents/Map";
import { View } from "components/styledComponents";
import type { Node } from "react";
import React, { useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import {
ActivityIndicator, Animated, Dimensions, Text, View
ActivityIndicator,
Animated, Dimensions
} from "react-native";
import useLoggedIn from "sharedHooks/useLoggedIn";
import { textStyles, viewStyles } from "styles/observations/obsList";
import EmptyList from "./EmptyList";
import GridItem from "./GridItem";
@@ -28,7 +28,6 @@ type Props = {
testID: string,
taxonId?: number,
mapHeight?: number,
totalObservations?: number,
handleEndReached?: Function,
syncObservations?: Function
}
@@ -39,7 +38,6 @@ const ObservationViews = ( {
testID,
taxonId,
mapHeight,
totalObservations,
handleEndReached,
syncObservations
}: Props ): Node => {
@@ -114,7 +112,13 @@ const ObservationViews = ( {
const renderItem = ( { item } ) => (
<ObsCard item={item} handlePress={navToObsDetails} />
);
const renderGridItem = ( { item } ) => <GridItem item={item} handlePress={navToObsDetails} />;
const renderGridItem = ( { item, index } ) => (
<GridItem
item={item}
index={index}
handlePress={navToObsDetails}
/>
);
const renderEmptyState = ( ) => {
if ( name !== "Explore" && isLoggedIn === false ) {
@@ -123,8 +127,6 @@ const ObservationViews = ( {
return <ActivityIndicator />;
};
const { t } = useTranslation( );
const renderBottomSheet = ( ) => {
if ( numOfUnuploadedObs === 0 ) { return null; }
@@ -158,7 +160,7 @@ const ObservationViews = ( {
if ( isLoggedIn === false ) { return <View />; }
return loading
? <InfiniteScrollFooter />
: <View style={viewStyles.footer} />;
: <View className="pt-16" />;
};
const isExplore = name === "Explore";
@@ -169,11 +171,12 @@ const ObservationViews = ( {
isLoggedIn={isLoggedIn}
translateY={translateY}
isExplore={isExplore}
headerHeight={headerHeight}
syncObservations={syncObservations}
setView={setView}
/>
), [isExplore, isLoggedIn, translateY, numOfUnuploadedObs, headerHeight, syncObservations] );
), [isExplore, isLoggedIn, translateY, numOfUnuploadedObs, syncObservations] );
const renderItemSeparator = ( ) => <View className="border border-border" />;
const renderView = ( ) => {
if ( view === "map" ) {
@@ -192,6 +195,7 @@ const ObservationViews = ( {
onEndReached={handleEndReached}
ListFooterComponent={renderFooter}
ListHeaderComponent={renderHeader}
ItemSeparatorComponent={view !== "grid" && renderItemSeparator}
stickyHeaderIndices={[0]}
bounces={false}
contentContainerStyle={{ minHeight: flatListHeight }}
@@ -203,13 +207,6 @@ const ObservationViews = ( {
return (
<View testID="ObservationViews.myObservations">
{isExplore && (
<View style={[viewStyles.whiteBanner, view === "map" && viewStyles.greenBanner]}>
<Text style={[textStyles.center, view === "map" && textStyles.whiteText]}>
{t( "X-Observations", { count: totalObservations } )}
</Text>
</View>
)}
{renderView( )}
</View>
);

View File

@@ -1,51 +1,32 @@
// @flow
import { Pressable, View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { Pressable, View } from "react-native";
import IconMaterial from "react-native-vector-icons/MaterialIcons";
import colors from "styles/colors";
import { viewStyles } from "styles/observations/obsList";
type Props = {
isExplore: boolean,
isLoggedIn: ?boolean,
syncObservations: Function,
setView: Function,
numOfUnuploadedObs: number,
setView: Function
}
const Toolbar = ( {
isExplore,
isLoggedIn,
syncObservations,
setView,
numOfUnuploadedObs
setView
}: Props ): Node => (
<>
{!isExplore && (
<View style={viewStyles.toggleButtons}>
{isLoggedIn && (
<Pressable onPress={syncObservations}>
<IconMaterial
name="sync"
size={30}
color={numOfUnuploadedObs > 0 ? colors.inatGreen : colors.gray}
/>
</Pressable>
)}
</View>
<View className="py-5 flex-row justify-between bg-white">
{!isExplore && isLoggedIn ? (
<Pressable onPress={syncObservations} className="mx-3">
<IconMaterial name="sync" size={30} />
</Pressable>
) : (
<View className="mx-3" />
)}
<View style={viewStyles.toggleButtons}>
{isExplore && (
<Pressable
onPress={( ) => setView( "map" )}
accessibilityRole="button"
testID="Explore.toggleMapView"
>
<IconMaterial name="map" size={30} />
</Pressable>
)}
<View className="flex flex-row flex-nowrap mx-3">
<Pressable
onPress={( ) => setView( "list" )}
accessibilityRole="button"
@@ -60,7 +41,7 @@ const Toolbar = ( {
<IconMaterial name="grid-view" size={30} />
</Pressable>
</View>
</>
</View>
);
export default Toolbar;

View File

@@ -5,7 +5,6 @@ import { t } from "i18next";
import type { Node } from "react";
import React from "react";
import { Text } from "react-native";
import viewStyles from "styles/upload/uploadPrompt";
type Props = {
numOfUnuploadedObs: number,
@@ -21,7 +20,7 @@ const UploadPrompt = ( {
<Button
level="neutral"
text={t( "UPLOAD-X-OBSERVATIONS", { count: numOfUnuploadedObs } )}
style={viewStyles.button}
className="mt-5"
onPress={( ) => {
updateUploadStatus( );
uploadObservations( );

View File

@@ -2,16 +2,14 @@
import { useNavigation } from "@react-navigation/native";
import { fetchRemoteUser } from "api/users";
import TranslatedText from "components/SharedComponents/TranslatedText";
import UserIcon from "components/SharedComponents/UserIcon";
import { Pressable, Text, View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import { Pressable, Text, View } from "react-native";
import IconMaterial from "react-native-vector-icons/MaterialIcons";
import useAuthenticatedQuery from "sharedHooks/useAuthenticatedQuery";
import useCurrentUser from "sharedHooks/useCurrentUser";
import colors from "styles/colors";
import { textStyles, viewStyles } from "styles/observations/userCard";
import User from "../../../models/User";
@@ -29,23 +27,23 @@ const UserCard = ( ): Node => {
// TODO: this currently doesn't show up on initial login
// because user id can't be fetched
const navigation = useNavigation( );
if ( !user ) { return <View style={viewStyles.topCard} />; }
if ( !user ) { return <View className="flex-row mx-5 items-center" />; }
const navToUserProfile = ( ) => navigation.navigate( "UserProfile", { userId: user.id } );
return (
<View style={viewStyles.userCard}>
<UserIcon uri={{ uri: remoteUser?.icon_url }} large />
<View style={viewStyles.userDetails}>
<Text style={textStyles.text}>{User.userHandle( user )}</Text>
<TranslatedText
style={textStyles.text}
text="X-Observations"
count={user.observations_count || 0}
/>
<View className="flex-row mx-5 items-center">
{remoteUser && <UserIcon uri={{ uri: remoteUser?.icon_url }} large />}
<View className="ml-2">
<Text className="color-white my-1">{User.userHandle( user )}</Text>
{remoteUser && (
<Text className="color-white my-1">
{`${remoteUser?.observations_count} Observations`}
</Text>
)}
</View>
<Pressable
onPress={navToUserProfile}
style={viewStyles.editProfile}
className="absolute right-0"
>
<IconMaterial name="edit" size={30} color={colors.white} />
</Pressable>

View File

@@ -1,15 +1,14 @@
// @flow
import { Image, Pressable, View } from "components/styledComponents";
import type { Node } from "react";
import React from "react";
import {
ActivityIndicator, FlatList, Image, Pressable, View
ActivityIndicator, FlatList
} from "react-native";
import { Avatar, useTheme } from "react-native-paper";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Icon from "react-native-vector-icons/MaterialIcons";
import colors from "styles/colors";
import { imageStyles, viewStyles } from "styles/sharedComponents/photoCarousel";
type Props = {
emptyComponent?: Function,
@@ -35,15 +34,17 @@ const PhotoCarousel = ( {
showAddButton = false
}: Props ): Node => {
const insets = useSafeAreaInsets( );
const { colors: themeColors } = useTheme( );
const imageClass = "h-16 w-16 justify-center mx-1.5 rounded-lg";
const renderDeleteButton = photoUri => (
<Pressable
onPress={( ) => {
if ( !handleDelete ) { return; }
handleDelete( photoUri );
}}
style={viewStyles.deleteButton}
className="absolute top-10 right-0"
>
<Avatar.Icon
icon="delete-forever"
@@ -56,7 +57,7 @@ const PhotoCarousel = ( {
const renderSkeleton = ( ) => {
if ( savingPhoto ) {
return (
<View style={viewStyles.photoLoading}>
<View className={`${imageClass} bg-midGray`}>
<ActivityIndicator />
</View>
);
@@ -64,19 +65,31 @@ const PhotoCarousel = ( {
return null;
};
const renderPhoto = ( { item, index } ) => {
const renderPhotoOrEvidenceButton = ( { item, index } ) => {
if ( index === photoUris.length ) {
return (
<Pressable
onPress={handleAddEvidence}
className={`${imageClass} border border-midGray items-center justify-center mt-6`}
>
<View style={viewStyles.addEvidenceButton}>
<Icon name="add" size={40} color={colors.logInGray} />
</View>
<Icon name="add" size={40} color={colors.logInGray} />
</Pressable>
);
}
const setClassName = ( ) => {
let className = imageClass;
if ( containerStyle === "camera" ) {
className += " mt-12";
} else {
className += " mt-6";
}
if ( selectedPhotoIndex === index ) {
className += " border border-selectionGreen border-4";
}
return className;
};
return (
<>
<Pressable
@@ -85,15 +98,12 @@ const PhotoCarousel = ( {
setSelectedPhotoIndex( index );
}
}}
className={setClassName( )}
>
<Image
source={{ uri: item }}
style={[
imageStyles.photo,
selectedPhotoIndex === index && viewStyles.greenSelectionBorder,
( containerStyle === "camera" ) && imageStyles.photoStandardCamera
]}
testID="ObsEdit.photo"
className="w-fit h-full"
/>
{( containerStyle === "camera" ) && renderDeleteButton( item )}
</Pressable>
@@ -108,11 +118,7 @@ const PhotoCarousel = ( {
return (
<FlatList
data={data}
contentContainerStyle={( containerStyle === "camera" ) && [
viewStyles.photoContainer, {
top: insets.top
}]}
renderItem={renderPhoto}
renderItem={renderPhotoOrEvidenceButton}
horizontal
ListEmptyComponent={savingPhoto ? renderSkeleton( ) : emptyComponent}
/>

View File

@@ -1,16 +1,17 @@
// @flow
import { Image, Text } from "components/styledComponents";
import * as React from "react";
import { Image, Pressable, Text } from "react-native";
import { FlatList } from "react-native-gesture-handler";
import { imageStyles, textStyles } from "styles/sharedComponents/photoScroll";
type Props = {
photos: Array<Object>
}
const PhotoScroll = ( { photos }: Props ): React.Node => {
const extractKey = item => item?.uuid || `photo-${item?.id}`;
const extractKey = item => (
item?.uuid || `photo-${item?.id || item?.localFilePath}`
);
const renderImage = ( { item: photo } ) => {
// check for local file path for unuploaded photos
@@ -23,11 +24,11 @@ const PhotoScroll = ( { photos }: Props ): React.Node => {
<Image
testID="PhotoScroll.photo"
source={{ uri: photoUrl }}
style={imageStyles.fullWidthImage}
className="object-contain w-screen h-52"
/>
<Pressable accessibilityRole="button">
<Text style={textStyles.license}>{photo.licenseCode || photo.license_code}</Text>
</Pressable>
<Text className="absolute bottom-5 right-5 text-white" accessibilityRole="button">
{photo.licenseCode || photo.license_code}
</Text>
</>
);
};

View File

@@ -1,27 +1,19 @@
// @flow
import { Text, View } from "components/styledComponents";
import { t } from "i18next";
import * as React from "react";
import { useTranslation } from "react-i18next";
import { View } from "react-native";
import { Text } from "react-native-paper";
import { textStyles, viewStyles } from "styles/sharedComponents/qualityBadge";
type Props = {
qualityGrade: ?string,
}
const QualityBadge = ( { qualityGrade }: Props ): React.Node => {
const { t } = useTranslation( );
return (
<View style={viewStyles.badgeContainer}>
<Text
style={textStyles.badgeText}
>
{t( `Quality-Grade-Badge-${qualityGrade ? qualityGrade.replace( "_", "_" ) : ""}` )}
</Text>
</View>
);
};
const QualityBadge = ( { qualityGrade }: Props ): React.Node => (
<View className="bg-primary">
<Text className="text-white m-1">
{t( `Quality-Grade-Badge-${qualityGrade ? qualityGrade.replace( "_", "_" ) : ""}` )}
</Text>
</View>
);
export default QualityBadge;

View File

@@ -1,10 +1,10 @@
// @flow
import { SafeAreaView, ScrollView } from "components/styledComponents";
import * as React from "react";
import {
Keyboard, SafeAreaView, ScrollView, StatusBar
Keyboard, StatusBar
} from "react-native";
import viewStyles from "styles/sharedComponents/viewWithFooter";
type Props = {
children: React.Node,
@@ -12,14 +12,13 @@ type Props = {
style?: Object
}
const ScrollWithFooter = ( { children, testID, style }: Props ): React.Node => {
const ScrollNoFooter = ( { children, testID, style }: Props ): React.Node => {
const dismissKeyboard = ( ) => Keyboard.dismiss( );
return (
<SafeAreaView style={[viewStyles.safeAreaContainer, style]} testID={testID}>
<SafeAreaView className="flex-1 bg-white" style={style} testID={testID}>
<StatusBar barStyle="dark-content" />
<ScrollView
contentContainerStyle={viewStyles.scrollPadding}
keyboardDismissMode="on-drag"
onScroll={dismissKeyboard}
scrollEventThrottle={16}
@@ -30,4 +29,4 @@ const ScrollWithFooter = ( { children, testID, style }: Props ): React.Node => {
);
};
export default ScrollWithFooter;
export default ScrollNoFooter;

View File

@@ -1,35 +0,0 @@
// @flow strict-local
import * as React from "react";
import {
Keyboard, SafeAreaView, ScrollView, StatusBar
} from "react-native";
import viewStyles from "styles/sharedComponents/viewWithFooter";
import Footer from "./Footer";
type Props = {
children: React.Node,
testID?: string
}
const ScrollWithFooter = ( { children, testID }: Props ): React.Node => {
const dismissKeyboard = ( ) => Keyboard.dismiss( );
return (
<SafeAreaView style={viewStyles.safeAreaContainer} testID={testID}>
<StatusBar barStyle="dark-content" />
<ScrollView
contentContainerStyle={viewStyles.scrollPadding}
keyboardDismissMode="on-drag"
onScroll={dismissKeyboard}
scrollEventThrottle={16}
>
{children}
</ScrollView>
<Footer />
</SafeAreaView>
);
};
export default ScrollWithFooter;

View File

@@ -7,14 +7,18 @@ import { Text } from "react-native";
type Props = {
text: string,
style?: Object,
style?: any,
className?: string,
count?: number
}
const TranslatedText = ( { text, style, count }: Props ): Node => {
const TranslatedText = ( {
text, style, className, count
}: Props ): Node => {
const { t } = useTranslation( );
return <Text style={style}>{t( text, { count } )}</Text>;
// $FlowIgnore
return <Text className={className} style={style}>{t( text, { count } )}</Text>;
};
export default TranslatedText;

View File

@@ -1,8 +1,8 @@
// @flow
import { SafeAreaView } from "components/styledComponents";
import * as React from "react";
import { SafeAreaView, StatusBar } from "react-native";
import viewStyles from "styles/sharedComponents/viewWithFooter";
import { StatusBar } from "react-native";
type Props = {
children: React.Node,
@@ -11,7 +11,7 @@ type Props = {
}
const ViewNoFooter = ( { children, testID, style }: Props ): React.Node => (
<SafeAreaView style={[viewStyles.safeAreaContainer, style]} testID={testID}>
<SafeAreaView className="flex-1 bg-white" style={style} testID={testID}>
<StatusBar barStyle="dark-content" />
{children}
</SafeAreaView>

View File

@@ -1,8 +1,8 @@
// @flow strict-local
import { SafeAreaView } from "components/styledComponents";
import * as React from "react";
import { SafeAreaView, StatusBar } from "react-native";
import viewStyles from "styles/sharedComponents/viewWithFooter";
import { StatusBar } from "react-native";
import Footer from "./Footer";
@@ -12,7 +12,7 @@ type Props = {
}
const ViewWithFooter = ( { children, testID }: Props ): React.Node => (
<SafeAreaView style={viewStyles.safeAreaContainer} testID={testID}>
<SafeAreaView className="flex-1 bg-white" testID={testID}>
<StatusBar barStyle="dark-content" />
{children}
<Footer />

View File

@@ -2,18 +2,18 @@
import { useRoute } from "@react-navigation/native";
import fetchTaxon from "api/taxa";
import CustomHeader from "components/SharedComponents/CustomHeader";
import PhotoScroll from "components/SharedComponents/PhotoScroll";
import ViewWithFooter from "components/SharedComponents/ViewWithFooter";
import { Pressable, Text, View } from "components/styledComponents";
import _ from "lodash";
import * as React from "react";
import { useTranslation } from "react-i18next";
import {
ActivityIndicator, Linking, Pressable, ScrollView, Text, useWindowDimensions,
View
ActivityIndicator, Linking, ScrollView, useWindowDimensions
} from "react-native";
import HTML from "react-native-render-html";
import useAuthenticatedQuery from "sharedHooks/useAuthenticatedQuery";
import { textStyles, viewStyles } from "styles/taxonDetails";
const TaxonDetails = ( ): React.Node => {
const { params } = useRoute( );
@@ -70,7 +70,7 @@ const TaxonDetails = ( ): React.Node => {
<Text>{taxon.rank}</Text>
<Text>{taxon.preferred_common_name}</Text>
<Text>{taxon.name}</Text>
<Text style={textStyles.header}>{ t( "ABOUT-taxon-header" ) }</Text>
<Text className="text-lg text-grayText my-3">{ t( "ABOUT-taxon-header" ) }</Text>
{ taxon.wikipedia_summary && (
<HTML
contentWidth={width}
@@ -82,26 +82,24 @@ const TaxonDetails = ( ): React.Node => {
accessibilityRole="link"
testID="TaxonDetails.wikipedia"
>
<Text style={textStyles.header}>{ t( "Read-more-on-Wikipedia" )}</Text>
<Text className="my-3">{ t( "Read-more-on-Wikipedia" )}</Text>
</Pressable>
<Text style={textStyles.header}>{ t( "TAXONOMY-header" ) }</Text>
<Text className="text-lg text-grayText my-3">{ t( "TAXONOMY-header" ) }</Text>
{displayTaxonomyList}
<Text style={textStyles.header}>{ t( "STATUS-header" ) }</Text>
<Text style={textStyles.header}>{ t( "SIMILAR-SPECIES-header" ) }</Text>
<Text className="text-lg text-grayText my-3">{ t( "STATUS-header" ) }</Text>
<Text className="pb-32 text-lg text-grayText my-3">{ t( "SIMILAR-SPECIES-header" ) }</Text>
</>
);
};
return (
<ViewWithFooter>
<ScrollView
contentContainerStyle={viewStyles.scrollView}
testID={`TaxonDetails.${taxon?.id}`}
>
<View style={viewStyles.photoContainer}>
<CustomHeader hideRightIcon />
<ScrollView testID={`TaxonDetails.${taxon?.id}`}>
<View className="bg-black">
{taxon && <PhotoScroll photos={_.compact( taxon.taxonPhotos?.map( tp => tp.photo ) )} />}
</View>
<View style={viewStyles.textContainer}>
<View className="m-5">
{renderContent( )}
</View>
</ScrollView>

View File

@@ -6,18 +6,22 @@ import Button from "components/SharedComponents/Buttons/Button";
import CustomHeader from "components/SharedComponents/CustomHeader";
import UserIcon from "components/SharedComponents/UserIcon";
import ViewWithFooter from "components/SharedComponents/ViewWithFooter";
import { Text, View } from "components/styledComponents";
import { t } from "i18next";
import * as React from "react";
import { Text, useWindowDimensions, View } from "react-native";
import { useWindowDimensions } from "react-native";
import { Button as RNPaperButton } from "react-native-paper";
import HTML from "react-native-render-html";
import useAuthenticatedQuery from "sharedHooks/useAuthenticatedQuery";
import { textStyles, viewStyles } from "styles/userProfile/userProfile";
import useCurrentUser from "sharedHooks/useCurrentUser";
import colors from "styles/colors";
import User from "../../models/User";
import updateRelationship from "./helpers/updateRelationship";
import UserProjects from "./UserProjects";
const UserProfile = ( ): React.Node => {
const currentUser = useCurrentUser( );
const { params } = useRoute( );
const { userId } = params;
const { width } = useWindowDimensions( );
@@ -32,9 +36,9 @@ const UserProfile = ( ): React.Node => {
const user = remoteUser ? remoteUser[0] : null;
const showCount = ( count, label ) => (
<View style={viewStyles.countBox}>
<Text style={textStyles.text}>{count}</Text>
<Text style={textStyles.text}>{label}</Text>
<View className="w-1/4 border border-border">
<Text className="self-center">{count}</Text>
<Text className="self-center">{label}</Text>
</View>
);
@@ -46,8 +50,11 @@ const UserProfile = ( ): React.Node => {
return (
<ViewWithFooter>
<CustomHeader headerText={User.userHandle( user )} />
<View style={viewStyles.row} testID={`UserProfile.${userId}`}>
<CustomHeader
headerText={User.userHandle( user )}
rightIcon={<RNPaperButton icon="pencil" textColor={colors.gray} />}
/>
<View className="flex-row m-3" testID={`UserProfile.${userId}`}>
<UserIcon uri={User.uri( user )} large />
<View>
<Text>{user.name}</Text>
@@ -57,40 +64,43 @@ const UserProfile = ( ): React.Node => {
<Text>{`${t( "Affiliation-colon" )} ${user.site_id}`}</Text>
</View>
</View>
{/* TODO: hide follow and messages for current user */}
<View style={viewStyles.buttonRow}>
<View style={viewStyles.button}>
<Button
level="primary"
text="Follow"
onPress={followUser}
testID="UserProfile.followButton"
/>
</View>
<View style={viewStyles.button}>
<Button
level="primary"
text="Messages"
onPress={( ) => console.log( "open messages" )}
testID="UserProfile.messagesButton"
/>
</View>
</View>
<View style={viewStyles.countRow}>
<View className="flex-row">
{showCount( user.observations_count, t( "Observations" ) )}
{showCount( user.species_count, t( "Species" ) )}
{showCount( user.identifications_count, t( "IDs" ) )}
{showCount( user.journal_posts_count, t( "Journal-Posts" ) )}
</View>
<Text>{t( "BIO" )}</Text>
{ user?.description?.length > 0 && (
<View className="mx-3 mt-5">
<Text>{t( "BIO" )}</Text>
{ user && user.description && user.description.length > 0 && (
<HTML
contentWidth={width}
source={{ html: user.description }}
/>
) }
<Text>{t( "PROJECTS" )}</Text>
<UserProjects userId={userId} />
) }
<Text className="mt-5">{t( "PROJECTS" )}</Text>
<UserProjects userId={userId} />
</View>
{!currentUser && (
<View className="flex-row">
<View className="w-1/2">
<Button
level="primary"
text="Follow"
onPress={followUser}
testID="UserProfile.followButton"
/>
</View>
<View className="w-1/2">
<Button
level="primary"
text="Messages"
onPress={( ) => console.log( "open messages" )}
testID="UserProfile.messagesButton"
/>
</View>
</View>
)}
</ViewWithFooter>
);
};

View File

@@ -0,0 +1,41 @@
// @flow
import { styled } from "nativewind";
import {
Image as StyledImage,
KeyboardAvoidingView as StyledKeyboardAvoidingView,
Pressable as StyledPressable,
SafeAreaView as StyledSafeAreaView,
ScrollView as StyledScrollView,
Text as StyledText,
TextInput as StyledTextInput,
View as StyledView
} from "react-native";
// $FlowIgnore
const View = styled( StyledView );
// $FlowIgnore
const KeyboardAvoidingView = styled( StyledKeyboardAvoidingView );
// $FlowIgnore
const SafeAreaView = styled( StyledSafeAreaView );
// $FlowIgnore
const ScrollView = styled( StyledScrollView );
// $FlowIgnore
const Text = styled( StyledText );
// $FlowIgnore
const TextInput = styled( StyledTextInput );
// $FlowIgnore
const Pressable = styled( StyledPressable );
// $FlowIgnore
const Image = styled( StyledImage );
export {
Image,
KeyboardAvoidingView,
Pressable,
SafeAreaView,
ScrollView,
Text,
TextInput,
View
};

View File

@@ -7,8 +7,14 @@ Accept-community-identifications = Accept community identifications
Account = Account
Add-an-ID = Add an ID
Add-an-Identification = Add an Identification
Add-Date-Time = Add Date/Time
Add-evidence-of-an-organism = Add evidence of an organism. This helps others identify what you saw.
Add-Location = Add Location
Add-optional-notes = Add optional notes
@@ -419,8 +425,6 @@ Read-more-on-Wikipedia = Read more on Wikipedia
Recently-observed = Recently observed
Record-a-sound = Record a sound
Record-new-sound = Record new sound
Recording-Sound = Recording Sound
@@ -463,6 +467,8 @@ Search-for-a-project = Search for a project
Search-for-a-taxon = Search for a taxon
Search-for-a-taxon-to-add-an-identification = Search for a taxon to add an identification.
Search-for-a-user = Search for a user
Search-for-description-tags-text = Search for description/tags text
@@ -491,13 +497,6 @@ Status = Status
# Header for a block of text describing a taxon's conservation status
STATUS-header = STATUS
# Header in pop up explaining options for creating an observation
STEP-1-EVIDENCE = STEP 1. EVIDENCE
Submit-without-evidence = Submit without evidence
Take-a-photo-with-your-camera = Take a photo with your camera
Tap-to-search-for-taxa = Tap to search for taxa
Taxon = Taxon
@@ -507,9 +506,6 @@ TAXONOMY-header = TAXONOMY
Taxonomy-Settings = Taxonomy Settings
# Onboarding for users adding their first evidence of an organism
The-first-thing-you-need-is-evidence = The first thing you need is evidence of an organism. This helps others identify what you saw.
The-iNaturalist-Network-is-a-collection-of-localized-websites = The iNaturalist Network is a collection of localized websites that are fully connected to the global iNaturalist community. Network sites are supported by local institutions that have signed an agreement with iNaturalist to promote local use and benefit local biodiversity. They have access to true coordinates from their countries that are automatically obscured from public view in order to protect threatened species. Your username and password works on all sites that are part of the iNaturalist Network. If you choose to affiliate with a Network site, the local institutions that operate each site will also have access to your email address (only to communicate with you about site activities) and access to the true coordinates for observations that are publicly obscured or private. Note: Please do not experimentally change your affiliation if you have more than 1000 observations.
This-is-how-all-taxon-names-will-be-displayed-to-you-across-iNaturalist = This is how all taxon names will be displayed to you across iNaturalist
@@ -535,8 +531,6 @@ Unmute = Unmute
Unreviewed-only = Unreviewed only
Upload-a-photo-from-your-gallery = Upload a photo from your gallery
UPLOAD-OBSERVATION = UPLOAD OBSERVATION
# Shows the number of observations a user can upload to iNat from my observations page
@@ -598,6 +592,12 @@ Yes-delete-observation = Yes, delete observation
Yes-delete-photo = Yes, delete photo
You-can = You can:
Take-a-photo-with-your-camera = Take a photo with your camera
Upload-a-photo-from-your-gallery = Upload a photo from your gallery
Record-a-sound = Record a sound
# Message shown when a permission is required to use a part of the app
# (e.g. permission to access the camera) but the user denied the permission.
You-denied-iNaturalist-permission-to-do-that = You denied iNaturalist permission to do that

View File

@@ -5,7 +5,10 @@
},
"Accept-community-identifications": "Accept community identifications",
"Account": "Account",
"Add-an-ID": "Add an ID",
"Add-an-Identification": "Add an Identification",
"Add-Date-Time": "Add Date/Time",
"Add-evidence-of-an-organism": "Add evidence of an organism. This helps others identify what you saw.",
"Add-Location": "Add Location",
"Add-optional-notes": "Add optional notes",
"Add-to-projects": "Add to projects",
@@ -281,7 +284,6 @@
"Ranks-infrahybrid": "infrahybrid",
"Read-more-on-Wikipedia": "Read more on Wikipedia",
"Recently-observed": "Recently observed",
"Record-a-sound": "Record a sound",
"Record-new-sound": "Record new sound",
"Recording-Sound": "Recording Sound",
"Relationships": "Relationships",
@@ -313,6 +315,7 @@
"Search-for-a-location": "Search for a location",
"Search-for-a-project": "Search for a project",
"Search-for-a-taxon": "Search for a taxon",
"Search-for-a-taxon-to-add-an-identification": "Search for a taxon to add an identification.",
"Search-for-a-user": "Search for a user",
"Search-for-description-tags-text": "Search for description/tags text",
"Select": "Select",
@@ -332,12 +335,6 @@
"comment": "Header for a block of text describing a taxon's conservation status",
"val": "STATUS"
},
"STEP-1-EVIDENCE": {
"comment": "Header in pop up explaining options for creating an observation",
"val": "STEP 1. EVIDENCE"
},
"Submit-without-evidence": "Submit without evidence",
"Take-a-photo-with-your-camera": "Take a photo with your camera",
"Tap-to-search-for-taxa": "Tap to search for taxa",
"Taxon": "Taxon",
"TAXONOMY-header": {
@@ -345,10 +342,6 @@
"val": "TAXONOMY"
},
"Taxonomy-Settings": "Taxonomy Settings",
"The-first-thing-you-need-is-evidence": {
"comment": "Onboarding for users adding their first evidence of an organism",
"val": "The first thing you need is evidence of an organism. This helps others identify what you saw."
},
"The-iNaturalist-Network-is-a-collection-of-localized-websites": "The iNaturalist Network is a collection of localized websites that are fully connected to the global iNaturalist community. Network sites are supported by local institutions that have signed an agreement with iNaturalist to promote local use and benefit local biodiversity. They have access to true coordinates from their countries that are automatically obscured from public view in order to protect threatened species. Your username and password works on all sites that are part of the iNaturalist Network. If you choose to affiliate with a Network site, the local institutions that operate each site will also have access to your email address (only to communicate with you about site activities) and access to the true coordinates for observations that are publicly obscured or private. Note: Please do not experimentally change your affiliation if you have more than 1000 observations.",
"This-is-how-all-taxon-names-will-be-displayed-to-you-across-iNaturalist": "This is how all taxon names will be displayed to you across iNaturalist",
"This-observation-was-created-using": {
@@ -364,7 +357,6 @@
"Unknown-organism": "Unknown organism",
"Unmute": "Unmute",
"Unreviewed-only": "Unreviewed only",
"Upload-a-photo-from-your-gallery": "Upload a photo from your gallery",
"UPLOAD-OBSERVATION": "UPLOAD OBSERVATION",
"UPLOAD-X-OBSERVATIONS": {
"comment": "Shows the number of observations a user can upload to iNat from my observations page",
@@ -400,6 +392,10 @@
"Yes": "Yes",
"Yes-delete-observation": "Yes, delete observation",
"Yes-delete-photo": "Yes, delete photo",
"You-can": "You can:",
"Take-a-photo-with-your-camera": "Take a photo with your camera",
"Upload-a-photo-from-your-gallery": "Upload a photo from your gallery",
"Record-a-sound": "Record a sound",
"You-denied-iNaturalist-permission-to-do-that": {
"comment": "Message shown when a permission is required to use a part of the app\n(e.g. permission to access the camera) but the user denied the permission.",
"val": "You denied iNaturalist permission to do that"

View File

@@ -7,8 +7,14 @@ Accept-community-identifications = Accept community identifications
Account = Account
Add-an-ID = Add an ID
Add-an-Identification = Add an Identification
Add-Date-Time = Add Date/Time
Add-evidence-of-an-organism = Add evidence of an organism. This helps others identify what you saw.
Add-Location = Add Location
Add-optional-notes = Add optional notes
@@ -419,8 +425,6 @@ Read-more-on-Wikipedia = Read more on Wikipedia
Recently-observed = Recently observed
Record-a-sound = Record a sound
Record-new-sound = Record new sound
Recording-Sound = Recording Sound
@@ -463,6 +467,8 @@ Search-for-a-project = Search for a project
Search-for-a-taxon = Search for a taxon
Search-for-a-taxon-to-add-an-identification = Search for a taxon to add an identification.
Search-for-a-user = Search for a user
Search-for-description-tags-text = Search for description/tags text
@@ -491,13 +497,6 @@ Status = Status
# Header for a block of text describing a taxon's conservation status
STATUS-header = STATUS
# Header in pop up explaining options for creating an observation
STEP-1-EVIDENCE = STEP 1. EVIDENCE
Submit-without-evidence = Submit without evidence
Take-a-photo-with-your-camera = Take a photo with your camera
Tap-to-search-for-taxa = Tap to search for taxa
Taxon = Taxon
@@ -507,9 +506,6 @@ TAXONOMY-header = TAXONOMY
Taxonomy-Settings = Taxonomy Settings
# Onboarding for users adding their first evidence of an organism
The-first-thing-you-need-is-evidence = The first thing you need is evidence of an organism. This helps others identify what you saw.
The-iNaturalist-Network-is-a-collection-of-localized-websites = The iNaturalist Network is a collection of localized websites that are fully connected to the global iNaturalist community. Network sites are supported by local institutions that have signed an agreement with iNaturalist to promote local use and benefit local biodiversity. They have access to true coordinates from their countries that are automatically obscured from public view in order to protect threatened species. Your username and password works on all sites that are part of the iNaturalist Network. If you choose to affiliate with a Network site, the local institutions that operate each site will also have access to your email address (only to communicate with you about site activities) and access to the true coordinates for observations that are publicly obscured or private. Note: Please do not experimentally change your affiliation if you have more than 1000 observations.
This-is-how-all-taxon-names-will-be-displayed-to-you-across-iNaturalist = This is how all taxon names will be displayed to you across iNaturalist
@@ -535,8 +531,6 @@ Unmute = Unmute
Unreviewed-only = Unreviewed only
Upload-a-photo-from-your-gallery = Upload a photo from your gallery
UPLOAD-OBSERVATION = UPLOAD OBSERVATION
# Shows the number of observations a user can upload to iNat from my observations page
@@ -598,6 +592,12 @@ Yes-delete-observation = Yes, delete observation
Yes-delete-photo = Yes, delete photo
You-can = You can:
Take-a-photo-with-your-camera = Take a photo with your camera
Upload-a-photo-from-your-gallery = Upload a photo from your gallery
Record-a-sound = Record a sound
# Message shown when a permission is required to use a part of the app
# (e.g. permission to access the camera) but the user denied the permission.
You-denied-iNaturalist-permission-to-do-that = You denied iNaturalist permission to do that

View File

@@ -25,7 +25,6 @@ import { GestureHandlerRootView } from "react-native-gesture-handler";
import { MD3LightTheme as DefaultTheme, Provider as PaperProvider } from "react-native-paper";
import { SafeAreaProvider } from "react-native-safe-area-context";
import colors from "styles/colors";
import { viewStyles } from "styles/navigation/rootNavigation";
import IdentifyStackNavigation from "./identifyStackNavigation";
import MainStackNavigation from "./mainStackNavigation";
@@ -106,7 +105,7 @@ const App = ( ): React.Node => {
<RealmProvider>
<SafeAreaProvider>
<PaperProvider theme={theme}>
<GestureHandlerRootView style={viewStyles.container}>
<GestureHandlerRootView className="flex-1">
<NavigationContainer>
<ObsEditProvider>
<Drawer.Navigator

View File

@@ -37,12 +37,27 @@ const ObsEditProvider = ( { children }: Props ): Node => {
};
const obsEditValue = useMemo( ( ) => {
const addPhotos = async photos => {
const obsPhotos = await Promise.all( photos.map(
const addPhotos = async photoUris => {
const obsPhotos = await Promise.all( photoUris.map(
async photo => ObservationPhoto.new( photo, realm )
) );
const newObs = await Observation.createObsWithPhotos( obsPhotos );
setObservations( [newObs] );
let targetObservation = currentObs;
if ( targetObservation ) {
targetObservation = {
...(
targetObservation.toJSON
? targetObservation.toJSON( )
: targetObservation
),
observationPhotos: [
...Array.from( targetObservation.observationPhotos ),
...obsPhotos
]
};
} else {
targetObservation = await Observation.createObsWithPhotos( obsPhotos );
}
setObservations( [targetObservation] );
};
const updateObservationKey = ( key, value ) => {

View File

@@ -1,23 +0,0 @@
// @flow strict-local
import { Dimensions, StyleSheet } from "react-native";
import type { ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const { width, height } = Dimensions.get( "screen" );
const heightPhotoContainerCamera = 134;
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
fadingContainer: {
backgroundColor: colors.black,
position: "absolute",
width,
height
},
bottomOfPhotoPreview: {
height: heightPhotoContainerCamera
}
} );
export default viewStyles;

View File

@@ -1,34 +0,0 @@
// @flow strict-local
import { Dimensions, StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const { width } = Dimensions.get( "screen" );
const heightPhotoContainerCamera = 134;
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
photoPreviewContainer: {
backgroundColor: colors.black,
position: "absolute",
top: 0,
height: heightPhotoContainerCamera,
width
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
topPhotoText: {
bottom: 16,
marginLeft: 28,
color: colors.white,
position: "absolute",
fontSize: 18
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,76 +0,0 @@
// @flow strict-local
import { Dimensions, StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const { width } = Dimensions.get( "screen" );
const buttonRow = {
position: "absolute",
bottom: 75
};
const cameraCaptureRowHeight = 159;
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
container: {
flex: 1,
backgroundColor: colors.black
},
bottomButtons: {
bottom: 0,
position: "absolute"
},
captureButton: {
position: "absolute",
bottom: 54,
alignSelf: "center"
},
nextButton: {
position: "absolute",
bottom: 71,
right: 47,
alignSelf: "flex-end"
},
flashButton: {
...buttonRow,
alignSelf: "flex-start",
left: 50
},
cameraFlipButton: {
...buttonRow,
alignSelf: "flex-end",
right: 50
},
tapToFocusSquare: {
width: 80,
height: 80,
borderRadius: 10,
borderWidth: 2,
zIndex: 100,
position: "absolute",
borderColor: colors.white
},
cameraSettingsRow: {
bottom: -52
},
cameraCaptureRow: {
backgroundColor: colors.black,
width,
height: cameraCaptureRowHeight
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
whiteText: {
color: colors.white,
zIndex: 1,
fontSize: 24
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,32 +1,9 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type {
ImageStyleProp,
TextStyleProp, ViewStyleProp
} from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
import type { ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
container: {
flex: 1
},
paddedContainer: {
flex: 1,
padding: "10%",
justifyContent: "center"
},
greenButton: {
backgroundColor: colors.inatGreen,
borderRadius: 40,
alignSelf: "center"
},
grayButton: {
backgroundColor: colors.gray,
borderRadius: 40,
alignSelf: "center",
marginRight: 20
},
input: {
height: 50
},
@@ -38,53 +15,4 @@ const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
header: {
fontSize: 27,
alignSelf: "center",
marginTop: 10
},
subtitle: {
fontSize: 20,
alignSelf: "center",
marginTop: 20,
marginBottom: 20,
textAlign: "center"
},
fieldText: {
fontSize: 17,
marginBottom: 5,
marginTop: 10
},
error: {
color: colors.red,
marginTop: 20,
textAlign: "center"
},
forgotPassword: {
alignSelf: "flex-end",
marginTop: 10,
textDecorationLine: "underline"
}
} );
const imageStyles: { [string]: ImageStyleProp } = StyleSheet.create( {
logo: {
width: 150,
height: 150,
alignSelf: "center"
}
} );
const closeButton: { [string]: ViewStyleProp } = StyleSheet.create( {
close: {
position: "absolute",
top: 20,
end: 20
}
} );
export {
closeButton, imageStyles, textStyles, viewStyles
};
export default viewStyles;

View File

@@ -1,57 +0,0 @@
// @flow strict-local
import { Dimensions, StyleSheet } from "react-native";
import type {
ImageStyleProp, TextStyleProp, ViewStyleProp
} from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const { width, height } = Dimensions.get( "screen" );
const imageStyles: { [string]: ImageStyleProp } = StyleSheet.create( {
selectedPhoto: {
width,
height: height - 350
},
fullSize: {
width: "100%",
height: "100%"
}
} );
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
fullSize: {
width: "100%",
height: "100%"
},
container: {
backgroundColor: colors.black,
margin: 0
},
alignRight: {
alignItems: "flex-end"
},
confirmButton: {
backgroundColor: colors.inatGreen,
borderRadius: 40,
alignSelf: "center"
},
cancelButton: {
backgroundColor: colors.gray,
borderRadius: 40,
alignSelf: "center",
marginRight: 10
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
whiteText: {
color: colors.white
}
} );
export {
imageStyles,
textStyles,
viewStyles
};

View File

@@ -1,19 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
container: {
flex: 1
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,33 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
footerRow: {
flexDirection: "row",
justifyContent: "space-around",
height: 80,
backgroundColor: colors.white
},
shadow: {
shadowColor: colors.black,
shadowOffset: {
width: 0,
height: -3
},
shadowOpacity: 0.20,
shadowRadius: 2
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,17 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
notes: {
backgroundColor: colors.white,
paddingLeft: 5
}
} );
export {
// eslint-disable-next-line import/prefer-default-export
textStyles
};

View File

@@ -2,7 +2,6 @@
import { Dimensions, StyleSheet } from "react-native";
import type {
ImageStyleProp,
TextStyleProp,
ViewStyleProp
} from "react-native/Libraries/StyleSheet/StyleSheet";
@@ -10,38 +9,10 @@ import colors from "styles/colors";
const { width } = Dimensions.get( "screen" );
const pickerContainer = {
paddingHorizontal: 10
};
const pickerText = {
};
const pickerSelectStyles: { [string]: TextStyleProp } = StyleSheet.create( {
inputIOS: pickerText,
inputAndroid: pickerText,
inputIOSContainer: pickerContainer,
inputAndroidContainer: pickerContainer
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
headerText: {
marginHorizontal: 20
},
text: {
marginHorizontal: 20
},
smallLabel: {
fontSize: 11
},
verticalCenter: {
lineHeight: 0
},
evidenceCancel: {
marginTop: 20,
textDecorationLine: "underline"
},
evidenceWarning: {
marginBottom: 10,
marginTop: 10,
@@ -49,39 +20,9 @@ const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
}
} );
const imageWidth = 66;
const smallImageWidth = imageWidth - 40;
const imageStyles: { [string]: ImageStyleProp } = StyleSheet.create( {
obsPhoto: {
width: imageWidth,
height: imageWidth,
borderRadius: 8,
marginHorizontal: 6,
marginVertical: 27
},
smallPhoto: {
width: smallImageWidth,
height: smallImageWidth,
marginVertical: 40,
borderRadius: 10,
marginHorizontal: 5
}
} );
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
addEvidenceBottomSheet: {
display: "flex",
flexDirection: "column",
alignItems: "center",
padding: 20
},
evidenceButtonsContainer: {
display: "flex",
flexDirection: "row",
justifyContent: "space-evenly",
width: 300,
marginTop: 20
editIcon: {
backgroundColor: colors.white
},
headerRow: {
height: 30,
@@ -90,9 +31,6 @@ const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
alignItems: "center",
marginBottom: 20
},
caret: {
width: 35
},
bottomModal: {
backgroundColor: colors.white,
borderTopRightRadius: 30,
@@ -106,27 +44,15 @@ const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
bottom: 0,
width: "100%"
},
noMargin: {
margin: 0
},
greenSelectionBorder: {
borderWidth: 3,
borderColor: colors.selectionGreen
},
iconicTaxaButtons: {
marginHorizontal: 20,
marginVertical: 20
},
row: {
flexDirection: "row",
flexWrap: "nowrap",
marginVertical: 10
},
multipleObsRow: {
flexDirection: "row",
flexWrap: "nowrap",
alignItems: "center"
},
buttonRow: {
paddingTop: 10,
flexDirection: "row",
@@ -135,22 +61,6 @@ const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
evidenceList: {
marginBottom: 20
},
evidenceButton: {
backgroundColor: colors.inatGreen,
width: imageWidth,
height: imageWidth,
borderRadius: 20,
marginHorizontal: 20,
marginVertical: 20
},
soundButton: {
backgroundColor: colors.gray,
width: imageWidth,
height: imageWidth,
borderRadius: 20,
marginHorizontal: 20,
marginVertical: 20
},
selected: {
backgroundColor: colors.inatGreen
},
@@ -171,8 +81,6 @@ const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
} );
export {
imageStyles,
pickerSelectStyles,
textStyles,
viewStyles
};

View File

@@ -1,35 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const borderRadius = 30;
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
header: {
backgroundColor: colors.inatGreen,
borderBottomLeftRadius: borderRadius,
borderBottomRightRadius: borderRadius,
position: "absolute",
left: 0,
right: 0,
zIndex: 1
},
toolbar: {
paddingVertical: 10,
flexDirection: "row",
flexWrap: "nowrap",
justifyContent: "space-between",
backgroundColor: colors.white
},
exploreButtons: {
borderRadius: 40,
borderWidth: 1,
top: 400,
zIndex: 1,
backgroundColor: colors.white
}
} );
export default viewStyles;

View File

@@ -1,29 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const borderRadius = 30;
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
loggedOutCard: {
height: 101,
justifyContent: "center",
backgroundColor: colors.inatGreen,
borderBottomLeftRadius: borderRadius,
borderBottomRightRadius: borderRadius
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
centerText: {
textAlign: "center",
color: colors.white
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,62 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
whiteBackground: {
backgroundColor: colors.white
},
screenContainer: {
alignItems: "center",
alignSelf: "stretch",
flex: 1,
justifyContent: "center"
},
stretch: {
alignSelf: "stretch"
},
stretchContainer: {
alignSelf: "stretch",
flex: 1
},
greenBanner: {
paddingVertical: 20,
backgroundColor: colors.inatGreen
},
whiteBanner: {
paddingVertical: 20
},
footer: {
paddingTop: 100
},
toggleButtons: {
flexDirection: "row",
flexWrap: "nowrap",
marginHorizontal: 15
},
grayButton: {
borderRadius: 40,
marginTop: 17,
minHeight: 48,
justifyContent: "center"
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
center: {
alignSelf: "center"
},
whiteText: {
color: colors.white
},
grayButtonText: {
fontSize: 18
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,32 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
userCard: {
flexDirection: "row",
marginHorizontal: 20,
alignItems: "center"
},
userDetails: {
marginLeft: 10
},
editProfile: {
position: "absolute",
right: 0
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
text: {
color: colors.white,
marginVertical: 3
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,58 +0,0 @@
// @flow strict-local
import { Dimensions, StyleSheet } from "react-native";
import type {
ImageStyleProp,
ViewStyleProp
} from "react-native/Libraries/StyleSheet/StyleSheet";
const { width } = Dimensions.get( "screen" );
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
createObsButton: {
height: 75,
justifyContent: "center"
},
centerImages: {
paddingHorizontal: 20
},
selectionIcon: {
zIndex: 1,
position: "absolute",
top: 20,
right: 10
},
numOfPhotosIcon: {
zIndex: 1,
position: "absolute",
bottom: 20,
right: 10
}
} );
const galleryImageWidth = width / 4 - 2;
const groupImageWidth = width / 2 - 40;
const imageStyles: { [string]: ImageStyleProp } = StyleSheet.create( {
galleryImage: {
height: galleryImageWidth,
width: galleryImageWidth
},
imagesForGrouping: {
height: groupImageWidth,
width: groupImageWidth,
marginHorizontal: 10,
marginVertical: 10
},
selectedIcon: {
zIndex: 1,
right: 0,
position: "absolute",
top: 0
}
} );
export {
imageStyles,
viewStyles
};

View File

@@ -1,44 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
footer: {
height: 70,
flexDirection: "row",
justifyContent: "space-between"
},
selectionModal: {
padding: 20,
backgroundColor: colors.white,
position: "absolute",
bottom: 100,
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.23,
shadowRadius: 2.62,
elevation: 4
},
nextButton: {
width: 100
},
selectionButtons: {
flexDirection: "row"
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
selections: {
marginVertical: 10
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,52 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type {
TextStyleProp,
ViewStyleProp
} from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const pickerContainer = {
alignItems: "center",
flexDirection: "row",
flexWrap: "nowrap",
paddingHorizontal: 30
};
const pickerText = {
fontSize: 20,
marginTop: 20
};
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
header: {
height: 70,
flexDirection: "row",
flexWrap: "nowrap",
alignItems: "center"
},
inputIOSContainer: pickerContainer,
inputAndroidContainer: pickerContainer,
// $FlowFixMe
inputIOS: pickerText,
// $FlowFixMe
inputAndroid: pickerText
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
header: {
marginLeft: 10
},
text: {
margin: 10
},
disabled: {
color: colors.lightGray
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,57 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const PRIMARY = "#5D8017";
const PRIMARY_DISABLED = "#C6DC98";
const WARNING = "#9B1111";
const WARNING_DISABLED = "#B95F5E";
const NEUTRAL = "#979797";
const NEUTRAL_DISABLED = "#D3D3D3";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
containerDefault: {
borderRadius: 40
},
containerPrimary: {
backgroundColor: PRIMARY,
borderColor: PRIMARY
},
containerWarning: {
backgroundColor: WARNING,
borderColor: WARNING
},
containerNeutral: {
backgroundColor: NEUTRAL,
borderColor: NEUTRAL
},
containerPrimaryDisabled: {
backgroundColor: PRIMARY_DISABLED,
borderColor: PRIMARY_DISABLED
},
containerNeutralDisabled: {
backgroundColor: NEUTRAL_DISABLED,
borderColor: NEUTRAL_DISABLED
},
containerWarningDisabled: {
backgroundColor: WARNING_DISABLED,
borderColor: WARNING_DISABLED
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
textDefault: {
fontSize: 16,
fontWeight: "500",
color: colors.white
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,38 +0,0 @@
// @flow strict-local
import { Dimensions, StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const { width } = Dimensions.get( "screen" );
const oneThirdWidth = width / 3;
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
row: {
backgroundColor: colors.white,
flexDirection: "row",
flexWrap: "nowrap",
alignItems: "center",
justifyContent: "space-between"
},
headerRow: {
flexDirection: "row",
flexWrap: "nowrap",
justifyContent: "space-around"
},
oneThirdWidth: {
width: oneThirdWidth
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
text: {
textAlign: "center"
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,24 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
confirmButton: {
backgroundColor: colors.inatGreen,
borderRadius: 40,
alignSelf: "center"
},
cancelButton: {
backgroundColor: colors.gray,
borderRadius: 40,
alignSelf: "center",
marginRight: 10
}
} );
export {
// eslint-disable-next-line import/prefer-default-export
viewStyles
};

View File

@@ -5,17 +5,6 @@ import type { ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
row: {
backgroundColor: colors.white,
flexDirection: "row",
flexWrap: "nowrap",
position: "absolute",
bottom: 0,
left: 0,
right: 0,
height: 75,
justifyContent: "space-evenly"
},
shadow: {
shadowColor: colors.black,
shadowOffset: {

View File

@@ -1,21 +0,0 @@
// @flow
import { StyleSheet } from "react-native";
import type { TextStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
inputField: {
backgroundColor: colors.white,
borderColor: colors.gray,
borderRadius: 40,
borderWidth: 0.5,
color: colors.black,
height: 37,
paddingLeft: 15,
marginHorizontal: 50
// marginVertical: 20
}
} );
export default textStyles;

View File

@@ -1,7 +1,7 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import type { ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
@@ -10,13 +10,4 @@ const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
title: {
color: colors.black
}
} );
export {
textStyles,
viewStyles
};
export default viewStyles;

View File

@@ -1,25 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
whiteModal: {
backgroundColor: colors.white,
borderRadius: 40,
padding: 20
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
whiteText: {
color: colors.white,
marginVertical: 10
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,20 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
infiniteScroll: {
height: 100,
alignItems: "center",
backgroundColor: colors.white,
borderBottomColor: colors.lightGray,
borderBottomWidth: 1
}
} );
export {
// eslint-disable-next-line import/prefer-default-export
viewStyles
};

View File

@@ -1,60 +0,0 @@
// @flow strict-local
import { Dimensions, StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const { width, height } = Dimensions.get( "screen" );
const imageWidth = width / 2 - 20;
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
icon: {
marginRight: 5
},
center: {
top: height / 3,
alignItems: "center"
},
imageBackground: {
width: 75,
height: 75,
borderRadius: 10,
backgroundColor: colors.black,
marginHorizontal: 20
},
obsDetailsColumn: {
width: 200
},
row: {
flexDirection: "row",
flexWrap: "nowrap",
marginVertical: 10
},
iconRow: {
flexDirection: "row",
flexWrap: "nowrap",
alignItems: "center"
},
photoContainer: {
backgroundColor: colors.black,
height: 200
},
photoStatRow: {
flexDirection: "row",
flexWrap: "nowrap",
justifyContent: "space-between",
position: "absolute",
bottom: 80,
width: imageWidth,
backgroundColor: colors.white
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,68 +0,0 @@
// @flow strict-local
import { Dimensions, StyleSheet } from "react-native";
import type { ImageStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const { width } = Dimensions.get( "screen" );
const imageWidth = 66;
const standardCameraImage = {
backgroundColor: colors.midGray,
width: imageWidth,
height: imageWidth,
borderRadius: 8,
marginHorizontal: 6,
marginVertical: 27,
marginTop: 50,
marginBottom: 18,
justifyContent: "center"
};
const imageStyles: { [string]: ImageStyleProp } = StyleSheet.create( {
photo: {
width: imageWidth,
height: imageWidth,
borderRadius: 8,
marginHorizontal: 6,
marginVertical: 27
},
photoStandardCamera: standardCameraImage
} );
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
addEvidenceButton: {
width: imageWidth,
height: imageWidth,
borderWidth: 2,
borderColor: colors.logInGray,
borderRadius: 8,
marginHorizontal: 6,
marginVertical: 27,
display: "flex",
alignItems: "center",
justifyContent: "center"
},
photoContainer: {
top: 50,
minWidth: width
},
// $FlowIgnore
photoLoading: standardCameraImage,
greenSelectionBorder: {
borderWidth: 3,
borderColor: colors.selectionGreen
},
deleteButton: {
paddingHorizontal: 10,
left: 45,
top: -100,
paddingVertical: 10
}
} );
export {
imageStyles,
viewStyles
};

View File

@@ -1,29 +0,0 @@
// @flow strict-local
import { Dimensions, StyleSheet } from "react-native";
import type { ImageStyleProp, TextStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const { width } = Dimensions.get( "screen" );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
license: {
color: colors.white,
position: "absolute",
right: 10,
bottom: 10
}
} );
const imageStyles: { [string]: ImageStyleProp } = StyleSheet.create( {
fullWidthImage: {
width,
height: 200,
resizeMode: "contain"
}
} );
export {
imageStyles,
textStyles
};

View File

@@ -1,26 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
badgeContainer: {
backgroundColor: colors.inatGreen,
display: "flex",
flexDirection: "row",
alignItems: "center"
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
badgeText: {
color: "white",
margin: 5
}
} );
export {
textStyles,
viewStyles
};

View File

@@ -1,17 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "styles/colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
safeAreaContainer: {
flex: 1,
backgroundColor: colors.white
},
scrollPadding: {
paddingBottom: 140
}
} );
export default viewStyles;

View File

@@ -1,31 +0,0 @@
// @flow strict-local
import { StyleSheet } from "react-native";
import type { TextStyleProp, ViewStyleProp } from "react-native/Libraries/StyleSheet/StyleSheet";
import colors from "./colors";
const viewStyles: { [string]: ViewStyleProp } = StyleSheet.create( {
photoContainer: {
backgroundColor: colors.black,
height: 200
},
scrollView: {
paddingBottom: 150
},
textContainer: {
marginHorizontal: 25,
marginTop: 10
}
} );
const textStyles: { [string]: TextStyleProp } = StyleSheet.create( {
header: {
marginVertical: 10
}
} );
export {
textStyles,
viewStyles
};

Some files were not shown because too many files have changed in this diff Show More