Just developing a simple react-native app using expo and react-native-webview library.
The problem is that when users try to download an invoice in pdf format, iOS shows the preview and it's not possible to go back to the app.
Here attached the main app screen component:
import React, { useState } from "react";
import { ActivityIndicator, Share, StyleSheet } from "react-native";
import * as FileSystem from 'expo-file-system';
const { downloadAsync, documentDirectory } = FileSystem;
import { SafeAreaView } from 'react-native-safe-area-context';
import { WebView } from 'react-native-webview';
const HomeScreen = ({ navigation }) => {
const [loading, setLoading] = useState(true);
let downloadDocument = async (downloadUrl) => {
alert('downloadUrl 2: ', downloadUrl);
let fileURI = await downloadAsync(
downloadUrl,
`${documentDirectory}/invoice.pdf`,
{}
);
await onShare(fileURI.uri);
}
const onShare = async (url) => {
try {
return Share.share({
message: 'Select storage location',
url: url
});
} catch (error) {
alert('error: ', error);
return error;
}
};
return (
<SafeAreaView style={styles.container}>
<WebView
source={{ uri: '<url>' }}
onError={() =>
navigation.navigate('Error')
}
setSupportMultipleWindows={false}
startInLoadingState={true}
renderLoading={() =>
<ActivityIndicator
style={styles.spinner}
size='large'
color='#0098D4'
/>
}
domStorageEnabled={true}
// iOS
onFileDownload={({ nativeEvent: { downloadUrl } }) => {
alert('downloadUrl: ', downloadUrl);
downloadDocument(downloadUrl);
}}
/>
</SafeAreaView>
);
}
We added some alerts, but the're never fired.
In the html code, there is an tag with href property pointing to the file's url and the download option set.
Any solution?
Related
Trying to achieve the following scenario
Client clicks a button and it opens the Expo web browser with a URL e.g wwww.example.com/test
User does something and is redirected to a URL like wwww.example.com/success
The app recognizes the URL and auto-closes the web browser
It correctly opens the web browser but nothing happens afterward when I go to wwww.example.com/success.
I'm not getting any errors and with the iOS Expo preview, I get no console.log triggers, but with the Web Expo preview, I get generic logging.
Code below
import React, { useState } from 'react';
import { Button, StyleSheet, Text, View } from 'react-native';
import * as Linking from 'expo-linking';
import * as WebBrowser from 'expo-web-browser';
export const WebVerification = () => {
const [redirectData, setRedirectData] = useState(null);
const _handleRedirect = (event) => {
console.log('handle Request is called')
let data = Linking.parse(event.url);
console.log(data)
if( data.url === "wwww.example.com/success") {
WebBrowser.dismissBrowser();
}
setRedirectData(data);
};
const _openBrowserAsync = async () => {
try {
_addLinkingListener();
let result = await WebBrowser.openBrowserAsync(
`wwww.example.com/test`
);
} catch (error) {
alert(error);
console.log(error);
}
};
const _addLinkingListener = () => {
Linking.addEventListener("url", _handleRedirect);
};
const _removeLinkingListener = () => {
Linking.removeEventListener("url", _handleRedirect);
};
const _maybeRenderRedirectData = () => {
console.log("check RenderRedirect" + redirectData)
if (!redirectData) {
return;
}
return (
<Text style={{ marginTop: 30 }}>
{JSON.stringify(redirectData)}
</Text>
);
};
return (
<View>
<Text>Redirect Example</Text>
<Button onPress={_openBrowserAsync} title="Open Browser" />
{_maybeRenderRedirectData()}
</View>
);
};
Trying to import .ttf for font in expo cli.
I also have splash screen. I wanna show the splash screen until the font loads.
Font: Josefin Sans.
"expo": "~45.0.0"
I took reference from following links but nothing works:
Using Custom Fonts: https://docs.expo.dev/guides/using-custom-fonts/
Splash Screen: https://docs.expo.dev/versions/latest/sdk/splash-screen/
Code (App.js)
import { useState, useEffect, useCallback } from "react";
import { Text } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { StatusBar } from "expo-status-bar";
import Header from "./components/Header.component";
import styles from "./styles/appStyle";
import * as Font from "expo-font";
import * as SplashScreen from "expo-splash-screen";
const App = () => {
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
// Pre-load fonts
await Font.loadAsync({
"JosefinSans-Regular": require("./assets/fonts/JosefinSans-Regular.ttf"),
});
// Artificially delay for two seconds to simulate a slow loading
// experience. Please remove this if you copy and paste the code!
await new Promise((resolve) => setTimeout(resolve, 2000));
} catch (e) {
} finally {
// Tell the application to render
setAppIsReady(true);
}
}
prepare();
}, []);
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
// This tells the splash screen to hide immediately! If we call this after
// `setAppIsReady`, then we may see a blank screen while the app is
// loading its initial state and rendering its first pixels. So instead,
// we hide the splash screen once we know the root view has already
// performed layout.
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;
}
return (
<>
<SafeAreaView style={styles.container} onLayout={onLayoutRootView}>
<Header />
<Text>Hello World!</Text>
<StatusBar style="light" backgroundColor="#05060B" />
</SafeAreaView>
</>
);
};
export default App;
Error
Android Bundling failed 12ms
Unable to resolve module ./assets/fonts/JosefinSans-Regular.ttf from C:\Users\user\Desktop\app\App.js:
None of these files exist:
* JosefinSans-Regular.ttf
* assets\fonts\JosefinSans-Regular.ttf\index(.native|.android.ts|.native.ts|.ts|.android.tsx|.native.tsx|.tsx|.android.js|.native.js|.js|.android.jsx|.native.jsx|.jsx|.android.json|.native.json|.json)
18 | // Pre-load fonts
19 | await Font.loadAsync({
> 20 | "JosefinSans-Regular": require("./assets/fonts/JosefinSans-Regular.ttf"),
| ^
21 | });
22 | // Artificially delay for two seconds to simulate a slow loading
23 | // experience. Please remove this if you copy and paste the code!
File Structure:
Snap.png
Answer
expo install #expo-google-fonts/josefin-sans expo-font
And the code looks like this.
import { useState, useEffect, useCallback } from "react";
import { Text } from "react-native";
import { SafeAreaView } from "react-native-safe-area-context";
import { StatusBar } from "expo-status-bar";
import Header from "./components/Header.component";
import styles from "./styles/appStyle";
import * as Font from "expo-font";
import * as SplashScreen from "expo-splash-screen";
import {
useFonts,
JosefinSans_100Thin,
JosefinSans_200ExtraLight,
JosefinSans_300Light,
JosefinSans_400Regular,
JosefinSans_500Medium,
JosefinSans_600SemiBold,
JosefinSans_700Bold,
JosefinSans_100Thin_Italic,
JosefinSans_200ExtraLight_Italic,
JosefinSans_300Light_Italic,
JosefinSans_400Regular_Italic,
JosefinSans_500Medium_Italic,
JosefinSans_600SemiBold_Italic,
JosefinSans_700Bold_Italic,
} from "#expo-google-fonts/josefin-sans";
const App = () => {
const [appIsReady, setAppIsReady] = useState(false);
let [fontsLoaded] = useFonts({
JosefinSans_100Thin,
JosefinSans_200ExtraLight,
JosefinSans_300Light,
JosefinSans_400Regular,
JosefinSans_500Medium,
JosefinSans_600SemiBold,
JosefinSans_700Bold,
JosefinSans_100Thin_Italic,
JosefinSans_200ExtraLight_Italic,
JosefinSans_300Light_Italic,
JosefinSans_400Regular_Italic,
JosefinSans_500Medium_Italic,
JosefinSans_600SemiBold_Italic,
JosefinSans_700Bold_Italic,
});
const prepare = async () => {
try {
// Pre-load fonts
await Font.loadAsync(fontsLoaded)
.then(() => {
setAppIsReady(true);
})
.catch((err) => {});
// Artificially delay for two seconds to simulate a slow loading
// experience. Please remove this if you copy and paste the code!
// await new Promise((resolve) => setTimeout(resolve, 2000));
} catch (e) {}
};
useEffect(() => {
prepare();
}, []);
const onLayoutRootView = useCallback(async () => {
if (appIsReady) {
// This tells the splash screen to hide immediately! If we call this after
// `setAppIsReady`, then we may see a blank screen while the app is
// loading its initial state and rendering its first pixels. So instead,
// we hide the splash screen once we know the root view has already
// performed layout.
await SplashScreen.hideAsync();
}
}, [appIsReady]);
if (!appIsReady) {
return null;
}
return (
<>
<SafeAreaView style={styles.container} onLayout={onLayoutRootView}>
<Header />
<Text>Hello World!</Text>
<StatusBar style="light" backgroundColor="#05060B" />
</SafeAreaView>
</>
);
};
export default App;
I installed the react-navigation package in react-native
I have implemented tab navigation and one of them is implemented in webview format.
My problem is that if I press the back physical button on Android, I go from the app itself to the previous tab, not back from the webview.
I've already applied the back button for the webview on the internet, but I have not done that.
I tried to display the onNavigationStateChange log when debugging, but it was not updated when url was moved after it was loaded at first startup. Here is the code I implemented:
import React from "react";
import {BackHandler} from "react-native";
import {WebView} from "react-native-webview";
class SermonScreen extends React.Component {
constructor(props) {
super(props);
}
static navigationOptions = {
header: null
};
componentDidMount() {
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
_onNavigationStateChange(navState) {
console.log(navState);
this.setState({
canGoBack: navState.canGoBack
});
}
handleBackButton = () => {
console.log(this.state);
if (this.state.canGoBack === true) {
this.webView.goBack();
return true;
} else {
return false;
}
};
render() {
return (
<WebView
source={{uri: 'https://m.youtube.com/channel/UCw3kP3qCCF7ZpLUNzm_Q9Xw/videos' }}
ref={(webView) => this.webView = webView}
onNavigationStateChange={this._onNavigationStateChange.bind(this)}
/>
);
}
}
export default SermonScreen;
Following the official webview documnentation you could try to do this: https://github.com/react-native-community/react-native-webview/blob/master/docs/Guide.md#intercepting-hash-url-changes
In general you were almost there, however the way the YT navigation works made it impossible to be caught via the onNavigationStateChange, that's why we inject a JS code that intercepts these hash changes and posts a message to the parent component, we then catch it inside the onMessage handler and set the state variable properly. Copying the injectedJavaScript and onMessage properties to your example should solve your problem.
I prepared a component for you that seems to do what is needed:
* Sample React Native App
* https://github.com/facebook/react-native
*
* #format
* #flow
*/
import React, { Fragment } from "react";
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
BackHandler,
StatusBar
} from "react-native";
import { WebView } from "react-native-webview";
import {
Header,
LearnMoreLinks,
Colors,
DebugInstructions,
ReloadInstructions
} from "react-native/Libraries/NewAppScreen";
class App extends React.Component {
constructor(props) {
super(props);
this.startingUrl =
"https://m.youtube.com/channel/UCw3kP3qCCF7ZpLUNzm_Q9Xw/videos";
this.handleBackButton = this.handleBackButton.bind(this);
}
componentDidMount() {
BackHandler.addEventListener("hardwareBackPress", this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener("hardwareBackPress", this.handleBackButton);
}
handleBackButton = () => {
console.log(this.state);
const { canGoBack } = this.state;
if (canGoBack) {
this.webView.goBack();
return true;
} else {
return false;
}
};
render() {
return (
<Fragment>
<WebView
source={{ uri: this.startingUrl }}
style={{ marginTop: 20 }}
ref={webView => (this.webView = webView)}
injectedJavaScript={`
(function() {
function wrap(fn) {
return function wrapper() {
var res = fn.apply(this, arguments);
window.ReactNativeWebView.postMessage('navigationStateChange');
return res;
}
}
history.pushState = wrap(history.pushState);
history.replaceState = wrap(history.replaceState);
window.addEventListener('popstate', function() {
window.ReactNativeWebView.postMessage('navigationStateChange');
});
})();
true;
`}
onMessage={({ nativeEvent: state }) => {
if (state.data === "navigationStateChange") {
// Navigation state updated, can check state.canGoBack, etc.
this.setState({
canGoBack: state.canGoBack
});
}
}}
/>
</Fragment>
);
}
}
export default App;
The response above was perfect. I set the state true for canGoBack though; I was getting a null error, so:
constructor(props) {
super(props);
this.startingUrl = "https://app.vethorcardpag.com.br/GIF/login/0/";
this.state = {
canGoBack : true
}
this.handleBackButton = this.handleBackButton.bind(this);
}
Here is a simple solution using the magic of React's State.
Hope this helps.
import React, { useRef, useState } from 'react'
export default function Component () {
// This is used to save the reference of your webview, so you can control it
const webViewRef = useRef(null);
// This state saves whether your WebView can go back
const [webViewcanGoBack, setWebViewcanGoBack] = useState(false);
const goBack = () => {
// Getting the webview reference
const webView = webViewRef.current
if (webViewcanGoBack)
// Do stuff here if your webview can go back
else
// Do stuff here if your webview can't go back
}
return (
<WebView
source={{ uri: `Your URL` }}
ref={webViewRef}
javaScriptEnabled={true}
onLoadProgress={({ nativeEvent }) => {
// This function is called everytime your web view loads a page
// and here we change the state of can go back
setWebViewcanGoBack(nativeEvent.canGoBack)
}}
/>
)
}
Original answer
https://stackoverflow.com/a/74500469/7823800
I am developing an app where I take photos using react-native-image-picker and upload those to the AWS server. Once after uploading those I want to encrypt those taken images in the Android device i.e in saved folder. Is it possible to do? If yes, How can I do that using react-native?
My code is,
I am able to capture images and uploading to the AWS s3.
import React, { Component } from "react";
import {
Platform,
StyleSheet,
Alert,
Text,
TouchableOpacity,
View,
Picker,
Animated,
Easing,
Image
} from "react-native";
import ImagePicker from "react-native-image-picker";
import { RNS3 } from "react-native-aws3";
export default class SecondScreen extends Component<Props> {
constructor(props) {
super(props);
this.state = {
file: "",
saveImages: []
};
}
takePic() {
ImagePicker.launchCamera({}, responce => {
const file = {
uri: responce.uri,
name: responce.fileName,
method: "POST",
path: responce.path,
type: responce.type,
notification: {
enabled: true
}
};
this.state.saveImages.push(file);
});
}
_upload = saveImages => {
const config = {
keyPrefix: "uploads/",
bucket: "s3merahkee",
region: "us-east-2",
accessKey: "***",
secretKey: "***",
successActionStatus: 201
};
this.state.saveImages.map(image => {
RNS3.put(image, config).then(responce => {
console.log(saveImages);
});
});
};
render() {
return (
<View style={styles.container}>
<View style={styles.Camera}>
<TouchableOpacity onPress={this.takePic.bind(this)}>
<Text>Take Picture</Text>
</TouchableOpacity>
</View>
<View style={styles.Send}>
<TouchableOpacity onPress={() => this._upload()}>
<Text>Send</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
I am using two methods here, one the for capturing images and once the user clicks on send it to upload the file to AWS s3.
I hope I can encrypt the image If possible tell me how can I implement it. Or if not possible suggest me the other way that I can do it. (EX: deleting, etc..)
I have done this using converting image to base64. Using react-native-fs library, I am able to achieve this. Here, Once I captured the images I convert them to a base64 string and delete the real image from the folder.
My code is,
takePic = () => {
// create a path you want to write to
var pictureFolder = RNFetchBlob.fs.dirs.SDCardDir+'/Schoolapp';
ImagePicker.launchCamera(options,(responce)=>{
const file ={
uri : responce.uri,
name : responce.fileName,
method: 'POST',
path : responce.path,
type : responce.type,
notification: {
enabled: true
}
}
//convert image into base64
const base64 = RNFS.writeFile(responce.uri, responce.data);
return base64;
//delete the original image
RNFS.unlink(responce.path)
.then(() => {
console.log('deleted');
RNFS.scanFile(responce.path)
.then(() => {
console.log('scanned');
})
.catch(err => {
console.log(err);
});
})
.catch((err) => {
console.log(err);
})
});
}
I want to share screenshot of particular View Component instead of whole screen.
Any one help me out with this.
Take a look a picture. Want screenshot of Red mark area which is within View Component.
You can use library named react-native-view-shot
You just have to give wrap your View inside ViewShot, take a reference of that and call capture()
Here is example of code taken from that library
import ViewShot from "react-native-view-shot";
class ExampleCaptureOnMountManually extends Component {
componentDidMount () {
this.refs.viewShot.capture().then(uri => {
console.log("do something with ", uri);
});
}
render() {
return (
<ViewShot ref="viewShot" options={{ format: "jpg", quality: 0.9 }}>
<Text>...Something to rasterize...</Text>
</ViewShot>
);
}
}
Here is a working example example of code using react-native-view-shot with hooks
import React, { useState, useRef, useEffect } from "react";
import { View, Image, ScrollView, TouchableOpacity } from "react-native";
import ViewShot from "react-native-view-shot";
var RNFS = require("react-native-fs");
import Share from "react-native-share";
const TransactionReceipt = () => {
const viewShotRef = useRef(null);
const [isSharingView, setSharingView] = useState(false);
useEffect(() => {
if (isSharingView) {
const shareScreenshot = async () => {
try {
const uri = await viewShotRef.current.capture();
const res = await RNFS.readFile(uri, "base64");
const urlString = `data:image/jpeg;base64,${res}`;
const info = '...';
const filename = '...';
const options = {
title: info,
message: info,
url: urlString,
type: "image/jpeg",
filename: filename,
subject: info,
};
await Share.open(options);
setSharingView(false);
} catch (error) {
setSharingView(false);
console.log("shareScreenshot error:", error);
}
};
shareScreenshot();
}
}, [isSharingView]);
return (
<ViewShot ref={viewShotRef} options={{ format: "jpg", quality: 0.9 }}>
<View>
{!isSharingView && (
<TouchableOpacity onPress={() => setSharingView(true)}>
<Image source={Images.shareIcon} />
</TouchableOpacity>
)}
<ScrollView />
</View>
</ViewShot>);
}