gallery and ui flow for publishing

This commit is contained in:
Akinwale Ariwodola 2019-06-14 16:21:43 +01:00
parent b58f2db030
commit 380e11218c
11 changed files with 417 additions and 40 deletions

54
app/package-lock.json generated
View file

@ -4404,13 +4404,11 @@
},
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"optional": true
"bundled": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -4423,18 +4421,15 @@
},
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"optional": true
"bundled": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"optional": true
"bundled": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"optional": true
"bundled": true
},
"core-util-is": {
"version": "1.0.2",
@ -4537,8 +4532,7 @@
},
"inherits": {
"version": "2.0.3",
"bundled": true,
"optional": true
"bundled": true
},
"ini": {
"version": "1.3.5",
@ -4548,7 +4542,6 @@
"is-fullwidth-code-point": {
"version": "1.0.0",
"bundled": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -4561,20 +4554,17 @@
"minimatch": {
"version": "3.0.4",
"bundled": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"bundled": true,
"optional": true
"bundled": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@ -4591,7 +4581,6 @@
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -4664,8 +4653,7 @@
},
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"optional": true
"bundled": true
},
"object-assign": {
"version": "4.1.1",
@ -4675,7 +4663,6 @@
"once": {
"version": "1.4.0",
"bundled": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -4781,7 +4768,6 @@
"string-width": {
"version": "1.0.2",
"bundled": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -5567,8 +5553,8 @@
}
},
"lbry-redux": {
"version": "github:lbryio/lbry-redux#a01b919c72139d82fa981df6be4e0fe902ff8f70",
"from": "github:lbryio/lbry-redux",
"version": "github:lbryio/lbry-redux#03998a2acf1a9e6c1b0818821612d137b31ebea3",
"from": "github:lbryio/lbry-redux#03998a2acf1a9e6c1b0818821612d137b31ebea3",
"requires": {
"proxy-polyfill": "0.1.6",
"reselect": "^3.0.0",
@ -7690,6 +7676,15 @@
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-5.4.2.tgz",
"integrity": "sha512-S4E96Lwmx6z6QD3MaAuP7cNcXRLfgEUYU2GB694TbGEoOjk/FO1OnfbxfFp0vUs/klr4HJwACcwihPPxrFTt8w=="
},
"react-native-fs": {
"version": "2.13.3",
"resolved": "https://registry.npmjs.org/react-native-fs/-/react-native-fs-2.13.3.tgz",
"integrity": "sha512-B62LSSAEYQGItg7KVTzTVVCxezOYFBYp4DMVFbdoZUd1mZVFdqR2sy1HY1mye1VI/Lf3IbxSyZEQ0GmrrdwLjg==",
"requires": {
"base-64": "^0.1.0",
"utf8": "^2.1.1"
}
},
"react-native-gesture-handler": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-1.2.1.tgz",
@ -7752,6 +7747,14 @@
"prop-types": "^15.6.2"
}
},
"react-native-super-grid": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-native-super-grid/-/react-native-super-grid-3.0.4.tgz",
"integrity": "sha512-aoK71FGP5sFcLujuODYkAqyFDAZZRpvTeEwwaoXsc0JENhExEG7rGg65T5ELqyykiDOLBihuCLKasK5gLb0WtQ==",
"requires": {
"prop-types": "^15.6.0"
}
},
"react-native-tab-view": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/react-native-tab-view/-/react-native-tab-view-1.4.1.tgz",
@ -9548,6 +9551,11 @@
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
},
"utf8": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/utf8/-/utf8-2.1.2.tgz",
"integrity": "sha1-H6DZJw6b6FDZsFAn9jUZv0ZFfZY="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View file

@ -10,7 +10,7 @@
"dependencies": {
"base-64": "^0.1.0",
"@expo/vector-icons": "^8.1.0",
"lbry-redux": "lbryio/lbry-redux",
"lbry-redux": "lbryio/lbry-redux#03998a2acf1a9e6c1b0818821612d137b31ebea3",
"lbryinc": "lbryio/lbryinc",
"lodash": ">=4.17.11",
"merge": ">=1.2.1",
@ -21,6 +21,7 @@
"react-native-country-picker-modal": "^0.6.2",
"react-native-exception-handler": "2.9.0",
"react-native-fast-image": "^5.0.3",
"react-native-fs": "^2.13.3",
"react-native-gesture-handler": "^1.1.0",
"react-native-image-zoom-viewer": "^2.2.5",
"react-native-password-strength-meter": "^0.0.2",

View file

@ -149,6 +149,12 @@ const drawer = createDrawerNavigator(
drawerIcon: ({ tintColor }) => <Icon name="wallet" size={20} style={{ color: tintColor }} />,
},
},
Publish: {
screen: PublishPage,
navigationOptions: {
drawerIcon: ({ tintColor }) => <Icon name="upload" size={20} style={{ color: tintColor }} />,
},
},
Rewards: {
screen: RewardsPage,
navigationOptions: {

View file

@ -11,6 +11,9 @@ const Constants = {
PHASE_COLLECTION: 'collection',
PHASE_VERIFICATION: 'verification',
PHASE_SELECTOR: 'selector',
PHASE_DETAILS: 'details',
CONTENT_TAB: 'content',
ABOUT_TAB: 'about',

View file

@ -1,9 +1,40 @@
import React from 'react';
import { NativeModules, Text, View } from 'react-native';
import {
ActivityIndicator,
Image,
NativeModules,
Picker,
ScrollView,
Switch,
Text,
TextInput,
TouchableOpacity,
View
} from 'react-native';
import { FlatGrid } from 'react-native-super-grid';
import Button from 'component/button';
import Colors from 'styles/colors';
import Constants from 'constants';
import FastImage from 'react-native-fast-image';
import FloatingWalletBalance from 'component/floatingWalletBalance';
import Icon from 'react-native-vector-icons/FontAwesome5';
import Link from 'component/link';
import UriBar from 'component/uriBar';
import publishStyle from 'styles/reward';
import publishStyle from 'styles/publish';
class PublishPage extends React.PureComponent {
state = {
thumbnailPath: null,
videos: null,
currentMedia: null,
currentPhase: Constants.PHASE_SELECTOR,
// publish
anonymous: true,
channelName: null,
priceFree: true,
};
didFocusListener;
componentWillMount() {
@ -22,8 +53,13 @@ class PublishPage extends React.PureComponent {
pushDrawerStack();
setPlayerVisible();
NativeModules.Gallery.getThumbnailPath().then(thumbnailPath => {
if (thumbnailPath != null) {
this.setState({ thumbnailPath });
}
});
NativeModules.Gallery.getVideos().then(videos => {
console.log(videos);
this.setState({ videos });
});
};
@ -40,12 +76,156 @@ class PublishPage extends React.PureComponent {
}
}
setCurrentMedia(media) {
this.setState({ currentMedia: media, currentPhase: Constants.PHASE_DETAILS });
}
showSelector() {
this.setState({
currentMedia: null,
currentPhase: Constants.PHASE_SELECTOR,
// reset publish state
anonymous: true,
channelName: null,
priceFree: true
});
}
render() {
const { navigation } = this.props;
const { thumbnailPath } = this.state;
let content;
if (Constants.PHASE_SELECTOR === this.state.currentPhase) {
content = (
<View style={publishStyle.gallerySelector}>
<View style={publishStyle.actionsView}>
<View style={publishStyle.record}>
<Icon name="video" size={48} color={Colors.White} />
<Text style={publishStyle.actionText}>Record</Text>
</View>
<View style={publishStyle.subActions}>
<View style={publishStyle.photo}>
<Icon name="camera" size={48} color={Colors.White} />
<Text style={publishStyle.actionText}>Take a photo</Text>
</View>
<View style={publishStyle.upload}>
<Icon name="file-upload" size={48} color={Colors.White} />
<Text style={publishStyle.actionText}>Upload a file</Text>
</View>
</View>
</View>
{(!this.state.videos || !thumbnailPath) &&
<View style={publishStyle.loadingView}>
<ActivityIndicator size='large' color={Colors.LbryGreen} />
</View>
}
{(this.state.videos && thumbnailPath) &&
<FlatGrid
style={publishStyle.galleryGrid}
itemDimension={134}
spacing={2}
items={this.state.videos}
renderItem={({ item, index }) => {
return (
<TouchableOpacity key={index} onPress={() => this.setCurrentMedia(item)}>
<FastImage
style={publishStyle.galleryGridImage}
resizeMode={FastImage.resizeMode.cover}
source={{ uri: `file://${thumbnailPath}/${item.id}.png` }} />
</TouchableOpacity>
);
}}
/>}
</View>
);
} else if (Constants.PHASE_DETAILS === this.state.currentPhase && this.state.currentMedia) {
const { currentMedia } = this.state;
content = (
<ScrollView style={publishStyle.publishDetails}>
<View style={publishStyle.mainThumbnailContainer}>
<FastImage
style={publishStyle.mainThumbnail}
resizeMode={FastImage.resizeMode.contain}
source={{ uri: `file://${thumbnailPath}/${currentMedia.id}.png` }}
/>
</View>
<View style={publishStyle.card}>
<TextInput
placeholder={"Title"}
style={publishStyle.inputText}
value={currentMedia.name}
numberOfLines={1}
underlineColorAndroid={Colors.NextLbryGreen} />
<TextInput
placeholder={"Description"}
style={publishStyle.inputText}
underlineColorAndroid={Colors.NextLbryGreen} />
</View>
<View style={publishStyle.card}>
<Text style={publishStyle.cardTitle}>Price</Text>
<View style={publishStyle.cardRow}>
<View style={publishStyle.switchRow}>
<Switch value={this.state.priceFree} onValueChange={value => this.setState({ priceFree: value }) } />
<Text style={publishStyle.switchText}>Free</Text>
</View>
{!this.state.priceFree &&
<View style={[publishStyle.inputRow, publishStyle.priceInputRow]}>
<TextInput placeholder={"0.00"} style={publishStyle.priceInput} underlineColorAndroid={Colors.NextLbryGreen} numberOfLines={1} />
<Text style={publishStyle.currency}>LBC</Text>
</View>}
</View>
</View>
<View style={publishStyle.card}>
<Text style={publishStyle.cardTitle}>Publish anonymously or as a channel?</Text>
<View style={publishStyle.cardRow}>
<View style={publishStyle.switchRow}>
<Switch value={this.state.anonymous} onValueChange={value => this.setState({ anonymous: value }) } />
<Text style={publishStyle.switchText}>Anonymous</Text>
</View>
{!this.state.anonymous &&
<Picker
selectedValue={this.state.channelName}
style={publishStyle.channelPicker}
onValueChange={(itemValue, itemIndex) =>
this.setState({channelName: itemValue})
}>
<Picker.Item label="Select..." value={null} />
</Picker>}
</View>
</View>
<View style={publishStyle.card}>
<Text style={publishStyle.cardTitle}>Where can people find this content?</Text>
<Text style={publishStyle.helpText}>The LBRY URL is the exact address where people can find your content (ex. lbry://myvideo)</Text>
<TextInput placeholder={"lbry://"} style={publishStyle.inputText} underlineColorAndroid={Colors.NextLbryGreen} numberOfLines={1} />
<View style={publishStyle.inputRow}>
<TextInput placeholder={"0.00"} style={publishStyle.priceInput} underlineColorAndroid={Colors.NextLbryGreen} numberOfLines={1} />
<Text style={publishStyle.currency}>LBC</Text>
</View>
<Text style={publishStyle.helpText}>This LBC remains yours and the deposit can be undone at any time.</Text>
</View>
<View style={publishStyle.actionButtons}>
<Link style={publishStyle.cancelLink} text="Cancel" onPress={() => this.setState({ currentPhase: Constants.PHASE_SELECTOR })} />
<Button style={publishStyle.publishButton} text="Publish" />
</View>
</ScrollView>
);
}
return (
<View style={publishStyle.container}>
<UriBar navigation={navigation} />
{content}
{(false && Constants.PHASE_SELECTOR !== this.state.currentPhase) && <FloatingWalletBalance navigation={navigation} />}
</View>
);
}

View file

@ -3,6 +3,7 @@ const Colors = {
Black: '#000000',
ChannelGrey: '#9b9b9b',
DarkerGrey: '#222222',
DarkGrey: '#555555',
DescriptionGrey: '#999999',
LbryGreen: '#2f9176',

View file

@ -6,6 +6,130 @@ const publishStyle = StyleSheet.create({
flex: 1,
backgroundColor: Colors.PageBackground,
},
gallerySelector: {
flex: 1,
marginTop: 62,
paddingTop: 2,
backgroundColor: Colors.DarkGrey
},
galleryGrid: {
flex: 1
},
galleryGridImage: {
width: 134,
height: 90
},
inputText: {
fontFamily: 'Inter-UI-Regular',
fontSize: 16
},
card: {
backgroundColor: Colors.White,
marginTop: 16,
marginLeft: 16,
marginRight: 16,
padding: 16,
},
actionButtons: {
margin: 16,
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between'
},
publishButton: {
backgroundColor: Colors.LbryGreen,
alignSelf: 'flex-end'
},
cardTitle: {
fontFamily: 'Inter-UI-Regular',
fontSize: 20,
marginBottom: 8
},
actionsView: {
flexDirection: 'row',
width: '100%',
height: 240,
},
record: {
backgroundColor: Colors.Black,
flex: 0.5,
justifyContent: 'center',
alignItems: 'center'
},
subActions: {
flex: 0.5,
borderLeftWidth: 2,
borderLeftColor: Colors.DarkerGrey
},
actionText: {
color: Colors.White,
fontFamily: 'Inter-UI-Regular',
fontSize: 14,
marginTop: 8
},
photo: {
backgroundColor: Colors.Black,
height: 120,
justifyContent: 'center',
alignItems: 'center'
},
upload: {
backgroundColor: Colors.Black,
height: 120,
borderTopWidth: 2,
borderTopColor: Colors.DarkerGrey,
justifyContent: 'center',
alignItems: 'center'
},
publishDetails: {
marginTop: 60
},
mainThumbnailContainer: {
backgroundColor: Colors.Black,
width: '100%',
height: 240
},
mainThumbnail: {
height: 240
},
inputRow: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
priceInput: {
width: 80,
fontFamily: 'Inter-UI-Regular',
fontSize: 16
},
currency: {
fontFamily: 'Inter-UI-Regular'
},
cardRow: {
flex: 1,
flexDirection: 'row',
alignItems: 'center'
},
switchRow: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
height: 52
},
switchText: {
marginLeft: 6,
fontSize: 16
},
channelPicker: {
height: 52,
width: 160
},
loadingView: {
flex: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
export default publishStyle;

View file

@ -102,6 +102,7 @@ dependencies {
compile project(':@react-native-community_async-storage')
compile project(':react-native-exception-handler')
compile project(':react-native-fast-image')
compile project(':react-native-fs')
compile project(':react-native-gesture-handler')
compile project(':react-native-video')
compile project(':rn-fetch-blob')

View file

@ -5,6 +5,8 @@ include ':react-native-exception-handler'
project(':react-native-exception-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-exception-handler/android')
include ':react-native-fast-image'
project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-fast-image/android')
include ':react-native-fs'
project(':react-native-fs').projectDir = new File(settingsDir, './react/node_modules/react-native-fs/android')
include ':react-native-gesture-handler'
project(':react-native-gesture-handler').projectDir = new File(rootProject.projectDir, './react/node_modules/react-native-gesture-handler/android')
include ':react-native-video'

View file

@ -34,6 +34,7 @@ import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.react.ReactRootView;
import com.reactnativecommunity.asyncstorage.AsyncStoragePackage;
import com.rnfs.RNFSPackage;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import com.swmansion.gesturehandler.react.RNGestureHandlerPackage;
import com.RNFetchBlob.RNFetchBlobPackage;
@ -141,6 +142,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
.addPackage(new FastImageViewPackage())
.addPackage(new ReactVideoPackage())
.addPackage(new RNFetchBlobPackage())
.addPackage(new RNFSPackage())
.addPackage(new RNGestureHandlerPackage())
.addPackage(new LbryReactPackage())
.setUseDeveloperSupport(true)

View file

@ -1,9 +1,13 @@
package io.lbry.browser.reactmodules;
import android.content.Context;
import android.content.ContentResolver;
import android.database.Cursor;
import android.provider.MediaStore;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
@ -13,6 +17,9 @@ import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
@ -40,6 +47,18 @@ public class GalleryModule extends ReactContextBaseJavaModule {
promise.resolve(items);
}
@ReactMethod
public void getThumbnailPath(Promise promise) {
if (context != null) {
File cacheDir = context.getExternalCacheDir();
String thumbnailPath = String.format("%s/thumbnails", cacheDir.getAbsolutePath());
promise.resolve(thumbnailPath);
return;
}
promise.resolve(null);
}
private List<GalleryItem> loadVideos() {
String[] projection = {
MediaStore.MediaColumns._ID,
@ -49,6 +68,7 @@ public class GalleryModule extends ReactContextBaseJavaModule {
MediaStore.Video.Media.DURATION
};
List<String> ids = new ArrayList<String>();
List<GalleryItem> items = new ArrayList<GalleryItem>();
Cursor cursor = context.getContentResolver().query(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection, null, null, null);
while (cursor.moveToNext()) {
@ -58,17 +78,57 @@ public class GalleryModule extends ReactContextBaseJavaModule {
int pathColumn = cursor.getColumnIndex(MediaStore.MediaColumns.DATA);
int durationColumn = cursor.getColumnIndex(MediaStore.Video.Media.DURATION);
String id = cursor.getString(idColumn);
GalleryItem item = new GalleryItem();
item.setId(cursor.getString(idColumn));
item.setId(id);
item.setName(cursor.getString(nameColumn));
item.setType(cursor.getString(typeColumn));
item.setFilePath(cursor.getString(pathColumn));
items.add(item);
ids.add(id);
}
checkThumbnails(ids);
return items;
}
private void checkThumbnails(final List<String> ids) {
(new AsyncTask<Void, Void, Void>() {
protected Void doInBackground(Void... param) {
if (context != null) {
ContentResolver resolver = context.getContentResolver();
for (int i = 0; i < ids.size(); i++) {
String id = ids.get(i);
File cacheDir = context.getExternalCacheDir();
File thumbnailsDir = new File(String.format("%s/thumbnails", cacheDir.getAbsolutePath()));
if (!thumbnailsDir.isDirectory()) {
thumbnailsDir.mkdirs();
}
String thumbnailPath = String.format("%s/%s.png", thumbnailsDir.getAbsolutePath(), id);
File file = new File(thumbnailPath);
if (!file.exists()) {
// save the thumbnail to the path
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 1;
Bitmap thumbnail = MediaStore.Video.Thumbnails.getThumbnail(
resolver, Long.parseLong(id), MediaStore.Video.Thumbnails.MINI_KIND, options);
if (thumbnail != null) {
try (FileOutputStream os = new FileOutputStream(thumbnailPath)) {
thumbnail.compress(Bitmap.CompressFormat.PNG, 80, os);
} catch (IOException ex) {
// skip
}
}
}
}
}
return null;
}
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
private static class GalleryItem {
private String id;
@ -79,8 +139,6 @@ public class GalleryModule extends ReactContextBaseJavaModule {
private String name;
private String thumbnailUri;
private String type;
public String getId() {
@ -115,14 +173,6 @@ public class GalleryModule extends ReactContextBaseJavaModule {
this.name = name;
}
public String getThumbnailUri() {
return thumbnailUri;
}
public void setThumnbailUri(String thumbnailUri) {
this.thumbnailUri = thumbnailUri;
}
public String getType() {
return type;
}
@ -137,7 +187,6 @@ public class GalleryModule extends ReactContextBaseJavaModule {
map.putString("name", name);
map.putString("filePath", filePath);
map.putString("type", type);
map.putString("thumbnailUri", thumbnailUri);
map.putInt("duration", duration);
return map;