1
0
Fork 0
mirror of https://github.com/TeamNewPipe/NewPipe.git synced 2025-10-03 01:39:38 +02:00

Merge branch 'refactor' into Playlist-Compose

# Conflicts:
#	app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java
#	app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java
This commit is contained in:
Isira Seneviratne 2025-09-07 09:50:21 +05:30
commit 0931d44cc1
100 changed files with 659 additions and 572 deletions

View file

@ -12,6 +12,7 @@ import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import java.util.Arrays;
import java.util.Objects;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@ -23,8 +24,23 @@ import static org.junit.Assert.assertTrue;
@LargeTest
public class ErrorInfoTest {
/**
* @param errorInfo the error info to access
* @return the private field errorInfo.message.stringRes using reflection
*/
private int getMessageFromErrorInfo(final ErrorInfo errorInfo)
throws NoSuchFieldException, IllegalAccessException {
final var message = ErrorInfo.class.getDeclaredField("message");
message.setAccessible(true);
final var messageValue = (ErrorInfo.Companion.ErrorMessage) message.get(errorInfo);
final var stringRes = ErrorInfo.Companion.ErrorMessage.class.getDeclaredField("stringRes");
stringRes.setAccessible(true);
return (int) Objects.requireNonNull(stringRes.get(messageValue));
}
@Test
public void errorInfoTestParcelable() {
public void errorInfoTestParcelable() throws NoSuchFieldException, IllegalAccessException {
final ErrorInfo info = new ErrorInfo(new ParsingException("Hello"),
UserAction.USER_REPORT, "request", ServiceList.YouTube.getServiceId());
// Obtain a Parcel object and write the parcelable object to it:
@ -39,7 +55,7 @@ public class ErrorInfoTest {
assertEquals(ServiceList.YouTube.getServiceInfo().getName(),
infoFromParcel.getServiceName());
assertEquals("request", infoFromParcel.getRequest());
assertEquals(R.string.parsing_error, infoFromParcel.getMessageStringId());
assertEquals(R.string.parsing_error, getMessageFromErrorInfo(infoFromParcel));
parcel.recycle();
}

View file

@ -435,6 +435,7 @@
</activity>
<service
android:name=".RouterActivity$FetcherService"
android:foregroundServiceType="dataSync"
android:exported="false" />
<!-- opting out of sending metrics to Google in Android System WebView -->

View file

@ -58,20 +58,10 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.StreamingService.LinkType;
import org.schabi.newpipe.extractor.channel.ChannelInfo;
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException;
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException;
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException;
import org.schabi.newpipe.extractor.exceptions.PaidContentException;
import org.schabi.newpipe.extractor.exceptions.PrivateContentException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException;
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException;
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler;
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.local.dialog.PlaylistDialog;
import org.schabi.newpipe.player.PlayerType;
import org.schabi.newpipe.player.helper.PlayerHelper;
@ -260,7 +250,8 @@ public class RouterActivity extends AppCompatActivity {
showUnsupportedUrlDialog(url);
}
}, throwable -> handleError(this, new ErrorInfo(throwable,
UserAction.SHARE_TO_NEWPIPE, "Getting service from url: " + url))));
UserAction.SHARE_TO_NEWPIPE, "Getting service from url: " + url,
null, url))));
}
/**
@ -269,40 +260,19 @@ public class RouterActivity extends AppCompatActivity {
* @param errorInfo the error information
*/
private static void handleError(final Context context, final ErrorInfo errorInfo) {
if (errorInfo.getThrowable() != null) {
errorInfo.getThrowable().printStackTrace();
}
if (errorInfo.getThrowable() instanceof ReCaptchaException) {
if (errorInfo.getRecaptchaUrl() != null) {
Toast.makeText(context, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
final Intent intent = new Intent(context, ReCaptchaActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, errorInfo.getRecaptchaUrl());
context.startActivity(intent);
} else if (errorInfo.getThrowable() != null
&& ExceptionUtils.isNetworkRelated(errorInfo.getThrowable())) {
Toast.makeText(context, R.string.network_error, Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof AgeRestrictedContentException) {
Toast.makeText(context, R.string.restricted_video_no_stream,
Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof GeographicRestrictionException) {
Toast.makeText(context, R.string.georestricted_content, Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof PaidContentException) {
Toast.makeText(context, R.string.paid_content, Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof PrivateContentException) {
Toast.makeText(context, R.string.private_content, Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof SoundCloudGoPlusContentException) {
Toast.makeText(context, R.string.soundcloud_go_plus_content,
Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof YoutubeMusicPremiumContentException) {
Toast.makeText(context, R.string.youtube_music_premium_content,
Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof ContentNotAvailableException) {
Toast.makeText(context, R.string.content_not_available, Toast.LENGTH_LONG).show();
} else if (errorInfo.getThrowable() instanceof ContentNotSupportedException) {
Toast.makeText(context, R.string.content_not_supported, Toast.LENGTH_LONG).show();
} else {
} else if (errorInfo.isReportable()) {
ErrorUtil.createNotification(context, errorInfo);
} else {
// this exception does not usually indicate a problem that should be reported,
// so just show a toast instead of the notification
Toast.makeText(context, errorInfo.getMessage(context), Toast.LENGTH_LONG).show();
}
if (context instanceof RouterActivity) {
@ -665,7 +635,8 @@ public class RouterActivity extends AppCompatActivity {
startActivity(intent);
finish();
}, throwable -> handleError(this, new ErrorInfo(throwable,
UserAction.SHARE_TO_NEWPIPE, "Starting info activity: " + currentUrl)))
UserAction.SHARE_TO_NEWPIPE, "Starting info activity: " + currentUrl,
null, currentUrl)))
);
return;
}
@ -852,10 +823,10 @@ public class RouterActivity extends AppCompatActivity {
})
)),
throwable -> runOnVisible(ctx -> handleError(ctx, new ErrorInfo(
throwable,
UserAction.REQUESTED_STREAM,
throwable, UserAction.REQUESTED_STREAM,
"Tried to add " + currentUrl + " to a playlist",
((RouterActivity) ctx).currentService.getServiceId())
((RouterActivity) ctx).currentService.getServiceId(),
currentUrl)
))
)
);
@ -995,7 +966,7 @@ public class RouterActivity extends AppCompatActivity {
}
}, throwable -> handleError(this, new ErrorInfo(throwable, finalUserAction,
choice.url + " opened with " + choice.playerChoice,
choice.serviceId)));
choice.serviceId, choice.url)));
}
}

View file

@ -389,8 +389,7 @@ public class DownloadDialog extends DialogFragment
}
}, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading video stream size",
currentInfo.getServiceId()))));
"Downloading video stream size", currentInfo))));
disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(getWrappedAudioStreams())
.subscribe(result -> {
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
@ -399,8 +398,7 @@ public class DownloadDialog extends DialogFragment
}
}, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading audio stream size",
currentInfo.getServiceId()))));
"Downloading audio stream size", currentInfo))));
disposables.add(StreamInfoWrapper.fetchMoreInfoForWrapper(wrappedSubtitleStreams)
.subscribe(result -> {
if (dialogBinding.videoAudioGroup.getCheckedRadioButtonId()
@ -409,8 +407,7 @@ public class DownloadDialog extends DialogFragment
}
}, throwable -> ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.DOWNLOAD_OPEN_DIALOG,
"Downloading subtitle stream size",
currentInfo.getServiceId()))));
"Downloading subtitle stream size", currentInfo))));
}
private void setupAudioTrackSpinner() {

View file

@ -36,8 +36,8 @@ public class AcraReportSender implements ReportSender {
ErrorUtil.openActivity(context, new ErrorInfo(
new String[]{report.getString(ReportField.STACK_TRACE)},
UserAction.UI_ERROR,
ErrorInfo.SERVICE_NONE,
"ACRA report",
null,
R.string.app_ui_crash));
}
}

View file

@ -115,7 +115,7 @@ public class ErrorActivity extends AppCompatActivity {
// normal bugreport
buildInfo(errorInfo);
activityErrorBinding.errorMessageView.setText(errorInfo.getMessageStringId());
activityErrorBinding.errorMessageView.setText(errorInfo.getMessage(this));
activityErrorBinding.errorView.setText(formErrorText(errorInfo.getStackTraces()));
// print stack trace once again for debugging:

View file

@ -1,115 +1,304 @@
package org.schabi.newpipe.error
import android.content.Context
import android.os.Parcelable
import androidx.annotation.StringRes
import androidx.core.content.ContextCompat
import com.google.android.exoplayer2.ExoPlaybackException
import kotlinx.parcelize.IgnoredOnParcel
import com.google.android.exoplayer2.upstream.HttpDataSource
import com.google.android.exoplayer2.upstream.Loader
import kotlinx.parcelize.Parcelize
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.Info
import org.schabi.newpipe.extractor.ServiceList
import org.schabi.newpipe.extractor.ServiceList.YouTube
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException
import org.schabi.newpipe.extractor.exceptions.ExtractionException
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException
import org.schabi.newpipe.extractor.exceptions.PaidContentException
import org.schabi.newpipe.extractor.exceptions.PrivateContentException
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.extractor.exceptions.SignInConfirmNotBotException
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException
import org.schabi.newpipe.extractor.exceptions.UnsupportedContentInCountryException
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException
import org.schabi.newpipe.ktx.isNetworkRelated
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.player.mediasource.FailedMediaSource
import org.schabi.newpipe.player.resolver.PlaybackResolver
import java.net.UnknownHostException
/**
* An error has occurred in the app. This class contains plain old parcelable data that can be used
* to report the error and to show it to the user along with correct action buttons.
*/
@Parcelize
class ErrorInfo(
class ErrorInfo private constructor(
val stackTraces: Array<String>,
val userAction: UserAction,
val serviceName: String,
val request: String,
val messageStringId: Int
val serviceId: Int?,
private val message: ErrorMessage,
/**
* If `true`, a report button will be shown for this error. Otherwise the error is not something
* that can really be reported (e.g. a network issue, or content not being available at all).
*/
val isReportable: Boolean,
/**
* If `true`, the process causing this error can be retried, otherwise not.
*/
val isRetryable: Boolean,
/**
* If present, indicates that the exception was a ReCaptchaException, and this is the URL
* provided by the service that can be used to solve the ReCaptcha challenge.
*/
val recaptchaUrl: String?,
/**
* If present, this resource can alternatively be opened in browser (useful if NewPipe is
* badly broken).
*/
val openInBrowserUrl: String?,
) : Parcelable {
// no need to store throwable, all data for report is in other variables
// also, the throwable might not be serializable, see TeamNewPipe/NewPipe#7302
@IgnoredOnParcel
var throwable: Throwable? = null
private constructor(
@JvmOverloads
constructor(
throwable: Throwable,
userAction: UserAction,
serviceName: String,
request: String
request: String,
serviceId: Int? = null,
openInBrowserUrl: String? = null,
) : this(
throwableToStringList(throwable),
userAction,
serviceName,
request,
getMessageStringId(throwable, userAction)
) {
this.throwable = throwable
}
serviceId,
getMessage(throwable, userAction, serviceId),
isReportable(throwable),
isRetryable(throwable),
(throwable as? ReCaptchaException)?.url,
openInBrowserUrl,
)
private constructor(
throwable: List<Throwable>,
@JvmOverloads
constructor(
throwables: List<Throwable>,
userAction: UserAction,
serviceName: String,
request: String
request: String,
serviceId: Int? = null,
openInBrowserUrl: String? = null,
) : this(
throwableListToStringList(throwable),
throwableListToStringList(throwables),
userAction,
serviceName,
request,
getMessageStringId(throwable.firstOrNull(), userAction)
) {
this.throwable = throwable.firstOrNull()
serviceId,
getMessage(throwables.firstOrNull(), userAction, serviceId),
throwables.any(::isReportable),
throwables.isEmpty() || throwables.any(::isRetryable),
throwables.firstNotNullOfOrNull { it as? ReCaptchaException }?.url,
openInBrowserUrl,
)
// constructor to manually build ErrorInfo when no throwable is available
constructor(
stackTraces: Array<String>,
userAction: UserAction,
request: String,
serviceId: Int?,
@StringRes message: Int
) :
this(
stackTraces, userAction, request, serviceId, ErrorMessage(message),
true, false, null, null
)
// constructor with only one throwable to extract service id and openInBrowserUrl from an Info
constructor(
throwable: Throwable,
userAction: UserAction,
request: String,
info: Info?,
) :
this(throwable, userAction, request, info?.serviceId, info?.url)
// constructor with multiple throwables to extract service id and openInBrowserUrl from an Info
constructor(
throwables: List<Throwable>,
userAction: UserAction,
request: String,
info: Info?,
) :
this(throwables, userAction, request, info?.serviceId, info?.url)
fun getServiceName(): String {
return getServiceName(serviceId)
}
// constructors with single throwable
constructor(throwable: Throwable, userAction: UserAction, request: String) :
this(throwable, userAction, SERVICE_NONE, request)
constructor(throwable: Throwable, userAction: UserAction, request: String, serviceId: Int) :
this(throwable, userAction, ServiceHelper.getNameOfServiceById(serviceId), request)
constructor(throwable: Throwable, userAction: UserAction, request: String, info: Info?) :
this(throwable, userAction, getInfoServiceName(info), request)
// constructors with list of throwables
constructor(throwable: List<Throwable>, userAction: UserAction, request: String) :
this(throwable, userAction, SERVICE_NONE, request)
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, serviceId: Int) :
this(throwable, userAction, ServiceHelper.getNameOfServiceById(serviceId), request)
constructor(throwable: List<Throwable>, userAction: UserAction, request: String, info: Info?) :
this(throwable, userAction, getInfoServiceName(info), request)
fun getMessage(context: Context): String {
return message.getString(context)
}
companion object {
const val SERVICE_NONE = "none"
@Parcelize
class ErrorMessage(
@StringRes
private val stringRes: Int,
private vararg val formatArgs: String,
) : Parcelable {
fun getString(context: Context): String {
return if (formatArgs.isEmpty()) {
// use ContextCompat.getString() just in case context is not AppCompatActivity
ContextCompat.getString(context, stringRes)
} else {
// ContextCompat.getString() with formatArgs does not exist, so we just
// replicate its source code but with formatArgs
ContextCompat.getContextForLanguage(context).getString(stringRes, *formatArgs)
}
}
}
const val SERVICE_NONE = "<unknown_service>"
private fun getServiceName(serviceId: Int?) =
// not using getNameOfServiceById since we want to accept a nullable serviceId and we
// want to default to SERVICE_NONE
ServiceList.all()?.firstOrNull { it.serviceId == serviceId }?.serviceInfo?.name
?: SERVICE_NONE
fun throwableToStringList(throwable: Throwable) = arrayOf(throwable.stackTraceToString())
fun throwableListToStringList(throwableList: List<Throwable>) =
throwableList.map { it.stackTraceToString() }.toTypedArray()
private fun getInfoServiceName(info: Info?) =
if (info == null) SERVICE_NONE else ServiceHelper.getNameOfServiceById(info.serviceId)
@StringRes
private fun getMessageStringId(
fun getMessage(
throwable: Throwable?,
action: UserAction
): Int {
action: UserAction?,
serviceId: Int?,
): ErrorMessage {
return when {
throwable is AccountTerminatedException -> R.string.account_terminated
throwable is ContentNotAvailableException -> R.string.content_not_available
throwable != null && throwable.isNetworkRelated -> R.string.network_error
throwable is ContentNotSupportedException -> R.string.content_not_supported
throwable is ExtractionException -> R.string.parsing_error
// player exceptions
// some may be IOException, so do these checks before isNetworkRelated!
throwable is ExoPlaybackException -> {
when (throwable.type) {
ExoPlaybackException.TYPE_SOURCE -> R.string.player_stream_failure
ExoPlaybackException.TYPE_UNEXPECTED -> R.string.player_recoverable_failure
else -> R.string.player_unrecoverable_failure
val cause = throwable.cause
when {
cause is HttpDataSource.InvalidResponseCodeException -> {
if (cause.responseCode == 403) {
if (serviceId == YouTube.serviceId) {
ErrorMessage(R.string.youtube_player_http_403)
} else {
ErrorMessage(R.string.player_http_403)
}
} else {
ErrorMessage(R.string.player_http_invalid_status, cause.responseCode.toString())
}
}
cause is Loader.UnexpectedLoaderException && cause.cause is ExtractionException ->
getMessage(throwable, action, serviceId)
throwable.type == ExoPlaybackException.TYPE_SOURCE ->
ErrorMessage(R.string.player_stream_failure)
throwable.type == ExoPlaybackException.TYPE_UNEXPECTED ->
ErrorMessage(R.string.player_recoverable_failure)
else ->
ErrorMessage(R.string.player_unrecoverable_failure)
}
}
action == UserAction.UI_ERROR -> R.string.app_ui_crash
action == UserAction.REQUESTED_COMMENTS -> R.string.error_unable_to_load_comments
action == UserAction.SUBSCRIPTION_CHANGE -> R.string.subscription_change_failed
action == UserAction.SUBSCRIPTION_UPDATE -> R.string.subscription_update_failed
action == UserAction.LOAD_IMAGE -> R.string.could_not_load_thumbnails
action == UserAction.DOWNLOAD_OPEN_DIALOG -> R.string.could_not_setup_download_menu
else -> R.string.general_error
throwable is FailedMediaSource.FailedMediaSourceException ->
getMessage(throwable.cause, action, serviceId)
throwable is PlaybackResolver.ResolverException ->
ErrorMessage(R.string.player_stream_failure)
// content not available exceptions
throwable is AccountTerminatedException ->
throwable.message
?.takeIf { reason -> !reason.isEmpty() }
?.let { reason ->
ErrorMessage(
R.string.account_terminated_service_provides_reason,
getServiceName(serviceId),
reason
)
}
?: ErrorMessage(R.string.account_terminated)
throwable is AgeRestrictedContentException ->
ErrorMessage(R.string.restricted_video_no_stream)
throwable is GeographicRestrictionException ->
ErrorMessage(R.string.georestricted_content)
throwable is PaidContentException ->
ErrorMessage(R.string.paid_content)
throwable is PrivateContentException ->
ErrorMessage(R.string.private_content)
throwable is SoundCloudGoPlusContentException ->
ErrorMessage(R.string.soundcloud_go_plus_content)
throwable is UnsupportedContentInCountryException ->
ErrorMessage(R.string.unsupported_content_in_country)
throwable is YoutubeMusicPremiumContentException ->
ErrorMessage(R.string.youtube_music_premium_content)
throwable is SignInConfirmNotBotException ->
ErrorMessage(R.string.sign_in_confirm_not_bot_error, getServiceName(serviceId))
throwable is ContentNotAvailableException ->
ErrorMessage(R.string.content_not_available)
// other extractor exceptions
throwable is ContentNotSupportedException ->
ErrorMessage(R.string.content_not_supported)
// ReCaptchas will be handled in a special way anyway
throwable is ReCaptchaException ->
ErrorMessage(R.string.recaptcha_request_toast)
// test this at the end as many exceptions could be a subclass of IOException
throwable != null && throwable.isNetworkRelated ->
ErrorMessage(R.string.network_error)
// an extraction exception unrelated to the network
// is likely an issue with parsing the website
throwable is ExtractionException ->
ErrorMessage(R.string.parsing_error)
// user actions (in case the exception is null or unrecognizable)
action == UserAction.UI_ERROR ->
ErrorMessage(R.string.app_ui_crash)
action == UserAction.REQUESTED_COMMENTS ->
ErrorMessage(R.string.error_unable_to_load_comments)
action == UserAction.SUBSCRIPTION_CHANGE ->
ErrorMessage(R.string.subscription_change_failed)
action == UserAction.SUBSCRIPTION_UPDATE ->
ErrorMessage(R.string.subscription_update_failed)
action == UserAction.LOAD_IMAGE ->
ErrorMessage(R.string.could_not_load_thumbnails)
action == UserAction.DOWNLOAD_OPEN_DIALOG ->
ErrorMessage(R.string.could_not_setup_download_menu)
else ->
ErrorMessage(R.string.error_snackbar_message)
}
}
fun isReportable(throwable: Throwable?): Boolean {
return when (throwable) {
// we don't have an exception, so this is a manually built error, which likely
// indicates that it's important and is thus reportable
null -> true
// the service explicitly said that content is not available (e.g. age restrictions,
// video deleted, etc.), there is no use in letting users report it
is ContentNotAvailableException -> false
// we know the content is not supported, no need to let the user report it
is ContentNotSupportedException -> false
// happens often when there is no internet connection; we don't use
// `throwable.isNetworkRelated` since any `IOException` would make that function
// return true, but not all `IOException`s are network related
is UnknownHostException -> false
// by default, this is an unexpected exception, which the user could report
else -> true
}
}
fun isRetryable(throwable: Throwable?): Boolean {
return when (throwable) {
// we know the content is not available, retrying won't help
is ContentNotAvailableException -> false
// we know the content is not supported, retrying won't help
is ContentNotSupportedException -> false
// by default (including if throwable is null), enable retrying (though the retry
// button will be shown only if a way to perform the retry is implemented)
else -> true
}
}
}

View file

@ -2,7 +2,6 @@ package org.schabi.newpipe.error
import android.content.Context
import android.content.Intent
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
@ -14,21 +13,7 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.disposables.Disposable
import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.R
import org.schabi.newpipe.extractor.exceptions.AccountTerminatedException
import org.schabi.newpipe.extractor.exceptions.AgeRestrictedContentException
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.exceptions.ContentNotSupportedException
import org.schabi.newpipe.extractor.exceptions.GeographicRestrictionException
import org.schabi.newpipe.extractor.exceptions.PaidContentException
import org.schabi.newpipe.extractor.exceptions.PrivateContentException
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
import org.schabi.newpipe.extractor.exceptions.SoundCloudGoPlusContentException
import org.schabi.newpipe.extractor.exceptions.YoutubeMusicPremiumContentException
import org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty
import org.schabi.newpipe.ktx.animate
import org.schabi.newpipe.ktx.isInterruptedCaused
import org.schabi.newpipe.ktx.isNetworkRelated
import org.schabi.newpipe.util.ServiceHelper
import org.schabi.newpipe.util.external_communication.ShareUtils
import java.util.concurrent.TimeUnit
@ -78,64 +63,32 @@ class ErrorPanelHelper(
}
fun showError(errorInfo: ErrorInfo) {
if (errorInfo.throwable != null && errorInfo.throwable!!.isInterruptedCaused) {
if (DEBUG) {
Log.w(TAG, "onError() isInterruptedCaused! = [$errorInfo.throwable]")
}
return
}
ensureDefaultVisibility()
errorTextView.text = errorInfo.getMessage(context)
if (errorInfo.throwable is ReCaptchaException) {
errorTextView.setText(R.string.recaptcha_request_toast)
showAndSetErrorButtonAction(
R.string.recaptcha_solve
) {
if (errorInfo.recaptchaUrl != null) {
showAndSetErrorButtonAction(R.string.recaptcha_solve) {
// Starting ReCaptcha Challenge Activity
val intent = Intent(context, ReCaptchaActivity::class.java)
intent.putExtra(
ReCaptchaActivity.RECAPTCHA_URL_EXTRA,
(errorInfo.throwable as ReCaptchaException).url
)
intent.putExtra(ReCaptchaActivity.RECAPTCHA_URL_EXTRA, errorInfo.recaptchaUrl)
fragment.startActivityForResult(intent, ReCaptchaActivity.RECAPTCHA_REQUEST)
errorActionButton.setOnClickListener(null)
}
errorRetryButton.isVisible = retryShouldBeShown
showAndSetOpenInBrowserButtonAction(errorInfo)
} else if (errorInfo.throwable is AccountTerminatedException) {
errorTextView.setText(R.string.account_terminated)
if (!isNullOrEmpty((errorInfo.throwable as AccountTerminatedException).message)) {
errorServiceInfoTextView.text = context.resources.getString(
R.string.service_provides_reason,
ServiceHelper.getSelectedService(context)?.serviceInfo?.name ?: "<unknown>"
)
errorServiceInfoTextView.isVisible = true
errorServiceExplanationTextView.text =
(errorInfo.throwable as AccountTerminatedException).message
errorServiceExplanationTextView.isVisible = true
}
} else {
showAndSetErrorButtonAction(
R.string.error_snackbar_action
) {
} else if (errorInfo.isReportable) {
showAndSetErrorButtonAction(R.string.error_snackbar_action) {
ErrorUtil.openActivity(context, errorInfo)
}
}
errorTextView.setText(getExceptionDescription(errorInfo.throwable))
if (errorInfo.isRetryable) {
errorRetryButton.isVisible = retryShouldBeShown
}
if (errorInfo.throwable !is ContentNotAvailableException &&
errorInfo.throwable !is ContentNotSupportedException
) {
// show retry button only for content which is not unavailable or unsupported
errorRetryButton.isVisible = retryShouldBeShown
if (errorInfo.openInBrowserUrl != null) {
errorOpenInBrowserButton.isVisible = true
errorOpenInBrowserButton.setOnClickListener {
ShareUtils.openUrlInBrowser(context, errorInfo.openInBrowserUrl)
}
showAndSetOpenInBrowserButtonAction(errorInfo)
}
setRootVisible()
@ -153,15 +106,6 @@ class ErrorPanelHelper(
errorActionButton.setOnClickListener(listener)
}
fun showAndSetOpenInBrowserButtonAction(
errorInfo: ErrorInfo
) {
errorOpenInBrowserButton.isVisible = true
errorOpenInBrowserButton.setOnClickListener {
ShareUtils.openUrlInBrowser(context, errorInfo.request)
}
}
fun showTextError(errorString: String) {
ensureDefaultVisibility()
@ -192,27 +136,5 @@ class ErrorPanelHelper(
companion object {
val TAG: String = ErrorPanelHelper::class.simpleName!!
val DEBUG: Boolean = MainActivity.DEBUG
@StringRes
fun getExceptionDescription(throwable: Throwable?): Int {
return when (throwable) {
is AgeRestrictedContentException -> R.string.restricted_video_no_stream
is GeographicRestrictionException -> R.string.georestricted_content
is PaidContentException -> R.string.paid_content
is PrivateContentException -> R.string.private_content
is SoundCloudGoPlusContentException -> R.string.soundcloud_go_plus_content
is YoutubeMusicPremiumContentException -> R.string.youtube_music_premium_content
is ContentNotAvailableException -> R.string.content_not_available
is ContentNotSupportedException -> R.string.content_not_supported
else -> {
// show retry button only for content which is not unavailable or unsupported
if (throwable != null && throwable.isNetworkRelated) {
R.string.network_error
} else {
R.string.error_snackbar_message
}
}
}
}
}
}

View file

@ -122,7 +122,7 @@ class ErrorUtil {
)
.setSmallIcon(R.drawable.ic_bug_report)
.setContentTitle(context.getString(R.string.error_report_notification_title))
.setContentText(context.getString(errorInfo.messageStringId))
.setContentText(errorInfo.getMessage(context))
.setAutoCancel(true)
.setContentIntent(
PendingIntentCompat.getActivity(
@ -156,10 +156,10 @@ class ErrorUtil {
// fallback to showing a notification if no root view is available
createNotification(context, errorInfo)
} else {
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
Snackbar.make(rootView, errorInfo.getMessage(context), Snackbar.LENGTH_LONG)
.setActionTextColor(Color.YELLOW)
.setAction(context.getString(R.string.error_snackbar_action).uppercase()) {
openActivity(context, errorInfo)
context.startActivity(getErrorActivityIntent(context, errorInfo))
}.show()
}
}

View file

@ -33,7 +33,9 @@ public enum UserAction {
SHARE_TO_NEWPIPE("share to newpipe"),
CHECK_FOR_NEW_APP_VERSION("check for new app version"),
OPEN_INFO_ITEM_DIALOG("open info item dialog"),
GETTING_MAIN_SCREEN_TAB("getting main screen tab");
GETTING_MAIN_SCREEN_TAB("getting main screen tab"),
PLAY_ON_POPUP("play on popup"),
SUBSCRIPTIONS("loading subscriptions");
private final String message;

View file

@ -771,7 +771,7 @@ class VideoDetailFragment :
},
{ throwable ->
showError(
ErrorInfo(throwable, UserAction.REQUESTED_STREAM, url ?: "no url", serviceId)
ErrorInfo(throwable, UserAction.REQUESTED_STREAM, url ?: "no url", serviceId, url)
)
}
)
@ -1460,7 +1460,7 @@ class VideoDetailFragment :
if (!info.errors.isEmpty()) {
showSnackBarError(
ErrorInfo(info.errors, UserAction.REQUESTED_STREAM, info.url, info)
ErrorInfo(info.errors, UserAction.REQUESTED_STREAM, "Some info not extracted: " + info.url, info)
)
}
}

View file

@ -153,7 +153,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
handleResult(result);
}, throwable ->
showError(new ErrorInfo(throwable, errorUserAction,
"Start loading: " + url, serviceId)));
"Start loading: " + url, serviceId, url)));
}
/**
@ -184,7 +184,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
handleNextItems(infoItemsPage);
}, (@NonNull Throwable throwable) ->
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(throwable,
errorUserAction, "Loading more items: " + url, serviceId)));
errorUserAction, "Loading more items: " + url, serviceId, url)));
}
private void forbidDownwardFocusScroll() {
@ -210,7 +210,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
if (!result.getErrors().isEmpty()) {
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(), errorUserAction,
"Get next items of: " + url, serviceId));
"Get next items of: " + url, serviceId, url));
}
}
@ -250,7 +250,7 @@ public abstract class BaseListInfoFragment<I extends InfoItem, L extends ListInf
if (!errors.isEmpty()) {
dynamicallyShowErrorPanelOrSnackbar(new ErrorInfo(result.getErrors(),
errorUserAction, "Start loading: " + url, serviceId));
errorUserAction, "Start loading: " + url, serviceId, url));
}
}
}

View file

@ -3,6 +3,7 @@ package org.schabi.newpipe.fragments.list.channel;
import static org.schabi.newpipe.ktx.TextViewUtils.animateTextColor;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ktx.ViewUtils.animateBackgroundColor;
import static org.schabi.newpipe.ui.emptystate.EmptyStateUtil.setEmptyStateComposable;
import android.content.Context;
import android.content.SharedPreferences;
@ -45,7 +46,6 @@ import org.schabi.newpipe.ktx.AnimationType;
import org.schabi.newpipe.local.feed.notifications.NotificationHelper;
import org.schabi.newpipe.local.subscription.SubscriptionManager;
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec;
import org.schabi.newpipe.ui.emptystate.EmptyStateUtil;
import org.schabi.newpipe.util.ChannelTabHelper;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ExtractorHelper;
@ -194,10 +194,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
};
activity.addMenuProvider(menuProvider, getViewLifecycleOwner());
EmptyStateUtil.setEmptyStateComposable(
binding.emptyStateView,
EmptyStateSpec.Companion.getContentNotSupported()
);
setEmptyStateComposable(binding.emptyStateView, EmptyStateSpec.ContentNotSupported);
tabAdapter = new TabAdapter(getChildFragmentManager());
binding.viewPager.setAdapter(tabAdapter);
@ -568,7 +565,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
isLoading.set(false);
handleResult(result);
}, throwable -> showError(new ErrorInfo(throwable, UserAction.REQUESTED_CHANNEL,
url == null ? "No URL" : url, serviceId)));
url == null ? "No URL" : url, serviceId, url)));
}
@Override

View file

@ -3,6 +3,7 @@ package org.schabi.newpipe.fragments.list.search;
import static androidx.recyclerview.widget.ItemTouchHelper.Callback.makeMovementFlags;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.ktx.ViewUtils.animate;
import static org.schabi.newpipe.ui.emptystate.EmptyStateUtil.setEmptyStateComposable;
import static org.schabi.newpipe.util.ExtractorHelper.showMetaInfoInTextView;
import static java.util.Arrays.asList;
@ -54,6 +55,7 @@ import org.schabi.newpipe.extractor.MetaInfo;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.Page;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ParsingException;
import org.schabi.newpipe.extractor.search.SearchExtractor;
import org.schabi.newpipe.extractor.search.SearchInfo;
import org.schabi.newpipe.extractor.services.peertube.linkHandler.PeertubeSearchQueryHandlerFactory;
@ -65,7 +67,6 @@ import org.schabi.newpipe.ktx.ExceptionUtils;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec;
import org.schabi.newpipe.ui.emptystate.EmptyStateUtil;
import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ExtractorHelper;
@ -356,9 +357,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
protected void initViews(final View rootView, final Bundle savedInstanceState) {
super.initViews(rootView, savedInstanceState);
EmptyStateUtil.setEmptyStateComposable(
searchBinding.emptyStateView,
EmptyStateSpec.Companion.getNoSearchResult());
setEmptyStateComposable(searchBinding.emptyStateView, EmptyStateSpec.NoSearchResult);
searchBinding.suggestionsList.setAdapter(suggestionListAdapter);
// animations are just strange and useless, since the suggestions keep changing too much
@ -940,7 +939,21 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
infoListAdapter.clearStreamItemList();
showEmptyState();
} else {
showError(new ErrorInfo(exception, UserAction.SEARCHED, searchString, serviceId));
showError(new ErrorInfo(exception, UserAction.SEARCHED, searchString, serviceId,
getOpenInBrowserUrlForErrors()));
}
}
@Nullable
private String getOpenInBrowserUrlForErrors() {
if (TextUtils.isEmpty(searchString)) {
return null;
}
try {
return service.getSearchQHFactory().getUrl(searchString,
Arrays.asList(contentFilter), sortFilter);
} catch (final NullPointerException | ParsingException ignored) {
return null;
}
}
@ -1028,7 +1041,7 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
&& !(exceptions.size() == 1
&& exceptions.get(0) instanceof SearchExtractor.NothingFoundException)) {
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
searchString, serviceId));
searchString, serviceId, getOpenInBrowserUrlForErrors()));
}
searchSuggestion = result.getSearchSuggestion();
@ -1101,13 +1114,14 @@ public class SearchFragment extends BaseListFragment<SearchInfo, ListExtractor.I
// whose results are handled here, but let's check it anyway
if (nextPage == null) {
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
"\"" + searchString + "\" → nextPage == null", serviceId));
"\"" + searchString + "\" → nextPage == null", serviceId,
getOpenInBrowserUrlForErrors()));
} else {
showSnackBarError(new ErrorInfo(result.getErrors(), UserAction.SEARCHED,
"\"" + searchString + "\" → pageUrl: " + nextPage.getUrl() + ", "
+ "pageIds: " + nextPage.getIds() + ", "
+ "pageCookies: " + nextPage.getCookies(),
serviceId));
serviceId, getOpenInBrowserUrlForErrors()));
}
}

View file

@ -15,6 +15,7 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.compose.ui.platform.ComposeView;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.RecyclerView;
@ -124,10 +125,8 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
super.initViews(rootView, savedInstanceState);
itemListAdapter.setUseItemHandle(true);
EmptyStateUtil.setEmptyStateComposable(
rootView.findViewById(R.id.empty_state_view),
EmptyStateSpec.Companion.getNoBookmarkedPlaylist()
);
final ComposeView emptyView = rootView.findViewById(R.id.empty_state_view);
EmptyStateUtil.setEmptyStateComposable(emptyView, EmptyStateSpec.NoBookmarkedPlaylist);
}
@Override

View file

@ -1,6 +1,8 @@
package org.schabi.newpipe.local.feed.notifications
import android.content.Context
import android.content.pm.ServiceInfo
import android.os.Build
import android.util.Log
import androidx.core.app.NotificationCompat
import androidx.work.Constraints
@ -83,7 +85,9 @@ class NotificationWorker(
.setPriority(NotificationCompat.PRIORITY_LOW)
.setContentTitle(applicationContext.getString(R.string.feed_notification_loading))
.build()
setForegroundAsync(ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification))
// ServiceInfo constants are not used below Android Q, so 0 is set here
val serviceType = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC else 0
setForegroundAsync(ForegroundInfo(FeedLoadService.NOTIFICATION_ID, notification, serviceType))
}
companion object {

View file

@ -85,8 +85,8 @@ public class SubscriptionsImportFragment extends BaseFragment {
if (supportedSources.isEmpty() && currentServiceId != Constants.NO_SERVICE_ID) {
ErrorUtil.showSnackbar(activity,
new ErrorInfo(new String[]{}, UserAction.SUBSCRIPTION_IMPORT_EXPORT,
ServiceHelper.getNameOfServiceById(currentServiceId),
"Service does not support importing subscriptions",
currentServiceId,
R.string.general_error));
activity.finish();
}

View file

@ -1283,7 +1283,8 @@ public final class Player implements PlaybackListener, Listener {
UserAction.PLAY_STREAM,
"Loading failed for [" + currentMetadata.getTitle()
+ "]: " + currentMetadata.getStreamUrl(),
currentMetadata.getServiceId());
currentMetadata.getServiceId(),
currentMetadata.getStreamUrl());
ErrorUtil.createNotification(context, errorInfo);
}
@ -1499,7 +1500,7 @@ public final class Player implements PlaybackListener, Listener {
errorInfo = new ErrorInfo(error, UserAction.PLAY_STREAM,
"Player error[type=" + error.getErrorCodeName()
+ "] occurred while playing " + currentMetadata.getStreamUrl(),
currentMetadata.getServiceId());
currentMetadata.getServiceId(), currentMetadata.getStreamUrl());
}
ErrorUtil.createNotification(context, errorInfo);
}

View file

@ -17,6 +17,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.MainActivity
import org.schabi.newpipe.NewPipeDatabase
import org.schabi.newpipe.R
import org.schabi.newpipe.error.ErrorInfo
import org.schabi.newpipe.extractor.InfoItem.InfoType
import org.schabi.newpipe.extractor.exceptions.ContentNotAvailableException
import org.schabi.newpipe.extractor.linkhandler.ListLinkHandler
@ -84,7 +85,7 @@ class MediaBrowserPlaybackPreparer(
},
{ throwable ->
Log.e(TAG, "Failed to start playback of media ID [$mediaId]", throwable)
onPrepareError()
onPrepareError(throwable)
}
)
}
@ -115,9 +116,9 @@ class MediaBrowserPlaybackPreparer(
)
}
private fun onPrepareError() {
private fun onPrepareError(throwable: Throwable) {
setMediaSessionError.accept(
ContextCompat.getString(context, R.string.error_snackbar_message),
ErrorInfo.getMessage(throwable, null, null).getString(context),
PlaybackStateCompat.ERROR_CODE_APP_ERROR
)
}

View file

@ -167,19 +167,17 @@ public final class NotificationUtil {
&& notificationBuilder.mActions.get(2).actionIntent != null);
}
public void createNotificationAndStartForeground() {
if (notificationBuilder == null) {
notificationBuilder = createNotification();
}
updateNotification();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
player.getService().startForeground(NOTIFICATION_ID, notificationBuilder.build(),
ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK);
} else {
player.getService().startForeground(NOTIFICATION_ID, notificationBuilder.build());
}
// ServiceInfo constants are not used below Android Q, so 0 is set here
final int serviceType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
? ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK : 0;
ServiceCompat.startForeground(player.getService(), NOTIFICATION_ID,
notificationBuilder.build(), serviceType);
}
public void cancelNotificationAndStopForeground() {

View file

@ -95,8 +95,7 @@ public class SelectChannelFragment extends DialogFragment {
progressBar = v.findViewById(R.id.progressBar);
emptyView = v.findViewById(R.id.empty_state_view);
EmptyStateUtil.setEmptyStateComposable(emptyView,
EmptyStateSpec.Companion.getNoSubscriptions());
EmptyStateUtil.setEmptyStateComposable(emptyView, EmptyStateSpec.NoSubscriptions);
progressBar.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
emptyView.setVisibility(View.GONE);

View file

@ -65,8 +65,7 @@ public class SelectPlaylistFragment extends DialogFragment {
recyclerView = v.findViewById(R.id.items_list);
emptyView = v.findViewById(R.id.empty_state_view);
EmptyStateUtil.setEmptyStateComposable(emptyView,
EmptyStateSpec.Companion.getNoBookmarkedPlaylist());
EmptyStateUtil.setEmptyStateComposable(emptyView, EmptyStateSpec.NoBookmarkedPlaylist);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
final SelectPlaylistAdapter playlistAdapter = new SelectPlaylistAdapter();
recyclerView.setAdapter(playlistAdapter);

View file

@ -1,5 +1,7 @@
package org.schabi.newpipe.settings.preferencesearch;
import static org.schabi.newpipe.ui.emptystate.EmptyStateUtil.setEmptyStateComposable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@ -12,7 +14,6 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import org.schabi.newpipe.databinding.SettingsPreferencesearchFragmentBinding;
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec;
import org.schabi.newpipe.ui.emptystate.EmptyStateUtil;
import java.util.List;
@ -41,9 +42,7 @@ public class PreferenceSearchFragment extends Fragment {
binding = SettingsPreferencesearchFragmentBinding.inflate(inflater, container, false);
binding.searchResults.setLayoutManager(new LinearLayoutManager(getContext()));
EmptyStateUtil.setEmptyStateComposable(
binding.emptyStateView,
EmptyStateSpec.Companion.getNoSearchMaxSizeResult());
setEmptyStateComposable(binding.emptyStateView, EmptyStateSpec.NoSearchResult);
adapter = new PreferenceSearchAdapter();
adapter.setOnItemClickListener(this::onItemClicked);

View file

@ -1,12 +1,14 @@
package org.schabi.newpipe.ui.components.about
import androidx.annotation.StringRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
@ -16,7 +18,6 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.NonRestartableComposable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -26,13 +27,12 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat.getDrawable
import coil3.compose.AsyncImage
import my.nanihadesuka.compose.ColumnScrollbar
import org.schabi.newpipe.BuildConfig
import org.schabi.newpipe.R
import org.schabi.newpipe.ui.components.common.defaultThemedScrollbarSettings
import org.schabi.newpipe.util.external_communication.ShareUtils
import org.schabi.newpipe.util.image.NewPipeSquircleIcon
private val ABOUT_ITEMS = listOf(
AboutData(R.string.faq_title, R.string.faq_description, R.string.faq, R.string.faq_url),
@ -83,12 +83,10 @@ fun AboutTab() {
.wrapContentSize(Alignment.Center),
horizontalAlignment = Alignment.CenterHorizontally
) {
// note: the preview
val context = LocalContext.current
val launcherDrawable = remember { getDrawable(context, R.mipmap.ic_launcher) }
AsyncImage(
model = launcherDrawable,
Image(
imageVector = NewPipeSquircleIcon,
contentDescription = stringResource(R.string.app_name),
modifier = Modifier.size(64.dp),
)
Spacer(Modifier.height(4.dp))
Text(

View file

@ -19,7 +19,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
import androidx.compose.ui.unit.dp
@ -122,28 +121,23 @@ private fun CommentRepliesDialog(
if (comments.itemCount == 0) {
item {
val refresh = comments.loadState.refresh
if (refresh is LoadState.Loading) {
LoadingIndicator(modifier = Modifier.padding(top = 8.dp))
} else if (refresh is LoadState.Error) {
// TODO use error panel instead
EmptyStateComposable(
spec = EmptyStateSpec.DisabledComments.copy(
descriptionText = {
stringResource(R.string.error_unable_to_load_comments)
when (val refresh = comments.loadState.refresh) {
is LoadState.Loading -> {
LoadingIndicator(modifier = Modifier.padding(top = 8.dp))
}
else -> {
// TODO use error panel instead
EmptyStateComposable(
spec = if (refresh is LoadState.Error) {
EmptyStateSpec.ErrorLoadingComments
} else {
EmptyStateSpec.NoComments
},
),
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 128.dp)
)
} else {
EmptyStateComposable(
spec = EmptyStateSpec.NoComments,
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 128.dp)
)
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 128.dp)
)
}
}
}
} else {

View file

@ -15,7 +15,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@ -75,7 +74,6 @@ private fun CommentSection(
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 128.dp)
)
}
} else if (count == 0) {
@ -111,13 +109,7 @@ private fun CommentSection(
is LoadState.Error -> {
item {
// TODO use error panel instead
EmptyStateComposable(
EmptyStateSpec.DisabledComments.copy(
descriptionText = {
stringResource(R.string.error_unable_to_load_comments)
}
)
)
EmptyStateComposable(EmptyStateSpec.ErrorLoadingComments)
}
}
@ -134,11 +126,7 @@ private fun CommentSection(
item {
// TODO use error panel instead
EmptyStateComposable(
spec = EmptyStateSpec.DisabledComments.copy(
descriptionText = {
stringResource(R.string.error_unable_to_load_comments)
}
),
spec = EmptyStateSpec.ErrorLoadingComments,
modifier = Modifier
.fillMaxWidth()
.heightIn(min = 128.dp)

View file

@ -1,6 +1,7 @@
package org.schabi.newpipe.ui.emptystate
import android.graphics.Color
import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
@ -22,25 +23,14 @@ import org.schabi.newpipe.ui.theme.AppTheme
fun EmptyStateComposable(
spec: EmptyStateSpec,
modifier: Modifier = Modifier,
) = EmptyStateComposable(
emojiText = spec.emojiText(),
descriptionText = spec.descriptionText(),
modifier = modifier
)
@Composable
private fun EmptyStateComposable(
emojiText: String,
descriptionText: String,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
verticalArrangement = Arrangement.Center,
) {
Text(
text = emojiText,
text = spec.emojiText,
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center,
)
@ -49,7 +39,7 @@ private fun EmptyStateComposable(
modifier = Modifier
.padding(top = 6.dp)
.padding(horizontal = 16.dp),
text = descriptionText,
text = stringResource(spec.descriptionText),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
)
@ -82,66 +72,48 @@ fun EmptyStateComposableNoCommentPreview() {
}
}
data class EmptyStateSpec(
val emojiText: @Composable () -> String,
val descriptionText: @Composable () -> String,
enum class EmptyStateSpec(
val emojiText: String,
@field:StringRes val descriptionText: Int,
) {
companion object {
val GenericError =
EmptyStateSpec(
emojiText = { "¯\\_(ツ)_/¯" },
descriptionText = { stringResource(id = R.string.empty_list_subtitle) },
)
val NoVideos =
EmptyStateSpec(
emojiText = { "(╯°-°)╯" },
descriptionText = { stringResource(id = R.string.no_videos) },
)
val NoComments =
EmptyStateSpec(
emojiText = { "¯\\_(╹x╹)_/¯" },
descriptionText = { stringResource(id = R.string.no_comments) },
)
val DisabledComments =
NoComments.copy(
descriptionText = { stringResource(id = R.string.comments_are_disabled) },
)
val NoSearchResult =
NoComments.copy(
emojiText = { "╰(°●°╰)" },
descriptionText = { stringResource(id = R.string.search_no_results) }
)
val NoSearchMaxSizeResult =
NoSearchResult
val ContentNotSupported =
NoComments.copy(
emojiText = { "(︶︹︺)" },
descriptionText = { stringResource(id = R.string.content_not_supported) },
)
val NoBookmarkedPlaylist =
EmptyStateSpec(
emojiText = { "(╥﹏╥)" },
descriptionText = { stringResource(id = R.string.no_playlist_bookmarked_yet) },
)
val NoSubscriptionsHint =
EmptyStateSpec(
emojiText = { "(꩜ᯅ꩜)" },
descriptionText = { stringResource(id = R.string.import_subscriptions_hint) },
)
val NoSubscriptions =
NoSubscriptionsHint.copy(
descriptionText = { stringResource(id = R.string.no_channel_subscribed_yet) },
)
}
GenericError(
emojiText = "¯\\_(ツ)_/¯",
descriptionText = R.string.empty_list_subtitle,
),
NoVideos(
emojiText = "(╯°-°)╯",
descriptionText = R.string.no_videos,
),
NoComments(
emojiText = "¯\\_(╹x╹)_/¯",
descriptionText = R.string.no_comments,
),
DisabledComments(
emojiText = "¯\\_(╹x╹)_/¯",
descriptionText = R.string.comments_are_disabled,
),
ErrorLoadingComments(
emojiText = "¯\\_(╹x╹)_/¯",
descriptionText = R.string.error_unable_to_load_comments,
),
NoSearchResult(
emojiText = "╰(°●°╰)",
descriptionText = R.string.search_no_results,
),
ContentNotSupported(
emojiText = "(︶︹︺)",
descriptionText = R.string.content_not_supported,
),
NoBookmarkedPlaylist(
emojiText = "(╥﹏╥)",
descriptionText = R.string.no_playlist_bookmarked_yet,
),
NoSubscriptionsHint(
emojiText = "(꩜ᯅ꩜)",
descriptionText = R.string.import_subscriptions_hint,
),
NoSubscriptions(
emojiText = "(꩜ᯅ꩜)",
descriptionText = R.string.no_channel_subscribed_yet,
),
}

View file

@ -9,9 +9,11 @@ import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.text.Html;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
@ -113,14 +115,47 @@ public final class PermissionHelper {
@RequiresApi(api = Build.VERSION_CODES.M)
public static boolean checkSystemAlertWindowPermission(final Context context) {
if (!Settings.canDrawOverlays(context)) {
final Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + context.getPackageName()));
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(i);
} catch (final ActivityNotFoundException ignored) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
final Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + context.getPackageName()));
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
context.startActivity(i);
} catch (final ActivityNotFoundException ignored) {
}
return false;
// from Android R the ACTION_MANAGE_OVERLAY_PERMISSION will only point to the menu,
// so lets add a dialog that points the user to the right setting.
} else {
final String appName = context.getApplicationInfo()
.loadLabel(context.getPackageManager()).toString();
final String title = context.getString(R.string.permission_display_over_apps);
final String permissionName =
context.getString(R.string.permission_display_over_apps_permission_name);
final String appNameItalic = "<i>" + appName + "</i>";
final String permissionNameItalic = "<i>" + permissionName + "</i>";
final String message =
context.getString(R.string.permission_display_over_apps_message,
appNameItalic,
permissionNameItalic
);
new AlertDialog.Builder(context)
.setTitle(title)
.setMessage(Html.fromHtml(message, Html.FROM_HTML_MODE_COMPACT))
.setPositiveButton("OK", (dialog, which) -> {
// we dont need the package name here, since it wont do anything on >R
final Intent intent =
new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
try {
context.startActivity(intent);
} catch (final ActivityNotFoundException ignored) {
}
})
.setCancelable(true)
.show();
return false;
}
return false;
} else {
return true;
}

View file

@ -121,7 +121,7 @@ public final class SparseItemUtil {
callback.accept(result);
}, throwable -> ErrorUtil.createNotification(context,
new ErrorInfo(throwable, UserAction.REQUESTED_STREAM,
"Loading stream info: " + url, serviceId)
"Loading stream info: " + url, serviceId, url)
));
}
}

View file

@ -0,0 +1,99 @@
package org.schabi.newpipe.util.image
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
/**
* Generated with https://github.com/rafaeltonholo/svg-to-compose/
* based on assets/newpipe_squircle.svg.
*/
val NewPipeSquircleIcon: ImageVector
get() {
val current = _newPipeIcon
if (current != null) return current
return ImageVector.Builder(
name = "org.schabi.newpipe.ui.theme.AppTheme.NewPipeSquircleIcon",
defaultWidth = 100.0.dp,
defaultHeight = 100.0.dp,
viewportWidth = 100.0f,
viewportHeight = 100.0f,
).apply {
// M0 50 C0 15 15 0 50 0 s50 15 50 50 -15 50 -50 50 S0 85 0 50
path(
fill = SolidColor(Color(0xFFCD201F)),
) {
// M 0 50
moveTo(x = 0.0f, y = 50.0f)
// C 0 15 15 0 50 0
curveTo(
x1 = 0.0f,
y1 = 15.0f,
x2 = 15.0f,
y2 = 0.0f,
x3 = 50.0f,
y3 = 0.0f,
)
// s 50 15 50 50
reflectiveCurveToRelative(
dx1 = 50.0f,
dy1 = 15.0f,
dx2 = 50.0f,
dy2 = 50.0f,
)
// s -15 50 -50 50
reflectiveCurveToRelative(
dx1 = -15.0f,
dy1 = 50.0f,
dx2 = -50.0f,
dy2 = 50.0f,
)
// S 0 85 0 50
reflectiveCurveTo(
x1 = 0.0f,
y1 = 85.0f,
x2 = 0.0f,
y2 = 50.0f,
)
}
// M31.7 19.2 v61.7 l9.7 -5.73 V36 l23.8 14 -17.6 10.35 V71.5 L84 50
path(
fill = SolidColor(Color(0xFFFFFFFF)),
) {
// M 31.7 19.2
moveTo(x = 31.7f, y = 19.2f)
// v 61.7
verticalLineToRelative(dy = 61.7f)
// l 9.7 -5.73
lineToRelative(dx = 9.7f, dy = -5.73f)
// V 36
verticalLineTo(y = 36.0f)
// l 23.8 14
lineToRelative(dx = 23.8f, dy = 14.0f)
// l -17.6 10.35
lineToRelative(dx = -17.6f, dy = 10.35f)
// V 71.5
verticalLineTo(y = 71.5f)
// L 84 50
lineTo(x = 84.0f, y = 50.0f)
}
}.build().also { _newPipeIcon = it }
}
@Preview
@Composable
private fun IconPreview() {
Image(
imageVector = NewPipeSquircleIcon,
contentDescription = null,
)
}
@Suppress("ObjectPropertyName")
private var _newPipeIcon: ImageVector? = null

View file

@ -1,14 +1,13 @@
package org.schabi.newpipe.util.text;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.error.ErrorPanelHelper;
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.ErrorUtil;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@ -158,19 +157,13 @@ public final class InternalUrlsHandler {
disposables.add(single.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
final PlayQueue playQueue =
new SinglePlayQueue(info, seconds * 1000L);
final PlayQueue playQueue = new SinglePlayQueue(info, seconds * 1000L);
NavigationHelper.playOnPopupPlayer(context, playQueue, false);
}, throwable -> {
if (DEBUG) {
Log.e(TAG, "Could not play on popup: " + url, throwable);
}
new AlertDialog.Builder(context)
.setTitle(R.string.player_stream_failure)
.setMessage(
ErrorPanelHelper.Companion.getExceptionDescription(throwable))
.setPositiveButton(R.string.ok, null)
.show();
// This will only show a snackbar if the passed context has a root view:
// otherwise it will resort to showing a notification, so we are safe here.
ErrorUtil.showSnackbar(context,
new ErrorInfo(throwable, UserAction.PLAY_ON_POPUP, url, null, url));
}));
return true;
}

View file

@ -265,7 +265,7 @@ public class DownloadManager {
}
}
public void deleteMission(Mission mission) {
public void deleteMission(Mission mission, boolean alsoDeleteFile) {
synchronized (this) {
if (mission instanceof DownloadMission) {
mMissionsPending.remove(mission);
@ -274,7 +274,9 @@ public class DownloadManager {
mFinishedMissionStore.deleteMission(mission);
}
mission.delete();
if (alsoDeleteFile) {
mission.delete();
}
}
}

View file

@ -563,16 +563,16 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
}
request.append("]");
String service;
Integer service;
try {
service = NewPipe.getServiceByUrl(mission.source).getServiceInfo().getName();
service = NewPipe.getServiceByUrl(mission.source).getServiceId();
} catch (Exception e) {
service = ErrorInfo.SERVICE_NONE;
service = null;
}
ErrorUtil.createNotification(mContext,
new ErrorInfo(ErrorInfo.Companion.throwableToStringList(mission.errObject), action,
service, request.toString(), reason));
request.toString(), service, reason));
}
public void clearFinishedDownloads(boolean delete) {
@ -614,7 +614,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
while (i.hasNext()) {
Mission mission = i.next();
if (mission != null) {
mDownloadManager.deleteMission(mission);
mDownloadManager.deleteMission(mission, true);
mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mission.storage.getUri()));
}
i.remove();
@ -667,7 +667,14 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
shareFile(h.item.mission);
return true;
case R.id.delete:
mDeleter.append(h.item.mission);
// delete the entry and the file
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;
@ -676,7 +683,7 @@ public class MissionAdapter extends Adapter<ViewHolder> implements Handler.Callb
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);
mDeleter.append(h.item.mission, true);
applyChanges();
return true;
}

View file

@ -13,7 +13,9 @@ import com.google.android.material.snackbar.Snackbar;
import org.schabi.newpipe.R;
import java.util.ArrayList;
import java.util.Optional;
import kotlin.Pair;
import us.shandian.giga.get.FinishedMission;
import us.shandian.giga.get.Mission;
import us.shandian.giga.service.DownloadManager;
@ -30,7 +32,8 @@ public class Deleter {
private static final int DELAY_RESUME = 400;// ms
private Snackbar snackbar;
private ArrayList<Mission> items;
// list of missions to be deleted, and whether to also delete the corresponding file
private ArrayList<Pair<Mission, Boolean>> items;
private boolean running = true;
private final Context mContext;
@ -51,7 +54,7 @@ public class Deleter {
items = new ArrayList<>(2);
}
public void append(Mission item) {
public void append(Mission item, boolean alsoDeleteFile) {
/* If a mission is removed from the list while the Snackbar for a previously
* removed item is still showing, commit the action for the previous item
* immediately. This prevents Snackbars from stacking up in reverse order.
@ -60,13 +63,13 @@ public class Deleter {
commit();
mIterator.hide(item);
items.add(0, item);
items.add(0, new Pair<>(item, alsoDeleteFile));
show();
}
private void forget() {
mIterator.unHide(items.remove(0));
mIterator.unHide(items.remove(0).getFirst());
mAdapter.applyChanges();
show();
@ -84,7 +87,19 @@ public class Deleter {
private void next() {
if (items.size() < 1) return;
String msg = mContext.getString(R.string.file_deleted).concat(":\n").concat(items.get(0).storage.getName());
final Optional<String> fileToBeDeleted = items.stream()
.filter(Pair::getSecond)
.map(p -> p.getFirst().storage.getName())
.findFirst();
String msg;
if (fileToBeDeleted.isPresent()) {
msg = mContext.getString(R.string.file_deleted)
.concat(":\n")
.concat(fileToBeDeleted.get());
} else {
msg = mContext.getString(R.string.entry_deleted);
}
snackbar = Snackbar.make(mView, msg, Snackbar.LENGTH_INDEFINITE);
snackbar.setAction(R.string.undo, s -> forget());
@ -98,11 +113,13 @@ public class Deleter {
if (items.size() < 1) return;
while (items.size() > 0) {
Mission mission = items.remove(0);
Pair<Mission, Boolean> missionAndAlsoDeleteFile = items.remove(0);
Mission mission = missionAndAlsoDeleteFile.getFirst();
boolean alsoDeleteFile = missionAndAlsoDeleteFile.getSecond();
if (mission.deleted) continue;
mIterator.unHide(mission);
mDownloadManager.deleteMission(mission);
mDownloadManager.deleteMission(mission, alsoDeleteFile);
if (mission instanceof FinishedMission) {
mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, mission.storage.getUri()));
@ -137,7 +154,11 @@ public class Deleter {
pause();
for (Mission mission : items) mDownloadManager.deleteMission(mission);
for (Pair<Mission, Boolean> missionAndAlsoDeleteFile : items) {
Mission mission = missionAndAlsoDeleteFile.getFirst();
boolean alsoDeleteFile = missionAndAlsoDeleteFile.getSecond();
mDownloadManager.deleteMission(mission, alsoDeleteFile);
}
items = null;
}
}

View file

@ -27,7 +27,11 @@
<item
android:id="@+id/delete"
android:title="@string/delete" />
android:title="@string/delete_file" />
<item
android:id="@+id/delete_entry"
android:title="@string/delete_entry" />
<item
android:id="@+id/error_message_view"

View file

@ -640,7 +640,6 @@
<string name="play_queue_audio_track">الصوت : %s</string>
<string name="playback_step">خطوة</string>
<string name="recaptcha_solve">حل</string>
<string name="service_provides_reason">%s يقدم هذا السبب:</string>
<string name="selected_stream_external_player_not_supported">الدفق المحدد غير مدعوم من قبل المشغلون الخارجيون</string>
<string name="title_activity_about">عن تطبيق نيوپايپ</string>
<string name="seek_duration_title">تسريع إلى الأمام/-ترجيع وقت البحث</string>

View file

@ -649,7 +649,6 @@
<string name="description_select_enable">تمكين تحديد نص في الوصف</string>
<string name="description_select_note">يمكنك الآن تحديد نص داخل الوصف. لاحظ أن الصفحة قد تومض وقد لا تكون الروابط قابلة للنقر أثناء وضع التحديد.</string>
<string name="open_website_license">فتح الموقع</string>
<string name="service_provides_reason">%s يقدم هذا السبب:</string>
<string name="account_terminated">تم إنهاء الحساب</string>
<string name="feed_load_error_fast_unknown">لا يوفر وضع التغذية السريعة مزيدًا من المعلومات حول هذا الموضوع.</string>
<string name="feed_load_error_terminated">حساب منشئ المحتوى قد تم إنهائه.

View file

@ -499,7 +499,6 @@
<string name="enable_queue_limit">Endirmə növbəsini məhdudlaşdır</string>
<string name="enable_queue_limit_desc">Eyni vaxtda ancaq bir endirmə həyata keçiriləcək</string>
<string name="account_terminated">Hesab ləğv edildi</string>
<string name="service_provides_reason">%s bu səbəbi təmin edir:</string>
<string name="download_has_started">Yükləmə başladı</string>
<string name="description_select_disable">ıqlamadakı mətni seçməyi qeyri-aktiv et</string>
<string name="metadata_category">Kateqoriya</string>

View file

@ -704,7 +704,6 @@
<string name="private_content">Гэта змесціва з\'яўляецца прыватным, таму NewPipe не можа яго трансляваць або спампоўваць.</string>
<string name="youtube_music_premium_content">Гэта відэа даступна толькі для падпісчыкаў YouTube Music Premium, таму NewPipe не можа яго трансляваць або спампоўваць.</string>
<string name="account_terminated">Уліковы запіс спынены</string>
<string name="service_provides_reason">%s дае наступную прычыну:</string>
<string name="featured">Вартае ўвагі</string>
<string name="metadata_privacy_internal">Унутраная</string>
<string name="feed_show_watched">Прагледжаныя цалкам</string>

View file

@ -416,7 +416,6 @@
<string name="playlist_page_summary">Страница на плейлиста</string>
<string name="chapters">Глави</string>
<string name="metadata_licence">Лиценз</string>
<string name="service_provides_reason">%s посочва следната причина:</string>
<string name="metadata_tags">Маркери</string>
<string name="metadata_privacy">Поверителност</string>
<string name="metadata_language">Език</string>

View file

@ -540,7 +540,6 @@
<string name="feed_load_error_account_info">\'%s\' এর জন্য ফিড প্রক্রিয়া করা যাচ্ছে না।</string>
<string name="description_select_disable">বর্ণনার লেখা নির্বাচন করা নিষ্ক্রিয় করো</string>
<string name="description_select_enable">বর্ণনার লেখা নির্বাচন করা সক্ষম করো</string>
<string name="service_provides_reason">%s এই কারণ বলছে:</string>
<string name="feed_load_error">প্রক্রিয়াকরণ ফিডে ত্রুটি</string>
<string name="open_website_license">ওয়েবসাইট খুলুন</string>
<string name="account_terminated">অ্যাকাউন্ট ধ্বংসকৃত</string>

View file

@ -612,7 +612,6 @@
<string name="select_night_theme_toast">Pot seleccionar el seu tema fosc favorit aqui sota</string>
<string name="night_theme_summary">Selecciona el teu tema fosc favorit — %s</string>
<string name="auto_device_theme_title">Automàtic (tema del dispositiu)</string>
<string name="service_provides_reason">%s dóna aquesta raó:</string>
<string name="account_terminated">Usuari suspes</string>
<string name="feed_load_error_terminated">El compte de l\'autor ha estat esborrat.
\nNewPipe no serà capaç de carregar aquest fil en el futur.

View file

@ -596,7 +596,6 @@
<string name="radio">ڕادیۆ</string>
<string name="featured">تایبەتکراو</string>
<string name="paid_content">ئه‌م بابه‌ته‌ ته‌نیا بۆ ئه‌و كه‌سانه‌ به‌رده‌سته‌ كه‌ پاره‌یان داوه‌ ، بۆیه‌ ناتوانرێت له‌ نیوپایپه‌وه‌ داببه‌زێنرێت.</string>
<string name="service_provides_reason">%s ئه‌م هۆكاره‌ دابین ده‌كات:</string>
<string name="account_terminated">هه‌ژمار له‌ناوبراوه‌</string>
<string name="youtube_music_premium_content">ئه‌م ڤیدیۆیه‌ ته‌نیا له‌ وه‌شانی نایابی یوتوب میوزیك به‌رده‌سته‌ ، بۆیه‌ ناتوانرێت له‌ نیوپایپه‌وه‌ داببه‌زێنرێت.</string>
<string name="soundcloud_go_plus_content">ئه‌مه‌ تراكی SoundCloud Go+ ه ، لانی كه‌م له‌ وڵاته‌كه‌ی تۆدا، ناتوانرێت له‌لایه‌ن نیوپایپه‌وه‌ داببه‌زێنرێت.</string>

View file

@ -619,7 +619,6 @@
<string name="description_select_disable">Vypnout výběr textu v popisu</string>
<string name="description_select_enable">Zapnout výběr textu v popisu</string>
<string name="description_select_note">Nyní můžete vybrat v popisu text. Pamatujte, že v režimu výběru může stránka blikat a odkazy nemusí reagovat na kliknutí.</string>
<string name="service_provides_reason">%s udává teno důvod:</string>
<string name="account_terminated">Účet uzavřen</string>
<string name="feed_load_error_fast_unknown">Režim rychlého feedu o tom neposkytuje více informací.</string>
<string name="feed_load_error_terminated">Autorův účet byl uzavřen.

View file

@ -553,7 +553,6 @@
<string name="private_content">Dette indhold er privat, så det kan ikke streames eller hentes af NewPipe.</string>
<string name="recently_added">Nyligt tilføjede</string>
<string name="featured">Fremhævede</string>
<string name="service_provides_reason">%s giver denne grund:</string>
<plurals name="listening">
<item quantity="one">%s lytter</item>
<item quantity="other">%s lyttere</item>

View file

@ -627,7 +627,6 @@
<string name="downloads_storage_ask_summary_no_saf_notice">Du wirst jedes Mal gefragt werden, wohin der Download gespeichert werden soll</string>
<string name="feed_load_error">Fehler beim Laden des Feeds</string>
<string name="feed_load_error_account_info">Konnte Feed für \'%s\' nicht laden.</string>
<string name="service_provides_reason">%s gibt diesen Grund an:</string>
<string name="on">An</string>
<string name="tablet_mode_title">Tablet-Modus</string>
<string name="off">Aus</string>

View file

@ -608,7 +608,6 @@
<string name="description_select_enable">Ενεργοποίηση επιλογής κειμένου στην περιγραφή</string>
<string name="description_select_note">Τώρα μπορείτε να επιλέξετε κείμενο εντός της περιγραφής. Σημειώστε ότι, η σελίδα μπορεί να παρουσιάζει αστάθεια κατά τη διάρκεια της κατάστασης επιλογής κειμένου.</string>
<string name="open_website_license">Ανοικτή ιστοσελίδα</string>
<string name="service_provides_reason">Το %s παρέχει αυτή την αιτία:</string>
<string name="account_terminated">Ο λογαριασμός διαγράφηκε</string>
<string name="feed_load_error_fast_unknown">Η κατάσταση γρήγορης τροφοδοσίας δεν παρέχει περισσότερες πληροφορίες.</string>
<string name="feed_load_error_terminated">Ο λογαριασμός του δημιουργού έχει διαγραφεί.

View file

@ -504,7 +504,6 @@
<string name="on">Ŝaltita</string>
<string name="metadata_tags">Etikedoj</string>
<string name="download_has_started">Elŝutado komenciĝis</string>
<string name="service_provides_reason">%s donas tiun kialon:</string>
<string name="georestricted_content">Tiu enaĵo ne disponeblas en via lando.</string>
<string name="recent">Freŝaj</string>
<string name="video_detail_by">De %s</string>

View file

@ -611,7 +611,6 @@
<string name="description_select_disable">Deshabilitar la selección de texto de la descripción</string>
<string name="description_select_enable">Habilitar la selección de texto de la descripción</string>
<string name="description_select_note">Ahora puede seleccionar el texto dentro de la descripción. Note que la página puede parpadear y los links no serán cliqueables mientras está en el modo de selección.</string>
<string name="service_provides_reason">%s da esta razón:</string>
<string name="feed_load_error_account_info">No fue posible cargar el feed por \'%s\'.</string>
<string name="account_terminated">Cuenta cancelada</string>
<string name="feed_load_error_fast_unknown">El modo de muro rápido no arroja más información sobre esto.</string>

View file

@ -562,7 +562,6 @@
<string name="show_thumbnail_title">Näita pisipilte</string>
<string name="show_thumbnail_summary">Kasuta pisipilti nii lukustusvaate kui teavituste taustana</string>
<string name="account_terminated">Kasutajakonto on suletud</string>
<string name="service_provides_reason">%s toob põhjuseks:</string>
<string name="description_select_enable">Võimalda valida kirjelduse teksti</string>
<string name="description_select_disable">Ära võimalda valida kirjelduse teksti</string>
<string name="metadata_category">Kategooria</string>

View file

@ -615,7 +615,6 @@
<string name="downloads_storage_ask_summary_no_saf_notice">Non gorde galdetuko zaizu deskarga bakoitzean</string>
<string name="no_dir_yet">Ez da deskargatzeko karpetarik ezarri oraindik, aukeratu lehenetsitako deskargatzeko karpeta orain</string>
<string name="metadata_privacy">Pribatutasuna</string>
<string name="service_provides_reason">%s arrazoi hau ematen du:</string>
<string name="account_terminated">Kontua ezabatu da</string>
<string name="feed_load_error_fast_unknown">Jario azkarrak ez du honi buruz informazio gehiagorik ematen.</string>
<string name="metadata_age_limit">Adin muga</string>

View file

@ -625,7 +625,6 @@
\nنیوپایپ قادر به بار کردن این خوراک در آینده نیست.
\nمیخواهید اشتراک این کانال را لغو کنید؟</string>
<string name="feed_load_error_fast_unknown">حالت خوراک سریع، اطَلاعات بیش‌تری در این باره نمی‌دهد.</string>
<string name="service_provides_reason">%s این دلیل را آورد:</string>
<string name="seekbar_preview_thumbnail_title">پیش‌نمایش بندانگشتی نوار جویش</string>
<string name="detail_heart_img_view_description">قلب‌شده به دست ایجادگر</string>
<string name="local_search_suggestions">پیشنهادهای جست‌وجوی محلّی</string>

View file

@ -592,7 +592,6 @@
<string name="night_theme_title">Yöteema</string>
<string name="description_select_disable">Poista käytöstä tekstinvalinta kuvauskentän sisältä</string>
<string name="description_select_note">Voit nyt valita tekstin kuvauskentän sisältä. Huomioithan, että valintatilan aikana sivu voi vilkkua ja linkit eivät ehkä ole klikattavia.</string>
<string name="service_provides_reason">%s tuo tämän syyn:</string>
<string name="seekbar_preview_thumbnail_title">Säätövivun kuvakkeen esikatselu</string>
<string name="disable_media_tunneling_summary">Poista median tunnelointi käytöstä, jos havaitset mustan näyttöruudun tai änkytystä videon toistossa.</string>
<string name="disable_media_tunneling_title">Poista median tunnelointi käytöstä</string>

View file

@ -620,7 +620,6 @@
<string name="metadata_tags">Étiquettes</string>
<string name="metadata_category">Catégorie</string>
<string name="description_select_note">Vous pouvez maintenant sélectionner du texte à lintérieur de la description. Notez que la page peut scintiller et que les liens peuvent ne pas être cliquables en mode sélection.</string>
<string name="service_provides_reason">%s indique le motif :</string>
<string name="no_dir_yet">Aucun dossier de téléchargement nest défini pour le moment, sélectionnez le dossier de téléchargement par défaut</string>
<string name="open_website_license">Ouvrir le site web</string>
<string name="account_terminated">Compte résilié</string>

View file

@ -554,7 +554,6 @@
<string name="description_select_note">Agora pode seleccionar o texto na descrición. Teña en conta que a páxina pode cintilar e as ligazóns poden non ser clicábeis no modo selección.</string>
<string name="auto_device_theme_title">Automático (Tema do dispositivo)</string>
<string name="radio">Radio</string>
<string name="service_provides_reason">%s dá este motivo:</string>
<string name="georestricted_content">Este contido non está dispoñíbel no seu país.</string>
<string name="chapters">Capítulos</string>
<string name="recent">Recentes</string>

View file

@ -632,7 +632,6 @@
\nל־NewPipe לא תהיה אפשרות להוריד את ההזנה הזאת בעתיד.
\nלהסיר את המינוי מהערוץ הזה\?</string>
<string name="open_website_license">פתיחת האתר</string>
<string name="service_provides_reason">%s מספק את הסיבה הבאה:</string>
<string name="account_terminated">החשבון הושמד</string>
<string name="feed_load_error_fast_unknown">מצב ההזנה המהירה לא מספק מידע נוסף על כך.</string>
<string name="feed_load_error_account_info">לא ניתן לטעון את ההזנה עבור %s.</string>

View file

@ -654,7 +654,6 @@
<string name="disable_media_tunneling_title">मीडिया टनलिंग अक्षम करें</string>
<string name="show_crash_the_player_title">\"क्रैश द प्लेयर\" दिखाएं</string>
<string name="feed_subscription_not_loaded_count">लोड नहीं हुआ: %d</string>
<string name="service_provides_reason">%s इसका कारण प्रदान करता है:</string>
<string name="metadata_tags">टैग</string>
<string name="metadata_licence">लाइसेंस</string>
<string name="faq_description">यदि आपको ऐप का उपयोग करने में परेशानी हो रही है, तो सामान्य प्रश्नों के इन उत्तरों को देखना सुनिश्चित करें!</string>

View file

@ -639,7 +639,6 @@
<string name="metadata_privacy_internal">Interno</string>
<string name="metadata_privacy">Privatnost</string>
<string name="description_select_note">Sada možeš odabrati tekst u opisu. Napomena: stranica će možda treperiti i možda nećeš moći kliknuti poveznice u načinu rada za odabir teksta.</string>
<string name="service_provides_reason">%s pruža ovaj razlog:</string>
<string name="processing_may_take_a_moment">Obrada u tijeku … Može malo potrajati</string>
<string name="main_page_content_swipe_remove">Za ukljanjanje stavki povuci ih</string>
<plurals name="download_finished_notification">

View file

@ -537,7 +537,6 @@
<string name="related_items_tab_description">Kapcsolódó elemek</string>
<string name="error_report_open_github_notice">Ellenőrizze, hogy létezik-e már olyan jegy, amely az összeomlásával foglalkozik. Ha duplikált jegyet ad fel, akkor olyan időt vesz el tőlünk, amelyet a hiba javítására tudnánk fordítani.</string>
<string name="minimize_on_exit_title">Minimalizálás alkalmazásváltáskor</string>
<string name="service_provides_reason">A(z) %s ezt az okot adta meg:</string>
<string name="local_search_suggestions">Helyi keresési javaslatok</string>
<string name="remote_search_suggestions">Távoli keresési javaslatok</string>
<string name="start_main_player_fullscreen_title">A fő lejátszó teljes képernyős indítása</string>

View file

@ -599,7 +599,6 @@
<string name="description_select_enable">Aktifkan dapat memilih teks pada deskripsi</string>
<string name="description_select_note">Anda sekarang dapat memilih teks di dalam deskripsi. Perhatikan bahwa halaman mungkin berkedip dan tautan tidak dapat diklik saat dalam mode pemilihan.</string>
<string name="open_website_license">Buka situs web</string>
<string name="service_provides_reason">%s menyediakan alasan ini:</string>
<string name="account_terminated">Akun dinonaktifkan</string>
<string name="feed_load_error_fast_unknown">Mode langganan cepat tidak menyediakan lebih banyak info tentang ini.</string>
<string name="feed_load_error_terminated">Akun kreator telah dinonaktifkan.

View file

@ -573,7 +573,6 @@
<string name="no_appropriate_file_manager_message_android_10">Enginn viðeigandi skráarstjóri fannst fyrir þessa aðgerð.
\nVinsamlegast settu upp skráarstjóra sem styður Geymsluaðgangsramma (SAF)</string>
<string name="georestricted_content">Þetta efni er ekki fáanlegt í þínu landi.</string>
<string name="service_provides_reason">%s gefur þessa ástæðu:</string>
<string name="paid_content">Þetta efni er aðeins í boði fyrir notendur sem hafa greitt — það er ekki hægt að streyma því eða sækja með NewPipe.</string>
<string name="auto_device_theme_title">Sjálfvirk (þema tækis)</string>
<string name="night_theme_summary">Veldu uppáhalds næturþemu þína — %s</string>

View file

@ -619,7 +619,6 @@
<string name="description_select_enable">Attiva la selezione del testo nella descrizione</string>
<string name="description_select_note">È possibile selezionare il testo all\'interno della descrizione. In modalità selezione la pagina potrebbe sfarfallare e i collegamenti potrebbero non essere cliccabili.</string>
<string name="open_website_license">Visita il sito</string>
<string name="service_provides_reason">%s fornisce questa motivazione:</string>
<string name="account_terminated">Account chiuso</string>
<string name="feed_load_error_fast_unknown">Il recupero veloce dei feed non fornisce ulteriori informazioni al riguardo.</string>
<string name="feed_load_error_terminated">L\'account dell\'autore è stato chiuso.

View file

@ -612,7 +612,6 @@
<string name="off">オフ</string>
<string name="on">オン</string>
<string name="tablet_mode_title">タブレットモード</string>
<string name="service_provides_reason">%s がこの理由を提示:</string>
<string name="dont_show">表示しない</string>
<string name="low_quality_smaller">低品質 (小)</string>
<string name="high_quality_larger">高品質 (大)</string>

View file

@ -544,7 +544,6 @@
<string name="georestricted_content">ეს ხელმიუწვდომელია თქვენი ქვეყნიდან.</string>
<string name="private_content">ეს მასალა პირადულია, ამიტომაც NewPipe-ს მისი არც მთლიანად და არც თანდათანობით ჩამოწერა არ შეუძლია.</string>
<string name="account_terminated">ანგარიში შეწყვეტილია</string>
<string name="service_provides_reason">%s იძლევა ამ მიზეზს:</string>
<string name="paid_content">ეს მასალა ხელმისაწვდომია მხოლოდ გადამხდელებისთვის, ამიტომაც NewPipe-ს მისი არც მთლიანად და არც თანდათანობით ჩამოწერა არ შეუძლია.</string>
<string name="featured">გამორჩეული</string>
<string name="radio">რადიო</string>

View file

@ -643,7 +643,6 @@
<string name="chapters">챕터</string>
<string name="recent">최근</string>
<string name="account_terminated">계정이 해지됨</string>
<string name="service_provides_reason">%s은(는) 다음과 같은 이유를 제공:</string>
<string name="soundcloud_go_plus_content">이것은 적어도 귀하의 국가에서 SoundCloud Go+ 트랙이므로 NewPipe에서 스트리밍하거나 다운로드할 수 없습니다.</string>
<string name="auto_device_theme_title">자동 (장치 테마)</string>
<string name="detail_pinned_comment_view_description">고정된 댓글</string>

View file

@ -623,7 +623,6 @@
<string name="description_select_enable">Įgalinti teksto pasirinkimą apraše</string>
<string name="description_select_disable">Neleisti pasirinkti teksto apraše</string>
<string name="description_select_note">Dabar apraše galite pasirinkti tekstą aprašyme. Atminkite, kad puslapis gali mirgėti, o nuorodos gali būti nespustelėjamos, kai veikia pasirinkimo režimas.</string>
<string name="service_provides_reason">%s pateikia šią priežastį:</string>
<string name="account_terminated">Paskyra anuliuota</string>
<string name="feed_load_error_fast_unknown">Greito srauto režimas nesuteikia daugiau informacijos apie tai.</string>
<string name="feed_load_error_terminated">Autoriaus paskyra anuliuota.

View file

@ -629,7 +629,6 @@
\nNewPipe turpmāk nevarēs ielādēt šo plūsmu.
\nVai vēlaties atteikties no šī kanāla abonēšanas\?</string>
<string name="feed_load_error_fast_unknown">Ātrās straumes režīms nesniedz vairāk informācijas par šo.</string>
<string name="service_provides_reason">%s dod šādu pamatojumu:</string>
<string name="description_select_disable">Izslēgt teksta atlasīšanu video aprakstā</string>
<string name="metadata_privacy_internal">Iekšeji</string>
<string name="detail_heart_img_view_description">Autors piekrīt</string>

View file

@ -431,7 +431,6 @@
<string name="feed_load_error_account_info">Неуспешно вчитување на новинска лента за „%s“.</string>
<string name="feed_show_hide_streams">Прикажи / скриј стримови</string>
<string name="private_content">Оваа содржина е приватна, така што не може да биде емитувана или преземена од страна на NewPipe.</string>
<string name="service_provides_reason">%s ја посочува следната причина:</string>
<string name="featured">Истакнато</string>
<string name="radio">Радио</string>
<string name="auto_device_theme_title">Автоматски (режим на уредот)</string>

View file

@ -612,7 +612,6 @@
<string name="metadata_tags">ടാഗുക്കൾ</string>
<string name="metadata_category">വിഭാഗം</string>
<string name="description_select_note">താക്കൾക് ഇപ്പോൾ ഡിസ്ക്രിപ്ഷൻ ബോക്സിലെ ടെക്സ്റ്റ്‌ തിരഞ്ഞെടുക്കാൻ സാധിക്കും. ശ്രെദ്ധിക്കുക സെലെക്ഷൻ മോഡിൽ പേജ് ചിലപ്പോൾ മിന്നുകയും ലിങ്കുകൾ ക്ലിക്ക് ചെയ്യാനാകാതെയും വന്നേക്കാം.</string>
<string name="service_provides_reason">ഇതിന്റെ കാരണം %s നൽകും:</string>
<string name="account_terminated">അക്കൗണ്ട് ഇല്ലാതായിരിക്കുന്നു</string>
<string name="feed_load_error_fast_unknown">ഫാസ്റ്റ് ഫീഡ് മോഡ് കൂടുതൽ വിവരങ്ങൾ നൽകില്ല.</string>
<string name="feed_load_error_terminated">സൃഷ്ടാവിന്റെ അക്കൗണ്ട് ഇല്ലാതായിരിക്കുന്നു.

View file

@ -600,7 +600,6 @@
\nØnsker du å oppheve ditt abonnement på denne kanalen\?</string>
<string name="description_select_disable">Skru av merking av tekst i beskrivelsen</string>
<string name="description_select_enable">Skru på merking av tekst i beskrivelsen</string>
<string name="service_provides_reason">%s oppgav denne grunnen:</string>
<string name="account_terminated">Konto terminert</string>
<string name="feed_load_error_account_info">Kunne ikke laste inn informasjonskanal for «%s».</string>
<string name="feed_load_error">Kunne ikke laste inn informasjonskanal</string>

View file

@ -612,7 +612,6 @@
<string name="on">Aan</string>
<string name="tablet_mode_title">Tablet-modus</string>
<string name="open_website_license">Website openen</string>
<string name="service_provides_reason">%s geeft de volgende reden:</string>
<string name="account_terminated">Account getermineerd</string>
<string name="feed_load_error_fast_unknown">De snelle feed mode levert hierover niet meer informatie.</string>
<string name="feed_load_error_terminated">De account van de auteur is getermineerd.

View file

@ -625,7 +625,6 @@
<string name="no_appropriate_file_manager_message_android_10">ߞߐߕߐ߯ ߡߊߡߙߊߟߊ߲߫ ߛߌ߫ ߡߊ߫ ߛߐ߬ߘߐ߲߬ ߞߋߥߊߟߌ ߣߌ߲߬ ߞߊ߲ߡߊ߬.
\nߘߌ߬ߢߍ߬ ߦߋ߫ ߞߐߕߐ߯ ߡߊߡߙߊߟߊ߲ ߘߏ߫ ߡߊߞߍ߫ ߡߍ߲ ߣߌ߫ ߡߙߊ߬ߘߐ߬ߦߊ ߟߊߛߐ߬ߘߐ߲ ߡߎ߬ߙߊ߲߬ߞߊ߲ߞߋ ߘߌ߫ ߓߍ߲߬</string>
<string name="youtube_music_premium_content">ߦߋߡߍ߲ߕߊ ߘߌ߫ ߡߊߛߐ߬ߘߐ߲߬ YouTube Music Premium ߛߌ߲߬ߝߏ߲ ߠߎ߬ ߟߋ߬ ߘߐߙߐ߲߫ ߓߟߏ߫߸ ߏ߬ ߘߐ߫ ߊ߬ ߕߍ߫ ߛߋ߫ ߘߐߛߊߙߌ߫ ߟߊ߫ ߥߟߊ߫ ߞߵߊ߬ ߟߊߖߌ߰ ߣߌߎߔߌߔ ߓߟߏ.</string>
<string name="service_provides_reason">%s ߦߋ߫ ߞߎ߲߭ ߣߌ߲߬ ߠߋ߬ ߝߐ߫ ߟߊ߫:</string>
<string name="featured">ߛߊ߲ߞߊߥߟߌ</string>
<string name="radio">ߥߎߢߊ߲ߓߍ߲</string>
<string name="auto_device_theme_title">ߖߘߍ߬ߢߍ߫ (ߕߙߏߞߏ߫ ߛߊߛߊ)</string>

View file

@ -485,7 +485,6 @@
<string name="feed_group_dialog_select_subscriptions">ସଦସ୍ୟତା ଚୟନ କରନ୍ତୁ</string>
<string name="feed_group_dialog_empty_selection">କୌଣସି ସଦସ୍ୟତା ଚୟନ ହୋଇନାହିଁ</string>
<string name="feed_use_dedicated_fetch_method_enable_button">ଦ୍ରୁତ ମୋଡ୍ ସକ୍ଷମ କରନ୍ତୁ</string>
<string name="service_provides_reason">%s ଏହି କାରଣ ପ୍ରଦାନ କରେ:</string>
<string name="detail_sub_channel_thumbnail_view_description">ଚ୍ୟାନେଲର ଅବତାର ଥମ୍ୱନେଲ୍</string>
<string name="featured">ବୈଶିଷ୍ଟ୍ୟ</string>
<string name="radio">ରେଡିଓ</string>

View file

@ -455,7 +455,6 @@
<string name="radio">ਰੇਡੀਓ</string>
<string name="featured">ਫੀਚਰਡ</string>
<string name="paid_content">ਇਹ ਸਮੱਗਰੀ ਸਿਰਫ਼ ਉਹਨਾਂ ਵਰਤੋਂਕਾਰਾਂ ਲਈ ਉਪਲਬਧ ਹੈ ਜਿੰਨ੍ਹਾਂ ਨੇ ਇਸਦੇ ਲਈ ਕੀਮਤ ਦਿੱਤੀ ਹੈ, ਇਸ ਕਰਕੇ ਨਿਊ-ਪਾਈਪ ਦੁਆਰਾ ਚਲਾਈ ਜਾਂ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।</string>
<string name="service_provides_reason">%s ਇਸਦਾ ਕਾਰਨ ਪ੍ਰਦਾਨ ਕਰਦਾ ਹੈ:</string>
<string name="account_terminated">ਖਾਤਾ ਬੰਦ ਕੀਤਾ ਗਿਆ</string>
<string name="youtube_music_premium_content">ਇਹ ਵੀਡੀਓ ਸਿਰਫ਼ ਯੂਟਿਊਬ ਮਿਊਜ਼ਿਕ ਦੇ ਪ੍ਰੀਮੀਅਮ ਮੈਂਬਰਾਂ ਲਈ ਉਪਲਬਧ ਹੈ, ਇਸ ਕਰਕੇ ਨਿਊ-ਪਾਈਪ ਦੁਆਰਾ ਚਲਾਈ ਜਾਂ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।</string>
<string name="private_content">ਇਹ ਸਮੱਗਰੀ ਨਿੱਜੀ (ਪ੍ਰਾਈਵੇਟ) ਹੈ, ਇਸ ਕਰਕੇ ਨਿਊ-ਪਾਈਪ ਦੁਆਰਾ ਚਲਾਈ ਜਾਂ ਡਾਊਨਲੋਡ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ।</string>

View file

@ -623,7 +623,6 @@
<string name="metadata_category">Kategoria</string>
<string name="open_website_license">Otwórz stronę</string>
<string name="description_select_note">Teraz możesz zaznaczyć tekst wewnątrz opisu. Pamiętaj, że w trybie zaznaczania strona może migotać i linki nie będą klikalne.</string>
<string name="service_provides_reason">%s podaje ten powód:</string>
<string name="account_terminated">Konto zamknięte</string>
<string name="feed_load_error_fast_unknown">Tryb szybki dla ładowania kanału nie dostarcza więcej informacji na ten temat.</string>
<string name="feed_load_error_terminated">Konto autora zostało zawieszone.

View file

@ -619,7 +619,6 @@
<string name="description_select_enable">Ativar seleção de texto na descrição</string>
<string name="description_select_note">Agora você pode selecionar o texto dentro da descrição. Note que a página pode piscar e os URL podem não ser clicáveis no modo de seleção.</string>
<string name="open_website_license">Abrir site</string>
<string name="service_provides_reason">%s fornece este motivo:</string>
<string name="account_terminated">Conta encerrada</string>
<string name="feed_load_error_fast_unknown">O modo feed rápido não fornece mais informações sobre isso.</string>
<string name="feed_load_error_terminated">A conta do autor foi encerrada.

View file

@ -623,7 +623,6 @@
<string name="description_select_disable">Desativar seleção de texto na descrição</string>
<string name="description_select_enable">Ativar seleção de texto na descrição</string>
<string name="description_select_note">Agora pode selecionar o texto na descrição. Note que a página pode cintilar e as ligações podem não ser clicáveis enquanto estiver no modo de seleção.</string>
<string name="service_provides_reason">%s fornece este motivo:</string>
<string name="account_terminated">Conta encerrada</string>
<string name="feed_load_error_fast_unknown">O modo de feed rápido não fornece mais informações sobre isto.</string>
<string name="feed_load_error_terminated">A conta do autor foi encerrada.

View file

@ -605,7 +605,6 @@
<string name="disable_media_tunneling_title">Desativar túnel multimédia</string>
<string name="downloads_storage_ask_summary_no_saf_notice">Sempre que descarregar um ficheiro, terá que indicar o local para o guardar</string>
<string name="no_dir_yet">Ainda não definiu uma pasta para as descargas. Escolha agora a pasta a utilizar</string>
<string name="service_provides_reason">%s fornece este motivo:</string>
<string name="account_terminated">Conta encerrada</string>
<string name="feed_load_error_fast_unknown">O modo de fonte rápida não fornece mais informações sobre isto.</string>
<string name="feed_load_error_terminated">A conta do autor foi encerrada.

View file

@ -624,7 +624,6 @@
<string name="description_select_disable">Dezactivați selectarea textului în descriere</string>
<string name="description_select_enable">Activați selectarea textului în descriere</string>
<string name="description_select_note">Acum puteți selecta text în interiorul descrierii. Rețineți că este posibil ca pagina să pâlpâie, iar linkurile să nu poată fi accesate în modul de selecție.</string>
<string name="service_provides_reason">%s oferă acest motiv:</string>
<string name="account_terminated">Contul a fost închis</string>
<string name="feed_load_error_fast_unknown">Modul rapid nu furnizează mai multe informații în acest sens.</string>
<string name="feed_load_error_terminated">Contul autorului a fost închis.

View file

@ -633,7 +633,6 @@
<string name="feed_load_error_account_info">Не удалось загрузить подписку \'%s\'.</string>
<string name="feed_load_error">Ошибка загрузки подписки</string>
<string name="open_website_license">Открыть веб-сайт</string>
<string name="service_provides_reason">%s указывает следующую причину:</string>
<string name="account_terminated">Аккаунт отключён</string>
<string name="downloads_storage_use_saf_summary_api_29">Начиная с Android 10 поддерживается только «Storage Access Framework»</string>
<string name="downloads_storage_ask_summary_no_saf_notice">Спрашивать, куда сохранять каждую загрузку</string>

View file

@ -622,7 +622,6 @@
<string name="off">オフ</string>
<string name="on">オン</string>
<string name="tablet_mode_title">タブレットモード</string>
<string name="service_provides_reason">%sやしがくぬりゆうていじ</string>
<string name="dont_show">ひょうじさん</string>
<string name="low_quality_smaller">ていふぃんしち(しょう)</string>
<string name="high_quality_larger">かんふぃんしち(だい)</string>

View file

@ -330,7 +330,6 @@
<string name="chapters">ᱪᱮᱯᱴᱟᱨᱥ</string>
<string name="no_appropriate_file_manager_message_android_10">ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ ᱟᱢ ᱢᱤᱫ ᱯᱷᱤᱞ ᱢᱟᱱᱮᱡᱚᱨ ᱤᱱᱥᱴᱚᱞ ᱢᱮ ᱟᱨᱵᱟᱝ ᱰᱟᱩᱱᱞᱚᱰ ᱥᱤᱴᱤᱝ ᱨᱮ ᱵᱚᱫᱚᱞ ᱦᱚᱪᱚ ᱞᱟᱹᱜᱤᱫ ᱯᱨᱚᱵᱷᱟᱣ ᱢᱮ\"</string>
<string name="youtube_music_premium_content">ᱱᱚᱶᱟ ᱵᱷᱤᱰᱤᱭᱳ ᱫᱚ ᱭᱩᱴᱭᱩᱵᱽ ᱢᱤᱣᱡᱤᱠ ᱯᱨᱤᱢᱤᱭᱟᱢ ᱥᱮᱞᱮᱫᱤᱭᱟᱹ ᱠᱚ ᱞᱟᱹᱜᱤᱫ ᱜᱮ ᱧᱟᱢᱚᱜᱼᱟ, ᱚᱱᱟᱛᱮ ᱱᱚᱶᱟ ᱫᱚ ᱱᱤᱭᱩ ᱯᱟᱭᱤᱯ ᱦᱚᱛᱮᱛᱮ ᱵᱟᱝ ᱥᱴᱨᱤᱢ ᱟᱨ ᱵᱟᱝ ᱰᱟᱩᱱᱞᱳᱰ ᱦᱩᱭ ᱫᱟᱲᱮᱭᱟᱜᱼᱟ ᱾</string>
<string name="service_provides_reason">%s ᱫᱚ ᱱᱚᱶᱟ ᱞᱟᱹᱠᱛᱤ ᱠᱟᱱᱟ:</string>
<string name="auto_device_theme_title">ᱚᱴᱚᱢᱟᱴᱤᱠ (ᱰᱤᱵᱟᱤᱥ ᱛᱷᱮᱢ)</string>
<string name="night_theme_summary">ᱟᱢᱟᱜ ᱯᱩᱭᱞᱩ ᱧᱤᱫᱟᱹ ᱛᱷᱤᱢ ᱵᱟᱪᱷᱟᱣ ᱢᱮ ⁇ %s</string>
<string name="select_night_theme_toast">ᱟᱢ ᱞᱟᱛᱟᱨ ᱨᱮ ᱟᱢᱟᱜ ᱧᱤᱫᱟᱹ ᱪᱮᱛᱟᱱ ᱵᱟᱪᱷᱟᱣ ᱫᱟᱲᱮᱭᱟᱜ ᱟ</string>

View file

@ -610,7 +610,6 @@
<string name="description_select_note">Como podes ischertare su testu in intro de sa descritzione. Ammenta·ti chi sa pàgina diat pòdere trèmere e sos ligàmenes si diant pòdere no abèrrere cando ses in modalidade de ischerta.</string>
<string name="downloads_storage_use_saf_summary_api_29">Incumintzende dae Android 10 petzi sa \'Storage Access Framework\' (Istrutura de Atzessu a s\'Archiviatzione) est suportada</string>
<string name="open_website_license">Aberi su situ web</string>
<string name="service_provides_reason">%s frunit custa resone:</string>
<string name="account_terminated">Contu serradu</string>
<string name="feed_load_error_fast_unknown">Su recùperu lestru de sos flussos non frunit àteras informatziones in subra de custu.</string>
<string name="feed_load_error_terminated">Su contu de s\'autore l\'ant serradu.

View file

@ -618,7 +618,6 @@
<string name="description_select_enable">Povolenie výberu textu v popise</string>
<string name="description_select_note">Teraz môžete vybrať text vo vnútri popisu. Upozorňujeme, že stránka môže blikať a odkazy nemusia byť klikateľné, keď je v režime výberu.</string>
<string name="open_website_license">Otvoriť webstránku</string>
<string name="service_provides_reason">%s uvádza tento dôvod:</string>
<string name="account_terminated">Účet bol zrušený</string>
<string name="feed_load_error_fast_unknown">Tento rýchly režim neposkytuje viac informácií.</string>
<string name="feed_load_error_terminated">Účet autora bol zrušený.

View file

@ -608,7 +608,6 @@
<string name="description_select_disable">Xidh caalamadinta qoraalka</string>
<string name="description_select_enable">Fur caalamadinta qoraalka</string>
<string name="description_select_note">Hadda waad dooran kartaa qoraalka ku dhexjira faahfaahinta. Ogow markaad caalamdinayso qoraalka boggu wuu boodboodi karaa tixraacyadana waxay noqon karaan kuwo aan lagu dhufan karin.</string>
<string name="service_provides_reason">%s wuxuu sheegayaa sababtan:</string>
<string name="account_terminated">Akoonka waa lajoojiyay</string>
<string name="feed_load_error_fast_unknown">Nidaamka dagdaga ah faahfaahin dheeraad ah uma hayo shaygan.</string>
<string name="feed_load_error_terminated">Akoonka soosaaraha waa la joojiyay.

View file

@ -598,7 +598,6 @@
<string name="night_theme_summary">Zgjidhni temën tuaj të preferuar të natës - %s</string>
<string name="auto_device_theme_title">Automatike (tema e pajisjes)</string>
<string name="radio">Radio</string>
<string name="service_provides_reason">%s e jep këtë arsye:</string>
<string name="account_terminated">Llogaria është mbyllur</string>
<string name="private_content">Kjo përmbajtje është private, kështu që nuk mund të luhet apo shkarkohet nga NewPipe.</string>
<string name="georestricted_content">Kjo përmbajtje nuk është e disponueshme në shtetin tuaj.</string>

View file

@ -619,7 +619,6 @@
<string name="description_select_enable">Омогући бирање текста унутар описа</string>
<string name="description_select_disable">Онемогући бирање текста унутар описа</string>
<string name="description_select_note">Сада можете изабрати текст унутар описа. Имајте на уму да страница може треперети и да се на линкове можда неће моћи кликнути док сте у режиму избора.</string>
<string name="service_provides_reason">%s даје овај разлог:</string>
<string name="account_terminated">Налог укинут</string>
<string name="feed_load_error_fast_unknown">Режим брзог фида не пружа више информација о овоме.</string>
<string name="feed_load_error_terminated">Налог аутора је укинут.

View file

@ -586,7 +586,6 @@
<string name="download_has_started">Hämtningen har startat</string>
<string name="radio">Radio</string>
<string name="paid_content">Detta innehåll är endast tillgängligt för användare som har betalat för det, så det kan inte strömmas eller hämtas av NewPipe.</string>
<string name="service_provides_reason">%s anger detta skäl:</string>
<string name="account_terminated">Kontot avslutat</string>
<string name="youtube_music_premium_content">Denna video är endast tillgänglig för YouTube Music Premium-medlemmar, så den kan inte strömmas eller hämtas av NewPipe.</string>
<string name="private_content">Detta innehåll är privat, så det kan inte strömmas eller hämtas av NewPipe.</string>

View file

@ -428,7 +428,6 @@
<string name="enable_queue_limit">பதிவிறக்க வரிசையை கட்டுப்படுத்துங்கள்</string>
<string name="enable_queue_limit_desc">ஒரு பதிவிறக்கம் ஒரே நேரத்தில் இயங்கும்</string>
<string name="no_app_to_open_intent">உங்கள் சாதனத்தில் எந்த பயன்பாடும் இதைத் திறக்க முடியாது</string>
<string name="service_provides_reason">%s இந்த காரணத்தை வழங்குகிறது:</string>
<string name="metadata_subchannel_avatars">துணை சேனல் அவதாரங்கள்</string>
<string name="metadata_avatars">அவதாரங்கள்</string>
<string name="progressive_load_interval_exoplayer_default">எக்சோப்ளேயர் இயல்புநிலை</string>

View file

@ -608,7 +608,6 @@
<string name="description_select_enable">ıklamadaki metni seçmeyi etkinleştir</string>
<string name="description_select_note">Artık, açıklamadaki metni seçebilirsiniz. Seçim kipindeyken sayfanın titreyebileceğini ve bağlantıların tıklanamayacağını unutmayın.</string>
<string name="open_website_license">Web sitesini aç</string>
<string name="service_provides_reason">%s şu nedeni sağlıyor:</string>
<string name="account_terminated">Hesap sonlandırıldı</string>
<string name="feed_load_error_fast_unknown">Hızlı besleme kipi bununla ilgili daha çok bilgi sağlamıyor.</string>
<string name="feed_load_error_terminated">Yazarın hesabı sonlandırılmış.

View file

@ -624,7 +624,6 @@
<string name="description_select_disable">Заборонити виділення тексту в описі</string>
<string name="description_select_enable">Дозволити виділяти текст в описі</string>
<string name="description_select_note">Тепер можна виділяти текст в описі. Зауважте, що сторінка може мигати і посилання можуть не працювати в режимі виділення.</string>
<string name="service_provides_reason">%s подає таку причину:</string>
<string name="feed_load_error_account_info">Неможливо завантажити стрічку для «%s».</string>
<string name="feed_load_error">Помилка завантаження стрічки</string>
<string name="feed_load_error_terminated">Обліковий запис автора припинено.

View file

@ -602,7 +602,6 @@
<string name="description_select_disable">Tắt chọn văn bản trong mô tả</string>
<string name="description_select_enable">Bật chọn văn bản trong mô tả</string>
<string name="description_select_note">Bây giờ bạn có thể chọn văn bản trong mô tả. Lưu ý rằng trang có thể nhấp nháy và các liên kết có thể không nhấn vào được trong khi ở chế độ chọn.</string>
<string name="service_provides_reason">%s cung cấp lý do này:</string>
<string name="account_terminated">Tài khoản đã bị chấm dứt</string>
<string name="feed_load_error_fast_unknown">Chế độ nạp nhanh không cung cấp thêm thông tin về điều này.</string>
<string name="feed_load_error_terminated">Tài khoản của tác giả đã bị chấm dứt.

View file

@ -599,7 +599,6 @@
<string name="description_select_enable">启用简介中的文本选择功能</string>
<string name="description_select_note">你现在可以选择简介中的文本,注意,在选择模式下,页面可能会闪烁,链接可能无法点击。</string>
<string name="open_website_license">打开网站</string>
<string name="service_provides_reason">%s 提供这个原因:</string>
<string name="account_terminated">账号被终止</string>
<string name="feed_load_error_fast_unknown">快速 Feed 模式不提供关于这个的更多信息。</string>
<string name="feed_load_error_terminated">作者账号已被终止。

View file

@ -486,7 +486,6 @@
<string name="content_not_supported">NewPipe 仲未支援到呢樣。
\n
\n希望未來會喺日後嘅版本支援啦。</string>
<string name="service_provides_reason">%s 話理由如下:</string>
<string name="no_appropriate_file_manager_message">搵唔到合適嘅檔案總管進行呢個動作。
\n請安裝一個檔案管理程式又或者試下喺下載設定度停用「%s」</string>
<string name="no_appropriate_file_manager_message_android_10">搵唔到合適嘅檔案總管進行呢個動作。

View file

@ -599,7 +599,6 @@
<string name="description_select_enable">啟用選取描述中的文字</string>
<string name="description_select_note">您現在可以選取描述中的文字了。請注意,在選取模式下,頁面可能會閃爍,連結也可能無法點擊。</string>
<string name="open_website_license">開啟網站</string>
<string name="service_provides_reason">%s 提供了這個理由:</string>
<string name="account_terminated">帳號已終止</string>
<string name="feed_load_error_fast_unknown">快速 feed 模式不會提供更多資訊。</string>
<string name="feed_load_error_terminated">作者的帳號已被終止。

View file

@ -257,6 +257,8 @@
<string name="restore_defaults">Restore defaults</string>
<string name="restore_defaults_confirmation">Do you want to restore defaults?</string>
<string name="permission_display_over_apps">Give permission to display over other apps</string>
<string name="permission_display_over_apps_message">In order to use the Popup Player, please select %1$s in the following Android settings menu and enable %2$s.</string>
<string name="permission_display_over_apps_permission_name">“Allow display over other apps”</string>
<!-- error activity -->
<string name="error_report_notification_title">NewPipe encountered an error, tap to report</string>
<string name="error_report_notification_toast">An error occurred, see the notification</string>
@ -333,6 +335,8 @@
<string name="pause">Pause</string>
<string name="create">Create</string>
<string name="delete">Delete</string>
<string name="delete_file">Delete file</string>
<string name="delete_entry">Delete entry</string>
<string name="checksum">Checksum</string>
<string name="dismiss">Dismiss</string>
<string name="rename">Rename</string>
@ -752,7 +756,7 @@
<string name="private_content">This content is private, so it cannot be streamed or downloaded by NewPipe.</string>
<string name="youtube_music_premium_content">This video is available only to YouTube Music Premium members, so it cannot be streamed or downloaded by NewPipe.</string>
<string name="account_terminated">Account terminated</string>
<string name="service_provides_reason">%s provides this reason:</string>
<string name="account_terminated_service_provides_reason">Account terminated\n\n%1$s provides this reason: %2$s</string>
<string name="paid_content">This content is only available to users who have paid, so it cannot be streamed or downloaded by NewPipe.</string>
<string name="featured">Featured</string>
<string name="radio">Radio</string>
@ -891,4 +895,10 @@
<string name="trending_podcasts">Trending podcasts</string>
<string name="trending_movies">Trending movies and shows</string>
<string name="trending_music">Trending music</string>
<string name="entry_deleted">Entry deleted</string>
<string name="player_http_403">HTTP error 403 received from server while playing, likely caused by streaming URL expiration or an IP ban</string>
<string name="player_http_invalid_status">HTTP error %1$s received from server while playing</string>
<string name="youtube_player_http_403">HTTP error 403 received from server while playing, likely caused by an IP ban or streaming URL deobfuscation issues</string>
<string name="sign_in_confirm_not_bot_error">%1$s refused to provide data, asking for a login to confirm the requester is not a bot.\n\nYour IP might have been temporarily banned by %1$s, you can wait some time or switch to a different IP (for example by turning on/off a VPN, or by switching from WiFi to mobile data).</string>
<string name="unsupported_content_in_country">This content is not available for the currently selected content country.\n\nChange your selection from \"Settings > Content > Default content country\".</string>
</resources>

View file

@ -1,21 +1 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#CD201F;}
.st1{fill:#FFFFFF;}
</style>
<g id="Alapkör">
<circle id="XMLID_23_" class="st0" cx="50" cy="50" r="50"/>
</g>
<g id="Elemek">
<path id="XMLID_19_" class="st1" d="M47,28.2c-9-5.3-15.3-9-15.3-9v61.7c0,0,30.4-18,52.3-30.9C72.1,43,57.7,34.5,47,28.2z"/>
</g>
<g id="Fedő">
<path id="XMLID_5_" class="st0" d="M48.4,40.1c-4.1-2.4-7-4.1-7-4.1V64c0,0,13.9-8.2,23.8-14C59.8,46.8,53.3,42.9,48.4,40.1z"/>
<rect id="XMLID_4_" x="41.4" y="55.6" class="st0" width="6.2" height="21"/>
</g>
<g id="Vonalak">
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 100 100"><circle cx="50" cy="50" r="50" style="fill:#cd201f"/><path d="M31.7 19.2v61.7l9.7-5.73V36l23.8 14-17.6 10.35V71.5L84 50" style="fill:#fff"/></svg>

Before

Width:  |  Height:  |  Size: 850 B

After

Width:  |  Height:  |  Size: 229 B

Before After
Before After

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 100 100"><path d="M0 50C0 15 15 0 50 0s50 15 50 50-15 50-50 50S0 85 0 50" style="fill:#cd201f"/><path d="M31.7 19.2v61.7l9.7-5.73V36l23.8 14-17.6 10.35V71.5L84 50" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 263 B

View file

@ -1,67 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 100 100"
style="enable-background:new 0 0 100 100;"
xml:space="preserve"
id="svg12"
sodipodi:docname="NP logo v2.svg"
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
id="metadata18"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
id="defs16">
</defs><sodipodi:namedview
pagecolor="#9f9f9f"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1366"
inkscape:window-height="740"
id="namedview14"
showgrid="false"
inkscape:zoom="1.668772"
inkscape:cx="63.900143"
inkscape:cy="31.160181"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg12" />
<style
type="text/css"
id="style2">
.st0{fill:#CD201F;}
.st1{fill:#FFFFFF;}
</style>
<path
style="fill:#ffffff;stroke-width:1.2782383"
d="m 23.90906,10.210574 v 78.8688 c 0,0 7.70042,-4.556206 12.400409,-7.337388 V 67.476647 56.738946 31.685975 c 0,0 3.706891,2.172506 8.947669,5.240278 6.263367,3.579067 14.570418,8.564696 21.472905,12.655059 -9.358315,5.482649 -16.799873,9.875994 -22.496496,13.23426 V 77.053449 C 57.973932,68.927101 75.175728,58.76222 90.76192,49.581312 75.550885,40.633643 57.144253,29.76762 43.467104,21.714718 31.962959,14.940056 23.90906,10.210574 23.90906,10.210574 Z"
id="XMLID_19_"
inkscape:connector-curvature="0" />
<g
id="Vonalak">
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 100 100"><path d="M31.7 19.2v61.7l9.7-5.73V36l23.8 14-17.6 10.35V71.5L84 50" style="fill:#fff"/></svg>

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 176 B

Before After
Before After

View file

@ -58,7 +58,7 @@ teamnewpipe-nanojson = "e9d656ddb49a412a5a0a5d5ef20ca7ef09549996"
# the corresponding commit hash, since JitPack sometimes deletes artifacts.
# If theres already a git hash, just add more of it to the end (or remove a letter)
# to cause jitpack to regenerate the artifact.
teamnewpipe-newpipe-extractor = "v0.24.8"
teamnewpipe-newpipe-extractor = "0023b22095a2d62a60cdfc87f4b5cd85c8b266c3"
webkit = "1.9.0"
work = "2.10.0"