Merge pull request #1623 from TeamNewPipe/refactor_and_bugfix
Refactor and bugfix
This commit is contained in:
commit
ad065e9281
|
@ -49,12 +49,13 @@ ext {
|
||||||
icepickLibVersion = '3.2.0'
|
icepickLibVersion = '3.2.0'
|
||||||
stethoLibVersion = '1.5.0'
|
stethoLibVersion = '1.5.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
|
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2') {
|
||||||
exclude module: 'support-annotations'
|
exclude module: 'support-annotations'
|
||||||
}
|
}
|
||||||
|
|
||||||
implementation 'com.github.TeamNewPipe:NewPipeExtractor:1eff8c5708'
|
implementation 'com.github.TeamNewPipe:NewPipeExtractor:fef71aeccc37'
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
testImplementation 'org.mockito:mockito-core:2.8.9'
|
testImplementation 'org.mockito:mockito-core:2.8.9'
|
||||||
|
|
|
@ -76,10 +76,6 @@
|
||||||
android:name=".about.AboutActivity"
|
android:name=".about.AboutActivity"
|
||||||
android:label="@string/title_activity_about"/>
|
android:label="@string/title_activity_about"/>
|
||||||
|
|
||||||
<activity
|
|
||||||
android:name=".history.HistoryActivity"
|
|
||||||
android:label="@string/title_activity_history"/>
|
|
||||||
|
|
||||||
<service android:name=".local.subscription.services.SubscriptionsImportService"/>
|
<service android:name=".local.subscription.services.SubscriptionsImportService"/>
|
||||||
<service android:name=".local.subscription.services.SubscriptionsExportService"/>
|
<service android:name=".local.subscription.services.SubscriptionsExportService"/>
|
||||||
|
|
||||||
|
@ -122,6 +118,7 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ReCaptchaActivity"
|
android:name=".ReCaptchaActivity"
|
||||||
android:label="@string/reCaptchaActivity"/>
|
android:label="@string/reCaptchaActivity"/>
|
||||||
|
<activity android:name=".download.ExtSDDownloadFailedActivity" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name="android.support.v4.content.FileProvider"
|
android:name="android.support.v4.content.FileProvider"
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package org.schabi.newpipe.download;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.content.DialogInterface;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v7.app.AppCompatActivity;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||||
|
import org.schabi.newpipe.util.ServiceHelper;
|
||||||
|
import org.schabi.newpipe.util.ThemeHelper;
|
||||||
|
|
||||||
|
public class ExtSDDownloadFailedActivity extends AppCompatActivity {
|
||||||
|
@Override
|
||||||
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
ThemeHelper.setTheme(this, ServiceHelper.getSelectedServiceId(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
new AlertDialog.Builder(this)
|
||||||
|
.setTitle(R.string.download_to_sdcard_error_title)
|
||||||
|
.setMessage(R.string.download_to_sdcard_error_message)
|
||||||
|
.setPositiveButton(R.string.yes, (DialogInterface dialogInterface, int i) -> {
|
||||||
|
NewPipeSettings.resetDownloadFolders(this);
|
||||||
|
finish();
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.cancel, (DialogInterface dialogInterface, int i) -> {
|
||||||
|
dialogInterface.dismiss();
|
||||||
|
finish();
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
|
@ -87,12 +87,7 @@ public abstract class BaseStateFragment<I> extends BaseFragment implements ViewC
|
||||||
RxView.clicks(errorButtonRetry)
|
RxView.clicks(errorButtonRetry)
|
||||||
.debounce(300, TimeUnit.MILLISECONDS)
|
.debounce(300, TimeUnit.MILLISECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(new Consumer<Object>() {
|
.subscribe(o -> onRetryButtonClicked());
|
||||||
@Override
|
|
||||||
public void accept(Object o) throws Exception {
|
|
||||||
onRetryButtonClicked();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onRetryButtonClicked() {
|
protected void onRetryButtonClicked() {
|
||||||
|
|
|
@ -1227,10 +1227,10 @@ public class VideoDetailFragment
|
||||||
spinnerToolbar.setVisibility(View.GONE);
|
spinnerToolbar.setVisibility(View.GONE);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
if(info.getAudioStreams().isEmpty()) detailControlsBackground.setVisibility(View.GONE);
|
||||||
if (!info.getVideoStreams().isEmpty()
|
if (!info.getVideoStreams().isEmpty()
|
||||||
|| !info.getVideoOnlyStreams().isEmpty()) break;
|
|| !info.getVideoOnlyStreams().isEmpty()) break;
|
||||||
|
|
||||||
detailControlsBackground.setVisibility(View.GONE);
|
|
||||||
detailControlsPopup.setVisibility(View.GONE);
|
detailControlsPopup.setVisibility(View.GONE);
|
||||||
spinnerToolbar.setVisibility(View.GONE);
|
spinnerToolbar.setVisibility(View.GONE);
|
||||||
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_white_24dp);
|
thumbnailPlayButton.setImageResource(R.drawable.ic_headset_white_24dp);
|
||||||
|
|
|
@ -169,38 +169,35 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
context.getResources().getString(R.string.share)
|
context.getResources().getString(R.string.share)
|
||||||
};
|
};
|
||||||
|
|
||||||
final DialogInterface.OnClickListener actions = new DialogInterface.OnClickListener() {
|
final DialogInterface.OnClickListener actions = (DialogInterface dialogInterface, int i) -> {
|
||||||
@Override
|
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
|
||||||
public void onClick(DialogInterface dialogInterface, int i) {
|
switch (i) {
|
||||||
final int index = Math.max(infoListAdapter.getItemsList().indexOf(item), 0);
|
case 0:
|
||||||
switch (i) {
|
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
||||||
case 0:
|
break;
|
||||||
NavigationHelper.enqueueOnBackgroundPlayer(context, new SinglePlayQueue(item));
|
case 1:
|
||||||
break;
|
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
||||||
case 1:
|
break;
|
||||||
NavigationHelper.enqueueOnPopupPlayer(activity, new SinglePlayQueue(item));
|
case 2:
|
||||||
break;
|
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
||||||
case 2:
|
break;
|
||||||
NavigationHelper.playOnMainPlayer(context, getPlayQueue(index));
|
case 3:
|
||||||
break;
|
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
||||||
case 3:
|
break;
|
||||||
NavigationHelper.playOnBackgroundPlayer(context, getPlayQueue(index));
|
case 4:
|
||||||
break;
|
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
||||||
case 4:
|
break;
|
||||||
NavigationHelper.playOnPopupPlayer(activity, getPlayQueue(index));
|
case 5:
|
||||||
break;
|
if (getFragmentManager() != null) {
|
||||||
case 5:
|
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
|
||||||
if (getFragmentManager() != null) {
|
.show(getFragmentManager(), TAG);
|
||||||
PlaylistAppendDialog.fromStreamInfoItems(Collections.singletonList(item))
|
}
|
||||||
.show(getFragmentManager(), TAG);
|
break;
|
||||||
}
|
case 6:
|
||||||
break;
|
shareUrl(item.getName(), item.getUrl());
|
||||||
case 6:
|
break;
|
||||||
shareUrl(item.getName(), item.getUrl());
|
default:
|
||||||
break;
|
break;
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -258,12 +255,12 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
private static final int BUTTON_DEBOUNCE_INTERVAL = 100;
|
||||||
|
|
||||||
private void monitorSubscription(final ChannelInfo info) {
|
private void monitorSubscription(final ChannelInfo info) {
|
||||||
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
final Consumer<Throwable> onError = (Throwable throwable) -> {
|
||||||
@Override
|
|
||||||
public void accept(Throwable throwable) throws Exception {
|
|
||||||
animateView(headerSubscribeButton, false, 100);
|
animateView(headerSubscribeButton, false, 100);
|
||||||
showSnackBarError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Get subscription status", 0);
|
showSnackBarError(throwable, UserAction.SUBSCRIPTION,
|
||||||
}
|
NewPipe.getNameOfService(currentInfo.getServiceId()),
|
||||||
|
"Get subscription status",
|
||||||
|
0);
|
||||||
};
|
};
|
||||||
|
|
||||||
final Observable<List<SubscriptionEntity>> observable = subscriptionService.subscriptionTable()
|
final Observable<List<SubscriptionEntity>> observable = subscriptionService.subscriptionTable()
|
||||||
|
@ -279,50 +276,38 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
// so only update the UI for the latest emission ("sync" the subscribe button's state)
|
// so only update the UI for the latest emission ("sync" the subscribe button's state)
|
||||||
.debounce(100, TimeUnit.MILLISECONDS)
|
.debounce(100, TimeUnit.MILLISECONDS)
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(new Consumer<List<SubscriptionEntity>>() {
|
.subscribe((List<SubscriptionEntity> subscriptionEntities) ->
|
||||||
@Override
|
updateSubscribeButton(!subscriptionEntities.isEmpty())
|
||||||
public void accept(List<SubscriptionEntity> subscriptionEntities) throws Exception {
|
, onError));
|
||||||
updateSubscribeButton(!subscriptionEntities.isEmpty());
|
|
||||||
}
|
|
||||||
}, onError));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription) {
|
private Function<Object, Object> mapOnSubscribe(final SubscriptionEntity subscription) {
|
||||||
return new Function<Object, Object>() {
|
return (@NonNull Object o) -> {
|
||||||
@Override
|
subscriptionService.subscriptionTable().insert(subscription);
|
||||||
public Object apply(@NonNull Object o) throws Exception {
|
return o;
|
||||||
subscriptionService.subscriptionTable().insert(subscription);
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private Function<Object, Object> mapOnUnsubscribe(final SubscriptionEntity subscription) {
|
private Function<Object, Object> mapOnUnsubscribe(final SubscriptionEntity subscription) {
|
||||||
return new Function<Object, Object>() {
|
return (@NonNull Object o) -> {
|
||||||
@Override
|
subscriptionService.subscriptionTable().delete(subscription);
|
||||||
public Object apply(@NonNull Object o) throws Exception {
|
return o;
|
||||||
subscriptionService.subscriptionTable().delete(subscription);
|
|
||||||
return o;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSubscription(final ChannelInfo info) {
|
private void updateSubscription(final ChannelInfo info) {
|
||||||
if (DEBUG) Log.d(TAG, "updateSubscription() called with: info = [" + info + "]");
|
if (DEBUG) Log.d(TAG, "updateSubscription() called with: info = [" + info + "]");
|
||||||
final Action onComplete = new Action() {
|
final Action onComplete = () -> {
|
||||||
@Override
|
|
||||||
public void run() throws Exception {
|
|
||||||
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.getUrl());
|
if (DEBUG) Log.d(TAG, "Updated subscription: " + info.getUrl());
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
||||||
@Override
|
onUnrecoverableError(throwable,
|
||||||
public void accept(@NonNull Throwable throwable) throws Exception {
|
UserAction.SUBSCRIPTION,
|
||||||
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(info.getServiceId()), "Updating Subscription for " + info.getUrl(), R.string.subscription_update_failed);
|
NewPipe.getNameOfService(info.getServiceId()),
|
||||||
}
|
"Updating Subscription for " + info.getUrl(),
|
||||||
};
|
R.string.subscription_update_failed);
|
||||||
|
|
||||||
disposables.add(subscriptionService.updateChannelInfo(info)
|
disposables.add(subscriptionService.updateChannelInfo(info)
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
|
@ -331,19 +316,16 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Disposable monitorSubscribeButton(final Button subscribeButton, final Function<Object, Object> action) {
|
private Disposable monitorSubscribeButton(final Button subscribeButton, final Function<Object, Object> action) {
|
||||||
final Consumer<Object> onNext = new Consumer<Object>() {
|
final Consumer<Object> onNext = (@NonNull Object o) -> {
|
||||||
@Override
|
|
||||||
public void accept(@NonNull Object o) throws Exception {
|
|
||||||
if (DEBUG) Log.d(TAG, "Changed subscription status to this channel!");
|
if (DEBUG) Log.d(TAG, "Changed subscription status to this channel!");
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
final Consumer<Throwable> onError = new Consumer<Throwable>() {
|
final Consumer<Throwable> onError = (@NonNull Throwable throwable) ->
|
||||||
@Override
|
onUnrecoverableError(throwable,
|
||||||
public void accept(@NonNull Throwable throwable) throws Exception {
|
UserAction.SUBSCRIPTION,
|
||||||
onUnrecoverableError(throwable, UserAction.SUBSCRIPTION, NewPipe.getNameOfService(currentInfo.getServiceId()), "Subscription Change", R.string.subscription_change_failed);
|
NewPipe.getNameOfService(currentInfo.getServiceId()),
|
||||||
}
|
"Subscription Change",
|
||||||
};
|
R.string.subscription_change_failed);
|
||||||
|
|
||||||
/* Emit clicks from main thread unto io thread */
|
/* Emit clicks from main thread unto io thread */
|
||||||
return RxView.clicks(subscribeButton)
|
return RxView.clicks(subscribeButton)
|
||||||
|
@ -355,25 +337,25 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) {
|
private Consumer<List<SubscriptionEntity>> getSubscribeUpdateMonitor(final ChannelInfo info) {
|
||||||
return new Consumer<List<SubscriptionEntity>>() {
|
return (List<SubscriptionEntity> subscriptionEntities) -> {
|
||||||
@Override
|
if (DEBUG)
|
||||||
public void accept(List<SubscriptionEntity> subscriptionEntities) throws Exception {
|
Log.d(TAG, "subscriptionService.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]");
|
||||||
if (DEBUG)
|
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
|
||||||
Log.d(TAG, "subscriptionService.subscriptionTable.doOnNext() called with: subscriptionEntities = [" + subscriptionEntities + "]");
|
|
||||||
if (subscribeButtonMonitor != null) subscribeButtonMonitor.dispose();
|
|
||||||
|
|
||||||
if (subscriptionEntities.isEmpty()) {
|
if (subscriptionEntities.isEmpty()) {
|
||||||
if (DEBUG) Log.d(TAG, "No subscription to this channel!");
|
if (DEBUG) Log.d(TAG, "No subscription to this channel!");
|
||||||
SubscriptionEntity channel = new SubscriptionEntity();
|
SubscriptionEntity channel = new SubscriptionEntity();
|
||||||
channel.setServiceId(info.getServiceId());
|
channel.setServiceId(info.getServiceId());
|
||||||
channel.setUrl(info.getUrl());
|
channel.setUrl(info.getUrl());
|
||||||
channel.setData(info.getName(), info.getAvatarUrl(), info.getDescription(), info.getSubscriberCount());
|
channel.setData(info.getName(),
|
||||||
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel));
|
info.getAvatarUrl(),
|
||||||
} else {
|
info.getDescription(),
|
||||||
if (DEBUG) Log.d(TAG, "Found subscription to this channel!");
|
info.getSubscriberCount());
|
||||||
final SubscriptionEntity subscription = subscriptionEntities.get(0);
|
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnSubscribe(channel));
|
||||||
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription));
|
} else {
|
||||||
}
|
if (DEBUG) Log.d(TAG, "Found subscription to this channel!");
|
||||||
|
final SubscriptionEntity subscription = subscriptionEntities.get(0);
|
||||||
|
subscribeButtonMonitor = monitorSubscribeButton(headerSubscribeButton, mapOnUnsubscribe(subscription));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -491,8 +473,11 @@ public class ChannelFragment extends BaseListInfoFragment<ChannelInfo> {
|
||||||
super.handleNextItems(result);
|
super.handleNextItems(result);
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.REQUESTED_CHANNEL, NewPipe.getNameOfService(serviceId),
|
showSnackBarError(result.getErrors(),
|
||||||
"Get next page of: " + url, R.string.general_error);
|
UserAction.REQUESTED_CHANNEL,
|
||||||
|
NewPipe.getNameOfService(serviceId),
|
||||||
|
"Get next page of: " + url,
|
||||||
|
R.string.general_error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -365,7 +365,7 @@ public class SearchFragment
|
||||||
int itemId = 0;
|
int itemId = 0;
|
||||||
boolean isFirstItem = true;
|
boolean isFirstItem = true;
|
||||||
final Context c = getContext();
|
final Context c = getContext();
|
||||||
for(String filter : service.getSearchQIHFactory().getAvailableContentFilter()) {
|
for(String filter : service.getSearchQHFactory().getAvailableContentFilter()) {
|
||||||
menuItemToFilterName.put(itemId, filter);
|
menuItemToFilterName.put(itemId, filter);
|
||||||
MenuItem item = menu.add(1,
|
MenuItem item = menu.add(1,
|
||||||
itemId++,
|
itemId++,
|
||||||
|
@ -575,8 +575,7 @@ public class SearchFragment
|
||||||
.onNext(searchEditText.getText().toString()),
|
.onNext(searchEditText.getText().toString()),
|
||||||
throwable -> showSnackBarError(throwable,
|
throwable -> showSnackBarError(throwable,
|
||||||
UserAction.DELETE_FROM_HISTORY, "none",
|
UserAction.DELETE_FROM_HISTORY, "none",
|
||||||
"Deleting item failed", R.string.general_error)
|
"Deleting item failed", R.string.general_error));
|
||||||
);
|
|
||||||
disposables.add(onDelete);
|
disposables.add(onDelete);
|
||||||
})
|
})
|
||||||
.show();
|
.show();
|
||||||
|
@ -837,7 +836,10 @@ public class SearchFragment
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleResult(@NonNull SearchInfo result) {
|
public void handleResult(@NonNull SearchInfo result) {
|
||||||
if (!result.getErrors().isEmpty()) {
|
final List<Throwable> exceptions = result.getErrors();
|
||||||
|
if (!exceptions.isEmpty()
|
||||||
|
&& !(exceptions.size() == 1
|
||||||
|
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)){
|
||||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||||
NewPipe.getNameOfService(serviceId), searchString, 0);
|
NewPipe.getNameOfService(serviceId), searchString, 0);
|
||||||
}
|
}
|
||||||
|
@ -864,6 +866,7 @@ public class SearchFragment
|
||||||
showListFooter(false);
|
showListFooter(false);
|
||||||
currentPageUrl = result.getNextPageUrl();
|
currentPageUrl = result.getNextPageUrl();
|
||||||
infoListAdapter.addInfoItemList(result.getItems());
|
infoListAdapter.addInfoItemList(result.getItems());
|
||||||
|
nextPageUrl = result.getNextPageUrl();
|
||||||
|
|
||||||
if (!result.getErrors().isEmpty()) {
|
if (!result.getErrors().isEmpty()) {
|
||||||
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
showSnackBarError(result.getErrors(), UserAction.SEARCHED,
|
||||||
|
|
|
@ -26,6 +26,7 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
@ -343,6 +344,7 @@ public final class BackgroundPlayer extends Service {
|
||||||
|
|
||||||
if (!shouldUpdateOnProgress) return;
|
if (!shouldUpdateOnProgress) return;
|
||||||
resetNotification();
|
resetNotification();
|
||||||
|
if(Build.VERSION.SDK_INT >= 26 /*Oreo*/) updateNotificationThumbnail();
|
||||||
if (bigNotRemoteView != null) {
|
if (bigNotRemoteView != null) {
|
||||||
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
|
bigNotRemoteView.setProgressBar(R.id.notificationProgressBar, duration, currentProgress, false);
|
||||||
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
|
bigNotRemoteView.setTextViewText(R.id.notificationTime, getTimeString(currentProgress) + " / " + getTimeString(duration));
|
||||||
|
|
|
@ -51,6 +51,7 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.Downloader;
|
import org.schabi.newpipe.Downloader;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
|
@ -69,6 +70,7 @@ import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
import org.schabi.newpipe.player.playqueue.PlayQueueAdapter;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
|
||||||
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
import org.schabi.newpipe.player.resolver.MediaSourceTag;
|
||||||
|
import org.schabi.newpipe.report.ErrorActivity;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.SerializedCache;
|
import org.schabi.newpipe.util.SerializedCache;
|
||||||
|
|
||||||
|
@ -86,6 +88,7 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_INTERNAL
|
||||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
|
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_PERIOD_TRANSITION;
|
||||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
|
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK;
|
||||||
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
|
import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT;
|
||||||
|
import static org.schabi.newpipe.report.UserAction.PLAY_STREAM;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base for the players, joining the common properties
|
* Base for the players, joining the common properties
|
||||||
|
@ -96,7 +99,7 @@ import static com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK_ADJ
|
||||||
public abstract class BasePlayer implements
|
public abstract class BasePlayer implements
|
||||||
Player.EventListener, PlaybackListener, ImageLoadingListener {
|
Player.EventListener, PlaybackListener, ImageLoadingListener {
|
||||||
|
|
||||||
public static final boolean DEBUG = true;
|
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||||
@NonNull public static final String TAG = "BasePlayer";
|
@NonNull public static final String TAG = "BasePlayer";
|
||||||
|
|
||||||
@NonNull final protected Context context;
|
@NonNull final protected Context context;
|
||||||
|
@ -363,7 +366,10 @@ public abstract class BasePlayer implements
|
||||||
try {
|
try {
|
||||||
context.unregisterReceiver(broadcastReceiver);
|
context.unregisterReceiver(broadcastReceiver);
|
||||||
} catch (final IllegalArgumentException unregisteredException) {
|
} catch (final IllegalArgumentException unregisteredException) {
|
||||||
Log.e(TAG, "Broadcast receiver already unregistered.", unregisteredException);
|
ErrorActivity.reportError(context, unregisteredException, null, null,
|
||||||
|
ErrorActivity.ErrorInfo.make(PLAY_STREAM,
|
||||||
|
"none",
|
||||||
|
"play stream", R.string.general_error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1001,6 +1007,8 @@ public abstract class BasePlayer implements
|
||||||
try {
|
try {
|
||||||
metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
|
metadata = (MediaSourceTag) simpleExoPlayer.getCurrentTag();
|
||||||
} catch (IndexOutOfBoundsException | ClassCastException error) {
|
} catch (IndexOutOfBoundsException | ClassCastException error) {
|
||||||
|
if(DEBUG) Log.d(TAG, "Could not update metadata: " + error.getMessage());
|
||||||
|
if(DEBUG) error.printStackTrace();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1087,6 +1095,9 @@ public abstract class BasePlayer implements
|
||||||
return simpleExoPlayer.isCurrentWindowDynamic();
|
return simpleExoPlayer.isCurrentWindowDynamic();
|
||||||
} catch (@NonNull IndexOutOfBoundsException ignored) {
|
} catch (@NonNull IndexOutOfBoundsException ignored) {
|
||||||
// Why would this even happen =(
|
// Why would this even happen =(
|
||||||
|
// But lets log it anyway. Save is save
|
||||||
|
if(DEBUG) Log.d(TAG, "Could not update metadata: " + ignored.getMessage());
|
||||||
|
if(DEBUG) ignored.printStackTrace();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
|
@ -562,6 +563,12 @@ public abstract class ServicePlayerActivity extends AppCompatActivity
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
progressLiveSync.setClickable(!player.isLiveEdge());
|
progressLiveSync.setClickable(!player.isLiveEdge());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this will make shure progressCurrentTime has the same width as progressEndTime
|
||||||
|
final ViewGroup.LayoutParams endTimeParams = progressEndTime.getLayoutParams();
|
||||||
|
final ViewGroup.LayoutParams currentTimeParams = progressCurrentTime.getLayoutParams();
|
||||||
|
currentTimeParams.width = progressEndTime.getWidth();
|
||||||
|
progressCurrentTime.setLayoutParams(currentTimeParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.util.Log;
|
||||||
|
|
||||||
import org.reactivestreams.Subscriber;
|
import org.reactivestreams.Subscriber;
|
||||||
import org.reactivestreams.Subscription;
|
import org.reactivestreams.Subscription;
|
||||||
|
import org.schabi.newpipe.BuildConfig;
|
||||||
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
|
import org.schabi.newpipe.player.playqueue.events.AppendEvent;
|
||||||
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
|
import org.schabi.newpipe.player.playqueue.events.ErrorEvent;
|
||||||
import org.schabi.newpipe.player.playqueue.events.InitEvent;
|
import org.schabi.newpipe.player.playqueue.events.InitEvent;
|
||||||
|
@ -41,7 +42,7 @@ import io.reactivex.subjects.BehaviorSubject;
|
||||||
public abstract class PlayQueue implements Serializable {
|
public abstract class PlayQueue implements Serializable {
|
||||||
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
|
private final String TAG = "PlayQueue@" + Integer.toHexString(hashCode());
|
||||||
|
|
||||||
public static final boolean DEBUG = true;
|
public static final boolean DEBUG = !BuildConfig.BUILD_TYPE.equals("release");
|
||||||
|
|
||||||
private ArrayList<PlayQueueItem> backup;
|
private ArrayList<PlayQueueItem> backup;
|
||||||
private ArrayList<PlayQueueItem> streams;
|
private ArrayList<PlayQueueItem> streams;
|
||||||
|
|
|
@ -15,7 +15,8 @@ public enum UserAction {
|
||||||
REQUESTED_CHANNEL("requested channel"),
|
REQUESTED_CHANNEL("requested channel"),
|
||||||
REQUESTED_PLAYLIST("requested playlist"),
|
REQUESTED_PLAYLIST("requested playlist"),
|
||||||
REQUESTED_KIOSK("requested kiosk"),
|
REQUESTED_KIOSK("requested kiosk"),
|
||||||
DELETE_FROM_HISTORY("delete from history");
|
DELETE_FROM_HISTORY("delete from history"),
|
||||||
|
PLAY_STREAM("Play stream");
|
||||||
|
|
||||||
|
|
||||||
private final String message;
|
private final String message;
|
||||||
|
|
|
@ -71,7 +71,7 @@ public class NewPipeSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File getVideoDownloadFolder(Context context) {
|
public static File getVideoDownloadFolder(Context context) {
|
||||||
return getFolder(context, R.string.download_path_key, Environment.DIRECTORY_MOVIES);
|
return getDir(context, R.string.download_path_key, Environment.DIRECTORY_MOVIES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getVideoDownloadPath(Context context) {
|
public static String getVideoDownloadPath(Context context) {
|
||||||
|
@ -81,7 +81,7 @@ public class NewPipeSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static File getAudioDownloadFolder(Context context) {
|
public static File getAudioDownloadFolder(Context context) {
|
||||||
return getFolder(context, R.string.download_path_audio_key, Environment.DIRECTORY_MUSIC);
|
return getDir(context, R.string.download_path_audio_key, Environment.DIRECTORY_MUSIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getAudioDownloadPath(Context context) {
|
public static String getAudioDownloadPath(Context context) {
|
||||||
|
@ -90,21 +90,37 @@ public class NewPipeSettings {
|
||||||
return prefs.getString(key, Environment.DIRECTORY_MUSIC);
|
return prefs.getString(key, Environment.DIRECTORY_MUSIC);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getFolder(Context context, int keyID, String defaultDirectoryName) {
|
private static File getDir(Context context, int keyID, String defaultDirectoryName) {
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
final String key = context.getString(keyID);
|
final String key = context.getString(keyID);
|
||||||
String downloadPath = prefs.getString(key, null);
|
String downloadPath = prefs.getString(key, null);
|
||||||
if ((downloadPath != null) && (!downloadPath.isEmpty())) return new File(downloadPath.trim());
|
if ((downloadPath != null) && (!downloadPath.isEmpty())) return new File(downloadPath.trim());
|
||||||
|
|
||||||
final File folder = getFolder(defaultDirectoryName);
|
final File dir = getDir(defaultDirectoryName);
|
||||||
SharedPreferences.Editor spEditor = prefs.edit();
|
SharedPreferences.Editor spEditor = prefs.edit();
|
||||||
spEditor.putString(key, new File(folder, "NewPipe").getAbsolutePath());
|
spEditor.putString(key, getNewPipeChildFolderPathForDir(dir));
|
||||||
spEditor.apply();
|
spEditor.apply();
|
||||||
return folder;
|
return dir;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private static File getFolder(String defaultDirectoryName) {
|
private static File getDir(String defaultDirectoryName) {
|
||||||
return new File(Environment.getExternalStorageDirectory(), defaultDirectoryName);
|
return new File(Environment.getExternalStorageDirectory(), defaultDirectoryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void resetDownloadFolders(Context context) {
|
||||||
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
resetDownloadFolder(prefs, context.getString(R.string.download_path_audio_key), Environment.DIRECTORY_MUSIC);
|
||||||
|
resetDownloadFolder(prefs, context.getString(R.string.download_path_key), Environment.DIRECTORY_MOVIES);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void resetDownloadFolder(SharedPreferences prefs, String key, String defaultDirectoryName) {
|
||||||
|
SharedPreferences.Editor spEditor = prefs.edit();
|
||||||
|
spEditor.putString(key, getNewPipeChildFolderPathForDir(getDir(defaultDirectoryName)));
|
||||||
|
spEditor.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getNewPipeChildFolderPathForDir(File dir) {
|
||||||
|
return new File(dir, "NewPipe").getAbsolutePath();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ public final class ExtractorHelper {
|
||||||
return Single.fromCallable(() ->
|
return Single.fromCallable(() ->
|
||||||
SearchInfo.getInfo(NewPipe.getService(serviceId),
|
SearchInfo.getInfo(NewPipe.getService(serviceId),
|
||||||
NewPipe.getService(serviceId)
|
NewPipe.getService(serviceId)
|
||||||
.getSearchQIHFactory()
|
.getSearchQHFactory()
|
||||||
.fromQuery(searchString, contentFilter, sortFilter),
|
.fromQuery(searchString, contentFilter, sortFilter),
|
||||||
contentCountry));
|
contentCountry));
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ public final class ExtractorHelper {
|
||||||
return Single.fromCallable(() ->
|
return Single.fromCallable(() ->
|
||||||
SearchInfo.getMoreItems(NewPipe.getService(serviceId),
|
SearchInfo.getMoreItems(NewPipe.getService(serviceId),
|
||||||
NewPipe.getService(serviceId)
|
NewPipe.getService(serviceId)
|
||||||
.getSearchQIHFactory()
|
.getSearchQHFactory()
|
||||||
.fromQuery(searchString, contentFilter, sortFilter),
|
.fromQuery(searchString, contentFilter, sortFilter),
|
||||||
contentCountry,
|
contentCountry,
|
||||||
pageUrl));
|
pageUrl));
|
||||||
|
|
|
@ -31,8 +31,6 @@ import org.schabi.newpipe.extractor.stream.AudioStream;
|
||||||
import org.schabi.newpipe.extractor.stream.Stream;
|
import org.schabi.newpipe.extractor.stream.Stream;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
import org.schabi.newpipe.extractor.stream.VideoStream;
|
||||||
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
|
|
||||||
import org.schabi.newpipe.extractor.linkhandler.SearchQueryHandler;
|
|
||||||
import org.schabi.newpipe.fragments.MainFragment;
|
import org.schabi.newpipe.fragments.MainFragment;
|
||||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
package us.shandian.giga.get;
|
package us.shandian.giga.get;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.schabi.newpipe.download.ExtSDDownloadFailedActivity;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FilenameFilter;
|
import java.io.FilenameFilter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.RandomAccessFile;
|
import java.io.RandomAccessFile;
|
||||||
import java.net.HttpURLConnection;
|
import java.net.HttpURLConnection;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -23,7 +30,9 @@ public class DownloadManagerImpl implements DownloadManager {
|
||||||
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
|
private static final String TAG = DownloadManagerImpl.class.getSimpleName();
|
||||||
private final DownloadDataSource mDownloadDataSource;
|
private final DownloadDataSource mDownloadDataSource;
|
||||||
|
|
||||||
private final ArrayList<DownloadMission> mMissions = new ArrayList<DownloadMission>();
|
private final ArrayList<DownloadMission> mMissions = new ArrayList<>();
|
||||||
|
@NonNull
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance
|
* Create a new instance
|
||||||
|
@ -33,6 +42,13 @@ public class DownloadManagerImpl implements DownloadManager {
|
||||||
*/
|
*/
|
||||||
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource) {
|
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource) {
|
||||||
mDownloadDataSource = downloadDataSource;
|
mDownloadDataSource = downloadDataSource;
|
||||||
|
this.context = null;
|
||||||
|
loadMissions(searchLocations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DownloadManagerImpl(Collection<String> searchLocations, DownloadDataSource downloadDataSource, Context context) {
|
||||||
|
mDownloadDataSource = downloadDataSource;
|
||||||
|
this.context = context;
|
||||||
loadMissions(searchLocations);
|
loadMissions(searchLocations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,10 +293,12 @@ public class DownloadManagerImpl implements DownloadManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Initializer extends Thread {
|
private class Initializer extends Thread {
|
||||||
private DownloadMission mission;
|
private final DownloadMission mission;
|
||||||
|
private final Handler handler;
|
||||||
|
|
||||||
public Initializer(DownloadMission mission) {
|
public Initializer(DownloadMission mission) {
|
||||||
this.mission = mission;
|
this.mission = mission;
|
||||||
|
this.handler = new Handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -335,6 +353,13 @@ public class DownloadManagerImpl implements DownloadManager {
|
||||||
af.close();
|
af.close();
|
||||||
|
|
||||||
mission.start();
|
mission.start();
|
||||||
|
} catch (IOException ie) {
|
||||||
|
if(context == null) throw new RuntimeException(ie);
|
||||||
|
|
||||||
|
if(ie.getMessage().contains("Permission denied")) {
|
||||||
|
handler.post(() ->
|
||||||
|
context.startActivity(new Intent(context, ExtSDDownloadFailedActivity.class)));
|
||||||
|
} else throw new RuntimeException(ie);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// TODO Notify
|
// TODO Notify
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
|
|
|
@ -81,7 +81,7 @@ public class DownloadManagerService extends Service {
|
||||||
ArrayList<String> paths = new ArrayList<>(2);
|
ArrayList<String> paths = new ArrayList<>(2);
|
||||||
paths.add(NewPipeSettings.getVideoDownloadPath(this));
|
paths.add(NewPipeSettings.getVideoDownloadPath(this));
|
||||||
paths.add(NewPipeSettings.getAudioDownloadPath(this));
|
paths.add(NewPipeSettings.getAudioDownloadPath(this));
|
||||||
mManager = new DownloadManagerImpl(paths, mDataSource);
|
mManager = new DownloadManagerImpl(paths, mDataSource, this);
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
Log.d(TAG, "mManager == null");
|
Log.d(TAG, "mManager == null");
|
||||||
Log.d(TAG, "Download directory: " + paths);
|
Log.d(TAG, "Download directory: " + paths);
|
||||||
|
|
|
@ -170,6 +170,8 @@
|
||||||
<string name="search_history_deleted">Search history deleted.</string>
|
<string name="search_history_deleted">Search history deleted.</string>
|
||||||
<!-- error strings -->
|
<!-- error strings -->
|
||||||
<string name="general_error">Error</string>
|
<string name="general_error">Error</string>
|
||||||
|
<string name="download_to_sdcard_error_title">External storage not available.</string>
|
||||||
|
<string name="download_to_sdcard_error_message">Download to external SD Card is not possible yet. Should the download place be reset?</string>
|
||||||
<string name="network_error">Network error</string>
|
<string name="network_error">Network error</string>
|
||||||
<string name="could_not_load_thumbnails">Could not load all thumbnails</string>
|
<string name="could_not_load_thumbnails">Could not load all thumbnails</string>
|
||||||
<string name="youtube_signature_decryption_error">Could not decrypt video URL signature</string>
|
<string name="youtube_signature_decryption_error">Could not decrypt video URL signature</string>
|
||||||
|
|
|
@ -6,7 +6,7 @@ buildscript {
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.1.3'
|
classpath 'com.android.tools.build:gradle:3.1.4'
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
|
Loading…
Reference in New Issue