I am developping a Nativscript-Vue mobile app, and I need to be able to select a file from the image gallery and upload it to an online file manager (I use 000webhost.com).
I worked with a code sample I got from the NativeScript website, and I am able to get the picture without problems, but I can't manage to upload it to WebHost (the logs says that the upload is successful, and there's no error showing up, but my pictures folder remains empty for some reason).
Here's my code :
<template>
<Page class="page">
<ActionBar title="imagepicker + background-http" class="action-bar"></ActionBar>
<GridLayout rows="*, auto">
<GridLayout v-if="!showWelcome" rows="auto auto auto, *">
<Progress :value="event && event.eventData ? event.eventData.currentBytes : 0"
:maxValue="event && event.eventData ? event.eventData.totalBytes : 0">
</Progress>
<Label row="1" v-if="event && event.eventData && event.eventData.currentBytes !== NaN"
class="m-10 text" :text="'Transferred: ' + event.eventData.currentBytes / 1000 + ' KB'"></Label>
<Label row="2" class="m-10 text" text="Events"></Label>
<ListView row="3" for="item in eventLog">
<v-template>
<StackLayout class="t-12">
<Label :text="item.eventTitle" textWrap="true"></Label>
<Label v-if="item.eventData && item.eventData.error"
:text="item.eventData ? 'Error: ' + item.eventData.error : ''"
textWrap="true"></Label>
<Label v-if="item.eventData && item.eventData.body"
:text="item.eventData ? 'Body: ' + item.eventData.body : ''"
textWrap="true"></Label>
<Label v-if="item.eventData && item.eventData.raw"
:text="item.eventData ? 'Raw: ' + item.eventData.raw : ''"
textWrap="true"></Label>
</StackLayout>
</v-template>
</ListView>
</GridLayout>
<StackLayout v-if="showWelcome" verticalAlignment="middle">
<Label class="m-10 nativescript-label text" text="{N}"></Label>
<Label class="m-10 text" v-if="showWelcome" text="This sample app shows how to pick an image with"
textWrap="true"></Label>
<Label class="m-10 text plugin" text="nativescript-imagepicker"></Label>
<Label class="m-10 text" v-if="showWelcome" text="and upload it using"
textWrap="true"></Label>
<Label class="m-10 text plugin" text="nativescript-background-http"></Label>
</StackLayout>
<Button class="m-b-10 m-t-10 t-20" row="1" text="Choose image to upload" #tap="onSelectImageTap($event)"></Button>
</GridLayout>
</Page>
</template>
<script>
import VueRx from "../vue-rx";
import Vue from "nativescript-vue";
const app = require("tns-core-modules/application");
const platform = require("platform");
const fs = require("file-system");
const imagePicker = require("nativescript-imagepicker");
const rxjs = require("rxjs");
const operators = require("rxjs/operators");
const bgHttp = require("nativescript-background-http");
Vue.use(VueRx);
// Vue.config.silent = false; // uncomment for debugging purposes
export default {
data() {
return {
showWelcome: true,
session: bgHttp.session("image-upload"),
currentFileNameBeingUploaded: ""
};
},
subscriptions() {
this.event$ = new rxjs.BehaviorSubject({});
return {
event: this.event$,
eventLog: this.event$.pipe(
operators.sampleTime(200),
operators.concatMap(value => rxjs.of(value)),
operators.scan((acc, logEntry) => {
acc.push(logEntry);
return acc;
}, []),
// emit only logs for the this.currentFileNameBeingUploaded
operators.map(allLogs => allLogs.filter(logEntry => !!logEntry && logEntry.eventTitle && logEntry.eventTitle.indexOf(this.currentFileNameBeingUploaded) > 0))
)
};
},
methods: {
onSelectImageTap() {
let context = imagePicker.create({
mode: "single"
});
this.startSelection(context);
},
startSelection(context) {
context
.authorize()
.then(() => {
return context.present();
})
.then(selection => {
this.showWelcome = false;
let imageAsset = selection.length > 0 ? selection[
0] : null;
if (imageAsset) {
this.getImageFilePath(imageAsset).then(path => {
console.log(`path: ${path}`);
this.uploadImage(path);
});
}
})
.catch(function(e) {
console.log(e);
});
},
uploadImage(path) {
let file = fs.File.fromPath(path);
this.currentFileNameBeingUploaded = file.path.substr(
file.path.lastIndexOf("/") + 1
);
let request = this.createNewRequest();
request.description = "uploading image " + file.path;
request.headers["File-Name"] = this.currentFileNameBeingUploaded;
// -----> multipart upload
// var params = [{
// name: "test",
// value: "value"
// },
// {
// name: "fileToUpload",
// filename: file.path,
// mimeType: "image/jpeg"
// }
// ];
// var task = this.session.multipartUpload(params, request);
// <----- multipart upload
let task = this.session.uploadFile(file.path, request);
task.on("progress", this.onEvent.bind(this));
task.on("error", this.onEvent.bind(this));
task.on("responded", this.onEvent.bind(this));
task.on("complete", this.onEvent.bind(this));
},
createNewRequest() {
let url;
// NOTE: using https://httpbin.org/post for testing purposes,
// you'll need to use your own service in real-world app
if (platform.isIOS) {
url = "https://ipaccovoiturage.000webhostapp.com/pictures";
} else {
url = "https://ipaccovoiturage.000webhostapp.com/pictures";
}
let request = {
url: url,
method: "POST",
headers: {
"Content-Type": "application/octet-stream"
},
description: "uploading file...",
androidAutoDeleteAfterUpload: false,
androidNotificationTitle: "NativeScript HTTP background"
};
return request;
},
getImageFilePath(imageAsset) {
return new Promise(resolve => {
if (platform.isIOS) {
const options = PHImageRequestOptions.new();
options.synchronous = true;
options.version =
PHImageRequestOptionsVersion.Current;
options.deliveryMode =
PHImageRequestOptionsDeliveryMode.HighQualityFormat;
PHImageManager.defaultManager().requestImageDataForAssetOptionsResultHandler(
imageAsset.ios,
options,
nsData => {
// create file from image asset and return its path
const tempFolderPath = fs.knownFolders
.temp()
.getFolder("nsimagepicker").path;
const tempFilePath = fs.path.join(
tempFolderPath,
Date.now() + ".jpg"
);
nsData.writeToFileAtomically(
tempFilePath, true);
resolve(tempFilePath);
}
);
} else {
// return imageAsset.android, since it 's the path of the file
resolve(imageAsset.android);
}
});
},
onEvent(e) {
let eventEntry = {
eventTitle: e.eventName + " " + e.object.description,
eventData: {
error: e.error ? e.error.toString() : e.error,
currentBytes: e.currentBytes,
totalBytes: e.totalBytes,
body: e.data
// raw: JSON.stringify(e) // uncomment for debugging purposes
}
};
this.event$.next(eventEntry);
}
}
};
</script>
<style scoped>
.home-panel {
vertical-align: center;
font-size: 20;
margin: 15;
}
.description-label {
margin-bottom: 15;
}
.text {
text-align:
center;
font-size: 18px;
}
.plugin {
font-weight: 600;
font-size: 23px;
}
.nativescript-label {
font-size: 60px;
background-color: #3d5afe;
font-weight: 600;
color: white;
border-radius: 20px/20px;
width: 230px;
height: 230px;
}
</style>
I thought maybe WebHost was the problem, but I've been able to insert photos with PHP and HTML (unfortunately NativeScript-Vue doesn't allow HTML).
I had a similar issue in plain javascript. I was working on it for a couple of days. #Manoj answers didn't work for me. I had to use multipartUpload since I am sending an additional parameter.
Make sure to match the name parameter (fileToUpload) in the request to the name expected by your service. "file" in my case.
The service API signature looks like:
public async Task<IActionResult> Upload(string listingId, IFormFile file)
My request:
const request = {
url: "yoururl",
method: "POST",
headers: {
"Content-Type": "multipart/form-data",
"Authorization": token
},
description: `Uploading image ${name}`,
androidAutoDeleteAfterUpload: true,
androidNotificationTitle: "HTTP background"
};
const params = [
{
name: "listingId",
value: 2
},
{
name: "file", // Note it is not fileToUpload but file instead.
filename: path,
mimeType: "image/jpeg"
}
];
const task = session.multipartUpload(params, request);
I hope it helps. By the way both Content-Type multipart/form-data and application/octet-stream work for me, but I use multipart/form-data for consistency
Related
// store.js
import { createStore, combineReducers } from "redux";
const INITIAL_STATE = [];
const postingReducer = (state, action) => {
if (action.type === "POST_SUCCESS") {
const newPost = {
title: action.payload.title,
content: action.payload.content,
};
return state.concat(newPost);
} else if (action.type === "POST_DELETE") {
return state.filter((item) => {
return item.title !== action.payload;
});
} else if (action.type === "POST_EDIT_SUCCESS") {
const modifiedPost = {
title: action.payload.title,
content: action.payload.content,
};
// console.log(modifiedPost)
// console.log(state.map((item,index)=>item[0]))
// console.log(state[0].title)
// state[0].title = modifiedPost.title
// state[0].content = modifiedPost.title
return state.concat(modifiedPost);
}
return INITIAL_STATE;
};
const store = createStore(
combineReducers({
posting: postingReducer,
})
);
export default store;
// EditForm.js
import { Link , useParams} from "react-router-dom";
import { useState } from "react";
import { useDispatch , useSelector } from "react-redux";
const EditForm = () => {
const dispatch = useDispatch();
const state = useSelector(state=>state.posting)
const params = useParams()
const [titleInput, setTitleInput] = useState(state[params.title].title);
const [contentInput, setContentInput] = useState(state[params.title].content);
const publishBtn = () => {
window.alert('Modified.')
return dispatch({
type: "POST_EDIT_SUCCESS",
payload: { title: titleInput, content: contentInput },
});
}
return (
<>
<form>
<div>
<label htmlFor="Title">Title</label>
<br />
<input
id="Title"
value={titleInput}
onChange={event=>{setTitleInput(event.target.value)}}
type="text"
/>
<br />
</div>
<br />
<div>
<label htmlFor="Content">Content</label>
<br />
<textarea
id="Content"
value={contentInput}
onChange={event=>{setContentInput(event.target.value)}}
type="text"
/>
<br />
</div>
<div>
<Link to="/">
<button onClick={publishBtn}>Modify</button>
</Link>
</div>
</form>
</>
);
};
export default EditForm
I want to get my modified post, net ADD modified post on list.
when i click Modify button in 'EditForm.js' I want to get my modified post, net ADD modified post on list.
in this situation, modified post added on the postlist.
I don't know how to fix "POST_EDIT_SUCCESS" return in 'store.js'
please help me!
Voice recording comes empty in safari iphone always, my code is as below for my common component, its working in safari on mac and chrome easily
import React, { useEffect, useRef, useState } from "react";
import { useRouter } from "next/router";
import PropTypes from "prop-types";
import { useReactMediaRecorder } from "react-media-recorder";
import WaveSurfer from "wavesurfer.js";
import MicrophonePlugin from "wavesurfer.js/dist/plugin/wavesurfer.microphone";
import Button from "#mui/material/Button";
import Grid from "#mui/material/Grid";
import Typography from "#mui/material/Typography";
import StyledButton from "../StyledButton";
import Fab from "#mui/material/Fab";
// Icons
import MicIcon from "#mui/icons-material/Mic";
import StopIcon from "#mui/icons-material/Stop";
import DeleteIcon from "#mui/icons-material/Delete";
import Waveform from "./Waveform";
import styled from "#emotion/styled";
const FabAudio = styled(Fab)`
background-color: #fd0d1b;
color: #fff;
&:hover {
background-color: #fd0d1b;
color: #fff;
}
`;
const Small = styled("div")`
font-size: 10px;
`;
function VoiceInput({
title,
required,
id,
totalQuestions,
primaryButtonTitle,
secondaryButtonTitle,
nextRoute,
handleEndSurvey,
handleResponse,
}) {
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const [blob, setBlob] = useState(null);
const router = useRouter();
const waveSurferRef = useRef();
const containerRef = useRef();
let timeout;
let context, processor;
const { status, startRecording, stopRecording, mediaBlobUrl, clearBlobUrl } =
useReactMediaRecorder({
audio: true,
onStop: (blobUrl, blob) => {
console.log("Testing blob" + blob);
setBlob(blob);
},
});
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
useEffect(() => {
if (status === "recording") {
if (isSafari) {
// Safari 11 or newer automatically suspends new AudioContext's that aren't
// created in response to a user-gesture, like a click or tap, so create one
// here (inc. the script processor)
let AudioContext = window.AudioContext || window.webkitAudioContext;
context = new AudioContext();
processor = context.createScriptProcessor(1024, 1, 1);
}
waveSurferRef.current = WaveSurfer.create({
container: containerRef.current,
responsive: true,
barWidth: 2,
height: 80,
barHeight: 3,
barMinHeight: 1,
barRadius: 3,
barWidth: 3,
barGap: 5,
cursorWidth: 0,
waveColor: "red",
plugins: [MicrophonePlugin.create()],
});
const microphone = waveSurferRef.current.microphone;
timeout = setTimeout(() => {
microphone.stop();
stopRecording();
waveSurferRef.current.destroy();
}, 60000);
microphone.start();
}
if (status === "stopped") {
const microphone = waveSurferRef.current.microphone;
microphone.stop();
waveSurferRef.current.destroy();
clearTimeout(timeout);
}
return () => {
if (status === "recording") {
const microphone = waveSurferRef.current.microphone;
microphone.stop();
waveSurferRef.current.destroy();
clearTimeout(timeout);
}
};
}, [status]);
const handleNext = async () => {
if (required && !mediaBlobUrl) {
setError(true);
setErrorMessage("Please record the message");
return;
}
if (["recording", "paused"].includes(status)) {
setError(true);
setErrorMessage("Please stop the recording");
return;
}
if (mediaBlobUrl) {
const uniqueId =
Date.now().toString(36) + Math.random().toString(36).substring(2);
const audiofile = new File([blob], `${uniqueId}.webm`, {
type: "audio/webm",
});
const isLastAnswer = +id === totalQuestions ? true : false;
const res = await handleResponse(audiofile, isLastAnswer);
if (isLastAnswer) {
res && handleEndSurvey();
} else {
res && router.push(nextRoute);
}
} else {
if (+id === totalQuestions) {
handleEndSurvey();
} else {
router.push(nextRoute);
}
}
};
const handlePrev = () => {
router.back();
};
const removeAudio = () => {
setError(false);
clearBlobUrl();
setBlob(null);
};
return (
<Grid container spacing={5} height="100%" alignItems="center">
{/* question section */}
<Grid item xs={12}>
<Typography variant="h2" fontWeight={550}>
{title}
</Typography>
</Grid>
{/*Input Section */}
<Grid item md={2} xs={0}></Grid>
<Grid item md={8} xs={12}>
{mediaBlobUrl && <Waveform audio={mediaBlobUrl} />}
{["recording", "paused"].includes(status) && (
<Grid container>
<Grid item xs={12} ref={containerRef}></Grid>
</Grid>
)}
{mediaBlobUrl && (
<div>
<FabAudio aria-label="add" sx={{ mt: 2 }} onClick={removeAudio}>
<DeleteIcon />
</FabAudio>
</div>
)}
{["idle", "stopped"].includes(status) && !mediaBlobUrl && (
<>
<FabAudio
color="secondary"
aria-label="add"
sx={{ mt: 2 }}
onClick={() => {
startRecording();
setError(false);
}}
>
<MicIcon />
</FabAudio>
<Typography mt={2}>Hit Record to Start</Typography>
<Small>Speak close to the microphone for better response.</Small>
</>
)}
{["recording", "paused"].includes(status) && (
<>
<FabAudio aria-label="add" onClick={stopRecording}>
<StopIcon />
</FabAudio>
</>
)}
{error && (
<Typography mt={2} color="red">
{errorMessage}
</Typography>
)}
</Grid>
<Grid item md={2} xs={0}></Grid>
{/* Button Section */}
<Grid item md={3} xs={0}></Grid>
{secondaryButtonTitle && (
<Grid item md={3} xs={12}>
<Button
sx={{ width: "160px" }}
variant="outlined"
onClick={handlePrev}
>
{secondaryButtonTitle}
</Button>
</Grid>
)}
<Grid item md={secondaryButtonTitle ? 3 : 6} xs={12}>
<StyledButton onClick={handleNext}>
{+id === totalQuestions ? "Submit" : primaryButtonTitle}
</StyledButton>
</Grid>
<Grid item md={3} xs={0}></Grid>
</Grid>
);
}
VoiceInput.propTypes = {
title: PropTypes.string,
required: PropTypes.bool,
id: PropTypes.string,
totalQuestions: PropTypes.number,
primaryButtonTitle: PropTypes.string,
secondaryButtonTitle: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]),
nextRoute: PropTypes.string,
};
export default VoiceInput;
The blob size shows up as 0, and shows type: audio/wav is that an issue?
In an App created with npx react-native init MyApp
I am having a problem that I cannot correct.
I have a section to upload images and text to a database, Firebase.
On the screen I have a button to send the Post, as I show in the first image.
When I start to type the text and it is abundant, the Send button disappears, and I cannot send the text since I cannot leave the keyboard.
The problem also arises when I add an image, since it occupies more and the send button also disappears, and I cannot leave the keyboard.
I have added ScroolView to the View that contains this screen, but it breaks the application, brings the items to the top of the screen.
On other screens, I remove the lower task bar with a function to have more space for the device's keyboard, but this time I don't know how to solve the problem.
In iOS it is totally impossible to leave the keyboard to access the Send Post Button.
On Android devices, the Button is slightly displayed, as I show in the Android image, and also the screen automatically Scrools as I add text.
However, on iOS this does not happen, and I cannot send the Post.
How can I correct this error?
How can I make the application keyboard disappear when I want it to?
I EDIT THE QUESTION WITH MORE INFORMATION
I have tried a solution that the user #MichaelBahl has offered me,
adding KeyboardAvoidingView, and with the text it works, the text scrolls towards the top and always but if we add a large amount of Text and an image, the Send Button disappears at the bottom both in iOS and Android, making it impossible to send of the message.
How to fix this problem on iOS and Android?
I will continue looking for solutions.
I have added a styles file that I forget, these styles with "styled-components" are what create the screen
Adding seems to work, but when I open that screen the buttons are very close to the top, and I can't display the "add image" buttons
I edit the question again
I have used <KeyboardAwareScrollView> as suggested by user #MuhammadNuman.
This seems to work, but when starting on this screen, the buttons are at the top and thus prevent you from using the buttons on the right side to add images.
All this can happen due to the styles used in my TextInput, but I don't know how to correct it.
I show a new screenshot and a video with the operation of the buttons
import React, { useContext, useState } from "react"
import {
View,
Text,
Button,
Alert,
ActivityIndicator,
ScrollView,
TouchableWithoutFeedback,
Keyboard,
KeyboardAvoidingView,
} from "react-native"
import ActionButton from "react-native-action-button"
import Icon from 'react-native-vector-icons/Ionicons'
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'
import ImagePicker from "react-native-image-crop-picker"
import storage from '#react-native-firebase/storage'
import firestore from '#react-native-firebase/firestore'
import globalStyles from "../styles/global"
import {
InputField,
InputWrapper,
AddImage,
SubmitBtn,
SubmitBtnText,
StatusWrapper
} from '../styles/AddPostStyles'
import { AuthContext } from "../navigation/AuthProvider"
const AddPostScreen = () => {
const { user, logout } = useContext(AuthContext)
const [image, setImage] = useState(null)
const [uploading, setUploading] = useState(false)
const [transferred, setTransferred] = useState(0)
const [post, setPost] = useState(null)
const takePhotoFromCamera = () => {
ImagePicker.openCamera({
width: 1200,
height: 780,
cropping: true,
}).then((image) => {
console.log(image)
const imageUri = Platform.OS === 'ios' ? image.sourceURL : image.path
setImage(imageUri)
})
}
const choosePhotoFromLibrary = () => {
ImagePicker.openPicker({
width: 1200,
height: 780,
cropping: true,
}).then((image) => {
console.log(image)
const imageUri = Platform.OS === 'ios' ? image.sourceURL : image.path
setImage(imageUri)
})
}
const submitPost = async () => {
const imageUrl = await uploadImage()
console.log('Image Url', imageUrl)
firestore()
.collection('posts')
.add({
userId: user.uid,
post: post,
postImg: imageUrl,
postTime: firestore.Timestamp.fromDate(new Date()),
likes: null,
comments: null
})
.then(() => {
console.log('Post Added...')
Alert.alert(
'Post published!',
'Your post has been published Successfully!',
)
setPost(null)
})
.catch((error) => {
console.log('Something went wrong with added post to firestore.', error)
})
}
const uploadImage = async () => {
if (image == null) {
return null
}
const uploadUri = image
let filename = uploadUri.substring(uploadUri.lastIndexOf('/') + 1)
// Add timestad to File Name
const extension = filename.split('.').pop()
const name = filename.split('.').slice(0, -1).join('.')
filename = name + Date.now() + '.' + extension
setUploading(true)
setTransferred(0)
const storageRef = storage().ref(`photos/${filename}`)
const task = storageRef.putFile(uploadUri)
// Set transferred state
task.on('state_changed', (taskSnapshot) => {
console.log(`${taskSnapshot.bytesTransferred} transferred out of ${taskSnapshot.totalBytes}`)
setTransferred(
Math.round(taskSnapshot.bytesTransferred / taskSnapshot.totalBytes) * 100
)
})
try {
await task
const url = await storageRef.getDownloadURL()
setUploading(false)
setImage(null)
/* Alert.alert(
'Imagen subida!',
'Tu imagen se subio correctamente!',
) */
return url
} catch (e) {
console.log(e)
return null
}
}
return (
<KeyboardAvoidingView
behavior={Platform.OS === "android" ? "padding" : "height"}
style={{ flex: 1 }}>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={globalStyles.container}>
<InputWrapper>
{image != null ? <AddImage source={{ uri: image }} /> : null}
<InputField
placeholder="¿Qué tienes en mente?"
multiline
numberOfLines={4}
value={post}
onChangeText={(content) => setPost(content)}
/>
{uploading ? (
<StatusWrapper>
<Text>{transferred} % Completed!</Text>
<ActivityIndicator size="large" color="#27AE60" />
</StatusWrapper>
) : (
<SubmitBtn onPress={submitPost}>
<SubmitBtnText>Post</SubmitBtnText>
</SubmitBtn>
)}
</InputWrapper>
<ActionButton buttonColor="rgb(26, 188, 156)">
<ActionButton.Item
buttonColor='#9b59b6'
title="New Task" onPress={() => console.log("notes tapped!")}>
<Icon name="md-create" style={globalStyles.actionButtonIcon} />
</ActionButton.Item>
<ActionButton.Item
buttonColor='#3498db'
title="Take Photp"
onPress={takePhotoFromCamera}>
<Icon name="camera-outline" style={globalStyles.actionButtonIcon} />
</ActionButton.Item>
<ActionButton.Item
buttonColor='#1abc9c'
title="Elegir"
onPress={choosePhotoFromLibrary}>
<Icon name="md-images-outline" style={globalStyles.actionButtonIcon} />
</ActionButton.Item>
</ActionButton>
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
)
}
export default AddPostScreen
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}})
export default KeyboardAvoidingComponent
CODE:
import React, { useContext, useState } from "react"
import { View, Text, Button, Alert, ActivityIndicator, ScrollView } from "react-native"
import ActionButton from "react-native-action-button"
import Icon from 'react-native-vector-icons/Ionicons'
import FontAwesome5 from 'react-native-vector-icons/FontAwesome5'
import ImagePicker from "react-native-image-crop-picker"
import storage from '#react-native-firebase/storage'
import firestore from '#react-native-firebase/firestore'
import globalStyles from "../styles/global"
import {
InputField,
InputWrapper,
AddImage,
SubmitBtn,
SubmitBtnText,
StatusWrapper
} from '../styles/AddPostStyles'
import { AuthContext } from "../navigation/AuthProvider"
const AddPostScreen = () => {
const { user, logout } = useContext(AuthContext)
const [image, setImage] = useState(null)
const [uploading, setUploading] = useState(false)
const [transferred, setTransferred] = useState(0)
const [post, setPost] = useState(null)
const takePhotoFromCamera = () => {
ImagePicker.openCamera({
width: 1200,
height: 780,
cropping: true,
}).then((image) => {
console.log(image)
const imageUri = Platform.OS === 'ios' ? image.sourceURL : image.path
setImage(imageUri)
})
}
const choosePhotoFromLibrary = () => {
ImagePicker.openPicker({
width: 1200,
height: 780,
cropping: true,
}).then((image) => {
console.log(image)
const imageUri = Platform.OS === 'ios' ? image.sourceURL : image.path
setImage(imageUri)
})
}
const submitPost = async () => {
const imageUrl = await uploadImage()
console.log('Image Url', imageUrl)
firestore()
.collection('posts')
.add({
userId: user.uid,
post: post,
postImg: imageUrl,
postTime: firestore.Timestamp.fromDate(new Date()),
likes: null,
comments: null
})
.then(() => {
console.log('Post Added...')
Alert.alert(
'Post published!',
'Your post has been published Successfully!',
)
setPost(null)
})
.catch((error) => {
console.log('Something went wrong with added post to firestore.', error)
})
}
const uploadImage = async () => {
if (image == null) {
return null
}
const uploadUri = image
let filename = uploadUri.substring(uploadUri.lastIndexOf('/') + 1)
// Add timestad to File Name
const extension = filename.split('.').pop()
const name = filename.split('.').slice(0, -1).join('.')
filename = name + Date.now() + '.' + extension
setUploading(true)
setTransferred(0)
const storageRef = storage().ref(`photos/${filename}`)
const task = storageRef.putFile(uploadUri)
// Set transferred state
task.on('state_changed', (taskSnapshot) => {
console.log(`${taskSnapshot.bytesTransferred} transferred out of ${taskSnapshot.totalBytes}`)
setTransferred(
Math.round(taskSnapshot.bytesTransferred / taskSnapshot.totalBytes) * 100
)
})
try {
await task
const url = await storageRef.getDownloadURL()
setUploading(false)
setImage(null)
/* Alert.alert(
'Imagen subida!',
'Tu imagen se subio correctamente!',
) */
return url
} catch (e) {
console.log(e)
return null
}
}
return (
<View style={globalStyles.container}>
<InputWrapper>
{image != null ? <AddImage source={{ uri: image }} /> : null}
<InputField
placeholder="¿Qué tienes en mente?"
multiline
numberOfLines={4}
value={post}
onChangeText={(content) => setPost(content)}
/>
{uploading ? (
<StatusWrapper>
<Text>{transferred} % Completed!</Text>
<ActivityIndicator size="large" color="#27AE60" />
</StatusWrapper>
) : (
<SubmitBtn onPress={submitPost}>
<SubmitBtnText>Post</SubmitBtnText>
</SubmitBtn>
)}
</InputWrapper>
<ActionButton buttonColor="rgb(26, 188, 156)">
<ActionButton.Item
buttonColor='#9b59b6'
title="New Task" onPress={() => console.log("notes tapped!")}>
<Icon name="md-create" style={globalStyles.actionButtonIcon} />
</ActionButton.Item>
<ActionButton.Item
buttonColor='#3498db'
title="Take Photp"
onPress={takePhotoFromCamera}>
<Icon name="camera-outline" style={globalStyles.actionButtonIcon} />
</ActionButton.Item>
<ActionButton.Item
buttonColor='#1abc9c'
title="Elegir"
onPress={choosePhotoFromLibrary}>
<Icon name="md-images-outline" style={globalStyles.actionButtonIcon} />
</ActionButton.Item>
</ActionButton>
</View>
)
}
export default AddPostScreen
//////////////////////////
import styled from 'styled-components'
export const InputWrapper = styled.View`
flex: 1;
justify-content: center;
align-items: center;
width: 100%;
background-color: #2e64e515;
`
export const InputField = styled.TextInput`
justify-content: center;
align-items: center;
font-size: 24px;
text-align: center;
width:90%;
margin-bottom: 15px;
`
export const AddImage = styled.Image`
width: 100%;
height: 250px;
margin-bottom: 15px;
`
export const StatusWrapper = styled.View`
justify-content: center;
align-items: center;
`
export const SubmitBtn = styled.TouchableOpacity`
flex-direction: row;
justify-content: center;
background-color: #2e64e515;
border-radius: 5px;
padding: 10px 25px;
`
export const SubmitBtnText = styled.Text`
font-size: 18px;
font-family: 'Lato-Bold';
font-weight: bold;
color: #2e64e5;
`
you should use react-native-keyboard-aware-scroll-view
yarn add react-native-keyboard-aware-scroll-view
it will resolve your issue.
Usage
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'
<KeyboardAwareScrollView>
<View>
<TextInput />
</View>
</KeyboardAwareScrollView>
Note: if you are using react-native<65 then you should use react-native-keyboard-aware-scroll-view#0.9.4
Ok I think I found you a solution. Follow the previous steps I mentioned which are:
yarn add react-native-keyboard-aware-scroll-view
import { useRef } from 'react';
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
const scrollViewRef = useRef();
Use the KeyboardAwareScrollView and set its ref you just declared.
<KeyboardAwareScrollView ref={scrollViewRef} >
<View style={{flex: 1}} >
//Content
</View>
</KeyboardAwareScrollView>
With your TextInput (InputField in your case), make sure to scroll to the end every time the text is changed.
<TextInput
onChangeText={text => {
setText(text);
scrollViewRef.current.scrollToEnd({animated:true});
}
/>
That should work. If not here's a snack project I created that shows it working https://snack.expo.dev/#bgcodes/7ff849
The problem I have is that the WebView does not load height dynamically in iOS (in Android if it does), the question is that my content is dynamic and can grow high, and putting the fixed height would not work for me. Could you help me?
<CardView *ngFor="let itinerario of itinerario" class="card" elevation="40" radius="10" ios:shadowRadius="3">
<StackLayout class="card-layout text-center">
<WebView [src]="itinerario.mEstructura" height="auto"></WebView>
</StackLayout>
</CardView>
Use native method to evaluate JavaScript that can return height of the document.
HTML
<GridLayout>
<ScrollView class="page">
<StackLayout>
<WebView src="https://www.nativescript.org/" [height]="height"
(loadFinished)="onWebViewLoadFinished($event)"></WebView>
<Button class="btn btn-primary" text="Hello!"></Button>
</StackLayout>
</ScrollView>
</GridLayout>
TS
onWebViewLoadFinished(event: EventData) {
const webView = <WebView>event.object,
jsStr = `var body = document.body;
var html = document.documentElement;
Math.max( body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight);`;
if (webView.ios) {
webView.ios.scrollView.scrollEnabled = false;
webView.ios.evaluateJavaScriptCompletionHandler(jsStr,
(
result,
error
) => {
if (error) {
console.log("error...");
} else if (result) {
this.height = layout.toDeviceIndependentPixels(result);
this.changeDetectorRef.detectChanges();
}
});
} else if (webView.android) {
// Works only on Android 19 and above
webView.android.evaluateJavascript(
jsStr,
new android.webkit.ValueCallback({
onReceiveValue: (height) => {
this.height = layout.toDeviceIndependentPixels(height);
this.changeDetectorRef.detectChanges();
}
})
);
}
}
Playground Sample
I adapted #manoj's answer for nativescript-vue.
<template>
<ScrollView>
<StackLayout>
<WebView
src="https://www.nativescript.org/"
#loadFinished="loadFinished"
:ios:height="iosHeight"
/>
</StackLayout>
</ScrollView>
</template>
<script>
export default {
methods: {
loadFinished(args) {
if (!global.isIOS) {
return;
}
const webview = args.object;
const jsStr = `let body = document.body;
let html = document.documentElement;
Math.max(body.scrollHeight, body.offsetHeight,
html.clientHeight, html.scrollHeight, html.offsetHeight);`;
webview.ios.scrollView.scrollEnabled = false;
webview.ios.evaluateJavaScriptCompletionHandler(
jsStr,
(result, error) => {
if (result) {
this.iosHeight = result;
}
}
);
},
},
};
</script>
I am trying to upload image using react into rails active storage.
My component is:
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import User from './../../Assets/user.png';
import { addAvatar } from './../../actions/userAction';
class UploadAvatar extends Component {
state = {
image: null,
};
fileSelectHandler = (e) => {
this.setState({
image: e.target.files[0],
});
};
fileUploadHandler = () => {
if (this.state.image) {
console.log(this.state.image, this.props.userId);
const fd = new FormData();
fd.append('avatar', this.state.image, this.state.image.name);
this.props.addAvatar({ avatar: fd, userId: this.props.userId });
}
};
render() {
return (
<div className="avatar ">
<div className="avatar-content shadow-lg">
<div className="avatar-pic">
<img src={User} alt="userpic" />
</div>
<p>ADD PHOTO</p>
<input type="file" onChange={this.fileSelectHandler} />
<div className="avatar-foot">
<button type="button" className="skip">
SKIP
</button>
<button type="button" onClick={this.fileUploadHandler} className="submit">
SUBMIT
</button>
</div>
</div>
</div>
);
}
}
const mapStateToProps = store => ({
userId: store.userReducer.userId,
userEmail: store.userReducer.userEmail,
});
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
addAvatar,
},
dispatch,
);
export default connect(
mapStateToProps,
mapDispatchToProps,
)(UploadAvatar);
My ajax.js:
/* eslint-disable no-console no-param-reassign */
let CLIENT_URL = 'http://localhost:3000/api/v1';
function getDefaultOptions() {
return {
method: 'GET',
// credentials: "include",
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
};
}
function buildParam(params, asJSON = true) {
if (asJSON) {
return JSON.stringify(params);
}
const fD = new FormData();
Object.keys(params).forEach((param) => {
fD.append(param, params[param]);
});
return fD;
}
function ajax(uri, options = {}) {
const defaultOptions = getDefaultOptions();
options.method = options.method ? options.method : defaultOptions.method;
if (!options.formType) {
options.headers = options.headers ? options.headers : defaultOptions.headers;
}
options.credentials = options.credentials ? options.credentials : defaultOptions.credentials;
if (options.body && !options.formType) {
options.body = buildParam(options.body);
}
uri = uri.startsWith('/') ? uri : `/${uri}`;
return fetch(`${CLIENT_URL}${uri}`, options)
.then(data => data.json())
.catch(errors => console.log(errors));
}
export default ajax;
But on rails side I am getting empty object in avatar. Don't know why?
Please have a look to screenshot for rails side.
But from postman if I am trying to upload it is working fine.
Is there any alternative way to upload image.