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;