mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 15:23:00 +00:00 
			
		
		
		
	Merge branch 'dev' into bumpSomeLibraries
This commit is contained in:
		| @@ -220,6 +220,7 @@ dependencies { | |||||||
|     // https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01 |     // https://developer.android.com/jetpack/androidx/releases/viewpager2#1.1.0-alpha01 | ||||||
|     implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' |     implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' | ||||||
|     implementation 'androidx.webkit:webkit:1.4.0' |     implementation 'androidx.webkit:webkit:1.4.0' | ||||||
|  |     implementation 'androidx.work:work-runtime:2.7.1' | ||||||
|     implementation 'com.google.android.material:material:1.5.0' |     implementation 'com.google.android.material:material:1.5.0' | ||||||
|  |  | ||||||
| /** Third-party libraries **/ | /** Third-party libraries **/ | ||||||
|   | |||||||
| @@ -381,9 +381,6 @@ | |||||||
|         <service |         <service | ||||||
|             android:name=".RouterActivity$FetcherService" |             android:name=".RouterActivity$FetcherService" | ||||||
|             android:exported="false" /> |             android:exported="false" /> | ||||||
|         <service |  | ||||||
|             android:name=".CheckForNewAppVersion" |  | ||||||
|             android:exported="false" /> |  | ||||||
|  |  | ||||||
|         <!-- opting out of sending metrics to Google in Android System WebView --> |         <!-- opting out of sending metrics to Google in Android System WebView --> | ||||||
|         <meta-data android:name="android.webkit.WebView.MetricsOptOut" android:value="true" /> |         <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; | package org.schabi.newpipe; | ||||||
|  |  | ||||||
| import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService; |  | ||||||
| import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; | import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage; | ||||||
|  |  | ||||||
| import android.content.BroadcastReceiver; | import android.content.BroadcastReceiver; | ||||||
| @@ -174,10 +173,9 @@ public class MainActivity extends AppCompatActivity { | |||||||
|         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app); |         final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(app); | ||||||
|  |  | ||||||
|         if (prefs.getBoolean(app.getString(R.string.update_app_key), true)) { |         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. |             // and eventually searching for a new version. | ||||||
|             // The service searching for a new NewPipe version must not be started in background. |             NewVersionWorker.enqueueNewVersionCheckingWork(app); | ||||||
|             startNewVersionCheckService(); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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()) |                 new AlertDialog.Builder(requireContext()) | ||||||
|                         .setMessage(R.string.remove_watched_popup_warning) |                         .setMessage(R.string.remove_watched_popup_warning) | ||||||
|                         .setTitle(R.string.remove_watched_popup_title) |                         .setTitle(R.string.remove_watched_popup_title) | ||||||
|                         .setPositiveButton(R.string.yes, |                         .setPositiveButton(R.string.ok, | ||||||
|                                 (DialogInterface d, int id) -> removeWatchedStreams(false)) |                                 (DialogInterface d, int id) -> removeWatchedStreams(false)) | ||||||
|                         .setNeutralButton( |                         .setNeutralButton( | ||||||
|                                 R.string.remove_watched_popup_yes_and_partially_watched_videos, |                                 R.string.remove_watched_popup_yes_and_partially_watched_videos, | ||||||
|   | |||||||
| @@ -1,5 +1,9 @@ | |||||||
| package org.schabi.newpipe.player; | 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.ComponentName; | ||||||
| import android.content.Context; | import android.content.Context; | ||||||
| import android.content.Intent; | import android.content.Intent; | ||||||
| @@ -23,11 +27,9 @@ import androidx.recyclerview.widget.RecyclerView; | |||||||
| import com.google.android.exoplayer2.PlaybackParameters; | import com.google.android.exoplayer2.PlaybackParameters; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.database.stream.model.StreamEntity; |  | ||||||
| import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding; | import org.schabi.newpipe.databinding.ActivityPlayerQueueControlBinding; | ||||||
| import org.schabi.newpipe.extractor.stream.StreamInfo; | import org.schabi.newpipe.extractor.stream.StreamInfo; | ||||||
| import org.schabi.newpipe.fragments.OnScrollBelowItemsListener; | 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.event.PlayerEventListener; | ||||||
| import org.schabi.newpipe.player.helper.PlaybackParameterDialog; | import org.schabi.newpipe.player.helper.PlaybackParameterDialog; | ||||||
| import org.schabi.newpipe.player.playqueue.PlayQueue; | 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.ServiceHelper; | ||||||
| import org.schabi.newpipe.util.ThemeHelper; | 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 | public final class PlayQueueActivity extends AppCompatActivity | ||||||
|         implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, |         implements PlayerEventListener, SeekBar.OnSeekBarChangeListener, | ||||||
|         View.OnClickListener, PlaybackParameterDialog.Callback { |         View.OnClickListener, PlaybackParameterDialog.Callback { | ||||||
| @@ -129,7 +124,7 @@ public final class PlayQueueActivity extends AppCompatActivity | |||||||
|                 NavigationHelper.openSettings(this); |                 NavigationHelper.openSettings(this); | ||||||
|                 return true; |                 return true; | ||||||
|             case R.id.action_append_playlist: |             case R.id.action_append_playlist: | ||||||
|                 appendAllToPlaylist(); |                 player.onAddToPlaylistClicked(getSupportFragmentManager()); | ||||||
|                 return true; |                 return true; | ||||||
|             case R.id.action_playback_speed: |             case R.id.action_playback_speed: | ||||||
|                 openPlaybackParameterDialog(); |                 openPlaybackParameterDialog(); | ||||||
| @@ -443,24 +438,6 @@ public final class PlayQueueActivity extends AppCompatActivity | |||||||
|         seeking = false; |         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 |     // Binding Service Listener | ||||||
|     //////////////////////////////////////////////////////////////////////////// |     //////////////////////////////////////////////////////////////////////////// | ||||||
|   | |||||||
| @@ -105,6 +105,7 @@ import androidx.core.graphics.Insets; | |||||||
| import androidx.core.view.GestureDetectorCompat; | import androidx.core.view.GestureDetectorCompat; | ||||||
| import androidx.core.view.ViewCompat; | import androidx.core.view.ViewCompat; | ||||||
| import androidx.core.view.WindowInsetsCompat; | import androidx.core.view.WindowInsetsCompat; | ||||||
|  | import androidx.fragment.app.FragmentManager; | ||||||
| import androidx.preference.PreferenceManager; | import androidx.preference.PreferenceManager; | ||||||
| import androidx.recyclerview.widget.ItemTouchHelper; | import androidx.recyclerview.widget.ItemTouchHelper; | ||||||
| import androidx.recyclerview.widget.RecyclerView; | import androidx.recyclerview.widget.RecyclerView; | ||||||
| @@ -138,6 +139,7 @@ import com.squareup.picasso.Target; | |||||||
| import org.schabi.newpipe.DownloaderImpl; | import org.schabi.newpipe.DownloaderImpl; | ||||||
| import org.schabi.newpipe.MainActivity; | import org.schabi.newpipe.MainActivity; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
|  | import org.schabi.newpipe.database.stream.model.StreamEntity; | ||||||
| import org.schabi.newpipe.databinding.PlayerBinding; | import org.schabi.newpipe.databinding.PlayerBinding; | ||||||
| import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; | import org.schabi.newpipe.databinding.PlayerPopupCloseOverlayBinding; | ||||||
| import org.schabi.newpipe.error.ErrorInfo; | 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.fragments.detail.VideoDetailFragment; | ||||||
| import org.schabi.newpipe.info_list.StreamSegmentAdapter; | import org.schabi.newpipe.info_list.StreamSegmentAdapter; | ||||||
| import org.schabi.newpipe.ktx.AnimationType; | import org.schabi.newpipe.ktx.AnimationType; | ||||||
|  | import org.schabi.newpipe.local.dialog.PlaylistDialog; | ||||||
| import org.schabi.newpipe.local.history.HistoryRecordManager; | import org.schabi.newpipe.local.history.HistoryRecordManager; | ||||||
| import org.schabi.newpipe.player.MainPlayer.PlayerType; | import org.schabi.newpipe.player.MainPlayer.PlayerType; | ||||||
| import org.schabi.newpipe.player.event.DisplayPortion; | import org.schabi.newpipe.player.event.DisplayPortion; | ||||||
| @@ -197,6 +200,7 @@ import java.util.ArrayList; | |||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| import java.util.Optional; | import java.util.Optional; | ||||||
|  | import java.util.stream.Collectors; | ||||||
| import java.util.stream.IntStream; | import java.util.stream.IntStream; | ||||||
|  |  | ||||||
| import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; | import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; | ||||||
| @@ -541,6 +545,7 @@ public final class Player implements | |||||||
|         binding.segmentsButton.setOnClickListener(this); |         binding.segmentsButton.setOnClickListener(this); | ||||||
|         binding.repeatButton.setOnClickListener(this); |         binding.repeatButton.setOnClickListener(this); | ||||||
|         binding.shuffleButton.setOnClickListener(this); |         binding.shuffleButton.setOnClickListener(this); | ||||||
|  |         binding.addToPlaylistButton.setOnClickListener(this); | ||||||
|  |  | ||||||
|         binding.playPauseButton.setOnClickListener(this); |         binding.playPauseButton.setOnClickListener(this); | ||||||
|         binding.playPreviousButton.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 |     // Mute / Unmute | ||||||
|     //////////////////////////////////////////////////////////////////////////*/ |     //////////////////////////////////////////////////////////////////////////*/ | ||||||
| @@ -3131,6 +3162,7 @@ public final class Player implements | |||||||
|         binding.itemsListHeaderDuration.setVisibility(View.VISIBLE); |         binding.itemsListHeaderDuration.setVisibility(View.VISIBLE); | ||||||
|         binding.shuffleButton.setVisibility(View.VISIBLE); |         binding.shuffleButton.setVisibility(View.VISIBLE); | ||||||
|         binding.repeatButton.setVisibility(View.VISIBLE); |         binding.repeatButton.setVisibility(View.VISIBLE); | ||||||
|  |         binding.addToPlaylistButton.setVisibility(View.VISIBLE); | ||||||
|  |  | ||||||
|         hideControls(0, 0); |         hideControls(0, 0); | ||||||
|         binding.itemsListPanel.requestFocus(); |         binding.itemsListPanel.requestFocus(); | ||||||
| @@ -3168,6 +3200,7 @@ public final class Player implements | |||||||
|         binding.itemsListHeaderDuration.setVisibility(View.GONE); |         binding.itemsListHeaderDuration.setVisibility(View.GONE); | ||||||
|         binding.shuffleButton.setVisibility(View.GONE); |         binding.shuffleButton.setVisibility(View.GONE); | ||||||
|         binding.repeatButton.setVisibility(View.GONE); |         binding.repeatButton.setVisibility(View.GONE); | ||||||
|  |         binding.addToPlaylistButton.setVisibility(View.GONE); | ||||||
|  |  | ||||||
|         hideControls(0, 0); |         hideControls(0, 0); | ||||||
|         binding.itemsListPanel.requestFocus(); |         binding.itemsListPanel.requestFocus(); | ||||||
| @@ -3196,6 +3229,7 @@ public final class Player implements | |||||||
|  |  | ||||||
|         binding.shuffleButton.setVisibility(View.GONE); |         binding.shuffleButton.setVisibility(View.GONE); | ||||||
|         binding.repeatButton.setVisibility(View.GONE); |         binding.repeatButton.setVisibility(View.GONE); | ||||||
|  |         binding.addToPlaylistButton.setVisibility(View.GONE); | ||||||
|         binding.itemsListClose.setOnClickListener(view -> closeItemsList()); |         binding.itemsListClose.setOnClickListener(view -> closeItemsList()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -3733,6 +3767,11 @@ public final class Player implements | |||||||
|         } else if (v.getId() == binding.shuffleButton.getId()) { |         } else if (v.getId() == binding.shuffleButton.getId()) { | ||||||
|             onShuffleClicked(); |             onShuffleClicked(); | ||||||
|             return; |             return; | ||||||
|  |         } else if (v.getId() == binding.addToPlaylistButton.getId()) { | ||||||
|  |             if (getParentActivity() != null) { | ||||||
|  |                 onAddToPlaylistClicked(getParentActivity().getSupportFragmentManager()); | ||||||
|  |             } | ||||||
|  |             return; | ||||||
|         } else if (v.getId() == binding.moreOptionsButton.getId()) { |         } else if (v.getId() == binding.moreOptionsButton.getId()) { | ||||||
|             onMoreOptionsClicked(); |             onMoreOptionsClicked(); | ||||||
|         } else if (v.getId() == binding.share.getId()) { |         } else if (v.getId() == binding.share.getId()) { | ||||||
| @@ -3799,6 +3838,10 @@ public final class Player implements | |||||||
|             case KeyEvent.KEYCODE_SPACE: |             case KeyEvent.KEYCODE_SPACE: | ||||||
|                 if (isFullscreen) { |                 if (isFullscreen) { | ||||||
|                     playPause(); |                     playPause(); | ||||||
|  |                     if (isPlaying()) { | ||||||
|  |                         hideControls(0, 0); | ||||||
|  |                     } | ||||||
|  |                     return true; | ||||||
|                 } |                 } | ||||||
|                 break; |                 break; | ||||||
|             case KeyEvent.KEYCODE_BACK: |             case KeyEvent.KEYCODE_BACK: | ||||||
|   | |||||||
| @@ -88,6 +88,8 @@ public class PlayerMediaSession implements MediaSessionCallback { | |||||||
|     @Override |     @Override | ||||||
|     public void play() { |     public void play() { | ||||||
|         player.play(); |         player.play(); | ||||||
|  |         // hide the player controls even if the play command came from the media session | ||||||
|  |         player.hideControls(0, 0); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -7,10 +7,9 @@ import android.view.MenuItem; | |||||||
|  |  | ||||||
| import androidx.annotation.NonNull; | import androidx.annotation.NonNull; | ||||||
|  |  | ||||||
| import org.schabi.newpipe.App; |  | ||||||
| import org.schabi.newpipe.CheckForNewAppVersion; |  | ||||||
| import org.schabi.newpipe.MainActivity; | import org.schabi.newpipe.MainActivity; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
|  | import org.schabi.newpipe.util.ReleaseVersionUtil; | ||||||
|  |  | ||||||
| public class MainSettingsFragment extends BasePreferenceFragment { | public class MainSettingsFragment extends BasePreferenceFragment { | ||||||
|     public static final boolean DEBUG = MainActivity.DEBUG; |     public static final boolean DEBUG = MainActivity.DEBUG; | ||||||
| @@ -24,7 +23,7 @@ public class MainSettingsFragment extends BasePreferenceFragment { | |||||||
|         setHasOptionsMenu(true); // Otherwise onCreateOptionsMenu is not called |         setHasOptionsMenu(true); // Otherwise onCreateOptionsMenu is not called | ||||||
|  |  | ||||||
|         // Check if the app is updatable |         // Check if the app is updatable | ||||||
|         if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) { |         if (!ReleaseVersionUtil.isReleaseApk()) { | ||||||
|             getPreferenceScreen().removePreference( |             getPreferenceScreen().removePreference( | ||||||
|                     findPreference(getString(R.string.update_pref_screen_key))); |                     findPreference(getString(R.string.update_pref_screen_key))); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -191,7 +191,7 @@ public class PeertubeInstanceListFragment extends Fragment { | |||||||
|                 .setTitle(R.string.restore_defaults) |                 .setTitle(R.string.restore_defaults) | ||||||
|                 .setMessage(R.string.restore_defaults_confirmation) |                 .setMessage(R.string.restore_defaults_confirmation) | ||||||
|                 .setNegativeButton(R.string.cancel, null) |                 .setNegativeButton(R.string.cancel, null) | ||||||
|                 .setPositiveButton(R.string.yes, (dialog, which) -> { |                 .setPositiveButton(R.string.ok, (dialog, which) -> { | ||||||
|                     sharedPreferences.edit().remove(savedInstanceListKey).apply(); |                     sharedPreferences.edit().remove(savedInstanceListKey).apply(); | ||||||
|                     selectInstance(PeertubeInstance.defaultInstance); |                     selectInstance(PeertubeInstance.defaultInstance); | ||||||
|                     updateInstanceList(); |                     updateInstanceList(); | ||||||
|   | |||||||
| @@ -23,8 +23,6 @@ import androidx.preference.PreferenceFragmentCompat; | |||||||
|  |  | ||||||
| import com.jakewharton.rxbinding4.widget.RxTextView; | 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.MainActivity; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
| import org.schabi.newpipe.databinding.SettingsLayoutBinding; | 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.settings.preferencesearch.PreferenceSearcher; | ||||||
| import org.schabi.newpipe.util.DeviceUtils; | import org.schabi.newpipe.util.DeviceUtils; | ||||||
| import org.schabi.newpipe.util.KeyboardUtil; | import org.schabi.newpipe.util.KeyboardUtil; | ||||||
|  | import org.schabi.newpipe.util.ReleaseVersionUtil; | ||||||
| import org.schabi.newpipe.util.ThemeHelper; | import org.schabi.newpipe.util.ThemeHelper; | ||||||
| import org.schabi.newpipe.views.FocusOverlayView; | import org.schabi.newpipe.views.FocusOverlayView; | ||||||
|  |  | ||||||
| @@ -267,7 +266,7 @@ public class SettingsActivity extends AppCompatActivity implements | |||||||
|      */ |      */ | ||||||
|     private void ensureSearchRepresentsApplicationState() { |     private void ensureSearchRepresentsApplicationState() { | ||||||
|         // Check if the update settings are available |         // Check if the update settings are available | ||||||
|         if (!CheckForNewAppVersion.isReleaseApk(App.getApp())) { |         if (!ReleaseVersionUtil.isReleaseApk()) { | ||||||
|             SettingsResourceRegistry.getInstance() |             SettingsResourceRegistry.getInstance() | ||||||
|                     .getEntryByPreferencesResId(R.xml.update_settings) |                     .getEntryByPreferencesResId(R.xml.update_settings) | ||||||
|                     .setSearchable(false); |                     .setSearchable(false); | ||||||
|   | |||||||
| @@ -1,12 +1,11 @@ | |||||||
| package org.schabi.newpipe.settings; | package org.schabi.newpipe.settings; | ||||||
|  |  | ||||||
| import static org.schabi.newpipe.CheckForNewAppVersion.startNewVersionCheckService; |  | ||||||
|  |  | ||||||
| import android.os.Bundle; | import android.os.Bundle; | ||||||
| import android.widget.Toast; | import android.widget.Toast; | ||||||
|  |  | ||||||
| import androidx.preference.Preference; | import androidx.preference.Preference; | ||||||
|  |  | ||||||
|  | import org.schabi.newpipe.NewVersionWorker; | ||||||
| import org.schabi.newpipe.R; | import org.schabi.newpipe.R; | ||||||
|  |  | ||||||
| public class UpdateSettingsFragment extends BasePreferenceFragment { | 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. |         // Reset the expire time. This is necessary to check for an update immediately. | ||||||
|         defaultPreferences.edit() |         defaultPreferences.edit() | ||||||
|                 .putLong(getString(R.string.update_expiry_key), 0).apply(); |                 .putLong(getString(R.string.update_expiry_key), 0).apply(); | ||||||
|         startNewVersionCheckService(); |         NewVersionWorker.enqueueNewVersionCheckingWork(getContext()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|   | |||||||
| @@ -136,7 +136,7 @@ public class ChooseTabsFragment extends Fragment { | |||||||
|                 .setTitle(R.string.restore_defaults) |                 .setTitle(R.string.restore_defaults) | ||||||
|                 .setMessage(R.string.restore_defaults_confirmation) |                 .setMessage(R.string.restore_defaults_confirmation) | ||||||
|                 .setNegativeButton(R.string.cancel, null) |                 .setNegativeButton(R.string.cancel, null) | ||||||
|                 .setPositiveButton(R.string.yes, (dialog, which) -> { |                 .setPositiveButton(R.string.ok, (dialog, which) -> { | ||||||
|                     tabsManager.resetTabs(); |                     tabsManager.resetTabs(); | ||||||
|                     updateTabList(); |                     updateTabList(); | ||||||
|                     selectedTabsAdapter.notifyDataSetChanged(); |                     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:srcCompat="@drawable/ic_close" | ||||||
|                 app:tint="@color/white" /> |                 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 |             <androidx.appcompat.widget.AppCompatImageButton | ||||||
|                 android:id="@+id/repeatButton" |                 android:id="@+id/repeatButton" | ||||||
|                 android:layout_width="50dp" |                 android:layout_width="50dp" | ||||||
| @@ -620,7 +635,7 @@ | |||||||
|                 android:layout_width="wrap_content" |                 android:layout_width="wrap_content" | ||||||
|                 android:layout_height="wrap_content" |                 android:layout_height="wrap_content" | ||||||
|                 android:layout_centerVertical="true" |                 android:layout_centerVertical="true" | ||||||
|                 android:layout_toStartOf="@id/itemsListClose" |                 android:layout_toStartOf="@id/addToPlaylistButton" | ||||||
|                 android:layout_toEndOf="@id/shuffleButton" |                 android:layout_toEndOf="@id/shuffleButton" | ||||||
|                 android:gravity="center" |                 android:gravity="center" | ||||||
|                 android:textColor="@android:color/white" /> |                 android:textColor="@android:color/white" /> | ||||||
|   | |||||||
| @@ -91,7 +91,6 @@ | |||||||
|     <string name="show_age_restricted_content_title">محتوى مقيد للبالغين</string> |     <string name="show_age_restricted_content_title">محتوى مقيد للبالغين</string> | ||||||
|     <string name="duration_live">بث مباشر</string> |     <string name="duration_live">بث مباشر</string> | ||||||
|     <string name="error_report_title">تقرير عن المشكلة</string> |     <string name="error_report_title">تقرير عن المشكلة</string> | ||||||
|     <string name="yes">نعم</string> |  | ||||||
|     <string name="disabled">متوقف</string> |     <string name="disabled">متوقف</string> | ||||||
|     <string name="clear">تنظيف</string> |     <string name="clear">تنظيف</string> | ||||||
|     <string name="best_resolution">أفضل دقة</string> |     <string name="best_resolution">أفضل دقة</string> | ||||||
|   | |||||||
| @@ -169,7 +169,6 @@ | |||||||
|     <string name="best_resolution">Ən yaxşı görüntü keyfiyyəti</string> |     <string name="best_resolution">Ən yaxşı görüntü keyfiyyəti</string> | ||||||
|     <string name="clear">Təmizlə</string> |     <string name="clear">Təmizlə</string> | ||||||
|     <string name="disabled">Deaktiv edilib</string> |     <string name="disabled">Deaktiv edilib</string> | ||||||
|     <string name="yes">Bəli</string> |  | ||||||
|     <string name="artists">İfaçılar</string> |     <string name="artists">İfaçılar</string> | ||||||
|     <string name="albums">Albomlar</string> |     <string name="albums">Albomlar</string> | ||||||
|     <string name="songs">Mahnılar</string> |     <string name="songs">Mahnılar</string> | ||||||
|   | |||||||
| @@ -44,7 +44,6 @@ | |||||||
|     <string name="detail_dislikes_img_view_description">Tarrezmes</string> |     <string name="detail_dislikes_img_view_description">Tarrezmes</string> | ||||||
|     <string name="default_video_format_title">Formatu de videu predetermináu</string> |     <string name="default_video_format_title">Formatu de videu predetermináu</string> | ||||||
|     <string name="black_theme_title">Prietu</string> |     <string name="black_theme_title">Prietu</string> | ||||||
|     <string name="yes">Sí</string> |  | ||||||
|     <string name="short_thousand">mil</string> |     <string name="short_thousand">mil</string> | ||||||
|     <string name="short_million">mill.</string> |     <string name="short_million">mill.</string> | ||||||
|     <string name="short_billion">mil mill.</string> |     <string name="short_billion">mil mill.</string> | ||||||
|   | |||||||
| @@ -112,7 +112,6 @@ | |||||||
|     <string name="best_resolution">Eng yaxshi qaror</string> |     <string name="best_resolution">Eng yaxshi qaror</string> | ||||||
|     <string name="clear">Tozalash</string> |     <string name="clear">Tozalash</string> | ||||||
|     <string name="disabled">Ijrochilar o\'chirib qo\'yilgan</string> |     <string name="disabled">Ijrochilar o\'chirib qo\'yilgan</string> | ||||||
|     <string name="yes">Ha</string> |  | ||||||
|     <string name="artists">Artistlar</string> |     <string name="artists">Artistlar</string> | ||||||
|     <string name="albums">Albomlar</string> |     <string name="albums">Albomlar</string> | ||||||
|     <string name="songs">Qo\'shiqlar</string> |     <string name="songs">Qo\'shiqlar</string> | ||||||
|   | |||||||
| @@ -28,7 +28,6 @@ | |||||||
|     <string name="unsupported_url">不支持的 URL</string> |     <string name="unsupported_url">不支持的 URL</string> | ||||||
|     <string name="settings_category_appearance_title">外观</string> |     <string name="settings_category_appearance_title">外观</string> | ||||||
|     <string name="all">全部</string> |     <string name="all">全部</string> | ||||||
|     <string name="yes">是</string> |  | ||||||
|     <string name="network_error">网络错误</string> |     <string name="network_error">网络错误</string> | ||||||
|     <plurals name="videos"> |     <plurals name="videos"> | ||||||
|         <item quantity="other">%s 个视频</item> |         <item quantity="other">%s 个视频</item> | ||||||
|   | |||||||
| @@ -97,7 +97,6 @@ | |||||||
|     <string name="playlists">Плэйлісты</string> |     <string name="playlists">Плэйлісты</string> | ||||||
|     <string name="tracks">Дарожкі</string> |     <string name="tracks">Дарожкі</string> | ||||||
|     <string name="users">Карыстальнікі</string> |     <string name="users">Карыстальнікі</string> | ||||||
|     <string name="yes">Так</string> |  | ||||||
|     <string name="disabled">Адключана</string> |     <string name="disabled">Адключана</string> | ||||||
|     <string name="clear">Ачысціць</string> |     <string name="clear">Ачысціць</string> | ||||||
|     <string name="best_resolution">Лепшае разрозненне</string> |     <string name="best_resolution">Лепшае разрозненне</string> | ||||||
|   | |||||||
| @@ -56,7 +56,6 @@ | |||||||
|     <string name="file">ⴰⴼⴰⵢⵍⵓ</string> |     <string name="file">ⴰⴼⴰⵢⵍⵓ</string> | ||||||
|     <string name="play_all">ⵖⵔ ⵎⴰⵕⵕⴰ</string> |     <string name="play_all">ⵖⵔ ⵎⴰⵕⵕⴰ</string> | ||||||
|     <string name="file_deleted">ⵉⵜⵜⵡⴰⴽⴽⵙ ⵓⴼⴰⵢⵍⵓ</string> |     <string name="file_deleted">ⵉⵜⵜⵡⴰⴽⴽⵙ ⵓⴼⴰⵢⵍⵓ</string> | ||||||
|     <string name="yes">ⵢⴰⵀ</string> |  | ||||||
|     <string name="videos_string">ⵉⴼⵉⴷⵢⵓⵜⵏ</string> |     <string name="videos_string">ⵉⴼⵉⴷⵢⵓⵜⵏ</string> | ||||||
|     <string name="all">ⵎⴰⵕⵕⴰ</string> |     <string name="all">ⵎⴰⵕⵕⴰ</string> | ||||||
|     <string name="downloads_title">ⵓⴳⴳⴰⵎⵏ</string> |     <string name="downloads_title">ⵓⴳⴳⴰⵎⵏ</string> | ||||||
|   | |||||||
| @@ -75,7 +75,6 @@ | |||||||
|     <string name="downloads_title">Изтегляния</string> |     <string name="downloads_title">Изтегляния</string> | ||||||
|     <string name="error_report_title">Съобщение за грешка</string> |     <string name="error_report_title">Съобщение за грешка</string> | ||||||
|     <string name="all">Всички</string> |     <string name="all">Всички</string> | ||||||
|     <string name="yes">Да</string> |  | ||||||
|     <string name="disabled">Забранено</string> |     <string name="disabled">Забранено</string> | ||||||
|     <string name="clear">Изчисти</string> |     <string name="clear">Изчисти</string> | ||||||
|     <string name="best_resolution">Най-добра резолюция</string> |     <string name="best_resolution">Най-добра резолюция</string> | ||||||
|   | |||||||
| @@ -55,7 +55,6 @@ | |||||||
|     <string name="downloads_title">ডাউনলোডগুলি</string> |     <string name="downloads_title">ডাউনলোডগুলি</string> | ||||||
|     <string name="error_report_title">ত্রুটি প্রতিবেদন</string> |     <string name="error_report_title">ত্রুটি প্রতিবেদন</string> | ||||||
|     <string name="all">সবগুলি</string> |     <string name="all">সবগুলি</string> | ||||||
|     <string name="yes">হ্যাঁ</string> |  | ||||||
|     <string name="disabled">নিস্ক্রীয়</string> |     <string name="disabled">নিস্ক্রীয়</string> | ||||||
|     <string name="clear">পরিষ্কার</string> |     <string name="clear">পরিষ্কার</string> | ||||||
|     <!-- error strings --> |     <!-- error strings --> | ||||||
|   | |||||||
| @@ -53,7 +53,6 @@ | |||||||
|     <string name="always">সবসময়</string> |     <string name="always">সবসময়</string> | ||||||
|     <string name="clear">পরিষ্কার</string> |     <string name="clear">পরিষ্কার</string> | ||||||
|     <string name="disabled">নিস্ক্রীয়</string> |     <string name="disabled">নিস্ক্রীয়</string> | ||||||
|     <string name="yes">হ্যাঁ</string> |  | ||||||
|     <string name="all">সবগুলি</string> |     <string name="all">সবগুলি</string> | ||||||
|     <string name="error_report_title">ত্রুটি প্রতিবেদন</string> |     <string name="error_report_title">ত্রুটি প্রতিবেদন</string> | ||||||
|     <string name="downloads_title">ডাউনলোডগুলি</string> |     <string name="downloads_title">ডাউনলোডগুলি</string> | ||||||
|   | |||||||
| @@ -176,7 +176,6 @@ | |||||||
|     <string name="best_resolution">সেরা রেজুলিউসন</string> |     <string name="best_resolution">সেরা রেজুলিউসন</string> | ||||||
|     <string name="clear">পরিষ্কার</string> |     <string name="clear">পরিষ্কার</string> | ||||||
|     <string name="disabled">নিস্ক্রীয়</string> |     <string name="disabled">নিস্ক্রীয়</string> | ||||||
|     <string name="yes">হ্যাঁ</string> |  | ||||||
|     <string name="artists">শিল্পীরা</string> |     <string name="artists">শিল্পীরা</string> | ||||||
|     <string name="albums">অ্যালবাম গুলি</string> |     <string name="albums">অ্যালবাম গুলি</string> | ||||||
|     <string name="songs">গান গুলি</string> |     <string name="songs">গান গুলি</string> | ||||||
|   | |||||||
| @@ -37,7 +37,6 @@ | |||||||
|     <string name="downloads">Baixades</string> |     <string name="downloads">Baixades</string> | ||||||
|     <string name="downloads_title">Baixades</string> |     <string name="downloads_title">Baixades</string> | ||||||
|     <string name="all">Tot</string> |     <string name="all">Tot</string> | ||||||
|     <string name="yes">Sí</string> |  | ||||||
|     <string name="disabled">Desactivat</string> |     <string name="disabled">Desactivat</string> | ||||||
|     <string name="clear">Neteja</string> |     <string name="clear">Neteja</string> | ||||||
|     <string name="best_resolution">Millor resolució</string> |     <string name="best_resolution">Millor resolució</string> | ||||||
|   | |||||||
| @@ -101,7 +101,6 @@ | |||||||
|     <string name="title_licenses">مۆڵەتنامەی لایەنی-سێیەم</string> |     <string name="title_licenses">مۆڵەتنامەی لایەنی-سێیەم</string> | ||||||
|     <string name="app_license_title">مۆڵەتنامەی نیوپایپ</string> |     <string name="app_license_title">مۆڵەتنامەی نیوپایپ</string> | ||||||
|     <string name="show_hold_to_append_title">پیشاندانی ڕێنمایی ”داگرتن تا پاشکۆ”</string> |     <string name="show_hold_to_append_title">پیشاندانی ڕێنمایی ”داگرتن تا پاشکۆ”</string> | ||||||
|     <string name="yes">بەڵێ</string> |  | ||||||
|     <string name="msg_threads">دابەشکراوەکان</string> |     <string name="msg_threads">دابەشکراوەکان</string> | ||||||
|     <string name="title_most_played">زۆرترین لێدراو</string> |     <string name="title_most_played">زۆرترین لێدراو</string> | ||||||
|     <string name="unbookmark_playlist">لادانی نیشانهكراو</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="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="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="all">Vše</string> | ||||||
|     <string name="yes">Ano</string> |  | ||||||
|     <string name="short_thousand">tis.</string> |     <string name="short_thousand">tis.</string> | ||||||
|     <string name="open_in_popup_mode">Otevřít ve vyskakovacím okně</string> |     <string name="open_in_popup_mode">Otevřít ve vyskakovacím okně</string> | ||||||
|     <string name="short_million">mil.</string> |     <string name="short_million">mil.</string> | ||||||
|   | |||||||
| @@ -108,7 +108,6 @@ | |||||||
|     </plurals> |     </plurals> | ||||||
|     <string name="tracks">Numre</string> |     <string name="tracks">Numre</string> | ||||||
|     <string name="users">Brugere</string> |     <string name="users">Brugere</string> | ||||||
|     <string name="yes">Ja</string> |  | ||||||
|     <string name="disabled">Slået fra</string> |     <string name="disabled">Slået fra</string> | ||||||
|     <string name="clear">Slet</string> |     <string name="clear">Slet</string> | ||||||
|     <string name="best_resolution">Bedste opløsning</string> |     <string name="best_resolution">Bedste opløsning</string> | ||||||
|   | |||||||
| @@ -88,7 +88,6 @@ | |||||||
|     <string name="black_theme_title">Schwarz</string> |     <string name="black_theme_title">Schwarz</string> | ||||||
|     <string name="title_activity_recaptcha">reCAPTCHA-Aufgabe</string> |     <string name="title_activity_recaptcha">reCAPTCHA-Aufgabe</string> | ||||||
|     <string name="recaptcha_request_toast">reCAPTCHA-Aufgabe angefordert</string> |     <string name="recaptcha_request_toast">reCAPTCHA-Aufgabe angefordert</string> | ||||||
|     <string name="yes">Ja</string> |  | ||||||
|     <string name="all">Alle</string> |     <string name="all">Alle</string> | ||||||
|     <string name="disabled">Deaktiviert</string> |     <string name="disabled">Deaktiviert</string> | ||||||
|     <string name="open_in_popup_mode">Im Pop-up-Modus öffnen</string> |     <string name="open_in_popup_mode">Im Pop-up-Modus öffnen</string> | ||||||
|   | |||||||
| @@ -52,7 +52,6 @@ | |||||||
|     <string name="downloads">Λήψεις</string> |     <string name="downloads">Λήψεις</string> | ||||||
|     <string name="downloads_title">Λήψεις</string> |     <string name="downloads_title">Λήψεις</string> | ||||||
|     <string name="all">Όλα</string> |     <string name="all">Όλα</string> | ||||||
|     <string name="yes">Ναι</string> |  | ||||||
|     <string name="general_error">Σφάλμα</string> |     <string name="general_error">Σφάλμα</string> | ||||||
|     <string name="error_snackbar_action">Αναφορά</string> |     <string name="error_snackbar_action">Αναφορά</string> | ||||||
|     <string name="what_device_headline">Πληροφορίες:</string> |     <string name="what_device_headline">Πληροφορίες:</string> | ||||||
|   | |||||||
| @@ -95,7 +95,6 @@ | |||||||
|         <item quantity="one">%s filmeto</item> |         <item quantity="one">%s filmeto</item> | ||||||
|         <item quantity="other">%s filmetoj</item> |         <item quantity="other">%s filmetoj</item> | ||||||
|     </plurals> |     </plurals> | ||||||
|     <string name="yes">Jes</string> |  | ||||||
|     <string name="msg_popup_permission">Tiu permeso estas necesa por |     <string name="msg_popup_permission">Tiu permeso estas necesa por | ||||||
| \nmalfermi en ŝprucfenestra modo</string> | \nmalfermi en ŝprucfenestra modo</string> | ||||||
|     <string name="popup_playing_toast">Ludante 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="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="black_theme_title">Negro</string> | ||||||
|     <string name="all">Todo</string> |     <string name="all">Todo</string> | ||||||
|     <string name="yes">Sí</string> |  | ||||||
|     <string name="short_thousand">k</string> |     <string name="short_thousand">k</string> | ||||||
|     <string name="short_million">M</string> |     <string name="short_million">M</string> | ||||||
|     <string name="short_billion">MM</string> |     <string name="short_billion">MM</string> | ||||||
|   | |||||||
| @@ -93,7 +93,6 @@ | |||||||
|     <string name="downloads_title">Allalaadimised</string> |     <string name="downloads_title">Allalaadimised</string> | ||||||
|     <string name="error_report_title">Vea teatamine</string> |     <string name="error_report_title">Vea teatamine</string> | ||||||
|     <string name="all">Kõik</string> |     <string name="all">Kõik</string> | ||||||
|     <string name="yes">Jah</string> |  | ||||||
|     <string name="disabled">Keelatud</string> |     <string name="disabled">Keelatud</string> | ||||||
|     <string name="clear">Kustuta</string> |     <string name="clear">Kustuta</string> | ||||||
|     <string name="best_resolution">Parim lahutus</string> |     <string name="best_resolution">Parim lahutus</string> | ||||||
|   | |||||||
| @@ -62,7 +62,6 @@ | |||||||
|     <string name="downloads_title">Deskargak</string> |     <string name="downloads_title">Deskargak</string> | ||||||
|     <string name="error_report_title">Errore-txostena</string> |     <string name="error_report_title">Errore-txostena</string> | ||||||
|     <string name="all">Dena</string> |     <string name="all">Dena</string> | ||||||
|     <string name="yes">Bai</string> |  | ||||||
|     <string name="disabled">Desgaituta</string> |     <string name="disabled">Desgaituta</string> | ||||||
|     <string name="clear">Garbitu</string> |     <string name="clear">Garbitu</string> | ||||||
|     <string name="best_resolution">Bereizmen onena</string> |     <string name="best_resolution">Bereizmen onena</string> | ||||||
|   | |||||||
| @@ -109,7 +109,6 @@ | |||||||
|     <string name="channels">کانالها</string> |     <string name="channels">کانالها</string> | ||||||
|     <string name="playlists">سیاهههای پخش</string> |     <string name="playlists">سیاهههای پخش</string> | ||||||
|     <string name="users">کاربران</string> |     <string name="users">کاربران</string> | ||||||
|     <string name="yes">بله</string> |  | ||||||
|     <string name="disabled">غیرفعال</string> |     <string name="disabled">غیرفعال</string> | ||||||
|     <string name="clear">پاککردن</string> |     <string name="clear">پاککردن</string> | ||||||
|     <string name="play_all">پخش همه</string> |     <string name="play_all">پخش همه</string> | ||||||
|   | |||||||
| @@ -73,7 +73,6 @@ | |||||||
|     <string name="downloads_title">Lataukset</string> |     <string name="downloads_title">Lataukset</string> | ||||||
|     <string name="error_report_title">Virheraportti</string> |     <string name="error_report_title">Virheraportti</string> | ||||||
|     <string name="all">Kaikki</string> |     <string name="all">Kaikki</string> | ||||||
|     <string name="yes">Kyllä</string> |  | ||||||
|     <string name="disabled">Poistettu käytöstä</string> |     <string name="disabled">Poistettu käytöstä</string> | ||||||
|     <string name="clear">Pyyhi</string> |     <string name="clear">Pyyhi</string> | ||||||
|     <string name="best_resolution">Paras resoluutio</string> |     <string name="best_resolution">Paras resoluutio</string> | ||||||
|   | |||||||
| @@ -86,7 +86,6 @@ | |||||||
|     <string name="recaptcha_request_toast">Défi reCAPTCHA demandé</string> |     <string name="recaptcha_request_toast">Défi reCAPTCHA demandé</string> | ||||||
|     <string name="open_in_popup_mode">Ouvrir en mode pop-up</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="popup_playing_toast">Lecture en mode flottant</string> | ||||||
|     <string name="yes">Oui</string> |  | ||||||
|     <string name="disabled">Désactivés</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="info_labels">Quoi :\\nRequest :\\nContent Language :\\nContent Country :\\nApp Language :\\nService :\\nGMT Time :\\nPackage :\\nVersion :\\nOS version :</string> | ||||||
|     <string name="short_thousand">k</string> |     <string name="short_thousand">k</string> | ||||||
|   | |||||||
| @@ -96,7 +96,6 @@ | |||||||
|     <string name="playlists">Listas de reprodución</string> |     <string name="playlists">Listas de reprodución</string> | ||||||
|     <string name="tracks">Pistas</string> |     <string name="tracks">Pistas</string> | ||||||
|     <string name="users">Usuarios</string> |     <string name="users">Usuarios</string> | ||||||
|     <string name="yes">Si</string> |  | ||||||
|     <string name="disabled">Desactivado</string> |     <string name="disabled">Desactivado</string> | ||||||
|     <string name="clear">Limpar</string> |     <string name="clear">Limpar</string> | ||||||
|     <string name="best_resolution">Mellor resolución</string> |     <string name="best_resolution">Mellor resolución</string> | ||||||
|   | |||||||
| @@ -58,7 +58,6 @@ | |||||||
|     <string name="downloads_title">הורדות</string> |     <string name="downloads_title">הורדות</string> | ||||||
|     <string name="error_report_title">דוח שגיאה</string> |     <string name="error_report_title">דוח שגיאה</string> | ||||||
|     <string name="all">הכול</string> |     <string name="all">הכול</string> | ||||||
|     <string name="yes">כן</string> |  | ||||||
|     <string name="disabled">מושבת</string> |     <string name="disabled">מושבת</string> | ||||||
|     <string name="clear">ניקוי</string> |     <string name="clear">ניקוי</string> | ||||||
|     <string name="general_error">שגיאה</string> |     <string name="general_error">שגיאה</string> | ||||||
|   | |||||||
| @@ -90,7 +90,6 @@ | |||||||
|     <string name="downloads_title">डाउनलोड</string> |     <string name="downloads_title">डाउनलोड</string> | ||||||
|     <string name="error_report_title">त्रुटी की रिपोर्ट</string> |     <string name="error_report_title">त्रुटी की रिपोर्ट</string> | ||||||
|     <string name="all">सारे</string> |     <string name="all">सारे</string> | ||||||
|     <string name="yes">सहमत हूँ</string> |  | ||||||
|     <string name="disabled">बंद करे</string> |     <string name="disabled">बंद करे</string> | ||||||
|     <string name="clear">साफ़</string> |     <string name="clear">साफ़</string> | ||||||
|     <string name="best_resolution">बेहतर विडियो की क्वालिटी</string> |     <string name="best_resolution">बेहतर विडियो की क्वालिटी</string> | ||||||
|   | |||||||
| @@ -71,7 +71,6 @@ | |||||||
|     <string name="downloads_title">Preuzimanja</string> |     <string name="downloads_title">Preuzimanja</string> | ||||||
|     <string name="error_report_title">Prijavi grešku</string> |     <string name="error_report_title">Prijavi grešku</string> | ||||||
|     <string name="all">Sve</string> |     <string name="all">Sve</string> | ||||||
|     <string name="yes">Da</string> |  | ||||||
|     <string name="disabled">Isključeno</string> |     <string name="disabled">Isključeno</string> | ||||||
|     <string name="clear">Očisti</string> |     <string name="clear">Očisti</string> | ||||||
|     <string name="best_resolution">Najbolja rezolucija</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="settings_category_debug_title">Hibaelhárítás</string> | ||||||
|     <string name="popup_playing_toast">Lejátszás felugró ablakban</string> |     <string name="popup_playing_toast">Lejátszás felugró ablakban</string> | ||||||
|     <string name="all">Összes</string> |     <string name="all">Összes</string> | ||||||
|     <string name="yes">Igen</string> |  | ||||||
|     <string name="disabled">Letiltva</string> |     <string name="disabled">Letiltva</string> | ||||||
|     <string name="clear">Törlés</string> |     <string name="clear">Törlés</string> | ||||||
|     <string name="best_resolution">Legjobb felbontás</string> |     <string name="best_resolution">Legjobb felbontás</string> | ||||||
|   | |||||||
| @@ -65,7 +65,6 @@ | |||||||
|     <string name="file_deleted">Ֆայլը ջնջվեց</string> |     <string name="file_deleted">Ֆայլը ջնջվեց</string> | ||||||
|     <string name="file">Ֆայլ</string> |     <string name="file">Ֆայլ</string> | ||||||
|     <string name="songs">Երգեր</string> |     <string name="songs">Երգեր</string> | ||||||
|     <string name="yes">Այո</string> |  | ||||||
|     <string name="enable_search_history_title">Որոնման պատմություն</string> |     <string name="enable_search_history_title">Որոնման պատմություն</string> | ||||||
|     <string name="close">Փակել</string> |     <string name="close">Փակել</string> | ||||||
|     <plurals name="days"> |     <plurals name="days"> | ||||||
|   | |||||||
| @@ -85,7 +85,6 @@ | |||||||
|     <string name="tracks">Pistas</string> |     <string name="tracks">Pistas</string> | ||||||
|     <string name="users">Usatores</string> |     <string name="users">Usatores</string> | ||||||
|     <string name="events">Eventos</string> |     <string name="events">Eventos</string> | ||||||
|     <string name="yes">Si</string> |  | ||||||
|     <string name="disabled">Disactivate</string> |     <string name="disabled">Disactivate</string> | ||||||
|     <string name="clear">Vacuar</string> |     <string name="clear">Vacuar</string> | ||||||
|     <string name="best_resolution">Melior resolution</string> |     <string name="best_resolution">Melior resolution</string> | ||||||
|   | |||||||
| @@ -86,7 +86,6 @@ | |||||||
|     <string name="short_thousand">r</string> |     <string name="short_thousand">r</string> | ||||||
|     <string name="short_million">J</string> |     <string name="short_million">J</string> | ||||||
|     <string name="short_billion">T</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="open_in_popup_mode">Buka dalam mode popup</string> | ||||||
|     <string name="msg_popup_permission">Izin ini dibutuhkan untuk |     <string name="msg_popup_permission">Izin ini dibutuhkan untuk | ||||||
| \nmembuka di mode sembul</string> | \nmembuka di mode sembul</string> | ||||||
|   | |||||||
| @@ -87,7 +87,6 @@ | |||||||
|     <string name="short_million">M</string> |     <string name="short_million">M</string> | ||||||
|     <string name="short_billion">Mrd</string> |     <string name="short_billion">Mrd</string> | ||||||
|     <string name="recaptcha_request_toast">È richiesta la risoluzione del reCAPTCHA</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="open_in_popup_mode">Apri in modalità popup</string> | ||||||
|     <string name="popup_playing_toast">Riproduzione in modalità popup</string> |     <string name="popup_playing_toast">Riproduzione in modalità popup</string> | ||||||
|     <string name="disabled">Disattivato</string> |     <string name="disabled">Disattivato</string> | ||||||
|   | |||||||
| @@ -87,7 +87,6 @@ | |||||||
|     <string name="short_thousand">k</string> |     <string name="short_thousand">k</string> | ||||||
|     <string name="short_million">M</string> |     <string name="short_million">M</string> | ||||||
|     <string name="short_billion">B</string> |     <string name="short_billion">B</string> | ||||||
|     <string name="yes">はい</string> |  | ||||||
|     <string name="open_in_popup_mode">ポップアップモードで開く</string> |     <string name="open_in_popup_mode">ポップアップモードで開く</string> | ||||||
|     <string name="msg_popup_permission">ポップアップモードで開くには |     <string name="msg_popup_permission">ポップアップモードで開くには | ||||||
| \n権限の許可が必要です</string> | \n権限の許可が必要です</string> | ||||||
|   | |||||||
| @@ -30,7 +30,6 @@ | |||||||
|     <string name="export_to">Sifeḍ ɣer</string> |     <string name="export_to">Sifeḍ ɣer</string> | ||||||
|     <string name="controls_add_to_playlist_title">Rnu ɣer</string> |     <string name="controls_add_to_playlist_title">Rnu ɣer</string> | ||||||
|     <string name="playback_step">Amecwaṛ</string> |     <string name="playback_step">Amecwaṛ</string> | ||||||
|     <string name="yes">Ih</string> |  | ||||||
|     <string name="msg_running">Azdam n NewPipe</string> |     <string name="msg_running">Azdam n NewPipe</string> | ||||||
|     <string name="restore_defaults">Err-d imezwar</string> |     <string name="restore_defaults">Err-d imezwar</string> | ||||||
|     <string name="channel_created_by">Yerna-t %s</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="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="clear">Zelal</string> | ||||||
|     <string name="disabled">Bêmecel</string> |     <string name="disabled">Bêmecel</string> | ||||||
|     <string name="yes">Erê</string> |  | ||||||
|     <string name="artists">Hunermend</string> |     <string name="artists">Hunermend</string> | ||||||
|     <string name="albums">Album</string> |     <string name="albums">Album</string> | ||||||
|     <string name="songs">Stran</string> |     <string name="songs">Stran</string> | ||||||
|   | |||||||
| @@ -102,7 +102,6 @@ | |||||||
|     <string name="popup_playing_toast">팝업 모드에서 재생 중</string> |     <string name="popup_playing_toast">팝업 모드에서 재생 중</string> | ||||||
|     <string name="error_report_title">오류 보고</string> |     <string name="error_report_title">오류 보고</string> | ||||||
|     <string name="all">전부</string> |     <string name="all">전부</string> | ||||||
|     <string name="yes">네</string> |  | ||||||
|     <string name="disabled">해제됨</string> |     <string name="disabled">해제됨</string> | ||||||
|     <string name="clear">지우기</string> |     <string name="clear">지우기</string> | ||||||
|     <string name="best_resolution">최대 해상도</string> |     <string name="best_resolution">최대 해상도</string> | ||||||
|   | |||||||
| @@ -86,7 +86,6 @@ | |||||||
|     <string name="downloads_title">دابەزاندنەکان</string> |     <string name="downloads_title">دابەزاندنەکان</string> | ||||||
|     <string name="error_report_title">ناتوانرێ سکاڵابکرێ</string> |     <string name="error_report_title">ناتوانرێ سکاڵابکرێ</string> | ||||||
|     <string name="all">گشتی</string> |     <string name="all">گشتی</string> | ||||||
|     <string name="yes">بەڵێ</string> |  | ||||||
|     <string name="disabled">ناکارایە</string> |     <string name="disabled">ناکارایە</string> | ||||||
|     <string name="clear">پاککردنەوە</string> |     <string name="clear">پاککردنەوە</string> | ||||||
|     <string name="best_resolution">باشترین قەبارە</string> |     <string name="best_resolution">باشترین قەبارە</string> | ||||||
|   | |||||||
| @@ -58,7 +58,6 @@ | |||||||
|     <string name="downloads_title">Atsisiuntimai</string> |     <string name="downloads_title">Atsisiuntimai</string> | ||||||
|     <string name="error_report_title">Klaidų ataskaita</string> |     <string name="error_report_title">Klaidų ataskaita</string> | ||||||
|     <string name="all">Visi</string> |     <string name="all">Visi</string> | ||||||
|     <string name="yes">Taip</string> |  | ||||||
|     <string name="disabled">Išjungta</string> |     <string name="disabled">Išjungta</string> | ||||||
|     <string name="clear">Išvalyti</string> |     <string name="clear">Išvalyti</string> | ||||||
|     <string name="best_resolution">Geriausia raiška</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="best_resolution">Labākā izšķirtspēja</string> | ||||||
|     <string name="clear">Notīrīt</string> |     <string name="clear">Notīrīt</string> | ||||||
|     <string name="disabled">Atspējots</string> |     <string name="disabled">Atspējots</string> | ||||||
|     <string name="yes">Jā</string> |  | ||||||
|     <string name="artists">Mākslinieki</string> |     <string name="artists">Mākslinieki</string> | ||||||
|     <string name="albums">Albūmi</string> |     <string name="albums">Albūmi</string> | ||||||
|     <string name="songs">Dziesmas</string> |     <string name="songs">Dziesmas</string> | ||||||
|   | |||||||
| @@ -93,7 +93,6 @@ | |||||||
|     <string name="downloads_title">Превземања</string> |     <string name="downloads_title">Превземања</string> | ||||||
|     <string name="error_report_title">Извештај за грешки</string> |     <string name="error_report_title">Извештај за грешки</string> | ||||||
|     <string name="all">Сите</string> |     <string name="all">Сите</string> | ||||||
|     <string name="yes">Да</string> |  | ||||||
|     <string name="disabled">Оневозможено</string> |     <string name="disabled">Оневозможено</string> | ||||||
|     <string name="clear">Избриши</string> |     <string name="clear">Избриши</string> | ||||||
|     <string name="best_resolution">Најдобра резолуција</string> |     <string name="best_resolution">Најдобра резолуција</string> | ||||||
|   | |||||||
| @@ -270,7 +270,6 @@ | |||||||
|     <string name="best_resolution">മികച്ച റിസല്യൂഷൻ</string> |     <string name="best_resolution">മികച്ച റിസല്യൂഷൻ</string> | ||||||
|     <string name="clear">തെളിക്കുക</string> |     <string name="clear">തെളിക്കുക</string> | ||||||
|     <string name="disabled">അസാധുവാക്കപ്പെട്ടു</string> |     <string name="disabled">അസാധുവാക്കപ്പെട്ടു</string> | ||||||
|     <string name="yes">അതെ</string> |  | ||||||
|     <string name="artists">കലാകാരന്മാർ</string> |     <string name="artists">കലാകാരന്മാർ</string> | ||||||
|     <string name="albums">ആൽബങ്ങൾ</string> |     <string name="albums">ആൽബങ്ങൾ</string> | ||||||
|     <string name="songs">പാട്ടുകൾ</string> |     <string name="songs">പാട്ടുകൾ</string> | ||||||
|   | |||||||
| @@ -105,7 +105,6 @@ | |||||||
|     <string name="tracks">Trek</string> |     <string name="tracks">Trek</string> | ||||||
|     <string name="users">Pengguna</string> |     <string name="users">Pengguna</string> | ||||||
|     <string name="events">Peristiwa</string> |     <string name="events">Peristiwa</string> | ||||||
|     <string name="yes">Ya</string> |  | ||||||
|     <string name="disabled">Dinyahdayakan</string> |     <string name="disabled">Dinyahdayakan</string> | ||||||
|     <string name="clear">Bersihkan</string> |     <string name="clear">Bersihkan</string> | ||||||
|     <string name="best_resolution">Resolusi terbaik</string> |     <string name="best_resolution">Resolusi terbaik</string> | ||||||
|   | |||||||
| @@ -89,7 +89,6 @@ | |||||||
|     <string name="black_theme_title">Svart</string> |     <string name="black_theme_title">Svart</string> | ||||||
|     <string name="popup_playing_toast">Spiller av i oppsprettsmodus</string> |     <string name="popup_playing_toast">Spiller av i oppsprettsmodus</string> | ||||||
|     <string name="all">Alle</string> |     <string name="all">Alle</string> | ||||||
|     <string name="yes">Ja</string> |  | ||||||
|     <string name="disabled">Avskrudd</string> |     <string name="disabled">Avskrudd</string> | ||||||
|     <string name="short_thousand">k</string> |     <string name="short_thousand">k</string> | ||||||
|     <string name="short_million">M</string> |     <string name="short_million">M</string> | ||||||
|   | |||||||
| @@ -111,7 +111,6 @@ | |||||||
|     <string name="tracks">ट्रयाकहरु</string> |     <string name="tracks">ट्रयाकहरु</string> | ||||||
|     <string name="users">प्रयोगकर्ताहरु</string> |     <string name="users">प्रयोगकर्ताहरु</string> | ||||||
|     <string name="events">घटनाहरू</string> |     <string name="events">घटनाहरू</string> | ||||||
|     <string name="yes">हो</string> |  | ||||||
|     <string name="disabled">अक्षम</string> |     <string name="disabled">अक्षम</string> | ||||||
|     <string name="clear">स्पष्ट</string> |     <string name="clear">स्पष्ट</string> | ||||||
|     <string name="best_resolution">सर्वश्रेष्ठ रेसोलुशन</string> |     <string name="best_resolution">सर्वश्रेष्ठ रेसोलुशन</string> | ||||||
|   | |||||||
| @@ -93,7 +93,6 @@ | |||||||
|     <string name="downloads_title">Downloads</string> |     <string name="downloads_title">Downloads</string> | ||||||
|     <string name="error_report_title">Foutrapport</string> |     <string name="error_report_title">Foutrapport</string> | ||||||
|     <string name="all">Alles</string> |     <string name="all">Alles</string> | ||||||
|     <string name="yes">Ja</string> |  | ||||||
|     <string name="disabled">Uitgeschakeld</string> |     <string name="disabled">Uitgeschakeld</string> | ||||||
|     <string name="clear">Wissen</string> |     <string name="clear">Wissen</string> | ||||||
|     <string name="best_resolution">Beste resolutie</string> |     <string name="best_resolution">Beste resolutie</string> | ||||||
|   | |||||||
| @@ -85,7 +85,6 @@ | |||||||
|     <string name="recaptcha_request_toast">reCAPTCHA-uitdaging gevraagd</string> |     <string name="recaptcha_request_toast">reCAPTCHA-uitdaging gevraagd</string> | ||||||
|     <string name="open_in_popup_mode">Openen in pop-upmodus</string> |     <string name="open_in_popup_mode">Openen in pop-upmodus</string> | ||||||
|     <string name="all">Alles</string> |     <string name="all">Alles</string> | ||||||
|     <string name="yes">Ja</string> |  | ||||||
|     <string name="short_thousand">k</string> |     <string name="short_thousand">k</string> | ||||||
|     <string name="short_million">M</string> |     <string name="short_million">M</string> | ||||||
|     <string name="short_billion">B</string> |     <string name="short_billion">B</string> | ||||||
|   | |||||||
| @@ -92,7 +92,6 @@ | |||||||
|     <string name="downloads_title">ਡਾਊਨਲੋਡਸ</string> |     <string name="downloads_title">ਡਾਊਨਲੋਡਸ</string> | ||||||
|     <string name="error_report_title">Error ਰਿਪੋਰਟ</string> |     <string name="error_report_title">Error ਰਿਪੋਰਟ</string> | ||||||
|     <string name="all">ਸਾਰੇ</string> |     <string name="all">ਸਾਰੇ</string> | ||||||
|     <string name="yes">ਹਾਂ</string> |  | ||||||
|     <string name="disabled">ਬੰਦ ਕੀਤਾ</string> |     <string name="disabled">ਬੰਦ ਕੀਤਾ</string> | ||||||
|     <string name="clear">ਮਿਟਾਓ</string> |     <string name="clear">ਮਿਟਾਓ</string> | ||||||
|     <string name="best_resolution">ਵਧੀਆ Resolution</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="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="popup_playing_toast">Odtwarzanie w trybie okienkowym</string> | ||||||
|     <string name="all">Wszystkie</string> |     <string name="all">Wszystkie</string> | ||||||
|     <string name="yes">Tak</string> |  | ||||||
|     <string name="disabled">Wyłączone</string> |     <string name="disabled">Wyłączone</string> | ||||||
|     <string name="clear">Wyczyść</string> |     <string name="clear">Wyczyść</string> | ||||||
|     <string name="short_thousand">tys.</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="default_video_format_title">Formato de vídeo padrão</string> | ||||||
|     <string name="popup_playing_toast">Reproduzindo em modo popup</string> |     <string name="popup_playing_toast">Reproduzindo em modo popup</string> | ||||||
|     <string name="all">Tudo</string> |     <string name="all">Tudo</string> | ||||||
|     <string name="yes">Sim</string> |  | ||||||
|     <string name="disabled">Desativado</string> |     <string name="disabled">Desativado</string> | ||||||
|     <string name="short_thousand">k</string> |     <string name="short_thousand">k</string> | ||||||
|     <string name="short_million">M</string> |     <string name="short_million">M</string> | ||||||
|   | |||||||
| @@ -395,7 +395,6 @@ | |||||||
|     <string name="max_retry_msg">Tentativas máximas</string> |     <string name="max_retry_msg">Tentativas máximas</string> | ||||||
|     <string name="title_activity_history">Histórico</string> |     <string name="title_activity_history">Histórico</string> | ||||||
|     <string name="playback_pitch">Velocidade</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="error_download_resource_gone">Não é possível recuperar esta descarga</string> | ||||||
|     <string name="rename_playlist">Mudar nome</string> |     <string name="rename_playlist">Mudar nome</string> | ||||||
|     <string name="feed_group_dialog_empty_selection">Nenhuma subscrição selecionada</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="open_in_popup_mode">Abrir no modo popup</string> | ||||||
|     <string name="black_theme_title">Preto</string> |     <string name="black_theme_title">Preto</string> | ||||||
|     <string name="all">Tudo</string> |     <string name="all">Tudo</string> | ||||||
|     <string name="yes">Sim</string> |  | ||||||
|     <string name="short_thousand">k</string> |     <string name="short_thousand">k</string> | ||||||
|     <string name="short_million">M</string> |     <string name="short_million">M</string> | ||||||
|     <string name="short_billion">MM</string> |     <string name="short_billion">MM</string> | ||||||
|   | |||||||
| @@ -90,7 +90,6 @@ | |||||||
|     <string name="black_theme_title">Negru</string> |     <string name="black_theme_title">Negru</string> | ||||||
|     <string name="popup_playing_toast">Redare în mod pop-up</string> |     <string name="popup_playing_toast">Redare în mod pop-up</string> | ||||||
|     <string name="all">Toate</string> |     <string name="all">Toate</string> | ||||||
|     <string name="yes">Da</string> |  | ||||||
|     <string name="disabled">Dezactivat</string> |     <string name="disabled">Dezactivat</string> | ||||||
|     <string name="app_ui_crash">Aplicația/UI s-a oprit</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> |     <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="black_theme_title">Чёрная</string> | ||||||
|     <string name="popup_remember_size_pos_title">Помнить параметры окна</string> |     <string name="popup_remember_size_pos_title">Помнить параметры окна</string> | ||||||
|     <string name="popup_playing_toast">Воспроизведение во всплывающем окне</string> |     <string name="popup_playing_toast">Воспроизведение во всплывающем окне</string> | ||||||
|     <string name="yes">Да</string> |  | ||||||
|     <string name="clear">Очистить</string> |     <string name="clear">Очистить</string> | ||||||
|     <string name="all">Всё</string> |     <string name="all">Всё</string> | ||||||
|     <string name="info_labels">Что:\\nЗапрос:\\nЯзык контента:\\nСтрана контента:\\nЯзык приложения:\\nСервис:\\nВремя по Гринвичу:\\nПакет:\\nВерсия пакета:\\nВерсия ОС:</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="best_resolution">Risolutzione mègius</string> | ||||||
|     <string name="clear">Isbòida</string> |     <string name="clear">Isbòida</string> | ||||||
|     <string name="disabled">Disabilitadu</string> |     <string name="disabled">Disabilitadu</string> | ||||||
|     <string name="yes">Eja</string> |  | ||||||
|     <string name="artists">Artista</string> |     <string name="artists">Artista</string> | ||||||
|     <string name="albums">Albums</string> |     <string name="albums">Albums</string> | ||||||
|     <string name="songs">Cantzones</string> |     <string name="songs">Cantzones</string> | ||||||
|   | |||||||
| @@ -87,7 +87,6 @@ | |||||||
|     <string name="short_million">M</string> |     <string name="short_million">M</string> | ||||||
|     <string name="short_billion">B</string> |     <string name="short_billion">B</string> | ||||||
|     <string name="recaptcha_request_toast">Požiadavka reCAPTCHA</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="open_in_popup_mode">Spustiť v okne</string> | ||||||
|     <string name="msg_popup_permission">Tieto práva sú potrebné pre |     <string name="msg_popup_permission">Tieto práva sú potrebné pre | ||||||
| \nprehrávanie v mini okne</string> | \nprehrávanie v mini okne</string> | ||||||
|   | |||||||
| @@ -87,7 +87,6 @@ | |||||||
|     <string name="short_thousand">k</string> |     <string name="short_thousand">k</string> | ||||||
|     <string name="short_million">mio</string> |     <string name="short_million">mio</string> | ||||||
|     <string name="short_billion">mrd</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="open_in_popup_mode">Odpri v pojavnem načinu</string> | ||||||
|     <string name="msg_popup_permission">To dovoljenje je potrebno za odpiranje  |     <string name="msg_popup_permission">To dovoljenje je potrebno za odpiranje  | ||||||
| \nv pojavnem načinu</string> | \nv pojavnem načinu</string> | ||||||
|   | |||||||
| @@ -319,7 +319,6 @@ | |||||||
|     <string name="duration_live">Toos</string> |     <string name="duration_live">Toos</string> | ||||||
|     <string name="undo">Soo celi</string> |     <string name="undo">Soo celi</string> | ||||||
|     <string name="disabled">Xidhan</string> |     <string name="disabled">Xidhan</string> | ||||||
|     <string name="yes">Haa</string> |  | ||||||
|     <string name="users">Isticmaale</string> |     <string name="users">Isticmaale</string> | ||||||
|     <string name="videos_string">Muuqaalo</string> |     <string name="videos_string">Muuqaalo</string> | ||||||
|     <string name="all">Dhammaan</string> |     <string name="all">Dhammaan</string> | ||||||
|   | |||||||
| @@ -34,7 +34,6 @@ | |||||||
|     <string name="downloads_title">Shkarkimet</string> |     <string name="downloads_title">Shkarkimet</string> | ||||||
|     <string name="error_report_title">Raporti i gabimit</string> |     <string name="error_report_title">Raporti i gabimit</string> | ||||||
|     <string name="all">Të gjitha</string> |     <string name="all">Të gjitha</string> | ||||||
|     <string name="yes">Po</string> |  | ||||||
|     <string name="missions_header_pending">Në pritje</string> |     <string name="missions_header_pending">Në pritje</string> | ||||||
|     <plurals name="feed_group_dialog_selection_count"> |     <plurals name="feed_group_dialog_selection_count"> | ||||||
|         <item quantity="one">%d i zgjedhur</item> |         <item quantity="one">%d i zgjedhur</item> | ||||||
|   | |||||||
| @@ -87,7 +87,6 @@ | |||||||
|     <string name="short_thousand">хиљ</string> |     <string name="short_thousand">хиљ</string> | ||||||
|     <string name="short_million">мил</string> |     <string name="short_million">мил</string> | ||||||
|     <string name="short_billion">млрд</string> |     <string name="short_billion">млрд</string> | ||||||
|     <string name="yes">Да</string> |  | ||||||
|     <string name="open_in_popup_mode">Отвори у искачућем режиму</string> |     <string name="open_in_popup_mode">Отвори у искачућем режиму</string> | ||||||
|     <string name="msg_popup_permission">Ова дозвола је потребна за |     <string name="msg_popup_permission">Ова дозвола је потребна за | ||||||
| \nотварање у искачућем режиму</string> | \nотварање у искачућем режиму</string> | ||||||
|   | |||||||
| @@ -58,7 +58,6 @@ | |||||||
|     <string name="downloads_title">Hämtningar</string> |     <string name="downloads_title">Hämtningar</string> | ||||||
|     <string name="error_report_title">Felrapport</string> |     <string name="error_report_title">Felrapport</string> | ||||||
|     <string name="all">Alla</string> |     <string name="all">Alla</string> | ||||||
|     <string name="yes">Ja</string> |  | ||||||
|     <string name="disabled">Inaktiverad</string> |     <string name="disabled">Inaktiverad</string> | ||||||
|     <string name="clear">Rensa</string> |     <string name="clear">Rensa</string> | ||||||
|     <string name="best_resolution">Bästa upplösningen</string> |     <string name="best_resolution">Bästa upplösningen</string> | ||||||
|   | |||||||
| @@ -73,7 +73,6 @@ | |||||||
|     <string name="all">அனைத்தும்</string> |     <string name="all">அனைத்தும்</string> | ||||||
|     <string name="playlists">ஒளிச்சரங்கள்</string> |     <string name="playlists">ஒளிச்சரங்கள்</string> | ||||||
|     <string name="users">பயனர்கள்</string> |     <string name="users">பயனர்கள்</string> | ||||||
|     <string name="yes">ஆம்</string> |  | ||||||
|     <string name="clear">அழி</string> |     <string name="clear">அழி</string> | ||||||
|     <string name="always">எப்பொழுதும்</string> |     <string name="always">எப்பொழுதும்</string> | ||||||
|     <string name="just_once">ஒரு முறை</string> |     <string name="just_once">ஒரு முறை</string> | ||||||
|   | |||||||
| @@ -52,7 +52,6 @@ | |||||||
|     <string name="downloads_title">డౌన్ లోడ్</string> |     <string name="downloads_title">డౌన్ లోడ్</string> | ||||||
|     <string name="error_report_title">లోపం నివేదిక</string> |     <string name="error_report_title">లోపం నివేదిక</string> | ||||||
|     <string name="all">అన్ని</string> |     <string name="all">అన్ని</string> | ||||||
|     <string name="yes">అవును</string> |  | ||||||
|     <string name="play_all">అన్నింటినీ ప్లే చేయండి</string> |     <string name="play_all">అన్నింటినీ ప్లే చేయండి</string> | ||||||
|     <string name="notification_channel_name">న్యూప్యాప్ నోటిఫికేషన్</string> |     <string name="notification_channel_name">న్యూప్యాప్ నోటిఫికేషన్</string> | ||||||
|     <string name="general_error">లోపం</string> |     <string name="general_error">లోపం</string> | ||||||
|   | |||||||
| @@ -104,7 +104,6 @@ | |||||||
|     <string name="tracks">แทร็ค</string> |     <string name="tracks">แทร็ค</string> | ||||||
|     <string name="users">ผู้ใช้</string> |     <string name="users">ผู้ใช้</string> | ||||||
|     <string name="events">เหตุการณ์</string> |     <string name="events">เหตุการณ์</string> | ||||||
|     <string name="yes">ใช่</string> |  | ||||||
|     <string name="disabled">ปิดการใช้งาน</string> |     <string name="disabled">ปิดการใช้งาน</string> | ||||||
|     <string name="clear">ล้าง</string> |     <string name="clear">ล้าง</string> | ||||||
|     <string name="best_resolution">ความละเอียดที่ดีที่สุด</string> |     <string name="best_resolution">ความละเอียดที่ดีที่สุด</string> | ||||||
|   | |||||||
| @@ -86,7 +86,6 @@ | |||||||
|     <string name="black_theme_title">Siyah</string> |     <string name="black_theme_title">Siyah</string> | ||||||
|     <string name="popup_playing_toast">Açılır pencere kipinde oynatılıyor</string> |     <string name="popup_playing_toast">Açılır pencere kipinde oynatılıyor</string> | ||||||
|     <string name="all">Tümü</string> |     <string name="all">Tümü</string> | ||||||
|     <string name="yes">Evet</string> |  | ||||||
|     <string name="disabled">Devre dışı</string> |     <string name="disabled">Devre dışı</string> | ||||||
|     <string name="your_comment">Yorumunuz (İngilizce):</string> |     <string name="your_comment">Yorumunuz (İngilizce):</string> | ||||||
|     <string name="error_details_headline">Ayrıntılar:</string> |     <string name="error_details_headline">Ayrıntılar:</string> | ||||||
|   | |||||||
| @@ -123,7 +123,6 @@ | |||||||
|     <string name="file">Afaylu</string> |     <string name="file">Afaylu</string> | ||||||
|     <string name="always">Ku dwal</string> |     <string name="always">Ku dwal</string> | ||||||
|     <string name="clear">Sfeḍ</string> |     <string name="clear">Sfeḍ</string> | ||||||
|     <string name="yes">Yah</string> |  | ||||||
|     <string name="artists">Inaẓuṛen</string> |     <string name="artists">Inaẓuṛen</string> | ||||||
|     <string name="songs">Tiɣennijin</string> |     <string name="songs">Tiɣennijin</string> | ||||||
|     <string name="events">Imezza</string> |     <string name="events">Imezza</string> | ||||||
|   | |||||||
| @@ -58,7 +58,6 @@ | |||||||
|     <string name="downloads_title">Завантаження</string> |     <string name="downloads_title">Завантаження</string> | ||||||
|     <string name="error_report_title">Звіт про помилку</string> |     <string name="error_report_title">Звіт про помилку</string> | ||||||
|     <string name="all">Усе</string> |     <string name="all">Усе</string> | ||||||
|     <string name="yes">Так</string> |  | ||||||
|     <string name="disabled">Вимкнено</string> |     <string name="disabled">Вимкнено</string> | ||||||
|     <string name="app_ui_crash">Збій застосунку/інтерфейсу</string> |     <string name="app_ui_crash">Збій застосунку/інтерфейсу</string> | ||||||
|     <string name="your_comment">Ваш коментар (англійською):</string> |     <string name="your_comment">Ваш коментар (англійською):</string> | ||||||
|   | |||||||
| @@ -92,7 +92,6 @@ | |||||||
|     <string name="downloads_title">ڈاؤن لوڈز</string> |     <string name="downloads_title">ڈاؤن لوڈز</string> | ||||||
|     <string name="error_report_title">خرابی کی اطلاع</string> |     <string name="error_report_title">خرابی کی اطلاع</string> | ||||||
|     <string name="all">تمام</string> |     <string name="all">تمام</string> | ||||||
|     <string name="yes">ہاں</string> |  | ||||||
|     <string name="disabled">غیر فعال</string> |     <string name="disabled">غیر فعال</string> | ||||||
|     <string name="clear">صاف</string> |     <string name="clear">صاف</string> | ||||||
|     <string name="best_resolution">بہترین ریزولوشن</string> |     <string name="best_resolution">بہترین ریزولوشن</string> | ||||||
|   | |||||||
| @@ -56,7 +56,6 @@ | |||||||
|     <string name="downloads_title">Tải xuống</string> |     <string name="downloads_title">Tải xuống</string> | ||||||
|     <string name="error_report_title">Báo lỗi</string> |     <string name="error_report_title">Báo lỗi</string> | ||||||
|     <string name="all">Tất cả</string> |     <string name="all">Tất cả</string> | ||||||
|     <string name="yes">Có</string> |  | ||||||
|     <string name="disabled">Vô hiệu</string> |     <string name="disabled">Vô hiệu</string> | ||||||
|     <string name="clear">Xóa</string> |     <string name="clear">Xóa</string> | ||||||
|     <string name="best_resolution">Độ phân giải tốt nhất</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="unsupported_url">不支持的 URL</string> | ||||||
|     <string name="settings_category_appearance_title">外观</string> |     <string name="settings_category_appearance_title">外观</string> | ||||||
|     <string name="all">全部</string> |     <string name="all">全部</string> | ||||||
|     <string name="yes">是</string> |  | ||||||
|     <string name="network_error">网络错误</string> |     <string name="network_error">网络错误</string> | ||||||
|     <plurals name="videos"> |     <plurals name="videos"> | ||||||
|         <item quantity="other">%s 个视频</item> |         <item quantity="other">%s 个视频</item> | ||||||
|   | |||||||
| @@ -86,7 +86,6 @@ | |||||||
|     <string name="black_theme_title">純黑</string> |     <string name="black_theme_title">純黑</string> | ||||||
|     <string name="popup_playing_toast">以畫中畫模式播放</string> |     <string name="popup_playing_toast">以畫中畫模式播放</string> | ||||||
|     <string name="all">所有</string> |     <string name="all">所有</string> | ||||||
|     <string name="yes">是</string> |  | ||||||
|     <string name="app_ui_crash">App/界面閃退</string> |     <string name="app_ui_crash">App/界面閃退</string> | ||||||
|     <string name="info_labels">經過:\\n請求:\\n內容語言:\\n內容國家:\\nApp 語言:\\n服務:\\nGMT 時間:\\n封裝:\\n版本:\\nOS 版本:</string> |     <string name="info_labels">經過:\\n請求:\\n內容語言:\\n內容國家:\\nApp 語言:\\n服務:\\nGMT 時間:\\n封裝:\\n版本:\\nOS 版本:</string> | ||||||
|     <string name="short_thousand">千</string> |     <string name="short_thousand">千</string> | ||||||
|   | |||||||
| @@ -61,7 +61,6 @@ | |||||||
|     <string name="downloads_title">下載</string> |     <string name="downloads_title">下載</string> | ||||||
|     <string name="error_report_title">錯誤回報</string> |     <string name="error_report_title">錯誤回報</string> | ||||||
|     <string name="all">全部</string> |     <string name="all">全部</string> | ||||||
|     <string name="yes">是的</string> |  | ||||||
|     <string name="disabled">已停用</string> |     <string name="disabled">已停用</string> | ||||||
|     <string name="clear">清除</string> |     <string name="clear">清除</string> | ||||||
|     <string name="best_resolution">最佳解析度</string> |     <string name="best_resolution">最佳解析度</string> | ||||||
|   | |||||||
| @@ -168,7 +168,6 @@ | |||||||
|     <string name="songs">Songs</string> |     <string name="songs">Songs</string> | ||||||
|     <string name="albums">Albums</string> |     <string name="albums">Albums</string> | ||||||
|     <string name="artists">Artists</string> |     <string name="artists">Artists</string> | ||||||
|     <string name="yes">Yes</string> |  | ||||||
|     <string name="disabled">Disabled</string> |     <string name="disabled">Disabled</string> | ||||||
|     <string name="clear">Clear</string> |     <string name="clear">Clear</string> | ||||||
|     <string name="best_resolution">Best resolution</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.assertFalse | ||||||
| import org.junit.Assert.assertTrue | import org.junit.Assert.assertTrue | ||||||
| import org.junit.Before |  | ||||||
| import org.junit.Test | 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.Instant | ||||||
| import java.time.ZoneId | import java.time.ZoneId | ||||||
| import java.time.format.DateTimeFormatter | import java.time.format.DateTimeFormatter | ||||||
| @@ -11,18 +12,11 @@ import kotlin.math.abs | |||||||
|  |  | ||||||
| class NewVersionManagerTest { | class NewVersionManagerTest { | ||||||
|  |  | ||||||
|     private lateinit var manager: NewVersionManager |  | ||||||
|  |  | ||||||
|     @Before |  | ||||||
|     fun setup() { |  | ||||||
|         manager = NewVersionManager() |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     fun `Expiry is reached`() { |     fun `Expiry is reached`() { | ||||||
|         val oneHourEarlier = Instant.now().atZone(ZoneId.of("GMT")).minusHours(1) |         val oneHourEarlier = Instant.now().atZone(ZoneId.of("GMT")).minusHours(1) | ||||||
|  |  | ||||||
|         val expired = manager.isExpired(oneHourEarlier.toEpochSecond()) |         val expired = isLastUpdateCheckExpired(oneHourEarlier.toEpochSecond()) | ||||||
|  |  | ||||||
|         assertTrue(expired) |         assertTrue(expired) | ||||||
|     } |     } | ||||||
| @@ -31,7 +25,7 @@ class NewVersionManagerTest { | |||||||
|     fun `Expiry is not reached`() { |     fun `Expiry is not reached`() { | ||||||
|         val oneHourLater = Instant.now().atZone(ZoneId.of("GMT")).plusHours(1) |         val oneHourLater = Instant.now().atZone(ZoneId.of("GMT")).plusHours(1) | ||||||
|  |  | ||||||
|         val expired = manager.isExpired(oneHourLater.toEpochSecond()) |         val expired = isLastUpdateCheckExpired(oneHourLater.toEpochSecond()) | ||||||
|  |  | ||||||
|         assertFalse(expired) |         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`() { |     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 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) |         assertNearlyEqual(sixHoursLater.toEpochSecond(), coerced) | ||||||
|     } |     } | ||||||
| @@ -56,7 +50,7 @@ class NewVersionManagerTest { | |||||||
|     fun `Expiry must be increased to 6 hours if below`() { |     fun `Expiry must be increased to 6 hours if below`() { | ||||||
|         val tooLow = Instant.now().atZone(ZoneId.of("GMT")).plusHours(5) |         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) |         assertNearlyEqual(tooLow.plusHours(1).toEpochSecond(), coerced) | ||||||
|     } |     } | ||||||
| @@ -65,7 +59,7 @@ class NewVersionManagerTest { | |||||||
|     fun `Expiry must be decreased to 72 hours if above`() { |     fun `Expiry must be decreased to 72 hours if above`() { | ||||||
|         val tooHigh = Instant.now().atZone(ZoneId.of("GMT")).plusHours(73) |         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) |         assertNearlyEqual(tooHigh.minusHours(1).toEpochSecond(), coerced) | ||||||
|     } |     } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 litetex
					litetex