Publishing #577
|
@ -170,7 +170,6 @@ export default class ChannelSelector extends React.PureComponent {
|
||||||
This isn't true anymore: You can make a generic statement like "Your channel name contains invalid characters" You could also create a const value containing This isn't true anymore: https://github.com/lbryio/lbry-redux/blob/efaacdd26b45a2f566d4f4508ab8e87f6fcb6c76/src/lbryURI.js#L5
You can make a generic statement like "Your channel name contains invalid characters"
You could also create a const value containing `=&#:$@%?/` in `lbryURI.js` and specifically tell people not to use these.
"Please enter a deposit above 0" (also, if the input is not disallowing negative numbers, the above check should be "Please enter a deposit above 0"
(also, if the input is not disallowing negative numbers, the above check should be `< 0` rather than `=== 0`)
same as above also this looks like a DRY violation with the above check same as above
also this looks like a DRY violation with the above check
|
|||||||
render() {
|
render() {
|
||||||
const channel = this.state.addingChannel ? 'new' : this.props.channel;
|
const channel = this.state.addingChannel ? 'new' : this.props.channel;
|
||||||
const { fetchingChannels, channels = [] } = this.props;
|
const { fetchingChannels, channels = [] } = this.props;
|
||||||
console.log(channels);
|
|
||||||
This isn't true anymore: You can make a generic statement like "Your channel name contains invalid characters" You could also create a const value containing This isn't true anymore: https://github.com/lbryio/lbry-redux/blob/efaacdd26b45a2f566d4f4508ab8e87f6fcb6c76/src/lbryURI.js#L5
You can make a generic statement like "Your channel name contains invalid characters"
You could also create a const value containing `=&#:$@%?/` in `lbryURI.js` and specifically tell people not to use these.
"Please enter a deposit above 0" (also, if the input is not disallowing negative numbers, the above check should be "Please enter a deposit above 0"
(also, if the input is not disallowing negative numbers, the above check should be `< 0` rather than `=== 0`)
same as above also this looks like a DRY violation with the above check same as above
also this looks like a DRY violation with the above check
|
|||||||
const pickerItems = [{ name: Constants.ITEM_ANONYMOUS }, { name: Constants.ITEM_CREATE_A_CHANNEL }].concat(
|
const pickerItems = [{ name: Constants.ITEM_ANONYMOUS }, { name: Constants.ITEM_CREATE_A_CHANNEL }].concat(
|
||||||
channels
|
channels
|
||||||
);
|
);
|
||||||
|
|
||||||
This isn't true anymore: You can make a generic statement like "Your channel name contains invalid characters" You could also create a const value containing This isn't true anymore: https://github.com/lbryio/lbry-redux/blob/efaacdd26b45a2f566d4f4508ab8e87f6fcb6c76/src/lbryURI.js#L5
You can make a generic statement like "Your channel name contains invalid characters"
You could also create a const value containing `=&#:$@%?/` in `lbryURI.js` and specifically tell people not to use these.
"Please enter a deposit above 0" (also, if the input is not disallowing negative numbers, the above check should be "Please enter a deposit above 0"
(also, if the input is not disallowing negative numbers, the above check should be `< 0` rather than `=== 0`)
same as above also this looks like a DRY violation with the above check same as above
also this looks like a DRY violation with the above check
This isn't true anymore: You can make a generic statement like "Your channel name contains invalid characters" You could also create a const value containing This isn't true anymore: https://github.com/lbryio/lbry-redux/blob/efaacdd26b45a2f566d4f4508ab8e87f6fcb6c76/src/lbryURI.js#L5
You can make a generic statement like "Your channel name contains invalid characters"
You could also create a const value containing `=&#:$@%?/` in `lbryURI.js` and specifically tell people not to use these.
"Please enter a deposit above 0" (also, if the input is not disallowing negative numbers, the above check should be "Please enter a deposit above 0"
(also, if the input is not disallowing negative numbers, the above check should be `< 0` rather than `=== 0`)
same as above also this looks like a DRY violation with the above check same as above
also this looks like a DRY violation with the above check
|
|
@ -34,17 +34,25 @@ class PublishPage extends React.PureComponent {
|
||||||
camera = null;
|
camera = null;
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
|
// gallery videos
|
||||||
|
videos: null,
|
||||||
|
|
||||||
|
// camera
|
||||||
cameraType: RNCamera.Constants.Type.back,
|
cameraType: RNCamera.Constants.Type.back,
|
||||||
videoRecordingMode: false,
|
videoRecordingMode: false,
|
||||||
recordingVideo: false,
|
recordingVideo: false,
|
||||||
showCameraOverlay: false,
|
showCameraOverlay: false,
|
||||||
|
|
||||||
|
// paths and media
|
||||||
|
uploadsPath: null,
|
||||||
thumbnailPath: null,
|
thumbnailPath: null,
|
||||||
videos: null,
|
|
||||||
currentMedia: null,
|
currentMedia: null,
|
||||||
|
currentThumbnailUri: null,
|
||||||
|
updatingThumbnailUri: false,
|
||||||
currentPhase: Constants.PHASE_SELECTOR,
|
currentPhase: Constants.PHASE_SELECTOR,
|
||||||
advancedMode: false,
|
|
||||||
|
|
||||||
// publish
|
// publish
|
||||||
|
advancedMode: false,
|
||||||
anonymous: true,
|
anonymous: true,
|
||||||
channelName: CLAIM_VALUES.CHANNEL_ANONYMOUS,
|
channelName: CLAIM_VALUES.CHANNEL_ANONYMOUS,
|
||||||
priceSet: false,
|
priceSet: false,
|
||||||
|
@ -145,6 +153,7 @@ class PublishPage extends React.PureComponent {
|
||||||
|
|
||||||
pushDrawerStack();
|
pushDrawerStack();
|
||||||
setPlayerVisible();
|
setPlayerVisible();
|
||||||
|
|
||||||
NativeModules.Gallery.getThumbnailPath().then(thumbnailPath => {
|
NativeModules.Gallery.getThumbnailPath().then(thumbnailPath => {
|
||||||
if (thumbnailPath != null) {
|
if (thumbnailPath != null) {
|
||||||
this.setState({ thumbnailPath });
|
this.setState({ thumbnailPath });
|
||||||
|
@ -184,6 +193,7 @@ class PublishPage extends React.PureComponent {
|
||||||
showSelector() {
|
showSelector() {
|
||||||
this.setState({
|
this.setState({
|
||||||
currentMedia: null,
|
currentMedia: null,
|
||||||
|
currentThumbnailUri: null,
|
||||||
currentPhase: Constants.PHASE_SELECTOR,
|
currentPhase: Constants.PHASE_SELECTOR,
|
||||||
|
|
||||||
// publish
|
// publish
|
||||||
|
@ -218,6 +228,10 @@ class PublishPage extends React.PureComponent {
|
||||||
this.setState({ showCameraOverlay: false, videoRecordingMode: false });
|
this.setState({ showCameraOverlay: false, videoRecordingMode: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFilePathFromUri = (uri) => {
|
||||||
|
return uri.substring('file://'.length);
|
||||||
|
}
|
||||||
|
|
||||||
handleCameraActionPressed = () => {
|
handleCameraActionPressed = () => {
|
||||||
// check if it's video or photo mode
|
// check if it's video or photo mode
|
||||||
if (this.state.videoRecordingMode) {
|
if (this.state.videoRecordingMode) {
|
||||||
|
@ -231,13 +245,15 @@ class PublishPage extends React.PureComponent {
|
||||||
this.setState({ recordingVideo: false });
|
this.setState({ recordingVideo: false });
|
||||||
const currentMedia = {
|
const currentMedia = {
|
||||||
id: -1,
|
id: -1,
|
||||||
filePath: data.uri,
|
filePath: this.getFilePathFromUri(data.uri),
|
||||||
name: generateCombination(2, ' ', true),
|
name: generateCombination(2, ' ', true),
|
||||||
type: 'video/mp4', // always MP4
|
type: 'video/mp4', // always MP4
|
||||||
duration: 0
|
duration: 0
|
||||||
};
|
};
|
||||||
this.setCurrentMedia(currentMedia);
|
this.setCurrentMedia(currentMedia);
|
||||||
this.setState({
|
this.setState({
|
||||||
|
currentThumbnailUri: null,
|
||||||
|
updatingThumbnailUri: false,
|
||||||
currentPhase: Constants.PHASE_DETAILS,
|
currentPhase: Constants.PHASE_DETAILS,
|
||||||
showCameraOverlay: false,
|
showCameraOverlay: false,
|
||||||
videoRecordingMode: false,
|
videoRecordingMode: false,
|
||||||
|
@ -250,13 +266,19 @@ class PublishPage extends React.PureComponent {
|
||||||
this.camera.takePictureAsync(options).then(data => {
|
this.camera.takePictureAsync(options).then(data => {
|
||||||
const currentMedia = {
|
const currentMedia = {
|
||||||
id: -1,
|
id: -1,
|
||||||
filePath: data.uri,
|
filePath: this.getFilePathFromUri(data.uri),
|
||||||
name: generateCombination(2, ' ', true),
|
name: generateCombination(2, ' ', true),
|
||||||
type: 'image/jpg', // always JPEG
|
type: 'image/jpg', // always JPEG
|
||||||
duration: 0
|
duration: 0
|
||||||
};
|
};
|
||||||
this.setCurrentMedia(currentMedia);
|
this.setCurrentMedia(currentMedia);
|
||||||
this.setState({ currentPhase: Constants.PHASE_DETAILS, showCameraOverlay: false, videoRecordingMode: false });
|
this.setState({
|
||||||
|
currentPhase: Constants.PHASE_DETAILS,
|
||||||
|
currentThumbnailUri: null,
|
||||||
|
updatingThumbnailUri: false,
|
||||||
|
showCameraOverlay: false,
|
||||||
|
videoRecordingMode: false
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -283,6 +305,12 @@ class PublishPage extends React.PureComponent {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getRandomFileId = () => {
|
||||||
|
// generate a random id for a photo or recorded video between 1 and 20 (for creating thumbnails)
|
||||||
|
const id = Math.floor(Math.random() * (20 - 2)) + 1;
|
||||||
|
return '_' + id;
|
||||||
|
}
|
||||||
|
|
||||||
handlePublishAgainPressed = () => {
|
handlePublishAgainPressed = () => {
|
||||||
this.showSelector();
|
this.showSelector();
|
||||||
};
|
};
|
||||||
|
@ -311,18 +339,35 @@ class PublishPage extends React.PureComponent {
|
||||||
this.setState({ channelName: channel });
|
this.setState({ channelName: channel });
|
||||||
};
|
};
|
||||||
|
|
||||||
getThumbnailUriForMedia = (media) => {
|
updateThumbnailUriForMedia = (media) => {
|
||||||
const { thumbnailPath } = this.state;
|
if (this.state.updatingThumbnailUri) {
|
||||||
if (media.type) {
|
return;
|
||||||
const mediaType = media.type.substring(0, 5);
|
|
||||||
if ('video' === mediaType && media.id > -1) {
|
|
||||||
return `file://${thumbnailPath}/${media.id}.png`
|
|
||||||
} else if ('image' === mediaType) {
|
|
||||||
return media.filePath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
const { notify } = this.props;
|
||||||
|
const { thumbnailPath } = this.state;
|
||||||
|
|
||||||
|
this.setState({ updatingThumbnailUri: true });
|
||||||
|
|
||||||
|
if (media.type) {
|
||||||
|
const mediaType = media.type.substring(0, 5);
|
||||||
|
const tempId = this.getRandomFileId();
|
||||||
|
|
||||||
|
if ('video' === mediaType && media.id > -1) {
|
||||||
|
const uri = `file://${thumbnailPath}/${media.id}.png`;
|
||||||
|
this.setState({ currentThumbnailUri: uri, updatingThumbnailUri: false });
|
||||||
|
} else if ('image' === mediaType) {
|
||||||
|
// photo taken or file selected
|
||||||
|
NativeModules.Gallery.createImageThumbnail(tempId, media.filePath).
|
||||||
|
then(path => this.setState({ currentThumbnailUri: `file://${path}`, updatingThumbnailUri: false })).
|
||||||
|
catch(err => { notify({ message: err }); this.setState({ updatingThumbnailUri: false }); });
|
||||||
|
} else if ('video' === mediaType) {
|
||||||
|
// recorded video
|
||||||
|
NativeModules.Gallery.createVideoThumbnail(tempId, media.filePath).
|
||||||
|
then(path => this.setState({ currentThumbnailUri: `file://${path}`, updatingThumbnailUri: false })).
|
||||||
|
catch(err => { notify({ message: err }); this.setState({ updatingThumbnailUri: false }); });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleTitleChange = title => {
|
handleTitleChange = title => {
|
||||||
|
@ -406,16 +451,18 @@ class PublishPage extends React.PureComponent {
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
} else if (Constants.PHASE_DETAILS === this.state.currentPhase && this.state.currentMedia) {
|
} else if (Constants.PHASE_DETAILS === this.state.currentPhase && this.state.currentMedia) {
|
||||||
const { currentMedia } = this.state;
|
const { currentMedia, currentThumbnailUri } = this.state;
|
||||||
const thumbnailUri = this.getThumbnailUriForMedia(currentMedia);
|
if (!currentThumbnailUri) {
|
||||||
|
this.updateThumbnailUriForMedia(currentMedia);
|
||||||
|
}
|
||||||
content = (
|
content = (
|
||||||
<ScrollView style={publishStyle.publishDetails}>
|
<ScrollView style={publishStyle.publishDetails}>
|
||||||
{thumbnailUri && thumbnailUri.trim().length > 0 &&
|
{currentThumbnailUri && currentThumbnailUri.trim().length > 0 &&
|
||||||
<View style={publishStyle.mainThumbnailContainer}>
|
<View style={publishStyle.mainThumbnailContainer}>
|
||||||
<FastImage
|
<FastImage
|
||||||
style={publishStyle.mainThumbnail}
|
style={publishStyle.mainThumbnail}
|
||||||
resizeMode={FastImage.resizeMode.contain}
|
resizeMode={FastImage.resizeMode.contain}
|
||||||
source={{ uri: thumbnailUri }}
|
source={{ uri: currentThumbnailUri }}
|
||||||
/>
|
/>
|
||||||
</View>}
|
</View>}
|
||||||
|
|
||||||
|
@ -589,7 +636,8 @@ class PublishPage extends React.PureComponent {
|
||||||
buttonNegative: 'Cancel',
|
buttonNegative: 'Cancel',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<View style={[publishStyle.cameraControls, this.state.videoRecordingMode ? publishStyle.transparentControls : opaqueControls ]}>
|
<View style={[publishStyle.cameraControls,
|
||||||
|
this.state.videoRecordingMode ? publishStyle.transparentControls : publishStyle.opaqueControls ]}>
|
||||||
<View style={publishStyle.controlsRow}>
|
<View style={publishStyle.controlsRow}>
|
||||||
<TouchableOpacity onPress={this.handleCloseCameraPressed}>
|
<TouchableOpacity onPress={this.handleCloseCameraPressed}>
|
||||||
<Icon name="arrow-left" size={28} color={Colors.White} />
|
<Icon name="arrow-left" size={28} color={Colors.White} />
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.content.ContentResolver;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.BitmapFactory;
|
import android.graphics.BitmapFactory;
|
||||||
|
import android.media.ThumbnailUtils;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
|
@ -17,7 +18,10 @@ import com.facebook.react.bridge.ReactMethod;
|
||||||
import com.facebook.react.bridge.WritableArray;
|
import com.facebook.react.bridge.WritableArray;
|
||||||
import com.facebook.react.bridge.WritableMap;
|
import com.facebook.react.bridge.WritableMap;
|
||||||
|
|
||||||
|
import io.lbry.browser.Utils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -59,6 +63,119 @@ public class GalleryModule extends ReactContextBaseJavaModule {
|
||||||
promise.resolve(null);
|
promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@ReactMethod
|
||||||
|
public void copyImage(String sourcePath, String destinationPath, Promise promise) {
|
||||||
|
try {
|
||||||
|
File source = new File(sourcePath);
|
||||||
|
File destination = new File(destinationPath);
|
||||||
|
if (source.exists()) {
|
||||||
|
FileChannel src = new FileInputStream(source).getChannel();
|
||||||
|
FileChannel dst = new FileOutputStream(destination).getChannel();
|
||||||
|
dst.transferFrom(src, 0, src.size());
|
||||||
|
src.close();
|
||||||
|
dst.close();
|
||||||
|
|
||||||
|
promise.resolve(true);
|
||||||
|
} else {
|
||||||
|
promise.reject("The source image could not be found. Please try again.");
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
promise.reject("The image could not be saved. Please try again.");
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void getUploadsPath(Promise promise) {
|
||||||
|
if (context != null) {
|
||||||
|
String baseFolder = Utils.getExternalStorageDir(context);
|
||||||
|
String uploadsPath = String.format("%s/LBRY/Uploads", baseFolder);
|
||||||
|
File uploadsDir = new File(uploadsPath);
|
||||||
|
if (!uploadsDir.isDirectory()) {
|
||||||
|
uploadsDir.mkdirs();
|
||||||
|
}
|
||||||
|
promise.resolve(uploadsPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.reject("The content could not be saved to the device. Please check your storage permissions.");
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void createVideoThumbnail(String targetId, String filePath, Promise promise) {
|
||||||
|
(new AsyncTask<Void, Void, String>() {
|
||||||
|
protected String doInBackground(Void... param) {
|
||||||
|
String thumbnailPath = null;
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
Bitmap thumbnail = ThumbnailUtils.createVideoThumbnail(filePath, MediaStore.Video.Thumbnails.MINI_KIND);
|
||||||
|
File cacheDir = context.getExternalCacheDir();
|
||||||
|
thumbnailPath = String.format("%s/thumbnails/%s.png", cacheDir.getAbsolutePath(), targetId);
|
||||||
|
|
||||||
|
File file = new File(thumbnailPath);
|
||||||
|
try (FileOutputStream os = new FileOutputStream(thumbnailPath)) {
|
||||||
|
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
|
||||||
|
os.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
promise.reject("Could not create a thumbnail for the video");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbnailPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPostExecute(String thumbnailPath) {
|
||||||
|
if (thumbnailPath != null && thumbnailPath.trim().length() > 0) {
|
||||||
|
promise.resolve(thumbnailPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void createImageThumbnail(String targetId, String filePath, Promise promise) {
|
||||||
|
(new AsyncTask<Void, Void, String>() {
|
||||||
|
protected String doInBackground(Void... param) {
|
||||||
|
String thumbnailPath = null;
|
||||||
|
FileOutputStream os = null;
|
||||||
|
try {
|
||||||
|
Bitmap source = BitmapFactory.decodeFile(filePath);
|
||||||
|
// MINI_KIND dimensions
|
||||||
|
Bitmap thumbnail = BitmapFactory.createScaledBitmap(source, 512, 384, false);
|
||||||
|
|
||||||
|
if (context != null) {
|
||||||
|
File cacheDir = context.getExternalCacheDir();
|
||||||
|
thumbnailPath = String.format("%s/thumbnails/%s.png", cacheDir.getAbsolutePath(), targetId);
|
||||||
|
os = new FileOutputStream(thumbnailPath);
|
||||||
|
if (thumbnail != null) {
|
||||||
|
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
|
||||||
|
}
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
} catch (IOException ex) {
|
||||||
|
promise.reject("Could not create a thumbnail for the image");
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (os != null) {
|
||||||
|
try {
|
||||||
|
os.close();
|
||||||
|
} catch (IOException ex) {
|
||||||
|
// ignoe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return thumbnailPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onPostExecute(String thumbnailPath) {
|
||||||
|
if (thumbnailPath != null && thumbnailPath.trim().length() > 0) {
|
||||||
|
promise.resolve(thumbnailPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
private List<GalleryItem> loadVideos() {
|
private List<GalleryItem> loadVideos() {
|
||||||
String[] projection = {
|
String[] projection = {
|
||||||
MediaStore.MediaColumns._ID,
|
MediaStore.MediaColumns._ID,
|
||||||
|
|
This isn't true anymore:
efaacdd26b/src/lbryURI.js (L5)
You can make a generic statement like "Your channel name contains invalid characters"
You could also create a const value containing
=&#:$@%?/
inlbryURI.js
and specifically tell people not to use these."Please enter a deposit above 0"
(also, if the input is not disallowing negative numbers, the above check should be
< 0
rather than=== 0
)same as above
also this looks like a DRY violation with the above check