Skip to content

Commit

Permalink
feat: add UI
Browse files Browse the repository at this point in the history
  • Loading branch information
MounirDhahri committed Jul 24, 2024
1 parent ba9756c commit 8d3bf82
Show file tree
Hide file tree
Showing 7 changed files with 364 additions and 3 deletions.
1 change: 1 addition & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
Expand Down
2 changes: 2 additions & 0 deletions ios/Artsy/App_Resources/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@
<string>Camera access is needed to submit consignments.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photos will be used to gauge the quality of the work you are submitting.</string>
<key>NSDocumentsUsageDescription</key>
<string>In order to add additional documents to your submission, we need access to your documents to pick files.</string>
<key>UIAppFonts</key>
<array>
<string>Unica77LL-Italic.otf</string>
Expand Down
6 changes: 6 additions & 0 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,8 @@ PODS:
- React
- react-native-cookies (6.0.11):
- React-Core
- react-native-document-picker (9.3.0):
- React-Core
- react-native-fbsdk-next (13.0.0):
- React-Core
- react-native-fbsdk-next/Core (= 13.0.0)
Expand Down Expand Up @@ -1019,6 +1021,7 @@ DEPENDENCIES:
- react-native-config (from `../node_modules/react-native-config`)
- react-native-context-menu-view (from `../node_modules/react-native-context-menu-view`)
- "react-native-cookies (from `../node_modules/@react-native-cookies/cookies`)"
- react-native-document-picker (from `../node_modules/react-native-document-picker`)
- react-native-fbsdk-next (from `../node_modules/react-native-fbsdk-next`)
- react-native-flipper (from `../node_modules/react-native-flipper`)
- react-native-flipper-performance-plugin (from `../node_modules/react-native-flipper-performance-plugin`)
Expand Down Expand Up @@ -1256,6 +1259,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-context-menu-view"
react-native-cookies:
:path: "../node_modules/@react-native-cookies/cookies"
react-native-document-picker:
:path: "../node_modules/react-native-document-picker"
react-native-fbsdk-next:
:path: "../node_modules/react-native-fbsdk-next"
react-native-flipper:
Expand Down Expand Up @@ -1496,6 +1501,7 @@ SPEC CHECKSUMS:
react-native-config: bcafda5b4c51491ee1b0e1d0c4e3905bc7b56c1b
react-native-context-menu-view: 4cd5ecaabd2cbb420018b3242ca1eef5efa0a629
react-native-cookies: cd92f3824ed1e32a20802e8185101e14bb5b76da
react-native-document-picker: 5b97e24a7f1a1e4a50a72c540a043f32d29a70a2
react-native-fbsdk-next: 84746ea062dc3b1a422154d7674ba4c5836b6d82
react-native-flipper: 457f5d70546cdd4281e748ff3726385ffa06ba41
react-native-flipper-performance-plugin: 42ec5017abd26e7c5a1f527f2db92c14a90cabdb
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@
"react-native-context-menu-view": "git+https://github.com/artsy/react-native-context-menu-view.git#v1.10.10-artsy",
"react-native-dev-menu-android": "1.0.10",
"react-native-device-info": "10.3.0",
"react-native-document-picker": "9.3.0",
"react-native-fast-image": "8.6.3",
"react-native-fbsdk-next": "13.0.0",
"react-native-gesture-handler": "2.13.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,60 @@
import { OwnerType } from "@artsy/cohesion"
import { Flex, Spacer, Text } from "@artsy/palette-mobile"
import {
Button,
CloseIcon,
DocumentIcon,
Flex,
INPUT_BORDER_RADIUS,
Separator,
Spacer,
Text,
Touchable,
useSpace,
} from "@artsy/palette-mobile"
import { useActionSheet } from "@expo/react-native-action-sheet"
import { NavigationProp, useNavigation } from "@react-navigation/native"
import { useToast } from "app/Components/Toast/toastHook"
import { SubmitArtworkFormStore } from "app/Scenes/SellWithArtsy/ArtworkForm/Components/SubmitArtworkFormStore"
import { SubmitArtworkStackNavigation } from "app/Scenes/SellWithArtsy/ArtworkForm/SubmitArtworkForm"
import { useNavigationListeners } from "app/Scenes/SellWithArtsy/ArtworkForm/Utils/useNavigationListeners"
import { SubmissionModel } from "app/Scenes/SellWithArtsy/ArtworkForm/Utils/validation"
import { ICON_SIZE } from "app/Scenes/SellWithArtsy/SubmitArtwork/UploadPhotos/UploadPhotosForm"
import {
isDocument,
isImage,
showDocumentsAndPhotosActionSheet,
} from "app/utils/showDocumentsAndPhotosActionSheet"
import { ProvideScreenTrackingWithCohesionSchema } from "app/utils/track"
import { screen } from "app/utils/track/helpers"
import { useFormikContext } from "formik"
import { ScrollView } from "react-native"
import { useState } from "react"
import { Image, LayoutAnimation, Platform, ScrollView } from "react-native"
import { DocumentPickerResponse } from "react-native-document-picker"
import { Image as RNPickerImage } from "react-native-image-crop-picker"

type UploadedFile = {
error?: string
} & DocumentPickerResponse

type UploadedImage = {
error?: string
} & RNPickerImage

// 50 MB in bytes
const FILE_SIZE_LIMIT = 1 * 1024 * 1024

export const SubmitArtworkAdditionalDocuments = () => {
const { values } = useFormikContext<SubmissionModel>()

const space = useSpace()

const { showActionSheetWithOptions } = useActionSheet()

const { show: showToast } = useToast()

const [documents, setDocuments] = useState<UploadedFile[]>([])
const [images, setImages] = useState<UploadedImage[]>([])

const setIsLoading = SubmitArtworkFormStore.useStoreActions((actions) => actions.setIsLoading)
const setCurrentStep = SubmitArtworkFormStore.useStoreActions((actions) => actions.setCurrentStep)

Expand All @@ -42,6 +81,89 @@ export const SubmitArtworkAdditionalDocuments = () => {
},
})

const handleUpload = async () => {
try {
const results = await showDocumentsAndPhotosActionSheet(showActionSheetWithOptions, true)
const imagesResults = results.filter(isImage)
const documentsResults = results.filter(isDocument)

const filteredDocuments = documentsResults
// Remove duplicates
.filter(({ uri }) => !documents.find(({ uri: fileUri }) => fileUri === uri))
// Remove files that are too large
.map((file) => {
if (file.size && file.size > FILE_SIZE_LIMIT) {
return {
...file,
error: "File is too large (max. 50 MB)",
}
}
return file
})

const filteredImages = imagesResults
// Remove duplicates
.filter(({ path }) => !images.find(({ path: ImagePath }) => ImagePath === path))
// Remove images that are too large
.map((image) => {
if (image.size && image.size > FILE_SIZE_LIMIT) {
return {
...image,
error: "Image is too large (max. 50 MB)",
}
}
return image
})

if (filteredDocuments.length > 0) {
setIsLoading(true)
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setDocuments(documents.concat(filteredDocuments))
}

if (filteredImages.length > 0) {
setIsLoading(true)
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
setImages(images.concat(filteredImages))
}
} catch (error) {
if (typeof error === "object" && (error as any).code === "DOCUMENT_PICKER_CANCELED") {
return
}
showToast("Could not upload documents, please try again.", "bottom", {
backgroundColor: "red100",
})
} finally {
setIsLoading(false)
}
}

// remove image assets from submission
const handleImageDelete = async (path: string) => {
try {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
const filteredImages = images.filter((image) => image.path !== path)
setImages(filteredImages)

// TODO: unlink from submission
} catch (error) {
console.error("Failed to delete image", error)
}
}

// remove image assets from submission
const handleDocumentDelete = async (uri: string) => {
try {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
const filteredDocuments = documents.filter(({ uri: fileUri }) => fileUri !== uri)
setDocuments(filteredDocuments)

// TODO: unlink from submission
} catch (error) {
console.error("Failed to delete document", error)
}
}

return (
<ProvideScreenTrackingWithCohesionSchema
info={screen({
Expand All @@ -50,7 +172,10 @@ export const SubmitArtworkAdditionalDocuments = () => {
})}
>
<Flex px={2} flex={1}>
<ScrollView>
<ScrollView
contentContainerStyle={{ paddingBottom: 50 }}
showsVerticalScrollIndicator={false}
>
<Flex>
<Text variant="lg-display">Additional documents</Text>

Expand All @@ -62,9 +187,141 @@ export const SubmitArtworkAdditionalDocuments = () => {
</Text>

<Spacer y={2} />

<Button block variant="outline" onPress={handleUpload}>
Add Documents
</Button>

<Separator my={2} borderColor="black10" />

<Flex rowGap={space(2)}>
{documents.map(({ uri, name, size, error, type }) => {
return (
<UploadedFile
// progress={progress}
error={error}
key={uri}
name={name}
uri={uri}
onRemove={() => {
handleDocumentDelete(uri)
}}
size={size}
type={type}
/>
)
})}
{images.map(({ path, size, error }) => {
return (
<UploadedFile
error={error}
key={path}
name={!error ? "Image added successfully" : "Could not add image"}
uri={path}
onRemove={() => {
handleImageDelete(path)
}}
size={size}
type="image"
/>
)
})}
</Flex>
</Flex>
</ScrollView>
</Flex>
</ProvideScreenTrackingWithCohesionSchema>
)
}

const CONTAINER_HEIGHT = 60

const UploadedFile: React.FC<{
error?: string
name: string | null
onRemove: () => void
progress?: number
size: number | null
type: string | null
uri: string
}> = ({ error, name, size, onRemove, type, uri }) => {
const space = useSpace()
const sizeInMb = size ? (size / 1024 / 1024).toFixed(2) : 0

return (
<Flex
flexDirection="row"
gap={space(1)}
alignItems="center"
borderWidth={1}
borderColor="black10"
borderRadius={INPUT_BORDER_RADIUS}
pr={1}
>
<Flex
height={CONTAINER_HEIGHT}
width={CONTAINER_HEIGHT}
borderTopLeftRadius={INPUT_BORDER_RADIUS}
borderBottomLeftRadius={INPUT_BORDER_RADIUS}
backgroundColor="black5"
justifyContent="center"
alignItems="center"
>
{type && type.includes("image") ? (
<Image
source={{ uri }}
resizeMode="cover"
width={CONTAINER_HEIGHT}
height={CONTAINER_HEIGHT}
/>
) : (
<DocumentIcon fill="black100" width={20} height={20} />
)}
</Flex>
<Flex flex={1}>
<Text
variant="xs"
color="black100"
numberOfLines={1}
// Middle ellipsize is broken on Android :shrug:
ellipsizeMode={Platform.OS === "ios" ? "middle" : "tail"}
>
{name}
</Text>
{error ? (
<Text variant="xs" color="red100">
{error}
</Text>
) : (
<Text variant="xs" color="black30">
{sizeInMb} MB
</Text>
)}
</Flex>
{!error && (
<Flex justifySelf="flex-end" ml={0.5}>
<Touchable
onPress={onRemove}
haptic="impactHeavy"
hitSlop={{
top: 5,
right: 5,
bottom: 5,
left: 5,
}}
style={{
backgroundColor: "black",
borderRadius: ICON_SIZE / 2,
height: ICON_SIZE,
width: ICON_SIZE,
justifyContent: "center",
alignItems: "center",
}}
>
<CloseIcon height={16} width={16} fill="white100" />
</Touchable>
</Flex>
)}
</Flex>
)
}
Loading

0 comments on commit 8d3bf82

Please sign in to comment.