mirror of
https://github.com/TeamNewPipe/NewPipe.git
synced 2024-11-22 02:53:09 +01:00
Merge branch 'dev' into bumpSomeLibraries
This commit is contained in:
commit
2e7503ff78
@ -220,6 +220,7 @@ dependencies {
|
||||
// https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01'
|
||||
implementation 'androidx.webkit:webkit:1.4.0'
|
||||
implementation 'androidx.work:work-runtime:2.7.1'
|
||||
implementation 'com.google.android.material:material:1.5.0'
|
||||
|
||||
/** Third-party libraries **/
|
||||
|
@ -381,9 +381,6 @@
|
||||
<service
|
||||
android:name=".RouterActivity$FetcherService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".CheckForNewAppVersion"
|
||||
android:exported="false" />
|
||||
|
||||
<!-- opting out of sending metrics to Google in Android System WebView -->
|
||||
<meta-data android:name="android.webkit.WebView.MetricsOptOut" android:value="true" />
|
||||
|
@ -1,264 +0,0 @@
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Application;
|
||||
import android.app.IntentService;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
import androidx.core.content.pm.PackageInfoCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import com.grack.nanojson.JsonObject;
|
||||
import com.grack.nanojson.JsonParser;
|
||||
import com.grack.nanojson.JsonParserException;
|
||||
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
import org.schabi.newpipe.error.ErrorUtil;
|
||||
import org.schabi.newpipe.error.UserAction;
|
||||
import org.schabi.newpipe.extractor.downloader.Response;
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.List;
|
||||
|
||||
public final class CheckForNewAppVersion extends IntentService {
|
||||
public CheckForNewAppVersion() {
|
||||
super("CheckForNewAppVersion");
|
||||
}
|
||||
|
||||
private static final boolean DEBUG = MainActivity.DEBUG;
|
||||
private static final String TAG = CheckForNewAppVersion.class.getSimpleName();
|
||||
|
||||
// Public key of the certificate that is used in NewPipe release versions
|
||||
private static final String RELEASE_CERT_PUBLIC_KEY_SHA1
|
||||
= "B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15";
|
||||
private static final String NEWPIPE_API_URL = "https://newpipe.net/api/data.json";
|
||||
|
||||
/**
|
||||
* Method to get the APK's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
|
||||
*
|
||||
* @param application The application
|
||||
* @return String with the APK's SHA1 fingerprint in hexadecimal
|
||||
*/
|
||||
@NonNull
|
||||
private static String getCertificateSHA1Fingerprint(@NonNull final Application application) {
|
||||
final List<Signature> signatures;
|
||||
try {
|
||||
signatures = PackageInfoCompat.getSignatures(application.getPackageManager(),
|
||||
application.getPackageName());
|
||||
} catch (final PackageManager.NameNotFoundException e) {
|
||||
ErrorUtil.createNotification(application, new ErrorInfo(e,
|
||||
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not find package info"));
|
||||
return "";
|
||||
}
|
||||
if (signatures.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final X509Certificate c;
|
||||
try {
|
||||
final byte[] cert = signatures.get(0).toByteArray();
|
||||
final InputStream input = new ByteArrayInputStream(cert);
|
||||
final CertificateFactory cf = CertificateFactory.getInstance("X509");
|
||||
c = (X509Certificate) cf.generateCertificate(input);
|
||||
} catch (final CertificateException e) {
|
||||
ErrorUtil.createNotification(application, new ErrorInfo(e,
|
||||
UserAction.CHECK_FOR_NEW_APP_VERSION, "Certificate error"));
|
||||
return "";
|
||||
}
|
||||
|
||||
try {
|
||||
final MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
final byte[] publicKey = md.digest(c.getEncoded());
|
||||
return byte2HexFormatted(publicKey);
|
||||
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
||||
ErrorUtil.createNotification(application, new ErrorInfo(e,
|
||||
UserAction.CHECK_FOR_NEW_APP_VERSION, "Could not retrieve SHA1 key"));
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static String byte2HexFormatted(final byte[] arr) {
|
||||
final StringBuilder str = new StringBuilder(arr.length * 2);
|
||||
|
||||
for (int i = 0; i < arr.length; i++) {
|
||||
String h = Integer.toHexString(arr[i]);
|
||||
final int l = h.length();
|
||||
if (l == 1) {
|
||||
h = "0" + h;
|
||||
}
|
||||
if (l > 2) {
|
||||
h = h.substring(l - 2, l);
|
||||
}
|
||||
str.append(h.toUpperCase());
|
||||
if (i < (arr.length - 1)) {
|
||||
str.append(':');
|
||||
}
|
||||
}
|
||||
return str.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to compare the current and latest available app version.
|
||||
* If a newer version is available, we show the update notification.
|
||||
*
|
||||
* @param application The application
|
||||
* @param versionName Name of new version
|
||||
* @param apkLocationUrl Url with the new apk
|
||||
* @param versionCode Code of new version
|
||||
*/
|
||||
private static void compareAppVersionAndShowNotification(@NonNull final Application application,
|
||||
final String versionName,
|
||||
final String apkLocationUrl,
|
||||
final int versionCode) {
|
||||
if (BuildConfig.VERSION_CODE >= versionCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
// A pending intent to open the apk location url in the browser.
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
final PendingIntent pendingIntent
|
||||
= PendingIntent.getActivity(application, 0, intent, 0);
|
||||
|
||||
final String channelId = application
|
||||
.getString(R.string.app_update_notification_channel_id);
|
||||
final NotificationCompat.Builder notificationBuilder
|
||||
= new NotificationCompat.Builder(application, channelId)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(application
|
||||
.getString(R.string.app_update_notification_content_title))
|
||||
.setContentText(application
|
||||
.getString(R.string.app_update_notification_content_text)
|
||||
+ " " + versionName);
|
||||
|
||||
final NotificationManagerCompat notificationManager
|
||||
= NotificationManagerCompat.from(application);
|
||||
notificationManager.notify(2000, notificationBuilder.build());
|
||||
}
|
||||
|
||||
public static boolean isReleaseApk(@NonNull final App app) {
|
||||
return getCertificateSHA1Fingerprint(app).equals(RELEASE_CERT_PUBLIC_KEY_SHA1);
|
||||
}
|
||||
|
||||
private void checkNewVersion() throws IOException, ReCaptchaException {
|
||||
final App app = App.getApp();
|
||||
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||
final NewVersionManager manager = new NewVersionManager();
|
||||
|
||||
// Check if the current apk is a github one or not.
|
||||
if (!isReleaseApk(app)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the last request has happened a certain time ago
|
||||
// to reduce the number of API requests.
|
||||
final long expiry = prefs.getLong(app.getString(R.string.update_expiry_key), 0);
|
||||
if (!manager.isExpired(expiry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make a network request to get latest NewPipe data.
|
||||
final Response response = DownloaderImpl.getInstance().get(NEWPIPE_API_URL);
|
||||
handleResponse(response, manager, prefs, app);
|
||||
}
|
||||
|
||||
private void handleResponse(@NonNull final Response response,
|
||||
@NonNull final NewVersionManager manager,
|
||||
@NonNull final SharedPreferences prefs,
|
||||
@NonNull final App app) {
|
||||
try {
|
||||
// Store a timestamp which needs to be exceeded,
|
||||
// before a new request to the API is made.
|
||||
final long newExpiry = manager
|
||||
.coerceExpiry(response.getHeader("expires"));
|
||||
prefs.edit()
|
||||
.putLong(app.getString(R.string.update_expiry_key), newExpiry)
|
||||
.apply();
|
||||
} catch (final Exception e) {
|
||||
if (DEBUG) {
|
||||
Log.w(TAG, "Could not extract and save new expiry date", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the json from the response.
|
||||
try {
|
||||
|
||||
final JsonObject githubStableObject = JsonParser.object()
|
||||
.from(response.responseBody()).getObject("flavors")
|
||||
.getObject("github").getObject("stable");
|
||||
|
||||
final String versionName = githubStableObject
|
||||
.getString("version");
|
||||
final int versionCode = githubStableObject
|
||||
.getInt("version_code");
|
||||
final String apkLocationUrl = githubStableObject
|
||||
.getString("apk");
|
||||
|
||||
compareAppVersionAndShowNotification(app, versionName,
|
||||
apkLocationUrl, versionCode);
|
||||
} catch (final JsonParserException e) {
|
||||
// Most likely something is wrong in data received from NEWPIPE_API_URL.
|
||||
// Do not alarm user and fail silently.
|
||||
if (DEBUG) {
|
||||
Log.w(TAG, "Could not get NewPipe API: invalid json", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a new service which
|
||||
* checks if all conditions for performing a version check are met,
|
||||
* fetches the API endpoint {@link #NEWPIPE_API_URL} containing info
|
||||
* about the latest NewPipe version
|
||||
* and displays a notification about ana available update.
|
||||
* <br>
|
||||
* Following conditions need to be met, before data is request from the server:
|
||||
* <ul>
|
||||
* <li> The app is signed with the correct signing key (by TeamNewPipe / schabi).
|
||||
* If the signing key differs from the one used upstream, the update cannot be installed.</li>
|
||||
* <li>The user enabled searching for and notifying about updates in the settings.</li>
|
||||
* <li>The app did not recently check for updates.
|
||||
* We do not want to make unnecessary connections and DOS our servers.</li>
|
||||
* </ul>
|
||||
* <b>Must not be executed</b> when the app is in background.
|
||||
*/
|
||||
public static void startNewVersionCheckService() {
|
||||
final Intent intent = new Intent(App.getApp().getApplicationContext(),
|
||||
CheckForNewAppVersion.class);
|
||||
App.getApp().startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(@Nullable final Intent intent) {
|
||||
try {
|
||||
checkNewVersion();
|
||||
} catch (final IOException e) {
|
||||
Log.w(TAG, "Could not fetch NewPipe API: probably network problem", e);
|
||||
} catch (final ReCaptchaException e) {
|
||||
Log.e(TAG, "ReCaptchaException should never happen here.", e);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -20,7 +20,6 @@
|
||||
|
||||
package org.schabi.newpipe;
|
||||
|
||||
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
@ -174,10 +173,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app);
|
||||
|
||||
if (prefs.getBoolean(app.getString(R.string.update_app_key), true)) {
|
||||
// Start the service which is checking all conditions
|
||||
// Start the worker which is checking all conditions
|
||||
// and eventually searching for a new version.
|
||||
// The service searching for a new NewPipe version must not be started in background.
|
||||
startNewVersionCheckService();
|
||||
NewVersionWorker.enqueueNewVersionCheckingWork(app);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,28 +0,0 @@
|
||||
package org.schabi.newpipe
|
||||
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
class NewVersionManager {
|
||||
|
||||
fun isExpired(expiry: Long): Boolean {
|
||||
return Instant.ofEpochSecond(expiry).isBefore(Instant.now())
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerce expiry date time in between 6 hours and 72 hours from now
|
||||
*
|
||||
* @return Epoch second of expiry date time
|
||||
*/
|
||||
fun coerceExpiry(expiryString: String?): Long {
|
||||
val now = ZonedDateTime.now()
|
||||
return expiryString?.let {
|
||||
|
||||
var expiry = ZonedDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(expiryString))
|
||||
expiry = maxOf(expiry, now.plusHours(6))
|
||||
expiry = minOf(expiry, now.plusHours(72))
|
||||
expiry.toEpochSecond()
|
||||
} ?: now.plusHours(6).toEpochSecond()
|
||||
}
|
||||
}
|
163
app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt
Normal file
163
app/src/main/java/org/schabi/newpipe/NewVersionWorker.kt
Normal file
@ -0,0 +1,163 @@
|
||||
package org.schabi.newpipe
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.util.Log
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkRequest
|
||||
import androidx.work.Worker
|
||||
import androidx.work.WorkerParameters
|
||||
import com.grack.nanojson.JsonParser
|
||||
import com.grack.nanojson.JsonParserException
|
||||
import org.schabi.newpipe.extractor.downloader.Response
|
||||
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException
|
||||
import org.schabi.newpipe.util.ReleaseVersionUtil.coerceUpdateCheckExpiry
|
||||
import org.schabi.newpipe.util.ReleaseVersionUtil.isLastUpdateCheckExpired
|
||||
import org.schabi.newpipe.util.ReleaseVersionUtil.isReleaseApk
|
||||
import java.io.IOException
|
||||
|
||||
class NewVersionWorker(
|
||||
context: Context,
|
||||
workerParams: WorkerParameters
|
||||
) : Worker(context, workerParams) {
|
||||
|
||||
/**
|
||||
* Method to compare the current and latest available app version.
|
||||
* If a newer version is available, we show the update notification.
|
||||
*
|
||||
* @param versionName Name of new version
|
||||
* @param apkLocationUrl Url with the new apk
|
||||
* @param versionCode Code of new version
|
||||
*/
|
||||
private fun compareAppVersionAndShowNotification(
|
||||
versionName: String,
|
||||
apkLocationUrl: String?,
|
||||
versionCode: Int
|
||||
) {
|
||||
if (BuildConfig.VERSION_CODE >= versionCode) {
|
||||
return
|
||||
}
|
||||
val app = App.getApp()
|
||||
|
||||
// A pending intent to open the apk location url in the browser.
|
||||
val intent = Intent(Intent.ACTION_VIEW, apkLocationUrl?.toUri())
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
val pendingIntent = PendingIntent.getActivity(app, 0, intent, 0)
|
||||
val channelId = app.getString(R.string.app_update_notification_channel_id)
|
||||
val notificationBuilder = NotificationCompat.Builder(app, channelId)
|
||||
.setSmallIcon(R.drawable.ic_newpipe_update)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(app.getString(R.string.app_update_notification_content_title))
|
||||
.setContentText(
|
||||
app.getString(R.string.app_update_notification_content_text) +
|
||||
" " + versionName
|
||||
)
|
||||
val notificationManager = NotificationManagerCompat.from(app)
|
||||
notificationManager.notify(2000, notificationBuilder.build())
|
||||
}
|
||||
|
||||
@Throws(IOException::class, ReCaptchaException::class)
|
||||
private fun checkNewVersion() {
|
||||
// Check if the current apk is a github one or not.
|
||||
if (!isReleaseApk()) {
|
||||
return
|
||||
}
|
||||
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
// Check if the last request has happened a certain time ago
|
||||
// to reduce the number of API requests.
|
||||
val expiry = prefs.getLong(applicationContext.getString(R.string.update_expiry_key), 0)
|
||||
if (!isLastUpdateCheckExpired(expiry)) {
|
||||
return
|
||||
}
|
||||
|
||||
// Make a network request to get latest NewPipe data.
|
||||
val response = DownloaderImpl.getInstance().get(NEWPIPE_API_URL)
|
||||
handleResponse(response)
|
||||
}
|
||||
|
||||
private fun handleResponse(response: Response) {
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||
try {
|
||||
// Store a timestamp which needs to be exceeded,
|
||||
// before a new request to the API is made.
|
||||
val newExpiry = coerceUpdateCheckExpiry(response.getHeader("expires"))
|
||||
prefs.edit {
|
||||
putLong(applicationContext.getString(R.string.update_expiry_key), newExpiry)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
if (DEBUG) {
|
||||
Log.w(TAG, "Could not extract and save new expiry date", e)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the json from the response.
|
||||
try {
|
||||
val githubStableObject = JsonParser.`object`()
|
||||
.from(response.responseBody()).getObject("flavors")
|
||||
.getObject("github").getObject("stable")
|
||||
|
||||
val versionName = githubStableObject.getString("version")
|
||||
val versionCode = githubStableObject.getInt("version_code")
|
||||
val apkLocationUrl = githubStableObject.getString("apk")
|
||||
compareAppVersionAndShowNotification(versionName, apkLocationUrl, versionCode)
|
||||
} catch (e: JsonParserException) {
|
||||
// Most likely something is wrong in data received from NEWPIPE_API_URL.
|
||||
// Do not alarm user and fail silently.
|
||||
if (DEBUG) {
|
||||
Log.w(TAG, "Could not get NewPipe API: invalid json", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun doWork(): Result {
|
||||
try {
|
||||
checkNewVersion()
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Could not fetch NewPipe API: probably network problem", e)
|
||||
return Result.failure()
|
||||
} catch (e: ReCaptchaException) {
|
||||
Log.e(TAG, "ReCaptchaException should never happen here.", e)
|
||||
return Result.failure()
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DEBUG = MainActivity.DEBUG
|
||||
private val TAG = NewVersionWorker::class.java.simpleName
|
||||
private const val NEWPIPE_API_URL = "https://newpipe.net/api/data.json"
|
||||
|
||||
/**
|
||||
* Start a new worker which
|
||||
* checks if all conditions for performing a version check are met,
|
||||
* fetches the API endpoint [.NEWPIPE_API_URL] containing info
|
||||
* about the latest NewPipe version
|
||||
* and displays a notification about ana available update.
|
||||
* <br></br>
|
||||
* Following conditions need to be met, before data is request from the server:
|
||||
*
|
||||
* * The app is signed with the correct signing key (by TeamNewPipe / schabi).
|
||||
* If the signing key differs from the one used upstream, the update cannot be installed.
|
||||
* * The user enabled searching for and notifying about updates in the settings.
|
||||
* * The app did not recently check for updates.
|
||||
* We do not want to make unnecessary connections and DOS our servers.
|
||||
*
|
||||
*/
|
||||
@JvmStatic
|
||||
fun enqueueNewVersionCheckingWork(context: Context) {
|
||||
val workRequest: WorkRequest =
|
||||
OneTimeWorkRequest.Builder(NewVersionWorker::class.java).build()
|
||||
WorkManager.getInstance(context).enqueue(workRequest)
|
||||
}
|
||||
}
|
||||
}
|
@ -350,7 +350,7 @@ public class LocalPlaylistFragment extends BaseLocalListFragment<List<PlaylistSt
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setMessage(R.string.remove_watched_popup_warning)
|
||||
.setTitle(R.string.remove_watched_popup_title)
|
||||
.setPositiveButton(R.string.yes,
|
||||
.setPositiveButton(R.string.ok,
|
||||
(DialogInterface d, int id) -> removeWatchedStreams(false))
|
||||
.setNeutralButton(
|
||||
R.string.remove_watched_popup_yes_and_partially_watched_videos,
|
||||
|
@ -1,5 +1,9 @@
|
||||
package org.schabi.newpipe.player;
|
||||
|
||||
import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@ -23,11 +27,9 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding;
|
||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||
import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||
import org.schabi.newpipe.player.event.PlayerEventListener;
|
||||
import org.schabi.newpipe.player.helper.PlaybackParameterDialog;
|
||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||
@ -42,13 +44,6 @@ import org.schabi.newpipe.util.PermissionHelper;
|
||||
import org.schabi.newpipe.util.ServiceHelper;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu;
|
||||
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
|
||||
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
|
||||
|
||||
public final class PlayQueueActivity extends AppCompatActivity
|
||||
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener,
|
||||
View.OnClickListener, PlaybackParameterDialog.Callback {
|
||||
@ -129,7 +124,7 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
NavigationHelper.openSettings(this);
|
||||
return true;
|
||||
case R.id.action_append_playlist:
|
||||
appendAllToPlaylist();
|
||||
player.onAddToPlaylistClicked(getSupportFragmentManager());
|
||||
return true;
|
||||
case R.id.action_playback_speed:
|
||||
openPlaybackParameterDialog();
|
||||
@ -443,24 +438,6 @@ public final class PlayQueueActivity extends AppCompatActivity
|
||||
seeking = false;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Playlist append
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
private void appendAllToPlaylist() {
|
||||
if (player != null && player.getPlayQueue() != null) {
|
||||
openPlaylistAppendDialog(player.getPlayQueue().getStreams());
|
||||
}
|
||||
}
|
||||
|
||||
private void openPlaylistAppendDialog(final List<PlayQueueItem> playQueueItems) {
|
||||
PlaylistDialog.createCorrespondingDialog(
|
||||
getApplicationContext(),
|
||||
playQueueItems.stream().map(StreamEntity::new).collect(Collectors.toList()),
|
||||
dialog -> dialog.show(getSupportFragmentManager(), TAG)
|
||||
);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
// Binding Service Listener
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -105,6 +105,7 @@ import androidx.core.graphics.Insets;
|
||||
import androidx.core.view.GestureDetectorCompat;
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.core.view.WindowInsetsCompat;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.preference.PreferenceManager;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
@ -138,6 +139,7 @@ import com.squareup.picasso.Target;
|
||||
import org.schabi.newpipe.DownloaderImpl;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.database.stream.model.StreamEntity;
|
||||
import org.schabi.newpipe.databinding.PlayerBinding;
|
||||
import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding;
|
||||
import org.schabi.newpipe.error.ErrorInfo;
|
||||
@ -152,6 +154,7 @@ import org.schabi.newpipe.fragments.OnScrollBelowItemsListener;
|
||||
import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
|
||||
import org.schabi.newpipe.info_list.StreamSegmentAdapter;
|
||||
import org.schabi.newpipe.ktx.AnimationType;
|
||||
import org.schabi.newpipe.local.dialog.PlaylistDialog;
|
||||
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
||||
import org.schabi.newpipe.player.MainPlayer.PlayerType;
|
||||
import org.schabi.newpipe.player.event.DisplayPortion;
|
||||
@ -197,6 +200,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
|
||||
@ -541,6 +545,7 @@ public final class Player implements
|
||||
binding.segmentsButton.setOnClickListener(this);
|
||||
binding.repeatButton.setOnClickListener(this);
|
||||
binding.shuffleButton.setOnClickListener(this);
|
||||
binding.addToPlaylistButton.setOnClickListener(this);
|
||||
|
||||
binding.playPauseButton.setOnClickListener(this);
|
||||
binding.playPreviousButton.setOnClickListener(this);
|
||||
@ -2389,6 +2394,32 @@ public final class Player implements
|
||||
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Playlist append
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
//region Playlist append
|
||||
|
||||
public void onAddToPlaylistClicked(@NonNull final FragmentManager fragmentManager) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onAddToPlaylistClicked() called");
|
||||
}
|
||||
|
||||
if (getPlayQueue() != null) {
|
||||
PlaylistDialog.createCorrespondingDialog(
|
||||
getContext(),
|
||||
getPlayQueue()
|
||||
.getStreams()
|
||||
.stream()
|
||||
.map(StreamEntity::new)
|
||||
.collect(Collectors.toList()),
|
||||
dialog -> dialog.show(fragmentManager, TAG)
|
||||
);
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
|
||||
|
||||
/*//////////////////////////////////////////////////////////////////////////
|
||||
// Mute / Unmute
|
||||
//////////////////////////////////////////////////////////////////////////*/
|
||||
@ -3131,6 +3162,7 @@ public final class Player implements
|
||||
binding.itemsListHeaderDuration.setVisibility(View.VISIBLE);
|
||||
binding.shuffleButton.setVisibility(View.VISIBLE);
|
||||
binding.repeatButton.setVisibility(View.VISIBLE);
|
||||
binding.addToPlaylistButton.setVisibility(View.VISIBLE);
|
||||
|
||||
hideControls(0, 0);
|
||||
binding.itemsListPanel.requestFocus();
|
||||
@ -3168,6 +3200,7 @@ public final class Player implements
|
||||
binding.itemsListHeaderDuration.setVisibility(View.GONE);
|
||||
binding.shuffleButton.setVisibility(View.GONE);
|
||||
binding.repeatButton.setVisibility(View.GONE);
|
||||
binding.addToPlaylistButton.setVisibility(View.GONE);
|
||||
|
||||
hideControls(0, 0);
|
||||
binding.itemsListPanel.requestFocus();
|
||||
@ -3196,6 +3229,7 @@ public final class Player implements
|
||||
|
||||
binding.shuffleButton.setVisibility(View.GONE);
|
||||
binding.repeatButton.setVisibility(View.GONE);
|
||||
binding.addToPlaylistButton.setVisibility(View.GONE);
|
||||
binding.itemsListClose.setOnClickListener(view -> closeItemsList());
|
||||
}
|
||||
|
||||
@ -3733,6 +3767,11 @@ public final class Player implements
|
||||
} else if (v.getId() == binding.shuffleButton.getId()) {
|
||||
onShuffleClicked();
|
||||
return;
|
||||
} else if (v.getId() == binding.addToPlaylistButton.getId()) {
|
||||
if (getParentActivity() != null) {
|
||||
onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager());
|
||||
}
|
||||
return;
|
||||
} else if (v.getId() == binding.moreOptionsButton.getId()) {
|
||||
onMoreOptionsClicked();
|
||||
} else if (v.getId() == binding.share.getId()) {
|
||||
@ -3799,6 +3838,10 @@ public final class Player implements
|
||||
case KeyEvent.KEYCODE_SPACE:
|
||||
if (isFullscreen) {
|
||||
playPause();
|
||||
if (isPlaying()) {
|
||||
hideControls(0, 0);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case KeyEvent.KEYCODE_BACK:
|
||||
|
@ -88,6 +88,8 @@ public class PlayerMediaSession implements MediaSessionCallback {
|
||||
@Override
|
||||
public void play() {
|
||||
player.play();
|
||||
// hide the player controls even if the play command came from the media session
|
||||
player.hideControls(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -7,10 +7,9 @@ import android.view.MenuItem;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.CheckForNewAppVersion;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.util.ReleaseVersionUtil;
|
||||
|
||||
public class MainSettingsFragment extends BasePreferenceFragment {
|
||||
public static final boolean DEBUG = MainActivity.DEBUG;
|
||||
@ -24,7 +23,7 @@ public class MainSettingsFragment extends BasePreferenceFragment {
|
||||
setHasOptionsMenu(true); // Otherwise onCreateOptionsMenu is not called
|
||||
|
||||
// Check if the app is updatable
|
||||
if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) {
|
||||
if (!ReleaseVersionUtil.isReleaseApk()) {
|
||||
getPreferenceScreen().removePreference(
|
||||
findPreference(getString(R.string.update_pref_screen_key)));
|
||||
|
||||
|
@ -191,7 +191,7 @@ public class PeertubeInstanceListFragment extends Fragment {
|
||||
.setTitle(R.string.restore_defaults)
|
||||
.setMessage(R.string.restore_defaults_confirmation)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||
sharedPreferences.edit().remove(savedInstanceListKey).apply();
|
||||
selectInstance(PeertubeInstance.defaultInstance);
|
||||
updateInstanceList();
|
||||
|
@ -23,8 +23,6 @@ import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import com.jakewharton.rxbinding4.widget.RxTextView;
|
||||
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.CheckForNewAppVersion;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.databinding.SettingsLayoutBinding;
|
||||
@ -37,6 +35,7 @@ import org.schabi.newpipe.settings.preferencesearch.PreferenceSearchResultListen
|
||||
import org.schabi.newpipe.settings.preferencesearch.PreferenceSearcher;
|
||||
import org.schabi.newpipe.util.DeviceUtils;
|
||||
import org.schabi.newpipe.util.KeyboardUtil;
|
||||
import org.schabi.newpipe.util.ReleaseVersionUtil;
|
||||
import org.schabi.newpipe.util.ThemeHelper;
|
||||
import org.schabi.newpipe.views.FocusOverlayView;
|
||||
|
||||
@ -267,7 +266,7 @@ public class SettingsActivity extends AppCompatActivity implements
|
||||
*/
|
||||
private void ensureSearchRepresentsApplicationState() {
|
||||
// Check if the update settings are available
|
||||
if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) {
|
||||
if (!ReleaseVersionUtil.isReleaseApk()) {
|
||||
SettingsResourceRegistry.getInstance()
|
||||
.getEntryByPreferencesResId(R.xml.update_settings)
|
||||
.setSearchable(false);
|
||||
|
@ -1,12 +1,11 @@
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
|
||||
import org.schabi.newpipe.NewVersionWorker;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
public class UpdateSettingsFragment extends BasePreferenceFragment {
|
||||
@ -33,7 +32,7 @@ public class UpdateSettingsFragment extends BasePreferenceFragment {
|
||||
// Reset the expire time. This is necessary to check for an update immediately.
|
||||
defaultPreferences.edit()
|
||||
.putLong(getString(R.string.update_expiry_key), 0).apply();
|
||||
startNewVersionCheckService();
|
||||
NewVersionWorker.enqueueNewVersionCheckingWork(getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -136,7 +136,7 @@ public class ChooseTabsFragment extends Fragment {
|
||||
.setTitle(R.string.restore_defaults)
|
||||
.setMessage(R.string.restore_defaults_confirmation)
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.yes, (dialog, which) -> {
|
||||
.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||
tabsManager.resetTabs();
|
||||
updateTabList();
|
||||
selectedTabsAdapter.notifyDataSetChanged();
|
||||
|
116
app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
Normal file
116
app/src/main/java/org/schabi/newpipe/util/ReleaseVersionUtil.kt
Normal file
@ -0,0 +1,116 @@
|
||||
package org.schabi.newpipe.util
|
||||
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.pm.Signature
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import org.schabi.newpipe.App
|
||||
import org.schabi.newpipe.error.ErrorInfo
|
||||
import org.schabi.newpipe.error.ErrorUtil.Companion.createNotification
|
||||
import org.schabi.newpipe.error.UserAction
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.InputStream
|
||||
import java.security.MessageDigest
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.cert.CertificateEncodingException
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.CertificateFactory
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
object ReleaseVersionUtil {
|
||||
// Public key of the certificate that is used in NewPipe release versions
|
||||
private const val RELEASE_CERT_PUBLIC_KEY_SHA1 =
|
||||
"B0:2E:90:7C:1C:D6:FC:57:C3:35:F0:88:D0:8F:50:5F:94:E4:D2:15"
|
||||
|
||||
@JvmStatic
|
||||
fun isReleaseApk(): Boolean {
|
||||
return certificateSHA1Fingerprint == RELEASE_CERT_PUBLIC_KEY_SHA1
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to get the APK's SHA1 key. See https://stackoverflow.com/questions/9293019/#22506133.
|
||||
*
|
||||
* @return String with the APK's SHA1 fingerprint in hexadecimal
|
||||
*/
|
||||
private val certificateSHA1Fingerprint: String
|
||||
get() {
|
||||
val app = App.getApp()
|
||||
val signatures: List<Signature> = try {
|
||||
PackageInfoCompat.getSignatures(app.packageManager, app.packageName)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
showRequestError(app, e, "Could not find package info")
|
||||
return ""
|
||||
}
|
||||
if (signatures.isEmpty()) {
|
||||
return ""
|
||||
}
|
||||
val x509cert = try {
|
||||
val cert = signatures[0].toByteArray()
|
||||
val input: InputStream = ByteArrayInputStream(cert)
|
||||
val cf = CertificateFactory.getInstance("X509")
|
||||
cf.generateCertificate(input) as X509Certificate
|
||||
} catch (e: CertificateException) {
|
||||
showRequestError(app, e, "Certificate error")
|
||||
return ""
|
||||
}
|
||||
|
||||
return try {
|
||||
val md = MessageDigest.getInstance("SHA1")
|
||||
val publicKey = md.digest(x509cert.encoded)
|
||||
byte2HexFormatted(publicKey)
|
||||
} catch (e: NoSuchAlgorithmException) {
|
||||
showRequestError(app, e, "Could not retrieve SHA1 key")
|
||||
""
|
||||
} catch (e: CertificateEncodingException) {
|
||||
showRequestError(app, e, "Could not retrieve SHA1 key")
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
private fun byte2HexFormatted(arr: ByteArray): String {
|
||||
val str = StringBuilder(arr.size * 2)
|
||||
for (i in arr.indices) {
|
||||
var h = Integer.toHexString(arr[i].toInt())
|
||||
val l = h.length
|
||||
if (l == 1) {
|
||||
h = "0$h"
|
||||
}
|
||||
if (l > 2) {
|
||||
h = h.substring(l - 2, l)
|
||||
}
|
||||
str.append(h.uppercase())
|
||||
if (i < arr.size - 1) {
|
||||
str.append(':')
|
||||
}
|
||||
}
|
||||
return str.toString()
|
||||
}
|
||||
|
||||
private fun showRequestError(app: App, e: Exception, request: String) {
|
||||
createNotification(
|
||||
app, ErrorInfo(e, UserAction.CHECK_FOR_NEW_APP_VERSION, request)
|
||||
)
|
||||
}
|
||||
|
||||
fun isLastUpdateCheckExpired(expiry: Long): Boolean {
|
||||
return Instant.ofEpochSecond(expiry).isBefore(Instant.now())
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerce expiry date time in between 6 hours and 72 hours from now
|
||||
*
|
||||
* @return Epoch second of expiry date time
|
||||
*/
|
||||
fun coerceUpdateCheckExpiry(expiryString: String?): Long {
|
||||
val now = ZonedDateTime.now()
|
||||
return expiryString?.let {
|
||||
var expiry =
|
||||
ZonedDateTime.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(expiryString))
|
||||
expiry = maxOf(expiry, now.plusHours(6))
|
||||
expiry = minOf(expiry, now.plusHours(72))
|
||||
expiry.toEpochSecond()
|
||||
} ?: now.plusHours(6).toEpochSecond()
|
||||
}
|
||||
}
|
@ -581,6 +581,21 @@
|
||||
app:srcCompat="@drawable/ic_close"
|
||||
app:tint="@color/white" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/addToPlaylistButton"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toLeftOf="@+id/itemsListClose"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:padding="10dp"
|
||||
android:scaleType="fitXY"
|
||||
android:tint="?attr/colorAccent"
|
||||
app:srcCompat="@drawable/ic_playlist_add"
|
||||
tools:ignore="ContentDescription,RtlHardcoded" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageButton
|
||||
android:id="@+id/repeatButton"
|
||||
android:layout_width="50dp"
|
||||
@ -620,7 +635,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@id/itemsListClose"
|
||||
android:layout_toStartOf="@id/addToPlaylistButton"
|
||||
android:layout_toEndOf="@id/shuffleButton"
|
||||
android:gravity="center"
|
||||
android:textColor="@android:color/white" />
|
||||
|
@ -91,7 +91,6 @@
|
||||
<string name="show_age_restricted_content_title">محتوى مقيد للبالغين</string>
|
||||
<string name="duration_live">بث مباشر</string>
|
||||
<string name="error_report_title">تقرير عن المشكلة</string>
|
||||
<string name="yes">نعم</string>
|
||||
<string name="disabled">متوقف</string>
|
||||
<string name="clear">تنظيف</string>
|
||||
<string name="best_resolution">أفضل دقة</string>
|
||||
|
@ -169,7 +169,6 @@
|
||||
<string name="best_resolution">Ən yaxşı görüntü keyfiyyəti</string>
|
||||
<string name="clear">Təmizlə</string>
|
||||
<string name="disabled">Deaktiv edilib</string>
|
||||
<string name="yes">Bəli</string>
|
||||
<string name="artists">İfaçılar</string>
|
||||
<string name="albums">Albomlar</string>
|
||||
<string name="songs">Mahnılar</string>
|
||||
|
@ -44,7 +44,6 @@
|
||||
<string name="detail_dislikes_img_view_description">Tarrezmes</string>
|
||||
<string name="default_video_format_title">Formatu de videu predetermináu</string>
|
||||
<string name="black_theme_title">Prietu</string>
|
||||
<string name="yes">Sí</string>
|
||||
<string name="short_thousand">mil</string>
|
||||
<string name="short_million">mill.</string>
|
||||
<string name="short_billion">mil mill.</string>
|
||||
|
@ -112,7 +112,6 @@
|
||||
<string name="best_resolution">Eng yaxshi qaror</string>
|
||||
<string name="clear">Tozalash</string>
|
||||
<string name="disabled">Ijrochilar o\'chirib qo\'yilgan</string>
|
||||
<string name="yes">Ha</string>
|
||||
<string name="artists">Artistlar</string>
|
||||
<string name="albums">Albomlar</string>
|
||||
<string name="songs">Qo\'shiqlar</string>
|
||||
|
@ -28,7 +28,6 @@
|
||||
<string name="unsupported_url">不支持的 URL</string>
|
||||
<string name="settings_category_appearance_title">外观</string>
|
||||
<string name="all">全部</string>
|
||||
<string name="yes">是</string>
|
||||
<string name="network_error">网络错误</string>
|
||||
<plurals name="videos">
|
||||
<item quantity="other">%s 个视频</item>
|
||||
|
@ -97,7 +97,6 @@
|
||||
<string name="playlists">Плэйлісты</string>
|
||||
<string name="tracks">Дарожкі</string>
|
||||
<string name="users">Карыстальнікі</string>
|
||||
<string name="yes">Так</string>
|
||||
<string name="disabled">Адключана</string>
|
||||
<string name="clear">Ачысціць</string>
|
||||
<string name="best_resolution">Лепшае разрозненне</string>
|
||||
|
@ -56,7 +56,6 @@
|
||||
<string name="file">ⴰⴼⴰⵢⵍⵓ</string>
|
||||
<string name="play_all">ⵖⵔ ⵎⴰⵕⵕⴰ</string>
|
||||
<string name="file_deleted">ⵉⵜⵜⵡⴰⴽⴽⵙ ⵓⴼⴰⵢⵍⵓ</string>
|
||||
<string name="yes">ⵢⴰⵀ</string>
|
||||
<string name="videos_string">ⵉⴼⵉⴷⵢⵓⵜⵏ</string>
|
||||
<string name="all">ⵎⴰⵕⵕⴰ</string>
|
||||
<string name="downloads_title">ⵓⴳⴳⴰⵎⵏ</string>
|
||||
|
@ -75,7 +75,6 @@
|
||||
<string name="downloads_title">Изтегляния</string>
|
||||
<string name="error_report_title">Съобщение за грешка</string>
|
||||
<string name="all">Всички</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="disabled">Забранено</string>
|
||||
<string name="clear">Изчисти</string>
|
||||
<string name="best_resolution">Най-добра резолюция</string>
|
||||
|
@ -55,7 +55,6 @@
|
||||
<string name="downloads_title">ডাউনলোডগুলি</string>
|
||||
<string name="error_report_title">ত্রুটি প্রতিবেদন</string>
|
||||
<string name="all">সবগুলি</string>
|
||||
<string name="yes">হ্যাঁ</string>
|
||||
<string name="disabled">নিস্ক্রীয়</string>
|
||||
<string name="clear">পরিষ্কার</string>
|
||||
<!-- error strings -->
|
||||
|
@ -53,7 +53,6 @@
|
||||
<string name="always">সবসময়</string>
|
||||
<string name="clear">পরিষ্কার</string>
|
||||
<string name="disabled">নিস্ক্রীয়</string>
|
||||
<string name="yes">হ্যাঁ</string>
|
||||
<string name="all">সবগুলি</string>
|
||||
<string name="error_report_title">ত্রুটি প্রতিবেদন</string>
|
||||
<string name="downloads_title">ডাউনলোডগুলি</string>
|
||||
|
@ -176,7 +176,6 @@
|
||||
<string name="best_resolution">সেরা রেজুলিউসন</string>
|
||||
<string name="clear">পরিষ্কার</string>
|
||||
<string name="disabled">নিস্ক্রীয়</string>
|
||||
<string name="yes">হ্যাঁ</string>
|
||||
<string name="artists">শিল্পীরা</string>
|
||||
<string name="albums">অ্যালবাম গুলি</string>
|
||||
<string name="songs">গান গুলি</string>
|
||||
|
@ -37,7 +37,6 @@
|
||||
<string name="downloads">Baixades</string>
|
||||
<string name="downloads_title">Baixades</string>
|
||||
<string name="all">Tot</string>
|
||||
<string name="yes">Sí</string>
|
||||
<string name="disabled">Desactivat</string>
|
||||
<string name="clear">Neteja</string>
|
||||
<string name="best_resolution">Millor resolució</string>
|
||||
|
@ -101,7 +101,6 @@
|
||||
<string name="title_licenses">مۆڵەتنامەی لایەنی-سێیەم</string>
|
||||
<string name="app_license_title">مۆڵەتنامەی نیوپایپ</string>
|
||||
<string name="show_hold_to_append_title">پیشاندانی ڕێنمایی ”داگرتن تا پاشکۆ”</string>
|
||||
<string name="yes">بەڵێ</string>
|
||||
<string name="msg_threads">دابەشکراوەکان</string>
|
||||
<string name="title_most_played">زۆرترین لێدراو</string>
|
||||
<string name="unbookmark_playlist">لادانی نیشانهكراو</string>
|
||||
|
@ -84,7 +84,6 @@
|
||||
<string name="no_available_dir">Určete prosím složku pro stahování později v nastavení</string>
|
||||
<string name="info_labels">Co:\\nŽádost:\\nJazyk obsahu\\nZemě obsahu:\\nJazyk aplikace:\\nSlužba:\\nČas GMT:\\nBalíček:\\nVerze:\\nVerze OS:</string>
|
||||
<string name="all">Vše</string>
|
||||
<string name="yes">Ano</string>
|
||||
<string name="short_thousand">tis.</string>
|
||||
<string name="open_in_popup_mode">Otevřít ve vyskakovacím okně</string>
|
||||
<string name="short_million">mil.</string>
|
||||
|
@ -108,7 +108,6 @@
|
||||
</plurals>
|
||||
<string name="tracks">Numre</string>
|
||||
<string name="users">Brugere</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="disabled">Slået fra</string>
|
||||
<string name="clear">Slet</string>
|
||||
<string name="best_resolution">Bedste opløsning</string>
|
||||
|
@ -88,7 +88,6 @@
|
||||
<string name="black_theme_title">Schwarz</string>
|
||||
<string name="title_activity_recaptcha">reCAPTCHA-Aufgabe</string>
|
||||
<string name="recaptcha_request_toast">reCAPTCHA-Aufgabe angefordert</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="all">Alle</string>
|
||||
<string name="disabled">Deaktiviert</string>
|
||||
<string name="open_in_popup_mode">Im Pop-up-Modus öffnen</string>
|
||||
|
@ -52,7 +52,6 @@
|
||||
<string name="downloads">Λήψεις</string>
|
||||
<string name="downloads_title">Λήψεις</string>
|
||||
<string name="all">Όλα</string>
|
||||
<string name="yes">Ναι</string>
|
||||
<string name="general_error">Σφάλμα</string>
|
||||
<string name="error_snackbar_action">Αναφορά</string>
|
||||
<string name="what_device_headline">Πληροφορίες:</string>
|
||||
|
@ -95,7 +95,6 @@
|
||||
<item quantity="one">%s filmeto</item>
|
||||
<item quantity="other">%s filmetoj</item>
|
||||
</plurals>
|
||||
<string name="yes">Jes</string>
|
||||
<string name="msg_popup_permission">Tiu permeso estas necesa por
|
||||
\nmalfermi en ŝprucfenestra modo</string>
|
||||
<string name="popup_playing_toast">Ludante en ŝprucfenestra modo</string>
|
||||
|
@ -82,7 +82,6 @@
|
||||
<string name="info_labels">Lo sucedido:\\nPetición:\\nIdioma del contenido:\\nPaís del contenido:\\nIdioma de la aplicación:\\nServicio:\\nHora GMT:\\nPaquete:\\nVersión:\\nVersión del SO:</string>
|
||||
<string name="black_theme_title">Negro</string>
|
||||
<string name="all">Todo</string>
|
||||
<string name="yes">Sí</string>
|
||||
<string name="short_thousand">k</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">MM</string>
|
||||
|
@ -93,7 +93,6 @@
|
||||
<string name="downloads_title">Allalaadimised</string>
|
||||
<string name="error_report_title">Vea teatamine</string>
|
||||
<string name="all">Kõik</string>
|
||||
<string name="yes">Jah</string>
|
||||
<string name="disabled">Keelatud</string>
|
||||
<string name="clear">Kustuta</string>
|
||||
<string name="best_resolution">Parim lahutus</string>
|
||||
|
@ -62,7 +62,6 @@
|
||||
<string name="downloads_title">Deskargak</string>
|
||||
<string name="error_report_title">Errore-txostena</string>
|
||||
<string name="all">Dena</string>
|
||||
<string name="yes">Bai</string>
|
||||
<string name="disabled">Desgaituta</string>
|
||||
<string name="clear">Garbitu</string>
|
||||
<string name="best_resolution">Bereizmen onena</string>
|
||||
|
@ -109,7 +109,6 @@
|
||||
<string name="channels">کانالها</string>
|
||||
<string name="playlists">سیاهههای پخش</string>
|
||||
<string name="users">کاربران</string>
|
||||
<string name="yes">بله</string>
|
||||
<string name="disabled">غیرفعال</string>
|
||||
<string name="clear">پاککردن</string>
|
||||
<string name="play_all">پخش همه</string>
|
||||
|
@ -73,7 +73,6 @@
|
||||
<string name="downloads_title">Lataukset</string>
|
||||
<string name="error_report_title">Virheraportti</string>
|
||||
<string name="all">Kaikki</string>
|
||||
<string name="yes">Kyllä</string>
|
||||
<string name="disabled">Poistettu käytöstä</string>
|
||||
<string name="clear">Pyyhi</string>
|
||||
<string name="best_resolution">Paras resoluutio</string>
|
||||
|
@ -86,7 +86,6 @@
|
||||
<string name="recaptcha_request_toast">Défi reCAPTCHA demandé</string>
|
||||
<string name="open_in_popup_mode">Ouvrir en mode pop-up</string>
|
||||
<string name="popup_playing_toast">Lecture en mode flottant</string>
|
||||
<string name="yes">Oui</string>
|
||||
<string name="disabled">Désactivés</string>
|
||||
<string name="info_labels">Quoi :\\nRequest :\\nContent Language :\\nContent Country :\\nApp Language :\\nService :\\nGMT Time :\\nPackage :\\nVersion :\\nOS version :</string>
|
||||
<string name="short_thousand">k</string>
|
||||
|
@ -96,7 +96,6 @@
|
||||
<string name="playlists">Listas de reprodución</string>
|
||||
<string name="tracks">Pistas</string>
|
||||
<string name="users">Usuarios</string>
|
||||
<string name="yes">Si</string>
|
||||
<string name="disabled">Desactivado</string>
|
||||
<string name="clear">Limpar</string>
|
||||
<string name="best_resolution">Mellor resolución</string>
|
||||
|
@ -58,7 +58,6 @@
|
||||
<string name="downloads_title">הורדות</string>
|
||||
<string name="error_report_title">דוח שגיאה</string>
|
||||
<string name="all">הכול</string>
|
||||
<string name="yes">כן</string>
|
||||
<string name="disabled">מושבת</string>
|
||||
<string name="clear">ניקוי</string>
|
||||
<string name="general_error">שגיאה</string>
|
||||
|
@ -90,7 +90,6 @@
|
||||
<string name="downloads_title">डाउनलोड</string>
|
||||
<string name="error_report_title">त्रुटी की रिपोर्ट</string>
|
||||
<string name="all">सारे</string>
|
||||
<string name="yes">सहमत हूँ</string>
|
||||
<string name="disabled">बंद करे</string>
|
||||
<string name="clear">साफ़</string>
|
||||
<string name="best_resolution">बेहतर विडियो की क्वालिटी</string>
|
||||
|
@ -71,7 +71,6 @@
|
||||
<string name="downloads_title">Preuzimanja</string>
|
||||
<string name="error_report_title">Prijavi grešku</string>
|
||||
<string name="all">Sve</string>
|
||||
<string name="yes">Da</string>
|
||||
<string name="disabled">Isključeno</string>
|
||||
<string name="clear">Očisti</string>
|
||||
<string name="best_resolution">Najbolja rezolucija</string>
|
||||
|
@ -111,7 +111,6 @@
|
||||
<string name="settings_category_debug_title">Hibaelhárítás</string>
|
||||
<string name="popup_playing_toast">Lejátszás felugró ablakban</string>
|
||||
<string name="all">Összes</string>
|
||||
<string name="yes">Igen</string>
|
||||
<string name="disabled">Letiltva</string>
|
||||
<string name="clear">Törlés</string>
|
||||
<string name="best_resolution">Legjobb felbontás</string>
|
||||
|
@ -65,7 +65,6 @@
|
||||
<string name="file_deleted">Ֆայլը ջնջվեց</string>
|
||||
<string name="file">Ֆայլ</string>
|
||||
<string name="songs">Երգեր</string>
|
||||
<string name="yes">Այո</string>
|
||||
<string name="enable_search_history_title">Որոնման պատմություն</string>
|
||||
<string name="close">Փակել</string>
|
||||
<plurals name="days">
|
||||
|
@ -85,7 +85,6 @@
|
||||
<string name="tracks">Pistas</string>
|
||||
<string name="users">Usatores</string>
|
||||
<string name="events">Eventos</string>
|
||||
<string name="yes">Si</string>
|
||||
<string name="disabled">Disactivate</string>
|
||||
<string name="clear">Vacuar</string>
|
||||
<string name="best_resolution">Melior resolution</string>
|
||||
|
@ -86,7 +86,6 @@
|
||||
<string name="short_thousand">r</string>
|
||||
<string name="short_million">J</string>
|
||||
<string name="short_billion">T</string>
|
||||
<string name="yes">Ya</string>
|
||||
<string name="open_in_popup_mode">Buka dalam mode popup</string>
|
||||
<string name="msg_popup_permission">Izin ini dibutuhkan untuk
|
||||
\nmembuka di mode sembul</string>
|
||||
|
@ -87,7 +87,6 @@
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">Mrd</string>
|
||||
<string name="recaptcha_request_toast">È richiesta la risoluzione del reCAPTCHA</string>
|
||||
<string name="yes">Sì</string>
|
||||
<string name="open_in_popup_mode">Apri in modalità popup</string>
|
||||
<string name="popup_playing_toast">Riproduzione in modalità popup</string>
|
||||
<string name="disabled">Disattivato</string>
|
||||
|
@ -87,7 +87,6 @@
|
||||
<string name="short_thousand">k</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">B</string>
|
||||
<string name="yes">はい</string>
|
||||
<string name="open_in_popup_mode">ポップアップモードで開く</string>
|
||||
<string name="msg_popup_permission">ポップアップモードで開くには
|
||||
\n権限の許可が必要です</string>
|
||||
|
@ -30,7 +30,6 @@
|
||||
<string name="export_to">Sifeḍ ɣer</string>
|
||||
<string name="controls_add_to_playlist_title">Rnu ɣer</string>
|
||||
<string name="playback_step">Amecwaṛ</string>
|
||||
<string name="yes">Ih</string>
|
||||
<string name="msg_running">Azdam n NewPipe</string>
|
||||
<string name="restore_defaults">Err-d imezwar</string>
|
||||
<string name="channel_created_by">Yerna-t %s</string>
|
||||
|
@ -169,7 +169,6 @@
|
||||
<string name="app_license">NewPipe nermalava kopîleft libre ye: Hûn dikarin li gorî kêfa xwe bikar bînin, parve bikin û baştir bikin. Bi taybetî hûn dikarin wê di bin mercên Lîsansa Giştî ya GNU ya Giştî ya ku ji hêla Weqfa Nermalava Azad ve hatî weşandin de, an guhertoya 3 ya Lîsansê, an jî (li gorî vebijarka we) guhertoyek paşîn ji nû ve belav bikin û / an biguherînin.</string>
|
||||
<string name="clear">Zelal</string>
|
||||
<string name="disabled">Bêmecel</string>
|
||||
<string name="yes">Erê</string>
|
||||
<string name="artists">Hunermend</string>
|
||||
<string name="albums">Album</string>
|
||||
<string name="songs">Stran</string>
|
||||
|
@ -102,7 +102,6 @@
|
||||
<string name="popup_playing_toast">팝업 모드에서 재생 중</string>
|
||||
<string name="error_report_title">오류 보고</string>
|
||||
<string name="all">전부</string>
|
||||
<string name="yes">네</string>
|
||||
<string name="disabled">해제됨</string>
|
||||
<string name="clear">지우기</string>
|
||||
<string name="best_resolution">최대 해상도</string>
|
||||
|
@ -86,7 +86,6 @@
|
||||
<string name="downloads_title">دابەزاندنەکان</string>
|
||||
<string name="error_report_title">ناتوانرێ سکاڵابکرێ</string>
|
||||
<string name="all">گشتی</string>
|
||||
<string name="yes">بەڵێ</string>
|
||||
<string name="disabled">ناکارایە</string>
|
||||
<string name="clear">پاککردنەوە</string>
|
||||
<string name="best_resolution">باشترین قەبارە</string>
|
||||
|
@ -58,7 +58,6 @@
|
||||
<string name="downloads_title">Atsisiuntimai</string>
|
||||
<string name="error_report_title">Klaidų ataskaita</string>
|
||||
<string name="all">Visi</string>
|
||||
<string name="yes">Taip</string>
|
||||
<string name="disabled">Išjungta</string>
|
||||
<string name="clear">Išvalyti</string>
|
||||
<string name="best_resolution">Geriausia raiška</string>
|
||||
|
@ -236,7 +236,6 @@
|
||||
<string name="best_resolution">Labākā izšķirtspēja</string>
|
||||
<string name="clear">Notīrīt</string>
|
||||
<string name="disabled">Atspējots</string>
|
||||
<string name="yes">Jā</string>
|
||||
<string name="artists">Mākslinieki</string>
|
||||
<string name="albums">Albūmi</string>
|
||||
<string name="songs">Dziesmas</string>
|
||||
|
@ -93,7 +93,6 @@
|
||||
<string name="downloads_title">Превземања</string>
|
||||
<string name="error_report_title">Извештај за грешки</string>
|
||||
<string name="all">Сите</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="disabled">Оневозможено</string>
|
||||
<string name="clear">Избриши</string>
|
||||
<string name="best_resolution">Најдобра резолуција</string>
|
||||
|
@ -270,7 +270,6 @@
|
||||
<string name="best_resolution">മികച്ച റിസല്യൂഷൻ</string>
|
||||
<string name="clear">തെളിക്കുക</string>
|
||||
<string name="disabled">അസാധുവാക്കപ്പെട്ടു</string>
|
||||
<string name="yes">അതെ</string>
|
||||
<string name="artists">കലാകാരന്മാർ</string>
|
||||
<string name="albums">ആൽബങ്ങൾ</string>
|
||||
<string name="songs">പാട്ടുകൾ</string>
|
||||
|
@ -105,7 +105,6 @@
|
||||
<string name="tracks">Trek</string>
|
||||
<string name="users">Pengguna</string>
|
||||
<string name="events">Peristiwa</string>
|
||||
<string name="yes">Ya</string>
|
||||
<string name="disabled">Dinyahdayakan</string>
|
||||
<string name="clear">Bersihkan</string>
|
||||
<string name="best_resolution">Resolusi terbaik</string>
|
||||
|
@ -89,7 +89,6 @@
|
||||
<string name="black_theme_title">Svart</string>
|
||||
<string name="popup_playing_toast">Spiller av i oppsprettsmodus</string>
|
||||
<string name="all">Alle</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="disabled">Avskrudd</string>
|
||||
<string name="short_thousand">k</string>
|
||||
<string name="short_million">M</string>
|
||||
|
@ -111,7 +111,6 @@
|
||||
<string name="tracks">ट्रयाकहरु</string>
|
||||
<string name="users">प्रयोगकर्ताहरु</string>
|
||||
<string name="events">घटनाहरू</string>
|
||||
<string name="yes">हो</string>
|
||||
<string name="disabled">अक्षम</string>
|
||||
<string name="clear">स्पष्ट</string>
|
||||
<string name="best_resolution">सर्वश्रेष्ठ रेसोलुशन</string>
|
||||
|
@ -93,7 +93,6 @@
|
||||
<string name="downloads_title">Downloads</string>
|
||||
<string name="error_report_title">Foutrapport</string>
|
||||
<string name="all">Alles</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="disabled">Uitgeschakeld</string>
|
||||
<string name="clear">Wissen</string>
|
||||
<string name="best_resolution">Beste resolutie</string>
|
||||
|
@ -85,7 +85,6 @@
|
||||
<string name="recaptcha_request_toast">reCAPTCHA-uitdaging gevraagd</string>
|
||||
<string name="open_in_popup_mode">Openen in pop-upmodus</string>
|
||||
<string name="all">Alles</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="short_thousand">k</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">B</string>
|
||||
|
@ -92,7 +92,6 @@
|
||||
<string name="downloads_title">ਡਾਊਨਲੋਡਸ</string>
|
||||
<string name="error_report_title">Error ਰਿਪੋਰਟ</string>
|
||||
<string name="all">ਸਾਰੇ</string>
|
||||
<string name="yes">ਹਾਂ</string>
|
||||
<string name="disabled">ਬੰਦ ਕੀਤਾ</string>
|
||||
<string name="clear">ਮਿਟਾਓ</string>
|
||||
<string name="best_resolution">ਵਧੀਆ Resolution</string>
|
||||
|
@ -95,7 +95,6 @@
|
||||
<string name="show_search_suggestions_summary">Wybierz podpowiedzi, które będą wyświetlane podczas wyszukiwania</string>
|
||||
<string name="popup_playing_toast">Odtwarzanie w trybie okienkowym</string>
|
||||
<string name="all">Wszystkie</string>
|
||||
<string name="yes">Tak</string>
|
||||
<string name="disabled">Wyłączone</string>
|
||||
<string name="clear">Wyczyść</string>
|
||||
<string name="short_thousand">tys.</string>
|
||||
|
@ -90,7 +90,6 @@
|
||||
<string name="default_video_format_title">Formato de vídeo padrão</string>
|
||||
<string name="popup_playing_toast">Reproduzindo em modo popup</string>
|
||||
<string name="all">Tudo</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="disabled">Desativado</string>
|
||||
<string name="short_thousand">k</string>
|
||||
<string name="short_million">M</string>
|
||||
|
@ -395,7 +395,6 @@
|
||||
<string name="max_retry_msg">Tentativas máximas</string>
|
||||
<string name="title_activity_history">Histórico</string>
|
||||
<string name="playback_pitch">Velocidade</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="error_download_resource_gone">Não é possível recuperar esta descarga</string>
|
||||
<string name="rename_playlist">Mudar nome</string>
|
||||
<string name="feed_group_dialog_empty_selection">Nenhuma subscrição selecionada</string>
|
||||
|
@ -83,7 +83,6 @@
|
||||
<string name="open_in_popup_mode">Abrir no modo popup</string>
|
||||
<string name="black_theme_title">Preto</string>
|
||||
<string name="all">Tudo</string>
|
||||
<string name="yes">Sim</string>
|
||||
<string name="short_thousand">k</string>
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">MM</string>
|
||||
|
@ -90,7 +90,6 @@
|
||||
<string name="black_theme_title">Negru</string>
|
||||
<string name="popup_playing_toast">Redare în mod pop-up</string>
|
||||
<string name="all">Toate</string>
|
||||
<string name="yes">Da</string>
|
||||
<string name="disabled">Dezactivat</string>
|
||||
<string name="app_ui_crash">Aplicația/UI s-a oprit</string>
|
||||
<string name="info_labels">Ce:\\nSolicitare:\\nLimba conținutului:\\nȚara conținutului:\\nLimba aplicației:\\nServiciu:\\nOra GMT:\\nPachet:\\nVersiune: \\nVersiune SO:</string>
|
||||
|
@ -86,7 +86,6 @@
|
||||
<string name="black_theme_title">Чёрная</string>
|
||||
<string name="popup_remember_size_pos_title">Помнить параметры окна</string>
|
||||
<string name="popup_playing_toast">Воспроизведение во всплывающем окне</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="clear">Очистить</string>
|
||||
<string name="all">Всё</string>
|
||||
<string name="info_labels">Что:\\nЗапрос:\\nЯзык контента:\\nСтрана контента:\\nЯзык приложения:\\nСервис:\\nВремя по Гринвичу:\\nПакет:\\nВерсия пакета:\\nВерсия ОС:</string>
|
||||
|
@ -415,7 +415,6 @@
|
||||
<string name="best_resolution">Risolutzione mègius</string>
|
||||
<string name="clear">Isbòida</string>
|
||||
<string name="disabled">Disabilitadu</string>
|
||||
<string name="yes">Eja</string>
|
||||
<string name="artists">Artista</string>
|
||||
<string name="albums">Albums</string>
|
||||
<string name="songs">Cantzones</string>
|
||||
|
@ -87,7 +87,6 @@
|
||||
<string name="short_million">M</string>
|
||||
<string name="short_billion">B</string>
|
||||
<string name="recaptcha_request_toast">Požiadavka reCAPTCHA</string>
|
||||
<string name="yes">Áno</string>
|
||||
<string name="open_in_popup_mode">Spustiť v okne</string>
|
||||
<string name="msg_popup_permission">Tieto práva sú potrebné pre
|
||||
\nprehrávanie v mini okne</string>
|
||||
|
@ -87,7 +87,6 @@
|
||||
<string name="short_thousand">k</string>
|
||||
<string name="short_million">mio</string>
|
||||
<string name="short_billion">mrd</string>
|
||||
<string name="yes">Da</string>
|
||||
<string name="open_in_popup_mode">Odpri v pojavnem načinu</string>
|
||||
<string name="msg_popup_permission">To dovoljenje je potrebno za odpiranje
|
||||
\nv pojavnem načinu</string>
|
||||
|
@ -319,7 +319,6 @@
|
||||
<string name="duration_live">Toos</string>
|
||||
<string name="undo">Soo celi</string>
|
||||
<string name="disabled">Xidhan</string>
|
||||
<string name="yes">Haa</string>
|
||||
<string name="users">Isticmaale</string>
|
||||
<string name="videos_string">Muuqaalo</string>
|
||||
<string name="all">Dhammaan</string>
|
||||
|
@ -34,7 +34,6 @@
|
||||
<string name="downloads_title">Shkarkimet</string>
|
||||
<string name="error_report_title">Raporti i gabimit</string>
|
||||
<string name="all">Të gjitha</string>
|
||||
<string name="yes">Po</string>
|
||||
<string name="missions_header_pending">Në pritje</string>
|
||||
<plurals name="feed_group_dialog_selection_count">
|
||||
<item quantity="one">%d i zgjedhur</item>
|
||||
|
@ -87,7 +87,6 @@
|
||||
<string name="short_thousand">хиљ</string>
|
||||
<string name="short_million">мил</string>
|
||||
<string name="short_billion">млрд</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="open_in_popup_mode">Отвори у искачућем режиму</string>
|
||||
<string name="msg_popup_permission">Ова дозвола је потребна за
|
||||
\nотварање у искачућем режиму</string>
|
||||
|
@ -58,7 +58,6 @@
|
||||
<string name="downloads_title">Hämtningar</string>
|
||||
<string name="error_report_title">Felrapport</string>
|
||||
<string name="all">Alla</string>
|
||||
<string name="yes">Ja</string>
|
||||
<string name="disabled">Inaktiverad</string>
|
||||
<string name="clear">Rensa</string>
|
||||
<string name="best_resolution">Bästa upplösningen</string>
|
||||
|
@ -73,7 +73,6 @@
|
||||
<string name="all">அனைத்தும்</string>
|
||||
<string name="playlists">ஒளிச்சரங்கள்</string>
|
||||
<string name="users">பயனர்கள்</string>
|
||||
<string name="yes">ஆம்</string>
|
||||
<string name="clear">அழி</string>
|
||||
<string name="always">எப்பொழுதும்</string>
|
||||
<string name="just_once">ஒரு முறை</string>
|
||||
|
@ -52,7 +52,6 @@
|
||||
<string name="downloads_title">డౌన్ లోడ్</string>
|
||||
<string name="error_report_title">లోపం నివేదిక</string>
|
||||
<string name="all">అన్ని</string>
|
||||
<string name="yes">అవును</string>
|
||||
<string name="play_all">అన్నింటినీ ప్లే చేయండి</string>
|
||||
<string name="notification_channel_name">న్యూప్యాప్ నోటిఫికేషన్</string>
|
||||
<string name="general_error">లోపం</string>
|
||||
|
@ -104,7 +104,6 @@
|
||||
<string name="tracks">แทร็ค</string>
|
||||
<string name="users">ผู้ใช้</string>
|
||||
<string name="events">เหตุการณ์</string>
|
||||
<string name="yes">ใช่</string>
|
||||
<string name="disabled">ปิดการใช้งาน</string>
|
||||
<string name="clear">ล้าง</string>
|
||||
<string name="best_resolution">ความละเอียดที่ดีที่สุด</string>
|
||||
|
@ -86,7 +86,6 @@
|
||||
<string name="black_theme_title">Siyah</string>
|
||||
<string name="popup_playing_toast">Açılır pencere kipinde oynatılıyor</string>
|
||||
<string name="all">Tümü</string>
|
||||
<string name="yes">Evet</string>
|
||||
<string name="disabled">Devre dışı</string>
|
||||
<string name="your_comment">Yorumunuz (İngilizce):</string>
|
||||
<string name="error_details_headline">Ayrıntılar:</string>
|
||||
|
@ -123,7 +123,6 @@
|
||||
<string name="file">Afaylu</string>
|
||||
<string name="always">Ku dwal</string>
|
||||
<string name="clear">Sfeḍ</string>
|
||||
<string name="yes">Yah</string>
|
||||
<string name="artists">Inaẓuṛen</string>
|
||||
<string name="songs">Tiɣennijin</string>
|
||||
<string name="events">Imezza</string>
|
||||
|
@ -58,7 +58,6 @@
|
||||
<string name="downloads_title">Завантаження</string>
|
||||
<string name="error_report_title">Звіт про помилку</string>
|
||||
<string name="all">Усе</string>
|
||||
<string name="yes">Так</string>
|
||||
<string name="disabled">Вимкнено</string>
|
||||
<string name="app_ui_crash">Збій застосунку/інтерфейсу</string>
|
||||
<string name="your_comment">Ваш коментар (англійською):</string>
|
||||
|
@ -92,7 +92,6 @@
|
||||
<string name="downloads_title">ڈاؤن لوڈز</string>
|
||||
<string name="error_report_title">خرابی کی اطلاع</string>
|
||||
<string name="all">تمام</string>
|
||||
<string name="yes">ہاں</string>
|
||||
<string name="disabled">غیر فعال</string>
|
||||
<string name="clear">صاف</string>
|
||||
<string name="best_resolution">بہترین ریزولوشن</string>
|
||||
|
@ -56,7 +56,6 @@
|
||||
<string name="downloads_title">Tải xuống</string>
|
||||
<string name="error_report_title">Báo lỗi</string>
|
||||
<string name="all">Tất cả</string>
|
||||
<string name="yes">Có</string>
|
||||
<string name="disabled">Vô hiệu</string>
|
||||
<string name="clear">Xóa</string>
|
||||
<string name="best_resolution">Độ phân giải tốt nhất</string>
|
||||
|
@ -28,7 +28,6 @@
|
||||
<string name="unsupported_url">不支持的 URL</string>
|
||||
<string name="settings_category_appearance_title">外观</string>
|
||||
<string name="all">全部</string>
|
||||
<string name="yes">是</string>
|
||||
<string name="network_error">网络错误</string>
|
||||
<plurals name="videos">
|
||||
<item quantity="other">%s 个视频</item>
|
||||
|
@ -86,7 +86,6 @@
|
||||
<string name="black_theme_title">純黑</string>
|
||||
<string name="popup_playing_toast">以畫中畫模式播放</string>
|
||||
<string name="all">所有</string>
|
||||
<string name="yes">是</string>
|
||||
<string name="app_ui_crash">App/界面閃退</string>
|
||||
<string name="info_labels">經過:\\n請求:\\n內容語言:\\n內容國家:\\nApp 語言:\\n服務:\\nGMT 時間:\\n封裝:\\n版本:\\nOS 版本:</string>
|
||||
<string name="short_thousand">千</string>
|
||||
|
@ -61,7 +61,6 @@
|
||||
<string name="downloads_title">下載</string>
|
||||
<string name="error_report_title">錯誤回報</string>
|
||||
<string name="all">全部</string>
|
||||
<string name="yes">是的</string>
|
||||
<string name="disabled">已停用</string>
|
||||
<string name="clear">清除</string>
|
||||
<string name="best_resolution">最佳解析度</string>
|
||||
|
@ -168,7 +168,6 @@
|
||||
<string name="songs">Songs</string>
|
||||
<string name="albums">Albums</string>
|
||||
<string name="artists">Artists</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="disabled">Disabled</string>
|
||||
<string name="clear">Clear</string>
|
||||
<string name="best_resolution">Best resolution</string>
|
||||
|
@ -2,8 +2,9 @@ package org.schabi.newpipe
|
||||
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.schabi.newpipe.util.ReleaseVersionUtil.coerceUpdateCheckExpiry
|
||||
import org.schabi.newpipe.util.ReleaseVersionUtil.isLastUpdateCheckExpired
|
||||
import java.time.Instant
|
||||
import java.time.ZoneId
|
||||
import java.time.format.DateTimeFormatter
|
||||
@ -11,18 +12,11 @@ import kotlin.math.abs
|
||||
|
||||
class NewVersionManagerTest {
|
||||
|
||||
private lateinit var manager: NewVersionManager
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
manager = NewVersionManager()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Expiry is reached`() {
|
||||
val oneHourEarlier = Instant.now().atZone(ZoneId.of("GMT")).minusHours(1)
|
||||
|
||||
val expired = manager.isExpired(oneHourEarlier.toEpochSecond())
|
||||
val expired = isLastUpdateCheckExpired(oneHourEarlier.toEpochSecond())
|
||||
|
||||
assertTrue(expired)
|
||||
}
|
||||
@ -31,7 +25,7 @@ class NewVersionManagerTest {
|
||||
fun `Expiry is not reached`() {
|
||||
val oneHourLater = Instant.now().atZone(ZoneId.of("GMT")).plusHours(1)
|
||||
|
||||
val expired = manager.isExpired(oneHourLater.toEpochSecond())
|
||||
val expired = isLastUpdateCheckExpired(oneHourLater.toEpochSecond())
|
||||
|
||||
assertFalse(expired)
|
||||
}
|
||||
@ -47,7 +41,7 @@ class NewVersionManagerTest {
|
||||
fun `Expiry must be returned as is because it is inside the acceptable range of 6-72 hours`() {
|
||||
val sixHoursLater = Instant.now().atZone(ZoneId.of("GMT")).plusHours(6)
|
||||
|
||||
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(sixHoursLater))
|
||||
val coerced = coerceUpdateCheckExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(sixHoursLater))
|
||||
|
||||
assertNearlyEqual(sixHoursLater.toEpochSecond(), coerced)
|
||||
}
|
||||
@ -56,7 +50,7 @@ class NewVersionManagerTest {
|
||||
fun `Expiry must be increased to 6 hours if below`() {
|
||||
val tooLow = Instant.now().atZone(ZoneId.of("GMT")).plusHours(5)
|
||||
|
||||
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooLow))
|
||||
val coerced = coerceUpdateCheckExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooLow))
|
||||
|
||||
assertNearlyEqual(tooLow.plusHours(1).toEpochSecond(), coerced)
|
||||
}
|
||||
@ -65,7 +59,7 @@ class NewVersionManagerTest {
|
||||
fun `Expiry must be decreased to 72 hours if above`() {
|
||||
val tooHigh = Instant.now().atZone(ZoneId.of("GMT")).plusHours(73)
|
||||
|
||||
val coerced = manager.coerceExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooHigh))
|
||||
val coerced = coerceUpdateCheckExpiry(DateTimeFormatter.RFC_1123_DATE_TIME.format(tooHigh))
|
||||
|
||||
assertNearlyEqual(tooHigh.minusHours(1).toEpochSecond(), coerced)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user