list selection mode and deleting published items (#26)
This commit is contained in:
parent
2c6ac47d1a
commit
41f511e233
7 changed files with 237 additions and 61 deletions
|
@ -46,6 +46,15 @@ class FileListItem extends React.PureComponent {
|
||||||
navigateToUri(navigation, shortUrl || uri, { autoplay });
|
navigateToUri(navigation, shortUrl || uri, { autoplay });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onPressHandler = () => {
|
||||||
|
const { claim, onPress } = this.props;
|
||||||
|
if (onPress) {
|
||||||
|
onPress(claim);
|
||||||
|
} else {
|
||||||
|
this.defaultOnPress();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
blackListedOutpoints,
|
blackListedOutpoints,
|
||||||
|
@ -63,6 +72,8 @@ class FileListItem extends React.PureComponent {
|
||||||
navigation,
|
navigation,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
hideChannel,
|
hideChannel,
|
||||||
|
onLongPress,
|
||||||
|
selected,
|
||||||
title,
|
title,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
|
@ -95,7 +106,7 @@ class FileListItem extends React.PureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={style}>
|
<View style={style}>
|
||||||
<TouchableOpacity style={style} onPress={onPress || this.defaultOnPress}>
|
<TouchableOpacity style={style} onPress={this.onPressHandler} onLongPress={() => onLongPress(claim)}>
|
||||||
<FileItemMedia
|
<FileItemMedia
|
||||||
style={fileListStyle.thumbnail}
|
style={fileListStyle.thumbnail}
|
||||||
blurRadius={obscure ? 15 : 0}
|
blurRadius={obscure ? 15 : 0}
|
||||||
|
@ -103,6 +114,11 @@ class FileListItem extends React.PureComponent {
|
||||||
title={title || name}
|
title={title || name}
|
||||||
thumbnail={thumbnail}
|
thumbnail={thumbnail}
|
||||||
/>
|
/>
|
||||||
|
{selected && (
|
||||||
|
<View style={fileListStyle.selectedOverlay}>
|
||||||
|
<Icon name={'check-circle'} solid color={Colors.NextLbryGreen} size={32} />
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
{fileInfo && fileInfo.completed && fileInfo.download_path && (
|
{fileInfo && fileInfo.completed && fileInfo.download_path && (
|
||||||
<Icon style={fileListStyle.downloadedIcon} solid color={Colors.NextLbryGreen} name={'folder'} size={16} />
|
<Icon style={fileListStyle.downloadedIcon} solid color={Colors.NextLbryGreen} name={'folder'} size={16} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { SEARCH_TYPES, isNameValid, isURIValid, normalizeURI } from 'lbry-redux';
|
import { SEARCH_TYPES, isNameValid, isURIValid, normalizeURI } from 'lbry-redux';
|
||||||
import { FlatList, Keyboard, TextInput, View } from 'react-native';
|
import { FlatList, Keyboard, Text, TextInput, TouchableOpacity, View } from 'react-native';
|
||||||
import { navigateToUri } from 'utils/helper';
|
import { navigateToUri } from 'utils/helper';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
import UriBarItem from './internal/uri-bar-item';
|
import UriBarItem from './internal/uri-bar-item';
|
||||||
|
import Icon from 'react-native-vector-icons/FontAwesome5';
|
||||||
import NavigationButton from 'component/navigationButton';
|
import NavigationButton from 'component/navigationButton';
|
||||||
import discoverStyle from 'styles/discover';
|
|
||||||
import uriBarStyle from 'styles/uriBar';
|
import uriBarStyle from 'styles/uriBar';
|
||||||
|
|
||||||
class UriBar extends React.PureComponent {
|
class UriBar extends React.PureComponent {
|
||||||
|
@ -16,6 +16,16 @@ class UriBar extends React.PureComponent {
|
||||||
|
|
||||||
keyboardDidHideListener = null;
|
keyboardDidHideListener = null;
|
||||||
|
|
||||||
|
state = {
|
||||||
|
changeTextTimeout: null,
|
||||||
|
currentValue: null,
|
||||||
|
inputText: null,
|
||||||
|
focused: false,
|
||||||
|
|
||||||
|
// TODO: Add a setting to enable / disable direct search?
|
||||||
|
directSearch: true,
|
||||||
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
|
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
|
||||||
this.setSelection();
|
this.setSelection();
|
||||||
|
@ -36,18 +46,6 @@ class UriBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this.state = {
|
|
||||||
changeTextTimeout: null,
|
|
||||||
currentValue: null,
|
|
||||||
inputText: null,
|
|
||||||
focused: false,
|
|
||||||
// TODO: Add a setting to enable / disable direct search?
|
|
||||||
directSearch: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
handleChangeText = text => {
|
handleChangeText = text => {
|
||||||
const newValue = text || '';
|
const newValue = text || '';
|
||||||
clearTimeout(this.state.changeTextTimeout);
|
clearTimeout(this.state.changeTextTimeout);
|
||||||
|
@ -136,7 +134,17 @@ class UriBar extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { navigation, suggestions, query, value, belowOverlay } = this.props;
|
const {
|
||||||
|
belowOverlay,
|
||||||
|
navigation,
|
||||||
|
onExitSelectionMode,
|
||||||
|
onDeleteActionPressed,
|
||||||
|
query,
|
||||||
|
selectedItemCount,
|
||||||
|
selectionMode,
|
||||||
|
suggestions,
|
||||||
|
value,
|
||||||
|
} = this.props;
|
||||||
if (this.state.currentValue === null) {
|
if (this.state.currentValue === null) {
|
||||||
this.setState({ currentValue: value });
|
this.setState({ currentValue: value });
|
||||||
}
|
}
|
||||||
|
@ -146,42 +154,78 @@ class UriBar extends React.PureComponent {
|
||||||
// TODO: Add optional setting to enable URI / search bar suggestions
|
// TODO: Add optional setting to enable URI / search bar suggestions
|
||||||
/* if (this.state.focused) { style.push(uriBarStyle.inFocus); } */
|
/* if (this.state.focused) { style.push(uriBarStyle.inFocus); } */
|
||||||
|
|
||||||
|
// TODO: selectionModeActions should be dynamically created / specified
|
||||||
return (
|
return (
|
||||||
<View style={style}>
|
<View style={style}>
|
||||||
<View style={[uriBarStyle.uriContainer, belowOverlay ? null : uriBarStyle.containerElevated]}>
|
<View style={[uriBarStyle.uriContainer, belowOverlay ? null : uriBarStyle.containerElevated]}>
|
||||||
<NavigationButton
|
{selectionMode && (
|
||||||
name="bars"
|
<View style={uriBarStyle.selectionModeBar}>
|
||||||
size={24}
|
<View style={uriBarStyle.selectionModeLeftBar}>
|
||||||
style={uriBarStyle.drawerMenuButton}
|
<TouchableOpacity
|
||||||
iconStyle={discoverStyle.drawerHamburger}
|
style={uriBarStyle.backTouchArea}
|
||||||
onPress={() => navigation.openDrawer()}
|
onPress={() => {
|
||||||
/>
|
if (onExitSelectionMode) {
|
||||||
<TextInput
|
onExitSelectionMode();
|
||||||
ref={ref => {
|
}
|
||||||
this.textInput = ref;
|
}}
|
||||||
}}
|
>
|
||||||
autoCorrect={false}
|
<Icon name="arrow-left" size={20} />
|
||||||
style={uriBarStyle.uriText}
|
</TouchableOpacity>
|
||||||
onLayout={() => {
|
{selectedItemCount > 0 && <Text style={uriBarStyle.itemCount}>{selectedItemCount}</Text>}
|
||||||
this.setSelection();
|
</View>
|
||||||
}}
|
|
||||||
selectTextOnFocus
|
<View style={uriBarStyle.selectionModeActions}>
|
||||||
placeholder={'Search movies, music, and more'}
|
<TouchableOpacity
|
||||||
underlineColorAndroid={'transparent'}
|
style={uriBarStyle.actionTouchArea}
|
||||||
numberOfLines={1}
|
onPress={() => {
|
||||||
clearButtonMode={'while-editing'}
|
if (onDeleteActionPressed) {
|
||||||
value={this.state.currentValue}
|
onDeleteActionPressed();
|
||||||
returnKeyType={'go'}
|
}
|
||||||
inlineImageLeft={'baseline_search_black_24'}
|
}}
|
||||||
inlineImagePadding={16}
|
>
|
||||||
onFocus={() => this.setState({ focused: true })}
|
<Icon name="trash-alt" solid={false} size={20} style={uriBarStyle.actionIcon} />
|
||||||
onBlur={() => {
|
</TouchableOpacity>
|
||||||
this.setState({ focused: false });
|
</View>
|
||||||
this.setSelection();
|
</View>
|
||||||
}}
|
)}
|
||||||
onChangeText={this.handleChangeText}
|
|
||||||
onSubmitEditing={this.handleSubmitEditing}
|
{!selectionMode && (
|
||||||
/>
|
<NavigationButton
|
||||||
|
name="bars"
|
||||||
|
size={24}
|
||||||
|
style={uriBarStyle.drawerMenuButton}
|
||||||
|
iconStyle={uriBarStyle.drawerHamburger}
|
||||||
|
onPress={() => navigation.openDrawer()}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{!selectionMode && (
|
||||||
|
<TextInput
|
||||||
|
ref={ref => {
|
||||||
|
this.textInput = ref;
|
||||||
|
}}
|
||||||
|
autoCorrect={false}
|
||||||
|
style={uriBarStyle.uriText}
|
||||||
|
onLayout={() => {
|
||||||
|
this.setSelection();
|
||||||
|
}}
|
||||||
|
selectTextOnFocus
|
||||||
|
placeholder={'Search movies, music, and more'}
|
||||||
|
underlineColorAndroid={'transparent'}
|
||||||
|
numberOfLines={1}
|
||||||
|
clearButtonMode={'while-editing'}
|
||||||
|
value={this.state.currentValue}
|
||||||
|
returnKeyType={'go'}
|
||||||
|
inlineImageLeft={'baseline_search_black_24'}
|
||||||
|
inlineImagePadding={16}
|
||||||
|
onFocus={() => this.setState({ focused: true })}
|
||||||
|
onBlur={() => {
|
||||||
|
this.setState({ focused: false });
|
||||||
|
this.setSelection();
|
||||||
|
}}
|
||||||
|
onChangeText={this.handleChangeText}
|
||||||
|
onSubmitEditing={this.handleSubmitEditing}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{this.state.focused && !this.state.directSearch && (
|
{this.state.focused && !this.state.directSearch && (
|
||||||
<View style={uriBarStyle.suggestions}>
|
<View style={uriBarStyle.suggestions}>
|
||||||
<FlatList
|
<FlatList
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import {
|
import {
|
||||||
|
doAbandonClaim,
|
||||||
doCheckPendingPublishes,
|
doCheckPendingPublishes,
|
||||||
doFetchClaimListMine,
|
doFetchClaimListMine,
|
||||||
selectMyClaimUrisWithoutChannels,
|
selectMyClaimUrisWithoutChannels,
|
||||||
|
@ -15,6 +16,7 @@ const select = state => ({
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
abandonClaim: (txid, nout) => dispatch(doAbandonClaim(txid, nout)),
|
||||||
fetchMyClaims: () => dispatch(doFetchClaimListMine()),
|
fetchMyClaims: () => dispatch(doFetchClaimListMine()),
|
||||||
checkPendingPublishes: () => dispatch(doCheckPendingPublishes()),
|
checkPendingPublishes: () => dispatch(doCheckPendingPublishes()),
|
||||||
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_PUBLISHES)),
|
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_PUBLISHES)),
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { ActivityIndicator, FlatList, Text, TouchableOpacity, View } from 'react-native';
|
import { ActivityIndicator, Alert, FlatList, Text, TouchableOpacity, View } from 'react-native';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Colors from 'styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
|
||||||
|
@ -7,9 +7,15 @@ import FileListItem from 'component/fileListItem';
|
||||||
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
||||||
import UriBar from 'component/uriBar';
|
import UriBar from 'component/uriBar';
|
||||||
import publishStyle from 'styles/publish';
|
import publishStyle from 'styles/publish';
|
||||||
import { __ } from 'utils/helper';
|
import { __, navigateToUri } from 'utils/helper';
|
||||||
|
|
||||||
class PublishesPage extends React.PureComponent {
|
class PublishesPage extends React.PureComponent {
|
||||||
|
state = {
|
||||||
|
selectionMode: false,
|
||||||
|
selectedUris: [],
|
||||||
|
selectedClaimsMap: {},
|
||||||
|
};
|
||||||
|
|
||||||
didFocusListener;
|
didFocusListener;
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
|
@ -36,12 +42,67 @@ class PublishesPage extends React.PureComponent {
|
||||||
checkPendingPublishes();
|
checkPendingPublishes();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
addOrRemoveItem = (uri, claim) => {
|
||||||
|
const { selectedClaimsMap } = this.state;
|
||||||
|
let selectedUris = [...this.state.selectedUris];
|
||||||
|
|
||||||
|
if (selectedUris.includes(uri)) {
|
||||||
|
delete selectedClaimsMap[uri];
|
||||||
|
selectedUris.splice(selectedUris.indexOf(uri), 1);
|
||||||
|
} else {
|
||||||
|
selectedClaimsMap[uri] = claim;
|
||||||
|
selectedUris.push(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ selectionMode: selectedUris.length > 0, selectedUris, selectedClaimsMap });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSelectItem = (uri, claim) => {
|
||||||
|
this.addOrRemoveItem(uri, claim);
|
||||||
|
};
|
||||||
|
|
||||||
|
handleItemLongPress = (uri, claim) => {
|
||||||
|
this.addOrRemoveItem(uri, claim);
|
||||||
|
};
|
||||||
|
|
||||||
|
onExitSelectionMode = () => {
|
||||||
|
this.setState({ selectionMode: false, selectedUris: [], selectedClaimsMap: {} });
|
||||||
|
};
|
||||||
|
|
||||||
|
onDeleteActionPressed = () => {
|
||||||
|
const { abandonClaim } = this.props;
|
||||||
|
const { selectedClaimsMap } = this.state;
|
||||||
|
|
||||||
|
// show confirm alert
|
||||||
|
Alert.alert(__('Unpublish'), __('Are you sure you want to unpublish the selected content?'), [
|
||||||
|
{ text: __('No') },
|
||||||
|
{
|
||||||
|
text: __('Yes'),
|
||||||
|
onPress: () => {
|
||||||
|
const uris = Object.keys(selectedClaimsMap);
|
||||||
|
uris.forEach(uri => {
|
||||||
|
const { txid, nout } = selectedClaimsMap[uri];
|
||||||
|
abandonClaim(txid, nout);
|
||||||
|
});
|
||||||
|
this.onExitSelectionMode();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { fetching, navigation, uris } = this.props;
|
const { fetching, navigation, uris } = this.props;
|
||||||
|
const { selectionMode, selectedUris } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={publishStyle.container}>
|
<View style={publishStyle.container}>
|
||||||
<UriBar navigation={navigation} />
|
<UriBar
|
||||||
|
navigation={navigation}
|
||||||
|
selectionMode={selectionMode}
|
||||||
|
selectedItemCount={selectedUris.length}
|
||||||
|
onExitSelectionMode={this.onExitSelectionMode}
|
||||||
|
onDeleteActionPressed={this.onDeleteActionPressed}
|
||||||
|
/>
|
||||||
{fetching && (
|
{fetching && (
|
||||||
<View style={publishStyle.centered}>
|
<View style={publishStyle.centered}>
|
||||||
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
|
<ActivityIndicator size={'small'} color={Colors.LbryGreen} />
|
||||||
|
@ -65,11 +126,28 @@ class PublishesPage extends React.PureComponent {
|
||||||
<FlatList
|
<FlatList
|
||||||
style={publishStyle.publishesList}
|
style={publishStyle.publishesList}
|
||||||
contentContainerStyle={publishStyle.publishesScrollPadding}
|
contentContainerStyle={publishStyle.publishesScrollPadding}
|
||||||
|
extraData={this.state}
|
||||||
initialNumToRender={8}
|
initialNumToRender={8}
|
||||||
maxToRenderPerBatch={24}
|
maxToRenderPerBatch={24}
|
||||||
removeClippedSubviews
|
removeClippedSubviews
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<FileListItem hideChannel key={item} uri={item} style={publishStyle.listItem} navigation={navigation} />
|
<FileListItem
|
||||||
|
hideChannel
|
||||||
|
key={item}
|
||||||
|
uri={item}
|
||||||
|
style={publishStyle.listItem}
|
||||||
|
selected={selectedUris.includes(item)}
|
||||||
|
onPress={claim => {
|
||||||
|
if (selectionMode) {
|
||||||
|
this.handleSelectItem(item, claim);
|
||||||
|
} else {
|
||||||
|
// TODO: when shortUrl is available for my claims, navigate to that URL instead
|
||||||
|
navigateToUri(navigation, item);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onLongPress={claim => this.handleItemLongPress(item, claim)}
|
||||||
|
navigation={navigation}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
data={uris}
|
data={uris}
|
||||||
keyExtractor={(item, index) => item}
|
keyExtractor={(item, index) => item}
|
||||||
|
|
|
@ -167,14 +167,6 @@ const discoverStyle = StyleSheet.create({
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
color: '#0c604b',
|
color: '#0c604b',
|
||||||
},
|
},
|
||||||
drawerMenuButton: {
|
|
||||||
height: '100%',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
drawerHamburger: {
|
|
||||||
marginLeft: 16,
|
|
||||||
marginRight: 16,
|
|
||||||
},
|
|
||||||
rightHeaderIcon: {
|
rightHeaderIcon: {
|
||||||
marginRight: 16,
|
marginRight: 16,
|
||||||
},
|
},
|
||||||
|
|
|
@ -38,6 +38,16 @@ const fileListStyle = StyleSheet.create({
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
},
|
},
|
||||||
|
selectedOverlay: {
|
||||||
|
position: 'absolute',
|
||||||
|
left: 0,
|
||||||
|
top: 0,
|
||||||
|
width: thumbnailWidth,
|
||||||
|
height: thumbnailHeight,
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
backgroundColor: '#000000aa',
|
||||||
|
},
|
||||||
title: {
|
title: {
|
||||||
fontFamily: 'Inter-UI-SemiBold',
|
fontFamily: 'Inter-UI-SemiBold',
|
||||||
fontSize: screenWidthPixels <= 720 ? 12 : 14,
|
fontSize: screenWidthPixels <= 720 ? 12 : 14,
|
||||||
|
|
|
@ -20,6 +20,10 @@ const uriBarStyle = StyleSheet.create({
|
||||||
containerElevated: {
|
containerElevated: {
|
||||||
elevation: 4,
|
elevation: 4,
|
||||||
},
|
},
|
||||||
|
drawerHamburger: {
|
||||||
|
marginLeft: 16,
|
||||||
|
marginRight: 16,
|
||||||
|
},
|
||||||
uriText: {
|
uriText: {
|
||||||
backgroundColor: Colors.VeryLightGrey,
|
backgroundColor: Colors.VeryLightGrey,
|
||||||
borderRadius: 24,
|
borderRadius: 24,
|
||||||
|
@ -73,6 +77,36 @@ const uriBarStyle = StyleSheet.create({
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
flex: 3,
|
flex: 3,
|
||||||
},
|
},
|
||||||
|
selectionModeBar: {
|
||||||
|
flex: 1,
|
||||||
|
height: 46,
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
selectionModeLeftBar: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginLeft: 16,
|
||||||
|
},
|
||||||
|
selectionModeActions: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 16,
|
||||||
|
},
|
||||||
|
backTouchArea: {
|
||||||
|
height: '100%',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
actionTouchArea: {
|
||||||
|
height: '100%',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
itemCount: {
|
||||||
|
fontFamily: 'Inter-UI-Regular',
|
||||||
|
fontSize: 20,
|
||||||
|
marginLeft: 30,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default uriBarStyle;
|
export default uriBarStyle;
|
||||||
|
|
Loading…
Reference in a new issue