Subscriptions overhaul #1872
14 changed files with 168 additions and 116 deletions
|
@ -25,10 +25,7 @@ type Props = {
|
||||||
const SideBar = (props: Props) => {
|
const SideBar = (props: Props) => {
|
||||||
const { navLinks, notifications } = props;
|
const { navLinks, notifications } = props;
|
||||||
|
|
||||||
const badges = Object.keys(notifications).reduce(
|
const badges = Object.keys(notifications).length;
|
||||||
(acc, cur) => (notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING ? acc : acc + 1),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="nav">
|
<nav className="nav">
|
||||||
|
|
|
@ -15,3 +15,4 @@ export const AUTOMATIC_DARK_MODE_ENABLED = 'automaticDarkModeEnabled';
|
||||||
export const AUTOPLAY = 'autoplay';
|
export const AUTOPLAY = 'autoplay';
|
||||||
export const RESULT_COUNT = 'resultCount';
|
export const RESULT_COUNT = 'resultCount';
|
||||||
export const OS_NOTIFICATIONS_ENABLED = 'osNotificationsEnabled';
|
export const OS_NOTIFICATIONS_ENABLED = 'osNotificationsEnabled';
|
||||||
|
export const AUTO_DOWNLOAD = 'autoDownload';
|
||||||
|
|
|
@ -40,7 +40,7 @@ const perform = dispatch => ({
|
||||||
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
navigate: (path, params) => dispatch(doNavigate(path, params)),
|
||||||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||||
checkSubscription: subscription => dispatch(doCheckSubscription(subscription)),
|
checkSubscription: uri => dispatch(doCheckSubscription(uri)),
|
||||||
openModal: (modal, props) => dispatch(doNotify(modal, props)),
|
openModal: (modal, props) => dispatch(doNotify(modal, props)),
|
||||||
prepareEdit: (publishData, uri) => dispatch(doPrepareEdit(publishData, uri)),
|
prepareEdit: (publishData, uri) => dispatch(doPrepareEdit(publishData, uri)),
|
||||||
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
|
||||||
|
|
|
@ -44,7 +44,7 @@ type Props = {
|
||||||
fetchFileInfo: string => void,
|
fetchFileInfo: string => void,
|
||||||
fetchCostInfo: string => void,
|
fetchCostInfo: string => void,
|
||||||
prepareEdit: ({}, string) => void,
|
prepareEdit: ({}, string) => void,
|
||||||
checkSubscription: ({ channelName: string, uri: string }) => void,
|
checkSubscription: (uri: string) => void,
|
||||||
subscriptions: Array<Subscription>,
|
subscriptions: Array<Subscription>,
|
||||||
setClientSetting: (string, boolean | string) => void,
|
setClientSetting: (string, boolean | string) => void,
|
||||||
autoplay: boolean,
|
autoplay: boolean,
|
||||||
|
@ -96,16 +96,12 @@ class FilePage extends React.Component<Props> {
|
||||||
|
|
||||||
checkSubscription = (props: Props) => {
|
checkSubscription = (props: Props) => {
|
||||||
if (props.subscriptions.find(sub => sub.channelName === props.claim.channel_name)) {
|
if (props.subscriptions.find(sub => sub.channelName === props.claim.channel_name)) {
|
||||||
props.checkSubscription({
|
props.checkSubscription(
|
||||||
channelName: props.claim.channel_name,
|
buildURI({
|
||||||
uri: buildURI(
|
contentName: props.claim.channel_name,
|
||||||
{
|
claimId: props.claim.value.publisherSignature.certificateId,
|
||||||
contentName: props.claim.channel_name,
|
}, false)
|
||||||
claimId: props.claim.value.publisherSignature.certificateId,
|
);
|
||||||
},
|
|
||||||
false
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ const select = state => ({
|
||||||
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
autoplay: makeSelectClientSetting(settings.AUTOPLAY)(state),
|
||||||
walletEncrypted: selectWalletIsEncrypted(state),
|
walletEncrypted: selectWalletIsEncrypted(state),
|
||||||
osNotificationsEnabled: selectosNotificationsEnabled(state),
|
osNotificationsEnabled: selectosNotificationsEnabled(state),
|
||||||
|
autoDownload: makeSelectClientSetting(settings.AUTO_DOWNLOAD)(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
|
|
|
@ -31,6 +31,7 @@ type Props = {
|
||||||
themes: Array<string>,
|
themes: Array<string>,
|
||||||
automaticDarkModeEnabled: boolean,
|
automaticDarkModeEnabled: boolean,
|
||||||
autoplay: boolean,
|
autoplay: boolean,
|
||||||
|
autoDownload: boolean,
|
||||||
encryptWallet: () => void,
|
encryptWallet: () => void,
|
||||||
decryptWallet: () => void,
|
decryptWallet: () => void,
|
||||||
walletEncrypted: boolean,
|
walletEncrypted: boolean,
|
||||||
|
@ -59,6 +60,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
(this: any).onAutoplayChange = this.onAutoplayChange.bind(this);
|
(this: any).onAutoplayChange = this.onAutoplayChange.bind(this);
|
||||||
(this: any).clearCache = this.clearCache.bind(this);
|
(this: any).clearCache = this.clearCache.bind(this);
|
||||||
(this: any).onDesktopNotificationsChange = this.onDesktopNotificationsChange.bind(this);
|
(this: any).onDesktopNotificationsChange = this.onDesktopNotificationsChange.bind(this);
|
||||||
|
(this: any).onAutoDownloadChange = this.onAutoDownloadChange.bind(this);
|
||||||
// (this: any).onLanguageChange = this.onLanguageChange.bind(this)
|
// (this: any).onLanguageChange = this.onLanguageChange.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,6 +121,10 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
this.props.setClientSetting(settings.SHOW_NSFW, event.target.checked);
|
this.props.setClientSetting(settings.SHOW_NSFW, event.target.checked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onAutoDownloadChange(event: SyntheticInputEvent<*>) {
|
||||||
|
this.props.setClientSetting(settings.AUTO_DOWNLOAD, event.target.checked);
|
||||||
|
}
|
||||||
|
|
||||||
onChangeEncryptWallet() {
|
onChangeEncryptWallet() {
|
||||||
const { props } = this;
|
const { props } = this;
|
||||||
props.walletEncrypted ? props.decryptWallet() : props.encryptWallet();
|
props.walletEncrypted ? props.decryptWallet() : props.encryptWallet();
|
||||||
|
@ -157,6 +163,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
autoplay,
|
autoplay,
|
||||||
walletEncrypted,
|
walletEncrypted,
|
||||||
osNotificationsEnabled,
|
osNotificationsEnabled,
|
||||||
|
autoDownload,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
const noDaemonSettings = !daemonSettings || Object.keys(daemonSettings).length === 0;
|
||||||
|
@ -265,6 +272,13 @@ class SettingsPage extends React.PureComponent<Props, State> {
|
||||||
checked={autoplay}
|
checked={autoplay}
|
||||||
postfix={__('Autoplay media files')}
|
postfix={__('Autoplay media files')}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
type="checkbox"
|
||||||
|
name="auto_download"
|
||||||
|
onChange={this.onAutoDownloadChange}
|
||||||
|
checked={autoDownload}
|
||||||
|
postfix={__('Automatically download new content from your subscriptions')}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
name="show_nsfw"
|
name="show_nsfw"
|
||||||
|
|
|
@ -25,6 +25,9 @@ export default class extends React.PureComponent<Props> {
|
||||||
const { notifications, setSubscriptionNotifications, doFetchMySubscriptions } = this.props;
|
const { notifications, setSubscriptionNotifications, doFetchMySubscriptions } = this.props;
|
||||||
doFetchMySubscriptions();
|
doFetchMySubscriptions();
|
||||||
|
|
||||||
|
// @sean will change this behavior when implementing new content labeling
|
||||||
|
// notifications should be cleared individually
|
||||||
|
// do we want a way to clear individual claims without viewing?
|
||||||
const newNotifications = {};
|
const newNotifications = {};
|
||||||
Object.keys(notifications).forEach(cur => {
|
Object.keys(notifications).forEach(cur => {
|
||||||
if (notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING) {
|
if (notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { doFetchDaemonSettings } from 'redux/actions/settings';
|
||||||
import { doAuthNavigate } from 'redux/actions/navigation';
|
import { doAuthNavigate } from 'redux/actions/navigation';
|
||||||
import { doAuthenticate } from 'redux/actions/user';
|
import { doAuthenticate } from 'redux/actions/user';
|
||||||
import { doPause } from 'redux/actions/media';
|
import { doPause } from 'redux/actions/media';
|
||||||
import { doCheckSubscriptions } from 'redux/actions/subscriptions';
|
import { doCheckSubscriptionsInit } from 'redux/actions/subscriptions';
|
||||||
import {
|
import {
|
||||||
selectIsUpgradeSkipped,
|
selectIsUpgradeSkipped,
|
||||||
selectUpdateUrl,
|
selectUpdateUrl,
|
||||||
|
@ -347,7 +347,7 @@ export function doDaemonReady() {
|
||||||
dispatch(doCheckUpgradeAvailable());
|
dispatch(doCheckUpgradeAvailable());
|
||||||
}
|
}
|
||||||
dispatch(doCheckUpgradeSubscribe());
|
dispatch(doCheckUpgradeSubscribe());
|
||||||
dispatch(doCheckSubscriptions());
|
dispatch(doCheckSubscriptionsInit());
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
|
||||||
dispatch(doUpdateLoadStatus(uri, outpoint));
|
dispatch(doUpdateLoadStatus(uri, outpoint));
|
||||||
}, DOWNLOAD_POLL_INTERVAL);
|
}, DOWNLOAD_POLL_INTERVAL);
|
||||||
} else if (fileInfo.completed) {
|
} else if (fileInfo.completed) {
|
||||||
|
const state = getState();
|
||||||
// TODO this isn't going to get called if they reload the client before
|
// TODO this isn't going to get called if they reload the client before
|
||||||
// the download finished
|
// the download finished
|
||||||
dispatch({
|
dispatch({
|
||||||
|
@ -121,13 +122,13 @@ export function doUpdateLoadStatus(uri, outpoint) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const badgeNumber = selectBadgeNumber(getState());
|
const badgeNumber = selectBadgeNumber(state);
|
||||||
setBadge(badgeNumber === 0 ? '' : `${badgeNumber}`);
|
setBadge(badgeNumber === 0 ? '' : `${badgeNumber}`);
|
||||||
|
|
||||||
const totalProgress = selectTotalDownloadProgress(getState());
|
const totalProgress = selectTotalDownloadProgress(state);
|
||||||
setProgressBar(totalProgress);
|
setProgressBar(totalProgress);
|
||||||
|
|
||||||
const notifications = selectNotifications(getState());
|
const notifications = selectNotifications(state);
|
||||||
if (notifications[uri] && notifications[uri].type === NOTIFICATION_TYPES.DOWNLOADING) {
|
if (notifications[uri] && notifications[uri].type === NOTIFICATION_TYPES.DOWNLOADING) {
|
||||||
const count = Object.keys(notifications).reduce(
|
const count = Object.keys(notifications).reduce(
|
||||||
(acc, cur) =>
|
(acc, cur) =>
|
||||||
|
@ -138,7 +139,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
|
||||||
0
|
0
|
||||||
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (selectosNotificationsEnabled(getState())) {
|
if (selectosNotificationsEnabled(state)) {
|
||||||
const notif = new window.Notification(notifications[uri].subscription.channelName, {
|
const notif = new window.Notification(notifications[uri].subscription.channelName, {
|
||||||
body: `Posted ${fileInfo.metadata.title}${
|
body: `Posted ${fileInfo.metadata.title}${
|
||||||
count > 1 && count < 10 ? ` and ${count - 1} other new items` : ''
|
count > 1 && count < 10 ? ` and ${count - 1} other new items` : ''
|
||||||
|
@ -153,13 +154,15 @@ export function doUpdateLoadStatus(uri, outpoint) {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
dispatch(
|
if (state.navigation.currentPath !== '/subscriptions') {
|
||||||
setSubscriptionNotification(
|
dispatch(
|
||||||
notifications[uri].subscription,
|
setSubscriptionNotification(
|
||||||
uri,
|
notifications[uri].subscription,
|
||||||
NOTIFICATION_TYPES.DOWNLOADED
|
uri,
|
||||||
)
|
NOTIFICATION_TYPES.DOWNLOADED
|
||||||
);
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// If notifications are disabled(false) just return
|
// If notifications are disabled(false) just return
|
||||||
if (!selectosNotificationsEnabled(getState())) return;
|
if (!selectosNotificationsEnabled(getState())) return;
|
||||||
|
@ -376,17 +379,18 @@ export function doFetchClaimsByChannel(uri, page) {
|
||||||
buildURI({ contentName: latest.name, claimId: latest.claim_id }, false)
|
buildURI({ contentName: latest.name, claimId: latest.claim_id }, false)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
const notifications = selectNotifications(getState());
|
// commented out as a note for @sean, notification will be clared individually
|
||||||
const newNotifications = {};
|
// const notifications = selectNotifications(getState());
|
||||||
Object.keys(notifications).forEach(cur => {
|
// const newNotifications = {};
|
||||||
if (
|
// Object.keys(notifications).forEach(cur => {
|
||||||
notifications[cur].subscription.channelName !== latest.channel_name ||
|
// if (
|
||||||
notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING
|
// notifications[cur].subscription.channelName !== latest.channel_name ||
|
||||||
) {
|
// notifications[cur].type === NOTIFICATION_TYPES.DOWNLOADING
|
||||||
newNotifications[cur] = { ...notifications[cur] };
|
// ) {
|
||||||
}
|
// newNotifications[cur] = { ...notifications[cur] };
|
||||||
});
|
// }
|
||||||
dispatch(setSubscriptionNotifications(newNotifications));
|
// });
|
||||||
|
// dispatch(setSubscriptionNotifications(newNotifications));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
@ -40,11 +40,13 @@ export function doClaimRewardType(rewardType, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userIsRewardApproved && rewardType !== rewards.TYPE_CONFIRM_EMAIL) {
|
if (!userIsRewardApproved && rewardType !== rewards.TYPE_CONFIRM_EMAIL) {
|
||||||
const action = doNotify({
|
if (!options || !options.failSilently) {
|
||||||
id: MODALS.REWARD_APPROVAL_REQUIRED,
|
const action = doNotify({
|
||||||
isError: false,
|
id: MODALS.REWARD_APPROVAL_REQUIRED,
|
||||||
});
|
isError: false,
|
||||||
dispatch(action);
|
});
|
||||||
|
dispatch(action);
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,19 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as ACTIONS from 'constants/action_types';
|
import * as ACTIONS from 'constants/action_types';
|
||||||
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
import * as NOTIFICATION_TYPES from 'constants/notification_types';
|
||||||
import type {
|
import * as SETTINGS from 'constants/settings';
|
||||||
Dispatch,
|
import rewards from 'rewards';
|
||||||
SubscriptionState,
|
import type { Dispatch, SubscriptionNotifications } from 'redux/reducers/subscriptions';
|
||||||
SubscriptionNotifications,
|
|
||||||
} from 'redux/reducers/subscriptions';
|
|
||||||
import type { Subscription } from 'types/subscription';
|
import type { Subscription } from 'types/subscription';
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import { Lbry, buildURI, parseURI } from 'lbry-redux';
|
import { Lbry, buildURI, parseURI } from 'lbry-redux';
|
||||||
import { doPurchaseUri } from 'redux/actions/content';
|
import { doPurchaseUri } from 'redux/actions/content';
|
||||||
|
import { doClaimRewardType } from 'redux/actions/rewards';
|
||||||
import Promise from 'bluebird';
|
import Promise from 'bluebird';
|
||||||
import Lbryio from 'lbryio';
|
import Lbryio from 'lbryio';
|
||||||
|
|
||||||
const CHECK_SUBSCRIPTIONS_INTERVAL = 60 * 60 * 1000;
|
const CHECK_SUBSCRIPTIONS_INTERVAL = 15 * 60 * 1000;
|
||||||
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
|
const SUBSCRIPTION_DOWNLOAD_LIMIT = 1;
|
||||||
|
|
||||||
export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () => any) => {
|
export const doFetchMySubscriptions = () => (dispatch: Dispatch, getState: () => any) => {
|
||||||
|
@ -124,63 +124,83 @@ export const setSubscriptionNotification = (
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const doCheckSubscription = (subscription: Subscription, notify?: boolean) => (
|
export const doCheckSubscription = (subscriptionUri: string, notify?: boolean) => (
|
||||||
dispatch: Dispatch
|
dispatch: Dispatch,
|
||||||
|
getState: () => {}
|
||||||
) => {
|
) => {
|
||||||
dispatch({
|
// no dispatching FETCH_CHANNEL_CLAIMS_STARTED; causes loading issues on <SubscriptionsPage>
|
||||||
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
|
|
||||||
data: subscription,
|
|
||||||
});
|
|
||||||
|
|
||||||
Lbry.claim_list_by_channel({ uri: subscription.uri, page: 1 }).then(result => {
|
const state = getState();
|
||||||
const claimResult = result[subscription.uri] || {};
|
const savedSubscription = state.subscriptions.subscriptions.find(
|
||||||
|
sub => sub.uri === subscriptionUri
|
||||||
|
);
|
||||||
|
|
||||||
|
Lbry.claim_list_by_channel({ uri: subscriptionUri, page: 1 }).then(result => {
|
||||||
|
const claimResult = result[subscriptionUri] || {};
|
||||||
const { claims_in_channel: claimsInChannel } = claimResult;
|
const { claims_in_channel: claimsInChannel } = claimResult;
|
||||||
|
|
||||||
if (claimsInChannel) {
|
const latestIndex = claimsInChannel.findIndex(
|
||||||
if (notify) {
|
claim => `${claim.name}#${claim.claim_id}` === savedSubscription.latest
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(
|
// if latest is 0, nothing has changed
|
||||||
setSubscriptionLatest(
|
// when there is no subscription latest, it is either a newly subscriubed channel or
|
||||||
{
|
// the user has cleared their cache. Either way, do not download or notify about new content
|
||||||
channelName: claimsInChannel[0].channel_name,
|
// as that would download/notify 10 claims per channel
|
||||||
uri: buildURI(
|
if (claimsInChannel.length && latestIndex !== 0 && savedSubscription.latest) {
|
||||||
{
|
let downloadCount = 0;
|
||||||
channelName: claimsInChannel[0].channel_name,
|
claimsInChannel.slice(0, latestIndex === -1 ? 10 : latestIndex).forEach(claim => {
|
||||||
claimId: claimsInChannel[0].claim_id,
|
const uri = buildURI({ contentName: claim.name, claimId: claim.claim_id }, false);
|
||||||
},
|
const shouldDownload = Boolean(
|
||||||
false
|
downloadCount < SUBSCRIPTION_DOWNLOAD_LIMIT &&
|
||||||
),
|
!claim.value.stream.metadata.fee &&
|
||||||
},
|
makeSelectClientSetting(SETTINGS.AUTO_DOWNLOAD)(state)
|
||||||
buildURI(
|
);
|
||||||
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
|
if (notify) {
|
||||||
false
|
dispatch(
|
||||||
)
|
setSubscriptionNotification(
|
||||||
)
|
savedSubscription,
|
||||||
);
|
uri,
|
||||||
|
shouldDownload ? NOTIFICATION_TYPES.DOWNLOADING : NOTIFICATION_TYPES.NOTIFY_ONLY
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (shouldDownload) {
|
||||||
|
downloadCount += 1;
|
||||||
|
dispatch(doPurchaseUri(uri, { cost: 0 }));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// always setLatest; important for newly subscribed channels
|
||||||
|
dispatch(
|
||||||
|
setSubscriptionLatest(
|
||||||
|
{
|
||||||
|
channelName: claimsInChannel[0].channel_name,
|
||||||
|
uri: buildURI(
|
||||||
|
{
|
||||||
|
channelName: claimsInChannel[0].channel_name,
|
||||||
|
claimId: claimsInChannel[0].claim_id,
|
||||||
|
},
|
||||||
|
false
|
||||||
|
),
|
||||||
|
},
|
||||||
|
buildURI(
|
||||||
|
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
|
||||||
|
false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// calling FETCH_CHANNEL_CLAIMS_COMPLETED after not calling STARTED
|
||||||
|
// means it will delete a non-existant fetchingChannelClaims[uri]
|
||||||
dispatch({
|
dispatch({
|
||||||
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
|
type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED,
|
||||||
data: subscription,
|
data: {
|
||||||
|
uri: subscriptionUri,
|
||||||
|
claims: claimsInChannel || [],
|
||||||
|
page: 1,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -217,8 +237,11 @@ export const doChannelSubscribe = (subscription: Subscription) => (
|
||||||
channel_name: subscription.channelName,
|
channel_name: subscription.channelName,
|
||||||
claim_id: claimId,
|
claim_id: claimId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
dispatch(doClaimRewardType(rewards.SUBSCRIPTION, { failSilently: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// should be subUri
|
||||||
dispatch(doCheckSubscription(subscription, true));
|
dispatch(doCheckSubscription(subscription, true));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -244,15 +267,22 @@ export const doChannelUnsubscribe = (subscription: Subscription) => (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const doCheckSubscriptions = () => (
|
export const doCheckSubscriptions = () => (dispatch: Dispatch, getState: () => any) => {
|
||||||
dispatch: Dispatch,
|
const state = getState();
|
||||||
getState: () => SubscriptionState
|
const subscriptions = selectSubscriptions(state);
|
||||||
) => {
|
subscriptions.forEach((sub: Subscription) => {
|
||||||
|
dispatch(doCheckSubscription(sub.uri, true));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const doCheckSubscriptionsInit = () => (dispatch: Dispatch) => {
|
||||||
|
// doCheckSubscriptionsInit is called by doDaemonReady
|
||||||
|
// setTimeout below is a hack to ensure redux is hydrated when subscriptions are checked
|
||||||
|
// this will be replaced with <PersistGate> which reqiures a package upgrade
|
||||||
|
setTimeout(() => dispatch(doFetchMySubscriptions()), 5000);
|
||||||
|
setTimeout(() => dispatch(doCheckSubscriptions()), 10000);
|
||||||
const checkSubscriptionsTimer = setInterval(
|
const checkSubscriptionsTimer = setInterval(
|
||||||
() =>
|
() => dispatch(doCheckSubscriptions()),
|
||||||
selectSubscriptions(getState()).map((subscription: Subscription) =>
|
|
||||||
dispatch(doCheckSubscription(subscription, true))
|
|
||||||
),
|
|
||||||
CHECK_SUBSCRIPTIONS_INTERVAL
|
CHECK_SUBSCRIPTIONS_INTERVAL
|
||||||
);
|
);
|
||||||
dispatch({
|
dispatch({
|
||||||
|
|
|
@ -26,6 +26,7 @@ const defaultState = {
|
||||||
automaticDarkModeEnabled: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false),
|
automaticDarkModeEnabled: getLocalStorageSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false),
|
||||||
autoplay: getLocalStorageSetting(SETTINGS.AUTOPLAY, false),
|
autoplay: getLocalStorageSetting(SETTINGS.AUTOPLAY, false),
|
||||||
resultCount: Number(getLocalStorageSetting(SETTINGS.RESULT_COUNT, 50)),
|
resultCount: Number(getLocalStorageSetting(SETTINGS.RESULT_COUNT, 50)),
|
||||||
|
autoDownload: getLocalStorageSetting(SETTINGS.AUTO_DOWNLOAD, true),
|
||||||
osNotificationsEnabled: Boolean(
|
osNotificationsEnabled: Boolean(
|
||||||
getLocalStorageSetting(SETTINGS.OS_NOTIFICATIONS_ENABLED, true)
|
getLocalStorageSetting(SETTINGS.OS_NOTIFICATIONS_ENABLED, true)
|
||||||
),
|
),
|
||||||
|
|
|
@ -25,7 +25,7 @@ export const selectSubscriptionClaims = createSelector(
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchedSubscriptions = [];
|
let fetchedSubscriptions = [];
|
||||||
|
|
||||||
savedSubscriptions.forEach(subscription => {
|
savedSubscriptions.forEach(subscription => {
|
||||||
let channelClaims = [];
|
let channelClaims = [];
|
||||||
|
@ -39,18 +39,20 @@ export const selectSubscriptionClaims = createSelector(
|
||||||
// loop over the list of ids and grab the claim
|
// loop over the list of ids and grab the claim
|
||||||
pageOneChannelIds.forEach(id => {
|
pageOneChannelIds.forEach(id => {
|
||||||
const grabbedClaim = allClaims[id];
|
const grabbedClaim = allClaims[id];
|
||||||
channelClaims.push(grabbedClaim);
|
channelClaims = channelClaims.concat([grabbedClaim]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchedSubscriptions.push({
|
fetchedSubscriptions = fetchedSubscriptions.concat([
|
||||||
claims: channelClaims,
|
{
|
||||||
channelName: subscription.channelName,
|
claims: [...channelClaims],
|
||||||
uri: subscription.uri,
|
channelName: subscription.channelName,
|
||||||
});
|
uri: subscription.uri,
|
||||||
|
},
|
||||||
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
return fetchedSubscriptions;
|
return [...fetchedSubscriptions];
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ rewards.TYPE_FIRST_PUBLISH = 'first_publish';
|
||||||
rewards.TYPE_FEATURED_DOWNLOAD = 'featured_download';
|
rewards.TYPE_FEATURED_DOWNLOAD = 'featured_download';
|
||||||
rewards.TYPE_REFERRAL = 'referral';
|
rewards.TYPE_REFERRAL = 'referral';
|
||||||
rewards.YOUTUBE_CREATOR = 'youtube_creator';
|
rewards.YOUTUBE_CREATOR = 'youtube_creator';
|
||||||
|
rewards.SUBSCRIPTION = 'subscription';
|
||||||
|
|
||||||
rewards.claimReward = type => {
|
rewards.claimReward = type => {
|
||||||
function requestReward(resolve, reject, params) {
|
function requestReward(resolve, reject, params) {
|
||||||
|
|
Loading…
Reference in a new issue
I think the idea behind snackbars is that they are reserved for user actions, which wouldn't make sense for this use case. Maybe we should create our own notification component that mimics desktop notifications but has some additional style.
Not needed for this PR but I think we should stick with the OS notifications for now.