diff --git a/app/build.gradle b/app/build.gradle
index 756d2562dc7..19f2bf79915 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -101,6 +101,10 @@ android {
androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
}
+ androidResources {
+ generateLocaleConfig = true
+ }
+
buildFeatures {
viewBinding true
compose true
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
index df38abdc54c..95b1f4164cf 100644
--- a/app/src/main/java/org/schabi/newpipe/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -186,6 +186,8 @@ protected void onCreate(final Bundle savedInstanceState) {
&& ReleaseVersionUtil.INSTANCE.isReleaseApk()) {
UpdateSettingsFragment.askForConsentToUpdateChecks(this);
}
+
+ Localization.migrateAppLanguageSettingIfNecessary(getApplicationContext());
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
index fca8c7162b0..b998b633763 100644
--- a/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/ContentSettingsFragment.java
@@ -1,10 +1,15 @@
package org.schabi.newpipe.settings;
import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Build;
import android.os.Bundle;
+import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
+import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.Preference;
import org.schabi.newpipe.DownloaderImpl;
@@ -15,13 +20,13 @@
import org.schabi.newpipe.util.image.ImageStrategy;
import org.schabi.newpipe.util.image.PreferredImageQuality;
+import java.util.Locale;
+
import coil3.SingletonImageLoader;
public class ContentSettingsFragment extends BasePreferenceFragment {
private String youtubeRestrictedModeEnabledKey;
- private Localization initialSelectedLocalization;
- private ContentCountry initialSelectedContentCountry;
private String initialLanguage;
@Override
@@ -30,12 +35,28 @@ public void onCreatePreferences(final Bundle savedInstanceState, final String ro
addPreferencesFromResourceRegistry();
- initialSelectedLocalization = org.schabi.newpipe.util.Localization
- .getPreferredLocalization(requireContext());
- initialSelectedContentCountry = org.schabi.newpipe.util.Localization
- .getPreferredContentCountry(requireContext());
initialLanguage = defaultPreferences.getString(getString(R.string.app_language_key), "en");
+ if (Build.VERSION.SDK_INT >= 33) {
+ requirePreference(R.string.app_language_key).setVisible(false);
+ final Preference newAppLanguagePref =
+ requirePreference(R.string.app_language_android_13_and_up_key);
+ newAppLanguagePref.setSummaryProvider(preference -> {
+ final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0);
+ if (customLocale != null) {
+ return customLocale.getDisplayName();
+ }
+ return getString(R.string.systems_language);
+ });
+ newAppLanguagePref.setOnPreferenceClickListener(preference -> {
+ final Intent intent = new Intent(Settings.ACTION_APP_LOCALE_SETTINGS)
+ .setData(Uri.fromParts("package", requireContext().getPackageName(), null));
+ startActivity(intent);
+ return true;
+ });
+ newAppLanguagePref.setVisible(true);
+ }
+
final Preference imageQualityPreference = requirePreference(R.string.image_quality_key);
imageQualityPreference.setOnPreferenceChangeListener(
(preference, newValue) -> {
@@ -70,19 +91,21 @@ public boolean onPreferenceTreeClick(final Preference preference) {
public void onDestroy() {
super.onDestroy();
- final Localization selectedLocalization = org.schabi.newpipe.util.Localization
- .getPreferredLocalization(requireContext());
- final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization
- .getPreferredContentCountry(requireContext());
final String selectedLanguage =
defaultPreferences.getString(getString(R.string.app_language_key), "en");
- if (!selectedLocalization.equals(initialSelectedLocalization)
- || !selectedContentCountry.equals(initialSelectedContentCountry)
- || !selectedLanguage.equals(initialLanguage)) {
- Toast.makeText(requireContext(), R.string.localization_changes_requires_app_restart,
- Toast.LENGTH_LONG).show();
-
+ if (!selectedLanguage.equals(initialLanguage)) {
+ if (Build.VERSION.SDK_INT < 33) {
+ Toast.makeText(
+ requireContext(),
+ R.string.localization_changes_requires_app_restart,
+ Toast.LENGTH_LONG
+ ).show();
+ }
+ final Localization selectedLocalization = org.schabi.newpipe.util.Localization
+ .getPreferredLocalization(requireContext());
+ final ContentCountry selectedContentCountry = org.schabi.newpipe.util.Localization
+ .getPreferredContentCountry(requireContext());
NewPipe.setupLocalization(selectedLocalization, selectedContentCountry);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/Localization.java b/app/src/main/java/org/schabi/newpipe/util/Localization.java
index f8e2df99f95..5720ed7adbb 100644
--- a/app/src/main/java/org/schabi/newpipe/util/Localization.java
+++ b/app/src/main/java/org/schabi/newpipe/util/Localization.java
@@ -12,12 +12,15 @@
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.DisplayMetrics;
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes;
+import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.math.MathUtils;
+import androidx.core.os.LocaleListCompat;
import androidx.preference.PreferenceManager;
import org.ocpsoft.prettytime.PrettyTime;
@@ -39,6 +42,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
+import java.util.Objects;
import java.util.stream.Collectors;
@@ -63,6 +67,7 @@
*/
public final class Localization {
+ private static final String TAG = Localization.class.toString();
public static final String DOT_SEPARATOR = " • ";
private static PrettyTime prettyTime;
@@ -101,6 +106,10 @@ public static Locale getPreferredLocale(@NonNull final Context context) {
}
public static Locale getAppLocale(@NonNull final Context context) {
+ if (Build.VERSION.SDK_INT >= 33) {
+ final Locale customLocale = AppCompatDelegate.getApplicationLocales().get(0);
+ return Objects.requireNonNullElseGet(customLocale, Locale::getDefault);
+ }
return getLocaleFromPrefs(context, R.string.app_language_key);
}
@@ -422,4 +431,32 @@ private static String getQuantity(@NonNull final Context context,
final int safeCount = (int) MathUtils.clamp(count, Integer.MIN_VALUE, Integer.MAX_VALUE);
return context.getResources().getQuantityString(pluralId, safeCount, formattedCount);
}
+
+ public static void migrateAppLanguageSettingIfNecessary(@NonNull final Context context) {
+ // Starting with pull request #12093, NewPipe on Android 13+ exclusively uses Android's
+ // public per-app language APIs to read and set the UI language for NewPipe.
+ // If running on Android 13+, the following code will migrate any existing custom
+ // app language in SharedPreferences to use the public per-app language APIs instead.
+ if (Build.VERSION.SDK_INT >= 33) {
+ final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
+ final String appLanguageKey = context.getString(R.string.app_language_key);
+ final String appLanguageValue = sp.getString(appLanguageKey, null);
+ if (appLanguageValue != null) {
+ sp.edit().remove(appLanguageKey).apply();
+ final String appLanguageDefaultValue =
+ context.getString(R.string.default_localization_key);
+ if (!appLanguageValue.equals(appLanguageDefaultValue)) {
+ try {
+ AppCompatDelegate.setApplicationLocales(
+ LocaleListCompat.forLanguageTags(appLanguageValue)
+ );
+ } catch (final RuntimeException e) {
+ Log.e(TAG, "Failed to migrate previous custom app language "
+ + "setting to public per-app language APIs"
+ );
+ }
+ }
+ }
+ }
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/potoken/JavaScriptUtil.kt b/app/src/main/java/org/schabi/newpipe/util/potoken/JavaScriptUtil.kt
index a9169e2c6d4..06740a00e61 100644
--- a/app/src/main/java/org/schabi/newpipe/util/potoken/JavaScriptUtil.kt
+++ b/app/src/main/java/org/schabi/newpipe/util/potoken/JavaScriptUtil.kt
@@ -17,7 +17,7 @@ fun parseChallengeData(rawChallengeData: String): String {
val descrambled = descramble(scrambled.getString(1))
JsonParser.array().from(descrambled)
} else {
- scrambled.getArray(1)
+ scrambled.getArray(0)
}
val messageId = challengeData.getString(0)
diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
index 77d270c0552..9722a9a1ffc 100644
--- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
+++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
@@ -71,6 +71,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
+import java.util.Date;
+import java.util.Locale;
+import java.text.DateFormat;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Observable;
@@ -208,11 +211,17 @@ public void onBindViewHolder(@NonNull ViewHolder view, @SuppressLint("RecyclerVi
h.pause.setTitle(mission.unknownLength ? R.string.stop : R.string.pause);
updateProgress(h);
mPendingDownloadsItems.add(h);
+
+ h.date.setText("");
} else {
h.progress.setMarquee(false);
h.status.setText("100%");
h.progress.setProgress(1.0f);
h.size.setText(Utility.formatBytes(item.mission.length));
+
+ DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault());
+ Date date = new Date(item.mission.timestamp);
+ h.date.setText(dateFormat.format(date));
}
}
@@ -832,6 +841,7 @@ class ViewHolderItem extends RecyclerView.ViewHolder {
ImageView icon;
TextView name;
TextView size;
+ TextView date;
ProgressDrawable progress;
PopupMenu popupMenu;
@@ -862,6 +872,7 @@ class ViewHolderItem extends RecyclerView.ViewHolder {
name = itemView.findViewById(R.id.item_name);
icon = itemView.findViewById(R.id.item_icon);
size = itemView.findViewById(R.id.item_size);
+ date = itemView.findViewById(R.id.item_date);
name.setSelected(true);
diff --git a/app/src/main/res/layout/mission_item.xml b/app/src/main/res/layout/mission_item.xml
index 5338949aa4a..c864f60f010 100644
--- a/app/src/main/res/layout/mission_item.xml
+++ b/app/src/main/res/layout/mission_item.xml
@@ -82,6 +82,18 @@
android:textColor="@color/white"
android:textSize="12sp" />
+
+
diff --git a/app/src/main/res/layout/mission_item_linear.xml b/app/src/main/res/layout/mission_item_linear.xml
index ce2d1af4be9..6288e47591f 100644
--- a/app/src/main/res/layout/mission_item_linear.xml
+++ b/app/src/main/res/layout/mission_item_linear.xml
@@ -62,6 +62,18 @@
android:textSize="12sp"
android:textStyle="bold" />
+
+
playback_skip_silence_key
app_language_key
+ app_language_android_13_and_up_key
feed_update_threshold_key
300
diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml
index 2cdc6c545c9..f17783a2285 100644
--- a/app/src/main/res/xml/content_settings.xml
+++ b/app/src/main/res/xml/content_settings.xml
@@ -13,6 +13,13 @@
app:iconSpaceReserved="false"
app:useSimpleSummaryProvider="true" />
+
+