Merge pull request #1180 from lbryio/download-after

Notify after download, badges and fix error
This commit is contained in:
Liam Cardenas 2018-03-26 00:51:50 -07:00 committed by GitHub
commit 433578c538
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 217 additions and 66 deletions

View file

@ -2,11 +2,13 @@ import React from 'react';
import { connect } from 'react-redux';
import { selectCurrentPage, selectHeaderLinks } from 'redux/selectors/navigation';
import { doNavigate } from 'redux/actions/navigation';
import { selectNotifications } from 'redux/selectors/subscriptions';
import SubHeader from './view';
const select = (state, props) => ({
currentPage: selectCurrentPage(state),
subLinks: selectHeaderLinks(state),
notifications: selectNotifications(state),
});
const perform = dispatch => ({

View file

@ -1,9 +1,15 @@
import React from 'react';
import Link from 'component/link';
import classnames from 'classnames';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
const SubHeader = props => {
const { subLinks, currentPage, navigate, fullWidth, smallMargin } = props;
const { subLinks, currentPage, navigate, fullWidth, smallMargin, notifications } = props;
const badges = Object.keys(notifications).reduce(
(acc, cur) => (notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING ? acc : acc + 1),
0
);
const links = [];
@ -14,7 +20,9 @@ const SubHeader = props => {
key={link}
className={link == currentPage ? 'sub-header-selected' : 'sub-header-unselected'}
>
{subLinks[link]}
{subLinks[link] === 'Subscriptions' && badges
? `Subscriptions (${badges})`
: subLinks[link]}
</Link>
);
}

View file

@ -165,6 +165,8 @@ export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST';
export const SET_SUBSCRIPTION_NOTIFICATION = 'SET_SUBSCRIPTION_NOTIFICATION';
export const SET_SUBSCRIPTION_NOTIFICATIONS = 'SET_SUBSCRIPTION_NOTIFICATIONS';
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';

View file

@ -0,0 +1,3 @@
export const DOWNLOADING = 'DOWNLOADING';
export const DOWNLOADED = 'DOWNLOADED';
export const NOTIFY_ONLY = 'NOTIFY_ONLY;';

View file

@ -4,18 +4,24 @@ import {
selectSubscriptionsFromClaims,
selectSubscriptions,
selectHasFetchedSubscriptions,
selectNotifications,
} from 'redux/selectors/subscriptions';
import { doFetchClaimsByChannel } from 'redux/actions/content';
import { setHasFetchedSubscriptions } from 'redux/actions/subscriptions';
import {
setHasFetchedSubscriptions,
setSubscriptionNotifications,
} from 'redux/actions/subscriptions';
import SubscriptionsPage from './view';
const select = state => ({
hasFetchedSubscriptions: state.subscriptions.hasFetchedSubscriptions,
savedSubscriptions: selectSubscriptions(state),
subscriptions: selectSubscriptionsFromClaims(state),
notifications: selectNotifications(state),
});
export default connect(select, {
doFetchClaimsByChannel,
setHasFetchedSubscriptions,
setSubscriptionNotifications,
})(SubscriptionsPage);

View file

@ -4,6 +4,7 @@ import SubHeader from 'component/subHeader';
import { BusyMessage } from 'component/common.js';
import { FeaturedCategory } from 'page/discover/view';
import type { Subscription } from 'redux/reducers/subscriptions';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
type SavedSubscriptions = Array<Subscription>;
@ -23,11 +24,23 @@ export default class extends React.PureComponent<Props> {
// that causes this component to be rendered with zero savedSubscriptions
// we need to wait until persist/REHYDRATE has fired before rendering the page
componentDidMount() {
const { savedSubscriptions, setHasFetchedSubscriptions } = this.props;
const {
savedSubscriptions,
setHasFetchedSubscriptions,
notifications,
setSubscriptionNotifications,
} = this.props;
if (savedSubscriptions.length) {
this.fetchSubscriptions(savedSubscriptions);
setHasFetchedSubscriptions();
}
const newNotifications = {};
Object.keys(notifications).forEach(cur => {
if (notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING) {
newNotifications[cur] = { ...notifications[cur] };
}
});
setSubscriptionNotifications(newNotifications);
}
componentWillReceiveProps(props: Props) {
@ -52,6 +65,7 @@ export default class extends React.PureComponent<Props> {
render() {
const { subscriptions, savedSubscriptions } = this.props;
// TODO: if you are subscribed to an empty channel, this will always be true (but it should not be)
const someClaimsNotLoaded = Boolean(
subscriptions.find(subscription => !subscription.claims.length)
);

View file

@ -1,13 +1,20 @@
import * as ACTIONS from 'constants/action_types';
import * as MODALS from 'constants/modal_types';
import * as SETTINGS from 'constants/settings';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
import { ipcRenderer } from 'electron';
import Lbry from 'lbry';
import Lbryio from 'lbryio';
import { normalizeURI, buildURI } from 'lbryURI';
import { doAlertError, doOpenModal } from 'redux/actions/app';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
import { setSubscriptionLatest } from 'redux/actions/subscriptions';
import { doNavigate } from 'redux/actions/navigation';
import {
setSubscriptionLatest,
setSubscriptionNotification,
setSubscriptionNotifications,
} from 'redux/actions/subscriptions';
import { selectNotifications } from 'redux/selectors/subscriptions';
import { selectBadgeNumber } from 'redux/selectors/app';
import { selectMyClaimsRaw } from 'redux/selectors/claims';
import { selectResolvingUris } from 'redux/selectors/content';
@ -164,13 +171,45 @@ export function doUpdateLoadStatus(uri, outpoint) {
const totalProgress = selectTotalDownloadProgress(getState());
setProgressBar(totalProgress);
const notif = new window.Notification('LBRY Download Complete', {
body: fileInfo.metadata.title,
silent: false,
});
notif.onclick = () => {
ipcRenderer.send('focusWindow', 'main');
};
const notifications = selectNotifications(getState());
if (notifications[uri] && notifications[uri].type === NOTIFICATION_TYPES.DOWNLOADING) {
const count = Object.keys(notifications).reduce(
(acc, cur) =>
notifications[cur].subscription.channelName ===
notifications[uri].subscription.channelName
? acc + 1
: acc,
0
);
const notif = new window.Notification(notifications[uri].subscription.channelName, {
body: `Posted ${fileInfo.metadata.title}${
count > 1 && count < 10 ? ` and ${count - 1} other new items` : ''
}${count > 9 ? ' and 9+ other new items' : ''}`,
silent: false,
});
notif.onclick = () => {
dispatch(
doNavigate('/show', {
uri,
})
);
};
dispatch(
setSubscriptionNotification(
notifications[uri].subscription,
uri,
NOTIFICATION_TYPES.DOWNLOADED
)
);
} else {
const notif = new window.Notification('LBRY Download Complete', {
body: fileInfo.metadata.title,
silent: false,
});
notif.onclick = () => {
ipcRenderer.send('focusWindow', 'main');
};
}
} else {
// ready to play
const { total_bytes: totalBytes, written_bytes: writtenBytes } = fileInfo;
@ -344,7 +383,7 @@ export function doPurchaseUri(uri, specificCostInfo) {
}
export function doFetchClaimsByChannel(uri, page) {
return dispatch => {
return (dispatch, getState) => {
dispatch({
type: ACTIONS.FETCH_CHANNEL_CLAIMS_STARTED,
data: { uri, page },
@ -371,6 +410,17 @@ export function doFetchClaimsByChannel(uri, page) {
buildURI({ contentName: latest.name, claimId: latest.claim_id }, false)
)
);
const notifications = selectNotifications(getState());
const newNotifications = {};
Object.keys(notifications).forEach(cur => {
if (
notifications[cur].subscription.channelName !== latest.channel_name ||
notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING
) {
newNotifications[cur] = { ...notifications[cur] };
}
});
dispatch(setSubscriptionNotifications(newNotifications));
}
dispatch({

View file

@ -1,6 +1,12 @@
// @flow
import * as ACTIONS from 'constants/action_types';
import type { Subscription, Dispatch, SubscriptionState } from 'redux/reducers/subscriptions';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
import type {
Subscription,
Dispatch,
SubscriptionState,
SubscriptionNotifications,
} from 'redux/reducers/subscriptions';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import Lbry from 'lbry';
import { doPurchaseUri } from 'redux/actions/content';
@ -9,6 +15,7 @@ import { buildURI } from 'lbryURI';
import analytics from 'analytics';
const CHECK_SUBSCRIPTIONS_INTERVAL = 60 * 60 * 1000;
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) => {
dispatch({
@ -59,63 +66,47 @@ export const doCheckSubscription = (subscription: Subscription, notify?: boolean
const claimResult = result[subscription.uri] || {};
const { claims_in_channel: claimsInChannel } = claimResult;
const count = subscription.latest
? claimsInChannel.reduce(
(prev, cur, index) =>
buildURI({ contentName: cur.name, claimId: cur.claim_id }, false) ===
subscription.latest
? index
: prev,
-1
)
: 1;
if (count !== 0 && notify) {
if (!claimsInChannel[0].value.stream.metadata.fee) {
dispatch(
doPurchaseUri(
buildURI(
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
false
),
{ cost: 0 }
)
);
if (claimsInChannel) {
if (notify) {
claimsInChannel.reduce((prev, cur, index) => {
const uri = buildURI({ contentName: cur.name, claimId: cur.claim_id }, false);
if (prev === -1 && uri !== subscription.latest) {
dispatch(
setSubscriptionNotification(
subscription,
uri,
index < SUBSCRIPTION_DOWNLOAD_LIMIT && !cur.value.stream.metadata.fee
? NOTIFICATION_TYPES.DOWNLOADING
: NOTIFICATION_TYPES.NOTIFY_ONLY
)
);
if (index < SUBSCRIPTION_DOWNLOAD_LIMIT && !cur.value.stream.metadata.fee) {
dispatch(doPurchaseUri(uri, { cost: 0 }));
}
}
return uri === subscription.latest || !subscription.latest ? index : prev;
}, -1);
}
const notif = new window.Notification(subscription.channelName, {
body: `Posted ${claimsInChannel[0].value.stream.metadata.title}${
count > 1 ? ` and ${count - 1} other new items` : ''
}${count < 0 ? ' and 9+ other new items' : ''}`,
silent: false,
});
notif.onclick = () => {
dispatch(
doNavigate('/show', {
dispatch(
setSubscriptionLatest(
{
channelName: claimsInChannel[0].channel_name,
uri: buildURI(
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
true
{
channelName: claimsInChannel[0].channel_name,
claimId: claimsInChannel[0].claim_id,
},
false
),
})
);
};
}
dispatch(
setSubscriptionLatest(
{
channelName: claimsInChannel[0].channel_name,
uri: buildURI(
{ channelName: claimsInChannel[0].channel_name, claimId: claimsInChannel[0].claim_id },
},
buildURI(
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
false
),
},
buildURI(
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
false
)
)
)
);
);
}
dispatch({
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
@ -135,5 +126,29 @@ export const setSubscriptionLatest = (subscription: Subscription, uri: string) =
},
});
export const setSubscriptionNotification = (
subscription: Subscription,
uri: string,
notificationType: string
) => (dispatch: Dispatch) =>
dispatch({
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
data: {
subscription,
uri,
type: notificationType,
},
});
export const setSubscriptionNotifications = (notifications: SubscriptionNotifications) => (
dispatch: Dispatch
) =>
dispatch({
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATIONS,
data: {
notifications,
},
});
export const setHasFetchedSubscriptions = () => (dispatch: Dispatch) =>
dispatch({ type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS });

View file

@ -1,5 +1,6 @@
// @flow
import * as ACTIONS from 'constants/action_types';
import * as NOTIFICATION_TYPES from 'constants/notification_types';
import { handleActions } from 'util/redux-utils';
export type Subscription = {
@ -8,10 +9,23 @@ export type Subscription = {
latest: ?string,
};
export type NotificationType =
| NOTIFICATION_TYPES.DOWNLOADING
| NOTIFICATION_TYPES.DOWNLOADED
| NOTIFICATION_TYPES.NOTIFY_ONLY;
export type SubscriptionNotifications = {
[string]: {
subscription: Subscription,
type: NotificationType,
},
};
// Subscription redux types
export type SubscriptionState = {
subscriptions: Array<Subscription>,
hasFetchedSubscriptions: boolean,
notifications: SubscriptionNotifications,
};
// Subscription action types
@ -37,6 +51,22 @@ type setSubscriptionLatest = {
},
};
type setSubscriptionNotification = {
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATION,
data: {
subscription: Subscription,
uri: string,
type: NotificationType,
},
};
type setSubscriptionNotifications = {
type: ACTIONS.SET_SUBSCRIPTION_NOTIFICATIONS,
data: {
notifications: SubscriptionNotifications,
},
};
type CheckSubscriptionStarted = {
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
};
@ -50,6 +80,7 @@ export type Action =
| doChannelUnsubscribe
| HasFetchedSubscriptions
| setSubscriptionLatest
| setSubscriptionNotification
| CheckSubscriptionStarted
| CheckSubscriptionCompleted
| Function;
@ -58,6 +89,7 @@ export type Dispatch = (action: Action) => any;
const defaultState = {
subscriptions: [],
hasFetchedSubscriptions: false,
notifications: {},
};
export default handleActions(
@ -106,6 +138,23 @@ export default handleActions(
: subscription
),
}),
[ACTIONS.SET_SUBSCRIPTION_NOTIFICATION]: (
state: SubscriptionState,
action: setSubscriptionNotification
): SubscriptionState => ({
...state,
notifications: {
...state.notifications,
[action.data.uri]: { subscription: action.data.subscription, type: action.data.type },
},
}),
[ACTIONS.SET_SUBSCRIPTION_NOTIFICATIONS]: (
state: SubscriptionState,
action: setSubscriptionNotifications
): SubscriptionState => ({
...state,
notifications: action.data.notifications,
}),
},
defaultState
);

View file

@ -4,6 +4,8 @@ import { selectAllClaimsByChannel, selectClaimsById } from './claims';
// get the entire subscriptions state
const selectState = state => state.subscriptions || {};
export const selectNotifications = createSelector(selectState, state => state.notifications);
// list of saved channel names and uris
export const selectSubscriptions = createSelector(selectState, state => state.subscriptions);
@ -23,7 +25,7 @@ export const selectSubscriptionsFromClaims = createSelector(
let channelClaims = [];
// if subscribed channel has content
if (channelIds[subscription.uri]) {
if (channelIds[subscription.uri] && channelIds[subscription.uri]['1']) {
// This will need to be more robust, we will want to be able to load more than the first page
const pageOneChannelIds = channelIds[subscription.uri]['1'];