Merge pull request #1066 from lbryio/subscribe-notify

Subscribe notify
This commit is contained in:
Liam Cardenas 2018-03-07 21:13:26 -08:00 committed by GitHub
commit 2342bad84b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 218 additions and 12 deletions

View file

@ -15,8 +15,8 @@ class FileList extends React.PureComponent {
this._sortFunctions = { this._sortFunctions = {
dateNew(fileInfos) { dateNew(fileInfos) {
return fileInfos.slice().sort((fileInfo1, fileInfo2) => { return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
const height1 = fileInfo1.height const height1 = fileInfo1.height;
const height2 = fileInfo2.height const height2 = fileInfo2.height;
if (height1 > height2) { if (height1 > height2) {
return -1; return -1;
} else if (height1 < height2) { } else if (height1 < height2) {
@ -27,8 +27,8 @@ class FileList extends React.PureComponent {
}, },
dateOld(fileInfos) { dateOld(fileInfos) {
return fileInfos.slice().sort((fileInfo1, fileInfo2) => { return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
const height1 = fileInfo1.height const height1 = fileInfo1.height;
const height2 = fileInfo2.height const height2 = fileInfo2.height;
if (height1 < height2) { if (height1 < height2) {
return -1; return -1;
} else if (height1 > height2) { } else if (height1 > height2) {

View file

@ -15,8 +15,8 @@ const RewardSummary = (props: Props) => {
<div className="card__title-primary"> <div className="card__title-primary">
<h3>{__('Rewards')}</h3> <h3>{__('Rewards')}</h3>
<p className="help"> <p className="help">
{__('Read our')}{' '} {__('Read our')} <Link href="https://lbry.io/faq/rewards">{__('FAQ')}</Link>{' '}
<Link href="https://lbry.io/faq/rewards">{__('FAQ')}</Link>{' '}{__('to learn more about LBRY Rewards')}. {__('to learn more about LBRY Rewards')}.
</p> </p>
</div> </div>
<div className="card__content"> <div className="card__content">

View file

@ -164,6 +164,10 @@ export const CLEAR_SHAPE_SHIFT = 'CLEAR_SHAPE_SHIFT';
export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE'; export const CHANNEL_SUBSCRIBE = 'CHANNEL_SUBSCRIBE';
export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE'; export const CHANNEL_UNSUBSCRIBE = 'CHANNEL_UNSUBSCRIBE';
export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS'; export const HAS_FETCHED_SUBSCRIPTIONS = 'HAS_FETCHED_SUBSCRIPTIONS';
export const SET_SUBSCRIPTION_LATEST = 'SET_SUBSCRIPTION_LATEST';
export const CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
// Video controls // Video controls
export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE'; export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';

View file

@ -63,7 +63,7 @@ ipcRenderer.on('window-is-focused', () => {
document.addEventListener('dragover', event => { document.addEventListener('dragover', event => {
event.preventDefault(); event.preventDefault();
}) });
document.addEventListener('drop', event => { document.addEventListener('drop', event => {
event.preventDefault(); event.preventDefault();
}); });

View file

@ -4,6 +4,7 @@ import { doFetchFileInfo } from 'redux/actions/file_info';
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info'; import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
import { selectRewardContentClaimIds } from 'redux/selectors/content'; import { selectRewardContentClaimIds } from 'redux/selectors/content';
import { doFetchCostInfoForUri } from 'redux/actions/cost_info'; import { doFetchCostInfoForUri } from 'redux/actions/cost_info';
import { checkSubscriptionLatest } from 'redux/actions/subscriptions';
import { import {
makeSelectClaimForUri, makeSelectClaimForUri,
makeSelectContentTypeForUri, makeSelectContentTypeForUri,
@ -13,6 +14,7 @@ import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
import { selectShowNsfw } from 'redux/selectors/settings'; import { selectShowNsfw } from 'redux/selectors/settings';
import FilePage from './view'; import FilePage from './view';
import { makeSelectCurrentParam } from 'redux/selectors/navigation'; import { makeSelectCurrentParam } from 'redux/selectors/navigation';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
const select = (state, props) => ({ const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state), claim: makeSelectClaimForUri(props.uri)(state),
@ -23,12 +25,15 @@ const select = (state, props) => ({
tab: makeSelectCurrentParam('tab')(state), tab: makeSelectCurrentParam('tab')(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state),
rewardedContentClaimIds: selectRewardContentClaimIds(state, props), rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
subscriptions: selectSubscriptions(state),
}); });
const perform = dispatch => ({ 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)),
checkSubscriptionLatest: (subscription, uri) =>
dispatch(checkSubscriptionLatest(subscription, uri)),
}); });
export default connect(select, perform)(FilePage); export default connect(select, perform)(FilePage);

View file

@ -17,6 +17,7 @@ class FilePage extends React.PureComponent {
componentDidMount() { componentDidMount() {
this.fetchFileInfo(this.props); this.fetchFileInfo(this.props);
this.fetchCostInfo(this.props); this.fetchCostInfo(this.props);
this.checkSubscriptionLatest(this.props);
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
@ -35,6 +36,28 @@ class FilePage extends React.PureComponent {
} }
} }
checkSubscriptionLatest(props) {
if (
props.subscriptions
.map(subscription => subscription.channelName)
.indexOf(props.claim.channel_name) !== -1
) {
props.checkSubscriptionLatest(
{
channelName: props.claim.channel_name,
uri: buildURI(
{
contentName: props.claim.channel_name,
claimId: props.claim.value.publisherSignature.certificateId,
},
false
),
},
buildURI({ contentName: props.claim.name, claimId: props.claim.claim_id }, false)
);
}
}
render() { render() {
const { const {
claim, claim,

View file

@ -11,6 +11,8 @@ import { doFetchDaemonSettings } from 'redux/actions/settings';
import { doAuthenticate } from 'redux/actions/user'; import { doAuthenticate } from 'redux/actions/user';
import { doBalanceSubscribe } from 'redux/actions/wallet'; import { doBalanceSubscribe } from 'redux/actions/wallet';
import { doPause } from 'redux/actions/media'; import { doPause } from 'redux/actions/media';
import { doCheckSubscriptions } from 'redux/actions/subscriptions';
import { import {
selectCurrentModal, selectCurrentModal,
selectIsUpgradeSkipped, selectIsUpgradeSkipped,
@ -253,6 +255,7 @@ export function doDaemonReady() {
dispatch(doCheckUpgradeAvailable()); dispatch(doCheckUpgradeAvailable());
} }
dispatch(doCheckUpgradeSubscribe()); dispatch(doCheckUpgradeSubscribe());
dispatch(doCheckSubscriptions());
}; };
} }

View file

@ -7,6 +7,7 @@ import Lbryio from 'lbryio';
import { normalizeURI, buildURI } from 'lbryURI'; import { normalizeURI, buildURI } from 'lbryURI';
import { doAlertError, doOpenModal } from 'redux/actions/app'; import { doAlertError, doOpenModal } from 'redux/actions/app';
import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards'; import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
import { setSubscriptionLatest } from 'redux/actions/subscriptions';
import { selectBadgeNumber } from 'redux/selectors/app'; import { selectBadgeNumber } from 'redux/selectors/app';
import { selectMyClaimsRaw } from 'redux/selectors/claims'; import { selectMyClaimsRaw } from 'redux/selectors/claims';
import { selectResolvingUris } from 'redux/selectors/content'; import { selectResolvingUris } from 'redux/selectors/content';
@ -288,7 +289,7 @@ export function doLoadVideo(uri) {
}; };
} }
export function doPurchaseUri(uri) { export function doPurchaseUri(uri, specificCostInfo) {
return (dispatch, getState) => { return (dispatch, getState) => {
const state = getState(); const state = getState();
const balance = selectBalance(state); const balance = selectBalance(state);
@ -321,7 +322,7 @@ export function doPurchaseUri(uri) {
return; return;
} }
const costInfo = makeSelectCostInfoForUri(uri)(state); const costInfo = makeSelectCostInfoForUri(uri)(state) || specificCostInfo;
const { cost } = costInfo; const { cost } = costInfo;
if (cost > balance) { if (cost > balance) {
@ -358,6 +359,25 @@ export function doFetchClaimsByChannel(uri, page) {
const claimResult = result[uri] || {}; const claimResult = result[uri] || {};
const { claims_in_channel: claimsInChannel, returned_page: returnedPage } = claimResult; const { claims_in_channel: claimsInChannel, returned_page: returnedPage } = claimResult;
if (claimsInChannel && claimsInChannel.length) {
const latest = claimsInChannel[0];
dispatch(
setSubscriptionLatest(
{
channelName: latest.channel_name,
uri: buildURI(
{
contentName: latest.channel_name,
claimId: latest.value.publisherSignature.certificateId,
},
false
),
},
buildURI({ contentName: latest.name, claimId: latest.claim_id }, false)
)
);
}
dispatch({ dispatch({
type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED, type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED,
data: { data: {

View file

@ -1,6 +1,13 @@
// @flow // @flow
import * as ACTIONS from 'constants/action_types'; import * as ACTIONS from 'constants/action_types';
import type { Subscription, Dispatch } from 'redux/reducers/subscriptions'; import type { Subscription, Dispatch, SubscriptionState } from 'redux/reducers/subscriptions';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import Lbry from 'lbry';
import { doPurchaseUri } from 'redux/actions/content';
import { doNavigate } from 'redux/actions/navigation';
import { buildURI } from 'lbryURI';
const CHECK_SUBSCRIPTIONS_INTERVAL = 10 * 60 * 1000;
export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) => export const doChannelSubscribe = (subscription: Subscription) => (dispatch: Dispatch) =>
dispatch({ dispatch({
@ -14,5 +21,113 @@ export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: D
data: subscription, data: subscription,
}); });
export const doCheckSubscriptions = () => (
dispatch: Dispatch,
getState: () => SubscriptionState
) => {
const checkSubscriptionsTimer = setInterval(
() =>
selectSubscriptions(getState()).map((subscription: Subscription) =>
dispatch(doCheckSubscription(subscription))
),
CHECK_SUBSCRIPTIONS_INTERVAL
);
dispatch({
type: ACTIONS.CHECK_SUBSCRIPTIONS_SUBSCRIBE,
data: { checkSubscriptionsTimer },
});
};
export const doCheckSubscription = (subscription: Subscription) => (dispatch: Dispatch) => {
dispatch({
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
data: subscription,
});
Lbry.claim_list_by_channel({ uri: subscription.uri, page: 1 }).then(result => {
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) {
if (!claimsInChannel[0].value.stream.metadata.fee) {
dispatch(
doPurchaseUri(
buildURI(
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
false
),
{ cost: 0 }
)
);
}
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', {
uri: buildURI(
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
true
),
})
);
};
}
//$FlowIssue
dispatch({
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
data: subscription,
});
});
};
export const checkSubscriptionLatest = (channel: Subscription, uri: string) => (
dispatch: Dispatch
) => {
Lbry.claim_list_by_channel({ uri: channel.uri, page: 1 }).then(result => {
const claimResult = result[channel.uri] || {};
const { claims_in_channel: claimsInChannel } = claimResult;
if (
claimsInChannel &&
claimsInChannel.length &&
buildURI(
{ contentName: claimsInChannel[0].name, claimId: claimsInChannel[0].claim_id },
false
) === uri
) {
dispatch(setSubscriptionLatest(channel, uri));
}
});
};
export const setSubscriptionLatest = (subscription: Subscription, uri: string) => (
dispatch: Dispatch
) =>
dispatch({
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
data: {
subscription,
uri,
},
});
export const setHasFetchedSubscriptions = () => (dispatch: Dispatch) => export const setHasFetchedSubscriptions = () => (dispatch: Dispatch) =>
dispatch({ type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS }); dispatch({ type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS });

View file

@ -5,6 +5,7 @@ import { handleActions } from 'util/redux-utils';
export type Subscription = { export type Subscription = {
channelName: string, channelName: string,
uri: string, uri: string,
latest: ?string,
}; };
// Subscription redux types // Subscription redux types
@ -28,7 +29,30 @@ type HasFetchedSubscriptions = {
type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS, type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS,
}; };
export type Action = doChannelSubscribe | doChannelUnsubscribe | HasFetchedSubscriptions; type setSubscriptionLatest = {
type: ACTIONS.SET_SUBSCRIPTION_LATEST,
data: {
subscription: Subscription,
uri: string,
},
};
type CheckSubscriptionStarted = {
type: ACTIONS.CHECK_SUBSCRIPTION_STARTED,
};
type CheckSubscriptionCompleted = {
type: ACTIONS.CHECK_SUBSCRIPTION_COMPLETED,
};
export type Action =
| doChannelSubscribe
| doChannelUnsubscribe
| HasFetchedSubscriptions
| setSubscriptionLatest
| CheckSubscriptionStarted
| CheckSubscriptionCompleted
| Function;
export type Dispatch = (action: Action) => any; export type Dispatch = (action: Action) => any;
const defaultState = { const defaultState = {
@ -70,6 +94,18 @@ export default handleActions(
...state, ...state,
hasFetchedSubscriptions: true, hasFetchedSubscriptions: true,
}), }),
[ACTIONS.SET_SUBSCRIPTION_LATEST]: (
state: SubscriptionState,
action: setSubscriptionLatest
): SubscriptionState => ({
...state,
subscriptions: state.subscriptions.map(
subscription =>
subscription.channelName === action.data.subscription.channelName
? { ...subscription, latest: action.data.uri }
: subscription
),
}),
}, },
defaultState defaultState
); );