mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2025-12-23 22:18:36 -05:00
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:
committed by
GitHub
parent
e81894d406
commit
c740a06224
@@ -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.
|
||||
|
||||
@@ -3,6 +3,7 @@ module.exports = {
|
||||
plugins: [
|
||||
"react-native-reanimated/plugin",
|
||||
"transform-inline-environment-variables",
|
||||
"nativewind/babel",
|
||||
["module-resolver", {
|
||||
alias: {
|
||||
api: "./src/api",
|
||||
|
||||
13
ios/Podfile
13
ios/Podfile
@@ -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
|
||||
|
||||
@@ -618,6 +618,6 @@ SPEC CHECKSUMS:
|
||||
VisionCamera: c1c171fcdbf18c438987847f785829c5638f3a4c
|
||||
Yoga: 99652481fcd320aefa4a7ef90095b95acd181952
|
||||
|
||||
PODFILE CHECKSUM: d1bc9d7f55b5f7449c3ceb6a7622454bc4f8b972
|
||||
PODFILE CHECKSUM: 75b1e337bfba8ed4c2d8a23c444fe54409246c63
|
||||
|
||||
COCOAPODS: 1.11.3
|
||||
|
||||
@@ -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
4463
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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": {
|
||||
|
||||
@@ -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 )}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
}
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" )}
|
||||
|
||||
@@ -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>
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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" )}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
|
||||
@@ -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( )}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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">⌄</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" )}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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( );
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
41
src/components/styledComponents.js
Normal file
41
src/components/styledComponents.js
Normal 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
|
||||
};
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ) => {
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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: {
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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;
|
||||
@@ -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
Reference in New Issue
Block a user