1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2026-02-06 10:10:16 +00:00

Compare commits

...

31 Commits

Author SHA1 Message Date
Aayush Gupta
d00b8cb0e0 FocusOverlayView: Avoid accessing restricted API
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 16:12:09 +08:00
Aayush Gupta
5c10e12b01 ic_smart_display: Fix invalid vector path on older devices
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 15:51:29 +08:00
Aayush Gupta
181a34ac12 lint: Supress more translation related errors
They should be fixed by translators or weblate in general.

 ../../src/main/res/values-lt/strings.xml:87: For locale "lt" (Lithuanian) the following quantity should also be defined: many (e.g. "1.1 obuolio")
 ../../src/main/res/values-is/strings.xml:318: The quantity 'one' matches more than one specific number in this locale (1, 21, 31, 41, 51, 61, 71, 81, 101, 1001, …), but the message did not include a formatting argument (such as %d). This is usually an internationalization error. See full issue explanation for more.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 15:24:12 +08:00
Aayush Gupta
1fb959b274 DownloadDialog: Avoid using restricted API for menuitem
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 15:02:28 +08:00
Aayush Gupta
01cd169442 activity_player_queue_control: Switch to app:tint instead of android:tint
../../src/main/res/layout/activity_player_queue_control.xml:208: Must use app:tint instead of android:tint

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 14:50:02 +08:00
Aayush Gupta
a8de5ae2ea DownloadRunnableFallback: Fix error with log tag being too long
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 14:46:28 +08:00
Aayush Gupta
83f4fa4253 SearchFragment: Fix hint translation
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 13:16:36 +08:00
Aayush Gupta
0edd1900d0 Use correct constant for hiding keyboard
../../src/main/java/org/schabi/newpipe/local/subscription/dialog/FeedGroupDialog.kt:509: Must be one or more of: InputMethodManager.HIDE_IMPLICIT_ONLY, InputMethodManager.HIDE_NOT_ALWAYS

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 13:08:56 +08:00
Aayush Gupta
d5cfdcab5b Use requireContext() instead of asserting non-null context
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 13:05:05 +08:00
Aayush Gupta
0866bdf040 FinishedMIssionStore: Throw exception if column is missing
../../src/main/java/us/shandian/giga/get/sqlite/FinishedMissionStore.java:105: Value must be ≥ 0 but getColumnIndex can be -1

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 13:03:13 +08:00
Aayush Gupta
735d1ab05d ReCaptchaActivity: Add missing super call on backpress
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 12:58:14 +08:00
Aayush Gupta
71b62e0bd1 Add missing permission checks for notifications
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 12:56:37 +08:00
Aayush Gupta
04ad5c7c53 Introduce lint configuration and enable checks
Supress missing translation errors as they are done by volunteers

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 12:19:23 +08:00
Aayush Gupta
5da54efcae Enable resources shrinking
AGP 9.0.0 has introduced additional resource shrinking tasks. Its better
to enable and fix this.

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 12:19:23 +08:00
Aayush Gupta
b906465f80 Address non-final resource IDs warnings
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-05 12:19:23 +08:00
Stypox
83f9646eec Merge pull request #13190 from TeamNewPipe/agp9fixes
Partially revert upgrade to AGP 9.0.0
2026-02-04 19:47:43 +01:00
Aayush Gupta
85d43fe45e proguard: Keep fields of generated proguard files
Inspired from https://github.com/protocolbuffers/protobuf/blob/main/java/lite.md#r8-rule-to-make-production-app-builds-work

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-04 16:22:48 +08:00
Aayush Gupta
8d6e68d6f4 Partially revert upgrade to AGP 9.0.0
Building is broken on ecrypted filesystems

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-02-04 15:56:44 +08:00
Aayush Gupta
15b5cef6c2 Merge pull request #13136 from TeamNewPipe/agp9
Upgrade Android Gradle Plugin to 9.0.0
2026-02-01 17:34:47 +08:00
Tobi
739b6ae57b Merge pull request #13141 from salmanmkc/upgrade-github-actions-node24-general
Upgrade GitHub Actions to latest versions
2026-01-30 02:31:40 -08:00
Tobi
cc33b685a5 Merge pull request #13140 from salmanmkc/upgrade-github-actions-node24
Upgrade GitHub Actions for Node 24 compatibility
2026-01-30 02:31:29 -08:00
Salman Muin Kayser Chishti
d051e8ecc8 Upgrade GitHub Actions to latest versions
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
2026-01-30 09:16:42 +00:00
Salman Muin Kayser Chishti
51e62f09ba Upgrade GitHub Actions for Node 24 compatibility
Signed-off-by: Salman Muin Kayser Chishti <13schishti@gmail.com>
2026-01-30 09:16:36 +00:00
Aayush Gupta
a7aad63bbb Upgrade Kotlin and KSP
Fixes multiple build errors. Once parcelize is fixed, we should be good
to use built-in Kotlin completely

Ref: https://issuetracker.google.com/issues/478401081

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:59:25 +08:00
Aayush Gupta
fd192b4f3f Drop default properties
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:53 +08:00
Aayush Gupta
19e94bd30c Migrate from deprecated srcDir to directories method
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:53 +08:00
Aayush Gupta
7758a27694 Migrate from deprecated android block to ApplicationExtension
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:53 +08:00
Aayush Gupta
a3301dcfb1 Enable resValues as build feature
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:53 +08:00
Aayush Gupta
d045b27cea Migrate to built-in Kotlin
Ref: https://developer.android.com/build/migrate-to-built-in-kotlin

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:51 +08:00
Aayush Gupta
4f70235ee8 Enable proguard android optimizations
AGP 9.0+ requires enabling optimizations

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:11 +08:00
Aayush Gupta
54f9bcb03e Upgrade AGP to 9.0.0
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
2026-01-29 14:00:11 +08:00
34 changed files with 510 additions and 469 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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
View 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>

View File

@@ -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>;
}

View File

@@ -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();

View File

@@ -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)

View File

@@ -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;
});

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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

View File

@@ -127,6 +127,7 @@ public class ReCaptchaActivity extends AppCompatActivity {
@Override
public void onBackPressed() {
super.onBackPressed();
saveCookiesAndFinish();
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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)
}
}
}

View File

@@ -185,7 +185,9 @@ class FeedLoadService : Service() {
}
}
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
if (notificationManager.areNotificationsEnabled()) {
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build())
}
}
// /////////////////////////////////////////////////////////////////////////

View File

@@ -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()
}

View File

@@ -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() {

View File

@@ -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) {

View File

@@ -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());
}
}
}

View File

@@ -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();
}

View File

@@ -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);

View File

@@ -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)
}
}

View File

@@ -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;

View File

@@ -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));

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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>

View File

@@ -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"

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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