diff --git a/src/renderer/component/fileList/view.jsx b/src/renderer/component/fileList/view.jsx
index f803ee141..eef0993c6 100644
--- a/src/renderer/component/fileList/view.jsx
+++ b/src/renderer/component/fileList/view.jsx
@@ -15,8 +15,8 @@ class FileList extends React.PureComponent {
this._sortFunctions = {
dateNew(fileInfos) {
return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
- const height1 = fileInfo1.height
- const height2 = fileInfo2.height
+ const height1 = fileInfo1.height;
+ const height2 = fileInfo2.height;
if (height1 > height2) {
return -1;
} else if (height1 < height2) {
@@ -27,8 +27,8 @@ class FileList extends React.PureComponent {
},
dateOld(fileInfos) {
return fileInfos.slice().sort((fileInfo1, fileInfo2) => {
- const height1 = fileInfo1.height
- const height2 = fileInfo2.height
+ const height1 = fileInfo1.height;
+ const height2 = fileInfo2.height;
if (height1 < height2) {
return -1;
} else if (height1 > height2) {
diff --git a/src/renderer/component/rewardSummary/view.jsx b/src/renderer/component/rewardSummary/view.jsx
index 36507d535..9fa611902 100644
--- a/src/renderer/component/rewardSummary/view.jsx
+++ b/src/renderer/component/rewardSummary/view.jsx
@@ -15,9 +15,9 @@ const RewardSummary = (props: Props) => {
{__('Rewards')}
- {__('Read our')}{' '}
- {__('FAQ')}{' '}{__('to learn more about LBRY Rewards')}.
-
+ {__('Read our')}
{__('FAQ')}{' '}
+ {__('to learn more about LBRY Rewards')}.
+
{unclaimedRewardAmount > 0 ? (
diff --git a/src/renderer/constants/action_types.js b/src/renderer/constants/action_types.js
index df8cf1407..584fa7a47 100644
--- a/src/renderer/constants/action_types.js
+++ b/src/renderer/constants/action_types.js
@@ -164,6 +164,10 @@ export const CLEAR_SHAPE_SHIFT = 'CLEAR_SHAPE_SHIFT';
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 CHECK_SUBSCRIPTION_STARTED = 'CHECK_SUBSCRIPTION_STARTED';
+export const CHECK_SUBSCRIPTION_COMPLETED = 'CHECK_SUBSCRIPTION_COMPLETED';
+export const CHECK_SUBSCRIPTIONS_SUBSCRIBE = 'CHECK_SUBSCRIPTIONS_SUBSCRIBE';
// Video controls
export const SET_VIDEO_PAUSE = 'SET_VIDEO_PAUSE';
diff --git a/src/renderer/index.js b/src/renderer/index.js
index a5eadda32..b4225577a 100644
--- a/src/renderer/index.js
+++ b/src/renderer/index.js
@@ -63,7 +63,7 @@ ipcRenderer.on('window-is-focused', () => {
document.addEventListener('dragover', event => {
event.preventDefault();
-})
+});
document.addEventListener('drop', event => {
event.preventDefault();
});
diff --git a/src/renderer/page/file/index.js b/src/renderer/page/file/index.js
index 4984ed812..37fb13c7f 100644
--- a/src/renderer/page/file/index.js
+++ b/src/renderer/page/file/index.js
@@ -4,6 +4,7 @@ import { doFetchFileInfo } from 'redux/actions/file_info';
import { makeSelectFileInfoForUri } from 'redux/selectors/file_info';
import { selectRewardContentClaimIds } from 'redux/selectors/content';
import { doFetchCostInfoForUri } from 'redux/actions/cost_info';
+import { checkSubscriptionLatest } from 'redux/actions/subscriptions';
import {
makeSelectClaimForUri,
makeSelectContentTypeForUri,
@@ -13,6 +14,7 @@ import { makeSelectCostInfoForUri } from 'redux/selectors/cost_info';
import { selectShowNsfw } from 'redux/selectors/settings';
import FilePage from './view';
import { makeSelectCurrentParam } from 'redux/selectors/navigation';
+import { selectSubscriptions } from 'redux/selectors/subscriptions';
const select = (state, props) => ({
claim: makeSelectClaimForUri(props.uri)(state),
@@ -23,12 +25,15 @@ const select = (state, props) => ({
tab: makeSelectCurrentParam('tab')(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
rewardedContentClaimIds: selectRewardContentClaimIds(state, props),
+ subscriptions: selectSubscriptions(state),
});
const perform = dispatch => ({
navigate: (path, params) => dispatch(doNavigate(path, params)),
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
+ checkSubscriptionLatest: (subscription, uri) =>
+ dispatch(checkSubscriptionLatest(subscription, uri)),
});
export default connect(select, perform)(FilePage);
diff --git a/src/renderer/page/file/view.jsx b/src/renderer/page/file/view.jsx
index e248ee5d6..069a0dfdc 100644
--- a/src/renderer/page/file/view.jsx
+++ b/src/renderer/page/file/view.jsx
@@ -17,6 +17,7 @@ class FilePage extends React.PureComponent {
componentDidMount() {
this.fetchFileInfo(this.props);
this.fetchCostInfo(this.props);
+ this.checkSubscriptionLatest(this.props);
}
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() {
const {
claim,
diff --git a/src/renderer/redux/actions/app.js b/src/renderer/redux/actions/app.js
index 1d9b55f8e..e67a5827e 100644
--- a/src/renderer/redux/actions/app.js
+++ b/src/renderer/redux/actions/app.js
@@ -11,6 +11,8 @@ import { doFetchDaemonSettings } from 'redux/actions/settings';
import { doAuthenticate } from 'redux/actions/user';
import { doBalanceSubscribe } from 'redux/actions/wallet';
import { doPause } from 'redux/actions/media';
+import { doCheckSubscriptions } from 'redux/actions/subscriptions';
+
import {
selectCurrentModal,
selectIsUpgradeSkipped,
@@ -253,6 +255,7 @@ export function doDaemonReady() {
dispatch(doCheckUpgradeAvailable());
}
dispatch(doCheckUpgradeSubscribe());
+ dispatch(doCheckSubscriptions());
};
}
diff --git a/src/renderer/redux/actions/content.js b/src/renderer/redux/actions/content.js
index 2ca2c4dbb..bdf831a03 100644
--- a/src/renderer/redux/actions/content.js
+++ b/src/renderer/redux/actions/content.js
@@ -7,6 +7,7 @@ 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 { selectBadgeNumber } from 'redux/selectors/app';
import { selectMyClaimsRaw } from 'redux/selectors/claims';
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) => {
const state = getState();
const balance = selectBalance(state);
@@ -321,7 +322,7 @@ export function doPurchaseUri(uri) {
return;
}
- const costInfo = makeSelectCostInfoForUri(uri)(state);
+ const costInfo = makeSelectCostInfoForUri(uri)(state) || specificCostInfo;
const { cost } = costInfo;
if (cost > balance) {
@@ -358,6 +359,25 @@ export function doFetchClaimsByChannel(uri, page) {
const claimResult = result[uri] || {};
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({
type: ACTIONS.FETCH_CHANNEL_CLAIMS_COMPLETED,
data: {
diff --git a/src/renderer/redux/actions/subscriptions.js b/src/renderer/redux/actions/subscriptions.js
index e3eb939ed..650f98a9b 100644
--- a/src/renderer/redux/actions/subscriptions.js
+++ b/src/renderer/redux/actions/subscriptions.js
@@ -1,6 +1,13 @@
// @flow
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) =>
dispatch({
@@ -14,5 +21,113 @@ export const doChannelUnsubscribe = (subscription: Subscription) => (dispatch: D
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) =>
dispatch({ type: ACTIONS.HAS_FETCHED_SUBSCRIPTIONS });
diff --git a/src/renderer/redux/reducers/subscriptions.js b/src/renderer/redux/reducers/subscriptions.js
index f7a846ffc..a8f40746f 100644
--- a/src/renderer/redux/reducers/subscriptions.js
+++ b/src/renderer/redux/reducers/subscriptions.js
@@ -5,6 +5,7 @@ import { handleActions } from 'util/redux-utils';
export type Subscription = {
channelName: string,
uri: string,
+ latest: ?string,
};
// Subscription redux types
@@ -28,7 +29,30 @@ type HasFetchedSubscriptions = {
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;
const defaultState = {
@@ -70,6 +94,18 @@ export default handleActions(
...state,
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
);