From 39f63d12aaf8227aae0a11f65dc5c82482fe7a2e Mon Sep 17 00:00:00 2001 From: Nurmuhammet Allanov Date: Thu, 11 Sep 2025 16:45:30 +0500 Subject: [PATCH] wip --- app.json | 33 +---- package-lock.json | 123 +++++++++++++++++- package.json | 7 +- src/navigation/MenuNavigator.js | 6 + .../Card/CardTransactionOrderDetailsScreen.js | 2 +- src/screens/Shared/PdfViewerScreen.js | 103 +++++++++++++++ 6 files changed, 242 insertions(+), 32 deletions(-) create mode 100644 src/screens/Shared/PdfViewerScreen.js diff --git a/app.json b/app.json index fdbbfe0..9b3673d 100644 --- a/app.json +++ b/app.json @@ -1,55 +1,30 @@ { "expo": { - "name": "TBBANK ONLINE", - "slug": "tbbank-online", + "name": "temp-expo", + "slug": "temp-expo", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", "userInterfaceStyle": "light", - "newArchEnabled": true, "splash": { "image": "./assets/splash-icon.png", "resizeMode": "contain", "backgroundColor": "#ffffff" }, "ios": { - "supportsTablet": true, - "statusBar": { - "barStyle": "dark-content" - }, - "bundleIdentifier": "com.nurmuhammet.ali.tbbankonline" + "supportsTablet": true }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", - "backgroundColor": "#17b69b" - }, - "edgeToEdgeEnabled": true, - "package": "com.nurmuhammet.ali.tbbankonline", - "statusBar": { - "barStyle": "dark-content", "backgroundColor": "#ffffff" } }, "web": { "favicon": "./assets/favicon.png" }, - "extra": { - "eas": { - "projectId": "280bed78-9335-4b73-a686-15a9f726a7ad" - } - }, "plugins": [ - "expo-font", - [ - "expo-splash-screen", - { - "backgroundColor": "#ffffff", - "image": "./assets/splash-icon.png", - "imageWidth": 200 - } - ], - "expo-system-ui" + "@config-plugins/react-native-pdf" ] } } diff --git a/package-lock.json b/package-lock.json index 758cfb8..3f6a48d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "temp-expo", "version": "1.0.0", "dependencies": { + "@config-plugins/react-native-pdf": "^11.0.0", "@expo/vector-icons": "^14.1.0", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/datetimepicker": "8.4.1", @@ -16,8 +17,10 @@ "@react-navigation/native": "^7.1.14", "@react-navigation/stack": "^7.4.2", "expo": "53.0.22", + "expo-file-system": "~18.1.11", "expo-font": "~13.3.2", "expo-image-picker": "~16.1.4", + "expo-media-library": "~17.1.7", "expo-splash-screen": "^31.0.8", "expo-status-bar": "~2.2.3", "expo-system-ui": "~5.0.11", @@ -26,10 +29,12 @@ "react-native-confirmation-code-field": "^8.0.1", "react-native-gesture-handler": "~2.24.0", "react-native-modal-datetime-picker": "^15.0.1", + "react-native-pdf": "^6.7.7", "react-native-safe-area-context": "5.4.0", "react-native-screens": "^4.11.1", "react-native-svg": "15.11.2", - "react-native-webview": "13.13.5" + "react-native-webview": "13.13.5", + "rn-fetch-blob": "^0.12.0" }, "devDependencies": { "@babel/core": "^7.20.0" @@ -1407,6 +1412,14 @@ "node": ">=6.9.0" } }, + "node_modules/@config-plugins/react-native-pdf": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@config-plugins/react-native-pdf/-/react-native-pdf-11.0.0.tgz", + "integrity": "sha512-zpGsZ2wMly37WgIJ0CVWOZHmdtM98j33VUeHAFQMM9hzxANZsG/BmoPLp0AOqIENmucxif//pESUgF8aMiiqmA==", + "peerDependencies": { + "expo": "^53" + } + }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", "resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz", @@ -2569,6 +2582,11 @@ "node": ">=18" } }, + "node_modules/@react-native/normalize-color": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.1.0.tgz", + "integrity": "sha512-Z1jQI2NpdFJCVgpY+8Dq/Bt3d+YUi1928Q+/CZm/oh66fzM0RUl54vvuXlPJKybH4pdCZey1eDTPaLHkMPNgWA==" + }, "node_modules/@react-native/normalize-colors": { "version": "0.79.5", "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.79.5.tgz", @@ -3151,6 +3169,11 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base-64": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz", + "integrity": "sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3709,6 +3732,11 @@ "node": ">= 8" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -3830,6 +3858,16 @@ "node": ">= 0.8" } }, + "node_modules/deprecated-react-native-prop-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/deprecated-react-native-prop-types/-/deprecated-react-native-prop-types-2.3.0.tgz", + "integrity": "sha512-pWD0voFtNYxrVqvBMYf5gq3NA2GCpfodS1yNynTPc93AYA/KEMGeWDqqeUB6R2Z9ZofVhks2aeJXiuQqKNpesA==", + "dependencies": { + "@react-native/normalize-color": "*", + "invariant": "*", + "prop-types": "*" + } + }, "node_modules/destroy": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", @@ -4169,6 +4207,15 @@ "react": "*" } }, + "node_modules/expo-media-library": { + "version": "17.1.7", + "resolved": "https://registry.npmjs.org/expo-media-library/-/expo-media-library-17.1.7.tgz", + "integrity": "sha512-hLCoMvlhjtt+iYxPe71P1F6t06mYGysuNOfjQzDbbf64PCkglCZJYmywPyUSV1V5Hu9DhRj//gEg+Ki+7VWXog==", + "peerDependencies": { + "expo": "*", + "react-native": "*" + } + }, "node_modules/expo-modules-autolinking": { "version": "2.1.14", "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-2.1.14.tgz", @@ -6734,6 +6781,20 @@ } } }, + "node_modules/react-native-blob-util": { + "version": "0.22.2", + "resolved": "https://registry.npmjs.org/react-native-blob-util/-/react-native-blob-util-0.22.2.tgz", + "integrity": "sha512-Czx01QMg7aLsm/4F/7+eqoRAi1q/qjLY2Kao16g+n2SRnTH1+qkD8Qhx2q9okB+VNQvZKB1LbiXhktzYQV52xQ==", + "peer": true, + "dependencies": { + "base-64": "0.1.0", + "glob": "^10.3.10" + }, + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-confirmation-code-field": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/react-native-confirmation-code-field/-/react-native-confirmation-code-field-8.0.1.tgz", @@ -6795,6 +6856,20 @@ "react-native": ">=0.65.0" } }, + "node_modules/react-native-pdf": { + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.7.tgz", + "integrity": "sha512-D0ga/eyPsVWSPEBm622sGVZLl3gibxPmfm2cxsLcUrZ4WDSGR5HyGmvvWaR/m9wXEyIbD4J6q9qzuG6yObcSXw==", + "dependencies": { + "crypto-js": "4.2.0", + "deprecated-react-native-prop-types": "^2.3.0" + }, + "peerDependencies": { + "react": "*", + "react-native": "*", + "react-native-blob-util": ">=0.13.7" + } + }, "node_modules/react-native-safe-area-context": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.4.0.tgz", @@ -7166,6 +7241,52 @@ "node": "*" } }, + "node_modules/rn-fetch-blob": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/rn-fetch-blob/-/rn-fetch-blob-0.12.0.tgz", + "integrity": "sha512-+QnR7AsJ14zqpVVUbzbtAjq0iI8c9tCg49tIoKO2ezjzRunN7YL6zFSFSWZm6d+mE/l9r+OeDM3jmb2tBb2WbA==", + "dependencies": { + "base-64": "0.1.0", + "glob": "7.0.6" + } + }, + "node_modules/rn-fetch-blob/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rn-fetch-blob/node_modules/glob": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", + "integrity": "sha512-f8c0rE8JiCxpa52kWPAOa3ZaYEnzofDzCQLCn3Vdk0Z5OVLq3BsRFJI4S4ykpeVW6QMGBUkMeUpoEgWnMTnw5Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.2", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/rn-fetch-blob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", diff --git a/package.json b/package.json index 9ecf69b..0f6cf52 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "web": "expo start --web" }, "dependencies": { + "@config-plugins/react-native-pdf": "^11.0.0", "@expo/vector-icons": "^14.1.0", "@react-native-async-storage/async-storage": "2.1.2", "@react-native-community/datetimepicker": "8.4.1", @@ -17,8 +18,10 @@ "@react-navigation/native": "^7.1.14", "@react-navigation/stack": "^7.4.2", "expo": "53.0.22", + "expo-file-system": "~18.1.11", "expo-font": "~13.3.2", "expo-image-picker": "~16.1.4", + "expo-media-library": "~17.1.7", "expo-splash-screen": "^31.0.8", "expo-status-bar": "~2.2.3", "expo-system-ui": "~5.0.11", @@ -27,10 +30,12 @@ "react-native-confirmation-code-field": "^8.0.1", "react-native-gesture-handler": "~2.24.0", "react-native-modal-datetime-picker": "^15.0.1", + "react-native-pdf": "^6.7.7", "react-native-safe-area-context": "5.4.0", "react-native-screens": "^4.11.1", "react-native-svg": "15.11.2", - "react-native-webview": "13.13.5" + "react-native-webview": "13.13.5", + "rn-fetch-blob": "^0.12.0" }, "devDependencies": { "@babel/core": "^7.20.0" diff --git a/src/navigation/MenuNavigator.js b/src/navigation/MenuNavigator.js index 69b40b6..1cb5221 100644 --- a/src/navigation/MenuNavigator.js +++ b/src/navigation/MenuNavigator.js @@ -25,6 +25,7 @@ import CardPinOrderDetailsScreen from '../screens/Card/CardPinOrderDetailsScreen import CardOrdersScreen from '../screens/Card/CardOrdersScreen'; import CreateCardOrderScreen from '../screens/Card/CreateCardOrderScreen'; import CardOrderDetailsScreen from '../screens/Card/CardOrderDetailsScreen'; +import PdfViewerScreen from '../screens/Shared/PdfViewerScreen'; const Stack = createStackNavigator(); @@ -54,6 +55,11 @@ const MenuNavigator = () => ( + ); diff --git a/src/screens/Card/CardTransactionOrderDetailsScreen.js b/src/screens/Card/CardTransactionOrderDetailsScreen.js index 5623052..545d222 100644 --- a/src/screens/Card/CardTransactionOrderDetailsScreen.js +++ b/src/screens/Card/CardTransactionOrderDetailsScreen.js @@ -75,7 +75,7 @@ const CardTransactionOrderDetailsScreen = () => { const res = await apiService.downloadCardTransactions(orderId, startDate, endDate); setDownloading(false); if (res.success && res.data?.url) { - Linking.openURL(res.data.url); + navigation.navigate('PdfViewer', { url: res.data.url, title: 'Card Transactions' }); setModalVisible(false); } else { Alert.alert('Ýalňyşlyk', res.data.message || 'Näsazlyk ýüze çykdy'); diff --git a/src/screens/Shared/PdfViewerScreen.js b/src/screens/Shared/PdfViewerScreen.js new file mode 100644 index 0000000..ea9533b --- /dev/null +++ b/src/screens/Shared/PdfViewerScreen.js @@ -0,0 +1,103 @@ +import React, { useLayoutEffect, useState } from 'react'; +import { View, TouchableOpacity, Text, StyleSheet, ActivityIndicator, Alert, Dimensions } from 'react-native'; +import Pdf from 'react-native-pdf'; +import { useNavigation, useRoute } from '@react-navigation/native'; +import { Ionicons } from '@expo/vector-icons'; +import * as MediaLibrary from 'expo-media-library'; +import { COLORS } from '../../constants/colors'; + +const PdfViewerScreen = () => { + const navigation = useNavigation(); + const route = useRoute(); + const { url: pdfUrl, title = 'Document' } = route.params; + + const [saving, setSaving] = useState(false); + const [cachedPdfPath, setCachedPdfPath] = useState(null); + + const source = { uri: pdfUrl, cache: true }; + + const handleSavePdf = async () => { + if (!cachedPdfPath) { + Alert.alert('Error', 'PDF is not loaded yet. Please wait.'); + return; + } + + setSaving(true); + try { + const { status } = await MediaLibrary.requestPermissionsAsync(); + if (status !== 'granted') { + Alert.alert('Permission required', 'We need permission to save files to your device.'); + setSaving(false); + return; + } + + const asset = await MediaLibrary.createAssetAsync(cachedPdfPath); + const album = await MediaLibrary.getAlbumAsync('Downloads'); + if (album == null) { + await MediaLibrary.createAlbumAsync('Downloads', asset, false); + } else { + await MediaLibrary.addAssetsToAlbumAsync([asset], album, false); + } + Alert.alert('Saved!', 'PDF has been saved to your Downloads folder.'); + } catch (error) { + console.error(error); + Alert.alert('Error', 'Could not save the PDF.'); + } finally { + setSaving(false); + } + }; + + useLayoutEffect(() => { + navigation.setOptions({ + title: title, + headerRight: () => ( + saving ? ( + + ) : ( + + + + ) + ), + }); + }, [navigation, saving, pdfUrl, title, cachedPdfPath]); + + return ( + + { + setCachedPdfPath(filePath); + }} + onPageChanged={(page, numberOfPages) => { + // console.log(`Current page: ${page}`); + }} + onError={(error) => { + console.log(error); + Alert.alert('Error', 'Could not load the document.'); + }} + onPressLink={(uri) => { + // console.log(`Link pressed: ${uri}`); + }} + style={styles.pdf} + /> + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'flex-start', + alignItems: 'center', + backgroundColor: '#f5f5f5', + }, + pdf: { + flex: 1, + width: Dimensions.get('window').width, + height: Dimensions.get('window').height, + }, +}); + +export default PdfViewerScreen;