Merge pull request #1180 from lbryio/download-after
Notify after download, badges and fix error
This commit is contained in:
commit
433578c538
10 changed files with 217 additions and 66 deletions
|
@ -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 => ({
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
3
src/renderer/constants/notification_types.js
Normal file
3
src/renderer/constants/notification_types.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
export const DOWNLOADING = 'DOWNLOADING';
|
||||
export const DOWNLOADED = 'DOWNLOADED';
|
||||
export const NOTIFY_ONLY = 'NOTIFY_ONLY;';
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
);
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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 });
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
|
|
@ -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'];
|
||||
|
||||
|
|
Loading…
Reference in a new issue