mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2025-10-03 01:39:38 +02:00
Merge branch 'refactor' into History-Compose
This commit is contained in:
commit
d7953d620c
100 changed files with 659 additions and 572 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.throwable !is ContentNotAvailableException &&
|
||||
errorInfo.throwable !is ContentNotSupportedException
|
||||
) {
|
||||
// show retry button only for content which is not unavailable or unsupported
|
||||
if (errorInfo.isRetryable) {
|
||||
errorRetryButton.isVisible = retryShouldBeShown
|
||||
}
|
||||
showAndSetOpenInBrowserButtonAction(errorInfo)
|
||||
|
||||
if (errorInfo.openInBrowserUrl != null) {
|
||||
errorOpenInBrowserButton.isVisible = true
|
||||
errorOpenInBrowserButton.setOnClickListener {
|
||||
ShareUtils.openUrlInBrowser(context, errorInfo.openInBrowserUrl)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
@ -200,10 +200,7 @@ public class ChannelFragment extends BaseStateFragment<ChannelInfo>
|
|||
protected void initViews(final View rootView, final Bundle savedInstanceState) {
|
||||
super.initViews(rootView, savedInstanceState);
|
||||
|
||||
EmptyStateUtil.setEmptyStateComposable(
|
||||
binding.emptyStateView,
|
||||
EmptyStateSpec.Companion.getContentNotSupported()
|
||||
);
|
||||
setEmptyStateComposable(binding.emptyStateView, EmptyStateSpec.ContentNotSupported);
|
||||
|
||||
tabAdapter = new TabAdapter(getChildFragmentManager());
|
||||
binding.viewPager.setAdapter(tabAdapter);
|
||||
|
@ -583,7 +580,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
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.fragment.app.FragmentManager;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
@ -125,10 +126,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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,30 +121,25 @@ private fun CommentRepliesDialog(
|
|||
|
||||
if (comments.itemCount == 0) {
|
||||
item {
|
||||
val refresh = comments.loadState.refresh
|
||||
if (refresh is LoadState.Loading) {
|
||||
when (val refresh = comments.loadState.refresh) {
|
||||
is LoadState.Loading -> {
|
||||
LoadingIndicator(modifier = Modifier.padding(top = 8.dp))
|
||||
} else if (refresh is LoadState.Error) {
|
||||
}
|
||||
else -> {
|
||||
// TODO use error panel instead
|
||||
EmptyStateComposable(
|
||||
spec = EmptyStateSpec.DisabledComments.copy(
|
||||
descriptionText = {
|
||||
stringResource(R.string.error_unable_to_load_comments)
|
||||
},
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 128.dp)
|
||||
)
|
||||
spec = if (refresh is LoadState.Error) {
|
||||
EmptyStateSpec.ErrorLoadingComments
|
||||
} else {
|
||||
EmptyStateComposable(
|
||||
spec = EmptyStateSpec.NoComments,
|
||||
EmptyStateSpec.NoComments
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.heightIn(min = 128.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
items(comments.itemCount) {
|
||||
Comment(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
}
|
||||
|
|
|
@ -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,6 +115,7 @@ public final class PermissionHelper {
|
|||
@RequiresApi(api = Build.VERSION_CODES.M)
|
||||
public static boolean checkSystemAlertWindowPermission(final Context context) {
|
||||
if (!Settings.canDrawOverlays(context)) {
|
||||
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);
|
||||
|
@ -121,6 +124,38 @@ public final class PermissionHelper {
|
|||
} catch (final ActivityNotFoundException ignored) {
|
||||
}
|
||||
return false;
|
||||
// from Android R the ACTION_MANAGE_OVERLAY_PERMISSION will only point to the menu,
|
||||
// so let’s 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 don’t need the package name here, since it won’t 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;
|
||||
}
|
||||
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,9 +274,11 @@ public class DownloadManager {
|
|||
mFinishedMissionStore.deleteMission(mission);
|
||||
}
|
||||
|
||||
if (alsoDeleteFile) {
|
||||
mission.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void forgetMission(StoredFileHelper storage) {
|
||||
synchronized (this) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -638,7 +638,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>
|
||||
|
|
|
@ -646,7 +646,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">حساب منشئ المحتوى قد تم إنهائه.
|
||||
|
|
|
@ -497,7 +497,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">Açıqlamadakı mətni seçməyi qeyri-aktiv et</string>
|
||||
<string name="metadata_category">Kateqoriya</string>
|
||||
|
|
|
@ -702,7 +702,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>
|
||||
|
|
|
@ -414,7 +414,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>
|
||||
|
|
|
@ -538,7 +538,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>
|
||||
|
|
|
@ -610,7 +610,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.
|
||||
|
|
|
@ -593,7 +593,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>
|
||||
|
|
|
@ -617,7 +617,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.
|
||||
|
|
|
@ -551,7 +551,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>
|
||||
|
|
|
@ -625,7 +625,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>
|
||||
|
|
|
@ -606,7 +606,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">Ο λογαριασμός του δημιουργού έχει διαγραφεί.
|
||||
|
|
|
@ -502,7 +502,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>
|
||||
|
|
|
@ -609,7 +609,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>
|
||||
|
|
|
@ -560,7 +560,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>
|
||||
|
|
|
@ -613,7 +613,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>
|
||||
|
|
|
@ -623,7 +623,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>
|
||||
|
|
|
@ -590,7 +590,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>
|
||||
|
|
|
@ -618,7 +618,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 à l’inté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 n’est 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>
|
||||
|
|
|
@ -552,7 +552,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>
|
||||
|
|
|
@ -630,7 +630,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>
|
||||
|
|
|
@ -652,7 +652,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>
|
||||
|
|
|
@ -637,7 +637,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">
|
||||
|
|
|
@ -535,7 +535,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>
|
||||
|
|
|
@ -597,7 +597,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.
|
||||
|
|
|
@ -571,7 +571,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>
|
||||
|
|
|
@ -617,7 +617,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.
|
||||
|
|
|
@ -610,7 +610,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>
|
||||
|
|
|
@ -542,7 +542,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>
|
||||
|
|
|
@ -641,7 +641,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>
|
||||
|
|
|
@ -621,7 +621,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.
|
||||
|
|
|
@ -627,7 +627,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>
|
||||
|
|
|
@ -429,7 +429,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>
|
||||
|
|
|
@ -610,7 +610,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">സൃഷ്ടാവിന്റെ അക്കൗണ്ട് ഇല്ലാതായിരിക്കുന്നു.
|
||||
|
|
|
@ -598,7 +598,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>
|
||||
|
|
|
@ -610,7 +610,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.
|
||||
|
|
|
@ -623,7 +623,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>
|
||||
|
|
|
@ -484,7 +484,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>
|
||||
|
|
|
@ -453,7 +453,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>
|
||||
|
|
|
@ -621,7 +621,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.
|
||||
|
|
|
@ -617,7 +617,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.
|
||||
|
|
|
@ -621,7 +621,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.
|
||||
|
|
|
@ -603,7 +603,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.
|
||||
|
|
|
@ -622,7 +622,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.
|
||||
|
|
|
@ -631,7 +631,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>
|
||||
|
|
|
@ -618,7 +618,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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -608,7 +608,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.
|
||||
|
|
|
@ -616,7 +616,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ý.
|
||||
|
|
|
@ -606,7 +606,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.
|
||||
|
|
|
@ -596,7 +596,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>
|
||||
|
|
|
@ -617,7 +617,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">Налог аутора је укинут.
|
||||
|
|
|
@ -584,7 +584,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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -606,7 +606,6 @@
|
|||
<string name="description_select_enable">Açı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ış.
|
||||
|
|
|
@ -622,7 +622,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">Обліковий запис автора припинено.
|
||||
|
|
|
@ -600,7 +600,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.
|
||||
|
|
|
@ -597,7 +597,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">作者账号已被终止。
|
||||
|
|
|
@ -484,7 +484,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">搵唔到合適嘅檔案總管進行呢個動作。
|
||||
|
|
|
@ -597,7 +597,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">作者的帳號已被終止。
|
||||
|
|
|
@ -258,6 +258,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>
|
||||
|
@ -334,6 +336,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>
|
||||
|
@ -753,7 +757,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>
|
||||
|
@ -894,4 +898,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>
|
||||
|
|
|
@ -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 |
1
assets/newpipe_squircle.svg
Normal file
1
assets/newpipe_squircle.svg
Normal 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 |
|
@ -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 |
|
@ -58,7 +58,7 @@ teamnewpipe-nanojson = "e9d656ddb49a412a5a0a5d5ef20ca7ef09549996"
|
|||
# the corresponding commit hash, since JitPack sometimes deletes artifacts.
|
||||
# If there’s 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"
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue