feat: redesign input komentar gaya messaging app dan tampilan file lampiran
- Input komentar: tombol + di kiri, pill input flex-1, tombol send lingkaran di kanan - Tampilan file di komentar: semua tipe file sebagai card (tidak ada preview inline) - Grid 2 kolom untuk file, chip '+N lainnya' jika lebih dari 2 - Modal full screen saat klik '+N lainnya' dengan grid 2 kolom - Preview gambar via ImageViewing (dalam modal dan di luar modal) - File non-gambar dibuka via native viewer (download + intent/share)
This commit is contained in:
171
components/discussion_general/discussionCommentInput.tsx
Normal file
171
components/discussion_general/discussionCommentInput.tsx
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import Text from "@/components/Text";
|
||||||
|
import { regexOnlySpacesOrEnter } from "@/constants/OnlySpaceOrEnter";
|
||||||
|
import Styles from "@/constants/Styles";
|
||||||
|
import { useTheme } from "@/providers/ThemeProvider";
|
||||||
|
import { Feather, Ionicons, MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
|
||||||
|
import * as DocumentPicker from "expo-document-picker";
|
||||||
|
import { Platform, Pressable, ScrollView, TextInput, View } from "react-native";
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
mode: 'new' | 'edit' | 'locked'
|
||||||
|
lockedReason?: string
|
||||||
|
value: string
|
||||||
|
onChange: (val: string) => void
|
||||||
|
loading: boolean
|
||||||
|
onSend: () => void
|
||||||
|
onCancelEdit?: () => void
|
||||||
|
files?: { uri: string; name: string }[]
|
||||||
|
onAddFile?: (files: { uri: string; name: string }[]) => void
|
||||||
|
onRemoveFile?: (index: number) => void
|
||||||
|
existingFiles?: { id: string; name: string; extension: string }[]
|
||||||
|
onRemoveExistingFile?: (id: string) => void
|
||||||
|
canSend: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DiscussionCommentInput({
|
||||||
|
mode, lockedReason, value, onChange, loading, onSend,
|
||||||
|
onCancelEdit, files = [], onAddFile, onRemoveFile,
|
||||||
|
existingFiles = [], onRemoveExistingFile, canSend
|
||||||
|
}: Props) {
|
||||||
|
const { colors } = useTheme()
|
||||||
|
|
||||||
|
async function pickFiles() {
|
||||||
|
const result = await DocumentPicker.getDocumentAsync({ type: ['*/*'], multiple: true })
|
||||||
|
if (!result.canceled && onAddFile) {
|
||||||
|
let skipped = 0
|
||||||
|
const newFiles: { uri: string; name: string }[] = []
|
||||||
|
for (const asset of result.assets) {
|
||||||
|
if (!asset.uri) continue
|
||||||
|
if (files.some(f => f.name === asset.name)) { skipped++; continue }
|
||||||
|
newFiles.push({ uri: asset.uri, name: asset.name })
|
||||||
|
}
|
||||||
|
if (skipped > 0) Toast.show({ type: 'small', text1: 'Beberapa file sudah ditambahkan' })
|
||||||
|
if (newFiles.length > 0) onAddFile(newFiles)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === 'locked') {
|
||||||
|
return (
|
||||||
|
<View style={[Styles.pv20, Styles.itemsCenter]}>
|
||||||
|
<Text style={[Styles.textInformation, { color: colors.dimmed }]}>{lockedReason}</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sendDisabled = loading || value.trim() === '' || regexOnlySpacesOrEnter.test(value) || !canSend
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={{ backgroundColor: colors.background, borderTopWidth: mode === 'edit' ? 1 : 0, borderTopColor: colors.icon + '20' }}>
|
||||||
|
{mode === 'edit' && (
|
||||||
|
<View style={[Styles.w90, Styles.rowSpaceBetween, Styles.pv05]}>
|
||||||
|
<View style={Styles.rowItemsCenter}>
|
||||||
|
<Feather name="edit-3" color={colors.text} size={20} style={Styles.mh05} />
|
||||||
|
<Text style={Styles.textMediumSemiBold}>Edit Komentar</Text>
|
||||||
|
</View>
|
||||||
|
<Pressable onPress={onCancelEdit}>
|
||||||
|
<MaterialIcons name="close" color={colors.text} size={20} />
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(existingFiles.length > 0 || files.length > 0) && (
|
||||||
|
<ScrollView horizontal style={[Styles.ph15, Styles.pv05]} showsHorizontalScrollIndicator={false}>
|
||||||
|
{existingFiles.map((f) => (
|
||||||
|
<Pressable
|
||||||
|
key={f.id}
|
||||||
|
onPress={() => onRemoveExistingFile?.(f.id)}
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row', alignItems: 'center', gap: 6,
|
||||||
|
paddingHorizontal: 10, paddingVertical: 6,
|
||||||
|
borderRadius: 20, borderWidth: 1,
|
||||||
|
backgroundColor: colors.card, borderColor: colors.icon + '18',
|
||||||
|
marginRight: 8
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MaterialCommunityIcons name="file-outline" size={14} color={colors.dimmed} />
|
||||||
|
<Text style={[Styles.textSmallSemiBold, { color: colors.dimmed, maxWidth: 100 }]} numberOfLines={1}>
|
||||||
|
{f.name}.{f.extension}
|
||||||
|
</Text>
|
||||||
|
<MaterialIcons name="close" size={14} color='#F03E3E' />
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
{files.map((f, idx) => (
|
||||||
|
<Pressable
|
||||||
|
key={idx}
|
||||||
|
onPress={() => onRemoveFile?.(idx)}
|
||||||
|
style={{
|
||||||
|
flexDirection: 'row', alignItems: 'center', gap: 6,
|
||||||
|
paddingHorizontal: 10, paddingVertical: 6,
|
||||||
|
borderRadius: 20, borderWidth: 1,
|
||||||
|
backgroundColor: colors.card, borderColor: colors.icon + '18',
|
||||||
|
marginRight: 8
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MaterialCommunityIcons name="file-outline" size={14} color={colors.dimmed} />
|
||||||
|
<Text style={[Styles.textSmallSemiBold, { color: colors.dimmed, maxWidth: 100 }]} numberOfLines={1}>
|
||||||
|
{f.name}
|
||||||
|
</Text>
|
||||||
|
<MaterialIcons name="close" size={14} color={colors.dimmed} />
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={{
|
||||||
|
flexDirection: 'row', alignItems: 'flex-end',
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingBottom: Platform.OS === 'ios' ? 10 : 6,
|
||||||
|
paddingTop: 6,
|
||||||
|
gap: 8
|
||||||
|
}}>
|
||||||
|
{mode === 'new' && (
|
||||||
|
<Pressable
|
||||||
|
onPress={pickFiles}
|
||||||
|
style={{ marginBottom: 6 }}
|
||||||
|
>
|
||||||
|
<MaterialIcons
|
||||||
|
name="add"
|
||||||
|
size={28}
|
||||||
|
color={files.length > 0 ? colors.tabActive : colors.dimmed}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={{
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: colors.input,
|
||||||
|
borderRadius: 30, borderWidth: 1, borderColor: colors.icon + '20',
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
paddingVertical: 10,
|
||||||
|
}}>
|
||||||
|
<TextInput
|
||||||
|
style={{
|
||||||
|
color: colors.text,
|
||||||
|
maxHeight: 100,
|
||||||
|
paddingVertical: Platform.OS === 'android' ? 4 : 0,
|
||||||
|
textAlignVertical: 'bottom',
|
||||||
|
}}
|
||||||
|
placeholder="Kirim Komentar"
|
||||||
|
placeholderTextColor={colors.dimmed}
|
||||||
|
value={value}
|
||||||
|
onChangeText={onChange}
|
||||||
|
multiline
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Pressable
|
||||||
|
onPress={() => !sendDisabled && onSend()}
|
||||||
|
style={{
|
||||||
|
width: 40, height: 40, borderRadius: 20,
|
||||||
|
backgroundColor: sendDisabled ? colors.dimmed + '40' : colors.tint,
|
||||||
|
justifyContent: 'center', alignItems: 'center',
|
||||||
|
marginBottom: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Ionicons name="send" size={18} color={sendDisabled ? colors.dimmed : '#fff'} />
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
275
components/discussion_general/discussionCommentList.tsx
Normal file
275
components/discussion_general/discussionCommentList.tsx
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
import ImageUser from "@/components/imageNew";
|
||||||
|
import Skeleton from "@/components/skeleton";
|
||||||
|
import Text from "@/components/Text";
|
||||||
|
import { ConstEnv } from "@/constants/ConstEnv";
|
||||||
|
import { isImageFile } from "@/constants/FileExtensions";
|
||||||
|
import Styles from "@/constants/Styles";
|
||||||
|
import { useTheme } from "@/providers/ThemeProvider";
|
||||||
|
import { MaterialCommunityIcons, MaterialIcons } from "@expo/vector-icons";
|
||||||
|
import * as FileSystem from 'expo-file-system';
|
||||||
|
import { startActivityAsync } from 'expo-intent-launcher';
|
||||||
|
import * as Sharing from 'expo-sharing';
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Modal, Platform, Pressable, SafeAreaView, ScrollView, View } from "react-native";
|
||||||
|
import ImageViewing from "react-native-image-viewing";
|
||||||
|
import * as mime from 'react-native-mime-types';
|
||||||
|
import Toast from "react-native-toast-message";
|
||||||
|
|
||||||
|
export type CommentFile = {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
extension: string
|
||||||
|
idStorage: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommentItem = {
|
||||||
|
id: string
|
||||||
|
comment: string
|
||||||
|
createdAt: string
|
||||||
|
idUser: string
|
||||||
|
img: string
|
||||||
|
username: string
|
||||||
|
isEdited: boolean
|
||||||
|
updatedAt: string
|
||||||
|
files: CommentFile[]
|
||||||
|
}
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
data: CommentItem[]
|
||||||
|
loading: boolean
|
||||||
|
myId: string
|
||||||
|
canInteract: boolean
|
||||||
|
onLongPress: (id: string, comment: string, files: CommentFile[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileIcon(ext: string): keyof typeof MaterialCommunityIcons.glyphMap {
|
||||||
|
if (isImageFile(ext)) return 'image-outline'
|
||||||
|
if (ext === 'pdf') return 'file-pdf-box'
|
||||||
|
if (['mp4', 'mov', 'avi', 'mkv'].includes(ext)) return 'video-outline'
|
||||||
|
if (['doc', 'docx'].includes(ext)) return 'file-word-outline'
|
||||||
|
if (['xls', 'xlsx'].includes(ext)) return 'file-excel-outline'
|
||||||
|
if (['zip', 'rar', '7z'].includes(ext)) return 'zip-box-outline'
|
||||||
|
return 'file-outline'
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFileColor(ext: string): string {
|
||||||
|
if (isImageFile(ext)) return '#339AF0'
|
||||||
|
if (ext === 'pdf') return '#F03E3E'
|
||||||
|
if (['mp4', 'mov', 'avi', 'mkv'].includes(ext)) return '#AE3EC9'
|
||||||
|
if (['doc', 'docx'].includes(ext)) return '#1C7ED6'
|
||||||
|
if (['xls', 'xlsx'].includes(ext)) return '#2F9E44'
|
||||||
|
if (['zip', 'rar', '7z'].includes(ext)) return '#E8590C'
|
||||||
|
return '#868E96'
|
||||||
|
}
|
||||||
|
|
||||||
|
function FileCard({ file, colors, onPress }: { file: CommentFile; colors: any; onPress: () => void }) {
|
||||||
|
const ext = file.extension.toLowerCase()
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
onPress={onPress}
|
||||||
|
style={({ pressed }) => [Styles.fileCard, {
|
||||||
|
borderColor: colors.icon + '18',
|
||||||
|
backgroundColor: pressed ? colors.icon + '10' : 'transparent'
|
||||||
|
}]}
|
||||||
|
>
|
||||||
|
<View style={[Styles.sectionIconBox, { backgroundColor: getFileColor(ext) + '20' }]}>
|
||||||
|
<MaterialCommunityIcons name={getFileIcon(ext)} size={18} color={getFileColor(ext)} />
|
||||||
|
</View>
|
||||||
|
<View style={Styles.flex1}>
|
||||||
|
<Text style={Styles.textDefault} numberOfLines={1}>{file.name}</Text>
|
||||||
|
<Text style={[Styles.textSmallSemiBold, { color: colors.dimmed }]}>{ext.toUpperCase()}</Text>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DiscussionCommentList({ data, loading, myId, canInteract, onLongPress }: Props) {
|
||||||
|
const { colors } = useTheme()
|
||||||
|
const [expandedIds, setExpandedIds] = useState<string[]>([])
|
||||||
|
const [modalFiles, setModalFiles] = useState<CommentFile[]>([])
|
||||||
|
const [modalVisible, setModalVisible] = useState(false)
|
||||||
|
const [previewFile, setPreviewFile] = useState<CommentFile | null>(null)
|
||||||
|
const [modalPreviewFile, setModalPreviewFile] = useState<CommentFile | null>(null)
|
||||||
|
const [loadingOpen, setLoadingOpen] = useState(false)
|
||||||
|
const arrSkeleton = Array.from({ length: 3 }, (_, i) => i)
|
||||||
|
|
||||||
|
function toggleExpand(id: string) {
|
||||||
|
setExpandedIds(prev => prev.includes(id) ? prev.filter(x => x !== id) : [...prev, id])
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openExternal(file: CommentFile) {
|
||||||
|
try {
|
||||||
|
setLoadingOpen(true)
|
||||||
|
const remoteUrl = `${ConstEnv.url_storage}/files/${file.idStorage}`
|
||||||
|
const fileName = `${file.name}.${file.extension}`
|
||||||
|
const localPath = `${FileSystem.documentDirectory}/${fileName}`
|
||||||
|
const dl = await FileSystem.downloadAsync(remoteUrl, localPath)
|
||||||
|
if (dl.status !== 200) throw new Error('Download failed')
|
||||||
|
const contentURL = await FileSystem.getContentUriAsync(dl.uri)
|
||||||
|
const mimeType = mime.lookup(fileName) as string
|
||||||
|
if (Platform.OS === 'android') {
|
||||||
|
await startActivityAsync('android.intent.action.VIEW', { data: contentURL, flags: 1, type: mimeType })
|
||||||
|
} else {
|
||||||
|
await Sharing.shareAsync(localPath)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Toast.show({ type: 'error', text1: 'Gagal membuka file' })
|
||||||
|
} finally {
|
||||||
|
setLoadingOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFilePress(file: CommentFile) {
|
||||||
|
if (isImageFile(file.extension.toLowerCase())) {
|
||||||
|
setPreviewFile(file)
|
||||||
|
} else {
|
||||||
|
openExternal(file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<View style={Styles.mt10}>
|
||||||
|
{arrSkeleton.map((_, i) => (
|
||||||
|
<Skeleton key={i} width={100} widthType="percent" height={40} borderRadius={5} />
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View style={Styles.mt10}>
|
||||||
|
{data.map((item, i) => (
|
||||||
|
<Pressable
|
||||||
|
key={i}
|
||||||
|
onPress={() => toggleExpand(item.id)}
|
||||||
|
onLongPress={() => item.idUser === myId && canInteract && onLongPress(item.id, item.comment, item.files ?? [])}
|
||||||
|
style={({ pressed }) => [
|
||||||
|
Styles.discussionCommentCard,
|
||||||
|
{ backgroundColor: pressed ? colors.icon + '10' : colors.card, borderColor: colors.icon + '20' }
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<View style={Styles.flex1}>
|
||||||
|
<View style={[Styles.rowSpaceBetween, Styles.itemsCenter, Styles.mb05]}>
|
||||||
|
<View style={[Styles.rowItemsCenter, { gap: 8, flex: 1, marginRight: 8 }]}>
|
||||||
|
<ImageUser src={`${ConstEnv.url_storage}/files/${item.img}`} size="xs" />
|
||||||
|
<Text style={[Styles.textMediumSemiBold, { color: colors.text }]} numberOfLines={1}>
|
||||||
|
{item.username}
|
||||||
|
</Text>
|
||||||
|
{item.isEdited && (
|
||||||
|
<Text style={[Styles.discussionEditedText, { color: colors.dimmed }]}>diedit</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<Text style={[Styles.discussionDateText, { color: colors.dimmed, flexShrink: 0 }]}>
|
||||||
|
{item.createdAt}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{item.comment.length > 0 && (
|
||||||
|
<Text
|
||||||
|
style={[Styles.textDefault, { color: colors.text }]}
|
||||||
|
numberOfLines={expandedIds.includes(item.id) ? 0 : 3}
|
||||||
|
>
|
||||||
|
{item.comment}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{item.files?.length > 0 && (
|
||||||
|
<View style={{ flexDirection: 'row', flexWrap: 'wrap', gap: 6, marginTop: item.comment.length > 0 ? 8 : 0 }}>
|
||||||
|
{(item.files.length > 2 ? item.files.slice(0, 1) : item.files).map((file, idx) => (
|
||||||
|
<FileCard key={idx} file={file} colors={colors} onPress={() => handleFilePress(file)} />
|
||||||
|
))}
|
||||||
|
{item.files.length > 2 && (
|
||||||
|
<Pressable
|
||||||
|
onPress={() => { setModalFiles(item.files); setModalVisible(true) }}
|
||||||
|
style={[Styles.fileCard, { borderColor: colors.icon + '18', backgroundColor: 'transparent' }]}
|
||||||
|
>
|
||||||
|
<View style={[Styles.sectionIconBox, { backgroundColor: '#868E96' + '20' }]}>
|
||||||
|
<MaterialCommunityIcons name="folder-multiple-outline" size={18} color="#868E96" />
|
||||||
|
</View>
|
||||||
|
<View style={Styles.flex1}>
|
||||||
|
<Text style={Styles.textDefault} numberOfLines={1}>+{item.files.length - 1} lainnya</Text>
|
||||||
|
<Text style={[Styles.textSmallSemiBold, { color: colors.dimmed }]}>Lihat semua</Text>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Modal visible={modalVisible} animationType="slide" onRequestClose={() => setModalVisible(false)}>
|
||||||
|
<SafeAreaView style={{ flex: 1, backgroundColor: colors.background }}>
|
||||||
|
<View style={[Styles.rowSpaceBetween, Styles.itemsCenter, Styles.ph15, Styles.pv10, { borderBottomWidth: 1, borderBottomColor: colors.icon + '20' }]}>
|
||||||
|
<Text style={Styles.textLargeSemiBold}>Lampiran ({modalFiles.length} file)</Text>
|
||||||
|
<Pressable onPress={() => setModalVisible(false)}>
|
||||||
|
<MaterialIcons name="close" size={24} color={colors.text} />
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
<ScrollView contentContainerStyle={[Styles.ph15, Styles.pv10, { flexDirection: 'row', flexWrap: 'wrap', gap: 8 }]}>
|
||||||
|
{modalFiles.map((file, idx) => (
|
||||||
|
<FileCard
|
||||||
|
key={idx} file={file} colors={colors}
|
||||||
|
onPress={() => {
|
||||||
|
if (isImageFile(file.extension.toLowerCase())) {
|
||||||
|
setModalPreviewFile(file)
|
||||||
|
} else {
|
||||||
|
openExternal(file)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
<ImageViewing
|
||||||
|
images={[{ uri: `${ConstEnv.url_storage}/files/${modalPreviewFile?.idStorage}` }]}
|
||||||
|
imageIndex={0}
|
||||||
|
visible={modalPreviewFile !== null}
|
||||||
|
onRequestClose={() => setModalPreviewFile(null)}
|
||||||
|
doubleTapToZoomEnabled
|
||||||
|
HeaderComponent={() => (
|
||||||
|
<View style={Styles.headerModalViewImg}>
|
||||||
|
<Pressable onPress={() => setModalPreviewFile(null)}>
|
||||||
|
<Text style={{ color: 'white', fontSize: 26 }}>✕</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable onPress={() => modalPreviewFile && openExternal(modalPreviewFile)} disabled={loadingOpen}>
|
||||||
|
<Text style={{ color: loadingOpen ? 'gray' : 'white', fontSize: 26 }}>⋯</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
FooterComponent={() => (
|
||||||
|
<View style={{ paddingBottom: 20, paddingHorizontal: 16, alignItems: 'center' }}>
|
||||||
|
<Text style={{ color: 'white', fontSize: 16 }}>{modalPreviewFile?.name}.{modalPreviewFile?.extension}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
|
||||||
|
<ImageViewing
|
||||||
|
images={[{ uri: `${ConstEnv.url_storage}/files/${previewFile?.idStorage}` }]}
|
||||||
|
imageIndex={0}
|
||||||
|
visible={previewFile !== null}
|
||||||
|
onRequestClose={() => setPreviewFile(null)}
|
||||||
|
doubleTapToZoomEnabled
|
||||||
|
HeaderComponent={() => (
|
||||||
|
<View style={Styles.headerModalViewImg}>
|
||||||
|
<Pressable onPress={() => setPreviewFile(null)}>
|
||||||
|
<Text style={{ color: 'white', fontSize: 26 }}>✕</Text>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable onPress={() => previewFile && openExternal(previewFile)} disabled={loadingOpen}>
|
||||||
|
<Text style={{ color: loadingOpen ? 'gray' : 'white', fontSize: 26 }}>⋯</Text>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
FooterComponent={() => (
|
||||||
|
<View style={{ paddingBottom: 20, paddingHorizontal: 16, alignItems: 'center' }}>
|
||||||
|
<Text style={{ color: 'white', fontSize: 16 }}>{previewFile?.name}.{previewFile?.extension}</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user