Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
6ddd4a7
VideoDetailFragment: hide relatedItemsLayout in tablet mode after ful…
dustdfg Jan 11, 2026
8a2c47b
Remove dead code from info_list/InfoItemBuilder
dustdfg Jan 29, 2026
07fe1e7
Refactor settings/tabs/TabsJsonHelper.java to use java streams
dustdfg Jan 27, 2026
5e1a198
VideoDetailFragment: Forcefully catch click events uncaught by children
dustdfg Feb 6, 2026
d6be966
Replace Illegal{State,Argument} exceptions with more idiomatic kotlin…
dustdfg Jan 9, 2026
5d79342
Merge pull request #13028 from dustdfg/idiomatic_kotlin_exceptions
theimpulson Feb 8, 2026
020dbdc
Merge pull request #13131 from dustdfg/tabs_json_helper_refactor
theimpulson Feb 8, 2026
dab8e05
Merge pull request #13137 from dustdfg/info_item_builder_dead
theimpulson Feb 8, 2026
37cef82
Correct inverted check
dustdfg Feb 9, 2026
6214ae3
Merge pull request #13219 from dustdfg/kotlin_check_is_not_if
Stypox Feb 9, 2026
21f446a
Refactor settings/preferencesearch/PreferenceSearchItem#allRelevantSe…
dustdfg Feb 9, 2026
289d22e
Utilize kotlins ifEmpty
dustdfg Feb 9, 2026
451409f
SharedPreferences.edit applies changes automatically
dustdfg Feb 9, 2026
edfdbe8
Uitilize kotlin elvis operator
dustdfg Feb 9, 2026
13186c0
Merge pull request #13224 from dustdfg/kotlin_idiomatic
theimpulson Feb 10, 2026
780e6a4
Convert newpipe/util/PlayButtonHelper to kotlin
dustdfg Jan 9, 2026
4846766
Convert newpipe/util/PeertubeHelper to kotlin
dustdfg Jan 9, 2026
6cf932b
Convert newpipe/ExitActivity to kotlin
dustdfg Jan 9, 2026
d665a4f
Convert newpipe/util/NewPipeTextViewHelper to kotlin
dustdfg Jan 9, 2026
09a746d
Convert newpipe/util/ServiceHelper to kotlin
dustdfg Jan 9, 2026
2c7654a
Covert newpipe/util/DependentPreferenceHelper to kotlin
dustdfg Jan 9, 2026
1eedfd7
Convert /newpipe/util/StreamTypeUtil to kotlin
dustdfg Jan 9, 2026
c6fc94e
Convert newpipe/settings/export/PreferencesObjectInputStream to kotlin
dustdfg Jan 9, 2026
224a5d0
Minor improvements
theimpulson Feb 10, 2026
e6e0be7
Merge pull request #13026 from dustdfg/kotlin_merged2
theimpulson Feb 10, 2026
59e5018
player/helper/PlayerHelper#getTimeString replace ints with longs
dustdfg Jan 28, 2026
3a0a3a4
Misc loop/stream based player refactors
dustdfg Jan 28, 2026
869a3ce
ErrorActivity small refactor
dustdfg Feb 9, 2026
4a7eaed
ErrorActivity convert to kotlin
dustdfg Feb 10, 2026
d7a4435
ErrorActivity: Use better variable names and encapsulation
theimpulson Feb 11, 2026
8968aab
ErrorActivity: Catch exceptions not throwables
theimpulson Feb 11, 2026
c3dbed5
ErrorActivity: Kotlin-fy buildMarkdown method
theimpulson Feb 11, 2026
8d45b6b
Merge pull request #13225 from dustdfg/error_activity_kotlin
theimpulson Feb 11, 2026
54090ca
ErrorActivity use normal `\n` instead of `\\n` in resource strings
dustdfg Feb 11, 2026
840664a
Merge pull request #13236 from dustdfg/error_activity_newlines
Stypox Feb 12, 2026
6c69a54
Translated using Weblate (Slovak)
weblate Feb 11, 2026
3815f5f
Translated using Weblate (Bengali (India))
theimpulson Feb 12, 2026
70cdaf5
Partially revert: ErrorActivity: Kotlin-fy buildMarkdown method
dustdfg Feb 13, 2026
95367dd
Fix subtitle post-processing error losing original exception
pierreeurope Feb 16, 2026
7e7ad1e
fix layout of comment replies header to work when avatar image is gon…
aivelon Feb 13, 2026
239f6c9
Merge pull request #13241 from aivelon/comment-replies-without-images
TobiGr Feb 18, 2026
9f8055f
remove existing comment replies screens before adding new one
aivelon Feb 8, 2026
51c9ed7
Merge pull request #13213 from aivelon/handle-comment-replies-screens
TobiGr Feb 19, 2026
4cfd36c
Merge pull request #13203 from dustdfg/video_detail_fragment_click_th…
TobiGr Feb 19, 2026
01e77e2
Merge pull request #13256 from pierreeurope/fix/ttml-postprocessing-e…
TobiGr Feb 19, 2026
834f136
Merge pull request #13247 from dustdfg/crashlog_fix
TobiGr Feb 19, 2026
feb4424
Translated using Weblate (Dutch)
weblate Feb 19, 2026
5b00945
Address non-final resource IDs warnings
theimpulson Feb 4, 2026
8c016c9
Enable resources shrinking
theimpulson Feb 4, 2026
89cb87b
Introduce lint configuration and enable checks
theimpulson Feb 4, 2026
582f852
Add missing permission checks for notifications
theimpulson Feb 5, 2026
2f3a993
ReCaptchaActivity: Supress lint error for missing super call
theimpulson Feb 5, 2026
deb6b42
FinishedMIssionStore: Throw exception if column is missing
theimpulson Feb 5, 2026
b3f4cb8
Use requireContext() instead of asserting non-null context
theimpulson Feb 5, 2026
2182ff1
Use correct constant for hiding keyboard
theimpulson Feb 5, 2026
f64e40e
DownloadRunnableFallback: Fix error with log tag being too long
theimpulson Feb 5, 2026
3d43e52
activity_player_queue_control: Switch to app:tint instead of android:…
theimpulson Feb 5, 2026
10b943f
DownloadDialog: Avoid using restricted API for menuitem
theimpulson Feb 5, 2026
15829c8
lint: Supress more translation related errors
theimpulson Feb 5, 2026
5eb5f75
ic_smart_display: Fix invalid vector path on older devices
theimpulson Feb 5, 2026
653b33b
FocusOverlayView: Avoid accessing restricted API
theimpulson Feb 5, 2026
35b70c5
Merge pull request #13194 from TeamNewPipe/r8fixes
TobiGr Feb 20, 2026
273b09a
App: Migrate from Java to Kotlin
theimpulson Feb 20, 2026
4481dd7
Merge pull request #13135 from dustdfg/player_loop_refactor
absurdlylongusername Feb 21, 2026
5736410
Migrate from Picasso to Coil
theimpulson Feb 21, 2026
402a0aa
Delete unused preference title and summary
theimpulson Feb 21, 2026
08b7da4
Accomodate extractor changes (EnumSet for service media capabilities)
dustdfg Jan 30, 2026
63e1989
Update extractor to the latest version
TobiGr Feb 21, 2026
4bad882
Merge pull request #13037 from dustdfg/tablet_related_items
TobiGr Feb 21, 2026
52a9b60
Merge pull request #13148 from dustdfg/extractor_enum_set
TobiGr Feb 21, 2026
caebf84
Hide controls when resuming playback via double tap
iampopovich Jan 9, 2026
423f95a
Refactor double tap logic to use isPlaying() method for better readab…
iampopovich Jan 9, 2026
232d023
Merge pull request #13030 from iampopovich/fix/6815-double-tap-to-resume
TobiGr Feb 21, 2026
9f193a3
Merge pull request #13272 from theimpulson/coilport
theimpulson Feb 22, 2026
8902ce8
Update dependencies and Gradle to latest stable releases
theimpulson Feb 22, 2026
d1ad1e8
Merge pull request #13276 from TeamNewPipe/depupdate
TobiGr Feb 22, 2026
064e532
Merge branch 'dev' into refactor
theimpulson Feb 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 3 additions & 7 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ configure<ApplicationExtension> {
resValue("string", "app_name", "NewPipe $suffix")
}
isMinifyEnabled = true
isShrinkResources = false // disabled to fix F-Droid"s reproducible build
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
Expand All @@ -93,13 +93,9 @@ configure<ApplicationExtension> {
}

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:
lintConfig = file("lint.xml")
// Continue the debug 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"
}

compileOptions {
Expand Down
10 changes: 10 additions & 0 deletions app/lint.xml
Original file line number Diff line number Diff line change
@@ -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>
50 changes: 0 additions & 50 deletions app/src/main/java/org/schabi/newpipe/ExitActivity.java

This file was deleted.

36 changes: 36 additions & 0 deletions app/src/main/java/org/schabi/newpipe/ExitActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2016-2026 NewPipe contributors <https://newpipe.net>
* SPDX-License-Identifier: GPL-3.0-or-later
*/

package org.schabi.newpipe

import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import org.schabi.newpipe.util.NavigationHelper

class ExitActivity : Activity() {
@SuppressLint("NewApi")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
finishAndRemoveTask()
NavigationHelper.restartApp(this)
}

companion object {
@JvmStatic
fun exitAndRemoveFromRecentApps(activity: Activity) {
val intent = Intent(activity, ExitActivity::class.java)
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
or Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
or Intent.FLAG_ACTIVITY_CLEAR_TASK
or Intent.FLAG_ACTIVITY_NO_ANIMATION
)

activity.startActivity(intent)
}
}
}
34 changes: 15 additions & 19 deletions app/src/main/java/org/schabi/newpipe/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -305,25 +305,21 @@ private void addDrawerMenuForCurrentService() throws ExtractionException {
}

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();
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
86 changes: 43 additions & 43 deletions app/src/main/java/org/schabi/newpipe/QueueItemMenuUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,50 +41,50 @@ public static void openPopupMenu(final PlayQueue playQueue,
}

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;
});
Expand Down
6 changes: 2 additions & 4 deletions app/src/main/java/org/schabi/newpipe/RouterActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -343,8 +343,7 @@ protected void onSuccess() {
return;
}

final List<StreamingService.ServiceInfo.MediaCapability> capabilities =
currentService.getServiceInfo().getMediaCapabilities();
final var capabilities = currentService.getServiceInfo().getMediaCapabilities();

// Check if the service supports the choice
if ((isVideoPlayerSelected && capabilities.contains(VIDEO))
Expand Down Expand Up @@ -522,8 +521,7 @@ private List<AdapterChoiceItem> getChoicesForService(final StreamingService serv
final List<AdapterChoiceItem> returnedItems = new ArrayList<>();
returnedItems.add(showInfo); // Always present

final List<StreamingService.ServiceInfo.MediaCapability> capabilities =
service.getServiceInfo().getMediaCapabilities();
final var capabilities = service.getServiceInfo().getMediaCapabilities();

if (linkType == LinkType.STREAM || linkType == LinkType.PLAYLIST) {
if (capabilities.contains(VIDEO)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,7 @@ data class PlaylistRemoteEntity(
orderingName = playlistInfo.name,
url = playlistInfo.url,
thumbnailUrl = ImageStrategy.imageListToDbUrl(
if (playlistInfo.thumbnails.isEmpty()) {
playlistInfo.uploaderAvatars
} else {
playlistInfo.thumbnails
}
playlistInfo.thumbnails.ifEmpty { playlistInfo.uploaderAvatars }
),
uploader = playlistInfo.uploaderName,
streamCount = playlistInfo.streamCount
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ abstract class StreamDAO : BasicDAO<StreamEntity> {

private fun compareAndUpdateStream(newerStream: StreamEntity) {
val existentMinimalStream = getMinimalStreamForCompare(newerStream.serviceId, newerStream.url)
?: throw IllegalStateException("Stream cannot be null just after insertion.")
?: error("Stream cannot be null just after insertion.")
newerStream.uid = existentMinimalStream.uid

if (!StreamTypeUtil.isLiveStream(newerStream.streamType)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
entity.uid = uidFromInsert
} else {
val subscriptionIdFromDb = getSubscriptionIdInternal(entity.serviceId, entity.url!!)
?: throw IllegalStateException("Subscription cannot be null just after insertion.")
?: error("Subscription cannot be null just after insertion.")
entity.uid = subscriptionIdFromDb

update(entity)
Expand Down
Loading