Subscription notifications implementation (#407)
* add notifications for unread subscriptions
This commit is contained in:
parent
ec928c943d
commit
2c56c78467
19 changed files with 389 additions and 68 deletions
|
@ -6,7 +6,7 @@ import {
|
||||||
makeSelectLoadingForUri,
|
makeSelectLoadingForUri,
|
||||||
makeSelectCostInfoForUri
|
makeSelectCostInfoForUri
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doPurchaseUri, doStartDownload } from '../../redux/actions/file';
|
import { doPurchaseUri, doStartDownload } from 'redux/actions/file';
|
||||||
import FileDownloadButton from './view';
|
import FileDownloadButton from './view';
|
||||||
|
|
||||||
const select = (state, props) => ({
|
const select = (state, props) => ({
|
||||||
|
|
25
app/src/component/subscribeNotificationButton/index.js
Normal file
25
app/src/component/subscribeNotificationButton/index.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
doChannelSubscriptionEnableNotifications,
|
||||||
|
doChannelSubscriptionDisableNotifications,
|
||||||
|
selectEnabledChannelNotifications,
|
||||||
|
selectSubscriptions,
|
||||||
|
makeSelectIsSubscribed,
|
||||||
|
} from 'lbryinc';
|
||||||
|
import { doToast } from 'lbry-redux';
|
||||||
|
import SubscribeNotificationButton from './view';
|
||||||
|
|
||||||
|
const select = (state, props) => ({
|
||||||
|
enabledChannelNotifications: selectEnabledChannelNotifications(state),
|
||||||
|
subscriptions: selectSubscriptions(state),
|
||||||
|
isSubscribed: makeSelectIsSubscribed(props.uri, true)(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
{
|
||||||
|
doChannelSubscriptionEnableNotifications,
|
||||||
|
doChannelSubscriptionDisableNotifications,
|
||||||
|
doToast,
|
||||||
|
}
|
||||||
|
)(SubscribeNotificationButton);
|
55
app/src/component/subscribeNotificationButton/view.js
Normal file
55
app/src/component/subscribeNotificationButton/view.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { parseURI } from 'lbry-redux';
|
||||||
|
import { NativeModules, Text, View, TouchableOpacity } from 'react-native';
|
||||||
|
import Button from 'component/button';
|
||||||
|
import Colors from 'styles/colors';
|
||||||
|
|
||||||
|
class SubscribeNotificationButton extends React.PureComponent {
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
uri,
|
||||||
|
name,
|
||||||
|
doChannelSubscriptionEnableNotifications,
|
||||||
|
doChannelSubscriptionDisableNotifications,
|
||||||
|
doToast,
|
||||||
|
enabledChannelNotifications,
|
||||||
|
isSubscribed,
|
||||||
|
style
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
if (!isSubscribed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let styles = [];
|
||||||
|
if (style) {
|
||||||
|
if (style.length) {
|
||||||
|
styles = styles.concat(style);
|
||||||
|
} else {
|
||||||
|
styles.push(style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldNotify = enabledChannelNotifications.indexOf(name) > -1;
|
||||||
|
const { claimName } = parseURI(uri);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
style={styles}
|
||||||
|
theme={"light"}
|
||||||
|
icon={shouldNotify ? "bell-slash" : "bell"}
|
||||||
|
solid={true}
|
||||||
|
onPress={() => {
|
||||||
|
if (shouldNotify) {
|
||||||
|
doChannelSubscriptionDisableNotifications(name);
|
||||||
|
doToast({ message: 'You will not receive notifications for new content.' });
|
||||||
|
} else {
|
||||||
|
doChannelSubscriptionEnableNotifications(name);
|
||||||
|
doToast({ message: 'You will receive all notifications for new content.' });
|
||||||
|
}
|
||||||
|
}} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SubscribeNotificationButton;
|
|
@ -114,7 +114,7 @@ window.store = store;
|
||||||
const compressor = createCompressor();
|
const compressor = createCompressor();
|
||||||
const authFilter = createFilter('auth', ['authToken']);
|
const authFilter = createFilter('auth', ['authToken']);
|
||||||
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
const saveClaimsFilter = createFilter('claims', ['byId', 'claimsByUri']);
|
||||||
const subscriptionsFilter = createFilter('subscriptions', ['subscriptions']);
|
const subscriptionsFilter = createFilter('subscriptions', ['enabledChannelNotifications', 'subscriptions']);
|
||||||
const settingsFilter = createFilter('settings', ['clientSettings']);
|
const settingsFilter = createFilter('settings', ['clientSettings']);
|
||||||
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
||||||
|
|
||||||
|
|
|
@ -3,20 +3,32 @@ import {
|
||||||
doFetchFeaturedUris,
|
doFetchFeaturedUris,
|
||||||
selectBalance,
|
selectBalance,
|
||||||
selectFeaturedUris,
|
selectFeaturedUris,
|
||||||
selectFetchingFeaturedUris
|
selectFetchingFeaturedUris,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { doFetchRewardedContent } from 'lbryinc';
|
import {
|
||||||
|
doFetchRewardedContent,
|
||||||
|
doFetchMySubscriptions,
|
||||||
|
doRemoveUnreadSubscriptions,
|
||||||
|
selectEnabledChannelNotifications,
|
||||||
|
selectSubscriptionClaims,
|
||||||
|
selectUnreadSubscriptions,
|
||||||
|
} from 'lbryinc';
|
||||||
import DiscoverPage from './view';
|
import DiscoverPage from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
|
allSubscriptions: selectSubscriptionClaims(state),
|
||||||
balance: selectBalance(state),
|
balance: selectBalance(state),
|
||||||
|
enabledChannelNotifications: selectEnabledChannelNotifications(state),
|
||||||
featuredUris: selectFeaturedUris(state),
|
featuredUris: selectFeaturedUris(state),
|
||||||
fetchingFeaturedUris: selectFetchingFeaturedUris(state),
|
fetchingFeaturedUris: selectFetchingFeaturedUris(state),
|
||||||
|
unreadSubscriptions: selectUnreadSubscriptions(state),
|
||||||
});
|
});
|
||||||
|
|
||||||
const perform = dispatch => ({
|
const perform = dispatch => ({
|
||||||
fetchFeaturedUris: () => dispatch(doFetchFeaturedUris()),
|
fetchFeaturedUris: () => dispatch(doFetchFeaturedUris()),
|
||||||
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
||||||
|
fetchSubscriptions: () => dispatch(doFetchMySubscriptions()),
|
||||||
|
removeUnreadSubscriptions: () => dispatch(doRemoveUnreadSubscriptions()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(DiscoverPage);
|
export default connect(select, perform)(DiscoverPage);
|
|
@ -8,7 +8,7 @@ import {
|
||||||
Text,
|
Text,
|
||||||
View
|
View
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { normalizeURI } from 'lbry-redux';
|
import { normalizeURI, parseURI } from 'lbry-redux';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Colors from '../../styles/colors';
|
import Colors from '../../styles/colors';
|
||||||
import discoverStyle from '../../styles/discover';
|
import discoverStyle from '../../styles/discover';
|
||||||
|
@ -41,9 +41,66 @@ class DiscoverPage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { fetchFeaturedUris, fetchRewardedContent } = this.props;
|
const {
|
||||||
|
fetchFeaturedUris,
|
||||||
|
fetchRewardedContent,
|
||||||
|
fetchSubscriptions
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
fetchFeaturedUris();
|
fetchFeaturedUris();
|
||||||
fetchRewardedContent();
|
fetchRewardedContent();
|
||||||
|
fetchSubscriptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriptionForUri = (uri, channelName) => {
|
||||||
|
const { allSubscriptions } = this.props;
|
||||||
|
const { claimId, claimName } = parseURI(uri);
|
||||||
|
|
||||||
|
if (allSubscriptions) {
|
||||||
|
for (let i = 0; i < allSubscriptions.length; i++) {
|
||||||
|
const sub = allSubscriptions[i];
|
||||||
|
if (sub.claim_id === claimId && sub.name === claimName && sub.channel_name === channelName) {
|
||||||
|
return sub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
const { allSubscriptions, unreadSubscriptions, enabledChannelNotifications } = this.props;
|
||||||
|
|
||||||
|
const utility = NativeModules.UtilityModule;
|
||||||
|
if (utility) {
|
||||||
|
const hasUnread = prevProps.unreadSubscriptions &&
|
||||||
|
prevProps.unreadSubscriptions.length !== unreadSubscriptions.length &&
|
||||||
|
unreadSubscriptions.length > 0;
|
||||||
|
|
||||||
|
if (hasUnread) {
|
||||||
|
unreadSubscriptions.map(({ channel, uris }) => {
|
||||||
|
const { claimName: channelName } = parseURI(channel);
|
||||||
|
|
||||||
|
// check if notifications are enabled for the channel
|
||||||
|
if (enabledChannelNotifications.indexOf(channelName) > -1) {
|
||||||
|
uris.forEach(uri => {
|
||||||
|
const sub = this.subscriptionForUri(uri, channelName);
|
||||||
|
if (sub && sub.value && sub.value.stream) {
|
||||||
|
let isPlayable = false;
|
||||||
|
const source = sub.value.stream.source;
|
||||||
|
const metadata = sub.value.stream.metadata;
|
||||||
|
if (source) {
|
||||||
|
isPlayable = source.contentType && ['audio', 'video'].indexOf(source.contentType.substring(0, 5)) > -1;
|
||||||
|
}
|
||||||
|
if (metadata) {
|
||||||
|
utility.showNotificationForContent(uri, metadata.title, channelName, metadata.thumbnail, isPlayable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
selectBlackListedOutpoints,
|
selectBlackListedOutpoints,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectRewardContentClaimIds } from 'lbryinc';
|
import { selectRewardContentClaimIds } from 'lbryinc';
|
||||||
import { doDeleteFile, doStopDownloadingFile } from '../../redux/actions/file';
|
import { doDeleteFile, doPurchaseUri, doStopDownloadingFile } from 'redux/actions/file';
|
||||||
import FilePage from './view';
|
import FilePage from './view';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
|
@ -44,6 +44,7 @@ const perform = dispatch => ({
|
||||||
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
fetchFileInfo: uri => dispatch(doFetchFileInfo(uri)),
|
||||||
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
fetchCostInfo: uri => dispatch(doFetchCostInfoForUri(uri)),
|
||||||
notify: data => dispatch(doToast(data)),
|
notify: data => dispatch(doToast(data)),
|
||||||
|
purchaseUri: (uri, failureCallback) => dispatch(doPurchaseUri(uri, null, failureCallback)),
|
||||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||||
sendTip: (amount, claimId, uri, successCallback, errorCallback) => dispatch(doSendTip(amount, claimId, uri, successCallback, errorCallback)),
|
sendTip: (amount, claimId, uri, successCallback, errorCallback) => dispatch(doSendTip(amount, claimId, uri, successCallback, errorCallback)),
|
||||||
stopDownload: (uri, fileInfo) => dispatch(doStopDownloadingFile(uri, fileInfo)),
|
stopDownload: (uri, fileInfo) => dispatch(doStopDownloadingFile(uri, fileInfo)),
|
||||||
|
|
|
@ -15,23 +15,24 @@ import {
|
||||||
View,
|
View,
|
||||||
WebView
|
WebView
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { navigateToUri } from '../../utils/helper';
|
import { navigateToUri } from 'utils/helper';
|
||||||
import ImageViewer from 'react-native-image-zoom-viewer';
|
import ImageViewer from 'react-native-image-zoom-viewer';
|
||||||
import Button from '../../component/button';
|
import Button from 'component/button';
|
||||||
import Colors from '../../styles/colors';
|
import Colors from 'styles/colors';
|
||||||
import ChannelPage from '../channel';
|
import ChannelPage from 'page/channel';
|
||||||
import FileDownloadButton from '../../component/fileDownloadButton';
|
import FileDownloadButton from 'component/fileDownloadButton';
|
||||||
import FileItemMedia from '../../component/fileItemMedia';
|
import FileItemMedia from 'component/fileItemMedia';
|
||||||
import FilePrice from '../../component/filePrice';
|
import FilePrice from 'component/filePrice';
|
||||||
import FloatingWalletBalance from '../../component/floatingWalletBalance';
|
import FloatingWalletBalance from 'component/floatingWalletBalance';
|
||||||
import Link from '../../component/link';
|
import Link from 'component/link';
|
||||||
import MediaPlayer from '../../component/mediaPlayer';
|
import MediaPlayer from 'component/mediaPlayer';
|
||||||
import RelatedContent from '../../component/relatedContent';
|
import RelatedContent from 'component/relatedContent';
|
||||||
import SubscribeButton from '../../component/subscribeButton';
|
import SubscribeButton from 'component/subscribeButton';
|
||||||
import UriBar from '../../component/uriBar';
|
import SubscribeNotificationButton from 'component/subscribeNotificationButton';
|
||||||
|
import UriBar from 'component/uriBar';
|
||||||
import Video from 'react-native-video';
|
import Video from 'react-native-video';
|
||||||
import filePageStyle from '../../styles/filePage';
|
import filePageStyle from 'styles/filePage';
|
||||||
import uriBarStyle from '../../styles/uriBar';
|
import uriBarStyle from 'styles/uriBar';
|
||||||
|
|
||||||
class FilePage extends React.PureComponent {
|
class FilePage extends React.PureComponent {
|
||||||
static navigationOptions = {
|
static navigationOptions = {
|
||||||
|
@ -48,6 +49,7 @@ class FilePage extends React.PureComponent {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
autoPlayMedia: false,
|
autoPlayMedia: false,
|
||||||
|
autoDownloadStarted: false,
|
||||||
downloadButtonShown: false,
|
downloadButtonShown: false,
|
||||||
downloadPressed: false,
|
downloadPressed: false,
|
||||||
fileViewLogged: false,
|
fileViewLogged: false,
|
||||||
|
@ -63,6 +65,7 @@ class FilePage extends React.PureComponent {
|
||||||
playerHeight: 0,
|
playerHeight: 0,
|
||||||
tipAmount: null,
|
tipAmount: null,
|
||||||
uri: null,
|
uri: null,
|
||||||
|
uriVars: null,
|
||||||
stopDownloadConfirmed: false
|
stopDownloadConfirmed: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -71,8 +74,8 @@ class FilePage extends React.PureComponent {
|
||||||
StatusBar.setHidden(false);
|
StatusBar.setHidden(false);
|
||||||
|
|
||||||
const { isResolvingUri, resolveUri, navigation } = this.props;
|
const { isResolvingUri, resolveUri, navigation } = this.props;
|
||||||
const { uri } = navigation.state.params;
|
const { uri, uriVars } = navigation.state.params;
|
||||||
this.setState({ uri });
|
this.setState({ uri, uriVars });
|
||||||
|
|
||||||
if (!isResolvingUri) resolveUri(uri);
|
if (!isResolvingUri) resolveUri(uri);
|
||||||
|
|
||||||
|
@ -330,6 +333,13 @@ class FilePage extends React.PureComponent {
|
||||||
sendTip(tipAmount, claim.claim_id, uri, () => { this.setState({ tipAmount: 0, showTipView: false }) });
|
sendTip(tipAmount, claim.claim_id, uri, () => { this.setState({ tipAmount: 0, showTipView: false }) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
startDownloadFailed = () => {
|
||||||
|
this.startTime = null;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setState({ downloadPressed: false, fileViewLogged: false, mediaLoaded: false });
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
claim,
|
claim,
|
||||||
|
@ -341,7 +351,8 @@ class FilePage extends React.PureComponent {
|
||||||
rewardedContentClaimIds,
|
rewardedContentClaimIds,
|
||||||
isResolvingUri,
|
isResolvingUri,
|
||||||
blackListedOutpoints,
|
blackListedOutpoints,
|
||||||
navigation
|
navigation,
|
||||||
|
purchaseUri
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { uri, autoplay } = navigation.state.params;
|
const { uri, autoplay } = navigation.state.params;
|
||||||
|
|
||||||
|
@ -435,6 +446,12 @@ class FilePage extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fileInfo && !this.state.autoDownloadStarted && this.state.uriVars && 'true' === this.state.uriVars.download) {
|
||||||
|
this.setState({ autoDownloadStarted: true }, () => {
|
||||||
|
purchaseUri(uri, this.startDownloadFailed);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
innerContent = (
|
innerContent = (
|
||||||
<View style={filePageStyle.pageContainer}>
|
<View style={filePageStyle.pageContainer}>
|
||||||
{this.state.showWebView && isWebViewable && <WebView source={{ uri: localFileUri }}
|
{this.state.showWebView && isWebViewable && <WebView source={{ uri: localFileUri }}
|
||||||
|
@ -462,12 +479,7 @@ class FilePage extends React.PureComponent {
|
||||||
this.setState({ downloadPressed: true, autoPlayMedia: true, stopDownloadConfirmed: false });
|
this.setState({ downloadPressed: true, autoPlayMedia: true, stopDownloadConfirmed: false });
|
||||||
}}
|
}}
|
||||||
onButtonLayout={() => this.setState({ downloadButtonShown: true })}
|
onButtonLayout={() => this.setState({ downloadButtonShown: true })}
|
||||||
onStartDownloadFailed={() => {
|
onStartDownloadFailed={this.startDownloadFailed} />}
|
||||||
this.startTime = null;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.setState({ downloadPressed: false, fileViewLogged: false, mediaLoaded: false });
|
|
||||||
}, 500);
|
|
||||||
}} />}
|
|
||||||
{!fileInfo && <FilePrice uri={uri} style={filePageStyle.filePriceContainer} textStyle={filePageStyle.filePriceText} />}
|
{!fileInfo && <FilePrice uri={uri} style={filePageStyle.filePriceContainer} textStyle={filePageStyle.filePriceText} />}
|
||||||
</View>
|
</View>
|
||||||
{canLoadMedia && fileInfo && <View style={playerBgStyle}
|
{canLoadMedia && fileInfo && <View style={playerBgStyle}
|
||||||
|
@ -496,14 +508,11 @@ class FilePage extends React.PureComponent {
|
||||||
{showActions &&
|
{showActions &&
|
||||||
<View style={filePageStyle.actions}>
|
<View style={filePageStyle.actions}>
|
||||||
<View style={filePageStyle.socialActions}>
|
<View style={filePageStyle.socialActions}>
|
||||||
{channelName && <SubscribeButton
|
<Button style={filePageStyle.actionButton}
|
||||||
style={[filePageStyle.actionButton, filePageStyle.subscribeButton]}
|
|
||||||
uri={channelUri} name={channelName} />}
|
|
||||||
{<Button style={filePageStyle.actionButton}
|
|
||||||
theme={"light"}
|
theme={"light"}
|
||||||
icon={"gift"}
|
icon={"gift"}
|
||||||
text={"Send a tip"}
|
text={"Send a tip"}
|
||||||
onPress={() => this.setState({ showTipView: true })} />}
|
onPress={() => this.setState({ showTipView: true })} />
|
||||||
</View>
|
</View>
|
||||||
{showFileActions &&
|
{showFileActions &&
|
||||||
<View style={filePageStyle.fileActions}>
|
<View style={filePageStyle.fileActions}>
|
||||||
|
@ -526,13 +535,27 @@ class FilePage extends React.PureComponent {
|
||||||
style={showActions ? filePageStyle.scrollContainerActions : filePageStyle.scrollContainer}
|
style={showActions ? filePageStyle.scrollContainerActions : filePageStyle.scrollContainer}
|
||||||
contentContainerstyle={showActions ? null : filePageStyle.scrollContent}>
|
contentContainerstyle={showActions ? null : filePageStyle.scrollContent}>
|
||||||
<Text style={filePageStyle.title} selectable={true}>{title}</Text>
|
<Text style={filePageStyle.title} selectable={true}>{title}</Text>
|
||||||
{channelName && <Link style={filePageStyle.channelName}
|
{channelName &&
|
||||||
|
<View style={filePageStyle.channelRow}>
|
||||||
|
<Link style={filePageStyle.channelName}
|
||||||
selectable={true}
|
selectable={true}
|
||||||
text={channelName}
|
text={channelName}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
const channelUri = normalizeURI(channelName);
|
const channelUri = normalizeURI(channelName);
|
||||||
navigateToUri(navigation, channelUri);
|
navigateToUri(navigation, channelUri);
|
||||||
}} />}
|
}} />
|
||||||
|
<View style={filePageStyle.subscriptionRow}>
|
||||||
|
<SubscribeButton
|
||||||
|
style={filePageStyle.actionButton}
|
||||||
|
uri={channelUri}
|
||||||
|
name={channelName} />
|
||||||
|
<SubscribeNotificationButton
|
||||||
|
style={[filePageStyle.actionButton, filePageStyle.bellButton]}
|
||||||
|
uri={channelUri}
|
||||||
|
name={channelName} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
|
||||||
{description && description.length > 0 && <View style={filePageStyle.divider} />}
|
{description && description.length > 0 && <View style={filePageStyle.divider} />}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { doBalanceSubscribe, doBlackListedOutpointsSubscribe, doToast } from 'lb
|
||||||
import {
|
import {
|
||||||
doAuthenticate,
|
doAuthenticate,
|
||||||
doCheckSubscriptionsInit,
|
doCheckSubscriptionsInit,
|
||||||
|
doFetchMySubscriptions,
|
||||||
doFetchRewardedContent,
|
doFetchRewardedContent,
|
||||||
doUserEmailToVerify,
|
doUserEmailToVerify,
|
||||||
doUserEmailVerify,
|
doUserEmailVerify,
|
||||||
|
@ -25,10 +26,11 @@ const perform = dispatch => ({
|
||||||
checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()),
|
checkSubscriptionsInit: () => dispatch(doCheckSubscriptionsInit()),
|
||||||
deleteCompleteBlobs: () => dispatch(doDeleteCompleteBlobs()),
|
deleteCompleteBlobs: () => dispatch(doDeleteCompleteBlobs()),
|
||||||
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
||||||
|
fetchSubscriptions: (callback) => dispatch(doFetchMySubscriptions(callback)),
|
||||||
notify: data => dispatch(doToast(data)),
|
notify: data => dispatch(doToast(data)),
|
||||||
setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),
|
setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),
|
||||||
verifyUserEmail: (token, recaptcha) => dispatch(doUserEmailVerify(token, recaptcha)),
|
verifyUserEmail: (token, recaptcha) => dispatch(doUserEmailVerify(token, recaptcha)),
|
||||||
verifyUserEmailFailure: error => dispatch(doUserEmailVerifyFailure(error)),
|
verifyUserEmailFailure: error => dispatch(doUserEmailVerifyFailure(error))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(select, perform)(SplashScreen);
|
export default connect(select, perform)(SplashScreen);
|
||||||
|
|
|
@ -33,7 +33,8 @@ class SplashScreen extends React.PureComponent {
|
||||||
launchUrl: null,
|
launchUrl: null,
|
||||||
isDownloadingHeaders: false,
|
isDownloadingHeaders: false,
|
||||||
headersDownloadProgress: 0,
|
headersDownloadProgress: 0,
|
||||||
shouldAuthenticate: false
|
shouldAuthenticate: false,
|
||||||
|
subscriptionsFetched: false
|
||||||
});
|
});
|
||||||
|
|
||||||
if (NativeModules.DaemonServiceControl) {
|
if (NativeModules.DaemonServiceControl) {
|
||||||
|
@ -119,7 +120,7 @@ class SplashScreen extends React.PureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateStatusCallback(status) {
|
_updateStatusCallback(status) {
|
||||||
const { deleteCompleteBlobs } = this.props;
|
const { deleteCompleteBlobs, fetchSubscriptions } = this.props;
|
||||||
const startupStatus = status.startup_status;
|
const startupStatus = status.startup_status;
|
||||||
// At the minimum, wallet should be started and blocks_behind equal to 0 before calling resolve
|
// At the minimum, wallet should be started and blocks_behind equal to 0 before calling resolve
|
||||||
const hasStarted = startupStatus.file_manager && startupStatus.wallet && status.wallet.blocks_behind <= 0;
|
const hasStarted = startupStatus.file_manager && startupStatus.wallet && status.wallet.blocks_behind <= 0;
|
||||||
|
@ -138,6 +139,7 @@ class SplashScreen extends React.PureComponent {
|
||||||
isRunning: true,
|
isRunning: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// fetch subscriptions, so that we can check for new content after resolve
|
||||||
Lbry.resolve({ uri: 'lbry://one' }).then(() => {
|
Lbry.resolve({ uri: 'lbry://one' }).then(() => {
|
||||||
// Leave the splash screen
|
// Leave the splash screen
|
||||||
const {
|
const {
|
||||||
|
|
|
@ -30,7 +30,7 @@ const downloadsStyle = StyleSheet.create({
|
||||||
noDownloadsText: {
|
noDownloadsText: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontFamily: 'Inter-UI-Regular',
|
fontFamily: 'Inter-UI-Regular',
|
||||||
fontSize: 14,
|
fontSize: 16,
|
||||||
position: 'absolute'
|
position: 'absolute'
|
||||||
},
|
},
|
||||||
loading: {
|
loading: {
|
||||||
|
|
|
@ -57,14 +57,23 @@ const filePageStyle = StyleSheet.create({
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
marginLeft: 20,
|
marginLeft: 20,
|
||||||
marginRight: 20,
|
marginRight: 20,
|
||||||
marginBottom: 12
|
marginBottom: 8
|
||||||
|
},
|
||||||
|
channelRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
marginLeft: 20,
|
||||||
|
marginRight: 20,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
subscriptionRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignSelf: 'flex-end'
|
||||||
},
|
},
|
||||||
channelName: {
|
channelName: {
|
||||||
fontFamily: 'Inter-UI-SemiBold',
|
fontFamily: 'Inter-UI-SemiBold',
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
marginLeft: 20,
|
marginTop: 6,
|
||||||
marginRight: 20,
|
|
||||||
marginBottom: 20,
|
|
||||||
color: Colors.LbryGreen
|
color: Colors.LbryGreen
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
|
@ -164,8 +173,8 @@ const filePageStyle = StyleSheet.create({
|
||||||
paddingLeft: 16,
|
paddingLeft: 16,
|
||||||
paddingRight: 16
|
paddingRight: 16
|
||||||
},
|
},
|
||||||
subscribeButton: {
|
bellButton: {
|
||||||
marginRight: 8
|
marginLeft: 8
|
||||||
},
|
},
|
||||||
loading: {
|
loading: {
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|
|
@ -24,7 +24,14 @@ export function dispatchNavigateToUri(dispatch, nav, uri) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = { uri };
|
let uriVars = {};
|
||||||
|
if (uri.indexOf('?') > -1) {
|
||||||
|
uriVarsStr = uri.substring(uri.indexOf('?') + 1);
|
||||||
|
uri = uri.substring(0, uri.indexOf('?'));
|
||||||
|
uriVars = parseUriVars(uriVarsStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = { uri, uriVars };
|
||||||
if (nav && nav.routes && nav.routes.length > 0 && 'Main' === nav.routes[0].routeName) {
|
if (nav && nav.routes && nav.routes.length > 0 && 'Main' === nav.routes[0].routeName) {
|
||||||
const mainRoute = nav.routes[0];
|
const mainRoute = nav.routes[0];
|
||||||
const discoverRoute = mainRoute.routes[0];
|
const discoverRoute = mainRoute.routes[0];
|
||||||
|
@ -62,6 +69,23 @@ export function formatBytes(bytes, decimalPoints = 0) {
|
||||||
return `${value} GB`;
|
return `${value} GB`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseUriVars(vars) {
|
||||||
|
const uriVars = {};
|
||||||
|
const parts = vars.split('&');
|
||||||
|
for (let i = 0; i < parts.length; i++) {
|
||||||
|
const str = parts[i];
|
||||||
|
if (str.indexOf('=') > -1) {
|
||||||
|
const key = str.substring(0, str.indexOf('='));
|
||||||
|
const value = str.substring(str.indexOf('=') + 1);
|
||||||
|
uriVars[key] = value;
|
||||||
|
} else {
|
||||||
|
uriVars[str] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return uriVars;
|
||||||
|
}
|
||||||
|
|
||||||
export function navigateToUri(navigation, uri, additionalParams) {
|
export function navigateToUri(navigation, uri, additionalParams) {
|
||||||
if (!navigation) {
|
if (!navigation) {
|
||||||
return;
|
return;
|
||||||
|
@ -76,7 +100,14 @@ export function navigateToUri(navigation, uri, additionalParams) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = Object.assign({ uri }, additionalParams);
|
let uriVars = {};
|
||||||
|
if (uri.indexOf('?') > -1) {
|
||||||
|
uriVarsStr = uri.substring(uri.indexOf('?') + 1);
|
||||||
|
uri = uri.substring(0, uri.indexOf('?'));
|
||||||
|
uriVars = parseUriVars(uriVarsStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = Object.assign({ uri, uriVars }, additionalParams);
|
||||||
if ('File' === navigation.state.routeName) {
|
if ('File' === navigation.state.routeName) {
|
||||||
const stackAction = StackActions.replace({ routeName: 'File', newKey: uri, params });
|
const stackAction = StackActions.replace({ routeName: 'File', newKey: uri, params });
|
||||||
navigation.dispatch(stackAction);
|
navigation.dispatch(stackAction);
|
||||||
|
|
|
@ -148,7 +148,7 @@ android.react_src = ./app
|
||||||
|
|
||||||
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
||||||
# bootstrap)
|
# bootstrap)
|
||||||
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.55.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0
|
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.55.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828
|
||||||
|
|
||||||
# (str) python-for-android branch to use, defaults to master
|
# (str) python-for-android branch to use, defaults to master
|
||||||
#p4a.branch = stable
|
#p4a.branch = stable
|
||||||
|
|
|
@ -148,7 +148,7 @@ android.react_src = ./app
|
||||||
|
|
||||||
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
||||||
# bootstrap)
|
# bootstrap)
|
||||||
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.55.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0
|
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.55.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828
|
||||||
|
|
||||||
# (str) python-for-android branch to use, defaults to master
|
# (str) python-for-android branch to use, defaults to master
|
||||||
#p4a.branch = stable
|
#p4a.branch = stable
|
||||||
|
|
|
@ -148,7 +148,7 @@ android.react_src = ./app
|
||||||
|
|
||||||
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
|
||||||
# bootstrap)
|
# bootstrap)
|
||||||
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.55.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0
|
android.gradle_dependencies = com.android.support:support-v4:27.1.1, com.android.support:support-media-compat:27.1.1, com.android.support:appcompat-v7:27.1.1, com.facebook.react:react-native:0.55.3, com.mixpanel.android:mixpanel-android:5+, com.google.android.gms:play-services-gcm:11.0.4+, com.facebook.fresco:fresco:1.9.0, com.facebook.fresco:animated-gif:1.9.0, com.squareup.picasso:picasso:2.71828
|
||||||
|
|
||||||
# (str) python-for-android branch to use, defaults to master
|
# (str) python-for-android branch to use, defaults to master
|
||||||
#p4a.branch = stable
|
#p4a.branch = stable
|
||||||
|
|
|
@ -57,7 +57,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
|
|
||||||
private static final int RECEIVE_SMS_PERMISSION_REQ_CODE = 203;
|
private static final int RECEIVE_SMS_PERMISSION_REQ_CODE = 203;
|
||||||
|
|
||||||
private BroadcastReceiver backgroundMediaReceiver;
|
private BroadcastReceiver notificationsReceiver;
|
||||||
|
|
||||||
private BroadcastReceiver smsReceiver;
|
private BroadcastReceiver smsReceiver;
|
||||||
|
|
||||||
|
@ -73,6 +73,8 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
|
|
||||||
public static final String DEVICE_ID_KEY = "deviceId";
|
public static final String DEVICE_ID_KEY = "deviceId";
|
||||||
|
|
||||||
|
public static final String SOURCE_NOTIFICATION_ID_KEY = "sourceNotificationId";
|
||||||
|
|
||||||
public static final String SETTING_KEEP_DAEMON_RUNNING = "keepDaemonRunning";
|
public static final String SETTING_KEEP_DAEMON_RUNNING = "keepDaemonRunning";
|
||||||
|
|
||||||
public static List<Integer> downloadNotificationIds = new ArrayList<Integer>();
|
public static List<Integer> downloadNotificationIds = new ArrayList<Integer>();
|
||||||
|
@ -129,7 +131,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
.build();
|
.build();
|
||||||
mReactRootView.startReactApplication(mReactInstanceManager, "LBRYApp", null);
|
mReactRootView.startReactApplication(mReactInstanceManager, "LBRYApp", null);
|
||||||
|
|
||||||
registerBackgroundMediaReceiver();
|
registerNotificationsReceiver();
|
||||||
|
|
||||||
setContentView(mReactRootView);
|
setContentView(mReactRootView);
|
||||||
}
|
}
|
||||||
|
@ -147,12 +149,12 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
registerReceiver(stopServiceReceiver, intentFilter);
|
registerReceiver(stopServiceReceiver, intentFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void registerBackgroundMediaReceiver() {
|
private void registerNotificationsReceiver() {
|
||||||
// Background media receiver
|
// Background media receiver
|
||||||
IntentFilter backgroundMediaFilter = new IntentFilter();
|
IntentFilter filter = new IntentFilter();
|
||||||
backgroundMediaFilter.addAction(BackgroundMediaModule.ACTION_PLAY);
|
filter.addAction(BackgroundMediaModule.ACTION_PLAY);
|
||||||
backgroundMediaFilter.addAction(BackgroundMediaModule.ACTION_PAUSE);
|
filter.addAction(BackgroundMediaModule.ACTION_PAUSE);
|
||||||
backgroundMediaReceiver = new BroadcastReceiver() {
|
notificationsReceiver = new BroadcastReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
|
@ -169,7 +171,7 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
registerReceiver(backgroundMediaReceiver, backgroundMediaFilter);
|
registerReceiver(notificationsReceiver, filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerSmsReceiver() {
|
public void registerSmsReceiver() {
|
||||||
|
@ -363,9 +365,9 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backgroundMediaReceiver != null) {
|
if (notificationsReceiver != null) {
|
||||||
unregisterReceiver(backgroundMediaReceiver);
|
unregisterReceiver(notificationsReceiver);
|
||||||
backgroundMediaReceiver = null;
|
notificationsReceiver = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (smsReceiver != null) {
|
if (smsReceiver != null) {
|
||||||
|
@ -410,6 +412,15 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
|
||||||
if (mReactInstanceManager != null) {
|
if (mReactInstanceManager != null) {
|
||||||
mReactInstanceManager.onNewIntent(intent);
|
mReactInstanceManager.onNewIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (intent != null) {
|
||||||
|
int sourceNotificationId = intent.getIntExtra(SOURCE_NOTIFICATION_ID_KEY, -1);
|
||||||
|
if (sourceNotificationId > -1) {
|
||||||
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
||||||
|
notificationManager.cancel(sourceNotificationId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ public class DownloadManagerModule extends ReactContextBaseJavaModule {
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
do {
|
do {
|
||||||
id = random.nextInt();
|
id = random.nextInt();
|
||||||
} while (id < 100);
|
} while (id < 1000);
|
||||||
|
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ public class DownloadManagerModule extends ReactContextBaseJavaModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private PendingIntent getLaunchPendingIntent(String uri) {
|
public static PendingIntent getLaunchPendingIntent(String uri, Context context) {
|
||||||
Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
|
Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri));
|
||||||
launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
PendingIntent intent = PendingIntent.getActivity(context, 0, launchIntent, 0);
|
PendingIntent intent = PendingIntent.getActivity(context, 0, launchIntent, 0);
|
||||||
|
@ -121,7 +121,7 @@ public class DownloadManagerModule extends ReactContextBaseJavaModule {
|
||||||
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
|
||||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||||
// The file URI is used as the unique ID
|
// The file URI is used as the unique ID
|
||||||
builder.setContentIntent(getLaunchPendingIntent(id))
|
builder.setContentIntent(getLaunchPendingIntent(id, context))
|
||||||
.setContentTitle(String.format("Downloading %s", truncateFilename(filename)))
|
.setContentTitle(String.format("Downloading %s", truncateFilename(filename)))
|
||||||
.setGroup(GROUP_DOWNLOADS)
|
.setGroup(GROUP_DOWNLOADS)
|
||||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||||
|
@ -160,7 +160,7 @@ public class DownloadManagerModule extends ReactContextBaseJavaModule {
|
||||||
builders.put(notificationId, builder);
|
builders.put(notificationId, builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.setContentIntent(getLaunchPendingIntent(id))
|
builder.setContentIntent(getLaunchPendingIntent(id, context))
|
||||||
.setContentText(String.format("%.0f%% (%s / %s)", progress, formatBytes(writtenBytes), formatBytes(totalBytes)))
|
.setContentText(String.format("%.0f%% (%s / %s)", progress, formatBytes(writtenBytes), formatBytes(totalBytes)))
|
||||||
.setGroup(GROUP_DOWNLOADS)
|
.setGroup(GROUP_DOWNLOADS)
|
||||||
.setProgress(MAX_PROGRESS, new Double(progress).intValue(), false)
|
.setProgress(MAX_PROGRESS, new Double(progress).intValue(), false)
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
package io.lbry.browser.reactmodules;
|
package io.lbry.browser.reactmodules;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.NotificationChannel;
|
||||||
|
import android.app.NotificationManager;
|
||||||
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.support.v4.content.FileProvider;
|
import android.support.v4.content.FileProvider;
|
||||||
|
import android.support.v4.app.NotificationCompat;
|
||||||
|
import android.support.v4.app.NotificationManagerCompat;
|
||||||
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
@ -18,14 +26,30 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||||
import com.facebook.react.bridge.ReactMethod;
|
import com.facebook.react.bridge.ReactMethod;
|
||||||
|
|
||||||
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import io.lbry.browser.MainActivity;
|
import io.lbry.browser.MainActivity;
|
||||||
|
import io.lbry.browser.R;
|
||||||
import io.lbry.browser.Utils;
|
import io.lbry.browser.Utils;
|
||||||
|
import io.lbry.browser.reactmodules.DownloadManagerModule;
|
||||||
|
|
||||||
public class UtilityModule extends ReactContextBaseJavaModule {
|
public class UtilityModule extends ReactContextBaseJavaModule {
|
||||||
|
private static final Map<String, Integer> activeNotifications = new HashMap<String, Integer>();
|
||||||
|
|
||||||
private static final String FILE_PROVIDER = "io.lbry.browser.fileprovider";
|
private static final String FILE_PROVIDER = "io.lbry.browser.fileprovider";
|
||||||
|
|
||||||
|
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.SUBSCRIPTIONS_NOTIFICATION_CHANNEL";
|
||||||
|
|
||||||
|
public static final String ACTION_NOTIFICATION_PLAY = "io.lbry.browser.ACTION_NOTIFICATION_PLAY";
|
||||||
|
|
||||||
|
public static final String ACTION_NOTIFICATION_LATER = "io.lbry.browser.ACTION_NOTIFICATION_LATER";
|
||||||
|
|
||||||
private Context context;
|
private Context context;
|
||||||
|
|
||||||
public UtilityModule(ReactApplicationContext reactContext) {
|
public UtilityModule(ReactApplicationContext reactContext) {
|
||||||
|
@ -172,6 +196,75 @@ public class UtilityModule extends ReactContextBaseJavaModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ReactMethod
|
||||||
|
public void showNotificationForContent(final String uri, String title, String publisher, final String thumbnail, boolean isPlayable) {
|
||||||
|
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
NotificationChannel channel = new NotificationChannel(
|
||||||
|
NOTIFICATION_CHANNEL_ID, "LBRY Subscriptions", NotificationManager.IMPORTANCE_DEFAULT);
|
||||||
|
channel.setDescription("LBRY subscription notifications");
|
||||||
|
notificationManager.createNotificationChannel(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeNotifications.containsKey(uri)) {
|
||||||
|
// the notification for the specified uri is already present, don't try to create another one
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int id = 0;
|
||||||
|
Random random = new Random();
|
||||||
|
do {
|
||||||
|
id = random.nextInt();
|
||||||
|
} while (id < 100);
|
||||||
|
final int notificationId = id;
|
||||||
|
|
||||||
|
String uriWithParam = String.format("%s?download=true", uri);
|
||||||
|
Intent playIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uriWithParam));
|
||||||
|
playIntent.putExtra(MainActivity.SOURCE_NOTIFICATION_ID_KEY, notificationId);
|
||||||
|
playIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||||
|
PendingIntent playPendingIntent = PendingIntent.getActivity(context, 0, playIntent, PendingIntent.FLAG_CANCEL_CURRENT);
|
||||||
|
|
||||||
|
boolean hasThumbnail = false;
|
||||||
|
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
|
||||||
|
builder.setAutoCancel(true)
|
||||||
|
.setColor(ContextCompat.getColor(context, R.color.lbrygreen))
|
||||||
|
.setContentIntent(DownloadManagerModule.getLaunchPendingIntent(uri, context))
|
||||||
|
.setContentTitle(publisher)
|
||||||
|
.setContentText(title)
|
||||||
|
.setSmallIcon(R.drawable.ic_lbry)
|
||||||
|
.addAction(android.R.drawable.ic_media_play, (isPlayable ? "Play" : "Open"), playPendingIntent);
|
||||||
|
|
||||||
|
activeNotifications.put(uri, notificationId);
|
||||||
|
if (thumbnail != null) {
|
||||||
|
// attempt to load the thumbnail Bitmap before displaying the notification
|
||||||
|
final Uri thumbnailUri = Uri.parse(thumbnail);
|
||||||
|
if (thumbnailUri != null) {
|
||||||
|
hasThumbnail = true;
|
||||||
|
(new AsyncTask<Void, Void, Bitmap>() {
|
||||||
|
protected Bitmap doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
|
return Picasso.get().load(thumbnailUri).get();
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onPostExecute(Bitmap result) {
|
||||||
|
if (result != null) {
|
||||||
|
builder.setLargeIcon(result)
|
||||||
|
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(result).bigLargeIcon(null));
|
||||||
|
}
|
||||||
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
}
|
||||||
|
}).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasThumbnail) {
|
||||||
|
notificationManager.notify(notificationId, builder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isEmulator() {
|
private static boolean isEmulator() {
|
||||||
String buildModel = Build.MODEL.toLowerCase();
|
String buildModel = Build.MODEL.toLowerCase();
|
||||||
return (// Check FINGERPRINT
|
return (// Check FINGERPRINT
|
||||||
|
|
Loading…
Reference in a new issue