upd: view file

Deskripsi:
- view file pada pengumuman, diskusi divisi dan diskusi umum

No Issues
This commit is contained in:
2026-02-04 11:37:57 +08:00
parent 9bab420f91
commit bbacd40ae9
3 changed files with 174 additions and 90 deletions

View File

@@ -14,7 +14,7 @@ import { startActivityAsync } from 'expo-intent-launcher';
import { router, Stack, useLocalSearchParams } from "expo-router"; import { router, Stack, useLocalSearchParams } from "expo-router";
import * as Sharing from 'expo-sharing'; import * as Sharing from 'expo-sharing';
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { Alert, Dimensions, Platform, Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native"; import { Dimensions, Platform, Pressable, RefreshControl, SafeAreaView, ScrollView, View } from "react-native";
import ImageViewing from 'react-native-image-viewing'; import ImageViewing from 'react-native-image-viewing';
import * as mime from 'react-native-mime-types'; import * as mime from 'react-native-mime-types';
import RenderHTML from 'react-native-render-html'; import RenderHTML from 'react-native-render-html';
@@ -68,6 +68,7 @@ export default function DetailAnnouncement() {
* Opens the image preview modal for the selected image file * Opens the image preview modal for the selected image file
* @param item The file data object containing image information * @param item The file data object containing image information
*/ */
function handleChooseFile(item: FileData) { function handleChooseFile(item: FileData) {
setChooseFile(item) setChooseFile(item)
setPreview(true) setPreview(true)
@@ -156,11 +157,13 @@ export default function DetailAnnouncement() {
} }
} catch (openError) { } catch (openError) {
console.error('Error opening file:', openError); console.error('Error opening file:', openError);
Alert.alert('INFO', 'Tidak ada aplikasi yang dapat membuka file ini'); Toast.show({
type: 'error',
text1: 'Tidak ada aplikasi yang dapat membuka file ini'
});
} }
} catch (error) { } catch (error) {
console.error('Error downloading or opening file:', error); console.error('Error downloading or opening file:', error);
Alert.alert('INFO', 'Gagal mengunduh atau membuka file');
Toast.show({ Toast.show({
type: 'error', type: 'error',
text1: 'Gagal membuka file', text1: 'Gagal membuka file',

View File

@@ -80,8 +80,6 @@ export default function DetailDiscussionGeneral() {
}) })
const [viewEdit, setViewEdit] = useState(false) const [viewEdit, setViewEdit] = useState(false)
useEffect(() => { useEffect(() => {
const onValueChange = reference.on('value', snapshot => { const onValueChange = reference.on('value', snapshot => {
if (snapshot.val() == null) { if (snapshot.val() == null) {

View File

@@ -1,14 +1,17 @@
import { ColorsStatus } from "@/constants/ColorsStatus"; import { ColorsStatus } from "@/constants/ColorsStatus";
import { ConstEnv } from "@/constants/ConstEnv"; import { ConstEnv } from "@/constants/ConstEnv";
import { isImageFile } from "@/constants/FileExtensions";
import Styles from "@/constants/Styles"; import Styles from "@/constants/Styles";
import { Ionicons } from "@expo/vector-icons"; import { MaterialCommunityIcons } from "@expo/vector-icons";
import * as FileSystem from 'expo-file-system'; import * as FileSystem from 'expo-file-system';
import { startActivityAsync } from 'expo-intent-launcher'; import { startActivityAsync } from 'expo-intent-launcher';
import * as Sharing from 'expo-sharing'; import * as Sharing from 'expo-sharing';
import React, { useState } from "react"; import React, { useState } from "react";
import { Alert, Dimensions, Platform, Pressable, View } from "react-native"; import { Dimensions, Platform, Pressable, View } from "react-native";
import { ScrollView } from "react-native-gesture-handler"; import { ScrollView } from "react-native-gesture-handler";
import ImageViewing from "react-native-image-viewing";
import * as mime from 'react-native-mime-types'; import * as mime from 'react-native-mime-types';
import Toast from "react-native-toast-message";
import Text from "./Text"; import Text from "./Text";
@@ -33,26 +36,47 @@ type Props = {
dataFile: { id: string; idStorage: string; name: string; extension: string }[] dataFile: { id: string; idStorage: string; name: string; extension: string }[]
} }
type PropsFile = {
id: string;
idStorage: string;
name: string;
extension: string
}
export default function BorderBottomItem2({ title, subtitle, icon, desc, onPress, onLongPress, rightTopInfo, borderType, leftBottomInfo, rightBottomInfo, titleWeight, bgColor, width, descEllipsize, textColor, colorPress, titleShowAll, dataFile }: Props) { export default function BorderBottomItem2({ title, subtitle, icon, desc, onPress, onLongPress, rightTopInfo, borderType, leftBottomInfo, rightBottomInfo, titleWeight, bgColor, width, descEllipsize, textColor, colorPress, titleShowAll, dataFile }: Props) {
const lebarDim = Dimensions.get("window").width; const lebarDim = Dimensions.get("window").width;
const lebar = width ? lebarDim * width / 100 : 'auto'; const lebar = width ? lebarDim * width / 100 : 'auto';
const textColorFix = textColor ? textColor : 'black'; const textColorFix = textColor ? textColor : 'black';
const [isTap, setIsTap] = useState(false); const [isTap, setIsTap] = useState(false);
const [loadingOpen, setLoadingOpen] = useState(false) const [loadingOpen, setLoadingOpen] = useState(false)
const [chooseFile, setChooseFile] = useState<PropsFile>()
const [preview, setPreview] = useState(false)
function handleChooseFile(item: PropsFile) {
setChooseFile(item)
setPreview(true)
}
const openFile = (item: { idStorage: string; name: string; extension: string }) => { const openFile = async (item: PropsFile) => {
if (Platform.OS == 'android') setLoadingOpen(true)
let remoteUrl = ConstEnv.url_storage + '/files/' + item.idStorage;
const fileName = item.name + '.' + item.extension;
let localPath = `${FileSystem.documentDirectory}/${fileName}`;
const mimeType = mime.lookup(fileName)
FileSystem.downloadAsync(remoteUrl, localPath).then(async ({ uri }) => {
const contentURL = await FileSystem.getContentUriAsync(uri);
setLoadingOpen(false)
try { try {
if (Platform.OS == 'android') { setLoadingOpen(true);
const remoteUrl = ConstEnv.url_storage + '/files/' + item.idStorage;
const fileName = item.name + '.' + item.extension;
const localPath = `${FileSystem.documentDirectory}/${fileName}`;
const mimeType = mime.lookup(fileName);
// Download the file
const downloadResult = await FileSystem.downloadAsync(remoteUrl, localPath);
if (downloadResult.status !== 200) {
throw new Error(`Download failed with status ${downloadResult.status}`);
}
const contentURL = await FileSystem.getContentUriAsync(downloadResult.uri);
try {
if (Platform.OS === 'android') {
await startActivityAsync( await startActivityAsync(
'android.intent.action.VIEW', 'android.intent.action.VIEW',
{ {
@@ -61,19 +85,31 @@ export default function BorderBottomItem2({ title, subtitle, icon, desc, onPress
type: mimeType as string, type: mimeType as string,
} }
); );
} else if (Platform.OS == 'ios') { } else if (Platform.OS === 'ios') {
Sharing.shareAsync(localPath); await Sharing.shareAsync(localPath);
}
} catch (openError) {
console.error('Error opening file:', openError);
Toast.show({
type: 'error',
text1: 'Tidak ada aplikasi yang dapat membuka file ini'
});
} }
} catch (error) { } catch (error) {
Alert.alert('INFO', 'Gagal membuka file, tidak ada aplikasi yang dapat membuka file ini'); console.error('Error downloading or opening file:', error);
} finally { Toast.show({
if (Platform.OS == 'android') setLoadingOpen(false) type: 'error',
} text1: 'Gagal membuka file',
text2: 'Silakan coba lagi nanti'
}); });
} finally {
setLoadingOpen(false);
}
}; };
return ( return (
<>
<Pressable onLongPress={onLongPress} onPress={onPress} <Pressable onLongPress={onLongPress} onPress={onPress}
onPressIn={() => setIsTap(true)} onPressIn={() => setIsTap(true)}
onPressOut={() => setIsTap(false)} onPressOut={() => setIsTap(false)}
@@ -116,10 +152,17 @@ export default function BorderBottomItem2({ title, subtitle, icon, desc, onPress
<Pressable <Pressable
key={index} key={index}
style={[Styles.rowItemsCenter, Styles.borderAll, Styles.round10, Styles.ph05, Styles.pv03, Styles.mr05]} style={[Styles.rowItemsCenter, Styles.borderAll, Styles.round10, Styles.ph05, Styles.pv03, Styles.mr05]}
onPress={() => { openFile({ idStorage: item.idStorage, name: item.name, extension: item.extension }) }} onPress={() => {
isImageFile(item.extension) ?
handleChooseFile(item)
: openFile(item)
}}
> >
<Ionicons name="document-text" size={18} color="grey" style={Styles.mr05} /> <MaterialCommunityIcons
<Text style={[Styles.textInformation, Styles.cGray, Styles.mb05]}>{item.name}.{item.extension}</Text> name={isImageFile(item.extension) ? "file-image-outline" : "file-document-outline"}
size={18}
color="grey" />
<Text style={[Styles.textInformation, Styles.cGray]}>{item.name}.{item.extension}</Text>
</Pressable> </Pressable>
))} ))}
</ScrollView> </ScrollView>
@@ -145,5 +188,45 @@ export default function BorderBottomItem2({ title, subtitle, icon, desc, onPress
) )
} }
</Pressable> </Pressable>
<ImageViewing
images={[{ uri: `${ConstEnv.url_storage}/files/${chooseFile?.idStorage}` }]}
imageIndex={0}
visible={preview}
onRequestClose={() => setPreview(false)}
doubleTapToZoomEnabled
HeaderComponent={({ imageIndex }) => (
<View style={[Styles.headerModalViewImg]}>
{/* CLOSE */}
<Pressable
onPress={() => setPreview(false)}
accessibilityRole="button"
accessibilityLabel="Close image viewer"
>
<Text style={{ color: 'white', fontSize: 26 }}></Text>
</Pressable>
{/* MENU */}
<Pressable
onPress={() => chooseFile && openFile(chooseFile)}
accessibilityRole="button"
accessibilityLabel="Download or share image"
disabled={loadingOpen}
>
<Text style={{ color: loadingOpen ? 'gray' : 'white', fontSize: 22 }}></Text>
</Pressable>
</View>
)}
FooterComponent={({ imageIndex }) => (
<View style={{
paddingBottom: 20,
paddingHorizontal: 16,
alignItems: 'center',
}}>
<Text style={{ color: 'white', fontSize: 16 }}>{chooseFile?.name}.{chooseFile?.extension}</Text>
</View>
)}
/>
</>
) )
} }