maintain form state when user navigates away

This commit is contained in:
Akinwale Ariwodola 2019-09-11 12:32:54 +01:00
parent c4218d9f8b
commit f008d95aa6
11 changed files with 261 additions and 21 deletions

View file

@ -59,6 +59,11 @@ const Constants = {
ACTION_SORT_BY_ITEM_CHANGED: 'SORT_BY_ITEM_CHANGED',
ACTION_TIME_ITEM_CHANGED: 'TIME_ITEM_CHANGED',
ACTION_UPDATE_PUBLISH_FORM_STATE: 'UPDATE_PUBLISH_FORM_STATE',
ACTION_UPDATE_CHANNEL_FORM_STATE: 'UPDATE_CHANNEL_FORM_STATE',
ACTION_CLEAR_PUBLISH_FORM_STATE: 'CLEAR_PUBLISH_FORM_STATE',
ACTION_CLEAR_CHANNEL_FORM_STATE: 'CLEAR_CHANNEL_FORM_STATE',
ORIENTATION_HORIZONTAL: 'horizontal',
ORIENTATION_VERTICAL: 'vertical',
@ -72,6 +77,7 @@ const Constants = {
DRAWER_ROUTE_SUBSCRIPTIONS: 'Subscriptions',
DRAWER_ROUTE_MY_LBRY: 'Downloads',
DRAWER_ROUTE_PUBLISH: 'Publish',
DRAWER_ROUTE_PUBLISH_FORM: 'PublishForm',
DRAWER_ROUTE_PUBLISHES: 'Publishes',
DRAWER_ROUTE_REWARDS: 'Rewards',
DRAWER_ROUTE_WALLET: 'Wallet',
@ -81,6 +87,7 @@ const Constants = {
DRAWER_ROUTE_TRANSACTION_HISTORY: 'TransactionHistory',
DRAWER_ROUTE_TAG: 'Tag',
DRAWER_ROUTE_CHANNEL_CREATOR: 'ChannelCreator',
DRAWER_ROUTE_CHANNEL_CREATOR_FORM: 'ChannnelCreatorForm',
FULL_ROUTE_NAME_DISCOVER: 'DiscoverStack',
FULL_ROUTE_NAME_WALLET: 'WalletStack',
@ -152,3 +159,6 @@ export const DrawerRoutes = [
Constants.DRAWER_ROUTE_TRANSACTION_HISTORY,
Constants.DRAWER_ROUTE_CHANNEL_CREATOR,
];
// sub-pages for main routes
export const InnerDrawerRoutes = [Constants.DRAWER_ROUTE_CHANNEL_CREATOR_FORM, Constants.DRAWER_ROUTE_PUBLISH_FORM];

View file

@ -37,12 +37,14 @@ import FilesystemStorage from 'redux-persist-filesystem-storage';
import createCompressor from 'redux-persist-transform-compress';
import createFilter from 'redux-persist-transform-filter';
import moment from 'moment';
import formReducer from 'redux/reducers/form';
import drawerReducer from 'redux/reducers/drawer';
import settingsReducer from 'redux/reducers/settings';
import thunk from 'redux-thunk';
const globalExceptionHandler = (error, isFatal) => {
if (error && NativeModules.Firebase) {
console.log(error);
NativeModules.Firebase.logException(isFatal, error.message ? error.message : 'No message', JSON.stringify(error));
}
};
@ -109,6 +111,7 @@ const reducers = persistCombineReducers(persistOptions, {
file: fileReducer,
fileInfo: fileInfoReducer,
filtered: filteredReducer,
form: formReducer,
homepage: homepageReducer,
nav: navigatorReducer,
notifications: notificationsReducer,

View file

@ -2,6 +2,7 @@
import React from 'react';
import {
ActivityIndicator,
Alert,
Dimensions,
Image,
NativeModules,
@ -170,6 +171,30 @@ class ChannelPage extends React.PureComponent {
}
};
onDeletePressed = () => {
const { abandonClaim, claim, navigation } = this.props;
if (claim) {
const { txid, nout } = claim;
// show confirm alert
Alert.alert(
__('Delete channel'),
__('Are you sure you want to delete this channel?'),
[
{ text: __('No') },
{
text: __('Yes'),
onPress: () => {
abandonClaim(txid, nout);
navigation.navigate({ routeName: Constants.DRAWER_ROUTE_CHANNEL_CREATOR });
},
},
],
{ cancelable: true }
);
}
};
render() {
const { channels, claim, navigation, uri, drawerStack, popDrawerStack, sortByItem, timeItem } = this.props;
const { name, permanent_url: permanentUrl } = claim;
@ -239,6 +264,15 @@ class ChannelPage extends React.PureComponent {
onPress={this.onEditPressed}
/>
)}
{ownedChannel && (
<Button
style={channelPageStyle.deleteButton}
theme={'light'}
icon={'trash-alt'}
text={'Delete'}
onPress={this.onDeletePressed}
/>
)}
{!ownedChannel && <SubscribeButton style={channelPageStyle.subscribeButton} uri={fullUri} name={name} />}
{!ownedChannel && (
<SubscribeNotificationButton

View file

@ -12,13 +12,18 @@ import {
doUpdateChannel,
doToast,
} from 'lbry-redux';
import { doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doPushDrawerStack, doPopDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import { doUpdateChannelFormState, doClearChannelFormState } from 'redux/actions/form';
import { selectDrawerStack } from 'redux/selectors/drawer';
import { selectChannelFormState } from 'redux/selectors/form';
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
import ChannelCreator from './view';
const select = state => ({
abandoningClaimIds: selectAbandoningIds(state),
channels: selectMyChannelClaims(state),
channelFormState: selectChannelFormState(state),
drawerStack: selectDrawerStack(state),
fetchingChannels: selectFetchingMyChannels(state),
balance: selectBalance(state),
updatingChannel: selectUpdatingChannel(state),
@ -28,10 +33,13 @@ const select = state => ({
const perform = dispatch => ({
abandonClaim: (txid, nout) => dispatch(doAbandonClaim(txid, nout)),
notify: data => dispatch(doToast(data)),
clearChannelFormState: () => dispatch(doClearChannelFormState()),
createChannel: (name, amount, optionalParams) => dispatch(doCreateChannel(name, amount, optionalParams)),
updateChannel: params => dispatch(doUpdateChannel(params)),
fetchChannelListMine: () => dispatch(doFetchChannelListMine()),
pushDrawerStack: () => dispatch(doPushDrawerStack(Constants.DRAWER_ROUTE_CHANNEL_CREATOR)),
updateChannel: params => dispatch(doUpdateChannel(params)),
updateChannelFormState: data => dispatch(doUpdateChannelFormState(data)),
pushDrawerStack: (routeName, params) => dispatch(doPushDrawerStack(routeName, params)),
popDrawerStack: () => dispatch(doPopDrawerStack()),
setPlayerVisible: () => dispatch(doSetPlayerVisible(false)),
});

View file

@ -110,8 +110,8 @@ export default class ChannelCreator extends React.PureComponent {
};
componentWillReceiveProps(nextProps) {
const { currentRoute, updatingChannel, updateChannelError } = nextProps;
const { currentRoute: prevRoute, notify } = this.props;
const { currentRoute, drawerStack, updatingChannel, updateChannelError } = nextProps;
const { currentRoute: prevRoute, drawerStack: prevDrawerStack, notify } = this.props;
if (Constants.DRAWER_ROUTE_CHANNEL_CREATOR === currentRoute && currentRoute !== prevRoute) {
this.onComponentFocused();
@ -126,6 +126,15 @@ export default class ChannelCreator extends React.PureComponent {
this.showChannelList();
}
}
if (
this.state.currentPhase === Constants.PHASE_CREATE &&
prevDrawerStack[prevDrawerStack.length - 1].route === Constants.DRAWER_ROUTE_CHANNEL_CREATOR_FORM &&
drawerStack[drawerStack.length - 1].route === Constants.DRAWER_ROUTE_CHANNEL_CREATOR
) {
// navigated back from the form
this.setState({ currentPhase: Constants.PHASE_LIST });
}
}
onComponentFocused = () => {
@ -142,7 +151,7 @@ export default class ChannelCreator extends React.PureComponent {
} = this.props;
NativeModules.Firebase.setCurrentScreen('Channels').then(result => {
pushDrawerStack();
pushDrawerStack(Constants.DRAWER_ROUTE_CHANNEL_CREATOR, navigation.state.params ? navigation.state.params : null);
setPlayerVisible();
if (!fetchingChannels) {
fetchChannelListMine();
@ -155,8 +164,13 @@ export default class ChannelCreator extends React.PureComponent {
DeviceEventEmitter.addListener('onDocumentPickerCanceled', this.onPickerCanceled);
if (navigation.state.params) {
const { editChannelUrl } = navigation.state.params;
this.setState({ editChannelUrl });
const { editChannelUrl, displayForm } = navigation.state.params;
if (editChannelUrl) {
this.setState({ editChannelUrl, currentPhase: Constants.PHASE_CREATE });
} else if (displayForm) {
this.loadPendingFormState();
this.setState({ currentPhase: Constants.PHASE_CREATE });
}
}
});
};
@ -166,7 +180,7 @@ export default class ChannelCreator extends React.PureComponent {
};
onFilePicked = evt => {
const { notify } = this.props;
const { notify, updateChannelFormState } = this.props;
if (evt.path && evt.path.length > 0) {
// check which image we're trying to upload
@ -192,8 +206,10 @@ export default class ChannelCreator extends React.PureComponent {
fileUrl,
({ url }) => {
if (isCover) {
updateChannelFormState({ coverImageUrl: url });
this.setState({ coverImageUrl: url, uploadingImage: false });
} else {
updateChannelFormState({ thumbnailUrl: url });
this.setState({ thumbnailUrl: url, uploadingImage: false });
}
},
@ -239,6 +255,8 @@ export default class ChannelCreator extends React.PureComponent {
}
handleCreateCancel = () => {
const { clearChannelFormState } = this.props;
clearChannelFormState(); // explicitly clear state on cancel?
this.setState({ showCreateChannel: false, newChannelName: '', newChannelBid: 0.1 });
};
@ -272,18 +290,26 @@ export default class ChannelCreator extends React.PureComponent {
};
handleDescriptionChange = value => {
const { updateChannelFormState } = this.props;
updateChannelFormState({ description: value });
this.setState({ description: value });
};
handleWebsiteChange = value => {
const { updateChannelFormState } = this.props;
updateChannelFormState({ website: value });
this.setState({ website: value });
};
handleEmailChange = value => {
const { updateChannelFormState } = this.props;
updateChannelFormState({ email: value });
this.setState({ email: value });
};
handleNewChannelTitleChange = value => {
const { updateChannelFormState } = this.props;
updateChannelFormState({ newChannelTitle: value });
this.setState({ newChannelTitle: value });
if (value && !this.state.editMode && !this.state.channelNameUserEdited) {
// build the channel name based on the title
@ -295,7 +321,7 @@ export default class ChannelCreator extends React.PureComponent {
};
handleNewChannelNameChange = (value, userInput) => {
const { notify } = this.props;
const { notify, updateChannelFormState } = this.props;
let newChannelName = value,
newChannelNameError = '';
@ -314,6 +340,7 @@ export default class ChannelCreator extends React.PureComponent {
this.setState({ channelNameUserEdited: true });
}
updateChannelFormState({ newChannelName });
this.setState({
newChannelName,
newChannelNameError,
@ -321,7 +348,7 @@ export default class ChannelCreator extends React.PureComponent {
};
handleNewChannelBidChange = newChannelBid => {
const { balance, notify } = this.props;
const { balance, notify, updateChannelFormState } = this.props;
let newChannelBidError;
if (newChannelBid <= 0) {
newChannelBidError = __('Please enter a deposit above 0');
@ -332,7 +359,7 @@ export default class ChannelCreator extends React.PureComponent {
}
notify({ message: newChannelBidError });
updateChannelFormState({ newChannelBid });
this.setState({
newChannelBid,
newChannelBidError,
@ -340,7 +367,7 @@ export default class ChannelCreator extends React.PureComponent {
};
handleCreateChannelClick = () => {
const { balance, createChannel, onChannelChange, notify, updateChannel } = this.props;
const { balance, clearChannelFormState, createChannel, onChannelChange, notify, updateChannel } = this.props;
const {
claimId,
coverImageUrl,
@ -361,6 +388,11 @@ export default class ChannelCreator extends React.PureComponent {
return;
}
if (email.trim().length > 0 && (email.indexOf('@') === -1 || email.indexOf('.') === -1)) {
notify({ message: 'Please provide a valid email address.' });
return;
}
// shouldn't do this check in edit mode
if (
(editMode && currentChannelName !== newChannelName && this.channelExists(newChannelName)) ||
@ -395,6 +427,7 @@ export default class ChannelCreator extends React.PureComponent {
}
// reset state and go back to the channel list
clearChannelFormState();
notify({ message: 'The channel was successfully created.' });
this.showChannelList();
};
@ -484,6 +517,9 @@ export default class ChannelCreator extends React.PureComponent {
};
handleNewChannelPress = () => {
const { pushDrawerStack } = this.props;
pushDrawerStack(Constants.DRAWER_ROUTE_CHANNEL_CREATOR_FORM);
this.loadPendingFormState();
this.setState({ currentPhase: Constants.PHASE_CREATE });
};
@ -492,6 +528,8 @@ export default class ChannelCreator extends React.PureComponent {
};
showChannelList = () => {
const { popDrawerStack } = this.props;
popDrawerStack();
this.resetChannelCreator();
this.setState({ currentPhase: Constants.PHASE_LIST });
};
@ -547,9 +585,18 @@ export default class ChannelCreator extends React.PureComponent {
this.prepareEdit(channel);
};
loadPendingFormState = () => {
const { channelFormState } = this.props;
const showOptionalFields =
channelFormState.description || channelFormState.website || channelFormState.email || channelFormState.tags;
this.setState({ ...channelFormState, showOptionalFields });
};
prepareEdit = channel => {
const { pushDrawerStack } = this.props;
const { value } = channel;
pushDrawerStack(Constants.DRAWER_ROUTE_CHANNEL_CREATOR_FORM);
this.setState({
claimId: channel.claim_id,
currentPhase: Constants.PHASE_CREATE,
@ -601,12 +648,13 @@ export default class ChannelCreator extends React.PureComponent {
return;
}
const { notify } = this.props;
const { notify, updateChannelFormState } = this.props;
const { tags } = this.state;
const index = tags.indexOf(tag.toLowerCase());
if (index === -1) {
const newTags = tags.slice();
newTags.push(tag);
updateChannelFormState({ tags: newTags });
this.setState({ tags: newTags });
} else {
notify({ message: __(`You already added the "${tag}" tag.`) });
@ -618,11 +666,13 @@ export default class ChannelCreator extends React.PureComponent {
return;
}
const { updateChannelFormState } = this.props;
const newTags = this.state.tags.slice();
const index = newTags.indexOf(tag.toLowerCase());
if (index > -1) {
newTags.splice(index, 1);
updateChannelFormState({ tags: newTags });
this.setState({ tags: newTags });
}
};

23
src/redux/actions/form.js Normal file
View file

@ -0,0 +1,23 @@
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
export const doUpdatePublishFormState = publishFormValue => dispatch =>
dispatch({
type: Constants.ACTION_UPDATE_PUBLISH_FORM_STATE,
data: { ...publishFormValue },
});
export const doUpdateChannelFormState = channelFormValue => dispatch =>
dispatch({
type: Constants.ACTION_UPDATE_CHANNEL_FORM_STATE,
data: { ...channelFormValue },
});
export const doClearPublishFormState = () => dispatch =>
dispatch({
type: Constants.ACTION_CLEAR_PUBLISH_FORM_STATE,
});
export const doClearChannelFormState = () => dispatch =>
dispatch({
type: Constants.ACTION_CLEAR_CHANNEL_FORM_STATE,
});

View file

@ -16,7 +16,19 @@ reducers[Constants.ACTION_PUSH_DRAWER_STACK] = (state, action) => {
const { routeName, params } = action.data;
const newStack = state.stack.slice();
if (routeName !== newStack[newStack.length - 1].route) {
const lastRoute = newStack[newStack.length - 1].route;
let canPushStack = routeName !== lastRoute;
if (
lastRoute === Constants.DRAWER_ROUTE_CHANNEL_CREATOR_FORM &&
routeName === Constants.DRAWER_ROUTE_CHANNEL_CREATOR
) {
canPushStack = false;
}
if (lastRoute === Constants.DRAWER_ROUTE_PUBLISH_FORM && routeName === Constants.DRAWER_ROUTE_PUBLISH) {
canPushStack = false;
}
if (canPushStack) {
newStack.push({ route: routeName, params });
}

View file

@ -0,0 +1,53 @@
import Constants from 'constants'; // eslint-disable-line node/no-deprecated-api
const reducers = {};
const defaultState = {
channelFormValues: {},
publishFormValues: {},
};
reducers[Constants.ACTION_UPDATE_PUBLISH_FORM_STATE] = (state, action) => {
const { data } = action;
const publishFormValues = Object.assign({}, state.publishFormValues);
const newPublishFormValues = Object.assign(publishFormValues, data);
return {
...state,
publishFormValues: newPublishFormValues,
};
};
reducers[Constants.ACTION_UPDATE_CHANNEL_FORM_STATE] = (state, action) => {
const { data } = action;
const channelFormValues = Object.assign({}, state.channelFormValues);
const newChannelFormValues = Object.assign(channelFormValues, data);
console.log(channelFormValues);
console.log('***newvalues***');
console.log(newChannelFormValues);
return {
...state,
channelFormValues: newChannelFormValues,
};
};
reducers[Constants.ACTION_CLEAR_PUBLISH_FORM_STATE] = state => {
return {
...state,
publishFormValues: {},
};
};
reducers[Constants.ACTION_CLEAR_CHANNEL_FORM_STATE] = state => {
return {
...state,
channelFormValues: {},
};
};
export default function reducer(state = defaultState, action) {
const handler = reducers[action.type];
if (handler) return handler(state, action);
return state;
}

View file

@ -0,0 +1,13 @@
import { createSelector } from 'reselect';
export const selectState = state => state.form || {};
export const selectPublishFormState = createSelector(
selectState,
state => state.publishFormValues || {}
);
export const selectChannelFormState = createSelector(
selectState,
state => state.channelFormValues || {}
);

View file

@ -31,6 +31,7 @@ const channelPageStyle = StyleSheet.create({
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 24,
},
infoText: {
fontFamily: 'Inter-UI-Regular',

View file

@ -1,7 +1,7 @@
import { NavigationActions, StackActions } from 'react-navigation';
import { buildURI, isURIValid, normalizeURI } from 'lbry-redux';
import { doPopDrawerStack, doPushDrawerStack, doSetPlayerVisible } from 'redux/actions/drawer';
import Constants, { DrawerRoutes } from 'constants'; // eslint-disable-line node/no-deprecated-api
import Constants, { DrawerRoutes, InnerDrawerRoutes } from 'constants'; // eslint-disable-line node/no-deprecated-api
const tagNameLength = 10;
@ -156,10 +156,26 @@ export function navigateBack(navigation, drawerStack, popDrawerStack) {
const target = drawerStack[drawerStack.length > 1 ? drawerStack.length - 2 : 0];
const { route, params } = target;
navigation.goBack(navigation.state.key);
if (DrawerRoutes.indexOf(route) === -1 && isURIValid(route)) {
if (!DrawerRoutes.includes(route) && !InnerDrawerRoutes.includes(route) && isURIValid(route)) {
navigateToUri(navigation, route, null, true);
} else {
navigation.navigate({ routeName: route, params });
let targetRoute = route;
let targetParams = params;
if (InnerDrawerRoutes.includes(route)) {
if (Constants.DRAWER_ROUTE_CHANNEL_CREATOR_FORM === route) {
targetRoute = Constants.DRAWER_ROUTE_CHANNEL_CREATOR;
} else if (Constants.DRAWER_ROUTE_PUBLISH_FORM === route) {
targetRoute = Constants.DRAWER_ROUTE_PUBLISH_FORM;
}
if (targetParams) {
targetParams.displayForm = true;
} else {
targetParams = { displayForm: true };
}
}
navigation.navigate({ routeName: targetRoute, targetParams });
}
}
@ -169,14 +185,31 @@ export function dispatchNavigateBack(dispatch, nav, drawerStack) {
const target = drawerStack[drawerStack.length > 1 ? drawerStack.length - 2 : 0];
const { route } = target;
dispatch(NavigationActions.back());
if (DrawerRoutes.indexOf(route) === -1 && isURIValid(route)) {
if (!DrawerRoutes.includes(route) && !InnerDrawerRoutes.includes(route) && isURIValid(route)) {
dispatchNavigateToUri(dispatch, nav, route, true);
} else {
const newTarget = drawerStack[drawerStack.length > 1 ? drawerStack.length - 2 : 0];
let targetRoute = newTarget.route;
let targetParams = newTarget.params;
if (InnerDrawerRoutes.includes(targetRoute)) {
if (Constants.DRAWER_ROUTE_CHANNEL_CREATOR_FORM === route) {
targetRoute = Constants.DRAWER_ROUTE_CHANNEL_CREATOR;
} else if (Constants.DRAWER_ROUTE_PUBLISH_FORM === route) {
targetRoute = Constants.DRAWER_ROUTE_PUBLISH_FORM;
}
if (targetParams) {
targetParams.displayForm = true;
} else {
targetParams = { displayForm: true };
}
}
const navigateAction = NavigationActions.navigate({
routeName: newTarget.route,
params: newTarget.params,
routeName: targetRoute,
params: targetParams,
});
dispatch(navigateAction);
}
}