Merge remote-tracking branch 'origin/master' into rewards

This commit is contained in:
Akinwale Ariwodola 2018-08-21 15:36:05 +01:00
commit e9594c38c0
15 changed files with 262 additions and 35 deletions

View file

@ -28,6 +28,7 @@ import {
TextInput,
ToastAndroid
} from 'react-native';
import { doDeleteCompleteBlobs } from '../redux/actions/file';
import { SETTINGS, doHideNotification, doNotify, selectNotification } from 'lbry-redux';
import {
doUserEmailVerify,
@ -239,6 +240,7 @@ class AppWithNavigationState extends React.Component {
}
_handleAppStateChange = (nextAppState) => {
const { backgroundPlayEnabled, dispatch } = this.props;
// Check if the app was suspended
if (AppState.currentState && AppState.currentState.match(/inactive|background/)) {
AsyncStorage.getItem('firstLaunchTime').then(start => {
@ -247,8 +249,22 @@ class AppWithNavigationState extends React.Component {
// If so, this needs to be included as a property when tracking
AsyncStorage.setItem('firstLaunchSuspended', 'true');
}
// Background media
if (backgroundPlayEnabled && NativeModules.BackgroundMedia && window.currentMediaInfo) {
const { title, channel } = window.currentMediaInfo;
NativeModules.BackgroundMedia.showPlaybackNotification(title, channel, null, false);
}
});
}
if (AppState.currentState && AppState.currentState.match(/active/)) {
// Cleanup blobs for completed files upon app resume to save space
dispatch(doDeleteCompleteBlobs());
if (backgroundPlayEnabled || NativeModules.BackgroundMedia) {
NativeModules.BackgroundMedia.hidePlaybackNotification();
}
}
}
_handleUrl = (evt) => {
@ -304,6 +320,7 @@ class AppWithNavigationState extends React.Component {
}
const mapStateToProps = state => ({
backgroundPlayEnabled: makeSelectClientSetting(SETTINGS.BACKGROUND_PLAY_ENABLED)(state),
keepDaemonRunning: makeSelectClientSetting(SETTINGS.KEEP_DAEMON_RUNNING)(state),
nav: state.nav,
notification: selectNotification(state),

View file

@ -1,6 +1,7 @@
import React from 'react';
import { Lbry } from 'lbry-redux';
import {
DeviceEventEmitter,
NativeModules,
PanResponder,
Text,
@ -225,9 +226,13 @@ class MediaPlayer extends React.PureComponent {
componentDidMount() {
this.setSeekerPosition(this.calculateSeekerPosition());
DeviceEventEmitter.addListener('onBackgroundPlayPressed', this.play);
DeviceEventEmitter.addListener('onBackgroundPausePressed', this.pause);
}
componentWillUnmount() {
DeviceEventEmitter.removeListener('onBackgroundPlayPressed', this.play);
DeviceEventEmitter.removeListener('onBackgroundPausePressed', this.pause);
this.clearControlsTimeout();
this.setState({ paused: true, fullscreenMode: false });
const { onFullscreenToggled } = this.props;
@ -236,6 +241,24 @@ class MediaPlayer extends React.PureComponent {
}
}
play = () => {
this.setState({ paused: false }, this.updateBackgroundMediaNotification);
}
pause = () => {
this.setState({ paused: true }, this.updateBackgroundMediaNotification);
}
updateBackgroundMediaNotification = () => {
const { backgroundPlayEnabled } = this.props;
if (backgroundPlayEnabled) {
if (NativeModules.BackgroundMedia && window.currentMediaInfo) {
const { title, channel } = window.currentMediaInfo;
NativeModules.BackgroundMedia.showPlaybackNotification(title, channel, null, this.state.paused);
}
}
}
renderPlayerControls() {
if (this.state.areControlsVisible) {
return (
@ -285,7 +308,7 @@ class MediaPlayer extends React.PureComponent {
return (
<View style={styles} onLayout={onLayout}>
<Video source={{ uri: 'file:///' + this.getEncodedDownloadPath(fileInfo) }}
ref={(ref: Video) => { this.video = ref }}
ref={(ref: Video) => { this.video = ref; }}
resizeMode={this.state.resizeMode}
playInBackground={backgroundPlayEnabled}
style={mediaPlayerStyle.player}

View file

@ -7,21 +7,22 @@ import uriBarStyle from '../../styles/uriBar';
class UriBar extends React.PureComponent {
static INPUT_TIMEOUT = 500;
textInput = null;
keyboardDidHideListener = null;
componentDidMount () {
this.keyboardDidHideListener = Keyboard.addListener('keyboardDidHide', this._keyboardDidHide);
this.setSelection();
}
componentWillUnmount() {
if (this.keyboardDidHideListener) {
this.keyboardDidHideListener.remove();
}
}
constructor(props) {
super(props);
this.state = {
@ -31,24 +32,24 @@ class UriBar extends React.PureComponent {
focused: false
};
}
handleChangeText = text => {
const newValue = text ? text : '';
clearTimeout(this.state.changeTextTimeout);
const { updateSearchQuery } = this.props;
let timeout = setTimeout(() => {
updateSearchQuery(text);
}, UriBar.INPUT_TIMEOUT);
this.setState({ inputText: newValue, currentValue: newValue, changeTextTimeout: timeout });
}
handleItemPress = (item) => {
const { navigation, updateSearchQuery } = this.props;
const { type, value } = item;
Keyboard.dismiss();
if (SEARCH_TYPES.SEARCH === type) {
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: value }});
} else {
@ -56,7 +57,7 @@ class UriBar extends React.PureComponent {
navigation.navigate({ routeName: 'File', key: uri, params: { uri }});
}
}
_keyboardDidHide = () => {
if (this.textInput) {
this.textInput.blur();
@ -64,17 +65,23 @@ class UriBar extends React.PureComponent {
this.setState({ focused: false });
}
setSelection() {
if (this.textInput) {
this.textInput.setNativeProps({ selection: { start: 0, end: 0 }});
}
}
render() {
const { navigation, suggestions, updateSearchQuery, value } = this.props;
if (this.state.currentValue === null) {
this.setState({ currentValue: value });
}
let style = [uriBarStyle.overlay];
if (this.state.focused) {
style.push(uriBarStyle.inFocus);
}
return (
<View style={style}>
{this.state.focused && (
@ -90,6 +97,7 @@ class UriBar extends React.PureComponent {
<View style={uriBarStyle.uriContainer}>
<TextInput ref={(ref) => { this.textInput = ref }}
style={uriBarStyle.uriText}
onLayout={() => { this.setSelection(); }}
selectTextOnFocus={true}
placeholder={'Search for videos, music, games and more'}
underlineColorAndroid={'transparent'}
@ -100,14 +108,17 @@ class UriBar extends React.PureComponent {
inlineImageLeft={'baseline_search_black_24'}
inlineImagePadding={16}
onFocus={() => this.setState({ focused: true })}
onBlur={() => this.setState({ focused: false })}
onBlur={() => {
this.setState({ focused: false });
this.setSelection();
}}
onChangeText={this.handleChangeText}
onSubmitEditing={() => {
if (this.state.inputText) {
let inputText = this.state.inputText;
if (isNameValid(inputText) || isURIValid(inputText)) {
const uri = normalizeURI(inputText);
navigation.navigate({ routeName: 'File', key: uri, params: { uri }});
navigation.navigate({ routeName: 'File', key: uri, params: { uri }});
} else {
// Open the search page with the query populated
navigation.navigate({ routeName: 'Search', key: 'searchPage', params: { searchQuery: inputText }});

View file

@ -1,10 +1,11 @@
const Constants = {
KEY_FIRST_RUN_EMAIL: "firstRunEmail",
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail",
KEY_FIRST_RUN_EMAIL: "firstRunEmail",
KEY_SHOULD_VERIFY_EMAIL: "shouldVerifyEmail",
SETTING_ALPHA_UNDERSTANDS_RISKS: "alphaUnderstandRisks",
SETTING_ALPHA_UNDERSTANDS_RISKS: "alphaUnderstandRisks",
ACTION_FIRST_RUN_PAGE_CHANGED: "FIRST_RUN_PAGE_CHANGED",
ACTION_DELETE_COMPLETED_BLOBS: "DELETE_COMPLETED_BLOBS",
ACTION_FIRST_RUN_PAGE_CHANGED: "FIRST_RUN_PAGE_CHANGED",
};
export default Constants;

View file

@ -152,6 +152,9 @@ class FilePage extends React.PureComponent {
utility.keepAwakeOff();
utility.showNavigationBar();
}
if (window.currentMediaInfo) {
window.currentMediaInfo = null;
}
}
localUriForFileInfo = (fileInfo) => {
@ -322,7 +325,13 @@ class FilePage extends React.PureComponent {
style={playerStyle}
autoPlay={this.state.autoPlayMedia}
onFullscreenToggled={this.handleFullscreenToggle}
onMediaLoaded={() => { this.setState({ mediaLoaded: true }); }}
onMediaLoaded={() => {
this.setState({ mediaLoaded: true });
window.currentMediaInfo = {
title: title,
channel: channelName
};
}}
onLayout={(evt) => {
if (!this.state.playerHeight) {
this.setState({ playerHeight: evt.nativeEvent.layout.height });

View file

@ -8,6 +8,7 @@ import {
selectUser,
selectEmailToVerify
} from 'lbryinc';
import { doDeleteCompleteBlobs } from '../../redux/actions/file';
import SplashScreen from './view';
const select = state => ({
@ -17,6 +18,7 @@ const select = state => ({
const perform = dispatch => ({
authenticate: (appVersion, deviceId) => dispatch(doAuthenticate(appVersion, deviceId)),
deleteCompleteBlobs: () => dispatch(doDeleteCompleteBlobs()),
balanceSubscribe: () => dispatch(doBalanceSubscribe()),
notify: data => dispatch(doNotify(data)),
setEmailToVerify: email => dispatch(doUserEmailToVerify(email)),

View file

@ -117,10 +117,13 @@ class SplashScreen extends React.PureComponent {
}
_updateStatusCallback(status) {
const { deleteCompleteBlobs } = this.props;
const startupStatus = status.startup_status;
// At the minimum, wallet should be started and blocks_behind equal to 0 before calling resolve
const hasStarted = startupStatus.wallet && status.wallet.blocks_behind <= 0;
if (hasStarted) {
deleteCompleteBlobs();
// Wait until we are able to resolve a name before declaring
// that we are done.
// TODO: This is a hack, and the logic should live in the daemon

View file

@ -10,6 +10,7 @@ import {
selectDownloadingByOutpoint,
} from 'lbry-redux';
import { Alert, NativeModules } from 'react-native';
import Constants from '../../constants';
const DOWNLOAD_POLL_INTERVAL = 250;
@ -40,14 +41,18 @@ export function doUpdateLoadStatus(uri, outpoint) {
if (NativeModules.LbryDownloadManager) {
NativeModules.LbryDownloadManager.updateDownload(uri, fileInfo.file_name, 100, writtenBytes, totalBytes);
}
// Once a download has been completed, delete the individual blob files to save space
Lbry.blob_list({ sd_hash: fileInfo.sd_hash }).then(hashes => {
hashes.forEach(hash => {
Lbry.blob_delete({ blob_hash: hash });
});
});
/*const notif = new window.Notification('LBRY Download Complete', {
body: fileInfo.metadata.stream.metadata.title,
silent: false,
});
notif.onclick = () => {
ipcRenderer.send('focusWindow', 'main');
};*/
});*/
} else {
// ready to play
const { total_bytes: totalBytes, written_bytes: writtenBytes } = fileInfo;
@ -66,7 +71,7 @@ export function doUpdateLoadStatus(uri, outpoint) {
if (NativeModules.LbryDownloadManager) {
NativeModules.LbryDownloadManager.updateDownload(uri, fileInfo.file_name, progress, writtenBytes, totalBytes);
}
setTimeout(() => {
dispatch(doUpdateLoadStatus(uri, outpoint));
}, DOWNLOAD_POLL_INTERVAL);
@ -96,7 +101,7 @@ export function doStartDownload(uri, outpoint) {
fileInfo,
},
});
if (NativeModules.LbryDownloadManager) {
NativeModules.LbryDownloadManager.startDownload(uri, fileInfo.file_name);
}
@ -110,7 +115,7 @@ export function doStopDownloadingFile(uri, fileInfo) {
return dispatch => {
let params = { status: 'stop' };
if (fileInfo.sd_hash) {
params.sd_hash = fileInfo.sd_hash;
params.sd_hash = fileInfo.sd_hash;
}
if (fileInfo.stream_hash) {
params.stream_hash = fileInfo.stream_hash;
@ -122,11 +127,11 @@ export function doStopDownloadingFile(uri, fileInfo) {
data: {}
});
});
if (NativeModules.LbryDownloadManager) {
NativeModules.LbryDownloadManager.stopDownload(uri, fileInfo.file_name);
}
// Should also delete the file after the user stops downloading
dispatch(doDeleteFile(fileInfo.outpoint, uri));
};
@ -159,7 +164,7 @@ export function doLoadVideo(uri) {
uri,
},
});
Lbry.get({ uri })
.then(streamInfo => {
const timeout =
@ -186,7 +191,7 @@ export function doLoadVideo(uri) {
type: ACTIONS.LOADING_VIDEO_FAILED,
data: { uri },
});
dispatch(doNotify({
message: `Failed to download ${uri}, please try again. If this problem persists, visit https://lbry.io/faq/support for support.`,
displayType: ['toast']
@ -301,3 +306,24 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) {
//setProgressBar(totalProgress);
};
}
export function doDeleteCompleteBlobs() {
return dispatch => {
dispatch({
type: Constants.ACTION_DELETE_COMPLETED_BLOBS,
data: {},
});
Lbry.file_list().then(files => {
files.forEach(fileInfo => {
if (fileInfo.completed) {
Lbry.blob_list({ sd_hash: fileInfo.sd_hash }).then(hashes => {
hashes.forEach(hash => {
Lbry.blob_delete({ blob_hash: hash });
});
});
}
});
});
};
}

View file

@ -148,7 +148,7 @@ android.react_src = ./app
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
android.gradle_dependencies = 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
# (str) python-for-android branch to use, defaults to master
#p4a.branch = stable

View file

@ -148,7 +148,7 @@ android.react_src = ./app
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
android.gradle_dependencies = 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
# (str) python-for-android branch to use, defaults to master
#p4a.branch = stable

View file

@ -148,7 +148,7 @@ android.react_src = ./app
# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
android.gradle_dependencies = 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
# (str) python-for-android branch to use, defaults to master
#p4a.branch = stable

View file

@ -4,6 +4,7 @@ import android.os.Build;
import android.os.Bundle;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@ -13,6 +14,7 @@ import android.Manifest;
import android.net.Uri;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import android.telephony.TelephonyManager;
import android.widget.Toast;
@ -22,10 +24,13 @@ import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.ReactRootView;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.shell.MainReactPackage;
import com.RNFetchBlob.RNFetchBlobPackage;
import io.lbry.browser.reactpackages.LbryReactPackage;
import io.lbry.browser.reactmodules.BackgroundMediaModule;
import io.lbry.browser.reactmodules.DownloadManagerModule;
import java.io.UnsupportedEncodingException;
@ -44,6 +49,8 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
private static final int PHONE_STATE_PERMISSION_REQ_CODE = 202;
private BroadcastReceiver backgroundMediaReceiver;
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@ -99,9 +106,36 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
.build();
mReactRootView.startReactApplication(mReactInstanceManager, "LBRYApp", null);
registerBackgroundMediaReceiver();
setContentView(mReactRootView);
}
private void registerBackgroundMediaReceiver() {
// Background media receiver
IntentFilter backgroundMediaFilter = new IntentFilter();
backgroundMediaFilter.addAction(BackgroundMediaModule.ACTION_PLAY);
backgroundMediaFilter.addAction(BackgroundMediaModule.ACTION_PAUSE);
backgroundMediaReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
if (reactContext != null) {
if (BackgroundMediaModule.ACTION_PLAY.equals(action)) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onBackgroundPlayPressed", null);
}
if (BackgroundMediaModule.ACTION_PAUSE.equals(action)) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("onBackgroundPausePressed", null);
}
}
}
};
registerReceiver(backgroundMediaReceiver, backgroundMediaFilter);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
@ -230,6 +264,12 @@ public class MainActivity extends Activity implements DefaultHardwareBackBtnHand
}
}
if (backgroundMediaReceiver != null) {
unregisterReceiver(backgroundMediaReceiver);
}
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.cancelAll();
super.onDestroy();
if (mReactInstanceManager != null) {

View file

@ -0,0 +1,91 @@
package io.lbry.browser.reactmodules;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.support.v4.content.ContextCompat;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import io.lbry.browser.MainActivity;
import io.lbry.browser.R;
public class BackgroundMediaModule extends ReactContextBaseJavaModule {
private static final int NOTIFICATION_ID = 900;
private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.MEDIA_PLAYER_NOTIFICATION_CHANNEL";
public static final String ACTION_PLAY = "io.lbry.browser.ACTION_MEDIA_PLAY";
public static final String ACTION_PAUSE = "io.lbry.browser.ACTION_MEDIA_PAUSE";
public static final String ACTION_STOP = "io.lbry.browser.ACTION_MEDIA_STOP";
private boolean channelCreated;
private Context context;
public BackgroundMediaModule(ReactApplicationContext reactContext) {
super(reactContext);
this.context = reactContext;
}
@Override
public String getName() {
return "BackgroundMedia";
}
@ReactMethod
public void showPlaybackNotification(String title, String publisher, String uri, boolean paused) {
if (!channelCreated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationChannel channel = new NotificationChannel(
NOTIFICATION_CHANNEL_ID, "LBRY Media", NotificationManager.IMPORTANCE_LOW);
channel.setDescription("LBRY media player");
notificationManager.createNotificationChannel(channel);
channelCreated = true;
}
Intent playIntent = new Intent();
playIntent.setAction(ACTION_PLAY);
PendingIntent playPendingIntent = PendingIntent.getBroadcast(context, 0, playIntent, 0);
Intent pauseIntent = new Intent();
pauseIntent.setAction(ACTION_PAUSE);
PendingIntent pausePendingIntent = PendingIntent.getBroadcast(context, 0, pauseIntent, 0);
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID);
builder.setColor(ContextCompat.getColor(context, R.color.lbrygreen))
.setContentTitle(title)
.setContentText(publisher)
.setOngoing(!paused)
.setSmallIcon(paused ? android.R.drawable.ic_media_pause : android.R.drawable.ic_media_play)
.setStyle(new android.support.v4.media.app.NotificationCompat.MediaStyle()
.setShowActionsInCompactView(0))
.addAction(paused ? android.R.drawable.ic_media_play : android.R.drawable.ic_media_pause,
paused ? "Play" : "Pause",
paused ? playPendingIntent : pausePendingIntent)
.build();
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
@ReactMethod
public void hidePlaybackNotification() {
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context);
notificationManager.cancel(NOTIFICATION_ID);
}
}

View file

@ -1,6 +1,7 @@
package io.lbry.browser.reactmodules;
import android.app.Activity;
import android.app.NotificationChannel;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.SharedPreferences;
@ -14,6 +15,7 @@ import io.lbry.browser.MainActivity;
import io.lbry.browser.ServiceHelper;
public class DaemonServiceControlModule extends ReactContextBaseJavaModule {
private Context context;
public DaemonServiceControlModule(ReactApplicationContext reactContext) {

View file

@ -5,6 +5,7 @@ import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import io.lbry.browser.reactmodules.BackgroundMediaModule;
import io.lbry.browser.reactmodules.DaemonServiceControlModule;
import io.lbry.browser.reactmodules.DownloadManagerModule;
import io.lbry.browser.reactmodules.FirstRunModule;
@ -27,6 +28,7 @@ public class LbryReactPackage implements ReactPackage {
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new BackgroundMediaModule(reactContext));
modules.add(new DaemonServiceControlModule(reactContext));
modules.add(new DownloadManagerModule(reactContext));
modules.add(new FirstRunModule(reactContext));