mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2026-02-06 10:10:16 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d00b8cb0e0 | ||
|
|
5c10e12b01 | ||
|
|
181a34ac12 | ||
|
|
1fb959b274 | ||
|
|
01cd169442 | ||
|
|
a8de5ae2ea | ||
|
|
83f4fa4253 | ||
|
|
0edd1900d0 | ||
|
|
d5cfdcab5b | ||
|
|
0866bdf040 | ||
|
|
735d1ab05d | ||
|
|
71b62e0bd1 | ||
|
|
04ad5c7c53 | ||
|
|
5da54efcae | ||
|
|
b906465f80 | ||
|
|
83f9646eec | ||
|
|
85d43fe45e | ||
|
|
8d6e68d6f4 | ||
|
|
15b5cef6c2 | ||
|
|
739b6ae57b | ||
|
|
cc33b685a5 | ||
|
|
d051e8ecc8 | ||
|
|
51e62f09ba | ||
|
|
a7aad63bbb | ||
|
|
fd192b4f3f | ||
|
|
19e94bd30c | ||
|
|
7758a27694 | ||
|
|
a3301dcfb1 | ||
|
|
d045b27cea | ||
|
|
4f70235ee8 | ||
|
|
54f9bcb03e |
2
.github/workflows/backport-pr.yml
vendored
2
.github/workflows/backport-pr.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
github.event.comment.author_association == 'MEMBER'
|
||||
)
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
- name: Get backport metadata
|
||||
# the target branch is the first argument after `/backport`
|
||||
env:
|
||||
|
||||
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: gradle/actions/wrapper-validation@v4
|
||||
- uses: gradle/actions/wrapper-validation@v5
|
||||
|
||||
- name: create and checkout branch
|
||||
# push events already checked out the branch
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import com.android.build.api.dsl.ApplicationExtension
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.jetbrains.kotlin.android)
|
||||
@@ -32,7 +34,7 @@ kotlin {
|
||||
}
|
||||
}
|
||||
|
||||
android {
|
||||
configure<ApplicationExtension> {
|
||||
compileSdk = 36
|
||||
namespace = "org.schabi.newpipe"
|
||||
|
||||
@@ -77,19 +79,16 @@ android {
|
||||
resValue("string", "app_name", "NewPipe $suffix")
|
||||
}
|
||||
isMinifyEnabled = true
|
||||
isShrinkResources = false // disabled to fix F-Droid"s reproducible build
|
||||
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
|
||||
isShrinkResources = true
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||
"proguard-rules.pro"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
checkReleaseBuilds = false
|
||||
// Or, if you prefer, you can continue to check for errors in release builds,
|
||||
// but continue the build even when errors are found:
|
||||
abortOnError = false
|
||||
// suppress false warning ("Resource IDs will be non-final in Android Gradle Plugin version
|
||||
// 5.0, avoid using them in switch case statements"), which affects only library projects
|
||||
disable += "NonConstantResourceId"
|
||||
lintConfig = file("lint.xml")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
@@ -100,7 +99,7 @@ android {
|
||||
|
||||
sourceSets {
|
||||
getByName("androidTest") {
|
||||
assets.srcDir("$projectDir/schemas")
|
||||
assets.directories += "$projectDir/schemas"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +110,7 @@ android {
|
||||
buildFeatures {
|
||||
viewBinding = true
|
||||
buildConfig = true
|
||||
resValues = true
|
||||
}
|
||||
|
||||
packaging {
|
||||
|
||||
10
app/lint.xml
Normal file
10
app/lint.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
~ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
<lint>
|
||||
<issue id="MissingTranslation" severity="ignore" />
|
||||
<issue id="MissingQuantity" severity="ignore" />
|
||||
<issue id="ImpliedQuantity" severity="ignore" />
|
||||
</lint>
|
||||
5
app/proguard-rules.pro
vendored
5
app/proguard-rules.pro
vendored
@@ -39,3 +39,8 @@
|
||||
|
||||
## For some reason NotificationModeConfigFragment wasn't kept (only referenced in a preference xml)
|
||||
-keep class org.schabi.newpipe.settings.notifications.** { *; }
|
||||
|
||||
# Prevent R8 from stripping or renaming Protobuf internal fields
|
||||
-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite {
|
||||
<fields>;
|
||||
}
|
||||
|
||||
@@ -309,25 +309,21 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private boolean drawerItemSelected(final MenuItem item) {
|
||||
switch (item.getGroupId()) {
|
||||
case R.id.menu_services_group:
|
||||
changeService(item);
|
||||
break;
|
||||
case R.id.menu_tabs_group:
|
||||
tabSelected(item);
|
||||
break;
|
||||
case R.id.menu_kiosks_group:
|
||||
try {
|
||||
kioskSelected(item);
|
||||
} catch (final Exception e) {
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Selecting drawer kiosk", e);
|
||||
}
|
||||
break;
|
||||
case R.id.menu_options_about_group:
|
||||
optionsAboutSelected(item);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
final int groupId = item.getGroupId();
|
||||
if (groupId == R.id.menu_services_group) {
|
||||
changeService(item);
|
||||
} else if (groupId == R.id.menu_tabs_group) {
|
||||
tabSelected(item);
|
||||
} else if (groupId == R.id.menu_kiosks_group) {
|
||||
try {
|
||||
kioskSelected(item);
|
||||
} catch (final Exception e) {
|
||||
ErrorUtil.showUiErrorSnackbar(this, "Selecting drawer kiosk", e);
|
||||
}
|
||||
} else if (groupId == R.id.menu_options_about_group) {
|
||||
optionsAboutSelected(item);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
mainBinding.getRoot().closeDrawers();
|
||||
|
||||
@@ -82,7 +82,9 @@ class NewVersionWorker(
|
||||
)
|
||||
|
||||
val notificationManager = NotificationManagerCompat.from(applicationContext)
|
||||
notificationManager.notify(2000, notificationBuilder.build())
|
||||
if (notificationManager.areNotificationsEnabled()) {
|
||||
notificationManager.notify(2000, notificationBuilder.build())
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ReCaptchaException::class)
|
||||
|
||||
@@ -41,50 +41,50 @@ public final class QueueItemMenuUtil {
|
||||
}
|
||||
|
||||
popupMenu.setOnMenuItemClickListener(menuItem -> {
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.menu_item_remove:
|
||||
final int index = playQueue.indexOf(item);
|
||||
playQueue.remove(index);
|
||||
return true;
|
||||
case R.id.menu_item_details:
|
||||
// playQueue is null since we don't want any queue change
|
||||
NavigationHelper.openVideoDetail(context, item.getServiceId(),
|
||||
item.getUrl(), item.getTitle(), null,
|
||||
false);
|
||||
return true;
|
||||
case R.id.menu_item_append_playlist:
|
||||
PlaylistDialog.createCorrespondingDialog(
|
||||
context,
|
||||
List.of(new StreamEntity(item)),
|
||||
dialog -> dialog.show(
|
||||
fragmentManager,
|
||||
"QueueItemMenuUtil@append_playlist"
|
||||
)
|
||||
);
|
||||
final int itemId = menuItem.getItemId();
|
||||
if (itemId == R.id.menu_item_remove) {
|
||||
final int index = playQueue.indexOf(item);
|
||||
playQueue.remove(index);
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_item_details) {
|
||||
// playQueue is null since we don't want any queue change
|
||||
NavigationHelper.openVideoDetail(context, item.getServiceId(),
|
||||
item.getUrl(), item.getTitle(), null,
|
||||
false);
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_item_append_playlist) {
|
||||
PlaylistDialog.createCorrespondingDialog(
|
||||
context,
|
||||
List.of(new StreamEntity(item)),
|
||||
dialog -> dialog.show(
|
||||
fragmentManager,
|
||||
"QueueItemMenuUtil@append_playlist"
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
case R.id.menu_item_channel_details:
|
||||
SparseItemUtil.fetchUploaderUrlIfSparse(context, item.getServiceId(),
|
||||
item.getUrl(), item.getUploaderUrl(),
|
||||
// An intent must be used here.
|
||||
// Opening with FragmentManager transactions is not working,
|
||||
// as PlayQueueActivity doesn't use fragments.
|
||||
uploaderUrl -> NavigationHelper.openChannelFragmentUsingIntent(
|
||||
context, item.getServiceId(), uploaderUrl, item.getUploader()
|
||||
));
|
||||
return true;
|
||||
case R.id.menu_item_share:
|
||||
shareText(context, item.getTitle(), item.getUrl(),
|
||||
item.getThumbnails());
|
||||
return true;
|
||||
case R.id.menu_item_download:
|
||||
fetchStreamInfoAndSaveToDatabase(context, item.getServiceId(), item.getUrl(),
|
||||
info -> {
|
||||
final DownloadDialog downloadDialog = new DownloadDialog(context,
|
||||
info);
|
||||
downloadDialog.show(fragmentManager, "downloadDialog");
|
||||
});
|
||||
return true;
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_item_channel_details) {
|
||||
SparseItemUtil.fetchUploaderUrlIfSparse(context, item.getServiceId(),
|
||||
item.getUrl(), item.getUploaderUrl(),
|
||||
// An intent must be used here.
|
||||
// Opening with FragmentManager transactions is not working,
|
||||
// as PlayQueueActivity doesn't use fragments.
|
||||
uploaderUrl -> NavigationHelper.openChannelFragmentUsingIntent(
|
||||
context, item.getServiceId(), uploaderUrl, item.getUploader()
|
||||
));
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_item_share) {
|
||||
shareText(context, item.getTitle(), item.getUrl(),
|
||||
item.getThumbnails());
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_item_download) {
|
||||
fetchStreamInfoAndSaveToDatabase(context, item.getServiceId(), item.getUrl(),
|
||||
info -> {
|
||||
final DownloadDialog downloadDialog = new DownloadDialog(context,
|
||||
info);
|
||||
downloadDialog.show(fragmentManager, "downloadDialog");
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
@@ -16,6 +16,7 @@ import android.os.IBinder;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
@@ -31,7 +32,6 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.view.menu.ActionMenuItemView;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.collection.SparseArrayCompat;
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
@@ -113,7 +113,7 @@ public class DownloadDialog extends DialogFragment
|
||||
private StoredDirectoryHelper mainStorageAudio = null;
|
||||
private StoredDirectoryHelper mainStorageVideo = null;
|
||||
private DownloadManager downloadManager = null;
|
||||
private ActionMenuItemView okButton = null;
|
||||
private MenuItem okButton = null;
|
||||
private Context context = null;
|
||||
private boolean askForSavePath;
|
||||
|
||||
@@ -558,17 +558,13 @@ public class DownloadDialog extends DialogFragment
|
||||
}
|
||||
boolean flag = true;
|
||||
|
||||
switch (checkedId) {
|
||||
case R.id.audio_button:
|
||||
setupAudioSpinner();
|
||||
break;
|
||||
case R.id.video_button:
|
||||
setupVideoSpinner();
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
setupSubtitleSpinner();
|
||||
flag = false;
|
||||
break;
|
||||
if (checkedId == R.id.audio_button) {
|
||||
setupAudioSpinner();
|
||||
} else if (checkedId == R.id.video_button) {
|
||||
setupVideoSpinner();
|
||||
} else if (checkedId == R.id.subtitle_button) {
|
||||
setupSubtitleSpinner();
|
||||
flag = false;
|
||||
}
|
||||
|
||||
dialogBinding.threads.setEnabled(flag);
|
||||
@@ -585,29 +581,26 @@ public class DownloadDialog extends DialogFragment
|
||||
+ "position = [" + position + "], id = [" + id + "]");
|
||||
}
|
||||
|
||||
switch (parent.getId()) {
|
||||
case R.id.quality_spinner:
|
||||
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.video_button:
|
||||
selectedVideoIndex = position;
|
||||
onVideoStreamSelected();
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
selectedSubtitleIndex = position;
|
||||
break;
|
||||
}
|
||||
onItemSelectedSetFileName();
|
||||
break;
|
||||
case R.id.audio_track_spinner:
|
||||
final boolean trackChanged = selectedAudioTrackIndex != position;
|
||||
selectedAudioTrackIndex = position;
|
||||
if (trackChanged) {
|
||||
updateSecondaryStreams();
|
||||
fetchStreamsSize();
|
||||
}
|
||||
break;
|
||||
case R.id.audio_stream_spinner:
|
||||
selectedAudioIndex = position;
|
||||
final int parentId = parent.getId();
|
||||
if (parentId == R.id.quality_spinner) {
|
||||
final int checkedRadioButtonId = dialogBinding.videoAudioGroup
|
||||
.getCheckedRadioButtonId();
|
||||
if (checkedRadioButtonId == R.id.video_button) {
|
||||
selectedVideoIndex = position;
|
||||
onVideoStreamSelected();
|
||||
} else if (checkedRadioButtonId == R.id.subtitle_button) {
|
||||
selectedSubtitleIndex = position;
|
||||
}
|
||||
onItemSelectedSetFileName();
|
||||
} else if (parentId == R.id.audio_track_spinner) {
|
||||
final boolean trackChanged = selectedAudioTrackIndex != position;
|
||||
selectedAudioTrackIndex = position;
|
||||
if (trackChanged) {
|
||||
updateSecondaryStreams();
|
||||
fetchStreamsSize();
|
||||
}
|
||||
} else if (parentId == R.id.audio_stream_spinner) {
|
||||
selectedAudioIndex = position;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,23 +615,20 @@ public class DownloadDialog extends DialogFragment
|
||||
|| prevFileName.startsWith(getString(R.string.caption_file_name, fileName, ""))) {
|
||||
// only update the file name field if it was not edited by the user
|
||||
|
||||
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
case R.id.video_button:
|
||||
if (!prevFileName.equals(fileName)) {
|
||||
// since the user might have switched between audio and video, the correct
|
||||
// text might already be in place, so avoid resetting the cursor position
|
||||
dialogBinding.fileName.setText(fileName);
|
||||
}
|
||||
break;
|
||||
|
||||
case R.id.subtitle_button:
|
||||
final String setSubtitleLanguageCode = subtitleStreamsAdapter
|
||||
.getItem(selectedSubtitleIndex).getLanguageTag();
|
||||
// this will reset the cursor position, which is bad UX, but it can't be avoided
|
||||
dialogBinding.fileName.setText(getString(
|
||||
R.string.caption_file_name, fileName, setSubtitleLanguageCode));
|
||||
break;
|
||||
final int radioButtonId = dialogBinding.videoAudioGroup
|
||||
.getCheckedRadioButtonId();
|
||||
if (radioButtonId == R.id.audio_button || radioButtonId == R.id.video_button) {
|
||||
if (!prevFileName.equals(fileName)) {
|
||||
// since the user might have switched between audio and video, the correct
|
||||
// text might already be in place, so avoid resetting the cursor position
|
||||
dialogBinding.fileName.setText(fileName);
|
||||
}
|
||||
} else if (radioButtonId == R.id.subtitle_button) {
|
||||
final String setSubtitleLanguageCode = subtitleStreamsAdapter
|
||||
.getItem(selectedSubtitleIndex).getLanguageTag();
|
||||
// this will reset the cursor position, which is bad UX, but it can't be avoided
|
||||
dialogBinding.fileName.setText(getString(
|
||||
R.string.caption_file_name, fileName, setSubtitleLanguageCode));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -770,47 +760,44 @@ public class DownloadDialog extends DialogFragment
|
||||
|
||||
filenameTmp = getNameEditText().concat(".");
|
||||
|
||||
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
selectedMediaType = getString(R.string.last_download_type_audio_key);
|
||||
mainStorage = mainStorageAudio;
|
||||
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
|
||||
size = getWrappedAudioStreams().getSizeInBytes(selectedAudioIndex);
|
||||
if (format == MediaFormat.WEBMA_OPUS) {
|
||||
mimeTmp = "audio/ogg";
|
||||
filenameTmp += "opus";
|
||||
} else if (format != null) {
|
||||
mimeTmp = format.mimeType;
|
||||
filenameTmp += format.getSuffix();
|
||||
}
|
||||
break;
|
||||
case R.id.video_button:
|
||||
selectedMediaType = getString(R.string.last_download_type_video_key);
|
||||
mainStorage = mainStorageVideo;
|
||||
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
|
||||
size = wrappedVideoStreams.getSizeInBytes(selectedVideoIndex);
|
||||
if (format != null) {
|
||||
mimeTmp = format.mimeType;
|
||||
filenameTmp += format.getSuffix();
|
||||
}
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
selectedMediaType = getString(R.string.last_download_type_subtitle_key);
|
||||
mainStorage = mainStorageVideo; // subtitle & video files go together
|
||||
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
|
||||
size = wrappedSubtitleStreams.getSizeInBytes(selectedSubtitleIndex);
|
||||
if (format != null) {
|
||||
mimeTmp = format.mimeType;
|
||||
}
|
||||
final int checkedRadioButtonId = dialogBinding.videoAudioGroup.getCheckedRadioButtonId();
|
||||
if (checkedRadioButtonId == R.id.audio_button) {
|
||||
selectedMediaType = getString(R.string.last_download_type_audio_key);
|
||||
mainStorage = mainStorageAudio;
|
||||
format = audioStreamsAdapter.getItem(selectedAudioIndex).getFormat();
|
||||
size = getWrappedAudioStreams().getSizeInBytes(selectedAudioIndex);
|
||||
if (format == MediaFormat.WEBMA_OPUS) {
|
||||
mimeTmp = "audio/ogg";
|
||||
filenameTmp += "opus";
|
||||
} else if (format != null) {
|
||||
mimeTmp = format.mimeType;
|
||||
filenameTmp += format.getSuffix();
|
||||
}
|
||||
} else if (checkedRadioButtonId == R.id.video_button) {
|
||||
selectedMediaType = getString(R.string.last_download_type_video_key);
|
||||
mainStorage = mainStorageVideo;
|
||||
format = videoStreamsAdapter.getItem(selectedVideoIndex).getFormat();
|
||||
size = wrappedVideoStreams.getSizeInBytes(selectedVideoIndex);
|
||||
if (format != null) {
|
||||
mimeTmp = format.mimeType;
|
||||
filenameTmp += format.getSuffix();
|
||||
}
|
||||
} else if (checkedRadioButtonId == R.id.subtitle_button) {
|
||||
selectedMediaType = getString(R.string.last_download_type_subtitle_key);
|
||||
mainStorage = mainStorageVideo; // subtitle & video files go together
|
||||
format = subtitleStreamsAdapter.getItem(selectedSubtitleIndex).getFormat();
|
||||
size = wrappedSubtitleStreams.getSizeInBytes(selectedSubtitleIndex);
|
||||
if (format != null) {
|
||||
mimeTmp = format.mimeType;
|
||||
}
|
||||
|
||||
if (format == MediaFormat.TTML) {
|
||||
filenameTmp += MediaFormat.SRT.getSuffix();
|
||||
} else if (format != null) {
|
||||
filenameTmp += format.getSuffix();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("No stream selected");
|
||||
if (format == MediaFormat.TTML) {
|
||||
filenameTmp += MediaFormat.SRT.getSuffix();
|
||||
} else if (format != null) {
|
||||
filenameTmp += format.getSuffix();
|
||||
}
|
||||
} else {
|
||||
throw new RuntimeException("No stream selected");
|
||||
}
|
||||
|
||||
if (!askForSavePath && (mainStorage == null
|
||||
@@ -1057,59 +1044,56 @@ public class DownloadDialog extends DialogFragment
|
||||
long nearLength = 0;
|
||||
|
||||
// more download logic: select muxer, subtitle converter, etc.
|
||||
switch (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()) {
|
||||
case R.id.audio_button:
|
||||
kind = 'a';
|
||||
selectedStream = audioStreamsAdapter.getItem(selectedAudioIndex);
|
||||
final int checkedRadioButtonId = dialogBinding.videoAudioGroup.getCheckedRadioButtonId();
|
||||
if (checkedRadioButtonId == R.id.audio_button) {
|
||||
kind = 'a';
|
||||
selectedStream = audioStreamsAdapter.getItem(selectedAudioIndex);
|
||||
|
||||
if (selectedStream.getFormat() == MediaFormat.M4A) {
|
||||
psName = Postprocessing.ALGORITHM_M4A_NO_DASH;
|
||||
} else if (selectedStream.getFormat() == MediaFormat.WEBMA_OPUS) {
|
||||
psName = Postprocessing.ALGORITHM_OGG_FROM_WEBM_DEMUXER;
|
||||
if (selectedStream.getFormat() == MediaFormat.M4A) {
|
||||
psName = Postprocessing.ALGORITHM_M4A_NO_DASH;
|
||||
} else if (selectedStream.getFormat() == MediaFormat.WEBMA_OPUS) {
|
||||
psName = Postprocessing.ALGORITHM_OGG_FROM_WEBM_DEMUXER;
|
||||
}
|
||||
} else if (checkedRadioButtonId == R.id.video_button) {
|
||||
kind = 'v';
|
||||
selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex);
|
||||
|
||||
final SecondaryStreamHelper<AudioStream> secondary = videoStreamsAdapter
|
||||
.getAllSecondary()
|
||||
.get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
|
||||
|
||||
if (secondary != null) {
|
||||
secondaryStream = secondary.getStream();
|
||||
|
||||
if (selectedStream.getFormat() == MediaFormat.MPEG_4) {
|
||||
psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER;
|
||||
} else {
|
||||
psName = Postprocessing.ALGORITHM_WEBM_MUXER;
|
||||
}
|
||||
break;
|
||||
case R.id.video_button:
|
||||
kind = 'v';
|
||||
selectedStream = videoStreamsAdapter.getItem(selectedVideoIndex);
|
||||
|
||||
final SecondaryStreamHelper<AudioStream> secondary = videoStreamsAdapter
|
||||
.getAllSecondary()
|
||||
.get(wrappedVideoStreams.getStreamsList().indexOf(selectedStream));
|
||||
final long videoSize = wrappedVideoStreams.getSizeInBytes(
|
||||
(VideoStream) selectedStream);
|
||||
|
||||
if (secondary != null) {
|
||||
secondaryStream = secondary.getStream();
|
||||
|
||||
if (selectedStream.getFormat() == MediaFormat.MPEG_4) {
|
||||
psName = Postprocessing.ALGORITHM_MP4_FROM_DASH_MUXER;
|
||||
} else {
|
||||
psName = Postprocessing.ALGORITHM_WEBM_MUXER;
|
||||
}
|
||||
|
||||
final long videoSize = wrappedVideoStreams.getSizeInBytes(
|
||||
(VideoStream) selectedStream);
|
||||
|
||||
// set nearLength, only, if both sizes are fetched or known. This probably
|
||||
// does not work on slow networks but is later updated in the downloader
|
||||
if (secondary.getSizeInBytes() > 0 && videoSize > 0) {
|
||||
nearLength = secondary.getSizeInBytes() + videoSize;
|
||||
}
|
||||
// set nearLength, only, if both sizes are fetched or known. This probably
|
||||
// does not work on slow networks but is later updated in the downloader
|
||||
if (secondary.getSizeInBytes() > 0 && videoSize > 0) {
|
||||
nearLength = secondary.getSizeInBytes() + videoSize;
|
||||
}
|
||||
break;
|
||||
case R.id.subtitle_button:
|
||||
threads = 1; // use unique thread for subtitles due small file size
|
||||
kind = 's';
|
||||
selectedStream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
|
||||
}
|
||||
} else if (checkedRadioButtonId == R.id.subtitle_button) {
|
||||
threads = 1; // use unique thread for subtitles due small file size
|
||||
kind = 's';
|
||||
selectedStream = subtitleStreamsAdapter.getItem(selectedSubtitleIndex);
|
||||
|
||||
if (selectedStream.getFormat() == MediaFormat.TTML) {
|
||||
psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
|
||||
psArgs = new String[] {
|
||||
selectedStream.getFormat().getSuffix(),
|
||||
"false" // ignore empty frames
|
||||
};
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
if (selectedStream.getFormat() == MediaFormat.TTML) {
|
||||
psName = Postprocessing.ALGORITHM_TTML_CONVERTER;
|
||||
psArgs = new String[]{
|
||||
selectedStream.getFormat().getSuffix(),
|
||||
"false" // ignore empty frames
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
if (secondaryStream == null) {
|
||||
|
||||
@@ -133,17 +133,16 @@ public class ErrorActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
onBackPressed();
|
||||
return true;
|
||||
case R.id.menu_item_share_error:
|
||||
ShareUtils.shareText(getApplicationContext(),
|
||||
getString(R.string.error_report_title), buildJson());
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
final int itemId = item.getItemId();
|
||||
if (itemId == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
} else if (itemId == R.id.menu_item_share_error) {
|
||||
ShareUtils.shareText(getApplicationContext(),
|
||||
getString(R.string.error_report_title), buildJson());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void openPrivacyPolicyDialog(final Context context, final String action) {
|
||||
|
||||
@@ -134,8 +134,11 @@ class ErrorUtil {
|
||||
)
|
||||
)
|
||||
|
||||
NotificationManagerCompat.from(context)
|
||||
.notify(ERROR_REPORT_NOTIFICATION_ID, notificationBuilder.build())
|
||||
val notificationManager = NotificationManagerCompat.from(context)
|
||||
if (notificationManager.areNotificationsEnabled()) {
|
||||
notificationManager
|
||||
.notify(ERROR_REPORT_NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
|
||||
ContextCompat.getMainExecutor(context).execute {
|
||||
// since the notification is silent, also show a toast, otherwise the user is confused
|
||||
|
||||
@@ -127,6 +127,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
super.onBackPressed();
|
||||
saveCookiesAndFinish();
|
||||
}
|
||||
|
||||
|
||||
@@ -160,34 +160,29 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(@NonNull final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.menu_item_notify:
|
||||
final boolean value = !item.isChecked();
|
||||
item.setEnabled(false);
|
||||
setNotify(value);
|
||||
break;
|
||||
case R.id.action_settings:
|
||||
NavigationHelper.openSettings(requireContext());
|
||||
break;
|
||||
case R.id.menu_item_rss:
|
||||
if (currentInfo != null) {
|
||||
ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl());
|
||||
}
|
||||
break;
|
||||
case R.id.menu_item_openInBrowser:
|
||||
if (currentInfo != null) {
|
||||
ShareUtils.openUrlInBrowser(requireContext(),
|
||||
currentInfo.getOriginalUrl());
|
||||
}
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
if (currentInfo != null) {
|
||||
ShareUtils.shareText(requireContext(), name,
|
||||
currentInfo.getOriginalUrl(), currentInfo.getAvatars());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
final int itemId = item.getItemId();
|
||||
if (itemId == R.id.menu_item_notify) {
|
||||
final boolean value = !item.isChecked();
|
||||
item.setEnabled(false);
|
||||
setNotify(value);
|
||||
} else if (itemId == R.id.action_settings) {
|
||||
NavigationHelper.openSettings(requireContext());
|
||||
} else if (itemId == R.id.menu_item_rss) {
|
||||
if (currentInfo != null) {
|
||||
ShareUtils.openUrlInApp(requireContext(), currentInfo.getFeedUrl());
|
||||
}
|
||||
} else if (itemId == R.id.menu_item_openInBrowser) {
|
||||
if (currentInfo != null) {
|
||||
ShareUtils.openUrlInBrowser(requireContext(),
|
||||
currentInfo.getOriginalUrl());
|
||||
}
|
||||
} else if (itemId == R.id.menu_item_share) {
|
||||
if (currentInfo != null) {
|
||||
ShareUtils.shareText(requireContext(), name,
|
||||
currentInfo.getOriginalUrl(), currentInfo.getAvatars());
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -232,35 +232,30 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_settings:
|
||||
NavigationHelper.openSettings(requireContext());
|
||||
break;
|
||||
case R.id.menu_item_openInBrowser:
|
||||
ShareUtils.openUrlInBrowser(requireContext(), url);
|
||||
break;
|
||||
case R.id.menu_item_share:
|
||||
ShareUtils.shareText(requireContext(), name, url,
|
||||
currentInfo == null ? List.of() : currentInfo.getThumbnails());
|
||||
break;
|
||||
case R.id.menu_item_bookmark:
|
||||
onBookmarkClicked();
|
||||
break;
|
||||
case R.id.menu_item_append_playlist:
|
||||
if (currentInfo != null) {
|
||||
disposables.add(PlaylistDialog.createCorrespondingDialog(
|
||||
getContext(),
|
||||
getPlayQueue()
|
||||
.getStreams()
|
||||
.stream()
|
||||
.map(StreamEntity::new)
|
||||
.collect(Collectors.toList()),
|
||||
dialog -> dialog.show(getFM(), TAG)
|
||||
));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
final int itemId = item.getItemId();
|
||||
if (itemId == R.id.action_settings) {
|
||||
NavigationHelper.openSettings(requireContext());
|
||||
} else if (itemId == R.id.menu_item_openInBrowser) {
|
||||
ShareUtils.openUrlInBrowser(requireContext(), url);
|
||||
} else if (itemId == R.id.menu_item_share) {
|
||||
ShareUtils.shareText(requireContext(), name, url,
|
||||
currentInfo == null ? List.of() : currentInfo.getThumbnails());
|
||||
} else if (itemId == R.id.menu_item_bookmark) {
|
||||
onBookmarkClicked();
|
||||
} else if (itemId == R.id.menu_item_append_playlist) {
|
||||
if (currentInfo != null) {
|
||||
disposables.add(PlaylistDialog.createCorrespondingDialog(
|
||||
getContext(),
|
||||
getPlayQueue()
|
||||
.getStreams()
|
||||
.stream()
|
||||
.map(StreamEntity::new)
|
||||
.collect(Collectors.toList()),
|
||||
dialog -> dialog.show(getFM(), TAG)
|
||||
));
|
||||
}
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ class FeedFragment : BaseStateFragment<FeedState>() {
|
||||
viewModel.getShowFutureItemsFromPreferences()
|
||||
)
|
||||
|
||||
AlertDialog.Builder(context!!)
|
||||
AlertDialog.Builder(requireContext())
|
||||
.setTitle(R.string.feed_hide_streams_title)
|
||||
.setMultiChoiceItems(dialogItems, checkedDialogItems) { _, which, isChecked ->
|
||||
checkedDialogItems[which] = isChecked
|
||||
|
||||
@@ -92,8 +92,10 @@ class NotificationHelper(val context: Context) {
|
||||
// Show individual stream notifications, set channel icon only if there is actually
|
||||
// one
|
||||
showStreamNotifications(newStreams, data.serviceId, data.url, bitmap)
|
||||
// Show summary notification
|
||||
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||
// Show summary notification if enabled
|
||||
if (manager.areNotificationsEnabled()) {
|
||||
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||
}
|
||||
|
||||
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
||||
}
|
||||
@@ -101,8 +103,10 @@ class NotificationHelper(val context: Context) {
|
||||
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable) {
|
||||
// Show individual stream notifications
|
||||
showStreamNotifications(newStreams, data.serviceId, data.url, null)
|
||||
// Show summary notification
|
||||
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||
// Show summary notification if enabled
|
||||
if (manager.areNotificationsEnabled()) {
|
||||
manager.notify(data.pseudoId, summaryBuilder.build())
|
||||
}
|
||||
iconLoadingTargets.remove(this) // allow it to be garbage-collected
|
||||
}
|
||||
|
||||
@@ -126,7 +130,9 @@ class NotificationHelper(val context: Context) {
|
||||
) {
|
||||
for (stream in newStreams) {
|
||||
val notification = createStreamNotification(stream, serviceId, channelUrl, channelIcon)
|
||||
manager.notify(stream.url.hashCode(), notification)
|
||||
if (manager.areNotificationsEnabled()) {
|
||||
manager.notify(stream.url.hashCode(), notification)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +185,9 @@ class FeedLoadService : Service() {
|
||||
}
|
||||
}
|
||||
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
||||
if (notificationManager.areNotificationsEnabled()) {
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -506,7 +506,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||
private fun hideKeyboardSearch() {
|
||||
inputMethodManager.hideSoftInputFromWindow(
|
||||
searchLayoutBinding.toolbarSearchEditText.windowToken,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN
|
||||
InputMethodManager.HIDE_NOT_ALWAYS
|
||||
)
|
||||
searchLayoutBinding.toolbarSearchEditText.clearFocus()
|
||||
}
|
||||
@@ -523,7 +523,7 @@ class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||
private fun hideKeyboard() {
|
||||
inputMethodManager.hideSoftInputFromWindow(
|
||||
feedGroupCreateBinding.groupNameInput.windowToken,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN
|
||||
InputMethodManager.HIDE_NOT_ALWAYS
|
||||
)
|
||||
feedGroupCreateBinding.groupNameInput.clearFocus()
|
||||
}
|
||||
|
||||
@@ -144,7 +144,9 @@ public abstract class BaseImportExportService extends Service {
|
||||
notificationBuilder.setContentText(text);
|
||||
}
|
||||
|
||||
notificationManager.notify(getNotificationId(), notificationBuilder.build());
|
||||
if (notificationManager.areNotificationsEnabled()) {
|
||||
notificationManager.notify(getNotificationId(), notificationBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
protected void stopService() {
|
||||
@@ -174,7 +176,10 @@ public abstract class BaseImportExportService extends Service {
|
||||
.setContentTitle(title)
|
||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(textOrEmpty))
|
||||
.setContentText(textOrEmpty);
|
||||
notificationManager.notify(getNotificationId(), notificationBuilder.build());
|
||||
|
||||
if (notificationManager.areNotificationsEnabled()) {
|
||||
notificationManager.notify(getNotificationId(), notificationBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
protected NotificationCompat.Builder createNotification() {
|
||||
|
||||
@@ -127,39 +127,39 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
return true;
|
||||
case R.id.action_settings:
|
||||
NavigationHelper.openSettings(this);
|
||||
return true;
|
||||
case R.id.action_append_playlist:
|
||||
PlaylistDialog.showForPlayQueue(player, getSupportFragmentManager());
|
||||
return true;
|
||||
case R.id.action_playback_speed:
|
||||
openPlaybackParameterDialog();
|
||||
return true;
|
||||
case R.id.action_mute:
|
||||
player.toggleMute();
|
||||
return true;
|
||||
case R.id.action_system_audio:
|
||||
startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS));
|
||||
return true;
|
||||
case R.id.action_switch_main:
|
||||
final int itemId = item.getItemId();
|
||||
if (itemId == android.R.id.home) {
|
||||
finish();
|
||||
return true;
|
||||
} else if (itemId == R.id.action_settings) {
|
||||
NavigationHelper.openSettings(this);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_append_playlist) {
|
||||
PlaylistDialog.showForPlayQueue(player, getSupportFragmentManager());
|
||||
return true;
|
||||
} else if (itemId == R.id.action_playback_speed) {
|
||||
openPlaybackParameterDialog();
|
||||
return true;
|
||||
} else if (itemId == R.id.action_mute) {
|
||||
player.toggleMute();
|
||||
return true;
|
||||
} else if (itemId == R.id.action_system_audio) {
|
||||
startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS));
|
||||
return true;
|
||||
} else if (itemId == R.id.action_switch_main) {
|
||||
this.player.setRecovery();
|
||||
NavigationHelper.playOnMainPlayer(this, player.getPlayQueue(), true);
|
||||
return true;
|
||||
} else if (itemId == R.id.action_switch_popup) {
|
||||
if (PermissionHelper.isPopupEnabledElseAsk(this)) {
|
||||
this.player.setRecovery();
|
||||
NavigationHelper.playOnMainPlayer(this, player.getPlayQueue(), true);
|
||||
return true;
|
||||
case R.id.action_switch_popup:
|
||||
if (PermissionHelper.isPopupEnabledElseAsk(this)) {
|
||||
this.player.setRecovery();
|
||||
NavigationHelper.playOnPopupPlayer(this, player.getPlayQueue(), true);
|
||||
}
|
||||
return true;
|
||||
case R.id.action_switch_background:
|
||||
this.player.setRecovery();
|
||||
NavigationHelper.playOnBackgroundPlayer(this, player.getPlayQueue(), true);
|
||||
return true;
|
||||
NavigationHelper.playOnPopupPlayer(this, player.getPlayQueue(), true);
|
||||
}
|
||||
return true;
|
||||
} else if (itemId == R.id.action_switch_background) {
|
||||
this.player.setRecovery();
|
||||
NavigationHelper.playOnBackgroundPlayer(this, player.getPlayQueue(), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (item.getGroupId() == MENU_ID_AUDIO_TRACK) {
|
||||
|
||||
@@ -72,7 +72,9 @@ public final class NotificationUtil {
|
||||
notificationBuilder = createNotification();
|
||||
}
|
||||
updateNotification();
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
if (notificationManager.areNotificationsEnabled()) {
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void updateThumbnail() {
|
||||
@@ -84,7 +86,9 @@ public final class NotificationUtil {
|
||||
}
|
||||
|
||||
setLargeIcon(notificationBuilder);
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
if (notificationManager.areNotificationsEnabled()) {
|
||||
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ public final class KeyboardUtil {
|
||||
final InputMethodManager imm = ContextCompat.getSystemService(activity,
|
||||
InputMethodManager.class);
|
||||
imm.hideSoftInputFromWindow(editText.getWindowToken(),
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN);
|
||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
|
||||
editText.clearFocus();
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ import android.view.Window;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.view.WindowCallbackWrapper;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
@@ -232,7 +231,7 @@ public final class FocusOverlayView extends Drawable implements
|
||||
// Unfortunately many such forms of "scrolling" do not count as scrolling for purpose
|
||||
// of dispatching ViewTreeObserver callbacks, so we have to intercept them by directly
|
||||
// receiving keys from Window.
|
||||
window.setCallback(new WindowCallbackWrapper(window.getCallback()) {
|
||||
window.setCallback(new SimpleWindowCallback(window.getCallback()) {
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(final KeyEvent event) {
|
||||
final boolean res = super.dispatchKeyEvent(event);
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 NewPipe e.V. <https://newpipe-ev.de>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe.views
|
||||
|
||||
import android.os.Build
|
||||
import android.view.KeyEvent
|
||||
import android.view.KeyboardShortcutGroup
|
||||
import android.view.Menu
|
||||
import android.view.Window
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
open class SimpleWindowCallback(private val baseCallback: Window.Callback) :
|
||||
Window.Callback by baseCallback {
|
||||
|
||||
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
|
||||
return baseCallback.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
override fun onPointerCaptureChanged(hasCapture: Boolean) {
|
||||
baseCallback.onPointerCaptureChanged(hasCapture)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
override fun onProvideKeyboardShortcuts(
|
||||
data: List<KeyboardShortcutGroup?>?,
|
||||
menu: Menu?,
|
||||
deviceId: Int
|
||||
) {
|
||||
baseCallback.onProvideKeyboardShortcuts(data, menu, deviceId)
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import static us.shandian.giga.get.DownloadMission.ERROR_HTTP_FORBIDDEN;
|
||||
* Single-threaded fallback mode
|
||||
*/
|
||||
public class DownloadRunnableFallback extends Thread {
|
||||
private static final String TAG = "DownloadRunnableFallback";
|
||||
private static final String TAG = DownloadRunnableFallback.class.getSimpleName();
|
||||
|
||||
private final DownloadMission mMission;
|
||||
|
||||
|
||||
@@ -102,14 +102,23 @@ public class FinishedMissionStore extends SQLiteOpenHelper {
|
||||
db.beginTransaction();
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KEY_SOURCE, cursor.getString(cursor.getColumnIndex(KEY_SOURCE)));
|
||||
values.put(KEY_DONE, cursor.getString(cursor.getColumnIndex(KEY_DONE)));
|
||||
values.put(KEY_TIMESTAMP, cursor.getLong(cursor.getColumnIndex(KEY_TIMESTAMP)));
|
||||
values.put(KEY_KIND, cursor.getString(cursor.getColumnIndex(KEY_KIND)));
|
||||
values.put(
|
||||
KEY_SOURCE,
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(KEY_SOURCE))
|
||||
);
|
||||
values.put(
|
||||
KEY_DONE,
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(KEY_DONE))
|
||||
);
|
||||
values.put(
|
||||
KEY_TIMESTAMP,
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(KEY_TIMESTAMP))
|
||||
);
|
||||
values.put(KEY_KIND, cursor.getString(cursor.getColumnIndexOrThrow(KEY_KIND)));
|
||||
values.put(KEY_PATH, Uri.fromFile(
|
||||
new File(
|
||||
cursor.getString(cursor.getColumnIndex(KEY_LOCATION)),
|
||||
cursor.getString(cursor.getColumnIndex(KEY_NAME))
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(KEY_LOCATION)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(KEY_NAME))
|
||||
)
|
||||
).toString());
|
||||
|
||||
@@ -141,7 +150,8 @@ public class FinishedMissionStore extends SQLiteOpenHelper {
|
||||
}
|
||||
|
||||
private FinishedMission getMissionFromCursor(Cursor cursor) {
|
||||
String kind = Objects.requireNonNull(cursor).getString(cursor.getColumnIndex(KEY_KIND));
|
||||
String kind = Objects.requireNonNull(cursor)
|
||||
.getString(cursor.getColumnIndexOrThrow(KEY_KIND));
|
||||
if (kind == null || kind.isEmpty()) kind = "?";
|
||||
|
||||
String path = cursor.getString(cursor.getColumnIndexOrThrow(KEY_PATH));
|
||||
|
||||
@@ -632,103 +632,95 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
|
||||
DownloadMission mission = h.item.mission instanceof DownloadMission ? (DownloadMission) h.item.mission : null;
|
||||
|
||||
if (mission != null) {
|
||||
switch (id) {
|
||||
case R.id.start:
|
||||
h.status.setText(UNDEFINED_PROGRESS);
|
||||
mDownloadManager.resumeMission(mission);
|
||||
return true;
|
||||
case R.id.pause:
|
||||
mDownloadManager.pauseMission(mission);
|
||||
return true;
|
||||
case R.id.error_message_view:
|
||||
showError(mission);
|
||||
return true;
|
||||
case R.id.queue:
|
||||
boolean flag = !h.queue.isChecked();
|
||||
h.queue.setChecked(flag);
|
||||
mission.setEnqueued(flag);
|
||||
updateProgress(h);
|
||||
return true;
|
||||
case R.id.retry:
|
||||
if (mission.isPsRunning()) {
|
||||
mission.psContinue(true);
|
||||
} else {
|
||||
mDownloadManager.tryRecover(mission);
|
||||
if (mission.storage.isInvalid())
|
||||
mRecover.tryRecover(mission);
|
||||
else
|
||||
recoverMission(mission);
|
||||
}
|
||||
return true;
|
||||
case R.id.cancel:
|
||||
mission.psContinue(false);
|
||||
return false;
|
||||
if (id == R.id.start) {
|
||||
h.status.setText(UNDEFINED_PROGRESS);
|
||||
mDownloadManager.resumeMission(mission);
|
||||
return true;
|
||||
} else if (id == R.id.pause) {
|
||||
mDownloadManager.pauseMission(mission);
|
||||
return true;
|
||||
} else if (id == R.id.error_message_view) {
|
||||
showError(mission);
|
||||
return true;
|
||||
} else if (id == R.id.queue) {
|
||||
boolean flag = !h.queue.isChecked();
|
||||
h.queue.setChecked(flag);
|
||||
mission.setEnqueued(flag);
|
||||
updateProgress(h);
|
||||
return true;
|
||||
} else if (id == R.id.retry) {
|
||||
if (mission.isPsRunning()) {
|
||||
mission.psContinue(true);
|
||||
} else {
|
||||
mDownloadManager.tryRecover(mission);
|
||||
if (mission.storage.isInvalid())
|
||||
mRecover.tryRecover(mission);
|
||||
else
|
||||
recoverMission(mission);
|
||||
}
|
||||
return true;
|
||||
} else if (id == R.id.cancel) {
|
||||
mission.psContinue(false);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
switch (id) {
|
||||
case R.id.menu_item_share:
|
||||
shareFile(h.item.mission);
|
||||
return true;
|
||||
case R.id.delete:
|
||||
// delete the entry and the file
|
||||
if (id == R.id.menu_item_share) {
|
||||
shareFile(h.item.mission);
|
||||
return true;
|
||||
} else if (id == R.id.delete) {// delete the entry and the file
|
||||
mDeleter.append(h.item.mission, true);
|
||||
applyChanges();
|
||||
checkMasterButtonsVisibility();
|
||||
return true;
|
||||
} else if (id == R.id.delete_entry) {// just delete the entry
|
||||
mDeleter.append(h.item.mission, false);
|
||||
applyChanges();
|
||||
checkMasterButtonsVisibility();
|
||||
return true;
|
||||
} else if (id == R.id.md5 || id == R.id.sha1) {
|
||||
final StoredFileHelper storage = h.item.mission.storage;
|
||||
if (!storage.existsAsFile()) {
|
||||
Toast.makeText(mContext, R.string.missing_file, Toast.LENGTH_SHORT).show();
|
||||
mDeleter.append(h.item.mission, true);
|
||||
applyChanges();
|
||||
checkMasterButtonsVisibility();
|
||||
return true;
|
||||
case R.id.delete_entry:
|
||||
// just delete the entry
|
||||
mDeleter.append(h.item.mission, false);
|
||||
applyChanges();
|
||||
checkMasterButtonsVisibility();
|
||||
return true;
|
||||
case R.id.md5:
|
||||
case R.id.sha1:
|
||||
final StoredFileHelper storage = h.item.mission.storage;
|
||||
if (!storage.existsAsFile()) {
|
||||
Toast.makeText(mContext, R.string.missing_file, Toast.LENGTH_SHORT).show();
|
||||
mDeleter.append(h.item.mission, true);
|
||||
applyChanges();
|
||||
return true;
|
||||
}
|
||||
final NotificationManager notificationManager
|
||||
= ContextCompat.getSystemService(mContext, NotificationManager.class);
|
||||
final NotificationCompat.Builder progressNotificationBuilder
|
||||
= new NotificationCompat.Builder(mContext,
|
||||
mContext.getString(R.string.hash_channel_id))
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
||||
.setContentTitle(mContext.getString(R.string.msg_calculating_hash))
|
||||
.setContentText(mContext.getString(R.string.msg_wait))
|
||||
.setProgress(0, 0, true)
|
||||
.setOngoing(true);
|
||||
}
|
||||
final NotificationManager notificationManager
|
||||
= ContextCompat.getSystemService(mContext, NotificationManager.class);
|
||||
final NotificationCompat.Builder progressNotificationBuilder
|
||||
= new NotificationCompat.Builder(mContext,
|
||||
mContext.getString(R.string.hash_channel_id))
|
||||
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_triangle_white)
|
||||
.setContentTitle(mContext.getString(R.string.msg_calculating_hash))
|
||||
.setContentText(mContext.getString(R.string.msg_wait))
|
||||
.setProgress(0, 0, true)
|
||||
.setOngoing(true);
|
||||
|
||||
notificationManager.notify(HASH_NOTIFICATION_ID, progressNotificationBuilder
|
||||
.build());
|
||||
compositeDisposable.add(
|
||||
Observable.fromCallable(() -> Utility.checksum(storage, id))
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(result -> {
|
||||
ShareUtils.copyToClipboard(mContext, result);
|
||||
notificationManager.cancel(HASH_NOTIFICATION_ID);
|
||||
})
|
||||
);
|
||||
return true;
|
||||
case R.id.source:
|
||||
/*Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(h.item.mission.source));
|
||||
mContext.startActivity(intent);*/
|
||||
try {
|
||||
Intent intent = NavigationHelper.getIntentByLink(mContext, h.item.mission.source);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
|
||||
mContext.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Selected item has a invalid source", e);
|
||||
}
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
notificationManager.notify(HASH_NOTIFICATION_ID, progressNotificationBuilder
|
||||
.build());
|
||||
compositeDisposable.add(
|
||||
Observable.fromCallable(() -> Utility.checksum(storage, id))
|
||||
.subscribeOn(Schedulers.computation())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(result -> {
|
||||
ShareUtils.copyToClipboard(mContext, result);
|
||||
notificationManager.cancel(HASH_NOTIFICATION_ID);
|
||||
})
|
||||
);
|
||||
return true;
|
||||
} else if (id == R.id.source) {
|
||||
try {
|
||||
Intent intent = NavigationHelper.getIntentByLink(mContext, h.item.mission.source);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP);
|
||||
mContext.startActivity(intent);
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Selected item has a invalid source", e);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void applyChanges() {
|
||||
|
||||
@@ -186,23 +186,24 @@ public class MissionsFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.switch_mode:
|
||||
mLinear = !mLinear;
|
||||
updateList();
|
||||
return true;
|
||||
case R.id.clear_list:
|
||||
showClearDownloadHistoryPrompt();
|
||||
return true;
|
||||
case R.id.start_downloads:
|
||||
mBinder.getDownloadManager().startAllMissions();
|
||||
return true;
|
||||
case R.id.pause_downloads:
|
||||
mBinder.getDownloadManager().pauseAllMissions(false);
|
||||
mAdapter.refreshMissionItems();// update items view
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
int itemId = item.getItemId();
|
||||
if (itemId == R.id.switch_mode) {
|
||||
mLinear = !mLinear;
|
||||
updateList();
|
||||
return true;
|
||||
} else if (itemId == R.id.clear_list) {
|
||||
showClearDownloadHistoryPrompt();
|
||||
return true;
|
||||
} else if (itemId == R.id.start_downloads) {
|
||||
mBinder.getDownloadManager().startAllMissions();
|
||||
return true;
|
||||
} else if (itemId == R.id.pause_downloads) {
|
||||
mBinder.getDownloadManager().pauseAllMissions(false);
|
||||
mAdapter.refreshMissionItems();// update items view
|
||||
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
public void showClearDownloadHistoryPrompt() {
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
android:viewportWidth="24"
|
||||
android:tint="@color/defaultIconTint">
|
||||
<path
|
||||
android:pathData="M20 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zM9.5 16.5v-9l7 4.5-7 4.5z"
|
||||
android:pathData="M20 4H4c-1.1 0-2 0.9-2 2v12c0 1.1 0.9 2 2 2h16c1.1 0 2-0.9 2-2V6c0-1.1-0.9-2-2-2zM9.5 16.5v-9l7 4.5-7 4.5z"
|
||||
android:fillColor="#FF000000"/>
|
||||
</vector>
|
||||
|
||||
@@ -122,8 +122,8 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/exo_controls_rewind"
|
||||
android:tint="?attr/colorAccent"
|
||||
android:contentDescription="@string/rewind" />
|
||||
android:contentDescription="@string/rewind"
|
||||
app:tint="?attr/colorAccent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/control_play_pause"
|
||||
@@ -139,8 +139,8 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_pause"
|
||||
android:tint="?attr/colorAccent"
|
||||
android:contentDescription="@string/pause" />
|
||||
android:contentDescription="@string/pause"
|
||||
app:tint="?attr/colorAccent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/control_progress_bar"
|
||||
@@ -172,8 +172,8 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/exo_controls_fastforward"
|
||||
android:tint="?attr/colorAccent"
|
||||
android:contentDescription="@string/forward" />
|
||||
android:contentDescription="@string/forward"
|
||||
app:tint="?attr/colorAccent" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
@@ -215,8 +215,8 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_repeat"
|
||||
android:tint="?attr/colorAccent"
|
||||
android:contentDescription="@string/notification_action_repeat" />
|
||||
android:contentDescription="@string/notification_action_repeat"
|
||||
app:tint="?attr/colorAccent" />
|
||||
|
||||
<View
|
||||
android:id="@+id/anchor"
|
||||
@@ -236,8 +236,8 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_shuffle"
|
||||
android:tint="?attr/colorAccent"
|
||||
android:contentDescription="@string/notification_action_shuffle" />
|
||||
android:contentDescription="@string/notification_action_shuffle"
|
||||
app:tint="?attr/colorAccent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/control_forward"
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_repeat"
|
||||
android:tint="?attr/colorAccent"
|
||||
app:tint="?attr/colorAccent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
@@ -205,7 +205,7 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/exo_controls_rewind"
|
||||
android:tint="?attr/colorAccent" />
|
||||
app:tint="?attr/colorAccent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/control_play_pause"
|
||||
@@ -220,7 +220,7 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_pause"
|
||||
android:tint="?attr/colorAccent"
|
||||
app:tint="?attr/colorAccent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<ProgressBar
|
||||
@@ -255,7 +255,7 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/exo_controls_fastforward"
|
||||
android:tint="?attr/colorAccent" />
|
||||
app:tint="?attr/colorAccent" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/control_forward"
|
||||
@@ -285,7 +285,7 @@
|
||||
android:focusable="true"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_shuffle"
|
||||
android:tint="?attr/colorAccent"
|
||||
app:tint="?attr/colorAccent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -309,6 +309,6 @@
|
||||
<string name="notification_action_nothing">কিছু না</string>
|
||||
<string name="yes">হ্যাঁ</string>
|
||||
<string name="no">না</string>
|
||||
<string name="search_with_service_name">সার্চ</string>
|
||||
<string name="search_with_service_name_and_filter">খুঁজুন</string>
|
||||
<string name="search_with_service_name">সার্চ %1$s</string>
|
||||
<string name="search_with_service_name_and_filter">খুঁজুন %1$s (%2$s)</string>
|
||||
</resources>
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
android.enableJetifier=false
|
||||
android.nonFinalResIds=false
|
||||
android.nonTransitiveRClass=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx2048M --add-opens jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
|
||||
systemProp.file.encoding=utf-8
|
||||
|
||||
@@ -23,8 +23,8 @@ groupie = "2.10.1"
|
||||
jsoup = "1.22.1"
|
||||
junit = "4.13.2"
|
||||
junit-ext = "1.3.0"
|
||||
kotlin = "2.2.21"
|
||||
ksp = "2.3.4"
|
||||
kotlin = "2.3.0"
|
||||
ksp = "2.3.5"
|
||||
ktlint = "1.8.0"
|
||||
leakcanary = "2.14"
|
||||
lifecycle = "2.9.4" # Newer versions require minSdk >= 23
|
||||
|
||||
Reference in New Issue
Block a user